Advanced Debugging
About AdvDbg Consult Train Services Products Tools Community Contact  
欢迎光临 高端调试 登录 | 注册 | FAQ
 
  ACPI调试
Linux内核调试
Windows内核调试
 
  调试战役
调试原理
新工具观察
 
  Linux
Windows Vista
Windows
 
  Linux驱动
WDF
WDM
 
  PCI Express
PCI/PCI-X
USB
无线通信协议
 
  64位CPU
ARM
IA-32
  CPU Info Center
 
  ACPI标准
系统认证
Desktop
服务器
 
  Embedded Linux
嵌入式开发工具
VxWorks
WinCE
嵌入式Windows
 
  格蠹调试套件(GDK)
  格蠹学院
  小朱书店
  老雷的微博
  《软件调试》
  《格蠹汇编》
  《软件调试(第二版)》
沪ICP备11027180号-1

C/C++本地代码调试

帖子发起人: 手语   发起时间: 2008-12-26 17:05 下午   回复: 6

Print Search
帖子排序:    
   2008-12-26, 17:05 下午
neilhsu 离线,最后访问时间: 2009/12/23 17:02:23 手语

发帖数前10位
男
注册: 2008-06-06
发 贴: 73
LoadLibrary() 加载DLL和EXE的不同行为
Reply Quote

上次开了个贴“请教一个.Net程序中PInvoke的问题”问了一个我遇到的奇怪现象。
问题描述有点乱,所以重开一贴讲一下我的跟踪结果。

把问题简化一下:
我发现LoadLibrary()对待DLL和EXE有不同的行为:加载EXE时不处理Base relocation。
如果EXE输出的函数再调用其他函数,一定会出问题。因为CALL指令里的地址没有被重定位。

VC默认是不为EXE产生重定位表的,因为一般EXE都能加载到默认基址。
强制产生重定位表的方法是在连接选项里添加“/FIXED:NO”。
但即使建立了重定位表,LoadLibrary()仍然不为EXE处理重定位。所有CALL/JMP指令里的地址还是保持原状。

跟踪了一下LoadLibraryW()。
环境:XP SP2, VS2005

加载DLL时:(为了看起来方便,去掉了一些无关行)
0:000> wt -l1
Tracing ntdll!LdrpMapDll to return address 7c916071
    3     0 [  0] ntdll!LdrpMapDll
   19     0 [  1]   ntdll!_SEH_prolog
   10     0 [  1]   ntdll!LdrpEnsureLoaderLockIsHeld
  120     0 [  1]   ntdll!LdrpCheckForKnownDll
  150     0 [  1]   ntdll!LdrpResolveDllName
   30     0 [  1]   ntdll!RtlDosPathNameToNtPathName_U
   78     0 [  1]   ntdll!LdrpCreateDllSection
   27     0 [  1]   ntdll!RtlFreeHeap
    1     0 [  1]   ntdll!ZwMapViewOfSection
    3     0 [  1]   ntdll!NtMapViewOfSection
    5     0 [  1]   ntdll!RtlImageNtHeader
   22     0 [  1]   ntdll!RtlpImageNtHeader
   23     0 [  1]   ntdll!RtlImageDirectoryEntryToData
   31     0 [  1]   ntdll!LdrpAllocateDataTableEntry
   13     0 [  1]   ntdll!LdrpFetchAddressOfEntryPoint
   41     0 [  1]   ntdll!LdrpInsertMemoryTableEntry
   20     0 [  1]   ntdll!LdrpSendDllLoadedNotifications
   23     0 [  1]   ntdll!RtlImageDirectoryEntryToData
   18     0 [  1]   ntdll!RtlEqualUnicodeString
   18     0 [  1]   ntdll!RtlEqualUnicodeString

  167     0 [  1]   ntdll!LdrpSetProtection
   13     0 [  1]   ntdll!LdrRelocateImage

    1     0 [  1]   ntdll!ZwMapViewOfSection
    3     0 [  1]   ntdll!NtMapViewOfSection
  199     0 [  1]   ntdll!LdrpSetProtection
   23     0 [  1]   ntdll!LdrpValidateImageForMp
    1     0 [  1]   ntdll!NtClose
    3     0 [  1]   ntdll!ZwClose
    9     0 [  1]   ntdll!_SEH_epilog
  367  1071 [  0] ntdll!LdrpMapDll

加载EXE时:
0:000> wt -l1
Tracing ntdll!LdrpMapDll to return address 7c916071
    3     0 [  0] ntdll!LdrpMapDll
   19     0 [  1]   ntdll!_SEH_prolog
   10     0 [  1]   ntdll!LdrpEnsureLoaderLockIsHeld
  120     0 [  1]   ntdll!LdrpCheckForKnownDll
  150     0 [  1]   ntdll!LdrpResolveDllName
   30     0 [  1]   ntdll!RtlDosPathNameToNtPathName_U
   78     0 [  1]   ntdll!LdrpCreateDllSection
   27     0 [  1]   ntdll!RtlFreeHeap
    1     0 [  1]   ntdll!ZwMapViewOfSection
    3     0 [  1]   ntdll!NtMapViewOfSection
    5     0 [  1]   ntdll!RtlImageNtHeader
   22     0 [  1]   ntdll!RtlpImageNtHeader
   23     0 [  1]   ntdll!RtlImageDirectoryEntryToData
   31     0 [  1]   ntdll!LdrpAllocateDataTableEntry
   13     0 [  1]   ntdll!LdrpFetchAddressOfEntryPoint
   41     0 [  1]   ntdll!LdrpInsertMemoryTableEntry
   20     0 [  1]   ntdll!LdrpSendDllLoadedNotifications
    1     0 [  1]   ntdll!ZwMapViewOfSection
    3     0 [  1]   ntdll!NtMapViewOfSection
    1     0 [  1]   ntdll!NtClose
    3     0 [  1]   ntdll!ZwClose
    9     0 [  1]   ntdll!_SEH_epilog
  310   610 [  0] ntdll!LdrpMapDll

上面有颜色的行就是不同的部分。可以明显看到对EXE文件,LoadLibrary()没有进行基址重定位操作。
不明白为什么会有这样的差异。肯定是我少做了什么步骤。
到底还需要做什么才能正确调用EXE的导出函数呢?

请赐教。


鸿鹄安知燕雀之志
IP 地址: 已记录   报告
   2008-12-26, 18:17 下午
neilhsu 离线,最后访问时间: 2009/12/23 17:02:23 手语

发帖数前10位
男
注册: 2008-06-06
发 贴: 73
Re: LoadLibrary() 加载DLL和EXE的不同行为
Reply Quote
继续跟踪了一下,发现EXE/DLL的差别出在这里:

0:000> kb
ChildEBP RetAddr Args to Child
0012f984 7c916071 00153598 0012fa10 0012ff38 ntdll!LdrpMapDll+0x551
0012fc44 7c9162da 00000000 00153598 0012ff38 ntdll!LdrpLoadDll+0x1e9
0012feec 7c801bb9 00153598 0012ff38 0012ff18 ntdll!LdrLoadDll+0x230
0012ff54 7c80ae5c 004020f4 00000000 00000000 kernel32!LoadLibraryExW+0x18e
0012ff68 004017f8 004020f4 00000000 0040302c kernel32!LoadLibraryW+0x11
......
0012fff0 00000000 004012b2 00000000 78746341 kernel32!BaseProcessStart+0x23

加载EXE时:
7c91c499 e88f020000 call ntdll!LdrpSendDllLoadedNotifications (7c91c72d)
7c91c49e 81ff0e000040 cmp edi,4000000Eh
7c91c4a4 0f84644b0200 je ntdll!LdrpMapDll+0x4b6 (7c94100e)
7c91c4aa 8b45a0 mov eax,dword ptr [ebp-60h] ss:0023:0012f924=003b00e8 //IMAGE_NT_HEADERS
7c91c4ad f6401720 test byte ptr [eax+17h],20h ds:0023:003b00ff=01 //HIBYTE(Characteristics)
7c91c4b1 7404 je ntdll!LdrpMapDll+0x55e (7c91c4b7)

加载DLL时:
7c91c499 e88f020000 call ntdll!LdrpSendDllLoadedNotifications (7c91c72d)
7c91c49e 81ff0e000040 cmp edi,4000000Eh
7c91c4a4 0f84644b0200 je ntdll!LdrpMapDll+0x4b6 (7c94100e)
7c91c4aa 8b45a0 mov eax,dword ptr [ebp-60h] ss:0023:0012f8ac=003b00f0 //IMAGE_NT_HEADERS
7c91c4ad f6401720 test byte ptr [eax+17h],20h ds:0023:003b0107=21 //HIBYTE(Characteristics)
7c91c4b1 7404 je ntdll!LdrpMapDll+0x55e (7c91c4b7)

区别行用注释标识。
[ebp-60h]是IMAGE_NT_HEADERS的指针,byte ptr [eax+17h]是PIMAGE_NT_HEADERS->FileHeader.Characteristics(WORD)的高字节。
实际上,DLL的Characteristics是0x2102,EXE的Characteristics是0x0102。

From WINNT.h:
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved externel references).
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
#define IMAGE_FILE_DLL 0x2000 // File is a DLL.

差别就是EXE没有IMAGE_FILE_DLL标志。后面两条语句就是根据这个标志判断走向。
所以,看起来LoadLibrary()确实对EXE/DLL进行了区别处理。只有对DLL才进行重定位操作。

那有什么方法解决这个问题呢?让从EXE导出的函数可以正常调用其他函数。
鸿鹄安知燕雀之志
IP 地址: 已记录   报告
   2008-12-27, 16:02 下午
neilhsu 离线,最后访问时间: 2009/12/23 17:02:23 手语

发帖数前10位
男
注册: 2008-06-06
发 贴: 73
Re: LoadLibrary() 加载DLL和EXE的不同行为
Reply Quote
LoadLibrary()加载EXE时不但不处理BaseRelocation,连EXE依赖的DLL都不会加载。
看来只有自己动手做了。
还有一个猜想,如果为EXE加上IMAGE_FILE_DLL标志会怎样。下周一试一下。
鸿鹄安知燕雀之志
IP 地址: 已记录   报告
   2008-12-27, 20:54 下午
Raymond 离线,最后访问时间: 2020/7/3 3:40:25 格蠹老雷

发帖数前10位
注册: 2005-12-19
发 贴: 1,303
Re: LoadLibrary() 加载DLL和EXE的不同行为
Reply Quote
分析的很清楚,期待后面的结果。关于加载映像文件这块很值得挖掘一下。
IP 地址: 已记录   报告
   2008-12-28, 13:51 下午
MJ0011 离线,最后访问时间: 2009/12/24 22:33:41 MJ0011

发帖数前10位
注册: 2008-04-24
发 贴: 112
Re: LoadLibrary() 加载DLL和EXE的不同行为
Reply Quote
参考360大赛第4题。。
IP 地址: 已记录   报告
   2008-12-30, 01:28 上午
neilhsu 离线,最后访问时间: 2009/12/23 17:02:23 手语

发帖数前10位
男
注册: 2008-06-06
发 贴: 73
Re: LoadLibrary() 加载DLL和EXE的不同行为
Reply Quote
今天试了一下,为.exe文件头添加IMAGE_FILE_DLL标志,并相应修改了CheckSum。
失败,Windows拒绝加载。估计就不可能成功。

没办法只能自己动手了。
做个函数用来做加载.exe文件的善后工作:
1、遍历并加载.exe所依赖的所有外部模块,建立IAT。
2、遍历重定位数据,自己做BaseRelocation处理。

函数原型和LoadLibraryW()相似:
HMODULE
WINAPI
LoadExecutableW( LPCWSTR pwszExecutableFile );

用法也一样:
// 假设XXXX.exe输出ExeExportedFunction()并且用/FIXED:NO选项连接以建立重定位表,这函数内部又调用其他DLL的函数。
HMODULE hExe = LoadExecutableW( L"XXXX.exe" );
if( NULL != hExe )
{
FUNCTIONPTR pFunc = (FUNCTIONPTR)GetProcAddress( hExe, "ExeExportedFunction" );
pFunc();
}

代码打包放在
http://www.live-share.com/files/373906/LoadExecutable.zip.html
很小,就俩文件.h/.cpp。

简单测了几个CASE都能成功,比如:
A.exe用LoadExecutableW()加载B.exe,并调用B输出的C(),C()调用D.dll输出的函数E()。
如果B.exe隐含连接其他带重定位表并输出函数.exe文件,一样可以递归处理。
经过调试一切正常,证明是可行的。

为了让每个内部函数都可以单独复用,代码写得比较啰嗦。
目前的实现不完善,只用来证明调试结论。

听老雷话,要好好跟踪学习一下映像加载。^_^
鸿鹄安知燕雀之志
IP 地址: 已记录   报告
   2008-12-30, 01:35 上午
neilhsu 离线,最后访问时间: 2009/12/23 17:02:23 手语

发帖数前10位
男
注册: 2008-06-06
发 贴: 73
Re: LoadLibrary() 加载DLL和EXE的不同行为
Reply Quote
MJ老大,第四题我想到一点解法,应该是复用.exe中的main(),让他分别扮演Dll中的DllMain()和Driver中的DriverEntry()的角色。其间要做我上面做的那些处理。
细节可能会遇到一些难题,等我随后试试。

有一事不明。
我看到LoadDll.dll的DllMain()是这么调用自己的.exe的入口函数:
BOOLEAN
WINAPI
DllMain( HINSTANCE hDllHandle, DWORD nReason, LPVOID Reserved )
{
GetDllInfo = GetProcAddress( GetModuleHandleA( NULL ), 100 );
if( GetDllInfo )
{
GetDllInfo( &hMod, &DllEP_RVA );
}

if( DLL_PROCESS_DETACH != nReason )
{
......
if( nReason <= DLL_THREAD_DETACH )
{
if( !DllEP_RVA )
return bRet;
DllEP = (DWORD)hMod + DllEP_RVA;

g_dwESP = ESP;
bRet = DllEP( hMod, nReason, Reserved );
ESP = g_dwESP;
return bRet;
}
}

bRet = TRUE;
return bRet;
}


从LoadDll.exe调用LoadDll.dll,又从DllMain()调用A.exe的EntryPoint()。
绕这么大一圈似乎有什么深层含义。
好像不只是为了复用.exe中的main()。
我比较菜,容我再想想。
鸿鹄安知燕雀之志
IP 地址: 已记录   报告
高端调试 » 软件调试 » C/C++本地代码调试 » LoadLibrary() 加载DLL和EXE的不同行为

 
Legal Notice Privacy Statement Corporate Governance Corporate Governance
(C)2004-2020 ADVDBG.ORG All Rights Reserved.