tgjarwl的博客

道可道,非常道;名可名,非常名

记录生活,记录更好的自己。


延迟导入表

生成延迟导入表

在vs项目属性 => 连接器 => 输入 => 延迟加载的dll avatar

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;
}