4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

脆弱エンジニアのAdvent Calendar 2024

Day 12

【CyberDefenders】$tealer Lab【Walkthrough】

Last updated at Posted at 2024-12-11

これは脆弱エンジニアの Advent Calendar 2024の12日目の記事です。

はじめに

本記事は CyberDefenders(以下リンク参考)の「$tealer Lab」にチャレンジした際のWalkthroughになります

※本チャレンジについてはRed側のペネトレというよりはBlue側の分析力を問われるものになります。CTFだとRevとかForensicとかの分野のチャレンジが詰まってる感じです。
※今回のチャレンジはMalware解析メインでしたね。

チャレンジ開始前

問題について

以下の画像の「Download Lab Files」に問題ファイルのリンクがあります。
アーカイブファイルで圧縮されているので仮想環境で解凍してください。
※ホストで解凍しないでください。中には本物のマルウェアが入ってたりします。

image.png

環境

この CyberDefenders を解く際には仮想環境でマルウェア解析やメモリフォレンジックを行う環境を用意する必要があります。
今回は以下のような環境を用意しました。

今回はWindows 10の環境を利用しました。
主にBinaryNinjaを利用しました。

Q1

The provided sample is fully unpacked. How many sections does the sample contain?

Malwareの基本的な構成や簡易なPacking技術を判断するためにDiEへ食わせます。

1.png
2.png
エントロピー高めなので難読化はしっかり入ってそう。この結果からセクション数は判断できます。
4n4lDetectorにも食わせます。

3.png
追加情報はなさそうですね。

Q2

How many imported windows APIs are being used by the sample?

pestuidoに食わせます。
4.png
これでわかりますね。

Q3

The sample is resolving the needed win APIs at run-time using API hashing. Looking at the DllEntryPoint, which function is responsible for resolving the wanted APIs?

とりあえずどういった難読化をされているかを事前に把握できると楽なのでcapaに食わせます。

5.png
RC4、XORが気になりますね。Heavens gateもあってアンチデバッグもありそう。
ここで静的解析を行うためBinary Ninjaに食わせます。エントリーポイントから見ていきます。

6.png
sub_6015C0にハッシュ化されたような文字列を渡しています。数回呼び出されてそうなのでここら辺がAPI Hashの関数なのかなと予測できます。

そのままhashdb_automatedで調べてみます。
7.png
別の0xa8d05acbの方もダメだったので、この値をそのまま使う感じではなさそう。

Q4

Looking inside the function described in question 3, which function is responsible for locating & retrieving the targetted module (DLL)?

sub_6015C0の中身を見てみるとreturn付近に以下のようなデコンパイル結果があります。
8.png
sub_607564が第一引数をもらってるのでおそらくそれがDLLの復号化、sub_6067c8がその結果を受けて第二引数を受けているのでAPIの復号化と予測できます。

sub_607564の中身を確認します。
9.png
予測あってそうですね。

Q5

What type of hashing is being used for the API hashing technique?

sub_607564の内部を調べているとsub_61D620の関数を見つけます。
中身を見ると以下のデコンパイル結果となります。
11.png
ぱっと見以下のような動作はCRC32の動作に似た挙動のように見えます。

eax_14 u>> 8 ^ var_420[zx.d(*(arg1 + (ecx_3 << 1)) ^ eax_14.b)]

Q6

What is the address of the function which performs the hashing?

Q5を見つけたところですね。

Q7

What key is being used for XORing the hashed names?

sub_607564の中に以下が見つかります。
先ほどのsub_61D620の結果eax_7にXORして第一引数と比較しています。
12.png
この値がXORKeyと予想してAPI Hash解決できなかった0xa8d05acbを調べます。
13.png
14.png
あってそうですね!

Auto Resolve API Hash

このままHashで難読化されたデコンパイル結果を眺めていても解析しにくいので、自動でAPI名を解決してコメントを残すBinary Ninjaのスクリプトを作成します。APIの名前解決はedxmovでハッシュを渡しているのでそのオペランドから取ってきます。

add_comment_1.py
import requests ,time, re
from binaryninja import *

def hash_lookup(offset):
    try:
        while True: 
            hunt_url = 'https://hashdb.openanalysis.net/hunt'
            hash_url = 'https://hashdb.openanalysis.net/hash'
            time.sleep(2)
            # Request the user to enter the hashing API values
            hashing_api_input = offset
            hashing_apis = [int(hash_value, 0) for hash_value in hashing_api_input.split(',')]
            for hashing_api in hashing_apis:
                # Create the payload for the hunt request
                hashdb_req = {"hashes": [hashing_api]}
                # Perform the hunt request
                hunt_req = requests.post(hunt_url, json=hashdb_req)
                # Check if there was a hit in the search
                hits = hunt_req.json()['hits']
                if hits:
                    # Extract the algorithm from the hit
                    algorithm = hits[0]['algorithm']
                    # Resolve the hash with the found algorithm
                    hash_resolve = requests.get(f"{hash_url}/{algorithm}/{hashing_api}")
                    # Extract DLL and API information
                    string_info = hash_resolve.json()['hashes'][0]['string']
                    dll_value = string_info['modules'][0]
                    api_value = string_info['api']
                    # Display the desired information
                    print(f"\nHashing Algorithm: {algorithm}")
                    print(f"DLL: {dll_value}")
                    print(f"API: {api_value}\n")
                    return api_value
                else:
                    print(f"\nNo match found for hash {hashing_api}")
                    return None
    except Exception as e: # 例外処理
        print(f"Error occurred: {e}")
        return None

def is_hex(text):
    pattern = r'^0x[0-9a-fA-F]+$'
    return bool(re.match(pattern, text))


def add_comment(bv, address, text):
    try:
        bv.get_functions_containing(address)[0].set_comment_at(address, text)
    except Exception as e:  # 例外処理
        print(f"Error occurred: {e}")
        return

def gather_offsets(bv, xref_address):
    new_address = xref_address
    # 指定されたアドレスが含まれている関数を取得
    functions = bv.get_functions_containing(xref_address)
    if not functions:
        print("Function not found at address:", hex(xref_address))
        return None
    while new_address >= new_address - 0x30: #大体ここくらいまで調べる。
        # 現在のアドレスにおける命令を取得
        instruction_length = bv.get_instruction_length(new_address)
        if instruction_length == 0:
            new_address -= 0x1
            continue
        # 現在の命令を取得(ディスアセンブリ)
        instruction_text = bv.get_disassembly(new_address)
        if "mov" in instruction_text and "edx" in instruction_text:  # "mov","edx"命令を探す
            operands = instruction_text.split()
            if len(operands) > 2:
                print(operands)
                # 3番目のオペランド
                if is_hex(str(operands[2])):
                    return operands[2]
                else:
                    pass
        new_address -= 0x1
    return None

def locate_xrefs(bv, function_address):
    xref_list = []
    func = bv.get_function_at(function_address)
    for xref in bv.get_code_refs(function_address):
        if xref.address not in xref_list:
            xref_list.append(xref.address)
    return xref_list

def main_fuc():
    func_addr = 0x006067c8
    xref_list = locate_xrefs(bv, func_addr) #呼び出し元
    print(xref_list)
    for xref in xref_list:
        offset = gather_offsets(bv, xref) #XOR前のoffset
        if offset and is_hex(str(offset)):
            offset = int(offset, 16)^0x38ba5c7b #XOR key
            print(hex(offset))
            decrypted_string = hash_lookup(str(offset))
            time.sleep(1)
            if decrypted_string:
                add_comment(bv, xref, decrypted_string)
        else:
            pass

main_fuc()
print("Done!")

これBinary Ninjaで回していきます。
15-1.png
終りました。コメントが残っているか確認します。
15-2.png
イケてそう!このAPIはHeavens gateとかで使われてそう。
sub_6067c8の関数はAPIfunc_Resolveとか名前を適当につけてます。
sub_607564Dll_resolveとか(命名規則...)

Q8

What information is being accessed at the address 0X60769A?
DllBaseの表記からすぐに判断できると思われる。それに関する名称を入れればいい。
image.png

Q9

Looking inside the function described in question 3, which function is responsible for locating & retrieving the targetted API from the module export table?
これはすでに予測出来てますね。

Q10

Diving inside the function described in question 8, what is being accessed at offset 0X3C within the first passed parameter?
0x3Cを見たらe_lfanewIMAGE_NT_HEADERSへの筋道というのを覚えておいて損はないです。
スクリーンショット 2024-12-03 214043.png

Q11

Which windows API is being resolved at the address 0X5F9E47 ?
Q7で見てますね。
14.png

Q12

Looking inside sub_607980, which DLL is being resolved?
関数を見てみます。
16.png
0x38ba5c7bとXORした値をhashdb-cliで確認します。
17.png

Q13

Also Looking inside sub_607980, which API is being resolved?
先ほど回したScriptでコメントされてます。

Q14

What is the appropriate data type of the only argument at function sub_607D40?
関数を見てみます。
18-1.png
argがeaxで各HEX値と比較されてます。この値を調べてみるといいかもですね。
18-2.png
例外コードっぽい。というわけで修正した結果の関数を以下に記す。
18-3.png

Q15

After reverse-engineering sub_607980 and knowing its purpose, Which assembly instruction is being abused for further anti-analysis complication? (especially when running the sample)

AntiDebugSeekerを使ってみる。

19-1.png
19-2.png
対象の関数では見つからなかった。
というわけで直接見てみると、その関数から呼び出されてるsub_607d40で以下のディスアセンブル結果での命令を見つける。
19-3.png
末尾のint3これは有名かなと思う。以下の記事などを参考にしてみてほしい。

Q16

After reverse-engineering sub_607980 and knowing its purpose, Which assembly instruction is being used for altering the process execution flow? (Also adds anti-disassembly complication)

見るだけ。

Q17

There are important encrypted strings in the .data section. Which encryption algorithm is being used for decryption?

capaの結果から判断できる。

Q18

What is the address of the function that is responsible for strings decryption?

Binary Ninjaの拡張機能を使う。
20.png
この関数を確認しにいく。
21.png
とても長ったらしい関数なのですが内部を探っていると、とある関数sub_61e5d0が見つかります。
22.png
0x100ループ...見つけたぞRC4。

Q19

What are the two first decrypted words (strings) at 0X629BE8?

先ほどの関数が呼び出しているXrefを適当に探ります。すると第二引数が0x28で安定して渡されており、おそらく鍵の長さ、Offsetであると推測できます。以下に変数名を変更した関数sub_61e5d0(RC4)を記します。
23-0.png
問題で言われている箇所の前0x28バイト長がKeyと予想できます。完全にGUESSです。この0x28バイト以前は0x00のバイトで埋められてますしね。
23-1.png
選択部分がKeyと予想できるので、これで復号を試してみます。
23-2.png
ダメだった。。。

適当にRC4関数の前の挙動を追っていると、気になる関数が2つ呼ばれているのが分かります。
以下は関数名変更後の表示ですが、大まかに何をやっているかわかるはずです。
23-3.png
中身はこんな感じ。
23-4.png
23-5.png
というわけで、鍵を反転させて、それで復号してみます。
23-6.png
23-7.png
復号出来ました!

Q20

What is the key used for decrypting the strings in question 16?

keyは先ほどの問題が解けていれば解けるはず!

Q21

What is the length (in bytes) of the used key in question 16?

wininet辺りのAPI関数が欲しかったのですが、以前回したPythonスクリプトでは全てのAPI関数を復号してコメントを残せているわけではないので、見つかりませんでした。
Wrap関数を用意してAPI復号関数sub_6067C8に渡してるパターンもあるからです。
というわけでwrapしてる関数sub_6015c0を探して、先ほどと同様にコメントを残すスクリプトを記述します。

add_comment_2.py
import requests ,time, re
from binaryninja import *

def hash_lookup(offset):
    try:
        while True: 
            hunt_url = 'https://hashdb.openanalysis.net/hunt'
            hash_url = 'https://hashdb.openanalysis.net/hash'
            time.sleep(2)
            # Request the user to enter the hashing API values
            hashing_api_input = offset
            hashing_apis = [int(hash_value, 0) for hash_value in hashing_api_input.split(',')]
            for hashing_api in hashing_apis:
                # Create the payload for the hunt request
                hashdb_req = {"hashes": [hashing_api]}
                # Perform the hunt request
                hunt_req = requests.post(hunt_url, json=hashdb_req)
                # Check if there was a hit in the search
                hits = hunt_req.json()['hits']
                if hits:
                    # Extract the algorithm from the hit
                    algorithm = hits[0]['algorithm']
                    # Resolve the hash with the found algorithm
                    hash_resolve = requests.get(f"{hash_url}/{algorithm}/{hashing_api}")
                    # Extract DLL and API information
                    string_info = hash_resolve.json()['hashes'][0]['string']
                    dll_value = string_info['modules'][0]
                    api_value = string_info['api']
                    # Display the desired information
                    print(f"\nHashing Algorithm: {algorithm}")
                    print(f"DLL: {dll_value}")
                    print(f"API: {api_value}\n")
                    return api_value
                else:
                    print(f"\nNo match found for hash {hashing_api}")
                    return None
    except Exception as e: # 例外処理
        print(f"Error occurred: {e}")
        return None

def is_hex(text):
    pattern = r'^0x[0-9a-fA-F]+$'
    return bool(re.match(pattern, text))


def add_comment(bv, address, text):
    try:
        bv.get_functions_containing(address)[0].set_comment_at(address, text)
    except Exception as e:  # 例外処理
        print(f"Error occurred: {e}")
        return

def gather_offsets(bv, xref_address):
    new_address = xref_address
    # 指定されたアドレスが含まれている関数を取得
    functions = bv.get_functions_containing(xref_address)
    if not functions:
        print("Function not found at address:", hex(xref_address))
        return None
    count = 0
    while new_address >= new_address - 0x30: #大体ここまで調べる。
        # 現在のアドレスにおける命令を取得
        instruction_length = bv.get_instruction_length(new_address)
        if instruction_length == 0:
            new_address -= 0x1
            continue  
        # 現在の命令を取得(ディスアセンブリ)
        instruction_text = bv.get_disassembly(new_address)
        if "push" in instruction_text:  # 2つめの"push"命令を探す
            operands = instruction_text.split()
            if len(operands) > 1:
                print(operands)
                if is_hex(str(operands[1])) and count == 1:
                    return operands[1]
                elif is_hex(str(operands[1])) and count == 0:
                    count += 1
                else:
                    pass
        new_address -= 0x1
    return None

def locate_xrefs(bv, function_address):
    xref_list = []
    func = bv.get_function_at(function_address)
    for xref in bv.get_code_refs(function_address):
        if xref.address not in xref_list:
            xref_list.append(xref.address)
    return xref_list

def main_fuc():
    func_addr = 0x006015c0
    xref_list = locate_xrefs(bv, func_addr) #呼び出し元
    print(xref_list)
    for xref in xref_list:
        offset = gather_offsets(bv, xref) #XOR前のoffset
        if offset and is_hex(str(offset)):
            offset = int(offset, 16)^0x38ba5c7b #XOR key
            print(hex(offset))
            decrypted_string = hash_lookup(str(offset))
            time.sleep(1)
            if decrypted_string:
                add_comment(bv, xref, decrypted_string)
        else:
            pass

main_fuc()
print("Done!")

APIのハッシュはDLLのハッシュの前にStackにPushされているので、2つ目のpush命令を探してます。

このスクリプトを回して以下の出力を確認しました。
24-1.png
おー、使ってそうですね。
24-2.png
ここらの関数にwininetのAPI関数が終結してます。

Q23

What is the first C&C IP address in the embedded configuration?

InternetConnectWAPIの第二引数辺りを確認すればアドレスが見えるはずです。

25-2.png
25-1.png
このAPIが使われている部分を確認します。
25-3.png
色々追っていたのですが以下のdata構造の中にC2アドレスが難読化されてそうです。
と言ってもわけわからな過ぎた。。。
25-6.png
わけがわからなかったので先人の知恵を借ります。Reversingなので自力で難読化解除したかったのですが、脅威インテル利用します。

HashをVTに投げます。
25-7.png
Dridexマルウェアぽいですね。こいつを調べてみると、以下のブログにヒットします。

ここにいいToolの情報が記載されています。

これでDridexマルウェアのコンフィグを抽出します。
25-8 - コピー.png
大体見えましたね。ありがとう。

Q24

What is the port associated with the first C&C IP address?

先ほどのToolで見えます。

Q25

How many C&C IP addresses are in the sample configuration?

先ほどのToolで見えます。

Q26

What is the address of the function which downloads additional modules to extend the malware functionality?

HttpSendRequestWのAPIを呼び出している以下の関数が怪しいですね。
25-4.png

最後に

最初動的解析が一切できずに鬼ムズで静的解析に挑んでました。見てみるとアンチデバッグもりもりだったのでそりゃそうかともなりました。
BinaryNinjaのいい練習になったと思います。
Malware解析時間溶ける。16進数がこびり付く。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?