SICEH CTF 2016 - Obratni inženiring
Ta objava vsebuje primere rešitev nalog iz kategorije obratni inženiring na tekmovanju SICEH CTF 2016, ki je potekalo v okviru konference Infosek 2016.
1. Napačno geslo
Program kličemo na način ./re1 <zastavica>
ter nam sporoči ali je zastavica, ki smo jo vnesli, pravilna. Z ogledom strojne kode lahko vidimo, da program preveri, ali je argument dolg natanko 0x15
znakov, ali prvi del ustreza besedilu ISCTF{
, ali srednji del ustreza besedilu dv4kr47_r0t_13
ter ali se konča s }
(local_8h je naslov zastavice, ki smo jo vnesli):
| | 0x00400644 488b45f8 mov rax, qword [rbp - local_8h] | | 0x00400648 4889c7 mov rdi, rax | | 0x0040064b e870feffff call sym.imp.strlen | | 0x00400650 4883f815 cmp rax, 0x15 | |,=< 0x00400654 740c je 0x400662 | || 0x00400656 b800000000 mov eax, 0 | || 0x0040065b e88bffffff call sym.failure | ,===< 0x00400660 eb7d jmp 0x4006df | ||| ; JMP XREF from 0x00400654 (sym.main) | ||`-> 0x00400662 488b45f8 mov rax, qword [rbp - local_8h] | || 0x00400666 ba06000000 mov edx, 6 | || 0x0040066b bebb074000 mov esi, str.ISCTF_ ; "ISCTF{" @ 0x4007bb | || 0x00400670 4889c7 mov rdi, rax | || 0x00400673 e828feffff call sym.imp.strncmp | || 0x00400678 85c0 test eax, eax | ||,=< 0x0040067a 740c je 0x400688 | ||| 0x0040067c b800000000 mov eax, 0 | ||| 0x00400681 e865ffffff call sym.failure | ,====< 0x00400686 eb57 jmp 0x4006df | |||| ; JMP XREF from 0x0040067a (sym.main) | |||`-> 0x00400688 488b45f8 mov rax, qword [rbp - local_8h] | ||| 0x0040068c 4883c006 add rax, 6 | ||| 0x00400690 ba0e000000 mov edx, 0xe | ||| 0x00400695 bec2074000 mov esi, str.dv4kr47_r0t_13 ; "dv4kr47_r0t_13" @ 0x4007c2 | ||| 0x0040069a 4889c7 mov rdi, rax | ||| 0x0040069d e8fefdffff call sym.imp.strncmp | ||| 0x004006a2 85c0 test eax, eax | |||,=< 0x004006a4 740c je 0x4006b2 | |||| 0x004006a6 b800000000 mov eax, 0 | |||| 0x004006ab e83bffffff call sym.failure | ,=====< 0x004006b0 eb2d jmp 0x4006df | ||||| ; JMP XREF from 0x004006a4 (sym.main) | ||||`-> 0x004006b2 488b45f8 mov rax, qword [rbp - local_8h] | |||| 0x004006b6 4883c014 add rax, 0x14 | |||| 0x004006ba 0fb610 movzx edx, byte [rax] | |||| 0x004006bd b8d1074000 mov eax, 0x4007d1 | |||| 0x004006c2 0fb600 movzx eax, byte [rax] | |||| 0x004006c5 38c2 cmp dl, al | ||||,=< 0x004006c7 740c je 0x4006d5 | ||||| 0x004006c9 b800000000 mov eax, 0 | ||||| 0x004006ce e818ffffff call sym.failure | ,======< 0x004006d3 eb0a jmp 0x4006df | |||||| ; JMP XREF from 0x004006c7 (sym.main) | |||||`-> 0x004006d5 b800000000 mov eax, 0 | ||||| 0x004006da e8f7feffff call sym.success
Zastavico lahko verjetno pravilno uganete tudi s uporabo strings
:
> strings re1
...
ISCTF{
dv4kr47_r0t_13
...
2. Osnovnošolska matematika
Naloga je po načinu uporabe podobna prejšnji, program poženemo s ./re2 <zastavica>
in nam pove, ali je naša zastavica pravilna. Proces preverjanja zastavica pa je nekoliko kompleksnejši, zato uporaba strings
ne zadošča. V funkciji main
je vsak znak srednjega dela naše zastavice (med {
in }
) spremenjen, nato pa je rezultat primerjan z besedilom ISCTF{zbxuxn_tm_sp_zolo}
. Znaki srednjega dela zastavice so nadomeščeni z vrednostjo, ki je vrne funkcija rotx
. Ta funkcija je klicana z i-tim znakom zastavice, i-tim številom zaporedja, ki za rešitev naloge ni relevantno in i-jem (local_18h
je spremenljivka, uporabljena za napredovanje zanke, local_20h
je dolžina srednjega dela zastavice, local_40h
je naslov stednjega dela zastavice, local_30h
pa je naslov zaporedja):
| ||`-> 0x00400811 c745e8000000. mov dword [rbp - local_18h], 0 | ||,=< 0x00400818 eb40 jmp 0x40085a | .----> 0x0040081a 8b45e8 mov eax, dword [rbp - local_18h] | |||| 0x0040081d 4863d0 movsxd rdx, eax | |||| 0x00400820 488b45c0 mov rax, qword [rbp - local_40h] | |||| 0x00400824 488d1c02 lea rbx, qword [rdx + rax] | |||| 0x00400828 488b45d0 mov rax, qword [rbp - local_30h] | |||| 0x0040082c 8b55e8 mov edx, dword [rbp - local_18h] | |||| 0x0040082f 4863d2 movsxd rdx, edx | |||| 0x00400832 8b0c90 mov ecx, dword [rax + rdx*4] | |||| 0x00400835 8b45e8 mov eax, dword [rbp - local_18h] | |||| 0x00400838 4863d0 movsxd rdx, eax | |||| 0x0040083b 488b45c0 mov rax, qword [rbp - local_40h] | |||| 0x0040083f 4801d0 add rax, rdx | |||| 0x00400842 0fb600 movzx eax, byte [rax] | |||| 0x00400845 0fbec0 movsx eax, al | |||| 0x00400848 8b55e8 mov edx, dword [rbp - local_18h] | |||| 0x0040084b 89ce mov esi, ecx | |||| 0x0040084d 89c7 mov edi, eax | |||| 0x0040084f e8f0fdffff call sym.rotx | |||| 0x00400854 8803 mov byte [rbx], al | |||| 0x00400856 8345e801 add dword [rbp - local_18h], 1 | |||| ; JMP XREF from 0x00400818 (sym.main) | |||`-> 0x0040085a 8b45e8 mov eax, dword [rbp - local_18h] | ||| 0x0040085d 3b45e0 cmp eax, dword [rbp - local_20h] | `====< 0x00400860 7cb8 jl 0x40081a
Funkcija rotx
vsakemu znaku prišteje i, če je to mogoče ne da bi vrednost znaka presegla 'z' (local_4h
je znak, ki ga funkcija spreminja, local_ch
pa je i):
| 0x00400679 807dfc60 cmp byte [rbp - local_4h], 0x60 ; [0x60:1]=248 ; '`' | ,=< 0x0040067d 7e39 jle 0x4006b8 | | 0x0040067f 807dfc7a cmp byte [rbp - local_4h], 0x7a ; [0x7a:1]=0 ; 'z' | ,==< 0x00400683 7f33 jg 0x4006b8 | || 0x00400685 8b45f4 mov eax, dword [rbp - local_ch] | || 0x00400688 89c2 mov edx, eax | || 0x0040068a 0fb645fc movzx eax, byte [rbp - local_4h] | || 0x0040068e 01d0 add eax, edx | || 0x00400690 8845fc mov byte [rbp - local_4h], al | || 0x00400693 807dfc7a cmp byte [rbp - local_4h], 0x7a ; [0x7a:1]=0 ; 'z' | ,===< 0x00400697 7e0c jle 0x4006a5 | ||| 0x00400699 0fb645fc movzx eax, byte [rbp - local_4h] | ||| 0x0040069d 8b55f4 mov edx, dword [rbp - local_ch] | ||| 0x004006a0 29d0 sub eax, edx | ||| 0x004006a2 8845fc mov byte [rbp - local_4h], al | `---> 0x004006a5 0fbe45fc movsx eax, byte [rbp - local_4h] ... | || 0x004006b5 8845fc mov byte [rbp - local_4h], al | ``-> 0x004006b8 0fb645fc movzx eax, byte [rbp - local_4h]
Zdaj ko poznamo algoritem, ga lahko implementiramo v obratni smeri in izračunamo zastavico. Primer rešitve v Python-u:
flag = "zbxuxn_tm_sp_zolo" solution = [] for i in range(len(flag)): char = ord(flag[i]) if (char >= ord('a') and char <= ord('z') and char - i >= ord('a')): solution.append(char - i) else: solution.append(char) print("ISCTF{" + "".join(map(lambda x: chr(x), solution)) + "}") // ISCTF{zavrti_me_se_malo}
Član zmagovalne ekipe Hekerski Sindikat je predlagal enostavnejšo rešitev. Če uporabimo program ltrace, ki spremlja klice funkcij dinamičnih knjižnjic, lahko vidimo primerjavo naše spremenjene zastavice s ciljnim besedilom in rešimo nalogo znak po znak:
> ltrace ./re2 "ISCTF{aaaaaaaaaaaaaaaaa}"
strlen("ISCTF{aaaaaaaaaaaaaaaaa}") = 24
strcmp("ISCTF{abcdefghijklmnopq}", "ISCTF{zbxuxn_tm_sp_zolo}") = -25
puts("Zastavica je napa\304\215na!"Zastavica je napačna!
) = 23
+++ exited (status 1) +++
3. Passage
Rešitev naloge smo poslali v uničenje!
4. Prijava iz varnega sistema
Rešitev naloge zahteva daljši opis, ki bo morda dodan v prihodnosti.