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