|
|
|
|
|
|
|
C/C++本地代码调试
帖子发起人: 手语 发起时间: 2008-12-26 17:05 下午 回复: 6
|
帖子排序:
|
|
|
|
2008-12-26, 17:05 下午
|
手语
注册: 2008-06-06
发 贴: 73
|
LoadLibrary() 加载DLL和EXE的不同行为
|
|
|
|
上次开了个贴“请教一个.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 下午
|
手语
注册: 2008-06-06
发 贴: 73
|
Re: LoadLibrary() 加载DLL和EXE的不同行为
|
|
|
|
继续跟踪了一下,发现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 下午
|
手语
注册: 2008-06-06
发 贴: 73
|
Re: LoadLibrary() 加载DLL和EXE的不同行为
|
|
|
|
LoadLibrary()加载EXE时不但不处理BaseRelocation,连EXE依赖的DLL都不会加载。
看来只有自己动手做了。
还有一个猜想,如果为EXE加上IMAGE_FILE_DLL标志会怎样。下周一试一下。
鸿鹄安知燕雀之志
|
|
|
IP 地址: 已记录
|
报告
|
|
|
|
2008-12-27, 20:54 下午
|
格蠹老雷
注册: 2005-12-19
发 贴: 1,303
|
Re: LoadLibrary() 加载DLL和EXE的不同行为
|
|
|
|
分析的很清楚,期待后面的结果。关于加载映像文件这块很值得挖掘一下。
|
|
|
IP 地址: 已记录
|
报告
|
|
|
|
2008-12-28, 13:51 下午
|
MJ0011
注册: 2008-04-24
发 贴: 112
|
Re: LoadLibrary() 加载DLL和EXE的不同行为
|
|
|
|
|
|
IP 地址: 已记录
|
报告
|
|
|
|
2008-12-30, 01:28 上午
|
手语
注册: 2008-06-06
发 贴: 73
|
Re: LoadLibrary() 加载DLL和EXE的不同行为
|
|
|
|
今天试了一下,为.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 上午
|
手语
注册: 2008-06-06
发 贴: 73
|
Re: LoadLibrary() 加载DLL和EXE的不同行为
|
|
|
|
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++本地代码调试 » Re: LoadLibrary() 加载DLL和EXE的不同行为
|
|
|
|
|
|