
シェルコードの内容を周期的に暗号化・復号化し、メモリ保護を RW(または NoAccess)と RX の間で変動させる、もう一つのインメモリ逃避技術の PoC 実装です。
シェルコードが RW または NoAccess のメモリページに存在する場合、Moneta や pe-sieve などのスキャナは、それを追跡してさらなる解析のためにダンプすることができなくなります。
ThreadStackSpoofer をリリースした後、私は以下の README の点についていくつか質問を受けました:
Beacon のメモリページの保護を(RX/RWX から)RW に変更し、スリープ前にその内容を暗号化する(これにより Moneta や pe-sieve などのスキャナを回避できる可能性があります)
それまで、コミュニティはペイロードを暗号化・復号化し、メモリ保護を切り替えて、異常な実行可能領域を探すメモリスキャナを簡単に回避する方法をすでに知っているだろうと思っていました。 しかし質問によってそうではないことが分かったため、私はこの非武器化された PoC をリリースして、もう一つの回避戦略を文書化し、コミュニティが利用できるサンプル実装を提供することにしました。
この PoC は、攻撃的セキュリティコミュニティにはすでに知られている、かなり単純なテクニックのデモンストレーションです(つまり、ここで本当に新しいものを紹介しているわけではありません)。前述の両メモリスキャナを標的にした回避能力を実演するいくつかの商用フレームワークが示すマジックの背後にある秘密を明らかにしたいという思いからです。
以下は、RW に変動させた場合の比較です(別のオプションとして、PAGE_NOACCESS に変動させる方法もあります。後述):

この実装は、私の ThreadStackSpoofer とともに、商用 C2 製品が提供する機能に追いつくためのサンプル実装を Offensive Security コミュニティに提供します。私たちの Red Team ツールでも遜色ないものを実現できるようにするためです。💪
このプログラムは、シェルコードの自己インジェクションを実行します(おおよそ古典的な VirtualAlloc + memcpy + CreateThread を経由します)。
シェルコードが実行されると(この実装は特に Cobalt Strike の Beacon インプラントを対象としています)、Beacon がスリープする瞬間を傍受するために Windows 関数 kernel32!Sleep がフックされます。
フックされた MySleep 関数が呼び出されるたびに、メモリ割り当ての境界を特定し、その保護を RW に変更して、そこに保存されているすべてのバイトを xor32 で暗号化します。
期待される時間だけ待機した後、シェルコードが私たちの MySleep ハンドラに戻ってきたら、シェルコードのデータを復号化し、保護を RX に戻します。
PAGE_READWRITE への変動の仕組みkernel32!Sleep をフックして、私たちのコールバックを指すようにします。VirtualAlloc + memcpy + CreateThread を介してシェルコードを注入し起動します。ThreadStackSpoofer で行ったのとは異なり、ここではシェルコードを起動するために ntdll 内の何かをフックするのではなく、独自の関数からシェルコードにジャンプします。これにより、改変された ntdll メモリを指し示す単純な IOC をメモリに残すことを回避しようとしています。MySleep コールバックが呼び出されます。RW に変更されます。kernel32!Sleep のフックを解除して、Sleep がトランポリン化(インラインフック)されたことを示す単純な IOC をメモリに残さないようにします。::Sleep を呼び出して、さらなる通信を待つ間 Beacon をスリープさせます。RX に戻してから、kernel32!Sleep を再フックして後続のスリープを確実に傍受します。PAGE_NOACCESS への変動の仕組みkernel32!Sleep をフックして、私たちのコールバックを指すようにします。VirtualAlloc + memcpy + CreateThread を介してシェルコードを注入し起動します...MySleep コールバックが呼び出されます。PAGE_NOACCESS に変更されます。kernel32!Sleep のフックを解除して、Sleep がトランポリン化(インラインフック)されたことを示す単純な IOC をメモリに残さないようにします。::Sleep を呼び出して、さらなる通信を待つ間 Beacon をスリープさせます。kernel32!Sleep を再フックして後続のスリープを確実に傍受します。RX に戻し、シェルコードの実行が再開されます。このテクニックは真新しいものではなく、私自身が考案したものでもありません。コンセプトとその実用的な活用方法を示す実装に過ぎず、商用 C2 フレームワークが提供する機能に Offensive Security コミュニティが追いつけるようにするためのものです。
実際、シェルコードのメモリ保護を切り替えるというアイデアは、数年前に Josh Lospinoso 氏の素晴らしい Gargoyle を通じて知りました。
関連する背景情報は以下のとおりです:
Gargoyle は、自己認識型で自己変動するシェルコードのコンセプトをさらに進め、VirtualProtect を呼び出す ROP シーケンスを活用しています。
しかし、このテクニックは印象的ではありますが、Cobalt Strike の Beacon と組み合わせて利用するのは同様に難しく、スレッドを殺してメモリ上で Beacon を再初期化し続ける必要があります。
完璧とは程遠いですが、私たちはすでに独自の自己インジェクションローダープロセスを基盤として操作しているため、シェルコードが動作する環境に対して望むことを何でも行い、好きなように隠すことができます。このテクニック(および以前の ThreadStackSpoofer)は、この方法でシェルコードを実行することの利点を示しています。
PAGE_NOACCESS への変動の実装は、ORCA666 氏が彼の https://github.com/ORCA666/0x41 インジェクターで示した作品に触発されています。
彼は次のことを示しました:
この実装にはこのアイデアが実装されており、<fluctuate> のオプション 2 で利用できます。
彼の他のプロジェクトもぜひチェックしてください。
ツール ShellcodeFluctuation は3つのパラメータを受け付けます。1つ目はシェルコードへのパス、2つ目は機能の修飾子です。```
Usage: ShellcodeFluctuation.exe
:
-1 - Read shellcode but dont inject it. Run in an infinite loop.
0 - Inject the shellcode but don't hook kernel32!Sleep and don't encrypt anything
1 - Inject shellcode and start fluctuating its memory with standard PAGE_READWRITE.
2 - Inject shellcode and start fluctuating its memory with ORCA666's PAGE_NOACCESS.
### Moneta(一見誤検知)```
C:\> ShellcodeFluctuation.exe beacon64.bin -1
まず、Moneta64 スキャナが、怪しいことを何もせず単に無限ループを実行するプロセスについてどう見るかを見てみましょう:

見てのとおり、いくつかの誤検知(少なくとも私の考えでは)があり、Mismatching PEB module / Phantom image を検出しているとされています。
メモリ境界は ShellcodeFluctuate.exe モジュール自体を指しており、このモジュールは MEM_IMAGE タイプであるにもかかわらず、プロセスの PEB にリンクされていない可能性を示しています。これは異例で、かなり奇妙に聞こえます。
この IOC の理由は私には分かっておらず、より深く理解しようともしませんでしたが、実際に心配するようなものではありません。
この検出の理由をご存知の方がいらっしゃいましたら、ぜひお聞かせください。どうかご連絡ください。
C:> ShellcodeFluctuation.exe beacon64.bin 0
2番目のユースケースは、私たちのプロセス内で動作するBeaconのメモリIOCを示しています。これは、カスタマイズされた`Artifact Kits`や`User-Defined Reflective Loaders`(私の[`ElusiveMice`](https://github.com/mgeeky/ElusiveMice)など)を一切使用しておらず、結果を損なうような初期アクションも一切行っていません。

`Moneta64`が、シェルコードが存在する場所を指す`Abnormal private executable memory`を正しく認識していることがわかります。
これは非常に強力なメモリIOCであり、自動スキャナによるダンプと分析のためにシェルコードを露出させてしまいます。クールではありません。
### RW保護付き暗号化Beacon```
C:\> ShellcodeFluctuation.exe beacon64.bin 1
さて、この実装の観点から最も興味深い3つ目のユースケースは、_変動する_ビーコンです。

最初のIOCは別として(多少_誤検知_と考えられます)、kernel32.dllのメモリが改変されたことを示す新しいIOCが見られます。
ただし、今回はAbnormal private executable memory IOCはありません。私たちの変動(暗号化・復号化の繰り返しとメモリ保護の切り替え)が有効になっています。
ちなみに、pe-sieveも/data 3オプションを指定して使用すると、注入されたPEを検出します(このオプションを指定しない限り、検出は行われません)。

私の現在の仮説では、PE-SieveはMonetaと同じ特性を検出しています(下の_Modified code in kernel32.dll_で説明)—つまり、PEマップ済みモジュールのワーキングセットが空ではないという事実であり、これは何らかのコードインジェクションの明らかな証拠です。 これは_Implanted PE_ / _Implanted_としてラベル付けされます。その場合、結論はMonetaの観察に似ています。検出の観点では、そのIOCをそれほど気にする必要はないと思います。
現時点では、シェルコードの実行を途中でインターセプトする(今はCobalt Strikeの話です)ために、kernel32!Sleepをフックする以外に良い選択肢を思いつきませんでした。したがって、この種のIOCを残さざるを得ません。
でもね、それでもファイルシステム上にある(C:\Windows\System32\kernel32.dll)ものと比較して、どのバイトも違っていませんし、フックされている関数もありません。どういうことでしょう?😉
C:> ShellcodeFluctuation.exe beacon64.bin 2

これにより、シェルコードは事実上 `RX` ページと `NA` ページの間を行き来することになります。
現時点では、`PAGE_NOACCESS` に切り替えることの利点が `PAGE_READWRITE` に切り替えることと比べて何かあるのかは確信が持てません。
### kernel32.dll 内の改変されたコード
それでは、改変された `kernel32` の IOC についてはどうでしょうか?
さて、この IOC の真相に迫って、何が起こっているのか見てみましょう。
まず、言及したメモリ領域、つまり `kernel32.dll` の `.text`(コード)セクションをダンプします。その目的のために、公に知られていて安定したツールである `ProcessHacker` を使いましょう:

改変されたとされる kernel32 のコードセクションをダンプし、次にその領域を改変しなかったプロセスで実行されている kernel32 についても同様に行います。
2つのダンプを取得したら、それらをバイト単位で比較し(私の [expdevBadChars](https://github.com/mgeeky/expdevBadChars) を使用)、不一致がないか確認します:

すると、それらが互いに一致していることがわかります。明らかに `kernel32.dll` 内では1バイトも改変されておらず、その理由は、呼び出す前に `kernel32!Sleep` のフックを解除しているからです:
`main.cpp:31:````
HookTrampolineBuffers buffers = { 0 };
buffers.originalBytes = g_hookedSleep.sleepStub;
buffers.originalBytesSize = sizeof(g_hookedSleep.sleepStub);
//
// Unhook kernel32!Sleep to evade hooked Sleep IOC.
// We leverage the fact that the return address left on the stack will make the thread
// get back to our handler anyway.
//
fastTrampoline(false, (BYTE*)::Sleep, &MySleep, &buffers);
// Perform sleep emulating originally hooked functionality.
::Sleep(dwMilliseconds);
So what's causing the IOC being triggered? Let us inspect Moneta more closely:

Breaking into Moneta's Ioc.cpp just around the 104 line where it reports MODIFIED_CODE IOC, we can modify the code a little to better expose the exact moment when it analyses kernel32 pool.
Now:
a = truekernel32 has b = 0x1000 private bytes. How come? There should be 0 of them.a && b) the IOC is reportedWhen Windows Image Loader maps a DLL module into process' memory space, the underlying memory pages will be labeled as MEM_MAPPED or MEM_IMAGE depending on scenario.
Whenever we modify even a single byte of the MEM_MAPPED/MEM_IMAGE allocation, the system will separate a single memory page (assuming we modified less then PAGE_SIZE bytes and did not cross page boundary) to indicate fragment that does not maps back to the original image.
This observation is then utilised as an IOC - an image should not have MEM_PRIVATE allocations within its memory region (inside of it) because that would indicate that some bytes where once modified within that region. Moneta is correctly picking up on code modification if though bytes were matching original module's bytes at the time of comparison.
For a comprehensive explanation of how Moneta, process injection implementation and related IOC works under the hood, read following top quality articles by Forrest Orr:
That's a truly outstanding research and documentation done by Forrest, great work pal!
Especially the second article outlines the justification for this detection, as we read what Forrest teaches us:
In the event that the module had been legitimately loaded and added to the PEB, the shellcode implant would still have been detected due to the 0x1000 bytes (1 page) of memory privately mapped into the address space and retrieved by Moneta by querying its working set - resulting in a modified code IOC as seen above.
To summarise, we're leaving an IOC behind but should we be worried about that? Even if there's an IOC there are no stolen bytes visible, so no immediate reference pointing back to our shellcode or distinguishing our shellcode's technique from others.
Long story short - we shouldn't be really worried about that IOC. :-)
One can say, that this implementation is far from perfect because it leaves something, still there are IOCs and the commercial products show they don't have similar traits.
When that argument's on the table I need to remind, that, the commercial frameworks have complete control over source code of their implants, shellcode loaders and thus can nicely integrate one with another to avoid necessity of hooking and hacking around their shellcode themselves. Here, we need to hook kernel32!Sleep to intercept Cobalt Strike's Beacon execution just before it falls asleep in order to kick on with our housekeeping. If there was a better mechanism for us kicking in without having to hook sleep - that would be perfect.
However there is a notion of Sleep Mask introduced to Cobalt Strike, the size restrictions for being hundreds of byte makes us totally unable to introduce this logic to the mask itself (otherwise we'd be able not to hook Sleep as well, leaving no IOCs just like commercial products do).
Another argument might be, that commercial framework integrate these sorts of logic into their Reflective Loaders and here we instead leave it in EXE harness. That's true, but the reason for such a decision is twofold:
I need to be really careful with releasing this kind of technology to avoid the risk of helping weaponize the real-world criminals with an implementation that will haunt us back with another Petya. In that manner I decided to skip some of the gore details that I use in my professional tooling used to deliver commercial, contracted Adversary Simulation exercises. Giving out the seed hopefully will be met with community professionals able to grow the concept in their own toolings, assuming they'll have apropriate skills.
I'd far prefer to move this entire logic to the User-Defined Reflective Loader of Cobalt Strike facilitating Red Team groups in elevated chances for their delivery phase. But firstly, see point (1), secondly that technology is currently limited to 5KBs size for their RDLLs, making me completely unable to implement it there as well. For those of us who build custom C2 & implants for in-house Adversary Simulation engagements - they now have received a sample implementation that will surely help them embellishing their tooling accordingly.
Look at the code and its implementation, understand the concept and re-implement the concept within your own Shellcode Loaders that you utilise to deliver your Red Team engagements. This is an yet another technique for advanced in-memory evasion that increases your Teams' chances for not getting caught by Anti-Viruses, EDRs and Malware Analysts taking look at your implants.
While developing your advanced shellcode loader, you might also want to implement:
BeaconEyeMEM_PRIVATE memory allocations referenced by these threads)Use case:``` Usage: ShellcodeFluctuation.exe : -1 - Read shellcode but dont inject it. Run in an infinite loop. 0 - Inject the shellcode but don't hook kernel32!Sleep and don't encrypt anything 1 - Inject shellcode and start fluctuating its memory with standard PAGE_READWRITE. 2 - Inject shellcode and start fluctuating its memory with ORCA666's PAGE_NOACCESS.
ここで:
- `<shellcode>` はシェルコードファイルへのパスです
- `<fluctuate>` は上記のとおり、`-1`、`0`、または `1` のいずれかを取ります
ビーコンのスレッドコールスタックを偽装する実行例:```
C:\> ShellcodeFluctuation.exe ..\..\tests\beacon64.bin 1
[.] Reading shellcode bytes...
[.] Hooking kernel32!Sleep...
[.] Injecting shellcode...
[+] Shellcode is now running. PID = 9456
[+] Fluctuation initialized.
Shellcode resides at 0x000002210C091000 and occupies 176128 bytes. XOR32 key: 0x1e602f0d
[>] Flipped to RW. Encoding...
===> MySleep(5000)
[.] Decoding...
[>] Flipped to RX.
[>] Flipped to RW. Encoding...
===> MySleep(5000)
独自のシェルコードローダー/ツールにこの機能を追加する予定がある場合は、kernel32.dll のフック解除を必ず回避してください。
kernel32 のフック解除を試みると、元の Sleep 機能が復元され、コールバックが呼び出されなくなります。
コールバックが呼び出されない場合、スレッドは自身のコールスタックを単独で偽装できなくなります。
もしそれを望むのであれば、別のウォッチドッグスレッドを実行して、Beacon のスレッドがスリープするたびに偽装されるようにする必要があるかもしれません。
Cobalt Strike と Raphael Mudge の BOF unhook-bof を使用している場合は、フック解除しないライブラリを指定するオプションパラメータを BOF に追加する、私の プルリクエスト を確認してください。
これにより、kernel32 内のフックを維持できます。
---``` beacon> unhook kernel32 [*] Running unhook. Will skip these modules: wmp.dll, kernel32.dll [+] host called home, sent: 9475 bytes [+] received output: ntdll.dll <.text> Unhook is done.
[指定したモジュールを無視するオプション付きの `unhook-bof` の修正版](https://github.com/mgeeky/unhook-bof)
---
## 最後に
このPoCは、Cobalt StrikeのBeaconシェルコードで動作するように設計されています。BeaconはC2からのさらなる指示を待つために `kernel32!Sleep` を呼び出すことが知られています。
このローダーは、その事実を利用して `Sleep` をフックし、ハウスキーピングを実行します。
この実装は、市場の他のシェルコード(_Meterpreter_ など)が `Sleep` を使用して待機しない場合、動作しない可能性があります。
これはあくまでテクニックを示す _Proof of Concept_ に過ぎないため、他のC2フレームワークのサポートを追加するつもりはありません。
コンセプトを理解すれば、きっと自分のシェルコード要件に変換し、ソリューションを自分の利点に合わせて適用できるようになるでしょう。
「このコードはXYZシェルコードで動作しない」といった内容のGithub issueは開かないでください。即座にクローズされます。
---
### ☕ サポート ☕
このプロジェクトや他のプロジェクトは、眠れない夜と**多くの努力**の成果です。私の活動が気に入り、常にコミュニティに還元していることを評価していただけるなら、
[コーヒーをごちそうしてくれることを検討してください](https://github.com/sponsors/mgeeky) _(またはビールの方がさらに良い)_ お礼として!💪
---
## 著者```
Mariusz Banach / mgeeky, 21
<mb [at] binary-offensive.com>
(https://github.com/mgeeky)