# Super Secret Login - zer0pts CTF 2021 ###### tags: `zer0pts CTF 2021` `reversing` ## 調査 stringsで見るとこんな文字列が出てくる。 ``` This is a third-party compiled AutoIt script. ``` AutoIT製と思われるので例のdecompilerに投げる。 ここがメイン処理: ``` $hgui = GUICreate("Super Secret Login", 0x160, 0x6d, + -1, + -1) GUISetFont(0x9, 0x190, 0x0, "Courier New") $hlabel = GUICtrlCreateLabel("Enter Password:", 0x19, 0x19, 0x6d, 0x12) $hinput = GUICtrlCreateInput('', 0x8c, 0x17, 0xbc, 0x14) GUICtrlSetFont(+ -1, 0x9, 0x190, 0x0, "Courier New", 0x5) $hbutton = GUICtrlCreateButton("Login!", 0x7a, 0x47, 0x6d, 0x19) GUICtrlSetStyle(+ -1, $BS_FLAT) $hcaret = _WinAPI_CreateCaret($hgui, 0xa, 0x2) $mserverkey = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" _WinAPI_ShowCaret($hgui) GUISetState() While 0x1 $hmsg = GUIGetMsg() Switch $hmsg Case $GUI_EVENT_CLOSE Exit Case $hbutton onclick() EndSwitch WEnd Func onclick() Local $text = GUICtrlRead($hinput) Local $fuckpython = StringLen($text) If $fuckpython == 0x0 Then Return Local $oldfuck = $fuckpython GUICtrlSetState($hbutton, $GUI_DISABLE) Local $fuck = fuckit($text, $mserverkey) GUICtrlSetState($hbutton, $GUI_ENABLE) $fuckpython = "7188bb1563e5702342e22a856ad3df1cfa9729b4115d8cfb1f07a0c6fc916477f02f77d656834379b32e" If String(BinaryToString($fuck)) == ('' & $fuckpython) Then MsgBox(0x40, "Good Job!", "Read the instructions and see if you can submit It!", 0x0, $hgui) Else MsgBox(0x40, ":(", "You have a long way to go buddy!" & @CRLF & ":(", 0x0, $hgui) EndIf EndFunc ; -> onclick Func fuckit($data, $key) Local $opcode = "0x5589e56031c081ec00010000880404403d0001000075f531f631db0fb6043401c389f0b92b00000031d2f7f18b45140fbe0c1001cb0fb6db8a0c348a041c880434880c1c83c60181fe0001000075cc" & _ "31f631d231c942460fb6d28a041401c10fb6c98a1c0c88040c881c1400d80fb6c08b7d0c8a5c37ff8a040431c38b7d08e8100000003b751075cc81c40001000061c9c210005053eb1858c1eb048a1c18" & _ "885c77fe5b83e30f8a1c18885c77ff58c3e8e3ffffff30313233343536373839616263646566" Local $codebuffer = DllStructCreate("byte[" & BinaryLen($opcode) & "]") DllStructSetData($codebuffer, 0x1, Binary($opcode)) Local $buffer = DllStructCreate("byte[" & StringLen($data) & "]") DllStructSetData($buffer, 0x1, $data) Local $destbuffer = DllStructCreate("char[" & (StringLen($data) * 0x2 + 0x1) & "]") DllCall("user32.dll", "none", "CallWindowProc", "ptr", DllStructGetPtr($codebuffer), "ptr", DllStructGetPtr($destbuffer), "ptr", DllStructGetPtr($buffer), "int", StringLen($data), "str", $key) Local $ret = DllStructGetData($destbuffer, 0x1) $buffer = 0x0 $codebuffer = 0x0 Return $ret EndFunc ; -> fuckit ``` onclikの処理を読むと、`fuckit($text, $mserverkey)`で暗号化か何かをして、その結果と`7188bb1563e5702342e22a856ad3df1cfa9729b4115d8cfb1f07a0c6fc916477f02f77d656834379b32e`が等しいかを確認している。 ## fuckitの解析 ``` Func fuckit($data, $key) Local $opcode = "0x5589e56031c081ec00010000880404403d0001000075f531f631db0fb6043401c389f0b92b00000031d2f7f18b45140fbe0c1001cb0fb6db8a0c348a041c880434880c1c83c60181fe0001000075cc" & _ "31f631d231c942460fb6d28a041401c10fb6c98a1c0c88040c881c1400d80fb6c08b7d0c8a5c37ff8a040431c38b7d08e8100000003b751075cc81c40001000061c9c210005053eb1858c1eb048a1c18" & _ "885c77fe5b83e30f8a1c18885c77ff58c3e8e3ffffff30313233343536373839616263646566" Local $codebuffer = DllStructCreate("byte[" & BinaryLen($opcode) & "]") DllStructSetData($codebuffer, 0x1, Binary($opcode)) Local $buffer = DllStructCreate("byte[" & StringLen($data) & "]") DllStructSetData($buffer, 0x1, $data) Local $destbuffer = DllStructCreate("char[" & (StringLen($data) * 0x2 + 0x1) & "]") DllCall("user32.dll", "none", "CallWindowProc", "ptr", DllStructGetPtr($codebuffer), "ptr", DllStructGetPtr($destbuffer), "ptr", DllStructGetPtr($buffer), "int", StringLen($data), "str", $key) Local $ret = DllStructGetData($destbuffer, 0x1) $buffer = 0x0 $codebuffer = 0x0 Return $ret EndFunc ; -> fuckit ``` なるほど機械語を実行してるっぽい。 呼び出しは ``` f(res, text, textlen, key) ``` みたいな感じ。 ## 機械語の解析 objdumpで読む。 ``` 0: 55 push ebp 1: 89 e5 mov ebp,esp 3: 60 pusha 4: 31 c0 xor eax,eax 6: 81 ec 00 01 00 00 sub esp,0x100 c: 88 04 04 mov BYTE PTR [esp+eax*1],al f: 40 inc eax 10: 3d 00 01 00 00 cmp eax,0x100 15: 75 f5 jne 0xc 17: 31 f6 xor esi,esi 19: 31 db xor ebx,ebx 1b: 0f b6 04 34 movzx eax,BYTE PTR [esp+esi*1] 1f: 01 c3 add ebx,eax 21: 89 f0 mov eax,esi 23: b9 2b 00 00 00 mov ecx,0x2b 28: 31 d2 xor edx,edx 2a: f7 f1 div ecx 2c: 8b 45 14 mov eax,DWORD PTR [ebp+0x14] 2f: 0f be 0c 10 movsx ecx,BYTE PTR [eax+edx*1] 33: 01 cb add ebx,ecx 35: 0f b6 db movzx ebx,bl 38: 8a 0c 34 mov cl,BYTE PTR [esp+esi*1] 3b: 8a 04 1c mov al,BYTE PTR [esp+ebx*1] 3e: 88 04 34 mov BYTE PTR [esp+esi*1],al 41: 88 0c 1c mov BYTE PTR [esp+ebx*1],cl 44: 83 c6 01 add esi,0x1 47: 81 fe 00 01 00 00 cmp esi,0x100 4d: 75 cc jne 0x1b 4f: 31 f6 xor esi,esi 51: 31 d2 xor edx,edx 53: 31 c9 xor ecx,ecx 55: 42 inc edx 56: 46 inc esi 57: 0f b6 d2 movzx edx,dl 5a: 8a 04 14 mov al,BYTE PTR [esp+edx*1] 5d: 01 c1 add ecx,eax 5f: 0f b6 c9 movzx ecx,cl 62: 8a 1c 0c mov bl,BYTE PTR [esp+ecx*1] 65: 88 04 0c mov BYTE PTR [esp+ecx*1],al 68: 88 1c 14 mov BYTE PTR [esp+edx*1],bl 6b: 00 d8 add al,bl 6d: 0f b6 c0 movzx eax,al 70: 8b 7d 0c mov edi,DWORD PTR [ebp+0xc] 73: 8a 5c 37 ff mov bl,BYTE PTR [edi+esi*1-0x1] 77: 8a 04 04 mov al,BYTE PTR [esp+eax*1] 7a: 31 c3 xor ebx,eax 7c: 8b 7d 08 mov edi,DWORD PTR [ebp+0x8] 7f: e8 10 00 00 00 call 0x94 84: 3b 75 10 cmp esi,DWORD PTR [ebp+0x10] 87: 75 cc jne 0x55 89: 81 c4 00 01 00 00 add esp,0x100 8f: 61 popa 90: c9 leave 91: c2 10 00 ret 0x10 94: 50 push eax 95: 53 push ebx 96: eb 18 jmp 0xb0 98: 58 pop eax 99: c1 eb 04 shr ebx,0x4 9c: 8a 1c 18 mov bl,BYTE PTR [eax+ebx*1] 9f: 88 5c 77 fe mov BYTE PTR [edi+esi*2-0x2],bl a3: 5b pop ebx a4: 83 e3 0f and ebx,0xf a7: 8a 1c 18 mov bl,BYTE PTR [eax+ebx*1] aa: 88 5c 77 ff mov BYTE PTR [edi+esi*2-0x1],bl ae: 58 pop eax af: c3 ret b0: e8 e3 ff ff ff call 0x98 ``` ここまで解析すると、初期化部分だけでRC4の臭いが強い。 ```c // ebp+0x14: key // ebp+0x10: len // ebp+0x0c: data // ebp+0x08: dst int f(char *dst, char *data, int len, char *key) { int i; char buf[0x100]; for (i = 0; i < 0x100; i++) buf[i] = i; unsigned int j = 0; for (i = 0; i < 0x100; i++) { j += buf[i]; j += key[i % 0x2b]; unsigned char tmp = buf[i]; buf[i] = buf[j % 0x100]; buf[j % 0x100] = tmp; } // not analyzed yet } ``` ということでRC4にかける。 ```python= def KSA(key): keylength = len(key) S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % keylength]) % 256 S[i], S[j] = S[j], S[i] # swap return S def PRGA(S): i = 0 j = 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] # swap K = S[(S[i] + S[j]) % 256] yield K def RC4(key): S = KSA(key) return PRGA(S) if __name__ == '__main__': key = b'https://www.youtube.com/watch?v=dQw4w9WgXcQ' plaintext = bytes.fromhex('7188bb1563e5702342e22a856ad3df1cfa9729b4115d8cfb1f07a0c6fc916477f02f77d656834379b32e') def convert_key(s): return [c for c in s] key = convert_key(key) keystream = RC4(key) import sys for c in plaintext: sys.stdout.write(chr(c ^ next(keystream))) print("") ``` 終わり。 ``` $ python solve.py zeropts{aut0it!_n0_!l0l!_y0u_g0t_tr0ll3d!} ``` と思ってtask.yml確認したらフラグが違うっぽい。 まぁ確かによく見ると`zer0pts`じゃなくて`zeropts`になってる。 ## 動的解析 コード読んでも分からんので動的解析する。 `DllCall`で止めるためにsupersecretlogin.exeモジュール中の`LoadLibrary`および`GetProcAddress`の参照箇所すべてにブレークポイントを付けて、適当な文字列でLoginする。 すると、まずuser32.dllがLoadLibraryに渡され、さらにGetProcAddressが呼ばれる様子が分かる。 GetProcAddressの戻り値でCallWindowProcのアドレスが貰えるので、そこにブレークポイントを付ける。 この時のスタックはこのような状態で、確かにkey, len, data, dst, codeが渡されている。 ![](https://i.imgur.com/7RDaEC4.png) で、機械語の結果を見てもやはり ``` 7188bb1563e5702342e22a856ad3df1cfa9729b4115d8cfb1f07a0c6fc916477f02f77d656834379b32e ``` にはなっている :thinking_face: ## リソース リソースをダンプするとAutoItのスクリプト(の中間言語)があるが、そのEOSマーカーが2つある。1つ目のマーカーの後ろがx86の関数の出だしっぽい見た目をしているので機械語としてobjdumpする。 ``` 00000000 <.data>: 0: 55 push ebp 1: 89 e5 mov ebp,esp 3: 60 pusha 4: 64 a1 30 00 00 00 mov eax,fs:0x30 a: 8b 58 08 mov ebx,DWORD PTR [eax+0x8] d: 68 6a ae 9c db push 0xdb9cae6a 12: 68 08 b7 01 00 push 0x1b708 17: e8 9a 00 00 00 call 0xb6 1c: 8d bb 74 7f 0c 00 lea edi,[ebx+0xc7f74] ... ``` ここらへんにbreakpointを付けてみる。 0xb6はたぶんdll-lessでwin32api取るやつ。動的に調べると最初のはNtQueryInformationprocessであると分かる。これはたぶんanti-debugなのでスキップする。 次の ``` 33: 68 8e ac 34 00 push 0x34ac8e 38: 68 88 8f 03 00 push 0x38f88 3d: e8 74 00 00 00 call 0xb6 ``` はGetWindowTextWと分かる。 あとはなんかxorっぽいのしてるので、対象の箇所のデータをダンプする。 ![](https://i.imgur.com/VI0DwJk.png) この辺。 ``` from ptrlib import xor fake = b'zeropts{aut0it!_n0_!l0l!_y0u_g0t_tr0ll3d!}' with open("hoge", "rb") as f: f.seek(0x5a) key = f.read() print(xor(fake, key)) ``` できた。 ``` zer0pts{1n_th3_w1ld_m4lw4r3s_us3_4ut0-i7!} ```