第14章 漏洞分析技术 (未完待续)

14.1软件漏洞原理

14.1.1 缓冲区溢出漏洞

缓冲区溢出,是指计算机向缓冲区内填充的数据超过了缓冲区本身的容量,导致合法的数据被覆盖。从安全编程的角度出发,理想情况下程序在执行复制,赋值等操作时,需要检查数据的长度,且不允许输入超过缓冲区长度的数据。

缓冲区溢出可以分为栈溢出,和堆溢出两种

1.栈溢出原理

演示源码如下:

void testFunc(char *Buf)
{
	char testBuf[8];
	memcpy(testBuf,Buf,32);
	return;
}

int main(int argc, char* argv[])
{
	char Buf[64]={0};
	memset(Buf,0x41,64);
	testFunc(Buf);
	return 0;
}

main函数


00401070    55              push ebp
00401071    8BEC            mov ebp,esp
00401073    81EC 80000000   sub esp,0x80  //为main函数分配栈空间
00401079    53              push ebx
0040107A    56              push esi
0040107B    57              push edi
0040107C    8D7D 80         lea edi,dword ptr ss:[ebp-0x80] //指向栈顶
0040107F    B9 20000000     mov ecx,0x20  
00401084    B8 CCCCCCCC     mov eax,0xCCCCCCCC
00401089    F3:AB           rep stos dword ptr es:[edi] //栈空间置为0xCC
0040108B    C645 C0 00      mov byte ptr ss:[ebp-0x40],0x0  //Buf[64]位置为 ebp-0x40
0040108F    B9 0F000000     mov ecx,0xF
00401094    33C0            xor eax,eax
00401096    8D7D C1         lea edi,dword ptr ss:[ebp-0x3F]
00401099    F3:AB           rep stos dword ptr es:[edi]
0040109B    66:AB           stos word ptr es:[edi]
0040109D    AA              stos byte ptr es:[edi] // 以上均为 char Buf[64]={0};
0040109E    6A 40           push 0x40
004010A0    6A 41           push 0x41
004010A2    8D45 C0         lea eax,dword ptr ss:[ebp-0x40]
004010A5    50              push eax
004010A6    E8 C5030000     call stack.00401470 //memset(Buf,0x41,64);
004010AB    83C4 0C         add esp,0xC
004010AE    8D4D C0         lea ecx,dword ptr ss:[ebp-0x40]
004010B1    51              push ecx
004010B2    E8 4EFFFFFF     call stack.00401005 //testFunc(Buf);
004010B7    83C4 04         add esp,0x4
004010BA    33C0            xor eax,eax
004010BC    5F              pop edi                                  ; stack.004015B9
004010BD    5E              pop esi                                  ; stack.004015B9
004010BE    5B              pop ebx                                  ; stack.004015B9
004010BF    81C4 80000000   add esp,0x80
004010C5    3BEC            cmp ebp,esp
004010C7    E8 64030000     call stack.00401430
004010CC    8BE5            mov esp,ebp
004010CE    5D              pop ebp                                  ; stack.004015B9
004010CF    C3              retn

testFunc处汇编:

00401020    55              push ebp
00401021    8BEC            mov ebp,esp
00401023    83EC 48         sub esp,0x48
00401026    53              push ebx
00401027    56              push esi
00401028    57              push edi
00401029    8D7D B8         lea edi,dword ptr ss:[ebp-0x48]
0040102C    B9 12000000     mov ecx,0x12
00401031    B8 CCCCCCCC     mov eax,0xCCCCCCCC
00401036    F3:AB           rep stos dword ptr es:[edi] //函数栈空间置0xCC
00401038    6A 40           push 0x40
0040103A    8B45 08         mov eax,dword ptr ss:[ebp+0x8]
0040103D    50              push eax
0040103E    8D4D F8         lea ecx,dword ptr ss:[ebp-0x8]
00401041    51              push ecx
00401042    E8 A9000000     call stack.004010F0  //memcpy(testBuf,Buf,32);
00401047    83C4 0C         add esp,0xC
0040104A    5F              pop edi                                  ; stack.004010B7
0040104B    5E              pop esi                                  ; stack.004010B7
0040104C    5B              pop ebx                                  ; stack.004010B7
0040104D    83C4 48         add esp,0x48
00401050    3BEC            cmp ebp,esp
00401052    E8 D9030000     call stack.00401430
00401057    8BE5            mov esp,ebp
00401059    5D              pop ebp                                  ; stack.004010B7

在执行 memcpy(testBuf,Buf,32)之前,我们先观察一下堆栈的情况。

EBP-48   > CCCCCCCC
EBP-44   > CCCCCCCC
EBP-40   > CCCCCCCC
EBP-3C   > CCCCCCCC
EBP-38   > CCCCCCCC
EBP-34   > CCCCCCCC
EBP-30   > CCCCCCCC
EBP-2C   > CCCCCCCC
EBP-28   > CCCCCCCC
EBP-24   > CCCCCCCC
EBP-20   > CCCCCCCC
EBP-1C   > CCCCCCCC
EBP-18   > CCCCCCCC
EBP-14   > CCCCCCCC
EBP-10   > CCCCCCCC
EBP-C    > CCCCCCCC
EBP-8    > CCCCCCCC
EBP-4    > CCCCCCCC
EBP ==>  >/0018FF48
EBP+4    >|004010B7  返回到 stack.004010B7 来自 stack.00401005
EBP+8    >|0018FF08

可以看到EBP+4,存储着我们需要的retn 值,返回到执行函数后的位置。

EBP+8 存放着我们进入函数之前push进来的参数。

EBP-8处为为临时变量testBuf的位置。

执行memset后我们再观察一下堆栈

EBP-48   > CCCCCCCC
EBP-44   > CCCCCCCC
EBP-40   > CCCCCCCC
EBP-3C   > CCCCCCCC
EBP-38   > CCCCCCCC
EBP-34   > CCCCCCCC
EBP-30   > CCCCCCCC
EBP-2C   > CCCCCCCC
EBP-28   > CCCCCCCC
EBP-24   > CCCCCCCC
EBP-20   > CCCCCCCC
EBP-1C   > CCCCCCCC
EBP-18   > CCCCCCCC
EBP-14   > CCCCCCCC
EBP-10   > CCCCCCCC
EBP-C    > CCCCCCCC
EBP-8    > 41414141
EBP-4    > 41414141
EBP ==>  > 41414141
EBP+4    > 41414141
EBP+8    > 41414141
EBP+C    > 41414141
EBP+10   > 41414141
EBP+14   > 41414141
EBP+18   > 41414141
EBP+1C   > 41414141
EBP+20   > 41414141
EBP+24   > 41414141
EBP+28   > 41414141
EBP+2C   > 41414141
EBP+30   > 41414141
EBP+34   > 41414141

可以看到EBP-8开始的值全部变为41,且retn的值也被覆盖掉。

因此,如果要防止栈溢出,需要在复制之前对数据的长度进行判断,阻止超过额定长度的赋值操作。

2.堆溢出原理

示例源码如下:

int main(int argc,char *argv[])
{
        char str[]="\nHello123456789213456789\n";
		
		
        char *a,*b,*c;
        long *hHeap;

        hHeap = (long *)HeapCreate(0x00040000,0,0);
        printf("\n(+) Creating a heap at: 0x00%xh\n",hHeap);
        printf("(+) Allocating chunk A\n");
		
        a = (char *)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
        printf("(+) Allocating chunk B\n");
		
        b = (char *)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
		
        printf("(+) Chunk A=0x00%x\n(+) Chunk B=0x00%x\n",a,b);
        printf("(+) Freeing chunk B to the lookaside\n");
		
        HeapFree(hHeap,0,b);
		
        printf("(+) Now overflow chunk A:\n");
		
        printf("%x\n",str);
        printf(str);
        memcpy(a,"XXXXXXXXXXXXXXXXAAAABBBB\x64\xff\x12\x00",28);
		
        // set software bp
        //__asm__("int $0x3");
		
        printf("(+) Allocating chunk B\n");
		

        b = (char *)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
        printf("(+) Allocating chunk C\n");
		

        c = (char *)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
		
        printf("(+) Chunk A=0x00%x\n(+)Chunk B=0x00%x\n(+) Chunk C=0x00%x\n",a,b,c);
		
        strcpy(c,"AAAAAAAAAAAA\n");
        printf(str);
        // set software bp
        //_asm int 0x3;
		
        return 0;
}

首先我们来看一下系统的堆的储存结构

源码中的堆溢出过程:

堆这面用OD调试的时候有点迷糊,不太明白Flink/Blink是怎么指向下一块堆的,DataB前的地址可以直接被导向str,但是A的Flink/Blink的位置存的却不是B堆的地址。回头研究一下再补充。

14.1.2 整数溢出漏洞

一般开发人员在写程序时,对整型数仅考虑范围,不考虑安全要求,对不同用途的整型数,安全要求也不同。

例如:在32位系统中,无符号整型数的范围是0~0xffffffff 不仅要保证用户提交的数据在此范围内,还要保证用户对数据进行运算和储存后,其结果仍在此范围内。

整数型溢出可以分为3个大类:存储溢出,计算溢出和符号问题。

1.存储溢出

造成储存溢出的原因是 使用不同的数据类型储存整形数据造成的:

int len1 = 0x10000;
short len2 = len1;

len1长度为32位,len2为16位,结果造成len2 为0,结果与预期不符。

同样把短类型赋值给长类型可能同样会出现状况

short len2 = 1;
int len1 = len2;

有些编译器编译的程序中可能会使len1 等于0xffff0001,实际上是一个负数。

2.运算溢出

原理是在运算过程中没有考虑其边界范围,造成运算后的数值超出了其存储空间。

示例代码:

bool func(char * userdata, short datalength)
{
    char* buff;
    if(datalength != strlen(userdata))
    {
        return false;
    }
    datalength = datalength*2;
    buff = malloc(datalenght);
    strncpy(buff,userdata,datalength);
}

3.符号问题

整型数分为有符号和无符号,因此符号问题也可能造成安全方面的隐患,

最典型的例子是 eEye 发现的Apache Http Server 分块编码漏洞

分块编码(chunked encoding)传输方式是HTTP 1.1协议中定义的Web用户向服务器提交数据的一种方法,当服务器收到chunked编码方式的数据时会分配一个缓冲区存放之,如果提交的数据大小未知,客户端会以一个协商好的分块大小向服务器提交数据。   Apache服务器缺省也提供了对分块编码(chunked encoding)支持。Apache使用了一个有符号变量储存分块长度,同时分配了一个固定大小的堆栈缓冲区来储存分块数据。出于安全考虑,在将分块数据拷贝到缓冲区之前,Apache会对分块长度进行检查,如果分块长度大于缓冲区长度,Apache将最多只拷贝缓冲区长度的数据,否则,则根据分块长度进行数据拷贝。然而在进行上述检查时,没有将分块长度转换为无符号型进行比较,因此,如果攻击者将分块长度设置成一个负值,就会绕过上述安全检查,Apache会将一个超长(至少>0x80000000字节)的分块数据拷贝到缓冲区中,这会造成一个缓冲区溢出。

14.1.3 UAF漏洞

UAF(Use-After-Free)漏洞,其原理从字面意思就可以理解:释放后被重用。

具体过程可以简单理解为:A先后调用B、C、D三个子函数,B会把A的某个资源释放,D判断不严谨,即使B把A资源释放后仍然引用他,这使D引用了危险的悬空指针。

因此,可以通过在B 释放资源后,立即执行C申请分配内存,占用刚才释放的空间并对其中数据做手脚,当D被调用的时候,就可以执行我们做了手脚的代码了。

14.2 Shellcode

Shellcode 是一段可以独立执行的代码,在触发了漏洞并获取了eip指针的控制权后,通常会将eip指针指向shellcode以完成漏洞利用过程。

14.2.1 Shellcode的结构

Shellcode 通过主动查找DLL基址并动态获取API地址来实现API调用,然后根据实际功能调用相应的API函数。

Shellcode分为两个模块,分别是基本模块,和功能模块

1.基本模块

基本模块用于实现Shellcode 初始运行,获取kernel32 基址 及获取API地址。

(1)获取Kernel32地址

常见方法:暴力搜索、异常处理链表搜索和TEB搜索

书中给的例子是TEB搜索

Kernel32.dll基址获取过程如上

int main(int argc, char* argv[])
{
	DWORD hKernel32 = 0;
	__asm
	{
		//assume	 	fs:nothing
		mov   		eax, fs:[30h]
		test  		eax, eax
		js  		os_9x
os_nt:  
		mov  		eax, dword ptr[eax+0ch]
		mov  		esi, dword ptr[eax+1ch]
		lodsd  
		mov   		eax, dword ptr[eax+8]
		jmp  		k_finished
os_9x:
		mov   		eax, dword ptr[eax+34h]
		mov  		eax, dword ptr[eax+7ch]
		mov  		eax, dword ptr[eax+3ch]
k_finished:
		mov  		hKernel32, eax  ;获取kernel32地址 

	}
	printf("hKernel32 = %x\n",hKernel32);
	return 0;
}

(2)获取API地址

从DLL文件中获取API地址的方法如下所示

在实际应用中,如果API函数名称以明文出现,就会降低Shellcode的分析难度,而且API函数名称占用的空间一般都比较大。

所以可以采用Hash算法将要获取的函数名称转换为一个4字节的Hash值,在搜索过程中按此算法计算DLL中的文件名称的Hash值,对比两个Hash值是否相同。如此,可以减小shellcode的体积,同时提高了隐蔽性。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇