Monday, March 7, 2011

CODEGATE YUT 2011: Issue 500 writeup

[DISCLAIMER]: I didn't check if the flag is valid (the server doesn't accept anymore sending new flags), so maybe more work is required. However, seems that the flag might be valid. If not, let me know. Seems that the solution was correct, check another writeup made by Leet more here.


The past weekend was the CODEGATE Quals, but you still can play.  I've arrived a bit late :'(, but here it goes the (complete?) writeup for Issue 500.


Objective:
    Find a key.
    you can get the key on URL : Link

Solution:

So... we need to find a valid key. If we enter random data, the server issues an error.

So, first step, take a look on the folder... and we'll find a file that we can download :).

After downloading the file, let's see if we can guess the filetype:

$ file 5645E2F51AC5BC420272E0A342164314
5645E2F51AC5BC420272E0A342164314: x86 boot sector, code offset 0xc0

Cool, we have a boot sector and we need to find a key. Opening with IDA (remember, 16 bits) we can see some strings like:
seg000:05DA "Wrong password:"
seg000:05FA "Enter password:"
seg000:05FA "\x02 gate"

Si it seems that we need to find a password. Reading at the code, we spot two interesting parts:
  • A loop that reads the keyboard input on seg000:021B (hint: the code is using INT 16H GET ENHANCED KEYSTROKE)
seg000:021B getChar():
seg000:021B
seg000:021B                 mov ah, 10h
seg000:021D                 int 16h ; KEYBOARD - GET ENHANCED KEYSTROKE
seg000:021D                     ; Return: AH = scan code, AL = character
seg000:021F                 cmp ah, 1
seg000:0222                 jz short loc_24F
seg000:0224                 cmp ah, 0Eh
seg000:0227                 jz short loc_25D
seg000:0229                 cmp ah, 1Ch
seg000:022C                 jz short loc_26B
seg000:022E                 cmp ah, 0E0h ; 'Ó'
seg000:0231                 jz short loc_26B
seg000:0233                 cmp al, 21h ; '!'
seg000:0235                 jb short getChar
seg000:0237                 cmp al, 7Eh ; '~'
seg000:0239                 ja short getChar
seg000:023B                 cmp di, 5
seg000:023E                 jnb short getChar
seg000:0240                 mov [bx+di], al
seg000:0242                 inc di
seg000:0243                 push bx
seg000:0244                 mov ax, 0E2Ah
seg000:0247                 mov bx, 7
seg000:024A                 int 10h ; - VIDEO - WRITE CHARACTER AND ADVANCE CURSOR (TTY WRITE)
seg000:024C                 pop bx
seg000:024D                 jmp short getChar
  • A condition check that: 
    • Goes to a special section than read the disk if the condition is meet (think like the HD is now in "unlocked mode")
    • If it's false, decrements a counter (max_tries?), prints an message and then start the getPassword() loop again
seg000:0289 checkPassword:
seg000:0289                  mov cl, 10h
seg000:028B                  xor dx, dx
seg000:028D                  mov si, 7E7Ah
seg000:0290                  cld
seg000:0291
seg000:0291 calcPasswordHash:
seg000:0291                  lodsb
seg000:0292                  call calc_hash
seg000:0295                  dec cl
seg000:0297                  jnz short calcPasswordHash
seg000:0299                  cmp dx, ds:7FFAh ;dx==0x2002
seg000:029D                  jz short goodPassword
seg000:029F                  mov si, 7FDAh
seg000:02A2                  call print_String
seg000:02A5                  call sub_358
seg000:02A8                  dec byte ptr ds:7E79h  ;numTries
seg000:02AC                  jnz loop_Input
seg000:02B0                  jmp short maxTriesReached
seg000:02B2 goodPassword:
seg000:02B2                  mov bx, 7E00h
seg000:02B5                  mov cx, 5
seg000:02B8                  mov dx, 80h ; 'Ç'
seg000:02BB                  mov ax, 201h
seg000:02BE                  int 13h ; DISK - READ SECTORS INTO MEMORY
seg000:02C0                  jnb short loc_2CA
seg000:02C2                  mov si, 7D81h
seg000:02C5                  call print_String
seg000:02C8
[...]
seg000:031D maxTriesReached:
seg000:031D                  mov ax, 40h ; '@'
seg000:0320                   mov es, ax
seg000:0322                   assume es:nothing
[...]
seg000:032D                  push 0
seg000:0330                   retf
seg000:0331

So, seems that we've spotted the algorithm. But before, we can see that the references to some variables are meaningless, because the align/offset are not correct. It's easy to guess, as the variables for sure are pointing to the string references ("Wrong password", "Enter password",...) that we've seen before.

So, if you can see properly all the string references, launch IDA with "Loading offset" to 0x00007A00. Another quick and dirty options is patch manually all the references to point to the string.




Next step will be bruteforcing a valid password: we can try to reverse the function "encrypt_pwd" (basic XOR operations) or bruteforce it.



But, before solving that, we need to know more about the password that is entered by the user:
  • First, looking at the code, no more than 5 characters seems to be written to the temp_password
seg000:023B                 cmp di, 5
seg000:023E                 jnb short getChar
seg000:0240                 mov [bx+di], al    ;bx=offset temp_password
                                               ;di=pointer
                                               ;al=read char
seg000:0242                 inc di
  • The password is of 16 bytes of length (0x10), and it's initialized with an space ' ' (0x20) except by the characters that the user entered previously.
seg000:027D                 mov al, 20h ; ' '
seg000:027F
seg000:027F pad_password:
seg000:027F                 cmp di, 10h   ;di=number of readed key-strokes
seg000:0282                 jnb short checkPassword
seg000:0284                 mov [bx+di], al
seg000:0286                 inc di
seg000:0287                 jmp short pad_password


So, with this information, now we can do a bruteforce attack. Basically, we need to replicate the 'encrypt_pwd' function and iterate over all the passwords until we found a collision:

password=' ' x 16
while (calcPasswordHash(password) != 0x2002)
    iterate(password[0..5])  //aaaaa baaaa caaaa
print "Found" + password[0..5]

I implemented the code in MASM, copy and paste the code and creating a bruteforce loop. In one second, we have at least one valid collision "KD#"




More keys:

MLJKE
FKBKF
DNKHG
NDIFI
ECAFJ
GFHEK
BCIBL
KAHAN

As we can see, the algorithm is quite easy to break, basically because:
  • XOR algorithm xD
  • Password constrains (only uses 5 first bytes of the password, rest are padding with ' ' --0x20--)
  • The hash is checked only against 2 bytes of the key (0x2002), so it's easy to check for collisions
As a reference, the source code (yep, i know, crappy code). If you prefer, you can download it from here.

; CODEGATE 2011 issue 500
 include \masm32\include\masm32rt.inc
;
.data
     item dd 0
     MIN_CHAR db 40h
     MAX_CHAR db 50h
     temp_passwd db 10h dup(' '),0
     password_ok db 2,' gate'
.code
start:
     call main
     exit


main proc
     cls
     mov ebx, offset temp_passwd
     mov edi, 5
     mov al, 20h
  loc_27F:
     cmp edi, 10h
     jnb short genNewPassword
     mov [ebx+edi], al
     inc di
     jmp short loc_27F
  genNewPassword:    ;Sloppy code to implement bruteforce (aaaaa,baaaa,...)
     mov cl,MIN_CHAR
     mov dl,MAX_CHAR


     mov ebx, offset temp_passwd
     inc BYTE PTR [ebx]
     cmp BYTE PTR [ebx],dl
     jne tryPassword


     mov BYTE PTR [ebx],cl
     inc BYTE PTR [ebx+1]
     cmp BYTE PTR [ebx+1],dl
     jne tryPassword


     mov BYTE PTR [ebx+1],cl
     inc BYTE PTR [ebx+2]
     cmp BYTE PTR [ebx+2],dl
     jne tryPassword

     mov BYTE PTR [ebx+2],cl
     inc BYTE PTR [ebx+3]
     cmp BYTE PTR [ebx+3],dl
     jne tryPassword


     mov BYTE PTR [ebx+3],cl
     inc BYTE PTR [ebx+4]
     cmp BYTE PTR [ebx+4],dl
     je end_main
     jmp tryPassword


  tryPassword:
     xor eax, eax
     xor ebx, ebx
     xor ecx, ecx
     xor edx, edx
     mov cl, 10h
     xor dx, dx
     mov esi, offset temp_passwd
     cld
  enc_loop:
     lodsb
     call checkPWD
     dec cl
     jnz short enc_loop
     cmp dx, word ptr ds:password_ok ;0x2002
     jz short key_found
     ;print "Wrong password",10,10
     ;mov ebx, offset temp_passwd
     ;print ebx,13,10
     jmp genNewPassword


   key_found:
     print "Found!:",13,10
     mov ebx, offset temp_passwd
     print ebx,13,10
     jmp genNewPassword


end_main:
     ret


main endp  

; obtained from Disassembling
checkPWD proc near
     push ax
     push cx
     mov ah, al
     xor al, al
     xor dx, ax
     mov cl, 8
  loc_372: ; CODE XREF: sub_368+14 j
     shl dx, 1
     jnb short loc_37A
     xor dx, 1975h
  loc_37A: ; CODE XREF: sub_368+C j
     dec cl
     jnz short loc_372
     pop cx
     pop ax
     retn
checkPWD endp
end start

1 comment: