Last night I took a quick look at BlackEnergy 2, a rootkit that surfaced in 2010. BlackEnergy 2 was essentially a rewrite of its predecessor as BlackEnergy 2 contains rootkit techniques, process-injection, and encryption. Surprisingly for being a now 'dated' rootkit, there's really not too much accessible (or not buried) reverse kernel-debugging documentation for the rootkit aside from when it was first surfacing. A lot of misc. information pops up throughout very few blogs/forums that are Russian, but that's about it.
There's a lot of additional lore behind the rootkit, but I really won't go into that. If you're interested about where the rootkit core came from before it was implemented into BlackEnergy 2, BlackReleaver is the answer!
NT Corruption
First off, we can view corruption regarding ntokskrnl:
!chkimg compares the current loaded executable with the version within the symbol store. This is a helpful command to detect corruptions with images, and especially helpful when dealing with rootkits. The -d parameter displays a summary of all mismatched areas. The -v parameter makes the information verbose. In this case, the -v parameter is optional.
As noted above, we have two out-of-range values. We're interested in disassembling nt!KiBBTUnexpectedRange+8, but not nt!KeInsertQueueApc (+0x6c47). nt!KeInsertQueueApc (+0x6c47) as I commented above is in relation to the Chameleon technology from Malwarebytes. I had MWB ARK installed on this VM for testing purposes, so that is where it was spawning from.
nt!KiBBTUnexpectedRange+8 Disassembly - Healthy
If we disassemble nt!KiBBTUnexpectedRange+8 on a system not infected with BlackEnergy 2, we should expect similar results:
nt!KiBBTUnexpectedRange+8 Disassembly - Corrupted
If we disassemble nt!KiBBTUnexpectedRange+8 on a system that has been infected with BlackEnergy 2, we should expect similar results:
So, why do we have corruptions in ntoskrnl and a corrupted nt!KiBBTUnexpectedRange+8 output? It's a side effect of the rootkit creating additional 'fake' service tables. It does this by patching the ETHREAD SystemTable pointer, which allows for things such as user threads to be patched, thread creation notification and service table pointer updating by using PsSetCreateThreadNotifyRoutine, etc.
The main use behind creating fake service tables is it gives anti-rootkit software a much harder time (harder back in 2010, at least) detecting its presence. It doesn't just 'hook' and/or modify the SSDT (which as we know would be a big red flag), it instead creates its own fake service tables, and then hooks (acquires?) the following functions:
KTHREAD Structure
Given we're adding new/fake service tables, we need applications to be able to access them. This is done by using pointers as discussed above, which is accomplished in the KTHREAD Structure. Every single thread has a pointer to a ServiceTable which is ultimately set by KeInitThread. Additionally, if the thread requires GUI functions within the Shadow SSDT, PsConvertToGuiThread is called.
We can dump the KTHREAD Structure:
At this point if you'd like to see the tables, you can use the following command:
If you see anything other than KeServiceDescriptorTable or KeServiceDescriptorTableShadow, it's a new/fake ServiceTable.
Registry Hiding
In order to survive reboots, etc, it hides its registry entry. If you're using Windows' Registry Editor, it won't find the hidden entry. For example, here's our hidden service:
If we try and find qtcst with Registry Editor:
If we however use a 3rd party registry tool (any will probably work so long as it doesn't use Windows API calls):
We catch our culprit and the dropped driver red-handed. The driver renames after each reboot, so if you remove it and don't get the driver+registry entry at once, it'll just re-create with a different name.
Main.dll
Main.dll is the payload that is injected via trusted svchost. It contains as you can see a lot of readable stings, like str.sys for example. We can see str.sys in action here:
Overall, this rootkit was certainly a step up from most SSDT hooking/modification rootkits at the time. It can be a pain in the butt to remove if you don't kill everything properly : )
Thanks for reading.
References
Black Energy 2.1+
BlackEnergy Version 2 Analysis
There's a lot of additional lore behind the rootkit, but I really won't go into that. If you're interested about where the rootkit core came from before it was implemented into BlackEnergy 2, BlackReleaver is the answer!
NT Corruption
First off, we can view corruption regarding ntokskrnl:
Code:
lkd> !chkimg -d -v nt
Searching for module with expression: nt
Will apply relocation fixups to file used for comparison
Will ignore NOP/LOCK errors
Will ignore patched instructions
Image specific ignores will be applied
Comparison image path: C:\Symbols\ntoskrnl.exe\41108004214780\ntoskrnl.exe
No range specified
Scanning section: .text
Size: 466369
Range to scan: 804d7580-80549341
804ded5a-804ded5d 4 bytes - [COLOR=#ff0000]nt!KiBBTUnexpectedRange+8[/COLOR]
[ 00 ff 09 00:6b a0 c1 01 ]
804e59a1-804e59a5 5 bytes - nt!KeInsertQueueApc (+0x6c47) [COLOR=#800080]// Not malicious -- Malwarebytes.[/COLOR]
[ 8b ff 55 8b ec:e9 e4 45 4e 77 ]
Total bytes compared: 466369(100%)
Number of errors: [COLOR=#ff0000]9[/COLOR]
!chkimg compares the current loaded executable with the version within the symbol store. This is a helpful command to detect corruptions with images, and especially helpful when dealing with rootkits. The -d parameter displays a summary of all mismatched areas. The -v parameter makes the information verbose. In this case, the -v parameter is optional.
As noted above, we have two out-of-range values. We're interested in disassembling nt!KiBBTUnexpectedRange+8, but not nt!KeInsertQueueApc (+0x6c47). nt!KeInsertQueueApc (+0x6c47) as I commented above is in relation to the Chameleon technology from Malwarebytes. I had MWB ARK installed on this VM for testing purposes, so that is where it was spawning from.
nt!KiBBTUnexpectedRange+8 Disassembly - Healthy
If we disassemble nt!KiBBTUnexpectedRange+8 on a system not infected with BlackEnergy 2, we should expect similar results:
Code:
lkd> u nt!KiBBTUnexpectedRange+8
nt!KiBBTUnexpectedRange+0x8:
804ded5a 00ff add bh,bh
804ded5c 0900 or dword ptr [eax],eax
804ded5e 0bc0 or eax,eax
804ded60 58 pop eax
804ded61 5a pop edx
804ded62 8bec mov ebp,esp
804ded64 89ae34010000 mov dword ptr [esi+134h],ebp
804ded6a 0f8490020000 je nt!KiFastCallEntry+0x8d (804df000)
nt!KiBBTUnexpectedRange+8 Disassembly - Corrupted
If we disassemble nt!KiBBTUnexpectedRange+8 on a system that has been infected with BlackEnergy 2, we should expect similar results:
Code:
lkd> u nt!KiBBTUnexpectedRange+8
nt!KiBBTUnexpectedRange+0x8:
804ded5a 6ba0c1010bc058 imul esp,dword ptr [eax-3FF4FE3Fh],58h
804ded61 5a pop edx
804ded62 8bec mov ebp,esp
804ded64 89ae34010000 mov dword ptr [esi+134h],ebp
804ded6a 0f8490020000 je nt!KiFastCallEntry+0x8d (804df000)
804ded70 8d15509b5580 [COLOR=#800080]lea edx,[nt!KeServiceDescriptorTableShadow+0x10 (80559b50)][/COLOR]
804ded76 8b4a08 mov ecx,dword ptr [edx+8]
804ded79 8b12 mov edx,dword ptr [edx]
So, why do we have corruptions in ntoskrnl and a corrupted nt!KiBBTUnexpectedRange+8 output? It's a side effect of the rootkit creating additional 'fake' service tables. It does this by patching the ETHREAD SystemTable pointer, which allows for things such as user threads to be patched, thread creation notification and service table pointer updating by using PsSetCreateThreadNotifyRoutine, etc.
The main use behind creating fake service tables is it gives anti-rootkit software a much harder time (harder back in 2010, at least) detecting its presence. It doesn't just 'hook' and/or modify the SSDT (which as we know would be a big red flag), it instead creates its own fake service tables, and then hooks (acquires?) the following functions:
Code:
NtDeleteValueKey
NtEnumerateValueKey
NtEnumerateKey
NtOpenKey
NtOpenProcess
NtOpenThread
NtProtectVirtualMemory
NtQuerySystemInformation
NtReadVirtualMemory
NtSetContextThread
NtSetValueKey
NtSuspendThread
NtTerminateThread
NtWriteVirtualMemory
etc...
KTHREAD Structure
Given we're adding new/fake service tables, we need applications to be able to access them. This is done by using pointers as discussed above, which is accomplished in the KTHREAD Structure. Every single thread has a pointer to a ServiceTable which is ultimately set by KeInitThread. Additionally, if the thread requires GUI functions within the Shadow SSDT, PsConvertToGuiThread is called.
We can dump the KTHREAD Structure:
Code:
lkd> dt -v nt!_KTHREAD
struct _KTHREAD, 73 elements, 0x1c0 bytes
+0x000 Header : struct _DISPATCHER_HEADER, 6 elements, 0x10 bytes
+0x010 MutantListHead : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x018 InitialStack : Ptr32 to Void
+0x01c StackLimit : Ptr32 to Void
+0x020 Teb : Ptr32 to Void
[COLOR=#ff0000]+0x0e0 ServiceTable : Ptr32 to Void [/COLOR]
At this point if you'd like to see the tables, you can use the following command:
Code:
!for_each_thread ".echo Thread: @#Thread; dt nt!_kthread ServiceTable @#Thread"
If you see anything other than KeServiceDescriptorTable or KeServiceDescriptorTableShadow, it's a new/fake ServiceTable.
Registry Hiding
In order to survive reboots, etc, it hides its registry entry. If you're using Windows' Registry Editor, it won't find the hidden entry. For example, here's our hidden service:
Code:
lkd> !ms_services
[205] | 0x01 | | [COLOR=#ff0000]qtcst [/COLOR]| [COLOR=#ff0000]qtcst [/COLOR]| [COLOR=#800080]SERVICE_RUNNING[/COLOR] | [COLOR=#ff0000]\Driver\qtcst [/COLOR]
If we try and find qtcst with Registry Editor:
If we however use a 3rd party registry tool (any will probably work so long as it doesn't use Windows API calls):
We catch our culprit and the dropped driver red-handed. The driver renames after each reboot, so if you remove it and don't get the driver+registry entry at once, it'll just re-create with a different name.
Main.dll
Code:
.exe SYS TMP cmd.exe /C b k e r n e l p l g _ d a t a getp v e r s i o n n a m e s l e e p f r e q c m d s p l u g i n s x%s_%X C:\ a d d r t y p e s e r v e r s i c m p _ a d d r b u i l d _
i d str.sys \drivers\ \ \ . \ \ \ . \ G l o b a l \ %s%s { 9 D D 6 A F A 1 - 8 6 4 6 - 4 7 2 0 - 8 3 6 B - E D C B 1 0 8 5 8 6 4 A } main.dll .bdata {3D5A1694-CC2C-4ee7-A3D5-A879A9E3A623}
POST %.2X & = bid nt %d cn ln id ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ Content-Type: application/x-www-form-urlencoded _TEST_ .dll user32.dll advapi32.dll
wininet.dll ws2_32.dll DispatchCommand DispatchEvent GetLastError GetCurrentProcessId ExitThread CloseHandle KERNEL32.dll wsprintfA USER32.dll CoCreateInstance CoInitializeEx ole32.dll
OLEAUT32.dll WS2_32.dll RtlUnwind InterlockedExchange VirtualQuery main.dll ConfAllocGetTextByNameA ConfAllocGetTextByNameW ConfGetListNodeByName ConfGetNodeByName ConfGetNodeTextA
ConfGetNodeTextW ConfGetPlgNode ConfGetRootNode DownloadFile PlgSendEvent RkLoadKernelImage RkProtectObject SrvAddRequestBinaryData SrvAddRequestStringData
Main.dll is the payload that is injected via trusted svchost. It contains as you can see a lot of readable stings, like str.sys for example. We can see str.sys in action here:
Overall, this rootkit was certainly a step up from most SSDT hooking/modification rootkits at the time. It can be a pain in the butt to remove if you don't kill everything properly : )
Thanks for reading.
References
Black Energy 2.1+
BlackEnergy Version 2 Analysis