Qakbot APIHash(2022/6の検体)

はじめに

この記事の内容は2022年6月の検体で確認したQbotについての記事です。

API Hashについて

API Hashとはマルウェアの解析を妨害する手法の一つである。呼び出すWindows APIをハッシュ化しておき、実行時に動的に解決をすることで、そのマルウェアがどういう機能を持っているかがわかりにくくなる。

Qakbotは通信の機能をもっているが、Qakbotの読み込んでいるImport関数やライブラリを見ても、通信に関連するものが見当たらない。通信に限らずAPIを隠すことで解析をしづらくしている。

Qakbotの読み込むライブラリ

Qakbotの読み込むAPI

QakbotのAPI解決の流れ

QakbotのAPI Hashを解決しているコードは次のようになっている。

API解決の関数

この部分では次のような処理を行って、APIのアドレスを取得している(実際に渡されるハッシュ値は複数渡されることもあり、最終的にはAPIのアドレスを格納したヒープ領域のポインタが返ってくる)

API解決処理の概要

QakbotのAPI Hashで使われている関数

Qakbot内では、APIの名前をもとに次のような計算を行っている。

Qakbotのハッシュ値計算①

Qakbotのハッシュ値計算②

①の図中で呼び出しているm_calc_hashが②の処理である。②の関数で計算した値と0x218fe95bをxorした値が、最初に渡したハッシュ値と一致していれば、APIのアドレスを解決し、ヒープ領域に格納するという処理を行っている。

なお、②で行っている計算はCRC32の処理を少しアレンジしているもののため、ハッシュ値 = CRC32(APIの名前) ^ 0x218fe95bという計算となっている。

次の図は、API解決をする際に渡されるデータの一部である。

図中の上から4Byteの0x1e4e54d6LoadLibraryAハッシュ値である。実際に計算をすると、先に示した計算方法とハッシュ値が一致することがわかる。

Pythonでのハッシュ値計算確認

検体のハッシュ値

  • 2022/6の検体:117a60cb0cde4199a78f99d4f6eb5f50

Qbotマルウェア解析

はじめに

Qakbotと呼ばれるばらまき型メールに添付されているウイルスがある。

事前にメールを盗み返信を装う形で攻撃メールを送付するといったことや、ショートカットファイルやFollinaを使って攻撃をすることも報告されていたと思う。

Qakbotの解析については、英語で書かれた解析記事はたくさんあるが日本語で書かれた記事は少ないと勝手に思っている。そのため、私が解析した内容を気ままにまとめる。更新頻度については期待しないでほしい。

Qakbot解析

QakbotのC2通信先について(2022/6、2022/9時点) - ごちうさ民の覚え書き

Qakbotの文字列暗号化(2022/6,2022/9) - ごちうさ民の覚え書き

Qakbot APIHash(2022/6の検体) - ごちうさ民の覚え書き

Qakbotの文字列暗号化(2022/6,2022/9)

はじめに

この記事の内容は2022年6月、9月時点での解析で確認したQbotについての記事です。

アンパック後Qakbotの文字列

アンパックした後の検体を見てみると、図のようにほとんど解析に有用な文字列は少ない。「%u.%u.%u.%u」のようなIPアドレスと思われるフォーマットはあるが、解析に有用そうな文字列は見当たらない。

Qakbotに限らず、マルウェアの文字列とかはアンパック後も暗号化されていることがある。ネタバレをするが、Qakbotの文字列はxorで暗号化されている。

Qakbotの復号関数

以下はQakbotの文字列復号関数の一部だが意味のない処理が大量に含まれている。私が確認した検体ではGhidraのデコンパイル画面で389行もあった。

しかし、389行のほとんどは意味のない処理のため、しっかり読み込んで整理すると、実際のところは次のような復号関数になる。

Qakbotの文字列復号

文字列復号の関数は以下の図のように呼び出されている(なお、確認した検体では同様の処理が4つあった。)

検体に含まれている2種類のデータ(下図のDAT_1001e5b0DAT_1001f5d8)をxorすることで文字列を復号している。

復号するスクリプトは次の通り、実際の検体は呼び出す際に復号したい文字列だけを得られるように数値を渡している。(スクリプト中のhex_numは呼び出す際に渡す数値をいれる)

def decrypt_string(hex_num, dat_addr1, dat_addr2):
    decrypted_str = ""
    # calc size of decrypted string
    cp_hex = hex_num
    while True:
        if (getBytes(dat_addr2.add(cp_hex % 0x5a),1)[0] & 0xff) == (getBytes(dat_addr1.add(cp_hex),1)[0] & 0xff):
            break
        cp_hex += 1
    size = cp_hex - hex_num

    # decrypted string
    for i in range(size):
        decrypted_str += chr((getBytes(dat_addr2.add((hex_num + i) % 0x5a),1)[0] & 0xff) ^ (getBytes(dat_addr1.add(hex_num + i),1)[0] & 0xff) )
    return decrypted_str 

この復号するスクリプトを使うことで次のようになり、復号した文字列が得られる。

単純にxorだけしてみた結果

Ghidra上のPythonで検体内のデータを単純にxorすると次のような結果となり、復号できていることがわかる。どんな文字列があるか知りたいときはさくっとやってしまうとよい。

検体のハッシュ値

今回のブログを書く上で確認した検体のハッシュ値は次の通り

  • 2022/6の検体:117a60cb0cde4199a78f99d4f6eb5f50
  • 2022/9の検体:3fd6ff929bb62358cee961d45ff1471d