893 views
# 2016 Flare-On Challenge ( Part-1 ) >Author: Lays > http://blog.l4ys.tw/ > [Flare-On Challenge](http://flare-on.com) 是由 FireEye 舉辦的類似 CTF 的線上競賽,題型主要以 Reverse 為主,時間長達一個多月 去年的 Flare-On 因為太忙所以不小心就錯過了,身為一個逆向工程狂熱份子當然不能放過今年的機會 台灣似乎沒有多少人在玩,最後破台的只有我跟 `Lucas`,期待明年可以有更多人嘗試 ![](https://www.fireeye.com/content/dam/fireeye-www/blog/images/Flare-On%203%20Solutions/image3.png) 雖然已經有官方版本的 [Solution](https://www.fireeye.com/blog/threat-research/2016/11/2016_flare-on_challe.html) ,但還是順手紀錄了一下解題過程 - [Part1](https://hackmd.io/s/HyOUlP9gg) ( 1 - 5 ) - [Part2](https://hackmd.io/s/B1Pr8gAlg) ( 6 - 10 ) --- ## Challenge 1: challenge1.exe 程式會將輸入字串做 `base64` 之後與特定結果比對 需要注意的是 `base64` 的 table 為 `ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/` ```c int main() { char Buffer[128]; // [sp+0h] [bp-94h]@1 _BYTE *v2; // [sp+80h] [bp-14h]@1 const char *v3; // [sp+84h] [bp-10h]@1 HANDLE stdin; // [sp+88h] [bp-Ch]@1 HANDLE stdout; // [sp+8Ch] [bp-8h]@1 DWORD NumberOfBytesWritten; // [sp+90h] [bp-4h]@1 stdout = GetStdHandle(STD_INPUT_HANDLE); stdin = GetStdHandle(STD_OUTPUT_HANDLE); WriteFile(stdout, "Enter password:\r\n", 18u, &NumberOfBytesWritten, 0); ReadFile(stdin, Buffer, 128u, &NumberOfBytesWritten, 0); v2 = MyBase64(Buffer, NumberOfBytesWritten - 2); if ( !strcmp(v2, "x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q") ) WriteFile(stdout, "Correct!\r\n", 0xBu, &NumberOfBytesWritten, 0); else WriteFile(stdout, "Wrong password\r\n", 0x11u, &NumberOfBytesWritten, 0); return 0; } ``` Solution: ```python #!/usr/bin/python import string import base64 STANDARD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" CUSTOM_ALPHABET = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/" DECODE_TRANS = string.maketrans(CUSTOM_ALPHABET, STANDARD_ALPHABET) def decode(input): return base64.b64decode(input.translate(DECODE_TRANS)) print decode("x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q") ``` Flag: `sh00ting_phish_in_a_barrel@flare-on.com` --- ## Challenge 2: DudeLocker.exe 題目是個 ransomeware,使用 `Windows Crypto API` 來加密檔案 提供了一個加密過的附檔 `BusinessPapers.doc` ### 程式行為 1. 檢查 `%USER%\Desktop\Briefcase` 目錄存在 2. 透過 `GetVolumeInformationA` 檢查磁碟序號是否為 `0x7DAB1D35` 3. 使用磁碟序號解密資料,產生一組 AES-256 key,內容固定為 ` sha1("thosefilesreallytiedthefoldertogether")` 的結果 4. 透過 AES-256 加密 `Briefcase` 目錄下的所有檔案,IV 為檔案名稱的 md5 hash 5. 將桌布修改為資源檔中的 `ve_vant_ze_money.jpg`,告知使用者檔案已被 `RSA-4096` 加密 ![](https://i.imgur.com/DL34UlW.jpg) ### 解密 分析後得知並非使用 `RSA-4096` 而是 `AES-256`,可直接用同一把 key 解密檔案 最簡單的做法是直接 patch binary,將 `CryptEncrypt` 的呼叫換成 `CryptDecrypt`,讓程式自己來替我們解密檔案 `CryptEncrypt` 比 `CryptDecrypt` 多了一個 `dwBufLen` 的參數,需要把第一個 `PUSH` 指令 `NOP` 掉 ``` > 0040165A 90 NOP 0040165B 8D4D E4 LEA ECX, DWORD PTR SS:[EBP-0x1C] 0040165E 51 PUSH ECX 0040165F 8B55 F8 MOV EDX, DWORD PTR SS:[EBP-0x8] 00401662 52 PUSH EDX 00401663 6A 00 PUSH 0x0 00401665 0FB645 FF MOVZX EAX, BYTE PTR SS:[EBP-0x1] 00401669 50 PUSH EAX 0040166A 6A 00 PUSH 0x0 0040166C 8B4D 08 MOV ECX, DWORD PTR SS:[EBP+0x8] 0040166F 8B11 MOV EDX, DWORD PTR DS:[ECX] 00401671 52 PUSH EDX > 00401672 E8 0D1CEA75 CALL ADVAPI32.CryptDecrypt > 00401677 90 NOP ``` 另外還需要 patch `GetVolumeInformationA` 的結果為 `0x7DAB1D35`,直接修改 `0x401040` 的 return value: ```asm 00401040 B8 351DAB7D MOV EAX, 0x7DAB1D35 00401045 C3 RETN ``` 將 `BusinessPapers.doc` 放到 `Briefcase` 後執行即可解密出一張圖片,得到 Flag ![](https://i.imgur.com/jHX9L1X.jpg) Flag: `clOse_t3h_f1le_On_th1s_One@flare-on.com` > 其實這題我一開始的解法是直接把整隻程式自己寫了一遍,加上解密功能,結果 Debug 了半天... > > 解完後與 Lucas 討論後才被點醒,可以直接 patch binary 而 LingLing 則是直接用 hexrays decompile 之後, 配合 `defs.h` 再小修改就能編譯成執行檔,IDA Pro 實在是太強大了 > > 覺得傻傻直接重寫一份出來的我真是笨蛋... Orz --- ## Challenge 3: unknown ### 程式行為: 1. 會先呼叫 `wcsrchr(argv[0], 'r');` 取得檔名中字元 `r` 位置後面的子字串 2. 將取得的子字串進行一連串 hash,透過修改過的 `md5` 及 `RC4` 解出一串長度為 104 bytes 的密文 3. 從 `argv[1]` 中每次依序取一個字元字元進行 hash,每次結果 為 4 bytes,最後與密文比對 ### 分析 檔案名稱中並沒有字元 `r`,但可以在程式中找到 pdb path 為 `C:\extraspecial.pdb`,可推測出執行檔原始名稱為 `extraspecial.exe` hash 函數行為如下: ```python def hash(s): v = 0 for i in s: v = ord(i) + 37 * v return v ``` 而 hash `argv[1]` 的方式為: ```python for i in range(27): hash(chr(argv[1][i]) + chr(ord("`") + i) + "FLARE On!") ``` 簡單的窮舉一下就能得到 Flag: ```python #!/usr/bin/env python l = [0xee613e2f, 0xde79eb45, 0xaf1b2f3d, 0x8747bbd7, 0x739ac49c, 0xc9a4f5ae, 0x4632c5c1, 0xa0029b24, 0xd6165059, 0xa6b79451, 0xe79d23ba, 0x8aae92ce, 0x85991a18, 0xfee05899, 0x430c7994, 0x1ab9f36f, 0x70c42481, 0x05bd27cf, 0xc4ff6e6f, 0x5a77847c, 0xdd9277b3, 0x25843cff, 0x5fdca944, 0x8ee42896, 0x2ae961c7, 0xa77731da] def hash(s): v = 0 for i in s: v = ord(i) + 37 * v return v out = "" for i, h in enumerate(l): for c in range(ord(' '), ord('~')): if hash(chr(c) + chr(ord("`") + i) + "FLARE On!") & 0xffffffff == h: out += chr(c) print out ``` Flag: `Ohs0pec1alpwd@flare-on.com` --- ## Challenge 4: flareon2016challenge.dll 只給了一個 dll,以 `ordinary` export 了 51 個函數: 前面 48 個函數的長相都差不多,都是在對 `byte_10007014` 這個 array 做操作: ```c signed int flareon2016challenge_1() { byte_10007014[dword_10007010-- % 16u] += 79; return 51; } signed int flareon2016challenge_2() { char v0; // cl@1 v0 = __ROL1__(byte_10007014[dword_10007010 % 0x10u], 7); byte_10007014[dword_10007010-- % 0x10u] = v0; return 8; } signed int flareon2016challenge_3() { char v0; // cl@1 v0 = __ROL1__(byte_10007014[dword_10007010 % 0x10u], 1); byte_10007014[dword_10007010-- % 0x10u] = v0; return 26; } ... ``` 第 51 個函數會拿 `byte_1007014` array 來當 key 解密一些資料: ```c void flareon2016challenge_51() { char subkey[140]; // [sp+0h] [bp-90h]@1 set_cast128_subkey(subkey, byte_10007014, 16u); cast128_decrypt_round16(subkey, byte_10007060, byte_10007060, 6672); printf("play me a song, have me play along..\n"); } ``` 仔細觀察這些 export 的函數會發現每個函數都會 return 一個不同的數字,猜測就是呼叫的順序 透過 `hexrays decompiler` 配合簡單的文字取代及排序得到函數跟回傳值的對應表: ``` 1: 51 2: 8 3: 26 4: 17 5: 47 6: 35 7: 16 8: 13 9: 4 10: 25 11: 21 12: 9 13: 28 14: 11 15: 42 16: 29 17: 33 18: 12 19: 38 20: 36 21: 27 22: 2 23: 6 24: 41 25: 3 26: 32 27: 23 28: 45 29: 39 30: 15 31: 44 32: 22 33: 31 34: 19 35: 1 36: 7 37: 10 38: 40 39: 24 40: 48 41: 43 42: 18 43: 46 44: 34 45: 5 46: 14 47: 20 48: 37 ``` 排序一下可以知道第一個呼叫的函數應該要是 `flareon2016challenge_30` 寫個 python script 來得到呼叫順序,依照順序呼叫函數並 dump 出解密的資料: > 寫這篇時才發現可以用 `c_char` 直接從 memory 讀取資料 ```python with open("list.txt") as f: for l in f.readlines(): t = l.split(":") no, ret = int(t[0]), int(t[1]) d[no] = ret chain = [30,] while True: if chain[-1] not in d: break nxt = d[chain[-1]] chain.append(nxt) print chain # [30, 15, 42, 18, 12, 9, 4, 17, 33, 31, 44, 34, 19, 38, 40, 48, 37, 10, 25, 3, 26, 32, 22, 2, 8, 13, 28, 45, 5, 47, 20, 36, 7, 16, 29, 39, 24, 41, 43, 46, 14, 11, 21, 27, 23, 6, 35, 1, 51] dll = cdll.LoadLibrary("./flareon2016challenge.dll") for i in chain: dll[i]() # dump memory from dll base + 0x7060 data = (c_char * 6672).from_address(dll._handle + 0x7060) open("./dump", "wb").write(data.raw) ``` Dump 出來的是一個 PE file,丟進 IDA Pro: ```c int main() { Beep(0x1B8u, 0x1F4u); Beep(0x1B8u, 0x1F4u); Beep(0x1B8u, 0x1F4u); Beep(0x15Du, 0x15Eu); Beep(0x20Bu, 0x96u); Beep(0x1B8u, 0x1F4u); Beep(0x15Du, 0x15Eu); Beep(0x20Bu, 0x96u); Beep(0x1B8u, 0x3E8u); Beep(0x293u, 0x1F4u); Beep(0x293u, 0x1F4u); Beep(0x293u, 0x1F4u); Beep(0x2BAu, 0x15Eu); Beep(0x20Bu, 0x96u); Beep(0x19Fu, 0x1F4u); Beep(0x15Du, 0x15Eu); Beep(0x20Bu, 0x96u); Beep(0x1B8u, 0x3E8u); return 0; } ``` 這對應的是 dll 中的第 50 個函數: ```c void __cdecl flareon2016challenge_50(DWORD dwFreq, DWORD dwDuration) { Beep(dwFreq, dwDuration); byte_10007028[dword_10008DD4] -= dwFreq; byte_10007028[dword_10008DD4] ^= dwDuration; if ( ++dword_10008DD4 == 18 ) flareon2016challenge_49(); } ``` 用同樣的參數呼叫 18 次 `flareon2016challenge_50` ,程式會播放一段 `Star Wars` 的音樂,接著印出 flag Solution: ```python from ctypes import * chain = [30, 15, 42, 18, 12, 9, 4, 17, 33, 31, 44, 34, 19, 38, 40, 48, 37, 10, 25, 3, 26, 32, 22, 2, 8, 13, 28, 45, 5, 47, 20, 36, 7, 16, 29, 39, 24, 41, 43, 46, 14, 11, 21, 27, 23, 6, 35, 1, 51] dll = cdll.LoadLibrary("./flareon2016challenge.dll") for i in chain: dll[i]() # data = (c_char * 6672).from_address(dll._handle + 0x7060) # open("./dump", "wb").write(data.raw) # dll[49]() Beep = dll[50] Beep(0x1B8, 0x1F4); Beep(0x1B8, 0x1F4); Beep(0x1B8, 0x1F4); Beep(0x15D, 0x15E); Beep(0x20B, 0x96); Beep(0x1B8, 0x1F4); Beep(0x15D, 0x15E); Beep(0x20B, 0x96); Beep(0x1B8, 0x3E8); Beep(0x293, 0x1F4); Beep(0x293, 0x1F4); Beep(0x293, 0x1F4); Beep(0x2BA, 0x15E); Beep(0x20B, 0x96); Beep(0x19F, 0x1F4); Beep(0x15D, 0x15E); Beep(0x20B, 0x96); Beep(0x1B8, 0x3E8); ``` Flag: `f0ll0w_t3h_3xp0rts@flare-on.com` --- ## Challenge 5: smokestack.exe ### 程式行為 會從 `argv[1]` 拿 10 個字元進行複雜的運算,將結果進行 100 次的 md5 hash 後當成 RC4 key 解出 flag 分析 `0x401610` 之後發現進行運算的其實是一個 stack based vm,執行前會把 `argv[1]` 依序 push 進 vm stack ### 分析 - 有 14 個指令集,每個指令為 2 bytes - 四個 register: `R0` `R1` `SP` `IP`,大小為 2 bytes - 14 個指令的處理函數位於 `0x40DEE0`,`op code` 分別為 0 ~ 13,大致分為 - push / pop - add / sub - lsl / lsr - xor - neg - cmp - push by flag - jmp - load reg - write reg - nop - pcode 位於 `0x40A140` 寫一個簡單的 dissasembler 來幫助理解 `pcode` 流程: ```python #!/usr/bin/env python # -*- coding: utf-8 -*- INS = [0x0000,0x0021,0x0002,0x0000,0x0091,0x0008,0x0000,0x0016,0x0000,0x000C,0x0009,0x000A,0x000B,0x0000,0x0000,0x000C,0x0002,0x000C,0x0000,0x0000,0x001D,0x000A,0x000B,0x0000,0x0000,0x0063,0x0002,0x000C,0x0000,0x0000,0x0018,0x0006,0x0000,0x0054,0x0008,0x0000,0x0033,0x0000,0x0029,0x0009,0x000A,0x000B,0x0000,0x0000,0x002C,0x0002,0x000C,0x0000,0x0000,0x003D,0x000A,0x0000,0x000E,0x0001,0x000B,0x0000,0x0000,0x0059,0x0002,0x000C,0x0000,0x000B,0x0000,0x0000,0x0000,0x000C,0x0001,0x0000,0x0009,0x000C,0x0000,0x000B,0x0001,0x0000,0x0002,0x0002,0x000C,0x0001,0x000B,0x0000,0x0000,0x0001,0x0003,0x000C,0x0000,0x000B,0x0000,0x0000,0x0000,0x0008,0x0000,0x0047,0x0000,0x0060,0x0009,0x000A,0x000C,0x0000,0x000B,0x0001,0x0003,0x0000,0x005D,0x0008,0x0000,0x007C,0x0000,0x006E,0x0009,0x000A,0x000B,0x0000,0x0000,0x0007,0x0003,0x000C,0x0000,0x0000,0x005B,0x000C,0x0001,0x0000,0x0087,0x000A,0x0000,0x0036,0x000C,0x0001,0x000B,0x0000,0x000B,0x0001,0x0002,0x000C,0x0001,0x000B,0x0001,0x0000,0x0058,0x0002,0x0006,0x0000,0x00F9,0x0008,0x0000,0x00A0,0x0000,0x0096,0x0009,0x000A,0x000B,0x0000,0x0000,0x004D,0x0006,0x000C,0x0000,0x0000,0x00AE,0x000A,0x0000,0x0323,0x0000,0x012B,0x0003,0x000C,0x0001,0x000B,0x0000,0x000B,0x0001,0x0002,0x000C,0x0001,0x000C,0x0001,0x000B,0x0001,0x000B,0x0001,0x0000,0x0001,0x0003,0x000C,0x0001,0x0000,0x0003,0x0002,0x000B,0x0001,0x0000,0x0000,0x0008,0x0000,0x00B2,0x0000,0x00C7,0x0009,0x000A,0x0007,0x0000,0xFE77,0x0008,0x0000,0x00D8,0x0000,0x00D1,0x0009,0x000A,0x000B,0x0000,0x0000,0x0058,0x0002,0x000C,0x0000,0x0000,0x0003,0x0004,0x0000,0x008C,0x0002,0x0000,0x6094,0x0008,0x0000,0x00EE,0x0000,0x00E7,0x0009,0x000A,0x000B,0x0000,0x0000,0x00E7,0x0002,0x000C,0x0000,0x000B,0x0001,0x0002,0x0000,0x000C,0x0006,0x0000,0x0074,0x0008,0x0000,0x0107,0x0000,0x00FD,0x0009,0x000A,0x000B,0x0000,0x0000,0x0009,0x0003,0x000C,0x0000,0x0000,0x011D,0x000A,0x0000,0x000A,0x000C,0x0001,0x000B,0x0001,0x0000,0x0001,0x0003,0x000C,0x0001,0x000B,0x0001,0x0000,0x0000,0x0008,0x0000,0x010B,0x0000,0x011D,0x0009,0x000A,0x0000,0x0006,0x0005,0x0000,0x1DC0,0x0008,0x0000,0x0133,0x0000,0x0129,0x0009,0x000A,0x000B,0x0000,0x0000,0x0071,0x0002,0x000C,0x0000,0x0000,0x013D,0x000A,0x000B,0x0000,0x0000,0x0077,0x0002,0x000C,0x0000,0x0000,0x013D,0x000A,0x0000,0x0016,0x0002,0x0000,0x000E,0x0003,0x0000,0x0061,0x0008,0x0000,0x0153,0x0000,0x014C,0x0009,0x000A,0x000B,0x0000,0x0000,0x002C,0x0003,0x000C,0x0000,0x000C,0x0001,0x000B,0x0001,0x0000,0x212C,0x000B,0x0001,0x0000,0x0001,0x0003,0x000C,0x0001,0x0000,0x0007,0x0003,0x000B,0x0001,0x0000,0x0000,0x0008,0x0000,0x0159,0x0000,0x016E,0x0009,0x000A,0x0000,0x01CA,0x0006,0x0000,0x1FF5,0x0008,0x0000,0x0181,0x0000,0x017A,0x0009,0x000A,0x000B,0x0000,0x0000,0x0012,0x0002,0x000C,0x0000,0x000D] ip = 0 sp = 9 r0 = 0 r1 = 0 while ip < len(INS): op = INS[ip] print "%04X:" % ip, if op == 0: ip += 1 arg = INS[ip] print "PUSH 0x%X" % arg ip += 1 elif op == 1: print "POP" ip += 1 elif op == 2: print "ADD" ip += 1 elif op == 3: print "SUB" ip += 1 elif op == 4: print "LSL" ip += 1 elif op == 5: print "LSR" ip += 1 elif op == 6: print "XOR" ip += 1 elif op == 7: print "NEG" ip += 1 elif op == 8: print "CMP" ip += 1 elif op == 9: print "FPUSH" ip += 1 elif op == 10: print "JMP" ip += 1 elif op == 11: ip += 1 arg = INS[ip] reg = ["R0", "R1", "SP", "IP"][arg] print "LOAD %s" % reg ip += 1 elif op == 12: ip += 1 arg = INS[ip] reg = ["R0", "R1", "SP", "IP"][arg] print "WRITE %s" % reg ip += 1 elif op == 13: print "NOP" ip += 1 else: print "UNKNOWN: %04X" % op ip += 1 ``` ``` $ python disasm.py 0000: PUSH 0x21 0002: ADD 0003: PUSH 0x91 0005: CMP 0006: PUSH 0x16 0008: PUSH 0xC 000A: FPUSH 000B: JMP 000C: LOAD R0 000E: PUSH 0xC 0010: ADD 0011: WRITE R0 0013: PUSH 0x1D 0015: JMP 0016: LOAD R0 0018: PUSH 0x63 001A: ADD 001B: WRITE R0 001D: PUSH 0x18 001F: XOR 0020: PUSH 0x54 0022: CMP 0023: PUSH 0x33 0025: PUSH 0x29 0027: FPUSH 0028: JMP 0029: LOAD R0 002B: PUSH 0x2C 002D: ADD 002E: WRITE R0 0030: PUSH 0x3D 0032: JMP 0033: PUSH 0xE 0035: POP 0036: LOAD R0 0038: PUSH 0x59 003A: ADD 003B: WRITE R0 003D: LOAD R0 003F: PUSH 0x0 0041: WRITE R1 0043: PUSH 0x9 0045: WRITE R0 0047: LOAD R1 0049: PUSH 0x2 004B: ADD 004C: WRITE R1 004E: LOAD R0 0050: PUSH 0x1 0052: SUB 0053: WRITE R0 0055: LOAD R0 0057: PUSH 0x0 0059: CMP 005A: PUSH 0x47 005C: PUSH 0x60 005E: FPUSH 005F: JMP 0060: WRITE R0 0062: LOAD R1 0064: SUB 0065: PUSH 0x5D 0067: CMP 0068: PUSH 0x7C 006A: PUSH 0x6E 006C: FPUSH 006D: JMP 006E: LOAD R0 0070: PUSH 0x7 0072: SUB 0073: WRITE R0 0075: PUSH 0x5B 0077: WRITE R1 0079: PUSH 0x87 007B: JMP 007C: PUSH 0x36 007E: WRITE R1 0080: LOAD R0 0082: LOAD R1 0084: ADD 0085: WRITE R1 0087: LOAD R1 0089: PUSH 0x58 008B: ADD 008C: XOR 008D: PUSH 0xF9 008F: CMP 0090: PUSH 0xA0 0092: PUSH 0x96 0094: FPUSH 0095: JMP 0096: LOAD R0 0098: PUSH 0x4D 009A: XOR 009B: WRITE R0 009D: PUSH 0xAE 009F: JMP 00A0: PUSH 0x323 00A2: PUSH 0x12B 00A4: SUB 00A5: WRITE R1 00A7: LOAD R0 00A9: LOAD R1 00AB: ADD 00AC: WRITE R1 00AE: WRITE R1 00B0: LOAD R1 00B2: LOAD R1 00B4: PUSH 0x1 00B6: SUB 00B7: WRITE R1 00B9: PUSH 0x3 00BB: ADD 00BC: LOAD R1 00BE: PUSH 0x0 00C0: CMP 00C1: PUSH 0xB2 00C3: PUSH 0xC7 00C5: FPUSH 00C6: JMP 00C7: NEG 00C8: PUSH 0xFE77 00CA: CMP 00CB: PUSH 0xD8 00CD: PUSH 0xD1 00CF: FPUSH 00D0: JMP 00D1: LOAD R0 00D3: PUSH 0x58 00D5: ADD 00D6: WRITE R0 00D8: PUSH 0x3 00DA: LSL 00DB: PUSH 0x8C 00DD: ADD 00DE: PUSH 0x6094 00E0: CMP 00E1: PUSH 0xEE 00E3: PUSH 0xE7 00E5: FPUSH 00E6: JMP 00E7: LOAD R0 00E9: PUSH 0xE7 00EB: ADD 00EC: WRITE R0 00EE: LOAD R1 00F0: ADD 00F1: PUSH 0xC 00F3: XOR 00F4: PUSH 0x74 00F6: CMP 00F7: PUSH 0x107 00F9: PUSH 0xFD 00FB: FPUSH 00FC: JMP 00FD: LOAD R0 00FF: PUSH 0x9 0101: SUB 0102: WRITE R0 0104: PUSH 0x11D 0106: JMP 0107: PUSH 0xA 0109: WRITE R1 010B: LOAD R1 010D: PUSH 0x1 010F: SUB 0110: WRITE R1 0112: LOAD R1 0114: PUSH 0x0 0116: CMP 0117: PUSH 0x10B 0119: PUSH 0x11D 011B: FPUSH 011C: JMP 011D: PUSH 0x6 011F: LSR 0120: PUSH 0x1DC0 0122: CMP 0123: PUSH 0x133 0125: PUSH 0x129 0127: FPUSH 0128: JMP 0129: LOAD R0 012B: PUSH 0x71 012D: ADD 012E: WRITE R0 0130: PUSH 0x13D 0132: JMP 0133: LOAD R0 0135: PUSH 0x77 0137: ADD 0138: WRITE R0 013A: PUSH 0x13D 013C: JMP 013D: PUSH 0x16 013F: ADD 0140: PUSH 0xE 0142: SUB 0143: PUSH 0x61 0145: CMP 0146: PUSH 0x153 0148: PUSH 0x14C 014A: FPUSH 014B: JMP 014C: LOAD R0 014E: PUSH 0x2C 0150: SUB 0151: WRITE R0 0153: WRITE R1 0155: LOAD R1 0157: PUSH 0x212C 0159: LOAD R1 015B: PUSH 0x1 015D: SUB 015E: WRITE R1 0160: PUSH 0x7 0162: SUB 0163: LOAD R1 0165: PUSH 0x0 0167: CMP 0168: PUSH 0x159 016A: PUSH 0x16E 016C: FPUSH 016D: JMP 016E: PUSH 0x1CA 0170: XOR 0171: PUSH 0x1FF5 0173: CMP 0174: PUSH 0x181 0176: PUSH 0x17A 0178: FPUSH 0179: JMP 017A: LOAD R0 017C: PUSH 0x12 017E: ADD 017F: WRITE R0 0181: NOP ``` 由於靜態分析還是會有些運算過程比較難分析,因此再修改出一個類似模擬器的版本,並在 stack 中放入 0xAA 開頭的資料幫助我們快速找出比對處: ```python #!/usr/bin/env python # -*- coding: utf-8 -*- INS = [0x0000,0x0021,0x0002,0x0000,0x0091,0x0008,0x0000,0x0016,0x0000,0x000C,0x0009,0x000A,0x000B,0x0000,0x0000,0x000C,0x0002,0x000C,0x0000,0x0000,0x001D,0x000A,0x000B,0x0000,0x0000,0x0063,0x0002,0x000C,0x0000,0x0000,0x0018,0x0006,0x0000,0x0054,0x0008,0x0000,0x0033,0x0000,0x0029,0x0009,0x000A,0x000B,0x0000,0x0000,0x002C,0x0002,0x000C,0x0000,0x0000,0x003D,0x000A,0x0000,0x000E,0x0001,0x000B,0x0000,0x0000,0x0059,0x0002,0x000C,0x0000,0x000B,0x0000,0x0000,0x0000,0x000C,0x0001,0x0000,0x0009,0x000C,0x0000,0x000B,0x0001,0x0000,0x0002,0x0002,0x000C,0x0001,0x000B,0x0000,0x0000,0x0001,0x0003,0x000C,0x0000,0x000B,0x0000,0x0000,0x0000,0x0008,0x0000,0x0047,0x0000,0x0060,0x0009,0x000A,0x000C,0x0000,0x000B,0x0001,0x0003,0x0000,0x005D,0x0008,0x0000,0x007C,0x0000,0x006E,0x0009,0x000A,0x000B,0x0000,0x0000,0x0007,0x0003,0x000C,0x0000,0x0000,0x005B,0x000C,0x0001,0x0000,0x0087,0x000A,0x0000,0x0036,0x000C,0x0001,0x000B,0x0000,0x000B,0x0001,0x0002,0x000C,0x0001,0x000B,0x0001,0x0000,0x0058,0x0002,0x0006,0x0000,0x00F9,0x0008,0x0000,0x00A0,0x0000,0x0096,0x0009,0x000A,0x000B,0x0000,0x0000,0x004D,0x0006,0x000C,0x0000,0x0000,0x00AE,0x000A,0x0000,0x0323,0x0000,0x012B,0x0003,0x000C,0x0001,0x000B,0x0000,0x000B,0x0001,0x0002,0x000C,0x0001,0x000C,0x0001,0x000B,0x0001,0x000B,0x0001,0x0000,0x0001,0x0003,0x000C,0x0001,0x0000,0x0003,0x0002,0x000B,0x0001,0x0000,0x0000,0x0008,0x0000,0x00B2,0x0000,0x00C7,0x0009,0x000A,0x0007,0x0000,0xFE77,0x0008,0x0000,0x00D8,0x0000,0x00D1,0x0009,0x000A,0x000B,0x0000,0x0000,0x0058,0x0002,0x000C,0x0000,0x0000,0x0003,0x0004,0x0000,0x008C,0x0002,0x0000,0x6094,0x0008,0x0000,0x00EE,0x0000,0x00E7,0x0009,0x000A,0x000B,0x0000,0x0000,0x00E7,0x0002,0x000C,0x0000,0x000B,0x0001,0x0002,0x0000,0x000C,0x0006,0x0000,0x0074,0x0008,0x0000,0x0107,0x0000,0x00FD,0x0009,0x000A,0x000B,0x0000,0x0000,0x0009,0x0003,0x000C,0x0000,0x0000,0x011D,0x000A,0x0000,0x000A,0x000C,0x0001,0x000B,0x0001,0x0000,0x0001,0x0003,0x000C,0x0001,0x000B,0x0001,0x0000,0x0000,0x0008,0x0000,0x010B,0x0000,0x011D,0x0009,0x000A,0x0000,0x0006,0x0005,0x0000,0x1DC0,0x0008,0x0000,0x0133,0x0000,0x0129,0x0009,0x000A,0x000B,0x0000,0x0000,0x0071,0x0002,0x000C,0x0000,0x0000,0x013D,0x000A,0x000B,0x0000,0x0000,0x0077,0x0002,0x000C,0x0000,0x0000,0x013D,0x000A,0x0000,0x0016,0x0002,0x0000,0x000E,0x0003,0x0000,0x0061,0x0008,0x0000,0x0153,0x0000,0x014C,0x0009,0x000A,0x000B,0x0000,0x0000,0x002C,0x0003,0x000C,0x0000,0x000C,0x0001,0x000B,0x0001,0x0000,0x212C,0x000B,0x0001,0x0000,0x0001,0x0003,0x000C,0x0001,0x0000,0x0007,0x0003,0x000B,0x0001,0x0000,0x0000,0x0008,0x0000,0x0159,0x0000,0x016E,0x0009,0x000A,0x0000,0x01CA,0x0006,0x0000,0x1FF5,0x0008,0x0000,0x0181,0x0000,0x017A,0x0009,0x000A,0x000B,0x0000,0x0000,0x0012,0x0002,0x000C,0x0000,0x000D] stack = [0xAA01, 0xAA02, 0xAA03, 0xAA04, 0xAA05, 0xAA06, 0xAA07, 0xAA08, 0xAA09, 0xAA0A] ip = 0 sp = 9 r0 = 0 r1 = 0 def pop(): global sp sp -= 1 return stack.pop() def push(v): global sp sp += 1 stack.append(v) while ip < len(INS): op = INS[ip] print "%04X:" % ip, if op == 0: ip += 1 v = INS[ip] ip += 1 push(v) print "PUSH 0x%X" % v elif op == 1: print "POP" ip += 1 pop() elif op == 2: print "ADD", ip += 1 v1 = pop() v2 = pop() push(v1 + v2) print "(0x%04X + 0x%04X) = 0x%04X" % (v1, v2, v1 + v2) elif op == 3: print "SUB", ip += 1 v1 = pop() v2 = pop() push(v2 - v1) print "(0x%04X - 0x%04X) = 0x%04X" % (v2, v1, v2 - v1) elif op == 4: print "LSL", ip += 1 v1 = pop() v2 = pop() v = ((v2 << (16 - v1)) | (v2 >> v1)) & 0xffff push(v) print "(0x%04X << 0x%04X) = 0x%04X" % (v2, v1, v) elif op == 5: print "LSR", ip += 1 v1 = pop() v2 = pop() v = ((v2 >> (16 - v1)) | (v2 << v1)) & 0xffff push(v) print "(0x%04X >> 0x%04X) = 0x%04X" % (v2, v1, v) elif op == 6: print "XOR", ip += 1 v1 = pop() v2 = pop() push(v2 ^ v1) print "(0x%04X ^ 0x%04X) = 0x%04X" % (v1, v2, v2 ^ v1) elif op == 7: print "NEG", ip += 1 v = pop() push(~v & 0xffff) print " 0x%04X (PUSH 0x%04X)" % (v, ~v & 0xffff) elif op == 8: print "CMP", ip += 1 v1 = pop() v2 = pop() print "(%04X == %04X) ?" % (v1, v2), if v1 == v2: push(1) print "PUSH 0x1" else: push(0) print "PUSH 0x0" elif op == 9: print "FPUSH", ip += 1 v1 = pop() v2 = pop() if stack.pop() == 1: push(v1) print "(0x%04X)" % v1 else: push(v2) print "(0x%04X)" % v2 elif op == 10: print "JMP", v = pop() print "%04X" % v ip = v elif op == 11: ip += 1 arg = INS[ip] reg = ["R0", "R1", "SP", "IP"][arg] print "LOAD %s" % reg, ip += 1 if reg == "R0": v = r0 elif reg == "R1": v = r1 elif reg == "SP": v = sp elif reg == "IP": v = ip - 1 push(v) print "(PUSH %04X)" % v elif op == 12: ip += 1 arg = INS[ip] reg = ["R0", "R1", "SP", "IP"][arg] print "WRITE %s" % reg, ip += 1 v = pop() if reg == "R0": r0 = v elif reg == "R1": r1 = v elif reg == "SP": sp = v elif reg == "IP": ip = v print "(%s = %04X)" % (reg, v) elif op == 13: print "NOP" ip += 1 else: print "UNKNOWN: %04X" % op ip += 1 ``` ``` $ python emu.py 0000: PUSH 0x21 0002: ADD (0x0021 + 0xAA0A) = 0xAA2B 0003: PUSH 0x91 0005: CMP (0091 == AA2B) ? PUSH 0x0 0006: PUSH 0x16 0008: PUSH 0xC 000A: FPUSH (0x0016) 000B: JMP 0016 0016: LOAD R0 (PUSH 0000) 0018: PUSH 0x63 ... ``` 以上例來說就可以發現 `argv[1][9]` + `0x21` 必須等於 `0x91`, 因此 `argv[1][9]` 為 `0x91-0x21` = `0x70 (p)` 結合靜態與動態分析方式後得出 `argv[1]` 必須為 `kYwxCbJoLp`,執行後得到 Flag Flag: `A_p0p_pu$H_&_a_Jmp@flare-on.com` ---