# Easy RM to MP3 Converter(2.7.3.700)栈溢出漏洞调试笔记

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2014-10-13T09:41:00Z
Canonical: https://wepostx.com/topics/591

> 乌云历史资料归档
>
> **原始作者：** Debug_Orz
> **原始编号：** superkieran-wooyundrops:362
> **原始发布时间：** 2014-10-13 17:41
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

## 0x00 基础知识

### 1 Windows环境
选取wmplayer.exe程序运行时的内存布局示意，包括栈，堆，加载模块（DLLs）和可执行文件本身。

![原文图片](/media/2016/06/32f5196bda330b5c6fdbe3420ca0ae10)

Win32进程空间布局示意。

![原文图片](/media/2016/06/b393a88ccf0e99e45322e7600a86c93e)

#### 1.2 入口点（Entrypoint）
运行一个EXE的时候，会先根据IAT表加载相应的DLL，并且用GetProcAddress得到API的真实地址。也就是说EXE运行后，DLL的EP将是第一个被调用的地方，而EXE本身的EP应该是最后被调用的，但它是EXE本身代码的入口。
不同系统平台下反汇编结果。本节程序选取《逆向工程核心原理》一书中的HelloWorld.exe进行调试示意。
Windows Server 2008下反汇编结果（存在地址随机化，相同的exe程序，载入调试器后入口地址与XP下不同）。

![原文图片](/media/2016/06/0182cc8ee1ff16190ab20759acd4ac0c)

Windows XP下反汇编结果。

![原文图片](/media/2016/06/2640907f00dfe209303986fbbe50e108)

F7 Step into。

![原文图片](/media/2016/06/e9cb2e7aec2e09a7037b3a7287ee135d)

004027A1地址处的RETN指令，用于返回到函数调用者的下一条指令（弹出ESP内容到EIP，然后跳转，0012FFC0处的值是004011A5，小端排序），一般是被调用函数的最后一句，即返回004011A5（JMP 0040104F）

![原文图片](/media/2016/06/251b890e131bd17a1f4ea4fb8e530246)

![原文图片](/media/2016/06/49da749ca1c0d4d78d481d9661dc3a58)

EIP值为004011A5，ESP指针继续向下移动，ESP从0012FFC0指向0012FFC4（ESP+4）。

![原文图片](/media/2016/06/db97cbabfc29c4ea3999362d4297f545)

不同的开发工具生成的启动函数不同。同一种开发工具，产生的启动函数也随版本的不同而不同。

![原文图片](/media/2016/06/a48a47c27396613428aac142027ecf45)

Call 00402524

![原文图片](/media/2016/06/074c8d41a222e8216a9cd14fc4b024be)

#### 1.3 函数调用步骤示意
过程描述汇编代码
参数入栈
返回地址入栈
代码区跳转
栈帧调整push 参数3
push 参数2
push 参数1
call (相当于push+jmp)
push ebp
mov ebp esp
sub esp,xxx

#### 1.4 函数返回步骤示意
过程描述汇编代码
栈回收
把ESP所指向内容弹出到EBP（相当于保存的上一栈的EBP弹出）
返回地址弹入EIP并JumpADD ESP,XXX
POP EBP
RETN （相对于POP+JUMP）
函数栈帧示意。

![原文图片](/media/2016/06/c70644812cdc849ba4624a4c69606691)

![原文图片](/media/2016/06/f31a0529745bc6f0d758b905ec9e7764)

### 1.5 栈溢出基础知识

#### 1.5.1 原理示意
栈溢出就是缓冲区溢出的一种。 由于缓冲区溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。程序在运行过程中，为了临时存取数据的需要，一般都要分配一些内存空间，通常称这些空间为缓冲区。如果向缓冲区中写入超过其本身长度的数据，导致缓冲区无法容纳，就会造成缓冲区以外的存储单元被改写，这种现象就称为缓冲区溢出。栈溢出原理示意入下图。

![原文图片](/media/2016/06/53ffca80c77d91fb63909eb3b3c83cdd)

![原文图片](/media/2016/06/c8267dc062a1941c6b71bfd3727271ba)

![原文图片](/media/2016/06/afb47d2de7e081d91d9b883920203330)

![原文图片](/media/2016/06/fcce131b740f6e91dbb487a9de13919b)

#### 1.5.2 程序调试
调试带有字符串拷贝的简单程序示意。程序可以用Dev-C++ 4.9.9.2编译，小巧简单。程序代码如下。

```text
#!cpp #include <string.h> void do_something(char *Buffer) { char MyVar[128]; strcpy(MyVar,Buffer); } int main (int argc, char **argv) { do_something(argv[1]); }
```

![原文图片](/media/2016/06/e67880ce0ab971d82c4f6c729e802740)

调用函数，CALL function.00401290。

![原文图片](/media/2016/06/78e432ad39c65c53aab0896834524a97)

函数返回后的下一条执行地址压栈，在栈中可以看到004012EA，函数跳转到00401290。

![原文图片](/media/2016/06/624a5197e07c7f5c91482813aee300d7)

进入子函数，PUSH EBP，EBP入栈。

![原文图片](/media/2016/06/f0da3798561834cbf339184e15f8da87)

MOV EBP ESP 改变栈底，让EBP指向ESP，EBP的内容（0x0022FF78）指向了前一个栈帧，所以[EBP+4]=004012EA。

![原文图片](/media/2016/06/19b439fdef85775af4b63996b7ef4ff8)

开辟栈存储空间：SUB ESP,0x98

![原文图片](/media/2016/06/987288c68d33bcf5f34a2322b48046b4)

根据调试截图可知，如果[Buffer]的大小大于0x98字节，strcpy()函数将会覆盖保存的EBP（saved EBP）和保存的EIP（saved EIP），覆盖过程示意如下。

![原文图片](/media/2016/06/848cef5d62510028f3e7b5a819c1cf7b)

## 0x01 漏洞调试

#### 2.1 漏洞测试环境
程序描述备注
Easy RM to MP3 Converter 2.7.3.700存在栈溢出漏洞的软件
Windows XP Pro SP3 En MSDN VL（不打任何补丁，关闭DEP）模拟受害端
Kali-linux-1.0.9-i386模拟攻击端
Windbg 6.12.0002.633 X86
Python 2.7.7
Immunity Debugger 1.85

### 2.2 Easy RM to MP3 Converter栈溢出调试过程
利用Perl和Python可以生成不同的m3u文件、POC进行测试。

![原文图片](/media/2016/06/568066b47ab47de1f899d71ca6c8051c)

打开Easy RM to MP3 Converter，加载具有10000个字符A的crash.m3u无效文件，我们发现目标软件捕获了该错误，跳出友好提示。 程序抛出一个错误，但是看起来这个错误被程序异常处理例程捕捉到了，程序并没崩掉。

![原文图片](/media/2016/06/fa54b03992684b8554101a347ad72302)

调整字符个数，继续运行，目标软件在20000和30000之间可以崩溃掉。很明显，EIP 0x41414141是crash.m3u中的数据，说明程序返回地址被覆盖，EIP跳转到0x41414141，但找不到可执行的指令，所以报错。同时，从图中可以看出程序的EIP也可以被我们填充成一个指向恶意代码的地址。

![原文图片](/media/2016/06/40b52dfb8763b96cfce6e7a49fdd9494)

![原文图片](/media/2016/06/614cf01348f8b490d46b2783f4618e32)

如果使用二分法。用25000个A和5000个B填充m3u文件，如果EIP变为41414141 （AAAA）。那么返回地址就位于20000到25000之间。 如果EIP变为42424242 （BBBB）那么返回地址就位于25000到30000之间。

![原文图片](/media/2016/06/d9ed5420a9541589ff252f47a751d02b)

使用25000A+5000B，可以看到EIP为42424242（BBBB），所以返回地址位于25000到30000之间了。

![原文图片](/media/2016/06/22bee974d0a7bf34ecda580a3a97e6ee)

根据调试信息，返回返回地址为42424242，说明ESP指向的返回地址已经弹出到EIP，则在内存中，栈顶指针ESP会指向EIP的下一个位置，如上图所示。 查看esp中的数据 d esp。

![原文图片](/media/2016/06/070d60010dc1780ac435acb529dd968c)

寻找存放shellcode的地址空间原理。
根据函数调用的堆栈平衡原理，缓冲区溢出之后，ESP应该停留在函数（这里假设为：XXCopy）调用之前所在位置上。也就是说，覆盖完EIP之后继续填充的数据都将保存在ESP所指地址中。

![原文图片](/media/2016/06/f74c042cd34bdee955a1e991ead2b527)

我们用BBBB重写了EIP和可以看到ESP所指的缓冲区。在我们调整脚本之前，需要精确的定位出来返回地址在缓冲区的位置，因为如果填充的都是AAAABBBB之类的，区分度不够高。调用metasploit中自带的工具。

```text
[email protected] :/opt/metasploit/apps/pro/msf3/tools# ./pattern_create.rb 5000
```

![原文图片](/media/2016/06/5690bc5730b433948ad6e7a0491cc29b)

![原文图片](/media/2016/06/26b19c47d0858429ac612ee9ebed77fc)

![原文图片](/media/2016/06/29964cee413d66618edd778eb95f7099)

![原文图片](/media/2016/06/13a4e377e8642c9cd43cd0c02e815e8e)

重写EIP前面需要覆盖的缓冲区长度。创建一个文件，填充25000+1069个A，再加4个B，EIP应该就会被重写成为42424242。

![原文图片](/media/2016/06/49d0ab91d8bb49c77963798559df28ca)

![原文图片](/media/2016/06/ede1eecaa345200fde694fb8230d1329)

d esp

![原文图片](/media/2016/06/fbf4273803d1efcf509f9c7ec4aa5762)

26061+4+4 = 26069
BufferEBPEIPESP 指向位置
A（x 26061）AAAABBBBCCCCCCCCCCCCCCCCCCCCC
414141414141414141414141...41414141414242424242
26061 bytes4bytes4bytes

![原文图片](/media/2016/06/18b8574f74d7dbe72d92177f0a6a62d7)

当函数返回，BBBB被置入EIP中（pop ebp，retn）,所以流程尝试到地址0x42424242（BBBB）执行。找内存空间存放我们的shellcode。
为了让应用程序崩溃，我们已经向内存中写入了26069 个A，我们已经向保存的EIP存储空间写入了一个新的值（函数返回执行时，RET将弹出并跳转到这个值），我们已经写了一堆的字符C。当应用程序崩溃时，可以查看所有这些寄存器（D ESP，D EAX，D EBX，D EBP，...）。如果你能在这些寄存器中的一个，看到缓冲区里的值（无论是A，还是C），那么你或许可以用shellcode取代它们的值，并跳转到该位置。在我们的例子中，我们可以看到，ESP似乎指向我们的C，所以理想情况下，我们会用实际的shellcode取代C，告诉EIP跳转到ESP的地址。

![原文图片](/media/2016/06/454b43acb54bf812573b7d32b593843c)

![原文图片](/media/2016/06/ef2116d651548e405302cf980d77efb6)

![原文图片](/media/2016/06/8c79df7aef6d2908d65bbdf62cd9a353)

直接跳到一个内存地址不是一个好的方法（000ff730包含了字符串终止符（NULL： 00） ...所以你看到来自缓冲区第一部分的字母A...我们无法到达重写EIP后我们的数据了....另一方面，在Exploit使用内存地址直接跳转是非常不可靠的...因为内存地址会因为系统版本，语言等的不同而不同）

![原文图片](/media/2016/06/f834a210dca15c44ee890faf8c3e5897)

windbg中输入a，然后再输入jmp esp ，报错，直接回车，返回命令输入界面。然后u jmp esp之前的地址。

![原文图片](/media/2016/06/4400fd13941cb4271a260758b4790928)

在地址7c90120e，你可以看到ffe4。这是操作码JMP ESP
现在，我们需要在这些加载的DLL中的某一个，找到这个操作码（opcode）。
查看WinDbg窗口，可以容易找到属于Easy RM to MP3应用程序的DLL。

![原文图片](/media/2016/06/3a5156f12d20874e08d6e7a48a2ef5c0)

如果我们能够在这些DLL中找到一个操作码，那么我们就可以在Windows平台上制作可靠的漏洞利用程序。
如果我们使用属于操作系统的DLL，那么我们可能会发现，漏洞利用程序在其他版本的操作系统上无法正常工作。
因此，我们在C:\Program Files\Easy RM to MP3 Converter\MSRMCcodec02.dll中搜索操作码。
此DLL在地址01c20000和020ed000之间加载。搜索操作码FF E4。

```text
s 01c20000 020ed000 ff e4
```

![原文图片](/media/2016/06/73e5e35bc701dd2e56def15a420e6069)

当选择一个地址时，寻找空字节是很重要的。
你应该尽量避免使用地址中含有空字节。空字节将成为一个字符串结束符，那么缓冲区数据其余的部分将变得不可用。

```text
s 70000000 l fffffff ff e4
```

![原文图片](/media/2016/06/4c99ffbddbab1e9c565f302c466f38dd)

因为我们希望把我们的shellcode放入ESP中（在覆盖的EIP之后放置载荷payload），从列表中选出的JMP ESP地址空间中不能有NULL字节。
空字节作为一个字符串结束，因此所有在它后面的内容会被忽略。
我们在覆盖的EIP之后放置我们的shellcode，这个地址不能包含空字节。
第一个地址起作用的地址是0x01ddf23a。
输入命令可以验证这个地址是否含有jmp esp（在地址01ccf23a处反汇编指令）

```text
u 01ddf23a
```

![原文图片](/media/2016/06/8cc33b1eea0585f1b9691a21fc32f444)

## 0x02 漏洞利用

3.1 弹计算器
如果我们用0x01ccf23a 覆盖EIP，那么 jmp esp将会被执行。esp 包含了shellcode，所以我们就有了一个可用的exploit。首先我们可以用带有“NOP & break”的shellcode测试下。用Windbg调试，软件打开m3u文件。

![原文图片](/media/2016/06/a4d2b40d5a3961b296a829511139ccd3)

再次运行程序，用windbg附加，运行，打开新的m3u文件。程序将会在地址000ff745暂停。那么说明jmp esp起作用了。esp开始于 000ff730, 它含有NOPs 指令直到 000ff744。
现在添加真实的shellcode，并开发exploit。再次打开，弹出计算器。

![原文图片](/media/2016/06/7e54d0c801a13cf2f6e471a472407d45)

![原文图片](/media/2016/06/45626aecdff2c9de822acb0f7db9d85f)

#### 3.2 绑定端口
利用msfpaload生成shellcode，绑定某端口，例如8888。

![原文图片](/media/2016/06/53754d9a78aaeacaae9f8659ef75482e)

Shellcode执行成功后，如果没有设置过防火墙，windows防火墙弹出拦截提示，unblock允许。

![原文图片](/media/2016/06/64b4aca7c52f3d0b176a490aa064922b)

netstat –ano 查看网络连接情况，可以看到，打开了8888端口，查看进程号PID为388，程序为EasyRMtoMP3Converter.exe。

![原文图片](/media/2016/06/1ed0828e44097afac5d03d9a0bbe7642)

![原文图片](/media/2016/06/c47bdf1bab789afcff09bede4994f27e)

在Windows防火墙打开的情况下，ping不通，如果防火墙允许8888端口通信，telnet可以连接。

![原文图片](/media/2016/06/21cce0cffa059ddf5238935f4a5df9ca)

查看网络状态，与windows机器上显示一致。

![原文图片](/media/2016/06/abddb4a09f528609336a93775ab48654)

## 0x03 问题说明

描述备注
Metasploit每次生成不同的shellcode的不同输出。所以，如果你看自己的机器上每次看到不同的shellcode，则不必惊慌。需要去掉坏字符 \x00\x0a\x0d\x1a
使用相同的命令，不同的系统，默认的编码器可能不同msfpayload windows/shell_bind_tcp LPORT=8888 R | msfencode -b '\x00\x0a\x0d\x1a' -t perl
Kali系统中metasploit默认所使用的编码器与BackTrack不一致。参考文献上使用的是Backtrack系统，其metasploit使用的默认编码器是x86/shikata_ga_nai，而Kali在不指定参数的情况下，使用的默认编码器是cmd/powershell_base64
Backtrack默认编码器x86/shikata_ga_nai
Backtrack系统metasploit所生成的shellcode
size 368

![原文图片](http://static.wooyun.org/drops/20141013/image116.png)

Kali默认编码器cmd/powershell_base64
Kali系统metasploit所生成的shellcode
size 985
![原文图片](http://static.wooyun.org/drops/20141013/image118.png)

使用Kali默认编码器生成的shellcode，程序执行后崩溃。
![原文图片](http://static.wooyun.org/drops/20141013/image120.png)

为了生成可绑定端口的的shellcode，在Kali系统中需要指定编码器
msfencode -e x86/shikata_ga_nai
![原文图片](http://static.wooyun.org/drops/20141013/image122.png)

参考文献 1) https://www.google.com 2) https://www.corelan.be/index.php/2009/07/19/exploit-writing-tutorial-part-1-stack-based-overflows/ 3) https://www.corelan.be/index.php/forum/exploit-writing-debuggers/error-when-executed-u-unassemble-followed-by-the-address-that-was-shown-before-entering-jmp-esp/ 4) http://blog.csdn.net/yuzl32/article/details/6126592 5) http://extreme-security.blogspot.com/2013/02/stack-overflows-part-2-executing.html 6) http://cstriker1407.info/blog/a-reading-notes-of-the-devils-training-camp-msfpayload-using-the-tool-and-free-to-kill/ 7) http://www.securitysift.com/windows-exploit-development-part-1-basics/ 8) 《逆向工程核心原理》

## Replies
