Exp10 经典漏洞的逆向分析 20155113徐步桥

Exp10 Windows下经典漏洞的逆向分析


1. 实践内容

  • 逆向工具的学习与使用
    • IDA pro静态分析
      • IDA的基本知识
      • IDA初步实践
      • CTF逆向分析
    • ollydbg动态分析
      • OD的基本知识
      • OD初步实践
      • CTF逆向分析
  • Windows平台下经典漏洞分析
    • CVE-2017-15222

2. 实践过程

2.1 IDA pro

2.1.1 IDA基本知识

  • 什么是IDA:IDA Pro(简称IDA)是一款交互式反汇编工具,它功能强大、操作复杂,要完全掌握它,需要很多知识。IDA最主要的特性是交互和多处理器。操作者可以通过对IDA的交互来指导IDA更好地反汇编,IDA并不自动解决程序中的问题,但它会按用户的指令找到可疑之处,用户的工作是通知IDA怎样去做。比如人工指定编译器类型,对变量名、结构定义、数组等定义等。这样的交互能力在反汇编大型软件时显得尤为重要。多处理器特点是指IDA支持常见处理器平台上的软件产品。

  • IDA会提供3种不同的打开方式;New(新建),Go(运行),Previous(上一个)

  • 界面介绍

    • IDA View-A是反汇编窗口,
    • HexView-A是十六进制格式显示的窗口,
    • Imports是导入表(程序中调用到的外面的函数),
    • Functions是函数表(这个程序中的函数),
    • Structures是结构,
    • Enums是枚举。
  • 寄存器介绍

    • 在反汇编窗口中大多是eax, ebx, ecx, edx, esi, edi, ebp, esp等。这些都是X86 汇编语言中CPU上的通用寄存器的名称,是32位的寄存器。这些寄存器相当于C语言中的变量。
    • EAX 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。
    • EBX 是”基地址”(base)寄存器, 在内存寻址时存放基地址。
    • ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
    • EDX 则总是被用来放整数除法产生的余数。
    • ESI/EDI 分别叫做”源/目标索引寄存器”(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串。
    • EBP 是”基址指针”(BASE POINTER), 它最经常被用作高级语言函数调用的”框架指针”(frame pointer)。
    • ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,ESP每次减少4字节。
  • 反汇编代码指令

    • 资料转移指令

      • MOV 移动
      • MOVC 程式记忆体移动
      • MOVX 外部RAM和扩展I/O口与累加器A的数据传送指令
      • PUSH 放入堆叠
      • POP 由堆叠取回
      • XCH 8位元交换
      • XCHD 低4位元交换
      • SWAP 高低4位元交换
    • 算术指令

      • ADD 两数相加
      • ADDC 两数相加再加C
      • SUBB 两数相减再减C
      • INC 加一指令
      • DEC 减一指令
      • MUL (MUL AB乘法指令仅此一条)相乘指令,所得的16位二进制数低8位存累加器A高8位存B
      • DIV (DIV AB 除法指令仅此一条)相除指令,所得商存A,余数存B
      • DA (DA A 只此一条指令)调整为十进数
    • 逻辑指令

      • ANL做AND(逻辑与)运算
      • ORL做OR(逻辑或)运算
      • XRL 做(逻辑异或)运算
      • CLR 清除为0
      • CPL 取反指令
      • RL 不带进位左环移
      • RLC 带进位左环移
      • RR 不带进位右环移
      • RRC 带进位右环移
    • 控制转移类指令

      • JC C=1时跳
      • JNC C=0时跳
      • JB 位元=1时跳
      • JNB 位元=0时跳
      • JBC 位元=1时跳且清除此位元
      • LCALL 长调用子程序
      • ACALL 绝对调用子程序
      • RET 由副程式返回
      • RETI 由中断副程式返回
      • AJMP 绝对转移
      • SJMP 相对转移
      • JMP @A+DPTR 散转,相对DPTR的间接转移
      • JZ A=0时跳
      • JNZA 0时跳
      • CJNE 二数比较,不相等时跳
      • DJNZ 减一,不等於0时跳
      • NOP 空操作
    • 位变量指令

      • SETB 设定为1
      • ORG 程序开始,规定程序的起始地址
      • END 程序结束
      • EQU 等值指令(先赋值后使用)例:SUM EQU 30H
      • DB 定义字节指令
      • DW 定义字内容
      • DS 定义保留一定的存贮单元数目
      • BIT 位地址符号指令 例:SAM BIT P1.0
      • RET 子程序返回指令
      • RETI 中断子程序返回指令
      • $ 本条指令地址

2.1.2 IDA初步实践

  • 这里是i春秋上的线上习题,拿过来做一下(ps:这个题目和课程里的不一样,很多好像在手册里找不到答案,所以只能自己做了)。环境提供了一个叫做lab05-01的程序,需要使用逆向工具破解软件并回答问题。
  • 第1题:有多少函数调用了gethostbyname?
  • 打开IDA,直接把程序拖进去,调到import窗口,可以找到函数gethostbyname。双击进入函数地址。

  • 可以看到,一共有很多个调用记录,但是这些并不完全是调用记录,r仅仅只是read,而只有p才是调用。再观察调用 的地址可以发现,第123个和第456个地址是相邻的,是同一个函数,所以是5个函数。

  • 第2题:DllMain的地址是____。
  • 跳至ida-a主界面,可以发现左边正好就有dll_main函数,双击跳转,打开option-general,将地址显示出来,就可以看到函数的地址了。

  • 第3题:0x10001656处的子过程中有1个参数____个局部变量。
  • 用键盘点击g,输入0x10001656跳转至目标位置,可以看到定义了很多变量,一般来说var定义的都是局部变量,数了数有11个。

  • 第4题:使用Strings窗口,来在反汇编中定位字符串\cmd.exe /c,它位于____。
  • 点击view-open subview,打开strings窗口,找到\cmd.exe /c提示的位置。

  • 双击,就OK了,地址是0x10095b34。

2.1.3 CTF逆向分析

  • 这里我找了实验吧里一个与IDA联系紧密的题目,题目入下:

  • 下载打开之后是这样的。

  • 丢进IDA。从之前的线索看,在输入错误之后会提示“密钥无效”,因此果断打开strings窗口,一眼就可以看见显眼的中文字符

  • 双击打开字符所在位置,可以看到这里没有什么可以点的,除了后面的注释,点一下注释跳转到了一个函数。

  • 观察一下这个函数,可以发现这个函数调用了一个别的函数。

  • 直接打开这个函数,发现这个函数的结构是这样的,当函数sub_4011D0()返回值不为0时,调用下面的blabla,所以我们看一眼这个sub_4011D0()函数。

  • 好了,关键来了,函数的返回值在这里,也就是关键。重点就在这个函数。

  • 通过分析可以知道 , 这段代码会将用户输入的用户名的每个字符遍历一遍
    把每个字符的序号(从 0 开始算)与这个字符的ASCII码的平方相乘 , 然后整体再加上序号 , 得到的和继续对 0x42 求余 , 最后将结果加上 33 , 然后再转为ASCII码
    然后再将上述结果连接在字符串 'Happy@' 之后构成注册码

    if ( GetDlgItemTextA(hDlg, 1000, String, 16) >= 5 )
    {
    GetDlgItemTextA(hDlg, 1001, &String1, 16);
    v1 = 0;
    if ( strlen(String) != 0 )
    {
    do
    {
    *(&String2 + v1) = (v1 + v1 * String[v1] * String[v1]) % 0x42 + 33;
    ++v1;
    }
    while ( v1 < strlen(String) );
    }
    strcpy(String, "Happy@");
    lstrcatA(String, &String2);
    result = lstrcmpA(&String1, String) != 0;
    }
    else
    {
    result = 1;
    }
    return result;

  • 然后我们写个c代码吧。。。没办法,只会c语言。。。

  • 运行的结果:Happy@!GA0U,输入程序。正确啦~

2.2 OD

2.2.1 OD基本知识

  • 什么是od

    • OLLYDBG是一个新的动态追踪工具,将IDA与SoftICE结合起来的思想,Ring 3级调试器,非常容易上手,己代替SoftICE成为当今最为流行的调试解密工具了。同时还支持插件扩展功能,是目前最强大的调试工具。
  • 调试中我们经常要用到的快捷键有这些:

    • F2:设置断点,只要在光标定位的位置(上图中灰色条)按F2键即可,再按一次F2键则会删除断点。(相当于 SoftICE 中的 F9)

    • F8:单步步过。每按一次这个键执行一条反汇编窗口中的一条指令,遇到 CALL 等子程序不进入其代码。(相当于 SoftICE 中的 F10)

    • F7:单步步入。功能同单步步过(F8)类似,区别是遇到 CALL 等子程序时会进入其中,进入后首先会停留在子程序的第一条指令上。(相当于 SoftICE 中的 F8)

    • F4:运行到选定位置。作用就是直接运行到光标所在位置处暂停。(相当于 SoftICE 中的 F7)

    • F9:运行。按下这个键如果没有设置相应断点的话,被调试的程序将直接开始运行。(相当于 SoftICE 中的 F5)

    • CTR+F9:执行到返回。此命令在执行到一个 ret (返回指令)指令时暂停,常用于从系统领空返回到我们调试的程序领空。(相当于 SoftICE 中的 F12)

    • ALT+F9:执行到用户代码。可用于从系统领空快速返回到我们调试的程序领空。(相当于 SoftICE 中的 F11)

2.2.1 OD初步实践

  • 我们还是来到i春秋的实验室,这一次的实验量还是比较大的。
  • 1.怎样让恶意代码的攻击负载(payload)获得运行?
  • 这个项目需要使用ida和od一起分析,首先把程序扔进这两个工具中。但是由于od并不是很好看,所以需要先借助ida寻找关键函数的地址。

  • 在od中找到主函数的位置,发现了很多进栈操作,因此可以通过跟随寄存器的方式来查看字符的具体数值。

  • 设置断点后运行,可以看到,一大堆的入栈操作是向0012fdd0压入数据,然后我们对这个地址进行跟随。

  • 单步运行,就可以看到出现的字符串了。看上去,应该是一个文件名。

  • 继续运行,然后可以发现一个显眼的函数:getmodulefilenamea,这个函数在ida里看一眼。

  • 看不出来什么东西,只知道这个函数大概是一个库函数,对栈空间里扔了当前的执行路径。

  • 然后我们跟随一下这个函数操作的栈。

  • 单步运行后可以发现数据已经改变,得到的就是这个程序现在的路径。

  • 然后继续单步走,又遇到了一个call指令。说明这里调用函数了。但是由于这里只有一个地址,所以返回ida,看看这个是什么函数。

  • 简单来说就是返回文件路径的最后一节,比如/a/b/c/d.exe,那么这个函数就返回d.exe。

  • 另外还发现寄存器的值也发生了变化,变成了当前的文件名。

  • 继续单步,又发现一个call,直接回ida查看一下,发现就是熟悉的strcmp,从ida里反汇编的代码来看,strcmp比较的是str1和str2,也就是eax和ecx寄存器里的值。

  • 跳过之后,发现eax中的值变成了1,这个应该就是返回的结果了。

  • 继续单步,发现了一个跳转,返回ida看看怎么个跳法。根据老师的解释,之前比较的两个字符串如果相等就是红色的线,然后就结束了。

  • 但是......我觉得有些牵强,而且od和ida在这个地方解析出来的函数有一些不对,在ida中显示的是JZ,在od中显示的是JE,那么问题来了,到底这里是怎么实现跳转的。我直接返回ida中的反汇编c语言界面,可以看出来,反汇编出的c语言非常的简答,就是strcmp完了是一个分支。这么看似乎要简单明了的多~

  • 整理一下这个程序的流程,首先记录下一个文件名叫做ocl.exe,再读取现在所在的路径的最后一节,一般是默认的lebel05-01.exe,然后对这两个字符串进行对比,如果不相等就结束.....
  • 这显然不是想要的结果,所以我们满足程序的要求,把文件名改成ocl.exe。看看会发生什么。

  • 设置断点后直接执行。可以看到寄存器内的两个字符已经是相同的了。

  • 继续单步执行,可以看到跳转的方向就发生了变化。跳向了40124c的地址,而在ida中,这个地址就是下面一大段的代码的起始地址。

  • 这一题的答案也就出来了,即将文件名改为ocl.exe即可触发攻击的payload。

  • 2.在地址0x00401133处发生了什么?

  • 看一眼ida,将地址跳转到00401133处,可以发现这里就是之前分析的地方,按r键就可以转换字符格式了。这里的作用是将ocl.exe加载到程序中。

  • 3.恶意代码使用的域名是什么?
  • 首先看到这个题目有些懵逼,但是经过前面的分析,如果程序的名字是ocl.exe的话就可以执行下面一大段代码的操作,那一大段代码铁定是恶意代码。翻一翻od,可以发现这些敏感词:WSASRARTUP,sock_stream......从这些敏感词可以看出,程序对外部应该想要建立一个socket连接,而域名一定就在这里面。

  • 然后一步步单步运行,就有了意外收获。

  • 可以看出来这个是一个域名,而这个域名是不是想要的域名还有待参考,返回ida,找到这个函数的地址。可以看到存在一个函数叫做gethostbyname,查一下可以知道这个函数是用来解析主机的信息和地址的,而函数的参数name是不是刚才看到的那个域名呢?双击一下就知道了

  • 可以看到,这个函数是一个操作字符串的函数,这个sub子函数的地址如下。再到od里找到这个地址并设置断点,跑一遍看看。

  • 在单步的过程中发现了一个“w”,跟踪一下这个寄存器,可以看出来,这个子函数就是把之前载入程序ocl.exe那一段字符串但是我们不知道那个是做什么用的一节解密了出来。

  • 到了这里就可以下结论了,程序通过载入一长串的字符串,前半截是加密的域名,后半截是一个文件名,通过比较文件是否符合载入的文件名,如果是,那么解密这个域名并尝试建立socket连接。而这个域名正是:www.praticalmalwareanalyis.com。

2.2.3 CTF逆向分析

  • 这里找了个实践题,还是破解crackme1,首先查壳(ps:之前在i春秋上这个步骤是逆向的第一步,但是因为是做过的所以就省略了,网课上老师教我们用的是peid这个软件,可以查出软件的壳或编译的语言,比较通用。),可以确定没壳,而且能看出来是c++写的

  • 然后丢进ida和od中,观察动态运行的结果。。。

  • 额,好像看不出来啥。。。观察ida的反汇编代码,可以看到,主函数很简单,只有两个分支,就是判断字符串是否相同,而出现flag的函数只能是在判断条件中的那个子函数。

  • 可以看到这个子函数还是比较复杂的,但是仔细看看就会发现,a1就是我们输入的字符串,v5就是要比较的字符串,然后跳到v5的地址,可以发现是一串乱码,应该是进行了加密。

  • 继续解读这个子函数,发现主要由两部分构成,第一部分是比较字符串的长度,第二部分是逐字节比较,同时,在比较之前,对a1进行了^20和对v5进行了-5的操作。

  • 总结一下,输入一个字符串,然后把输入的字符串进行异或操作,再与标准字符串减5之后的进行比较,因此,要想破解只需要将标准的字符串进行-5再异或就可以了。

    include<stdio.h>

    int main(){
    char ch[14]={0x68,0x57,0x19,0x48,0x50,0x6E,0x58,0x78,0x54,0x6A,0x19,0x58,0x5E,0x06};
    int i;
    FILE *fp;
    fp=fopen("flag.txt","w");
    for(i=0;i<14;i++){
    ch[i]=ch[i]-5;
    ch[i]=ch[i]^0x20;
    printf("%c",ch[i]);
    fprintf(fp,"%c",ch[i]);
    }
    fclose(fp);
    return 0;
    }

  • 运行上面的代码就可以得到flag了。

2.3 CVE-2017-15222漏洞逆向分析

2.3.1 漏洞原理

  • nftp是一款能够传输数据的应用,但是在接收数据的时候没有边界检查,因此存在缓冲区溢出漏洞。

2.3.2 漏洞分析

  • nftp需要连接到服务器才能正常的工作,因此首先需要写一个可供建立会话的脚本,脚本如下:

  • 这是一个建立会话的脚本,监听21端口,并准备建立tcp连接。当打开nftp时连接本机地址即可建立会话,传输的内容就是buf里的内容。

  • 当我们把buf中的内容扩展后,再次尝试建立连接的时候就会崩溃。

  • 打开错误报告查看一下日志,发现出现了“A”的ASCII码值41。

  • 然后开始分析,丢进ida和od中,由于是接收的数据出了问题,因此直接看接收数据的recv函数。从import函数组中找到这个函数然后查看该函数引用的位置。

  • 可以看到都是p调用,因此默默记下这三个位置,打开od,在这三个位置添加断点。分别是
  • 40ad14+11f = 40ae33
  • 410674+275 = 4108e9
  • 413ce0+f4 = 413dd4

  • 运行后发现程序中断,无论如何也走不了了,说明程序在这里出现了问题。回到ida,打开这个断点所在的位置。

  • 反汇编出c代码之后分析这里的代码,发现了一个高危函数strcpy,而且周围没有边界检查,因此可以猜测是这个地方出现了漏洞。

  • 然后回到od验证猜想是否正确,在strcpy处设置断点。运行脚本,再开始调试。

  • 可以看到,程序停在了数据传输之前,还没有开始接受数据,再次执行,发现程序再次中止,服务器窗口和nftp窗口都有了新的显示。

  • 再次执行,程序再次中止在这里,还有新的显示。

  • 到此为止程序还是一切正常,我们在脚本中所写的会话提示也就没有了,下一步就是传输数据了。单步调试的过程中就会发现字符串接收了进来

  • 再次运行,查看目的地址0x02472780,发现已经被覆盖了。

  • 再次运行,程序崩溃。可以肯定的是就是这里出现了问题。

  • 现在如何利用这个漏洞呢,首先知道崩溃的原因是因为跳转地址被覆盖,因此我们需要用到第一次试验中所说的“任意字符+返回地址+shellcode”来实现。首先我们需要知道字符串是在具体的哪个字节溢出的,因此我们需要用一个脚本,这个脚本可以生成一串字符,并通过返回的ASCII码定位字符的序号,运行脚本后生成字符串。

  • 接着我们将生成的长度为5000的字符串复制到服务器脚本中的buf中,运行程序使其崩溃,记住报错的字符串的ASCII码。

  • 然后查看字符的位置,就可以知道是哪个字符覆盖了返回地址。

  • 为了验证是不是第4116个字符覆盖了返回地址,我们改写一下脚本,在4116个a后面加上4个b,如果是在第4116个字符发生了覆盖,那么返回的报错提示就是b的ASCII表0x62。

  • 知道了具体字符发生覆盖的位置后,接下来就是在返回地址处填充想要返回的地址,打开od,调整到kernel32模式。

  • 查找jmp esp指令,记住这个指令的地址。

  • 现在就可以将jmp esp指令的地址填充到覆盖地址处,这样当程序读取返回地址的时候就会返回到这个jmp esp处,然后再跳向栈顶,即我们填充的shellcode处。用一张简单画的图来表示就是这样:

  • 最后我们需要一串shellcode,我在网上找了一个能够打开计算器的shellcode,执行后可以调用计算器。把这个代码放在最后,大功告成!

  • 运行之后。。。emmm没有出现想要的计算器。。。查了一下,为了能够跳转顺利,因此需要在shellcode前面加上一段空指令作为滑行区,即填充nop指令也就是0x90。填上16个不知道够不够。。。

  • 再次运行,实现了哟!!!

  • 终于明白为啥要有着陆区这种东西了,以前觉得可有可无来着。。。

3. 实践感想

  • IDA和OD是非常强大的分析工具,结合linux下的gdb调试,就构成了网上公认的反汇编三板斧,但是和三板斧一样,套路比较死,遇到难处理的问题就要看自己的水平是不是够强大了,关于这一点我还是需要继续找东西练习。

  • 对于常规反汇编问题,通用的解决流程就是查壳-脱壳-静态分析-动态分析-调试。

  • 虽然本次实验分析的这些漏洞有的已经很老很老,但是还是不得不佩服发现这些漏洞的人,另外也给我们写程序的人提了个醒:正如刘念老师所说,一个程序所包含的漏洞甚至比他本身代码的长度还要长。因此在做作品的时候需要仔细认真的去设置边界条件,减少出错的概率。

posted @ 2018-07-05 15:24  20155113XuBuqiao  阅读(318)  评论(0编辑  收藏