__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, R0The 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 1The 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 0x1000The 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_B96It 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 _strncmpThe 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, #1The 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, #1With 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