SchoolCTF 2011: You just love this thing, right? Writeup



        The challenge “You just love this thing, right?” gives you a GNU/Linux EFL binary called “mazzze” (that’s contained within a gzip file called “mazzze.gz”). ‘mazzze’ is an ASCII game in which you must go through a ‘maze’ and get the combination to a safe that will give you the key. It begins with a help message and after you press return the actual game starts like this (the colors are for visibility):

# # #
# @ #
# . .

When you uncover the whole map, it looks like this:

# # # # # # # # # # # # # # #
# @ # . # I # . # . . . . . #
# . . . . . # . # . # # # . #
# # # # . . # . . . . . # . #
# M # . . # # . # # # # # . #
# . # T # # . . # . . . . . #
# . # . . . . # # . # . . # #
# . # . # . # # # # S # . . #
# . . . # . . . . . # # # . #
# # # # # . # # # . . # . . #
# . . . # . # . # # . . . # #
# . # . . . . . . # . # # # #
# . # # # # . # . . . . . G #
# . . . . . . # # # # # # . #
# # # # # # # # # # # # # # #
step...

        The symbols you come across are ‘@’ (You), ‘#’ (Wall), ‘.’ (Road), ‘T’ (Torch), ‘I’ (Soldering-Iron), ‘M’ (Man), ‘S’ (Safe), and ‘G’ (Guard). The keys you will use are UP, DOWN, LEFT, and RIGHT arrows for movement; y,n to answer yes-or-no questions; q to quit the game; and h to show the help message that contains all of this.

If you go to the man, he says:

You have met a Man. Talk to him? (y/n): "I know a secret combination, but I have to keep silence!"
It seems you can "bruteforce" him... But how?..
Press any key to continue.

        Once you have the Soldering Iron, when you go to him, you can now ‘bruteforce’ the ‘secret combination’ out of him, which is a randomly generated two-digit decimal number only valid for that instance of the game.

You have met a Man. Talk to him? (y/n): "I know a secret combination, but I have to keep silence!"
It seems you can "bruteforce" him... But how?..
Use Soldering-Iron? (y/n): "Oh no, stop it! Here is the key: 41
Press any key to continue.

        So now that we have the secret combination of ‘41’, all we need to do is go to the safe and use the combination to unlock it, right? Wrong. The problem is, the safe is surrounded on all four sides by walls. So my approach was to attach gdb to the mazzze and change the memory contents of the map so we could get to the safe (or, A safe ;) ).

        So first, attach (my mazzze program at the time had a PID of 5877, which can be checked using top):

gdb> attach 5877

        So when I attached, it paused the program (but if it doesn’t, just use Ctrl+C), and they get the input for your movements and actions inside their main function ‘step(int () [15], int () [15], int, int, int () [4], int, VBAG&) ()’, so we were already somewhere buried inside their step function, which is good, because it’s easy to find the memory location we should break at now using the stack command in gdb.

gdb> stack
#0 0x008f5422 in __kernel_vsyscall ()
#1 0x001cde03 in read () from /lib/tls/i686/cmov/libc.so.6
#2 0x0017742b in _IO_file_underflow () from /lib/tls/i686/cmov/libc.so.6
#3 0x00178ccb in _IO_default_uflow () from /lib/tls/i686/cmov/libc.so.6
#4 0x0017a0f8 in __uflow () from /lib/tls/i686/cmov/libc.so.6
#5 0x0016fb6c in getchar () from /lib/tls/i686/cmov/libc.so.6
#6 0x0804972c in mygetch() ()
#7 **0x0804a626** in step(int (*) [15], int (*) [15], int, int*, int (*) [4], int, VBAG&) ()
#8 0x0804b757 in main ()

We see the step function starts at 0x0804a626, so we break there. (It actually starts a bit before this, but by this point it has its stack frame set up, which we will use to find the parameters that were passed into it.)

(The way to find this address statically using IDA is to simply find the function (through searching the .text section or in the functions list) and breaking several instructions in, after the mov ebp, esp.)

gdb> b *0x0804a626
Breakpoint 1 at 0x804a626
gdb> c

Now when it breaks again, we will be at the beginning of the step function.

        To find the first two arguments, which appear to be the 15x15 map representation, we see in IDA that arg_0 starts at an offset of 8, so we check 8 bytes past the base pointer:

gdb> x/10x ($ebp + 8)
0xbfa726e0: 0xbfa72a8c 0xbfa72708 0x0000000f 0xbfa72e20
0xbfa726f0: 0xbfa72e10 0x00000001 0xbfa72e28 0x00000000
0xbfa72700: 0x00000000 0x00000000

        Checking the first address, we find that it contains the whole map, 15x15 (225) ints between 0 and 7, which represent our different symbols:

gdb> x/225d 0xbfa72a8c
0xbfa72a8c: 1 1 1 1
0xbfa72a9c: 1 1 1 1
0xbfa72aac: 1 1 1 1
0xbfa72abc: 1 1 1 1
0xbfa72acc: 0 1 0 1
0xbfa72adc: 0 1 0 1
0xbfa72aec: 0 0 0 0
0xbfa72afc: 0 1 1 0
0xbfa72b0c: 0 0 0 0
0xbfa72b1c: 1 0 1 0
0xbfa72b2c: 1 1 1 0
0xbfa72b3c: 1 1 1 1
0xbfa72b4c: 1 0 0 1
0xbfa72b5c: 0 0 0 0
0xbfa72b6c: 0 1 0 1
0xbfa72b7c: 1 3 1 0
0xbfa72b8c: 0 1 1 0
0xbfa72b9c: 1 1 1 1
0xbfa72bac: 1 0 1 1
0xbfa72bbc: 0 1 0 1
0xbfa72bcc: 1 0 0 1
0xbfa72bdc: 0 0 0 0
0xbfa72bec: 0 1 1 0
0xbfa72bfc: 1 0 0 0
0xbfa72c0c: 0 1 1 0
0xbfa72c1c: 1 0 0 1
0xbfa72c2c: 1 1 0 1
0xbfa72c3c: 0 1 0 1
0xbfa72c4c: 1 1 1 **4**
0xbfa72c5c: 1 0 0 1
0xbfa72c6c: 1 0 0 0
0xbfa72c7c: 1 0 0 0
0xbfa72c8c: 0 0 1 1
0xbfa72c9c: 1 0 1 1
0xbfa72cac: 1 1 1 1
0xbfa72cbc: 0 1 1 1
0xbfa72ccc: 0 0 1 0
0xbfa72cdc: 0 1 1 0
0xbfa72cec: 0 0 1 0
0xbfa72cfc: 1 0 1 1
0xbfa72d0c: 0 0 0 1
0xbfa72d1c: 1 1 0 1
0xbfa72d2c: 0 0 0 0
0xbfa72d3c: 0 0 1 0
0xbfa72d4c: 1 1 1 1
0xbfa72d5c: 1 0 1 1
0xbfa72d6c: 1 1 0 1
0xbfa72d7c: 0 0 0 0
0xbfa72d8c: 0 0 1 1
0xbfa72d9c: 0 0 0 0
0xbfa72dac: 0 0 1 1
0xbfa72dbc: 1 1 1 1
0xbfa72dcc: 0 1 1 1
0xbfa72ddc: 1 1 1 1
0xbfa72dec: 1 1 1 1
0xbfa72dfc: 1 1 1 1
0xbfa72e0c: 1

Now this is the contents of the 15x15 map (array of 225 ints):

gdb> x/225d 0xbfa72708
0xbfa72708: 1 1 1 1
0xbfa72718: 1 1 1 1
0xbfa72728: 1 1 1 1
0xbfa72738: 1 1 1 1
0xbfa72748: 0 1 0 1
0xbfa72758: 0 1 0 1
0xbfa72768: 0 0 0 0
0xbfa72778: 0 1 1 0
0xbfa72788: 0 0 0 0
0xbfa72798: 1 0 1 0
0xbfa727a8: 1 1 1 0
0xbfa727b8: 1 1 1 1
0xbfa727c8: 1 0 0 1
0xbfa727d8: 0 0 0 0
0xbfa727e8: 0 1 0 1
0xbfa727f8: 1 3 1 0
0xbfa72808: 0 1 1 0
0xbfa72818: 1 1 1 1
0xbfa72828: 1 0 1 1
0xbfa72838: 0 1 0 1
0xbfa72848: 1 0 0 1
0xbfa72858: 0 0 0 0
0xbfa72868: 0 1 1 0
0xbfa72878: 1 0 0 0
0xbfa72888: 0 1 1 0
0xbfa72898: 1 0 0 1
0xbfa728a8: 1 1 0 1
0xbfa728b8: 0 1 0 1
0xbfa728c8: 1 1 1 4
0xbfa728d8: 1 0 0 1
0xbfa728e8: 1 0 0 0
0xbfa728f8: 1 0 0 0
0xbfa72908: 0 0 1 1
0xbfa72918: 1 0 1 1
0xbfa72928: 1 1 1 1
0xbfa72938: 0 1 1 1
0xbfa72948: 0 0 1 0
0xbfa72958: 0 1 1 0
0xbfa72968: 0 0 1 0
0xbfa72978: 1 0 1 1
0xbfa72988: 0 0 0 1
0xbfa72998: 1 1 0 1
0xbfa729a8: 0 0 0 0
0xbfa729b8: 0 0 1 0
0xbfa729c8: 1 1 1 1
0xbfa729d8: 1 0 1 1
0xbfa729e8: 1 1 0 1
0xbfa729f8: 0 0 0 0
0xbfa72a08: 0 0 1 1
0xbfa72a18: 0 0 0 0
0xbfa72a28: 0 0 1 1
0xbfa72a38: 1 1 1 1
0xbfa72a48: 0 1 1 1
0xbfa72a58: 1 1 1 1
0xbfa72a68: 1 1 1 1
0xbfa72a78: 1 1 1 1
0xbfa72a88: 1

(Just a side note: This second map ends right before the beginning of the first map.)

        Counting on the map, we see the safe is the 116th place on the map (map[115]) because there is a ‘4’ there. 115 * 4 = 460, which is 0x1cc in hex.

We confirm that the chest is at an offset of 1cc (460 base 10) bytes:

gdb> x/d (0xbfa72a8c + 0x1cc)
0xbfa72c58: 4

        We will change the spot above the Safe from a Wall to another Safe. We know each row has 15 characters in it, so we just have to subtract 0xf bytes from the position of the original Safe, so f*4:

gdb> set {int}(0xbfa72a8c + 1cc - (f*4)) = 4

# # # # # # # # # # # # # # #
# . # . # . # . # . . . . . #
# . . . . . # . # . # # # . #
# # # # . . # . . . . . # . #
# M # . . # # . # # # # # . #
# . # . # # . . # @ . . . . #
# . # . . . . # # . **S** . . # #
# . # . # . # . # # S # . . #
# . . . # . # # # . #
# # # # # . .

step...

Step on the Safe and it will say:

You have found Safe. Here you see bruteproof combination lock of 2 decimal numbers. Do you want to enter combination? (y/n): combination: 41 Answer: TpZpvLopxUijtQisbtfDboOpuCfBotxfs