-派生子类窗口,要派生窗口属于另一个进程
SetWindowLongPtr会检查一个进程试图修改的WndProc是否是另一个进程的窗口的,如果是,忽略调用。
可以在进程A获得进程B窗口的句柄。
FindWindow(xx);
// 派生子类窗口,现在应用:
SetWindowSubclass
GetWindowSubclass
RemoveWindowSubclass
DefSubclassProc
-通过使用注册表来注入DLL
1.注册表编辑器
Registry Editor
2.
HKEY_LOCAL_MACHINE\SoftWare\Microsoft\ Windows NT\CurrentVersion\Windows\
AppInit_Dlls键值可能包含一个DLL文件名或一组DLL文件名(用空格或逗号分隔)。
第一个DLL的文件名可包含路径,其它DLL包含的路径被忽略。
为了让系统使用这个注册表项,须创建一个项:
DWORD LoadAppInit_Dlls = 1
3.
处理过程:
User32.dll被映射到一个新进程时,会收到DLL_PROCESS_ATTACH通知。
User32.dll对它处理时,会取得上述注册表键值。调用LoadLibrary载入这个字符串中指定的每个DLL。
被注入的DLL,在进程生命期早期载入的。User32.dll不会检查每个DLL的载入和初始化是否成功。
-使用windows挂钩来注入DLL
// 安装挂钩
HHOOK WINAPI SetWindowsHookEx(
// 要挂钩的类型
_In_ int idHook,
// 挂钩函数地址
_In_ HOOKPROC lpfn,
// DLL标识,进程地址空间DLL被映射到的虚拟内存地址
_In_ HINSTANCE hMod,
// 要安装挂钩的线程
// 0 给系统中所有线程安装
_In_ DWORD dwThreadId
);
举例:
a.进程A安装了WH_GETMESSAGE挂钩。
HHOOK hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, hInstDll, 0);
b.
进程B的一个线程准备向一个窗口 派送消息。
系统检查该线程是否安装了 WH_GETMESSAGE挂钩。
系统检查GetMsgProc所在的DLL是否被映射到了进程B的地址空间。
如未映射,系统会强制将该DLL映射到进程B的地址空间,并将进程B中该DLL的锁计数器递增。
检查该DLL在进程B的基地址和进程A的基地址是否相同。
相同时,可直接在进程A的地址空间中调用GetMsgProc。
不同时,系统须确定GetMsgProc在进程B地址空间的虚地址:
GetMsgProc B = hInstDll B + (GetMsgProc A - hInstDll A)
系统在进程B中递增该DLL的锁计数器
系统在进程B的地址空间调用GetMsgProc函数。
GetMsgProc返回时,系统递减该DLL在进程B的锁计数器。
系统把挂钩过滤函数所在的DLL注入或映射到地址空间时,会映射整个DLL。
c.不需要DLL时,从进程地址空间撤销对它的映射
BOOL WINAPI UnhookWindowsHookEx(
_In_ HHOOK hhk
);
线程调用UnhookWindowsHookEx时,系统会遍历自己内部的一个已经注入过该DLL的进程列表,并将该DLL的锁计数器递减。
锁计数器减到0时,系统自动从进程的地址空间撤销对该DLL的映射。
-其它
一些公共空间的窗口消息,携带指针信息,指针一般使窗口所在进程地址空间内的虚拟地址,这些地址不应被另一进程使用。
1.
// 由窗口句柄,取得窗口所在线程,进程ID
DWORD WINAPI GetWindowThreadProcessId(
_In_ HWND hWnd,
_Out_opt_ LPDWORD lpdwProcessId
);
2.
// 安装一个应用定义的挂钩处理函数。
// 用于监视指定线程的,或和调用线程在相同桌面的所有线程的事件。
HHOOK WINAPI SetWindowsHookEx(
// 事件类型:消息投递到线程队列
// WH_GETMESSAGE
_In_ int idHook,
// 处理函数
_In_ HOOKPROC lpfn,
// 处理函数所在的模块。
// 指定为当前进程内线程时,模块为NULL
// 指定为其它进程的线程时,模块为包含处理函数的DLL
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
// WH_GETMESSAGE
类型回调函数
// 调用时机:线程消息队列出现有效消息
// 挂钩处理完,指定线程的GetMessage和PeekMessage调用才能返回
LRESULT CALLBACK GetMsgProc(
// HC_ACTION
// < 0,return CallNextHookEx继续处理
_In_ int code,
_In_ WPARAM wParam,
// MSG*,消息细节
_In_ LPARAM lParam
);
窗口句柄可以跨进程使用。
进程A可以取得进程B的所创建的一个窗口的句柄,进而给进程B的窗口发消息。
一些后来的通用控件,不能接收和处理另一个进程给它发的消息。
早期的控件,为了和16为windows兼容(16位windows所有进程共享同一地址空间),都做了,跨进程接收处理消息。
-注册表操作
注册表是系统定义的数据库,应用和系统组件在其中取得和存储配置数据。
可用注册表API来取得,修改,删除注册数据。
1.注册表的结构
键,有名字。
键名大小写不敏感。不包含’\’,值名和值可包含。
键–子键
子键–子键
值:值名,类型,值内容
1.1.注册值类型
REG_BINARY
二进制
REG_DWORD
32位数
REG_EXPAND_SZ
形如:”%PATH%”,ExpandEnvironmentStrings用于展开。
REG_LINK
REG_MULTI_SZ
形如:
“String1\0 String2\0 String3\0 LastString\0\0
”
“\0”
REG_QWORD
64位数
REG_SZ
字符串
存储字符串时,指定的长度要包含终止符’\0’。
字符串以ANSI还是UNICODE被存储,取决与存储时API的类型。
1.2.注册表元素尺寸限制
键名:255字节,包含键的绝对路径。
值名:16383字符。
值:
树:512深度
2.注册表容量空间
3.预定义键
用于在添加数据到注册表前,须先打开键。
为了打开键,应用须提供一个注册表中其它已打开键的句柄,系统预定义键通常被打开。
预定义键帮助应用在注册表导航。
HKEY_CLASSES_ROOT:
Shell和COM应用使用存储在这个键下的信息。
文件浏览和用户界面扩展存储它们的OLE类标识在此键下。
进程服务。
HKEY_CURRENT_CONFIG
:
包含本机系统当前硬件配置信息。
描述当前硬件配置和标准配置信息的不同。
标准硬件配置信息,存储在HKEY_LOCAL_MACHINE的software和System下。
HKEY_CURRENT_USER
:
当前用户,环境变量,程序组数据,颜色,打印,网络链接,应用设置。
HKEY_CURRENT_USER_LOCAL_SETTINGS
:
HKEY_LOCAL_MACHINE
:
电脑物理状态
HKEY_PERFORMANCE_DATA
:
存储性能数据
HKEY_USERS
:
默认用户配置
当前用户配置
4.Registry Hives:当前用户配置信息
存放配置信息文件目录:%SystemRoot%\System32\Config
文件后缀及含义:
none:hive数据的完整拷贝
.alt:HKEY_LOCAL_MACHINE\System
.log:日志
.sav
:备份
HKEY_CURRENT_CONFIG :
System, System.alt, System.log, System.sav
HKEY_CURRENT_USER:
Ntuser.dat, Ntuser.dat.log
HKEY_LOCAL_MACHINE\SAM:
Sam, Sam.log, Sam.sav
HKEY_LOCAL_MACHINE\Security:
Security, Security.log, Security.sav
HKEY_LOCAL_MACHINE\Software:
Software, Software.log, Software.sav
HKEY_LOCAL_MACHINE\System:
System, System.alt, System.log, System.sav
HKEY_USERS.DEFAULT:
Default, Default.log, Default.sav
5.数据目录
电脑指定数据:
放置于 HKEY_LOCAL_MACHINE\Software下
用户指定数据:
放置于HKEY_CURRENT_USER\Software下
6.打开,创建,关闭键
// 打开注册表
// 成功时,返回ERROR_SUCCESS.
LONG WINAPI RegOpenKeyEx(
// 打开的注册表键的句柄,可由RegOpenKeyEx或RegCreateKeyEx返回。
// 可为以下预定义键:
// HKEY_CLASSES_ROOT
// HKEY_CURRENT_CONFIG
// HKEY_CURRENT_USER
// HKEY_LOCAL_MACHINE
// HKEY_USERS
_In_ HKEY hKey,
// 要被打开的注册表子键名
_In_opt_ LPCTSTR lpSubKey,
// 打开选项
// REG_OPTION_OPEN_LINK
// 0
_In_ DWORD ulOptions,
// 想要的权限
_In_ REGSAM samDesired,
// 接收打开键句柄
_Out_ PHKEY phkResult
);
// 创建
// 调用进程须有对指定键的KEY_CREATE_SUB_KEY权限。
// 成功时,ERROR_SUCCESS
// 不能创建HKEY_USER HKEY_LOCAL_MACHINE的一级子键
LONG WINAPI RegCreateKeyEx(
// 注册表键的句柄,可由RegOpenKeyEx或RegCreateKeyEx返回。
// 可为以下预定义键:
// HKEY_CLASSES_ROOT
// HKEY_CURRENT_CONFIG
// HKEY_CURRENT_USER
// HKEY_LOCAL_MACHINE
// HKEY_USERS
_In_ HKEY hKey,
// 要打开或创建的子键名
_In_ LPCTSTR lpSubKey,
// 0
_Reserved_ DWORD Reserved,
// NULL
_In_opt_ LPTSTR lpClass,
// REG_OPTION_BACKUP_RESTORE
// REG_OPTION_NON_VOLATILE 键被写入文件,值被保存
// REG_OPTION_VOLATILE 键信息放于内存
_In_ DWORD dwOptions,
// 想要的权限
_In_ REGSAM samDesired,
// 安全描述
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// 接收创建或打开键句柄
_Out_ PHKEY phkResult,
// REG_CREATED_NEW_KEY 创建的
// REG_OPENED_EXISTING_KEY 打开的
_Out_opt_ LPDWORD lpdwDisposition
);
// 关闭指定键值句柄
// 关闭时,数据并不立即写入磁盘
LONG WINAPI RegCloseKey(
_In_ HKEY hKey
);
// 立即写数据到磁盘
LONG WINAPI RegCloseKey(
_In_ HKEY hKey
);
7.写/删除注册表数据
// 设置键
// 成功时,ERROR_SUCCESS
LONG WINAPI RegSetValueEx(
// 一个打开键的句柄,键应以KEY_SET_VALUE权限被打开
// 或
// HKEY_CLASSES_ROOT
// HKEY_CURRENT_CONFIG
// HKEY_CURRENT_USER
// HKEY_LOCAL_MACHINE
// HKEY_USERS
_In_ HKEY hKey,
// 值名。值名在键下不存在时,创建。
_In_opt_ LPCTSTR lpValueName,
// 0
_Reserved_ DWORD Reserved,
// 数据类型
_In_ DWORD dwType,
// 值
// 字符串中'\'应以'\\'形式被存储
_In_ const BYTE *lpData,
// 尺寸。类型为字符串时,尺寸大小要包括终止符。
_In_ DWORD cbData
);
// 删除值
LONG WINAPI RegDeleteValue(
_In_ HKEY hKey,
_In_opt_ LPCTSTR lpValueName
);
// 删除键
// 键下的值也会被删除
// 删除的键,在指向它的最后一个句柄被关闭时,才移除。
LONG WINAPI RegDeleteKey(
_In_ HKEY hKey,
// 键本身不能有子键
_In_ LPCTSTR lpSubKey
);
8.从注册表取得数据
从注册表取得数据,应用枚举键的子键,直到发现目标,然后取得数据。
// 枚举键的子键
RegEnumKeyEx
// 取得详细数据
RegQueryInfoKey
// 取得安全描述
RegGetKeySecurity
// 枚举键的值 值名。
RegEnumValue
// ERROR_NO_MORE_ITEMS 表示枚举结束
// 存储在注册表的字符串可能存储时没有用终止符结尾。
// 从注册表取得字符串,并使用时,应确保使用的字符串以终止符结尾。
LONG WINAPI RegEnumValue(
_In_ HKEY hKey,
// 每次枚举前,递增该索引
_In_ DWORD dwIndex,
// 接收值名。缓冲要足够大可容纳终止符。
_Out_ LPTSTR lpValueName,
// 缓存字符大小。
// 返回时,指示缓冲中字符数。不含终止符。
_Inout_ LPDWORD lpcchValueName,
// NULL
_Reserved_ LPDWORD lpReserved,
// 接收数据类型
_Out_opt_ LPDWORD lpType,
// 接收数据
_Out_opt_ LPBYTE lpData,
// 指定缓冲尺寸。尺寸要包含终止符。
// 返回时,接收缓冲中存储字节数。
_Inout_opt_ LPDWORD lpcbData
);
// 取得特定值。值名–>值
RegQueryValueEx
// 取得多个值
RegQueryMultipleValues
// 监视键变化
RegNotifyChangeKeyValue,键改变时,通知调用线程。键改变时,事件对象被触发,监视停止。
9.注册表文件
应用可保存部分注册表在文件,之后,把文件内容加载到注册表。
为保存键及其子键,值到注册表文件,应用可调用RegSaveKey或RegSaveKeyEx。
// 创建文件保存注册表部分数据
// 对本地键,文件被创建在进程当前目录
// 对远程键,在%systemroot%\system32
RegSaveKey/RegSaveKeyEx。
// 把注册表文件写回注册表
RegLoadKey/RegReplaceKey/RegRestoreKey
RegLoadKey:
加载注册表数据从指定文件,到HKEY_USERS或HKEY_LOCAL_MACHINE下的指定子键,在调用应用电脑或远程电脑。RegUnLoadKey恢复注册表到之前状态。
RegReplaceKey:
用文件包含数据替代键及其子键,值。下次启动生效。
RegRestoreKey:
加载注册表数据,从指定文件到指定键。
10.注册表键安全性和获取权限
// 获得安全描述
RegGetKeySecurity
//
GetNamedSecurityInfo
//
GetSecurityInfo
权限列表:
KEY_CREATE_SUB_KEY
KEY_ENUMERATE_SUB_KEYS
KEY_EXECUTE
KEY_NOTIFY
KEY_QUERY_VALUE
KEY_READ
KEY_SET_VALUE
KEY_WRITE
11.注册表中的32位和64位应用数据
在64位windows,部分注册表条目为32位应用,64位应用分开存储,且映射到独立的逻辑注册表视图。
12.注册表虚拟化
-使用远程线程来注入DLL
提供了最高的灵活性。
在目标进程中创建一个线程。
HANDLE CreateRemoteThread(
HANDLE hProcess,
PSECURITY_ATTRIBUTES psa,
DWORD dwStackSize,
// 线程函数的内存地址,内存地址应在远程进程地址空间
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD fdwCreate,
PDWORD pdwThreadId
);
// 远程线程调用LoadLibrary
HMODULE LoadLibrary(PCTSTR pszLibFile);
// 线程函数
DWORD WINAPI ThreadFunc(PVOID pvParam);
编译和链接一个程序时,生成的二进制文件中会包含一个导入段。
这个段由一系列转换函数构成。
链接器会生成一个调用,调用模块导入段的转换函数,转换函数会跳转到实际的函数。
略过转换函数,直接调用指定函数,须用GetProcAddress得到函数地址。
//
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
HANDLE hThread = CreateRemoteThread(
hProcessRemote,
NULL,
0,
pfnThreadRtn,
L("C:\\MyLib.dll"),
0,
NULL
);
// ANSI
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(
hProcessRemote,
NULL,
0,
pfnThreadRtn,
"C:\\MyLib.dll",
0,
NULL
);
需要把DLL的路径字符串存放到远程进程的地址空间,然后,调用CreateRemoteThread时,需传入远程进程中存放字符串的地址。
// 允许在另一进程地址空间预订+调拨。预订的地址是远程进程地址空间的
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
// 释放另一进程地址空间的内存。撤销预订+撤销调拨
BOOL WINAPI VirtualFreeEx(
_In_ HANDLE hProcess,
_In_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD dwFreeType
);
// windows提供了一些函数,可以让一个进程对另一个进程的地址空间进行读,写
BOOL ReadProcessMemory(
// 远程进程
HANDLE hProcess,
// 远程进程地址空间地址
LPCVOID pvAddressRemote,
// 调用进程地址空间地址
PVOID pvBufferLocal,
// 要传输字节
SIZE_T dwSize,
// 实际传输字节
SIZE_T* pdwNumBytesRead
);
BOOL WriteProcessMemory(
// 远程进程
HANDLE hProcess,
// 远程进程地址空间地址
PVOID pvAddressRemote,
// 调用进程地址空间地址
LPCVOID pvBufferLocal,
// 要传输字节
SIZE_T dwSize,
// 实际传输字节
SIZE_T* pdwNumBytesWritten
);
-Image Walk DLL
依据模块地址,取得模块路径
// 不去的以LOAD_LIBRARY_AS_DATAFILE 标志加载模块的路径
// 不包含终止符的拷贝字符数
DWORD WINAPI GetModuleFileName(
_In_opt_ HMODULE hModule,
_Out_ LPTSTR lpFilename,
// 字符数。包含终止符。
_In_ DWORD nSize
);
-使用木马来注入DLL
把知道的进程必然会载入的一个DLL替换掉。
例:
知道进程A会载入Xyz.dll。
创建自己的DLL,给它起同样的文件名。将原来的Xyz.dll改名。
自己的DLL内部,导出原来Xyz.dll导出的所有符号。
可给DLL起一个独一无二名字,修改exe模块的导入段。
在导入段找到要被替换的DLL名称,改为我们自己的DLL。
-把DLL作为调试器注入
载入被调试程序时,会在被调试程序地址空间准备完毕后,被调试程序主线程尚未开始执行前,通知调试器。
调试器这时,可将一些代码注入到被调试程序的地址空间,然后让被调试程序主线程执行注入代码。
-使用CreateProcess注入代码
父进程,在子进程主线程地址处,写入机器指令。达到主线程处,执行LoadLibrary功能,载入完毕。恢复主线程地址处内容,让进程从原来起始地址执行。
-API拦截
例:
DLL在卸载时,执行清理代码。
清理代码可能使用其它DLL中的函数,来执行清理。
但清理时,其它DLL可能已被卸载,导致该DLL函数调用失败,清理失败。
拦截ExitProcess:
ExitProcess被调用时,目标DLL立刻被通知,执行清理。之后,执行ExitProcess默认行为:通知所有DLL。
目标DLL被载入时,遍历所有已被载入的可执行模块和DLL模块,找到对ExitProcess的所有调用。
对各模块修改,使他们调用该公司DLL中的一个函数。
替代函数,先执行清理代码,再执行ExitProcess。
1.通过覆盖代码来拦截API
在内存对要拦截的函数进行定位,得到它的内存地址。
把函数的起始几个字节保存到我们自己的内存。
用CPU的一条JUMP指令来覆盖这个函数起始的几个字节,这条JUMP用来跳转到我们替代函数的内存地址。替代函数的函数签名须与要拦截函数的函数签名相同。
线程调用被拦截函数时,跳转指令会跳转到替代函数。
把保存的字节放回被拦截函数的起始几个字节来撤销拦截。
缺陷:
跳转的机器指令对CPU有依赖性,不同CPU下跳转指令不同。
不具备多线程安全性,线程覆盖起始位置代码需要时间,此时另一线程可能调用同一函数。
-修改模块的导入段来拦截API
须理解动态链接的工作方式。
理解模块的导入段包含了什么信息。
模块的导入段,包含一组DLL,还包含一个符号表。
当该模块调用一个导入函数时,线程实际先从模块的导入表,得到导入函数地址,再跳转到那个地址。
为拦截一个特定函数,须修改它在模块导入段中地址。
// 在一个模块的导入段,查找对一个符号的引用。如存在,修改该符号的地址。
void CAPIHook::ReplaceIATEntryInOneMod(
PCSTR pszCalleeModName,
PROC pfnCurrent,
PROC pfnNew,
HMODULE hmodCaller
)
{
ULONG ulSize;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
__try
{
pImportDesc =
(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
hmodCaller,
TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,
&ulSize);
}
__except(InvalidReadExceptionFilter(GetExceptionInformation()))
{
//
}
if(pImportDesc == NULL)
{
return;
}
for(; pImportDesc->Name; pImportDesc++)
{
PSTR pszModName = (PSTR)((PBYTE)hmodCaller + pImportDesc->Name);
if(lstrcmpiA(pszModName, pszCalleeModName) == 0)
{
PIMAGE_THUNK_DATA pThunk =
(PIMAEG_THUNK_DATA)
((PBYTE)hmodCaller + pImportDesc->FirstThunk);
for(; pThunk->ul.Function; pThunk++)
{
PROC* ppfn = (PROC*)&pThunk->ul.Function;
BOOL bFound = (*ppfn == pfnCurrent);
if(bFound)
{
if(!WriteProcessMemory(
GetCurrentProcess(),
ppfn,
&pfnNew,
sizeof(pfnNew),
NULL)
&&
(ERROR_NOACCESS == GetLastError()))
{
DWORD dwOldProtect;
if(VirtualProtect(ppfn,
sizeof(pfnNew),
PAGE_WRITECOPY,
&dwOldProtect))
{
WriteProcessMemory(
GetCurrentProcess(),
ppfn,
&pfnNew,
sizeof(pfnNew),
NULL);
VirtualProtect(
ppfn,
sizeof(pfnNew),
dwOldProtect,
&dwOldProtect
);
}
}
return;
}
}
}
}
}
PROC pfnOrig = GetProcAddress(
GetModuleHandle("Kernel32"),
"ExitProcess"
);
HMODULE hmodCaller = GetModuleHandle(
"Database.exe"
);
// 之后,任何线程执行Database.exe模块调用的ExitProcess时,会调用我们的替代函数。
// 须对应用的所有模块做相应处理
// 动态调用的LoadLibrary产生的载入模块(包含载入模块会由于DLL关联而隐式载入的DLL)也需考虑
// 通过GetProcAdddress直接获取目标函数地址方式的调用也须考虑
ReplaceIATEntryInOneMod(
"Kernel32.dll",
pfnOrig,
MyExitProcess,
hmodCaller
);
模块的导入段中的所有字符串都以ANSI格式保存。
-其它
GetProcAddress
参数:
模块基地址
函数名/函数序号
返回:
函数地址
// 定位一个目录项,在映像文件头,
// 返回对目录项数据地址
// 不具备多线程安全性
// 成功时,返回指向查询项相关信息的结构
PVOID WINAPI ImageDirectoryEntryToDataEx(
// 映像或数据文件的基地址
_In_ PVOID Base,
// TRUE 文件按映像(DLL或EXE)被映射
// FALSE 文件按数据被映射
_In_ BOOLEAN MappedAsImage,
// 要定位的目录项
// IMAGE_DIRECTORY_ENTRY_BASERELOC 基地址重定位表
// IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 绑定的导入目录
// IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 延迟导入表
// IMAGE_DIRECTORY_ENTRY_EXPORT 导出目录
// IMAGE_DIRECTORY_ENTRY_GLOBALPTR 全局指针相关虚拟地址
// IMAGE_DIRECTORY_ENTRY_IAT 导入地址表
// IMAGE_DIRECTORY_ENTRY_IMPORT 导入目录
// IMAGE_DIRECTORY_ENTRY_RESOURCE 资源目录
// IMAGE_DIRECTORY_ENTRY_TLS 线程本地存储目录
_In_ USHORT DirectoryEntry,
// 定位目录项数据尺寸
_Out_ PULONG Size,
// 接收数据
_Out_opt_ PIMAGE_SECTION_HEADER *FoundHeader
);