Mar 18, 2024 03:28 PM
(This post was last modified: Mar 16, 2026 08:20 AM by Hackers.)
DLL Whitelist
Proof of Concept
+ Notes
![[Image: header4.gif]](https://i.ibb.co/C33BJJJg/header4.gif)
Implementing a module whitelist is a way to start focusing on the integrity of your program in runtime. For example, if your program doesn't need user32.dll, then why load it? This POC (proof of concept) implements a strict whitelist of allowed modules ( or .dll files ) and unloads any others.
Benefits for Intellectual Property Protections & Runtime Security
- Due to this being a whitelist & not a blacklist, a program does not have to maintain a list of "unwanted modules" and instead only needs its own "approved modules" list. This is a smaller amount of data, is preserved over time, and is easier for devs to maintain
- The POC can be modified to exit the program or implement different security features upon unknown modules being loaded , rather than simply unloading them
Known Concerns / Dev Notes
- a bad actor could use the name of a whitelisted module & try to load it, tricking your software
- other software can always re-load the module after unloading and this tool doesn't prevent that, it just auto unloads every X milliseconds (defined in code)
- there are other, perhaps better, ways of preventing unwanted modules being loaded
- this is not meant to be a standalone class that accounts for everything you want to do. It's a POC - Take the code as-is & modify it if you want to.
Implications for Anti-*, Blue Team, etc
As this POC is entirely educational & for research purposes only, I find it important to mention some implications here.
Although an auto-unloader does not prevent a module from being loaded again, it can cause issues if a software relies on its dll being loaded into a program. For example, at least 1 well-known provider may be unable to stop file changes on a locked folder vault. In this test case, the provider's anti-ransomware tool was unable to stop a file on Desktop being modified due to its dll being unloaded repeatedly. This may be a unique case and not the norm, but the well-known provider is vulnerable to dll/module unloading attacks for that specific feature while its other main protection software does not have the same issue. It is important for all providers to ensure that their detections do not rely on a module always being loaded, or to detect patterns related to auto-unloading.
https://github.com/LNodesL/DLL-Whitelist-POC
If you want the FULL code, head to the git repo. The following code samples are just highlights:
The Whitelist
- Using this we can get all of the modules loaded into our program/process
- A standard vector list/array of std::string
- I added some common modules. To test, remove 1 that you know will load on your system to see it detected & unloaded
- We have to add our own executable name, which will show up in the module list
- We can use this to get the filename instead of a path
- You may want to consider using pathing instead of just filenames in future, especially if the origin of the module matters to you & not just the name
- Remember the known concerns list above - for 100% security in runtime, this POC won't cover you 100%
- This is the loop that checks to see if anything is in our whitelist or not. If not, it triggers the unload function!
- I recommend removing all std::cout / err / etc from your final program release
Unloading by name:
- In this code we are using standard functions to obtain the module handle & then use FreeLibrary to unload.
- You should look into LdrUnloadDll from ntdll if you want a more direct, lower level method
Using a Thread
One thing you will notice in main.cpp (our prototype / test program) is that we use a detached thread to run the auto unloader:
We do this so that the loop inside of dll-whitelist.cpp doesn't steal the main thread of our int main program.
The int is the number of milliseconds to delay between each module check / loop. 500 = 500ms, or 1/2 of a second (ex: use 1000 for 1 second delay)
- This doesn't delay the first check, just the delay in between
Non-threaded usage:
- This will cause your main program to freeze up / not continue if no changes are made in there.
- I just wanted to push the POC out and write this up - in hindsight I should've just used threads in the included file, not in main
Custom Sleep
The custom sleep function can be worked on or improved, but does work as-is on personal systems. I'm not sure if people know about how clocks can be manipulated in a VM, or if that matters, but I wanted to mention this just in case you haven't thought about that yet. I haven't tested how that works with loop ticks & clocks being manipulated & the implications for this method although I plan on releasing a cryptographic-cracking formula that has a scaling difficulty to implement rough-estimate timing functions without the ability to be skipped - that will be a different POC repo on my gits.
One more thought...
I just used this format for the first time today:
- Usually I would include this in the build / compile command or just use a header and not do it in the file like this, but I figured out that this works too so kept it in. Not sure how others will feel about this.
Proof of Concept
+ Notes
![[Image: header4.gif]](https://i.ibb.co/C33BJJJg/header4.gif)
Implementing a module whitelist is a way to start focusing on the integrity of your program in runtime. For example, if your program doesn't need user32.dll, then why load it? This POC (proof of concept) implements a strict whitelist of allowed modules ( or .dll files ) and unloads any others.
Benefits for Intellectual Property Protections & Runtime Security
- Due to this being a whitelist & not a blacklist, a program does not have to maintain a list of "unwanted modules" and instead only needs its own "approved modules" list. This is a smaller amount of data, is preserved over time, and is easier for devs to maintain
- The POC can be modified to exit the program or implement different security features upon unknown modules being loaded , rather than simply unloading them
Known Concerns / Dev Notes
- a bad actor could use the name of a whitelisted module & try to load it, tricking your software
- other software can always re-load the module after unloading and this tool doesn't prevent that, it just auto unloads every X milliseconds (defined in code)
- there are other, perhaps better, ways of preventing unwanted modules being loaded
- this is not meant to be a standalone class that accounts for everything you want to do. It's a POC - Take the code as-is & modify it if you want to.
Implications for Anti-*, Blue Team, etc
As this POC is entirely educational & for research purposes only, I find it important to mention some implications here.
Although an auto-unloader does not prevent a module from being loaded again, it can cause issues if a software relies on its dll being loaded into a program. For example, at least 1 well-known provider may be unable to stop file changes on a locked folder vault. In this test case, the provider's anti-ransomware tool was unable to stop a file on Desktop being modified due to its dll being unloaded repeatedly. This may be a unique case and not the norm, but the well-known provider is vulnerable to dll/module unloading attacks for that specific feature while its other main protection software does not have the same issue. It is important for all providers to ensure that their detections do not rely on a module always being loaded, or to detect patterns related to auto-unloading.
https://github.com/LNodesL/DLL-Whitelist-POC
If you want the FULL code, head to the git repo. The following code samples are just highlights:
The Whitelist
Code
EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded)Code
int moduleCount = cbNeeded / sizeof(HMODULE);
std::vector<std::string> knownModules = {
"ntdll.dll",
"KERNEL32.DLL",
"KERNELBASE.dll",
"apphelp.dll",
"ucrtbase.dll",
"msvcrt.dll",
"kernel.appcore.dll"
};
knownModules.push_back(GetExecutableFileName());- I added some common modules. To test, remove 1 that you know will load on your system to see it detected & unloaded
- We have to add our own executable name, which will show up in the module list
Code
GetModuleFileNameExA(hProcess, hModules[i], szModName, sizeof(szModName)- You may want to consider using pathing instead of just filenames in future, especially if the origin of the module matters to you & not just the name
- Remember the known concerns list above - for 100% security in runtime, this POC won't cover you 100%
Code
bool isKnownModule = false;
for (const auto& knownModule : knownModules) {
if (moduleName.find(knownModule) != std::string::npos) {
isKnownModule = true;
break;
}
}
if(!isKnownModule) {
std::cout << "Unknown module detected: " << moduleName << std::endl;
UnloadDllByName(moduleName);
}- I recommend removing all std::cout / err / etc from your final program release
Unloading by name:
Code
bool UnloadDllByName(std::string dllName) {
HMODULE hModule = GetModuleHandleA(dllName.c_str()); // Use ANSI version directly
if (hModule == NULL) {
std::cerr << "DLL not found: " << dllName << std::endl;
return false;
}
if (FreeLibrary(hModule)) {
std::cout << "DLL unloaded successfully: " << dllName << std::endl << std::endl;
return true;
} else {
std::cerr << "Failed to unload DLL: " << dllName << std::endl << std::endl;
return false;
}
}- You should look into LdrUnloadDll from ntdll if you want a more direct, lower level method
Using a Thread
One thing you will notice in main.cpp (our prototype / test program) is that we use a detached thread to run the auto unloader:
Code
std::thread t(ForceDllWhitelist, 500);
t.detach();The int is the number of milliseconds to delay between each module check / loop. 500 = 500ms, or 1/2 of a second (ex: use 1000 for 1 second delay)
- This doesn't delay the first check, just the delay in between
Non-threaded usage:
Code
ForceDllWhitelist(500);- I just wanted to push the POC out and write this up - in hindsight I should've just used threads in the included file, not in main
Custom Sleep
The custom sleep function can be worked on or improved, but does work as-is on personal systems. I'm not sure if people know about how clocks can be manipulated in a VM, or if that matters, but I wanted to mention this just in case you haven't thought about that yet. I haven't tested how that works with loop ticks & clocks being manipulated & the implications for this method although I plan on releasing a cryptographic-cracking formula that has a scaling difficulty to implement rough-estimate timing functions without the ability to be skipped - that will be a different POC repo on my gits.
Code
void customSleep(int milliseconds) {
auto start = std::chrono::high_resolution_clock::now();
auto end = start + std::chrono::milliseconds(milliseconds);
while (std::chrono::high_resolution_clock::now() < end) {
// Busy-wait loop.
}
}One more thought...
I just used this format for the first time today:
Code
#include "inc/dllWhitelist.cpp"



![[Image: vibesignature.gif]](https://i.ibb.co/8LvxwtYQ/vibesignature.gif)
![[Image: Screenshot-2024-03-18-210320.png]](https://i.ibb.co/3pN9BWL/Screenshot-2024-03-18-210320.png)