# reverse 2022 edu ctf ## [LAB] Sacred Arts ![](https://i.imgur.com/cNiNySn.png) 先open /tmp/flag ![](https://i.imgur.com/lnSCrMB.png) 沒辦法open的話, print wrong 程式結束 可以open的話,讀裡面的資料,然後 jmp short loc_4010c3 ![](https://i.imgur.com/H3cw7sS.png) loc_4010c3的行為是將 /tmp/flag 裡的 data 做2的補數,交換eax的高八位和低八位,最後跟 byte_40108B 每8個byte做比較,正確的話jmp loc_4010fd 輸出correct ```python with open('./byte_40108B') as f: t = f.readlines() t = [i.strip() for i in t] #xchg al, ah t = [bin(int(i,16))[2:] for i in t] t = [i[:-16] + i[-8:] + i[-16:-8] for i in t] #neg rax t = [int(i, 2)-1 for i in t] mask = int('1'*64, 2) t = [i^mask for i in t] t = [bin(i)[2:] for i in t] s = "" for i in t: i = i.rjust(64, '0') for j in range(0,64,8): s += chr(int(i[j:j+8],2)) print(s[::-1]) ``` 用python 模擬即可得flag ![](https://i.imgur.com/btA36VC.png) ## [LAB] Kekkou ![](https://i.imgur.com/fGF3Dd4.png) 把檔案丟進ida 觀察main function,程式的行為是輸入flag,call sub_11f1, sub_125f再用sub131c的result做判斷,正確就輸出correct ![](https://i.imgur.com/blD8SFL.png) sub_11f1 的行為是傳一個structure *a1和char** flag當參數 ![](https://i.imgur.com/XIksdfc.png) 用 gdb 觀察 sub_11f1 行為,發現做完後會將剛剛的 flag[i] 用linked list的形式 存在 a1 ![](https://i.imgur.com/UnVysrQ.png) sub_125f 的行為是將 linked list 的 data xor byte_4040[idx+2] ![](https://i.imgur.com/SbqByxc.png) sub131c 確認 linked list 的 data 是否和 arr410 相同 ![](https://i.imgur.com/g2mRBUe.png) 把 function 名子改成看得懂的,然後在將剛剛的 sub_125f (對input做xor的function) 設斷點,然後用gdb看 linked-list裡面存的值被xor後變成了多少。 ![](https://i.imgur.com/0fAjv7Y.png) - 將 input做完xor後 存在 linked list 裡的值用 gdb print出來 ( input xor $X_i$ ) xor input = $X_i$, 因此能知道 input 裡每一個 byte 會 xor 甚麼值,然後再把 arr410[i] xor $X_i$ 就能回推出正確的 input 了 ```python with open('xor_output', 'r') as f: t = f.read() after_xor = [t.split('\n')[i] for i in range(1, len(t.split('\n')), 2)] after_xor = [i.split(': ')[1] for i in after_xor] mod = [] for i in range(65): input_i = 49+(i+1) x = input_i ^ int(after_xor[i], 16) mod.append(x) arr410 = "41 92 41 47 EF BC 65 8B F2 6F 75 5F 6D 75 DF 9A 5F B3 8F 61 89 31 61 F5 3F 5D 61 69 8F 21 9D 96 A7 61 5C EC 03 5F 70 3C C0 DC 79 56 6E 25 6F 5F BD DD 72 FF 73 34 69 B5 6D 58 5F 0C 49 40 72 C8 5D" arr410 = [i for i in arr410.split(' ') if i != ''] correct_input = [] for i in range(65): correct_input.append(int(arr410[i],16) ^ mod[i]) t = b"" for i in correct_input: t += (i).to_bytes(1, byteorder='big') print(t) ``` 執行python script後得 ![](https://i.imgur.com/OieBHNa.png) ## [LAB] Why ![](https://i.imgur.com/StuxIz1.png) 用ida看 main的結構,why的行為是檢查輸入值 -10 後是否等於 enc_flag ,是的話 pass = 1 ![](https://i.imgur.com/yPyM3lq.png) ![](https://i.imgur.com/LYY96Iv.png) 從fini_array可以看到有sub_11f8 若 pass 則 correct ![](https://i.imgur.com/7nctkzu.png) gdb ![](https://i.imgur.com/oYm3eOL.png) ida 不知道為甚麼用ida 看是檢查輸入值 -10 後是否等於 enc_flag,gdb是+10 ```python #gdb with open('enc_flag') as f: t = f.read() t = [int(i, 16) - 10 for i in t.split()] t = [chr(i) for i in t] print(''.join(t)) #ida with open('enc_flag') as f: t = f.read() t = [int(i, 16) + 10 for i in t.split()] t = [chr(i) for i in t] print(''.join(t)) ``` -10 跟 +10 都試試看,ida好像寫錯了 ![](https://i.imgur.com/RLhYCtn.png) ## [HW] trace ㄧ ![](https://i.imgur.com/ebxW317.png) 執行一遍,程式行為是輸入flag錯了就 print wrong,用ida 看main,稍微分析後重新命名function name,第十行call了一個奇怪的東西先不管他往下看 ![](https://i.imgur.com/nH5h5Cz.png) - fork_child() : fork 一個process 成功的話執行cs_2022_fall_ouo ![](https://i.imgur.com/KyXMCep.png) - cs_2022_fall_ouo() : 執行/tmp/cs_2022_fall_ouo, argv[1]是 cs_2022_fall_ouo,由此可知剛剛main裡第十行應改是寫了一個elf 到 /tmp/cs_2022_fall_ouo fork child process 後進入一個無限迴圈執行兩個function: single_step_getReg(),readAddr() ![](https://i.imgur.com/lQW3oyF.png) - single_step_getReg() : child 往前走一步然後 load child 的registers 進 regs ![](https://i.imgur.com/YfMtTZO.png) - readAddr() : 若regs_80 (regs+0x80) == 0xe8cbccdeadbeefe8 就把regs_80 換成0x90909090909090,因為90 是 nop 的 machine code,推測regs_80 是program counter的address,若program counter指到的地方存的值是 0xe8cbccdeadbeefe8 就換成 換成0x90909090909090 ![](https://i.imgur.com/W3APrLn.png) 先執行看看剛剛的 child process在幹啥 ![](https://i.imgur.com/xvC7IOV.png) ![](https://i.imgur.com/AJJSGVj.png) 用ida 看 /tmp/cs_2022_fall_ouo在做啥,call了一個奇怪的ptr 還有 int 3,看起來不太合理 ![](https://i.imgur.com/DGbCVxW.png) 用hex editor把0xe8cbccdeadbeefe8 取代成0x90909090909090 ![](https://i.imgur.com/MrzHAbg.png) 再用ida分析可以發現行為變得稍微正常了,plz() 和 give_me_flag() print了一些無關緊要的東西 ![](https://i.imgur.com/7IFHlsE.png) - xor_s2_0_to_0x15() : 把s2 前 15個bytes xor 0x71 ![](https://i.imgur.com/2dzNPxl.png) - compare_and_print_():輸入 flag 若 == s2 就輸出 well done! ```python with open('s2', 'r') as f: t = f.read() t = t.split() t = [int(i,16) for i in t] for i in range(0x17): t[i] ^= 0x71 t = [chr(i) for i in t] print(t) print("".join(t)) ``` 執行後可得 ![](https://i.imgur.com/EdajJK2.png) ## [HW] pwn_myself 執行之後看起來毫無反應就直接exit了,用ida分析找到一個叫做main的function,main function會確認user的權限,權限太低就離開。改用root執行並且在main附近設個中斷點,用gdb 看 callstack 找到 readKeyboard 這個 function ![](https://i.imgur.com/GVYltqJ.png) readKeyboard的行為是讀取鍵盤的輸入經過一連串判斷後進入set_string ![](https://i.imgur.com/v0QqSHx.png) set_string 給arrr[0:44] 賦值,當index == 44 進入 printflag() ![](https://i.imgur.com/afAuB4b.png) print_flag 第十行先設置 sending_buf 若 sending_buf 前48個char 都等於 flag0 用 udp 傳出text,否則傳出 sending_buf,看來這邊的關鍵是第十行的set_buf() ![](https://i.imgur.com/aekPkKD.png) 進去set_buf裡面看,第16行推測是根據剛剛 set_string 裡 arr的內容來加密後賦值給 sending_buf。 ![](https://i.imgur.com/CPQUi6G.png) ![](https://i.imgur.com/ngSkBHW.png) 因為 set_buf() 前面幾行的function點進去看也不知道在幹嘛,且題目提示有使用 openssl,所以用 ida 提供的flair70製作openssl的signature 檔 ![](https://i.imgur.com/xuDLJiT.png) 將剛剛製作的openssl.sig load進ida,可以看到第十五行變成aes128cbc加密,推測這邊的行為是將 text2_near_flag, text1_near_flag 當做iv或key 然後對arr加密後傳給sending_buf,因此將 flag0 用 aes128 解密就能知道要將arr是多少 ![](https://i.imgur.com/dSJSLdI.png) ![](https://i.imgur.com/EmLUdUD.png) --- ## [LAB] AMessageBox ![](https://i.imgur.com/UWjBIZB.png) 觀察程式行為發現隨便輸入個flag會跳出一個msgBox ![](https://i.imgur.com/TNxK0Gf.png) user32.dll->MsgBox 從符號的視窗中將所有跟msgbox有關的function設置breakpoint ![](https://i.imgur.com/V4AWW2p.png) - call stack ![](https://i.imgur.com/hRMxF7Y.png) 從上圖的call stack可以找到疑似處理flag的function ![](https://i.imgur.com/zeo7VuG.png) 第一層的判斷是比較amessagebox_904ac0c699abc2f6.B63018和 flag 兩個string的長度,若相同則進入下一關 ![](https://i.imgur.com/jX2Xz32.png) 第二層的判斷是將flag的每個字元做 rol 2 和 xor 87 後若與b63018的string相同,則進入下一層 ![](https://i.imgur.com/HUEo7oO.png) ```python t = "B5 E5 8D BD 5C 46 36 4E 4E 1E 0E 26 A4 1E 0E 4E 46 06 16 AC B4 3E 4E 16 94 3E 94 8C 94 8C 9C 4E A4 8C 2E 46 8C 6C" for c in t.split(): n = "{:08b}".format(int(c,16) ^ 0x87) n = n[-3:] + n[:-3] print(chr(int(n, 2)), end='') ``` 做完運算結果為 FLAG{8699314d319802ef792b7babac9da58a} ## [LAB] IAT Hook ------------------------- ## [LAB] GetProcAddress------------------------- ## [HW]ooxx ![](https://i.imgur.com/jC4DBKO.png) 遊戲結束時會跳出msgbox ![](https://i.imgur.com/ZkisaA7.png) 從符號裡面把msgbox都設斷點 ![](https://i.imgur.com/5EeQD2Z.png) 從呼叫堆疊裡面找是誰call msgbox ![](https://i.imgur.com/8eqbk3n.png) ![](https://i.imgur.com/PX5xVR2.png) assembly 看不懂用ida看,可以看到這邊有三個條件print出各種不同的結果推測其行為是確認:贏或輸或平手 ![](https://i.imgur.com/SBabL7h.png) 進去check()裡面看,很明顯這邊是判斷如果2連成線對手就贏了,val存的是每個格子裡是X或是O ![](https://i.imgur.com/6YPV2Id.png) 找到val的位置在(.data +d388) ![](https://i.imgur.com/DZe2d3J.png) ![](https://i.imgur.com/qMBwPH9.png) 用cheat engine打開oxox,找到val的位置 7FF6B1B4D338 (ooxx.exe.text+d388) ![](https://i.imgur.com/kgh9xed.png) ![](https://i.imgur.com/kJfP0B6.png) cheat engine有個功能可以看哪些instruction 在修改 指定address的值,隨便走一步後,可以看到這行 count為1的 **mov [rcx+rax*4],00002** 修改了val ![](https://i.imgur.com/H99Y4XP.png) ![](https://i.imgur.com/cpSCPSa.png) 把 **mov [rcx+rax*4],00002** 改成 nop 後對手的行為就開始變得異常了 ![](https://i.imgur.com/gVi5VBX.png) ## [HW] trojan ![](https://i.imgur.com/LOZ1nmm.png) 用 wireshark 開 log.pcapng ![](https://i.imgur.com/hHvOhJL.png) 用 ida 找到 winmain,從 pcapng 推測 while loop 前面幾行是在localhost:19832 bind 一個 tcp server ![](https://i.imgur.com/QmuizEw.png) 用 x64dbg 分析,在 sreeenshot() 可以看到一個 png檔的路徑,png檔打開來看是螢幕截圖 ![](https://i.imgur.com/HJGif31.png) winmain 第十行的 function把 callBack 綁到 (v7+0x64),第十一行的 function 行為是設定socket,最後createThread() ![](https://i.imgur.com/Jo1AXbn.png) callback()的行為是收一個512 bytes的 bufffer,比較其是否等於 "cDqr0hUUz",若等於讀取 **/temp/_____.png** 的 bytes,tcp 回傳 png 檔案 bytes數,然後第二十行的encrypt 對每一個bytes xor,最後把 加密後的 bytes 由 tcp 回傳 ![](https://i.imgur.com/JkPkzSy.png) 把剛剛 log.pcappng tcp 回傳的第二個封包裡 的 bytes 覆蓋 /temp/_______.png,tcp server 再次 callback() 時剛剛的 encrypt() 會把 bytes xor 成未加密的模樣並回傳給client ```python import socket host = "127.0.0.1" port = 19832 # The same port as used by the server s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.sendall(b'cDqr0hUUz1') data = s.recv(1024) # drop data = s.recv(999999) with open('./data.png', 'wb') as f: f.write(data) s.close() ``` python script執行完,可得一png檔 ![](https://i.imgur.com/ES9MLKL.png) --- ## [LAB] Exception [12] ## [LAB] TLS Callback [11] ## [LAB] Meow [11] ## [HW] dropper ![](https://i.imgur.com/fOBKh0D.png) 用 die看發現 dropper 是用 upx 加殼 ![](https://i.imgur.com/yMbK3rR.png) 用 upx 脫殼 ![](https://i.imgur.com/iQayck9.png) ida 看 main,可以觀察到 CS_2002 字樣和一些疑似加密解密的動作 ![](https://i.imgur.com/7cDkaUY.png) 用 x64dbg 分析 callstack 程式最後是跑到這行就停住了,這邊的行為是 sleep(9a7ec800),手動把 ecx 改成0,往下走幾步就能看到flag了 ![](https://i.imgur.com/xNgTomI.png) ___ ## note IAT - import address table gdb x *(void**)0x600fe8 As @zwol mentioned in the comment, gdb needs type information about the operand to decide its size. This type casting tells gdb that 0x600fe8 is a pointer to a pointer. python [gdb.execute('ni') for i in range(50)] ![](https://i.imgur.com/G6aFAuk.png) --- ## x86 instruction - lea (load effective address) - lea eax, [ebp+8] - eax = (ebp + 8) - mov eax, [ebp+8] - eax = *(ebp + 8) --- ## 看瞴 ![](https://i.imgur.com/3QvBhmj.png) https://youtu.be/1EAV4XeQ8C4?t=5451 ### PEB(process enviroment block) ![](https://i.imgur.com/jxY66MC.png) ![](https://i.imgur.com/Ap4k26v.png) ### handle ### hmodule