Apr 4, 2024 03:54 AM
(This post was last modified: Mar 16, 2026 08:45 AM by Hackers.)
"Reverse Engineering" Virus Total
& Hybrid Analysis
(C++ POC)
& Hybrid Analysis
(C++ POC)
![[Image: header4.gif]](https://i.ibb.co/C33BJJJg/header4.gif)
Today we will be seeing if we can identify any indicators on Virus Total or Hybrid Analysis that could help us identify when our software is being analyzed on their platform.
I started this POC without knowing if I would be successful, but I quickly figured out how to get this POC started.
The goal? To see what modules are being loaded into a program on a runtime scanning service to identify how they analyze software. Let me show you what I mean...
![[Image: virus-total-detect-modules-2.jpg]](https://i.ibb.co/PQC6F1z/virus-total-detect-modules-2.jpg)
Note: I used .bat to increase detects and make sure each file shows up in the written/dropped files list - I did use .txt in some tests, but this may not always show up in scan details, so stick to .bat or even .exe if you want. To make sure each file is detected better, I threw in the EICAR test string to hopefully trigger more AV actions. The EICAR string didn't trigger all scans like I hoped, but it did increase detects *slightly*.
When a program runs on your computer, it may have its dependencies already included (static build), or it may dynamically link/include them when it starts up in runtime. Either way, C/C++ programs (or C# and others) will load in "modules" so that it has all of the functionality it needs. When you run your software on your own computer, you can easily see what modules are loaded by using the enumerated module list from the program. If you setup a loop to check the loaded module list, you can easily print out the names to understand what is running.
For example, if I run my software on my protected Desktop folder, I can see that AVG (an AV software I have) automatically adds 'aswhook.dll' in runtime.
![[Image: Screenshot-2024-04-04-061525.png]](https://i.ibb.co/9wy9zmF/Screenshot-2024-04-04-061525.png)
However, if I run the same exact program in my whitelisted folder (a personal C++ folder that is not scanned by WD or AVG), I do not have this module loaded.
![[Image: Screenshot-2024-04-04-061732.png]](https://i.ibb.co/vzZ79GG/Screenshot-2024-04-04-061732.png)
This is because AVG doesn't scan the program when it's in my whitelisted directories, so they do not load their hook into my program. This is an example of what we will be trying to find out: What modules load on Virus Total or Hybrid Analysis that don't load locally? I set out to answer the question, Can we identify any unique modules that only load when running in a VT or HA virtual machine / sandbox?
To detect loaded modules on any computer, VM, etc, we must set up a loop that can check the loaded modules:
Code
while (true) {
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
char szModName[MAX_PATH];
// Get the full path to the module's file
if (GetModuleFileNameExA(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(char))) {
std::string moduleName(szModName);
// Check if the module is already tracked
if (!isModuleTracked(trackedModules, moduleName)) {
// Not tracked, so add it to the list and create a log event for it
trackedModules.push_back(moduleName);
logEvent("Module loaded: " + moduleName, getBasename(moduleName));
createModuleFile(moduleName, hMods[i]);
}
}
}
}
else {
std::cerr << "Failed to enumerate process modules." << std::endl;
break; // Exit the loop if module enumeration fails
}
Sleep(1000); // Sleep for 1 second to reduce CPU load
}This is just some basic code using default Windows functionality. It calls the createModuleFile function which is important for tracking on all scanners. Many scanning tools will not report what modules were loaded by their programs, so to track all I came up with an idea: save the module names & info to txt files so they show up in scans as 'written files' or 'dropped files' or similar. This will let us use the file list as our visual list of loaded modules, so we can see what was used on scans!
![[Image: Screenshot-2024-04-03-192744.png]](https://i.ibb.co/X3CH9zY/Screenshot-2024-04-03-192744.png)
![[Image: Screenshot-2024-04-03-192149.png]](https://i.ibb.co/30xj7p9/Screenshot-2024-04-03-192149.png)
I then modified this to include more info, such as the address calling it, and the filepath. The idea is to add more info so that we can start to identify as much as possible about each module.
We can get something like this - the .txt and info in the [ ] brackets are from me, trying to get more info about each. This can definitely be improved further.
Code
"KERNELBASE.dll --- 7fffe5f00000.txt" Location: [C:\KERNELBASE.dll --- 7fffe5f00000.txt]
"gdi32full.dll --- 7fffe64d0000.txt" Location: [C:\gdi32full.dll --- 7fffe64d0000.txt]
"msvcp_win.dll --- 7fffe5d30000.txt" Location: [C:\msvcp_win.dll --- 7fffe5d30000.txt]
"ucrtbase.dll --- 7fffe65f0000.txt" Location: [C:\ucrtbase.dll --- 7fffe65f0000.txt]
"OLEAUT32.dll --- 7fffe7f10000.txt" Location: [C:\OLEAUT32.dll --- 7fffe7f10000.txt]
"KERNEL32.dll --- 7fffe7bc0000.txt" Location: [C:\KERNEL32.dll --- 7fffe7bc0000.txt]
"ADVAPI32.dll --- 7fffe8550000.txt" Location: [C:\ADVAPI32.dll --- 7fffe8550000.txt]
"sechost.dll --- 1ed4f9c0000.txt" Location: [C:\sechost.dll --- 1ed4f9c0000.txt]
"combase.dll --- 7fffe6760000.txt" Location: [C:\combase.dll --- 7fffe6760000.txt]
"apphelp.dll --- 7fffe31c0000.txt" Location: [C:\apphelp.dll --- 7fffe31c0000.txt]
"win32u.dll --- 7fffe62b0000.txt" Location: [C:\win32u.dll --- 7fffe62b0000.txt]
"FLTLIB.dll --- 7fffcbc40000.txt" Location: [C:\FLTLIB.dll --- 7fffcbc40000.txt]
"RPCRT4.dll --- 7fffe77f0000.txt" Location: [C:\RPCRT4.dll --- 7fffe77f0000.txt]
"WS2_32.dll --- 7fffe79a0000.txt" Location: [C:\WS2_32.dll --- 7fffe79a0000.txt]
"msvcrt.dll --- 7fffe7a60000.txt" Location: [C:\msvcrt.dll --- 7fffe7a60000.txt]
"USER32.dll --- 7fffe8600000.txt" Location: [C:\USER32.dll --- 7fffe8600000.txt]
"GDI32.dll --- 7fffe7a30000.txt" Location: [C:\GDI32.dll --- 7fffe7a30000.txt]
"ntdll.dll --- 7fffe8910000.txt" Location: [C:\ntdll.dll --- 7fffe8910000.txt]
"ole32.dll --- 7fffe81b0000.txt" Location: [C:\ole32.dll --- 7fffe81b0000.txt]
"IMM32.dll --- 7fffe84f0000.txt" Location: [C:\IMM32.dll --- 7fffe84f0000.txt]or this, as example of my list from 1 scanning platform without any other info:
Code
ADVAPI32.dll
CRYPT32.dll
DNSAPI.dll
GDI32.dll
IMM32.DLL
IPHLPAPI.DLL
KERNELBASE.dll
LPK.dll
MSASN1.dll
MSCTF.dll
NSI.dll
PSAPI.DLL
RPCRT4.dll
SHELL32.dll
SHLWAPI.dll
USER32.dll
USP10.dll
WINMM.dll
WINNSI.DLL
WS2_32.dll
api-ms-win-core-synch-l1-2-0.DLL
bcrypt.dll
cryptbase.dll
kernel32.dll
msvcrt.dll
ntdll.dll
ole32.dll
sechost.dll
KERNEL32.DLL
KERNELBASE.dll
apphelp.dll
ntdll.dllWhat we are looking for is modules that only load on 1 platform, to see if we can identify any software that is analyzing our program in runtime. Boom, found one!
![[Image: Screenshot-2024-04-04-062640.png]](https://i.ibb.co/CnPdKsH/Screenshot-2024-04-04-062640.png)
frida-agent is suspicious because it's associated with Frida, a free software for runtime analysis. We can basically assume that if frida is loaded into our program, its being debugged/analyzed/etc.
Another indicator for your program being debugged in runtime is anything related to Flt - the filter manager for Windows that lets AV use minifilters to detect system changes & actions.
![[Image: Screenshot-2024-04-04-063349.png]](https://i.ibb.co/PmxvQ7f/Screenshot-2024-04-04-063349.png)
Checkout my thread here which could be useful for detecting more about minifilters: https://hackforums.net/showthread.php?ti...minifilter
All in all, this POC is only intended to give us the data - it's up to you how you want to use this information. I think that there could easily be some work done to detect certain settings that are used, so your program knows 100% if it's in VT, HA, or another platform's sandbox modes. This may be useful to you. Keep in mind also that it might not always be a case of "this module was loaded" and may actually be related to not just which module is loaded, but when it is loaded, how it's loaded (order, from where, etc), and other context should be taken into account besides just looking for module names. Best of luck! :)
Here is an example of what it may look like if you have systeminfo and also the timestamp in the filename. We can see that it used Windows 7 (weird), x64, with 1GB of RAM. Keep in mind that this may be 'fake' to trick programs into triggering "all features" if they think they will work on an older Win7 machine? Not sure, but it was a surprise to see such an old version in use for this.
![[Image: Screenshot-2024-04-04-064343.png]](https://i.ibb.co/JcKxSWK/Screenshot-2024-04-04-064343.png)
There are many directions to go from here, but I hope this POC helps you understand how the filelist on Virus Total, Hybrid Analysis, and similar scanners can be used to track information. Because they print all written files, you can simply use the filenames as a personal log for any runtime data you want :)
Here is the full code to the POC, which has everything needed for the module detection, system info, RAM amount, architecture, etc saved to the filename.
Code
#include <windows.h>
#include <psapi.h>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <ctime>
#include <VersionHelpers.h>
std::string getSystemInfo() {
std::ostringstream oss;
oss << "Windows_";
if (IsWindows10OrGreater()) {
oss << "10_or_greater";
}
else if (IsWindows8Point1OrGreater()) {
oss << "8.1";
}
else if (IsWindows8OrGreater()) {
oss << "8";
}
else if (IsWindows7OrGreater()) {
oss << "7";
}
else {
oss << "Older_version";
}
SYSTEM_INFO siSysInfo;
GetSystemInfo(&siSysInfo);
oss << "_" << ((siSysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) ? "x64" : "x86");
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
GlobalMemoryStatusEx(&statex);
oss << "_RAM" << statex.ullTotalPhys / (1024 * 1024 * 1024) << "GB";
return oss.str();
}
std::string getCurrentSystemTime() {
time_t now = time(nullptr);
struct tm tstruct;
char buf[80];
localtime_s(&tstruct, &now); // Secure version of localtime
strftime(buf, sizeof(buf), "%Y%m%d_%H%M%S", &tstruct);
return std::string(buf);
}
// Enhanced Logging with Detailed Filenames
std::string generateLogFileName(const std::string& logType, const std::string& moduleName) {
std::string timestamp = getCurrentSystemTime();
std::string systemInfo = getSystemInfo();
std::string fileName = logType + "_" + moduleName + "_" + systemInfo + "_" + timestamp + ".exe";
return fileName;
}
void logEvent(const std::string& message, const std::string& moduleName) {
std::string fileName = generateLogFileName("EventLog", moduleName);
std::ofstream file(fileName, std::ios_base::app);
if (file.is_open()) {
file << message << std::endl;
file.close();
}
else {
std::cerr << "Unable to open log file: " << fileName << std::endl;
}
}
// Function to check if the module is already tracked
bool isModuleTracked(const std::vector<std::string>& trackedModules, const std::string& moduleName) {
for (const auto& trackedModule : trackedModules) {
if (trackedModule == moduleName) {
return true;
}
}
return false;
}
std::string wideStringToNarrowString(const std::wstring& wstr) {
if (wstr.empty()) return "";
int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
std::string strTo(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
return strTo;
}
std::string getBasename(const std::string& filepath) {
size_t lastSlash = filepath.find_last_of("/\\");
if (lastSlash == std::string::npos) {
return filepath; // No slashes found, the whole filepath is a basename
}
return filepath.substr(lastSlash + 1);
}
std::wstring stringToWideString(const std::string& str) {
if (str.empty()) return std::wstring();
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
std::wstring wstrTo(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed);
return wstrTo;
}
std::wstring getProcessName() {
WCHAR processName[MAX_PATH] = L"<unknown>";
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId());
if (nullptr != hProcess) {
HMODULE hMod;
DWORD cbNeeded;
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) {
GetModuleBaseNameW(hProcess, hMod, processName, sizeof(processName) / sizeof(WCHAR));
}
CloseHandle(hProcess);
}
return std::wstring(processName);
}
std::string generateFileName(const std::string& moduleName, HMODULE moduleHandle) {
std::wstring processName = getProcessName(); // Get the name of the current process in wide string
std::wstring wideModuleName(moduleName.begin(), moduleName.end()); // Convert moduleName to wide string
std::wstring fileName = processName + L"_" + wideModuleName + L".txt"; // Combine into wide string filename
return wideStringToNarrowString(fileName); // Convert the wide string filename back to a narrow string
}
void createModuleFile(const std::string& moduleName, HMODULE moduleHandle) {
std::string fileName = generateFileName(moduleName, moduleHandle);
std::wstring wideFileName = stringToWideString(fileName);
std::wofstream file(wideFileName);
if (file.is_open()) {
wchar_t exePath[MAX_PATH];
if (GetModuleFileNameW(NULL, exePath, MAX_PATH)) {
std::wstring executableName = exePath; // This will contain the full path of the executable in wide characters
// Convert the executable name to a narrow string for output (optional, based on your needs)
std::vector<char> narrowPath(MAX_PATH);
WideCharToMultiByte(CP_UTF8, 0, exePath, -1, &narrowPath[0], MAX_PATH, NULL, NULL);
std::string narrowExecutableName(narrowPath.begin(), narrowPath.end());
// Write the module name and the name/path of the executable to the file
file << stringToWideString(moduleName + " is loaded by " + narrowExecutableName + ".\n");
}
else {
file << stringToWideString(moduleName + " is loaded.\n"); // Fallback in case GetModuleFileNameW fails
}
std::string eicarTestString = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*";
file << stringToWideString(eicarTestString); // Add the EICAR string to the file contents
file.close();
std::wcout << L"File created! : " << wideFileName << std::endl;
}
else {
std::wcerr << L"Unable to create file for module! : " << wideFileName << std::endl;
}
}
int main() {
HANDLE hProcess = GetCurrentProcess(); // Get a handle to the current process
if (hProcess == NULL) {
std::cerr << "Failed to get current process handle." << std::endl;
return 1; // Early exit if unable to get process handle
}
std::vector<std::string> trackedModules; // Track modules to avoid duplication
HMODULE hMods[1024];
DWORD cbNeeded;
while (true) { // Continuous loop, consider adding a condition for termination
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
char szModName[MAX_PATH];
// Get the full path to the module's file
if (GetModuleFileNameExA(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(char))) {
std::string moduleName(szModName);
// Check if the module is already tracked
if (!isModuleTracked(trackedModules, moduleName)) {
// Not tracked, so add it to the list and create a log event for it
trackedModules.push_back(moduleName);
logEvent("Module loaded: " + moduleName, getBasename(moduleName));
createModuleFile(moduleName, hMods[i]);
}
}
}
}
else {
std::cerr << "Failed to enumerate process modules." << std::endl;
break; // Exit the loop if module enumeration fails
}
// Consider adding a sleep delay here to reduce CPU usage
Sleep(1000); // Sleep for 1 second to reduce CPU load
}
CloseHandle(hProcess); // Clean up the process handle
return 0;
}I hope you found this useful - please feel free to ask any questions you may have in this thread here :)
If you want to PM or convo then please start the PM or convo with me once you are upgraded & able to. Thanks!



![[Image: nodeslovesrussia.png]](https://i.ibb.co/Nskr6x9/nodeslovesrussia.png)
![[Image: Screenshot-2024-04-11-151108.png]](https://i.ibb.co/YQR2ZbJ/Screenshot-2024-04-11-151108.png)