Lobotomy Team

RWTH CTF 2012 - Azure Coast - writeup part1

0. traffic dumps

look at the following traffic dumps (images are clickable):
they show a conversation between service (blue) and checking system (red)

traffic dump #1traffic dump #2

You can download original PCAPs if you want: stream1.pcap , stream2.pcap

You can see that every conversation begins with four bytes of data server sends to client and then client answers with different four bytes.

1. challenge & response

Start with writing small script that reads data from server and writes something back:

socket = TCPSocket.new @host, TARGET_PORT
chal = socket.read(4).unpack('L')[0]
printf " in: %08x %10d\n", chal&0xffffffff, chal

resp = 0x11223344

outdata = [resp].pack('L')
data = outdata.unpack('L')[0]
printf "out: %08x %10d\n", data&0xffffffff, data

socket.write outdata
output:
#   in: e50a481c 3842656284
#  out: 11223344  287454020
now edit provided sources, uncomment some commented debug output and add more debug output, like:
diff -ur azurecoast_orig/service_source/service/go/src/rwthctfvm/cpu/cpu.go azurecoast/service_source/service/go/src/rwthctfvm/cpu/cpu.go
--- azurecoast_orig/service_source/service/go/src/rwthctfvm/cpu/cpu.go	2012-12-14 00:24:07.277183829 +0400
+++ azurecoast/service_source/service/go/src/rwthctfvm/cpu/cpu.go	2012-12-01 00:47:51.611877564 +0400
@@ -159,12 +159,13 @@
        if c.Step { getKey() }
        ipval,err := c.GetRegister(ip)
        //fmt.Println("fetching "+hex(ipval))
+	fmt.Print(hex(ipval) + ": ")
        if err != nil {return errors.New("unable to get ip") }
        instrval, err := c.GetMemory(ipval)
        if err != nil {return errors.New("unable to get cmd") }
        instr, err := c.GetInstr(instrval)
        if err != nil {return errors.New("unable to decode instr") }
-	//fmt.Println(instr.Inspect())
+	fmt.Println(instr.Inspect())
        if c.Step {
                fmt.Print("IP: "+c.InspectIP())
                fmt.Println(instr.Inspect())
@@ -287,9 +288,9 @@
                                i := int(bytes[0])
 
                                if i!=0x0a { //don't print newlines
-				//c.Log("readb from",arg1,"read", hex(int(i)), string(i), "ok:", num)
+				c.Log("readb from",arg1,"read", hex(int(i)), string(i), "ok:", num)
                                } else {
-				//c.Log("readb from",arg1,"read", hex(int(i)), "ok:", num)
+				c.Log("readb from",arg1,"read", hex(int(i)), "ok:", num)
                                }
                                c.SetRegister(t1,num)
                                c.SetRegister(t0,i)
( get a full diff )

that will provide us with nice detailed execution logs like

0x0: <ldw t0(0x0) 0x0 >
0x2: <ldw t6(0x0) 0x0 >
0x4: <jmp t6(0x2ce) 0x0 >
0x2ce: <mov t4(0x0) t0(0x5) >
0x2cf: <sys 0x9 0x0 >
0x2d0: <mov t2(0x0) t0(0x1f9c) >
0x2d1: <sub t2(0x1f9c) t1(0x9e2) >
( a full log of step 1 )

Now remember that we got a 0xe50a481c challenge value.
and responded with 0x11223344
So, let's grep it out of the execution log!

0x351: <jmp t6(0x3d1) 0x0 >
0x3d1: <mov t0(0x2410f8a0) t1(0xffffffffe50a481c) >
0x3d2: <sys 0x4 0x1 >
0x3d3: <ldw t1(0xffffffffe50a481c) 0x0 >
0x3d5: <xor t0(0xffffffffe50a481c) t1(0xffffffffa7f7e284) >
[.] R=0x42fdaa98

0x3d6: <mul t0(0x42fdaa98) t0(0x42fdaa98) >
[.] R=0x21e3a40
0x3d7: <ldw t1(0xffffffffa7f7e284) 0x0 >
0x3d9: <add t0(0x21e3a40) t1(0xffffffffa82d3274) >
[.] R=0xffffffffaa4b6cb4

0x3da: <mov t2(0x2410f8a0) t0(0xffffffffaa4b6cb4) >
0x3db: <sys 0x3 0x0 >
0x3dc: <cmp t0(0x11223344) t2(0xffffffffaa4b6cb4) >

Yay! Better than I expected! XOR, then MUL, then ADD.

2. We're in!

So, final challenge-response code is:

socket = TCPSocket.new @host, TARGET_PORT
chal = socket.read(4).unpack('L')[0]
printf " in: %08x %10d\n", chal&0xffffffff, chal

resp = (chal^0xa7f7e284)**2 + 0xa82d3274

outdata = [resp].pack('L')
data = outdata.unpack('L')[0]
printf "out: %08x %10d\n", data&0xffffffff, data

socket.write outdata

part 2