In this writeup, we will take a look at the second lab door and swap maze entrance challenges.
Lab door 2
Lets get the source code:
TERMINAL_INPUT = 0
TERMINAL_OUTPUT = 1
DOOR_CONTROL = 2
main:
.correct_pin = 0
.main_loop:
call ask_and_verify_code
test al, al
jz .invalid
call open_door
jmp .main_loop
.invalid:
; Show denied message
mov esi, invalid
mov ecx, end_invalid - invalid
mov dx, TERMINAL_OUTPUT
rep outsb
call sleep
jmp .main_loop
ask_and_verify_code:
; Variables
.input = -32
.len = -36
push esi
push edi
push ebp
mov ebp, esp
sub esp, 36
mov dword [ebp + .len], 0
; Display initial message
mov esi, message
mov ecx, end_message - message
mov dx, TERMINAL_OUTPUT
rep outsb
.input_loop:
; Grab next input
in al, TERMINAL_INPUT
; If enter is pressed, done
cmp al, '\n'
je .end_input
; Look for backspace
cmp al, 8
je .backspace
; Printable only
cmp al, ' '
jb .input_loop
cmp al, '~'
ja .input_loop
; Add character to input
mov ecx, [ebp + .len]
cmp ecx, 31
jae .input_loop
mov [ebp + .input + ecx], al
inc dword [ebp + .len]
out TERMINAL_OUTPUT, al
jmp .input_loop
.backspace:
cmp dword [ebp + .len], 0
je .input_loop
; Erase last character
mov al, 8
out TERMINAL_OUTPUT, al
mov al, ' '
out TERMINAL_OUTPUT, al
mov al, 8
out TERMINAL_OUTPUT, al
dec dword [ebp + .len]
jmp .input_loop
.end_input:
out TERMINAL_OUTPUT, al
; Null terminate input
mov ecx, [ebp + .len]
mov byte [ebp + .input + ecx], 0
; Check code
lea eax, [ebp + .input]
push eax
call verify_code
mov esp, ebp
pop ebp
pop edi
pop esi
ret
verify_code:
push esi
push ebp
mov ebp, esp
sub esp, 8
push dword [ebp + 12]
call strlen
cmp eax, 16 ;length has to be 16
jne .bad
mov esi, [ebp + 12] ;load input to esi
;initialization
mov edx, 0xfa
mov al, [esi] ;load first char from input
rol edx, 5 ;like shift but shifted bits
xor dl, al ;are rotated to the other end
add dl, 0xab
mov al, [esi+1]
rol edx, 3
xor dl, al
add dl, 0x45
mov al, [esi+2]
rol edx, 1
xor dl, al
add dl, 0x12
mov al, [esi+3]
rol edx, 9
xor dl, al
;calculate remaining characters based on first four
add dl, 0xcd
mov cl, dl
and cl, 15
add cl, 'a'
cmp [esi+4], cl
jne .bad
rol edx, 12
xor dl, cl
add dl, 0x87
mov cl, dl
and cl, 15
add cl, 'a'
cmp [esi+5], cl
jne .bad
rol edx, 3
xor dl, cl
add dl, 0xef
mov cl, dl
and cl, 15
add cl, 'C'
cmp [esi+6], cl
jne .bad
rol edx, 1
xor dl, cl
add dl, 0x10
mov cl, dl
and cl, 15
add cl, 'f'
cmp [esi+7], cl
jne .bad
rol edx, 13
xor dl, cl
add dl, 0x9a
mov cl, dl
and cl, 15
add cl, 'e'
cmp [esi+8], cl
jne .bad
rol edx, 9
xor dl, cl
add dl, 0xa8
mov cl, dl
and cl, 15
add cl, 'D'
cmp [esi+9], cl
jne .bad
rol edx, 7
xor dl, cl
add dl, 0xca
mov cl, dl
and cl, 15
add cl, 'D'
cmp [esi+10], cl
jne .bad
rol edx, 2
xor dl, cl
add dl, 0x91
mov cl, dl
and cl, 15
add cl, 'c'
cmp [esi+11], cl
jne .bad
rol edx, 5
xor dl, cl
add dl, 0x86
mov cl, dl
and cl, 15
add cl, 'A'
cmp [esi+12], cl
jne .bad
rol edx, 6
xor dl, cl
add dl, 0xf1
mov cl, dl
and cl, 15
add cl, 'e'
cmp [esi+13], cl
jne .bad
rol edx, 3
xor dl, cl
add dl, 0x1f
mov cl, dl
and cl, 15
add cl, 'B'
cmp [esi+14], cl
jne .bad
rol edx, 4
xor dl, cl
add dl, 0x90
mov cl, dl
and cl, 15
add cl, 'f'
cmp [esi+15], cl
jne .bad
mov al, 1
mov esp, ebp
pop ebp
pop esi
ret 4
.bad:
xor al, al
mov esp, ebp
pop ebp
pop esi
ret 4
strlen:
xor eax, eax
mov ecx, [esp + 4]
.loop:
mov dl, [ecx]
test dl, dl
jz .end
inc eax
inc ecx
jmp .loop
.end:
ret 4
open_door:
push esi
; Send command to unlock door
mov al, 1
out DOOR_CONTROL, al
; Show open message
mov esi, open
mov ecx, end_open - open
mov dx, TERMINAL_OUTPUT
rep outsb
call sleep
pop esi
ret
sleep:
mov ecx, 120
.sleep_loop:
pause
loop .sleep_loop
ret
message:
db "\fCLEARENCE LEVEL 2 REQUIRED\nAuthorization code:\n"
end_message:
open:
db "ACCESS GRANTED"
end_open:
invalid:
db "ACCESS DENIED"
end_invalid:
If we look at the verify_code
function carefully we see, that the key is calculated based on the first 4 characters. There is a lot of code, but basically there are just 2 unrolled for loops. If you don't want to look at assembly, C solver will give you a good overview of the way in which the key is being calculated.
But still, this challenge took some time to solve mostly due to bug caused by not properly emulating 8 bit addition. To track it down I actually had to write inline assembly version of the same code.
C solver:
#include <stdio.h>
#include <stdint.h>
uint8_t input[] = {'a', 'a', 'a', 'a', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t hardcoded_add[] = {0xfa, 0xab, 0x45, 0x12, 0xcd, 0x87, 0xef, 0x10, 0x9a,
0xa8, 0xca, 0x91, 0x86, 0xf1, 0x1f, 0x90};
uint8_t hardcoded_roll[] = {5, 3, 1, 9, 12, 3, 1, 13, 9, 7, 2, 5, 6, 3, 4, 0};
uint8_t hardcoded_add2[] = {'a', 'a', 'C', 'f', 'e', 'D', 'D', 'c', 'A', 'e', 'B', 'f'};
uint32_t rotl32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (8*sizeof(n) - 1);
c &= mask;
return (n<<c) | (n>>( (-c)&mask ));
}
int main()
{
int i = 0;
uint32_t tmp = 0;
for(i=0; i<4; i++){
tmp = tmp + hardcoded_add[i];
tmp = rotl32(tmp, hardcoded_roll[i]);
tmp = tmp ^ input[i];
}
for(i=4; i<16; i++){
tmp = (0xFFFFFF00 & tmp)+ ((tmp + hardcoded_add[i]) & 0xFF);
uint8_t test1 = tmp & 15;
test1 = test1 + hardcoded_add2[i-4];
input[i] = test1;
tmp = rotl32(tmp, hardcoded_roll[i]);
tmp = tmp ^ test1;
}
for(i=0; i<16; i++){
printf("%c", input[i]);
}
printf("\n");
return 0;
}
Solution: aaaaohFgqNRnHeBt
Inline assembly version:
#include <stdio.h>
#include <stdint.h>
uint8_t input[] = {'a', 'a', 'a', 'a', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t hardcoded_add[] = {0xfa, 0xab, 0x45, 0x12, 0xcd, 0x87, 0xef, 0x10, 0x9a,
0xa8, 0xca, 0x91, 0x86, 0xf1, 0x1f, 0x90};
uint8_t hardcoded_roll[] = {5, 3, 1, 9, 12, 3, 1, 13, 9, 7, 2, 5, 6, 3, 4, 0};
uint8_t hardcoded_add2[] = {'a', 'a', 'C', 'f', 'e', 'D', 'D', 'c', 'A', 'e', 'B', 'f'};
uint32_t rotl32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (8*sizeof(n) - 1);
c &= mask;
return (n<<c) | (n>>( (-c)&mask ));
}
int main()
{
int i = 0;
uint32_t tmp = 0;
for(i=0; i<4; i++){
asm(
"add %1, %%dl;"
"mov %2, %%al;"
"roll %3, %%edx;"
"xor %%al, %%dl;"
:"+d"(tmp)
:"r"(hardcoded_add[i]), "r"(input[i]), "cI"(hardcoded_roll[i])
:"eax"
);
}
for(i=4; i<16; i++){
asm(
"add %1, %%dl;"
:"+d"(tmp)
:"r"(hardcoded_add[i])
);
uint8_t test1 = 0;
asm(
"mov %2, %%edx;"
"mov %%dl, %%cl;"
"and $15, %%cl;"
"add %1, %%cl;"
"mov %%cl, %0;"
:"=r"(test1)
:"r"(hardcoded_add2[i-4]), "r"(tmp)
:"eax", "ecx", "edx"
);
input[i] = test1;
tmp = rotl32(tmp, hardcoded_roll[i]);
asm(
"xor %1, %%dl;"
:"+d"(tmp)
:"r"(test1)
);
}
for(i=0; i<16; i++){
printf("%c", input[i]);
}
printf("\n");
return 0;
}
Btw, there is also cave filled with spiders north from the desert with boss to beat :D
Lab door 3
The source code of the third lab door has ~6k lines of code. But fortunately, most of it is in decipher
function and represents the unrolled loop.
Full source code can be found here
Let's look at important few code snippets:
; read in the input, expect "XXXXXXXX-XXXXXXXX"
mov edi, inbuf
call input
; check input format
mov esi, inbuf
cmp byte [esi+8], '-'
jne .fail
mov byte [esi+8], 0
; parse first ctext
push v0
push inbuf
call parse_uint32_hex
cmp eax, -1
je .fail
; parse second ctext
push v1
push inbuf+9
call parse_uint32_hex
cmp eax, -1
je .fail
; decipher
push v0
call decipher
; check
cld ; clear direction flag so that string pointers auto increment after each string operation
mov esi, v0
lodsd ; load string instructions (loads to EAX)
cmp eax, 0x57415343 ; 'CSAW'
jne .fail
lodsd
cmp eax, 0x41484148 ; 'HAHA'
jne .fail
...
decipher:
push ebp ; esp - 4
mov ebp,esp
push esi
push ebx
mov ecx,dword [ebp+0x8] ; input address
mov edx,dword [ecx] ; first part of input i0 = y1
mov esi,dword [ecx+0x4] ; second part of input i1 = y0
mov eax,edx
mov ebx,edx
shr ebx,0x5 ; y0 >> 5
shl eax,0x4 ; y0 << 4
xor eax,ebx
add eax,edx
xor eax,0x2913260a
sub esi,eax ; y2 = y0 - ((y1 >> 5) ^ (y1 << 4) + y1 ) ^ v0
mov ebx,esi
mov eax,esi
shr esi,0x5
shl ebx,0x4
xor ebx,esi
add ebx,eax
xor ebx,0x37dbdd6f
sub edx,ebx ; y3 = y1 - ((y2 >> 5) ^ (y2 << 4) + y2 ) ^ v1
Looking at the algorithm used for pin derivation, we see that it calculates each new step based on two previous ones. Since in the end results are compared to 0x57415343
and 0x41484148
, the results of two last steps, we have everything we need to run derivation backwards and get required inputs.
Python solver:
x = 0x57415343
y = 0x41484148
def step(x):
return (((x >> 5)^(x<<4)) + x) & 0xffffffff
for val in values[::-1]:
tmp = (x + (step(y) ^ val)) & 0xffffffff
x = y
y = tmp
print("0x%X 0x%X" %(x, y))
Full code can be found here
Some things to note:
- inputs are taken in reverse order, the first part of the input is used as y1 and second as y0
- program treats inputted string as a hex number
Calculated pin: 9b916917-b6117336
Swamp maze entrace
After we enter ruins we see robot we can program. Source code of robot:
INPUT = 0
main:
mov esi, program
.loop:
in al, INPUT
cmp al, 0
je .done
mov [esi], al
inc esi
jmp .loop
.done:
mov eax, SYS_DISCONNECT
int 0x80
mov eax, SYS_ROM_UPDATE
mov ebx, program
mov ecx, esi
sub ecx, ebx ;get length of program
int 0x80
.data
program:
It executes input we pass to it. To solve this challenge we can use SYS_WALK
syscall.
mov eax, SYS_WALK
mov ebx, x ; x-vel
mov ecx, y ; y-vel
int 0x80
hlt
But we can't use null byte since it is used to trigger execution.
So we have to change mov eax, SYS_WALK
with xor eax, eax; mov al, SYS_WALK
. Also, we don't need y-vel so we will set ecx to 0 with xor ecx, ecx
and in ebx we will write max value (-1)
Solution:
mov esi, data
mov ecx, end-data
mov dx, 0
rep outsb
hlt
data:
xor eax, eax
xor ecx, ecx
mov al, SYS_WALK
mov ebx, -1
int 0x80
hlt
db 0 ;starts execution
end:
Comments
comments powered by Disqus