Monday, September 22, 2014

CSAW Reversing 300 Wololo

This challenged composed of a .lst file that seemed to be IDA output of an arm program. A short example of the file is:
__text:00000A80   SUB  SP, SP, #0xC
__text:00000A82   STR  R0, [SP,#0xC+var_8]
__text:00000A84   LDRB  R0, [R0]
They provided a python file which read a file and uploaded it to the server, printing out the server response. The provided code was:
#!/usr/bin/env python

import sys, socket, struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sys.argv[1], int(sys.argv[2])))
print s.recv(1024)

contents = open(sys.argv[3], "rb").read()
s.send(struct.pack("<I", len(contents)) + contents)

print "The challenge server says: ", s.recv(1024)
On to the analysis of the code. The arm seemed to compose primarily of two possible parts. It contains a validate_database and check_login routines. If we sent the server a blank file, then it responded that our table appeared to be incorrect. This lead us to analyze the validate_database first.
Before starting, it is important to include that they provided the C struct's for the table which were:
typedef struct
{
        uint32_t magic;
        uint32_t version;
        uint16_t num_cols;
        uint16_t num_rows;
} header_t;

typedef struct
{
        uint8_t type;
        char name[16];
} col_t;

The validate_database routine had several blocks of code where arguments where compared against static values, then a jump taken based on that compare. The first check ensures that there is actual input, so that won't be displayed. The second check compares the first four bytes to the static constant:
__text:00000B20   MOV  R0, #0x4F4C4F57   ;Does it start with WOLO
__text:00000B28   LDR  R1, [SP,#0x2C+inputThing]  ; MAGIC
__text:00000B2A   LDR  R1, [R1]
__text:00000B2C   CMP  R1, R0
The next check verified that the uint32_t version was 1:
__text:00000B3A   LDR  R0, [SP,#0x2C+inputThing]
__text:00000B3C   LDR  R0, [R0,#4]
__text:00000B3E   CMP  R0, #1  ;next four bytes 0x00000001
__text:00000B40   BEQ  loc_B4C  ;Version, must be 1
The next check offset's R0 by 6 instead of 4, so the next check verifies that the num_rows is greater than four and less than 0x1000:
__text:00000B4C   LDR  R0, [SP,#0x2C+inputThing]
__text:00000B4E   LDRH  R0, [R0,#0xA]
__text:00000B50   CMP  R0, #4  ;next Greater than 0x4

__text:00000B5E   LDR  R0, [SP,#0x2C+inputThing]
__text:00000B60   LDRH  R0, [R0,#0xA]
__text:00000B62   CMP.W  R0, #0x1000 ;next less than  0x1000
The next checks the columns are >= four or <= 10, since the offset is only 4 greater than where the version number was located:
_text:00000B72   LDR  R0, [SP,#0x2C+inputThing]
__text:00000B74   LDRH  R0, [R0,#8]
__text:00000B76   CMP  R0, #4  ;Greater than 0x4

__text:00000B86   LDRH  R0, [R0,#8]
__text:00000B88   CMP  R0, #0x10 ;Less than 0x10
__text:00000B8A   BLE  loc_B96
It then goes on to verify that there are the correct number of columns and that they are the correct type. Since we expect our rows to represent the columns we chose, we can worry less about this check.

After sending in a table composed of four columns and four rows that matched those columns, the python program gave us the error that our login credential were incorrect. Our next step is to determine how to make it through the check_login method.
The first check that the check_login appears to make is to verify that the first column was labled "USERNAME" and that the corresponding row data contained the string "captainfalcon".
__text:00000CDA   MOV  R1, #(aUsername - 0xCE6) ; "USERNAME"
__text:00000CE2   ADD  R1, PC ; "USERNAME"
__text:00000CE4   MOVS  R2, #8 ; size_t
__text:00000CEA   LDR  R0, [SP,#0x6C+var_4C]
__text:00000CEC   LDR  R3, [SP,#0x6C+var_1C]
__text:00000CEE   MOV  R9, #0x11
__text:00000CF6   MUL.W  R0, R0, R9
__text:00000CFA   ADD  R0, R3
__text:00000CFC   ADDS  R0, #1 ; char *
__text:00000CFE   BLX  _strncmp                ; Verify column name is "USERNAME"

__text:00000D1A   MOV  R1, #(aCaptainfalcon - 0xD26) ; "captainfalcon"
__text:00000D22   ADD  R1, PC ; "captainfalcon"
__text:00000D24   MOVS  R2, #0xE ; size_t
__text:00000D2A   LDR  R0, [SP,#0x6C+var_38] ; char *
__text:00000D2C   BLX  _strncmp                ;Verify row contains "captainfalcon"
The program then goes on to check that the second column is PASSWORD and contains the hash fc03329505475dd4be51627cc7f0b1f1:
__text:00000D3E   MOV  R1, #(aPassword - 0xD4A) ; "PASSWORD"
__text:00000D46   ADD  R1, PC ; "PASSWORD"
.......
__text:00000D5A   MUL.W  R0, R0, R9
__text:00000D5E   ADD  R0, R3
__text:00000D60   ADDS  R0, #1 ; char *
__text:00000D62   BLX  _strncmp

__text:00000D7E   MOV  R1, #(aFc03329505475d - 0xD8A) ; "fc03329505475dd4be51627cc7f0b1f1"
__text:00000D86   ADD  R1, PC ; "fc03329505475dd4be51627cc7f0b1f1"
__text:00000D88   MOVS  R2, #0x20 ; ' ' ; size_t
__text:00000D8E   LDR  R0, [SP,#0x6C+var_38] ; char *
__text:00000D90   BLX  _strncmp
The next check verifies that the third column is ADMIN and has a uint8_t of 1:
__text:00000DA2   MOV  R1, #(aAdmin - 0xDAE) ; "ADMIN"
__text:00000DAA   ADD  R1, PC ; "ADMIN"
...
__text:00000DC2   ADD  R0, R3
__text:00000DC4   ADDS  R0, #1 ; char *
__text:00000DC6   BLX  _strncmp

__text:00000DEA   LDRB.W  R0, [SP,#0x6C+var_50]
__text:00000DEE   CMP  R0, #1
The next check verifies that the fourth column is ISAWESOME and has a uint8_t of 1:
__text:00000E0C   MOV  R1, #(aIsawesome - 0xE18) ; "ISAWESOME"
__text:00000E14   ADD  R1, PC ; "ISAWESOME"
...
__text:00000E2E   ADDS  R0, #1 ; char *
__text:00000E30   BLX  _strncmp

__text:00000E54   LDRB.W  R0, [SP,#0x6C+var_54]
__text:00000E58   CMP  R0, #1
With this in mind, the following script was able to successfully pass all the checks and get a key from the server.
import struct
db = "WOLO"
db += struct.pack("<I",0x1) #Version
db += struct.pack("<H",0x0004) # columns
db += struct.pack("<H",0x0004) #rows

db += struct.pack("B",0x5) #Type, ensure 16 byte size
db += "USERNAME"+"\x00"*8 #Name of col
db += struct.pack("B",0x6) #Type, ensure 16 byte size
db += "PASSWORD"+"\x00"*8 #Name of col
db += struct.pack("B",0x0) #Type, ensure 16 byte size
db += "ADMIN"+"\x00"*11 #Name of col
db += struct.pack("B",0x0) #Type, ensure 16 byte size
db += "ISAWESOME"+"\x00"*7 #Name of col

for row in range(0, 0x4):
 db += "captainfalcon\x00\x00\x00" #ensure 16 byte size
 db += "fc03329505475dd4be51627cc7f0b1f1"
 db += struct.pack("<B",0x1)
 db += struct.pack("<B",0x1)
If you have any questions comments, please feel free to share!

--Imp3rial

No comments:

Post a Comment