Qakbot APIHash(2022/6の検体)
はじめに
この記事の内容は2022年6月の検体で確認したQbotについての記事です。
API Hashについて
API Hashとはマルウェアの解析を妨害する手法の一つである。呼び出すWindows APIをハッシュ化しておき、実行時に動的に解決をすることで、そのマルウェアがどういう機能を持っているかがわかりにくくなる。
Qakbotは通信の機能をもっているが、Qakbotの読み込んでいるImport関数やライブラリを見ても、通信に関連するものが見当たらない。通信に限らずAPIを隠すことで解析をしづらくしている。
QakbotのAPI解決の流れ
QakbotのAPI Hashを解決しているコードは次のようになっている。
この部分では次のような処理を行って、APIのアドレスを取得している(実際に渡されるハッシュ値は複数渡されることもあり、最終的にはAPIのアドレスを格納したヒープ領域のポインタが返ってくる)
QakbotのAPI Hashで使われている関数
Qakbot内では、APIの名前をもとに次のような計算を行っている。
①の図中で呼び出しているm_calc_hashが②の処理である。②の関数で計算した値と0x218fe95b
をxorした値が、最初に渡したハッシュ値と一致していれば、APIのアドレスを解決し、ヒープ領域に格納するという処理を行っている。
なお、②で行っている計算はCRC32の処理を少しアレンジしているもののため、ハッシュ値 = CRC32(APIの名前) ^ 0x218fe95b
という計算となっている。
次の図は、API解決をする際に渡されるデータの一部である。
図中の上から4Byteの0x1e4e54d6
はLoadLibraryA
のハッシュ値である。実際に計算をすると、先に示した計算方法とハッシュ値が一致することがわかる。
検体のハッシュ値
- 2022/6の検体:117a60cb0cde4199a78f99d4f6eb5f50
Qbotマルウェア解析
はじめに
Qakbotと呼ばれるばらまき型メールに添付されているウイルスがある。
事前にメールを盗み返信を装う形で攻撃メールを送付するといったことや、ショートカットファイルやFollinaを使って攻撃をすることも報告されていたと思う。
Qakbotの解析については、英語で書かれた解析記事はたくさんあるが日本語で書かれた記事は少ないと勝手に思っている。そのため、私が解析した内容を気ままにまとめる。更新頻度については期待しないでほしい。
Qakbot解析
QakbotのC2通信先について(2022/6、2022/9時点) - ごちうさ民の覚え書き
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_1001e5b0
とDAT_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