Lockbit 3.0で見つけたアンチデバッグテクニック(その2) | reverse-eg-mal-memoのブログ

reverse-eg-mal-memoのブログ

サイバーセキュリティに関して、あれこれとメモするという、チラシの裏的存在。
medium(英語):https://sachiel-archangel.medium.com/

RtlAllocateHeap を用いたデバッガ回避テクニック

 

前の記事で、 RtlCreateHeap で得られた構造体のデータを参照してデバッガを検知しているテクニックについて紹介しました。

今回は、RtlAllocateHeap でも同様のテクニックがあったため、その紹介をします。

 

 

どう見ても変な処理です、本当にありが(ry

 

Lockbit 3.0 の RtlCreateHeap にあったデバッガ回避テクニックを攻略し、ついに RtlAllocateHeap で領域を確保する時がきました。

rol 1 を nop で回避するようにしたおかげで、 RtlAllocateHeap には正しいハンドルが渡され、領域の確保は問題なく行われました。

 

さすがトラップ回避テクニックだ、なんともないぜ!(フラグ)

 

さて、正常に取得できた領域にどんなデータを入れてくれるのか!

・・・ん?なんか判定あるな?

 

 

①で RtlAllocateHeap で0x10(16バイト)の領域を確保しています。

この結果、eaxに確保した領域のアドレスが格納されます。

その後、②で確保した領域のアドレス+0x10 (eax + 0x10) のデータを参照しています。

・・・これっておかしいですよね?

 

何故なら、「確保した領域の外側」を参照しているからです。

図にするとこんな感じ。

 

 

まあ、おっちゃん的には、領域を参照する前に eax の値の Null チェックしろよ!と思っちゃいますがね。

「メモリの領域外にアクセスしているんだから、Memory access violation (メモリアクセス違反)エラーを起こすんでは?」と思う人もいるかと思います。

プログラムを開発する上ではその認識は正しく、基本的にはこのようなアクセスは推奨されません。

ただし、今回は RtlCreateHeap である程度のサイズのヒープを確保し、その一部を割り当てただけのため、その外側を参照してもメモリアクセス違反エラーにならなかったものと思われます。

 

メモリ確保ではVirtualAllocもありますが、こちらの場合はメモリアクセス違反を起こす可能性が高いと思います。

RtlCreateHeap & RtlAllocateHeap (またはHeapCreate & HeapAlloc) と VirtualAllocのメモリの動作の違いが原因ですが、このあたりも詳しくない場合は調べてみるといいと思います。

この記事がいい感じかも・・・と、いつも通り他所に丸投げぇ!(だって、このあたりの説明しだしたら収拾がつかないくらいグダグダになるんだもの)

 

ヒープに関する話題


 

そして、アヤシイ判定の中身をみてみると。

 

「確保した領域の最後尾より後ろの4バイトのデータが、0xABABABABかどうかチェックしている」

 

というものでした。

そして、このチェックにヒットすると、RtlAllocateHeap で確保した領域のアドレスが格納されている eax をコピーしている stosd 命令をスキップしてしまいます。

 

これがスキップされるとどうなるか?

 

この後の処理で、stosd 命令で設定されているハズのアドレスを参照したが、設定されておらず null だったため、nullアドレス参照でクラッシュ!!(フラグ回収)

 

つまり、「確保した領域の最後尾より後ろの4バイトのデータが、0xABABABABかどうかチェック」には、ヒットしてはいけなかったようです。

 

・・・とはいえ、「そもそも、確保した領域外のデータなんて不定じゃね?」と思うのが普通じゃないです?

私はそう思いますよ?

でも、それを固定値で判定に使っているということは、それがデバッガであることを判定できる理由があるはずってことになります。

 

 

困ったときは公式のマニュアルで確認!

 

では、RtlAllocateHeap で領域を確保した場合、その確保した領域外(特に後方)に何が格納されるのか、マニュアルを調べてみるとしましょう。

・・・。いや、流石に領域確保の API なら確保したアドレスが返るのは当たり前だし、領域外に設定される値なんて記載しないだろうし、そもそも何もしてないかもよ?

 

RtlAllocateHeap 関数 (ntifs.h)

 

 

戻り値
RtlAllocateHeap の呼び出しが成功した場合、戻り値は新しく割り当てられたブロックへのポインターです。 割り当てが失敗した場合、戻り値は NULL です。

 

以上。

デスヨネーwww

 

一応、英語の原文もチェックしよう。なぜなら、Microsoftはアメリカの(以下略)

 

RtlAllocateHeap function (ntifs.h)

 

 

 

さすがMicr〇soft公式だ、なんも書いてないぜ!(フラグ回収・その2)

 

あんまイジると、Micr〇softに怒られてしまいそうだw

いやまあ、流石に領域を確保した外のデータの設定までマニュアルには書かないだろうしなぁ・・・。

どちらかというと、デバッガ使用時特有の現象のハズ(でなければ、アンチデバッグにならない)なので、デバッグ時の動作仕様ということで別ドキュメントに書かれていそうではあります。

探す気もしないし、仮に見つかっても読む気もしないけども。

(でも、知ってる人がいたら教えて欲しいなぁ。)

 

(2022/11/12 追記)

この記事のコメントで、ypsilondelta さんに教えてもらいました!

「HEAP_TAIL_CHECKING_ENABLED」というフラグが設定されていると、確保した Heap の末尾に固有パラメータが追加されるようです。

また、このフラグの設定場所を調査していった結果、前回記事で触れた RtlCreateHeap で取得できるハンドル(アドレス)は、_SEGMENT_HEAP ではなく _HEAP であることも分かりました。

 

_HEAP のオフセット 0x0040 の Flags、0x0044の ForceFlags に、それぞれ「HEAP_TAIL_CHECKING_ENABLED」が設定されていると、このような動作になるようです。

_HEAP の当該パラメータについては、前回記事に追記したのでそちらを参照していただければと思います。

 

 

IDAおよびx64dbgで実験

 

そんなわけで、このパラメータについても検証です。

今回もIDA Proとx64dbgで検証します。

検証用のプログラムは、Lockbit 3.0で見つけたアンチデバッグテクニック(その1)で既に RtlAllocateHeap も書いていたので、前記事のものをそのまま使います。

検証用プログラムでは、RtlAllocateHeap でサイズ0x10(16バイト)を取得しています。

 

このプログラムを実行し、RtlAllocateHeap の結果のダンプし、確保した領域外(特に真後ろの4バイト)にどのようなデータが設定されているか確認してみましょう。

以下の通りの結果になりました。

 

 

うん、もう以前のゴミデータが残ってましたと言わんばかりの値ですね。

もちろん、これが当たり前だと思っています。

 

そして、前回同様、同じプログラムをIDA Proで実行し、RtlAllocateHeap の結果のダンプ、特に真後ろの4バイトを見てみましょう。
以下の通りの結果になりました。

 

 

緑色の枠が RtlAllocateHeap で取得された領域で、赤枠が領域の最後尾の後ろの4バイトです。

0xABABABABが格納されていることが確認できます。

なんなら、8バイト埋めている勢いです。

 

 

さらに、同じプログラムをx64dbgで実行し、RtlAllocateHeap の結果のダンプ、特に真後ろの4バイトを見てみましょう。
以下の通りの結果になりました。

 

 

やはり、確保された領域の最後尾の後ろに0xABABABABが格納されていることが確認できます。

 

デバッガを使っているとき、たまたまこの値が格納され、たまたまマルウェアがチェックしたパラメータと合致した、なんて偶然はないだろうなぁ。

つまり、この値はデバッガでデバッグしているときに発生する特有の現象、つまり仕様だと考えられます

(ただし、なんでそんな仕様にしているかはシラナイ。)

 

デバッガを変えても同じ結果ということは、OS側の仕様の可能性の方が高いのかな、と思います。

そして、Lockbit 3.0の作成者はそれを知っていたので、この値を参照してデバッガの検知、回避をしているのだと考えられます。

 

 

 

このケースでの回避法

 

今回の考察の結果、「RtlAllocateHeap で得られた領域の真後ろ4バイトの値が[AB AB AB AB]かどうかのチェックにヒット」した場合の「stosdのスキップ」は、攻撃者のデバッガ解析対策であることが分かりました。

では、解析妨害を回避することにしましょう。

 

「RtlAllocateHeap で得られた領域の真後ろ4バイトの値が[AB AB AB AB]かどうかのチェック結果をガン無視して、必ず stosd を実行させる」ようにすれば、万事解決となります。

 

そうであれば、このコードにパッチを当てるまでです。

条件ジャンプの「jz」部分を、「nop」(0x90) に置き換えてしまいましょう。

 

 

条件ジャンプ処理(jz)をマルウェアにとっては無駄処理になる nop に置き換えることで、必ず stosd が実行されるようになり、デバッガ対策を回避できるようになりました(無駄無駄ァ!)。

なお、類似したAPIの RtlReAllocateHeap でもやっているので、この処理でも同様のパッチを当てましょう。

 

 

 

まとめ

 

今回見つけたテクニックは、「RtlAllocateHeap で得られた領域の末尾より後ろの領域の値が、特定の値(0xABABABAB)かどうかをチェックすることでデバッガを使用しているかを判定し、デバッガと判定した場合は得られたアドレスを格納しないことで、後の処理を失敗させる」というものでした。

 

前回同様、この判定も最初は何のために行っているのか分かりませんでした。

特に、確保した領域の外にアクセスしており、最初は「Memory access violation を起こすものじゃないの?」と思ったものです。

これは、ヒープの取得機能が予め一定サイズの領域を確保するため、そのヒープ内であれば Allocate した範囲外でもOSとしてはエラーとしていない、という性質を利用したものといえるのかな、と思います。

(これを利用すれば、ヒープ系の領域ならある程度バッファオーバーランさせても動作し得るので、領域外を使ったテクニックが爆誕するのでは、というイケナイ想像を膨らませる話でもありますねー。)

 

これもコードを読めればそこまで難しいテクニックではないため、デバッガで一気に処理を進めることの妨害、それに伴い解析に時間をかけさせることや、レベルの低い解析者を振るい落とす目的だと推測しています。

 

他のテクニックもついでに書くつもりでしたが、思ったより長くなったので、ここで一旦切って、いつ書くか定かではない「その3」に持ち越そうと思います。

まったりウマの育成でもしながら次回をお待ちください。