Notes

Do not read codes but patch binary.

emotetのRC4

emotet loaderのRC4

最近emotetがまた流行っているらしいので、昨年流行したemotet(解析したのは今年の初め)との違いを見てみる

今回はLoader周りの記述のみ簡単に解説

前回を簡単におさらいすると、

  1. 外側のPEから.dataセクションに存在したPE loaderとPEが同一領域に確保される(base64で変換)
  2. PE loader内の処理で、起動されるPEが再度別領域に確保される
  3. PEが実行される

今回は、何やら

  1. 外側のPEがLoaderの前に、.dataセクションに存在したPre-loader(後述)をheap上に確保する(単純なadd/sub演算による変換)
  2. Pre-loaderがPE loaderとPEを同一領域に確保する
  3. PE loader内の処理で、起動されるPEが再度別領域に確保される
  4. PEが実行される

という感じになっている

2のPre-Loaderがやや技巧的になっていて詳細には、

  1. RtllFindResource_U/RtlAccessResource/VirtualAllocateのアドレスを解決する
  2. .rsrcセクションに存在する特定のエントリの領域分を確保する
  3. 2をRC4(*)で復号(PE loader+PEを生成)する

RC4の実装について、見てみる

f:id:vrodxda:20200922143436p:plain
emotet-RC4-01
f:id:vrodxda:20200922143521p:plain
emotet-RC4-02
f:id:vrodxda:20200922143541p:plain
emotet-RC4-03

これらのスクリーンショットは、実行時にヒープに確保された0x800程あるPre-loaderの最後の領域に存在しているRc4の実装である

一番下の図 emotet-RC4-03 から見ていく
多くの共通鍵暗号に当てはまるが、0x7e4のxor(*1)に注目する
xor edx,ecx
edxが更新されるので、

1. edxが取得されたメモリ(スタック)に暗号化されたデータ(今回の場合はリソースセクションから参照された領域)が存在する  
2. edxが格納されるメモリ(スタック)に復号されたデータ(今回の場合、事前に確保されたPE loader + PE )が存在する  
3. ecxが取得されたメモリ(スタック)に鍵から生成された疑似乱数が存在する  

ことが推察される

スタックに確保されたメモリはそれぞれローカル変数、または引数を指していると思われるが、それらを1,2,3から明らかにする 1.2からは簡単にわかる

  • スタック変数
    [ebp -18] : 現在のストリームのインデックス

  • 引数
    [ebp + 8] : 暗号化データのベースアドレス [ebp +18] : 復号データの格納先のベースアドレス

3(どのように鍵が生成されているか)を読み解く

中々読み解いていくことが難しい

そこで上のアセンブリを以下の様に、メモリに「ストア」した段階で、1つ前に現れた「ストア」移行の命令と共に、ボックスに囲ってみる

f:id:vrodxda:20200923095453p:plain

  • スタック変数

[ebp -1c] : 疑似乱数を一時的に格納している領域

~7bb | add ecx eaxの生成元を辿っていくと、

[ebp - 14 - 11c] : 疑似乱数の生成元1(addされるデータの1つが格納される)
[ebp - 8 - 11c] : 疑似乱数の生成元2(addされるデータの1つが格納される)
[ebp -1] : 内部パラメータ(swap処理の為に一時的に使われる)

それぞれの関係を見ていくと、

[ebp - 14 - 11c] <= [ebp -1] <= 5
[ebp - 8 - 11c] <= [ebp - 14] <= 6
[ebp - 1] <= [ebp - 8 - 11c] <= 4

これはスワップ

[ebp - 14] : 1ずつincrement <= 2
[ebp - 8] : [ebp - 14 - 11c] + 1 <= 3

まとめると、

i, j = 0, 0
loop:
        i = (i + 1) % 256 # <= 2
        j = (j + S[i]) % 256  # <= 3
        S[i], S[j] = S[j], S[i]  # <= 4,5,6
        K = S[(S[i] + S[j]) % 256] # <= 7

RC4のPRGAの処理をしていたことが分かる

emotet-RC4-01 に関しては、
1. 「昇順で値が並ぶ配列」
2. 下記の「K」に相当する値の一時的な配列

emotet-RC4-02 に関しては、
3. 先の1,2で生成された値を状態の1つ(下記変数「j」)に足し合わせ
4. 1で生成された配列の「i」番目の要素と、3で更新された「j」番目の要素をスワップ

といった動作を行っている

S = range(256) # <= 1
j = 0
for i in xrange(256):
        k = ord(key[i % len(key)]) # <= 2
        j = (j + S[i] + k) % 256 # <= 3
        S[i], S[j] = S[j], S[i] # <= 4

なお、この場合、鍵は.dataセクション内の、このペイロード自体が存在した直前に置かれ、ペイロードの引数として参照されるようだ

このアセンブリ(一般的なアセンブリにも当てはまるかももしれない)を分析する場合以下の点が難しい

  1. メモリのロードにSIBを用いたインデックスを使う場合
  2. 配列アクセスの場合だが、この時、SIBのレジスタがロードされた領域が別の命令によって、ロードされているので、配列のアクセスの際にどこを参照しているのか 直観的に分かりにくい
  3. 意味のある単位に分割しずらい
    • これは慣れの問題もあるかもしれないが、通常、「ロード => 処理 => ストア」という単位で、「ストア」をした場合、一度、アセンブリの意味の単位が切れるという認識がある
    • これは単純に分析の為の表現という問題だが、「774 - 7cd」辺りを見て、意味のある単位の計算が何個行われているのかぱっと見て分かりずらい(実際にこの領域にストアは5つある)

補足

*1 xorが使われている場合の暗号化パターン

xorの使い方次第で、暗号化アルゴリズムの推測が可能である

  1. メモリの値をロードして、xorの結果を直接ロードするパターン
    e.g. xor [eax],ebx
    mod == 0x00,0x01,0x10の場合である
    この場合、値は上書きされるので、暗号化される前の値を後で使用したりする計算を行う可能性は低い(事前に別途コピーしていなければ)

  2. 1byteのみ書き換えを行うパターン
    e.g. xor al,bl
    opcodeが0x30,0x32の場合である
    1byteずつ暗号化するということストリーム暗号である可能性が高い 最もこのRC4のケースの様に、1byteずつの演算だが、レジスタのサイズが4byteで計算している場合等はある

  3. 16byte以上のxor演算を行う場合
    e.g. pxor xmm1,xmm2
    16byte単位(128bit)や32byte(256bit)単位でxor演算を行う暗号化処理である(例えば、DESや、自前で実装したAESなど)

  4. 即値(immediate value)を用いる場合
    e.g. xor eax,12345
    opcode = 0x83の場合
    滅多にないかもしれないが、unpack処理等で鍵を固定値にしている場合等はあり得る

その他