生成延迟导入表
在vs项目属性 => 连接器 => 输入 => 延迟加载的dll
dll加载
延迟导入表,在PE头的数据目录中,有一个单独的类别
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
延迟导入表的数据目录
HEADER:000007FF4E8001D8 ; Delay Load Import Descriptors
HEADER:000007FF4E8001D8 dd rva __DELAY_IMPORT_DESCRIPTOR_API_MS_WIN_Service_Core_L1_1_0_dll ; Virtual address
HEADER:000007FF4E8001DC dd 1E0h ; Size
HEADER:000007FF4E8001E0 dd 2 dup(0) ; COM Runtime descriptor
HEADER:000007FF4E8001E8 dd 2 dup(0) ; Image data directory 15
对应的数据结构为
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
其中VirtualAddress是所有延迟导入表的开始。这里拿__DELAY_IMPORT_DESCRIPTOR_HTTPAPI_dll举例,看下其对应的数据
.text:000007FF4E9CD094 __DELAY_IMPORT_DESCRIPTOR_HTTPAPI_dll dd 1
.text:000007FF4E9CD094 ; DATA XREF: __tailMerge_HTTPAPI_dll+33↑o
.text:000007FF4E9CD094 ; Attributes
.text:000007FF4E9CD098 dd rva __sz_HTTPAPI_dll ; "HTTPAPI.dll"
.text:000007FF4E9CD09C dd rva __hmod__HTTPAPI_dll ; Module handle
.text:000007FF4E9CD0A0 dd rva __imp_HttpSetServiceConfiguration ; Delayed Import Address Table
.text:000007FF4E9CD0A4 dd rva HTTPAPI_dll_dint ; Delayed Import Name Table
.text:000007FF4E9CD0A8 dd rva HTTPAPI_dll_dbiat ; Bound Delayed Import Address Table
.text:000007FF4E9CD0AC dd 0 ; Unload Delayed Import Table
.text:000007FF4E9CD0B0 dd 0 ; Time stamp
延迟导入表对应的结构为
typedef struct _IMAGE_DELAY_IMPORT_DESCRIPTOR
{
DWORD Attributes; // 保留
RVA RVA_DLLName; // 指向延迟加载dll的名字字符串的RVA
RVA RVA_ModuleHandle; // 指向DLL句柄的RVA
RVA RVA_DelayIAT; // RVA of the IAT
RVA RVA_DelayINT; // RVA of the INT
RVA RVA_BoundIAT; // RVA of the optional bound IAT
RVA RVA_UnloadIAT; // RVA of optional copy of original IAT
DWORD dwTimeStamp; // 0 if not bound,
// 绑定到DLL的时间戳
} IMAGE_DELAY_IMPORT_DESCRIPTOR, *PIMAGE_DELAY_IMPORT_DESCRIPTOR;
到目前为止,跟普通的导入表是类似的,RVA_DelayIAT就是导入地址表,RVA_DelayINT就是导入名称表。 这里以__DELAY_IMPORT_DESCRIPTOR_HTTPAPI_dll(httpapi)为例,对应的延迟导入表数据是这样的
000007FEEE46C204 01 00 00 00 40 1B 00 00 D0 5B 1D 00 20 54 1D 00 ....@...Ð[.. T..
000007FEEE46C214 88 C5 1C 00 28 D2 1C 00 00 00 00 00 00 00 00 00 .Å..(Ò..........
RVA_DelayIAT 对应的RVA偏移为 0x1D5420,
000007FEEE475420 000007FEEE4203D2
000007FEEE475428 000007FEFB2320D8 httpapi.HttpCreateHttpHandle
000007FEEE475430 000007FEEE420372
000007FEEE475438 000007FEFB232168 httpapi.HttpAddUrl
000007FEEE475440 000007FEEE42038A
000007FEEE475448 000007FEEE420396
000007FEEE475450 000007FEFB231470 httpapi.HttpReceiveHttpRequest
000007FEEE475458 000007FEEE4203AE
000007FEEE475460 000007FEEE4203BA
000007FEEE475468 000007FEEE4203C6
000007FEEE475470 000007FEEE4203DE
000007FEEE475478 000007FEEE4203EA
000007FEEE475480 000007FEFB231B80 httpapi.HttpInitialize
000007FEEE475488 0000000000000000
这里拿 第一项为例000007FEEE4203D2对应的汇编为
000007FEEE4203D2 | 48:8D05 47500500 | lea rax,qword ptr ds:[7FEEE475420] |
000007FEEE4203D9 | E9 0FFFFFFF | jmp <wsmsvc.__tailMerge_HTTPAPI_dll> |
可以看到jmp到了__tailMerge_HTTPAPI_dll这个函数,通过ida查看对应的代码
__int64 __usercall _tailMerge_HTTPAPI_dll@<rax>(__int64 a1@<rax>, __int64 a2@<rdx>, __int64 a3@<rcx>, __int64 a4@<r8>, __int64 a5@<r9>, __m128i a6@<xmm0>, __m128i a7@<xmm1>, __m128i a8@<xmm2>, __m128i a9@<xmm3>)
{
FARPROC v9; // rax
__int128 v11; // [rsp+20h] [rbp-48h]
__int128 v12; // [rsp+30h] [rbp-38h]
__int128 v13; // [rsp+40h] [rbp-28h]
__int128 v14; // [rsp+50h] [rbp-18h]
__int64 v15; // [rsp+70h] [rbp+8h]
__int64 v16; // [rsp+78h] [rbp+10h]
__int64 v17; // [rsp+80h] [rbp+18h]
__int64 v18; // [rsp+88h] [rbp+20h]
v15 = a3;
v16 = a2;
v17 = a4;
v18 = a5;
_mm_store_si128((__m128i *)&v11, a6);
_mm_store_si128((__m128i *)&v12, a7);
_mm_store_si128((__m128i *)&v13, a8);
_mm_store_si128((__m128i *)&v14, a9);
v9 = _delayLoadHelper2((unsigned int *)&_DELAY_IMPORT_DESCRIPTOR_HTTPAPI_dll, (FARPROC *)a1);
return ((__int64 (__fastcall *)(__int64, __int64, __int64, __int64))v9)(v15, v16, v17, v18);
}
调用了_delayLoadHelper2这个函数
FARPROC __fastcall _delayLoadHelper2(unsigned int *a1, FARPROC *a2)
{
volatile signed __int64 *v2; // rbp
HMODULE v3; // rbx
FARPROC *v4; // r13
const CHAR *v5; // r12
int v6; // edi
__int64 v7; // rdx
const CHAR *v8; // rsi
HMODULE v9; // rax
signed __int64 v10; // rbp
DWORD v11; // eax
FARPROC v12; // rbx
DWORD v13; // eax
int v15; // [rsp+20h] [rbp-68h]
char Dst; // [rsp+28h] [rbp-60h]
const CHAR *v17; // [rsp+38h] [rbp-50h]
HMODULE v18; // [rsp+50h] [rbp-38h]
v2 = (volatile signed __int64 *)((char *)&_ImageBase + a1[2]);
v3 = (HMODULE)*v2;
v4 = a2;
v5 = (char *)&_ImageBase + a1[1];
v6 = 0;
v7 = a1[4] + 8i64 * (unsigned int)(((char *)a2 - a1[3] - (char *)&_ImageBase) >> 3);
if ( *(_QWORD *)((char *)&_ImageBase + v7) < 0i64 )
v8 = (const CHAR *)*(unsigned __int16 *)((char *)&_ImageBase + v7);
else
v8 = (char *)&word_7FF4E800002 + *(unsigned int *)((char *)&_ImageBase + v7);
if ( !v3 )
{
v9 = LoadLibraryExA(v5, 0i64, _ResolveDelayLoadedAPIFlags);
v3 = v9;
if ( v9 )
{
v10 = _InterlockedCompareExchange(v2, (signed __int64)v9, 0i64);
if ( v10 )
{
FreeLibrary(v9);
v3 = (HMODULE)v10;
}
else
{
memset(&Dst, 0, 0x40ui64);
v15 = 72;
v17 = v5;
v18 = v3;
if ( _pfnDliNotifyHook2 )
_pfnDliNotifyHook2(5i64, &v15);
}
}
else
{
v11 = GetLastError();
if ( v11 != 126 && v11 != 193 )
goto LABEL_21;
v3 = (HMODULE)_InterlockedCompareExchange(v2, -1i64, 0i64);
if ( !v3 )
{
LABEL_14:
v6 = 1;
LABEL_21:
v12 = (FARPROC)DelayLoadFailureHook(v5, v8);
goto LABEL_22;
}
}
}
if ( v3 == (HMODULE)-1i64 )
goto LABEL_14;
if ( !v3 )
goto LABEL_21;
v12 = GetProcAddress(v3, v8);
if ( v12 || (v13 = GetLastError(), v13 == 127) || v13 == 182 )
v6 = 1;
if ( !v12 )
goto LABEL_21;
LABEL_22:
if ( v6 )
*v4 = v12;
return v12;
}
可以看到 加载完dll后,取下函数的地址。然后,最后这句话修复了导入表为真正的函数地址
*v4 = v12;
v4来源是
v4 = a2;
通过代码回溯发现 a2 最开始的地址就是RVA_DelayIAT的第一项
000007FEEE475420 000007FEEE4203D2
在把IAT对应的地址,换成正确的地址。至此完整的延迟加载过程就结束了。 之后的调用会走正常的调用过程。
总结
延迟加载dll,会在当前的模块内部生成一些列的 桩函数 。初始时,这些桩函数就是RVA_DelayIAT的指向的地址。当第一次调用对应的函数时,桩函数会加载对应的模块,以及取到对应模块的地址,然后修复RVA_DelayIAT为真正的函数地址。
题外话
那如何hook延迟导入表中的函数呢?
首先把RVA_DelayIAT 中的项替换成自己的函数,在自己的函数内部调用延迟导入表的函数,因为调用过后,RVA_DelayIAT中的函数地址已经变成了真正的系统函数,这时再次修改RVA_DelayIAT中的项,则达到了hook的目的
第一次替换成自己的函数
ULONG_PTR uOrgFunc = *pHookFunc;
*pHookFunc = *pIatPtr;
*pIatPtr = uOrgFunc;
第二次替换
if (g_trueSysFunc)
{
uStatus = g_trueSysFunc(...);
}
if (g_DelayIatSysFuncAddr != g_UserHookFunc)
{
g_trueSysFunc = *g_DelayIatSysFuncAddr;
*g_DelayIatSysFuncAddr = g_UserHookFunc;
}