6. 原版zlib溢出
这个测试需要用到原版winmugen,winmugen最初是官方给捐献者私有的2人限制版,经过2004年无限制破解,以及2007年的多轮高分辨率破解,得到现在常用的Win Mugen Plus。但还有一种国内流行的星光主程序,其zlib和原版winmugen不一样,导致zlib溢出没有那么通杀。我前的文章全部使用星光主程序测试,现在替换成原版winmugen。
https://www.infinitymugenteam.com/infinity.wiki/mediawiki/index.php?title=M.U.G.E.N
zlib溢出常见的是两个人物模板,一个是五月雨的,一个是遥远的。
遥远的血神模板打开比较直接。
但五月雨的就没那么直接,上调试(换x32dbg了),根据前人研究,断点422438
可以看得出来,此时已经完成溢出,422438是栈溢出中覆盖了ret的地址,和之前Reboot用过的403246一样是个add esp;ret。看栈可以看得出来,接下来会跳到ALLEG40.DLL上的10078CEF上。跟进之后发现就是jmp esp。
跟我们之前用mona找到的1005FB9D(call esp)效果一样都是进栈执行。
血神的则是直接ret到10078D87(也是jmp esp),血神压缩文件符合010Editor的zip模板,比较方便我们定位溢出点在哪里,是deFileTime+deFileDate。
同理五月雨模板可以推算出来,他用的溢出点在deCompressedSize上。
接下来主要研究血神,断点10078D87,在栈上找到一个最近的返回地址418BE1,这个就是离栈溢出比较近的代码。
其位于sub_418B90(),似乎是个文件读取函数,非常像溢出点,断点下在418BDC(call
sub_418A70)上。
418BDC,F8之后完成栈溢出,直接跳到10078D87,那么证实栈溢出确实发生在sub_418A70()。
那么断点418A70,一点一点动态调试,最终搞清楚整个栈溢出流程。从最外层函数到最内层函数如下。
sub_40CF80()——0x40D8F9 call sub_418B90
sub_418B90()——0x418A70 call sub_418A70
sub_418A70()——0x418ACE call sub_4854E0
sub_4854E0()——0x48550A call sub_485520
sub_485520()——0x485D0E rep movsd(即qmemcpy,发生栈溢出)
sub_418A70()——0x418B2A call sub_486510
sub_486510()——0x4866E4 ret(跳转到jmp esp)
两张图概括如下
485D0E rep movsd(qmemcpy)为什么会导致栈溢出呢?rep movsd的意思很简单,就是从ESI复制内容到EDI上去(复制ECX个字节)。在发生栈溢出前,也有两次正常的rep movsd,我们可以看内存变化。
rep movsd前
rep movsd后
可以清楚的看到92C8120上被改写了,也就是说它将栈上的值复制到内存中,这也正是qmemcpy()的主要功能。然而在发生问题的rep movsd上,EDI居然也是栈上的地址,那么就是从栈复制到栈上。
再往前看edi的赋值汇编就会发现EDI本质上是a2,也就sub_485520()的第二个参数造成的。
00485CFD 8B7C24 78 mov edi,dword ptr ss:[esp+78]
那为什么之前两次rep movsd没问题呢?因为它们的调用链是这样的sub_418A70()——sub_485F00()——sub_485520(),结构图如下。
只有sub_4854E0(),将栈上的v6当成第二个参数传入,导致了最后栈溢出的发生。
如何从零制作zlib溢出包呢?根据遥远的提示,他是将正常人物包进行仅存储的压缩模式,然后发现mugen崩溃发现的。看血神模板也可以看得出来,整个人物本质上就是一个写了shellcode/0/空格的def,然后仅存储压缩,
那么我们将正常的kfm.def改为test.def,加上大概40KB左右的空格,然后单独将def仅存储压缩,断点004866E4。
这样非常轻松就获取了栈溢出的偏移量,将ZIPDIRENTRY dirEntry的deFileTime/deFileDate修改为我们自己找到的1005FB9D (call esp)。成功跳转,栈上执行。
那么如何执行shellcode呢?因为zip模板限制,接下来CRC等空间显然不够填充所有的shellcode。我们可以学习Reboot的思路,跳转到一个存储test.def自己文本的地方。而这个地方就在栈上。
那么CRC应该填充的东西就呼之欲出了。sub esp,0x20;ret(83EC20C3)
这也正是血神的利用方式,但是如果我们这样做的话,会发现溢出不成功了,会直接在40DAF1(repne scasb)抛出错误。
这个错误很有可能是因为CRC校验的问题,参考血神和五月雨模板的做法,是同时修改了ZIPFILERECORD record和ZIPDIRENTRY dirEntry两处的FileTime/FileDate/Crc为9DFB051083EC20C3,即可成功实现完美栈布局。
那么接下来要做的就是将test.def前面的那些注释替换成shellcode。注意原版kfm.def的那些注释不够填充220长度的shellcode,直接填充会覆盖到name导致报错,所以需要先加上220+的A,重新压缩,在010editor中手动替换成shellcode。
msfvenom -p windows/exec cmd="calc.exe" exitfunc=thread -b "\x00" -o calc.bin
但这样最终还是无法执行shellcode,动态调试一下,很容易发现我们sub esp,0x20;ret这一步错了,跳到其他地方去了。
929D550才是我们需要跳的地方,多跳了4位。但从929D550存储的0x20可以看得出来那是我们写入的非常多的空格,也可以往里面放shellcode。
当然,最好还是改成sub esp,0x1C;ret(83EC1CC3)
最终效果如下。