漏洞分析——ASX to MP3 Converter本地代码执行漏洞

对于软件ASX-to-MP3-Converter中存在的栈溢出漏洞的分析。

参考自:https://whereisk0shl.top/post/2016-11-04

1.背景知识

A.关于shellcode

(1)shellcode在内存中的放置

  1. shellcode放在栈帧内,shellcode起始地址在栈帧内,于是对上层栈帧的影响就会极小(EBP,返回地址受影响),不过当栈帧地址随机变化时,不易定位到shellcode,此时可通过利用相关寄存器间接定位到shellcode(如可能通过动态调试发现每次shellcode的起始地址都会变化,但是始终等于EDI-固定的偏移,注意这里的偏移如果包含可能造成截断的00字符,可通过先加后减两步操作,来避免shellcode出现\00字符,先减后加会造成越界,所以要先加后减)

  1. shellcode放在上层栈帧,它的起始地址在ret后4字节,此时ESP刚好指向此,可以在ret处写入jmp esp等类似地址,转到shellcode,不过这破坏了上层帧,对程序影响较大。
  2. 抬高栈顶保护,在A组织方式中,shellcode所在区域逻辑上已经被收回了,若程序再次使用栈,将会覆盖掉shellcode,于是可以将栈顶抬高(ESP-n)以保护shellcode。
  3. 空指针滑行,就是在shellcode前加大量的Nop类指令,即使未精确跳转到目标位置也可以在滑行一段时间后转到shellcode处。
  4. 增加大量的ret,即使shellcode长度不精确,一大片返回地址也能增加命中率,至于地址对齐,必要时可以使用字节相同的双字地址,与其他技术相结合。

(2)如何寻找合适的jmp esp地址

方法1

在内存中寻找地址固定的系统模块,在模块中寻找 JMP ESP 指令的地址跳转,以mona.py脚本为例,首先在内存中挑选合适的模块,输入命令!mona modules如下图——

主要关注红框四点。

  • rebase是重启后地址要变化吗?选false的。
  • 然后接着两点是是否有保护机制,也要选false的(DEP:阻止代码从数据页被执行,ASLR:随机内存地址加载执行程序和DLL,每次重启地址变化)。
  • OSDLL:是否是系统自带的,要选择true。

一般的PE文件都会加载kernel32.dll,user32.dll,ntdll.dll等动态链接库,所以我们不必要调用LoadLibrary(“kernel32.dll”),不过当我们的ShellCode需要使用到其他动态链接库的时候,如ws2_32.dll等,LoadLibrary()函数是必须的。

方法2

可参考《0day安全:软件漏洞分析技术》3.2.2节中的代码如下——

#include <stdio.h>
#include <windows.h>
#define DLL_NAME "user32.dll"
int main()
{
    BYTE *ptr;
    int position,address;
    HINSTANCE handle;
    BOOL done_flag=FALSE;
    handle=LoadLibrary(DLL_NAME);
    if(!handle)
    {
        printf("load dll error");
        return 0;
    }
    ptr=(BYTE *)handle;
    for(position=0;!done_flag;position++)
    {
        try
        {
            if(ptr[position]==0xFF &&ptr[position+1]==0xE4)
            {
                int address=(int)ptr+position;
                printf("OPCODE found at 0x%x\n",address);
            }
        }
        catch(...)
        {
        int address=(int)ptr+position;
        printf("END OF 0x%x\n",address);
        done_flag=true;
        }
    }
    return 0;
}

(3)处理shellcode的坏字符

  1. 寻找坏字符

除了null byte (0x00) 空字符,用于终止字符串的拷贝操作会导致shellcode的中断,每个应用程序(由于协议和漏洞的不同)都可能会有自己特有的坏字符。可以通过如下图所示方式查找可能的坏字符。

  1. 处理坏字符

(1)对shellcode进行编码,常见的编码就是异或后发送,运行前再次异或解码,如如下结构的shellcode:填充数据(长度44)+偏移长度+jmp esp的机器码+解码器+加密的弹框shellcode+结束字符
(2)通过以下方式精简shellcode。

  • 挑选短指令
  • 使用复合指令
  • 巧用内存
  • 代码当数据复用
  • 调整栈顶再次利用栈
  • 巧用寄存器
  • 使用hash

B.关于堆栈

  • 栈空间存储了用户程序的函数栈帧(包括参数、局部数据等),实现函数调用机制,它的数据增长方向是低地址方向。
  • 堆空间存储了程序运行时动态申请的内存数据等,数据增长方向是高地址方向。

2.漏洞复现

A.触发崩溃

运行POC生成exploit.m3u文件,用ASX-to-MP3-Converter打开,应用程序闪退。

poc = "\x41" * 50000

rst = open("exploit.m3u",'w')
rst.write(poc)
rst.close();

利用Windbg捕获应用程序崩溃场景如下图,可以看出此时eip已经被覆盖成了0x41414141,并由此引发了应用程序的崩溃。

B.定位崩溃代码

根据上图中所示的报错信息,先定位到00430402处的代码,并以此为入手点,动态调试分析,先根据参数传递,分别找到其上层调用函数和下层调用函数,然后进行具体分析。

3.漏洞分析

A.我的分析

关键在于搞清楚我们构造的输入数据,在程序中是怎样被处理的,并且是怎样造成程序的崩溃的。所以光有上述入手点并不够,我们还需要通过动态调试,从入手点回溯函数调用流程,从而分析清楚漏洞产生原因。

这里我通过Immunity Debugger进行动态调试和分析。

具体的分析记录如下图所示——

漏洞触发处的具体代码如下图——

分析到这里,虽然把整个流程走了一遍,但还是感觉没有非常清晰,漏洞触发流程理清楚了,但是这么长的数据是在哪里被读入,还是没有很清晰。所以就去参考了参考博客中的分析。

B.调整思路后的分析

所以之前我的分析入手点都没选好,就像大佬说的,要想定位恶意数据的输入点,既然是文件输入,那么可以考虑从fopen函数的调用处入手进行分析。

于是重新分析,记录如下——

文件操作相关代码如下图所示。

分析过程中主要的2个函数:sub_428920和sub_42B420, 从sub_428920中00428EA7处调用函数sub_42B420时,传入参数为一个地址(该地址为起始,存放着文件名长度和具体文件名)。

这就很尴尬了,感觉还是没看到溢出点。。。。。。继续调试。。。。。。

最后发现在0042B62B——1000D38E——这个地方调用完函数出来就读取到了文件中的所有信息,在0042B739处调用函数时就将其作为参数传入了(调试时的具体值如下示例)。也就是说这个函数中应该就存在漏洞代码,并导致后续漏洞的产生。但这个函数是在该exe文件调用的dll库中,所以此漏洞的产生原因应该是dll库中存在栈溢出漏洞。

000D1BA4   645C3A43  C:\d
000D1BA8   766E3954  T9nv  RETURN to WININET.766E3954 from WININET.766E340C
000D1BAC   56706E4B  KnpV
000D1BB0   50323634  462P
000D1BB4   66636742  Bgcf
000D1BB8   64494E4F  ONId
000D1BBC   424A5761  aWJB
000D1BC0   4A726B4C  LkrJ
000D1BC4   71726646  Ffrq
000D1BC8   76656474  tdev  CRYPT32.76656474
000D1BCC   624C3349  I3Lb
000D1BD0   49396A5A  Zj9I
000D1BD4   4F513664  d6QO

4.漏洞利用

在前面的分析中已经能够成功覆盖EIP,但是在尝试利用时碰到以下问题:在覆盖超过EIP位置之后的数据时,就会报access错误,所以感觉只能把shellcode放在EIP前面。那么问题来了,如何跳转过去?(如果通过覆盖之后的ECX,call ecx实现,但是地址有00,会导致截断,覆盖EIP没法成功)。

未完待续。。。。。。

5.疑问

不得不吐槽一下,我都感觉自己分析了个假漏洞23333

A.漏洞分析部分

同样是从fopen函数入手进行分析,参考博客中的分析表明,在5处fopen调用中会先后调用2处,然后在第二处fopen函数处,由于对于filename的长度检查不严格,导致直接作为参数回调到外层函数中,因此当外层函数结束时,程序返回到一个不可读的地址,从而触发了异常处理流程,因为文件畸形内容,导致了SEH指针被覆盖,程序可控。如下图所示。

但是在实际分析过程中,就只调用了一处fopen函数,然后继续运行就触发了崩溃,如下图所示,而且从上面的分析中也可以看出漏洞点应该是在dll文件中。

B.漏洞利用部分

为啥我在虚拟机里分析时在覆盖超过EIP位置之后的数据时,就会报access错误,而参考博客中的分析就可以覆盖到EIP之后呢?(毕竟如果可以覆盖到EIP之后的栈空间,将shellcode写进去,就可以直接利用jmp esp进行利用了。

在后来的调试分析过程中发现,应该是EIP之后刚好存放的是文件的名字和文件名的长度,覆盖之后程序再去访问此处存放的文件名对应的文件时就会报访问错误,所以可能需要在构造shellcode时花点心思。

对于实际调试的情况,我们是没法采用jmp esp的方式进行漏洞利用的,但本来jmp esp这种利用方式会覆盖上层函数的栈帧,对程序的影响就很大,所以有更好的利用方式我们就可以选择,针对我们遇到的实际情况,一种可能的利用方法如下:

参考

栈溢出利用
Windows Shellcode学习笔记——栈溢出中对jmp esp的利用与优化
缓冲区溢出攻击
ShellCode01: 弹出计算器