本文的目的就是想探究dll文件中的变量是如何导出。借此了解ntoskrnl.exe
的导出到底是怎么实现的。
在前面的《SSDT HOOK》代码段中有这么一句话:
extern "C" PSERVICE_DESCRIPTOR_TALBE KeServiceDescriptorTable;
当时是说:这个符号是从ntoskrnl.exe
中导出的。从当时测试的时候改变符号名发现执行错误就可以看出来,这个符号绝对是从ntoskrnl.exe
文件中查找出来的。今天使用Depend walker
查看了一下,有这样的结果。
按键F10(c/c++ 符号形式)
转换,发现都是同一个函数名,因此可以判定这个符号是用C导出的。
这是因为如果使用的是c++符号形式导出,那么由于多态性的原因,其导出格式会发生改变。
为此我又做了一次测试。新建dll文件生成cpp
格式。源码如下:
FINAL.H
// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the FINAL_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// FINAL_API functions as being imported from a DLL, wheras this DLL sees symbols
// defined with this macro as being exported.
#ifdef FINAL_EXPORTS
#define FINAL_API __declspec(dllexport)
#else
#define FINAL_API __declspec(dllimport)
#endif
// This class is exported from the FINAL.dll
class FINAL_API CFINAL {
public:
CFINAL(void);
// TODO: add your methods here.
};
extern FINAL_API int nFINAL;
extern FINAL_API int nTemp ; //add here,others are default input.
FINAL_API int fnFINAL(void);
--------------
// FINAL.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include "FINAL.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
// This is an example of an exported variable
FINAL_API int nFINAL=0;
FINAL_API int nTemp = 0x10; //add here.
// This is an example of an exported function.
FINAL_API int fnFINAL(void)
{
return 42;
}
// This is the constructor of a class that has been exported.
// see FINAL.h for the class definition
CFINAL::CFINAL()
{
return;
}
程序很简单,
这时候打开depends
查看一下导出情况。
下图是c++默认的导出符号表(最左侧的C++ 表示是通过C++方式导出)
F10切换后,去掉装饰,不知道这个算不算变为C的方式呢?后期注意这个问题。
虽然用depends
可以切换,但是我更想知道编译器做了什么,用PEView
可以查看(导出表name段在.rdata段)。
由于PEView
不能复制结果出来,在这里我用十六进制查看器(Winhex)查看了一下,结果如下。
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
0002CF80 41 45 40 58 5A 00 3F 3F 34 43 46 49 4E 41 4C 40 [email protected] ??4[email protected]
0002CF90 40 51 41 45 41 41 56 30 40 41 42 56 30 40 40 5A @[email protected]@@Z
0002CFA0 00 3F 66 6E 46 49 4E 41 4C 40 40 59 41 48 58 5A [email protected]@YAHXZ
0002CFB0 00 3F 6E 46 49 4E 41 4C 40 40 33 48 41 00 3F 6E [email protected]@3HA ?n
0002CFC0 54 65 6D 70 40 40 33 48 41 00 00 00 00 00 00 00 [email protected]@3HA
可以看到,编译链接后的文件中存放的变量符号为[email protected]@3HA
.
接着改变链接方式为Extern "C"
.
FINAL.H 其他不变
//extern FINAL_API int nTemp;
extern "C" FINAL_API int nTemp ;
生成的文件结果是:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
0002CF60 00 00 01 00 02 00 03 00 04 00 46 49 4E 41 4C 2E FINAL.
0002CF70 64 6C 6C 00 3F 3F 30 43 46 49 4E 41 4C 40 40 51 dll ??0[email protected]@Q
0002CF80 41 45 40 58 5A 00 3F 3F 34 43 46 49 4E 41 4C 40 [email protected] ??4[email protected]
0002CF90 40 51 41 45 41 41 56 30 40 41 42 56 30 40 40 5A @[email protected]@@Z
0002CFA0 00 3F 66 6E 46 49 4E 41 4C 40 40 59 41 48 58 5A [email protected]@YAHXZ
0002CFB0 00 3F 6E 46 49 4E 41 4C 40 40 33 48 41 00 6E 54 [email protected]@3HA nT
0002CFC0 65 6D 70 00 emp
可以看到,变量符号名并没有改变nTemp
.这就是C声明的意思,不改变原变量名。从上面的dump中可以看出来,函数名C++生成的也不是原有的函数名,那么在C下呢,我又试了一次:
//Final.h the last line.
//add here
extern "C" FINAL_API int sum(int a,int b);
-----
//FINAL.CPP末尾添加
FINAL_API int sum(int a,int b)
{
return a + b;
}
生成的dll文件用Depends
查看。
F10一下后
depends E 栏中 c 表示用C的方式生成,C++表示用C++的方式生成。
看到函数声明只剩下了函数名。
但是F10自由切换,还是不知道文件中到底是怎么样的,看一下。
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
0002DFA0 56 30 40 41 42 56 30 40 40 5A 00 3F 66 6E 46 49 [email protected]@@Z ?fnFI
0002DFB0 4E 41 4C 40 40 59 41 48 58 5A 00 3F 6E 46 49 4E [email protected]@YAHXZ ?nFIN
0002DFC0 41 4C 40 40 33 48 41 00 6E 54 65 6D 70 00 73 75 [email protected]@3HA nTemp su
0002DFD0 6D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 m
0002DFE0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
可以看到就是depends中的sum
(因为来回切换都没有变–)
来看下如何导入这些变量
#include "stdafx.h"
#include <stdio.h>
#pragma comment(lib,"FINAL.lib")
extern "C" int _declspec(dllimport) nTemp;
int main(int argc, char* argv[])
{
// printf("%d",nMydll);
printf("%d",nTemp);
return 0;
}
//output: 16Press any key to continue.
如果将extern "C" int _declspec(dllimport) nTemp;
中的C
去掉,会怎么样。
--------------------Configuration: mydll_test - Win32 Debug--------------------
Linking...
mydll_test.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) int nTemp" ([email protected]@3HA)
Debug/mydll_test.exe : fatal error LNK1120: 1 unresolved externals
执行 link.exe 时出
其实这个错误就是说找不到符号,上面实验做完应该知道我们文件是.cpp
,那么就会按照c++的方式去找:
1. 首先,编译器将nTemp
转化为C++下的符号[email protected]@3HA
。
2. 然后 ,通过PE文件结构找到符号[email protected]@3HA
,接着读出它的RVA。
问题是,我们的DLL文件根本就没有这个符号,用C的方式生成的符号是nTemp
。怎么找都找不到。
而最开始的例子就已经说明我要找的变量是用C方式导出的,让编译器将第一步用C的方式生成nTemp
,这次当然就可以找成功了。
最后来看下反汇编过程。
13: printf("%d",nTemp);
00401028 mov eax,[__imp__nTemp (0042a18c)]
0040102D mov ecx,dword ptr [eax]
0040102F push ecx
00401030 push offset string "%d" (0042201c)
00401035 call printf (00401060)
0040103A add esp,8
对于__imp__nTemp
我的解释是:首先这个是导入进来的,加前缀__imp__
,然后加入符号名nTemp
。注意导入表能得到的永远是地址,而不是值。也就是说导入nTemp
得到的是nTemp值的地址,要取值nTemp必须通过从地址中取出dword。否则汇编就变成了mov eax, __imp__nTemp
,或者说有&ntemp=__imp__nTemp=42a18c
.
同样的,用C++的方式形成的反汇编是:
13: printf("%d",nTemp);
00401028 mov eax,[[email protected]@3HA (0042a18c)]
0040102D mov ecx,dword ptr [eax]
0040102F push ecx
00401030 push offset string "%d" (0042201c)
00401035 call printf (00401060)
0040103A add esp,8
前缀为__imp_
少了一个下划线。
研究做完了,现在有几个问题待解决:
***
extern FINAL_API int nFINAL;
//extern FINAL_API int nTemp;
extern int FINAL_API nTemp ;
这两个声明都能通过,一个int在前,一个int在后,区别是什么?
***
最初的那个问题:
extern "C" PSERVICE_DESCRIPTOR_TALBE KeServiceDescriptorTable;
这句声明中没有用__declspec(dllimport)
.为什么还可以获得此值呢。
如果普通都可以的话,那么我测试程序改成这样,为什么就不能呢。是因为内核的原因么。
#pragma comment(lib,"FINAL.lib")
extern int nTemp; //no __declspec(dllimport) --编译失败
int main(int argc, char* argv[])
{
// printf("%d",nMydll);
printf("%d",nTemp);
return 0;
}
***
最后一点:
dll导出文件FINAL.H中的宏定义
#ifdef FINAL_EXPORTS
#define FINAL_API __declspec(dllexport)
#else
#define FINAL_API __declspec(dllimport)
#endif
在哪里测试是否已经define 了 FFINAL_EXPORTS
.绝对是宏定义了,以下为例:
#ifdef FINAL_EXPORTS
#define FINAL_API __declspec(dllex444444444port)
#else
#define FINAL_API __declspec(dllimport)
#endif
修改dllimport
不错,但是一旦修改dllexport
就出错,说明绝对是宏定义了。
那么到底是在哪里宏定义的呢,编译器自动搞的??????????