zoukankan      html  css  js  c++  java
  • 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调试,就构成了网上公认的反汇编三板斧,但是和三板斧一样,套路比较死,遇到难处理的问题就要看自己的水平是不是够强大了,关于这一点我还是需要继续找东西练习。

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

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

  • 相关阅读:
    积水路面Wet Road Materials 2.3
    门控时钟问题
    饮料机问题
    Codeforces Round #340 (Div. 2) E. XOR and Favorite Number (莫队)
    Educational Codeforces Round 82 (Rated for Div. 2)部分题解
    Educational Codeforces Round 86 (Rated for Div. 2)部分题解
    Grakn Forces 2020部分题解
    2020 年百度之星·程序设计大赛
    POJ Nearest Common Ancestors (RMQ+树上dfs序求LCA)
    算法竞赛进阶指南 聚会 (LCA)
  • 原文地址:https://www.cnblogs.com/xubuqiao/p/9268538.html
Copyright © 2011-2022 走看看