码迷,mamicode.com
首页 > 其他好文 > 详细

从DLL中导出变量学习

时间:2015-05-19 10:48:33      阅读:254      评论:0      收藏:0      [点我收藏+]

标签:dll

本文的目的就是想探究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   AE@XZ ??4CFINAL@
0002CF90   40 51 41 45 41 41 56 30  40 41 42 56 30 40 40 5A   @QAEAAV0@ABV0@@Z
0002CFA0   00 3F 66 6E 46 49 4E 41  4C 40 40 59 41 48 58 5A    ?fnFINAL@@YAHXZ
0002CFB0   00 3F 6E 46 49 4E 41 4C  40 40 33 48 41 00 3F 6E    ?nFINAL@@3HA ?n
0002CFC0   54 65 6D 70 40 40 33 48  41 00 00 00 00 00 00 00   Temp@@3HA       

可以看到,编译链接后的文件中存放的变量符号为?nTemp@@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 ??0CFINAL@@Q
0002CF80   41 45 40 58 5A 00 3F 3F  34 43 46 49 4E 41 4C 40   AE@XZ ??4CFINAL@
0002CF90   40 51 41 45 41 41 56 30  40 41 42 56 30 40 40 5A   @QAEAAV0@ABV0@@Z
0002CFA0   00 3F 66 6E 46 49 4E 41  4C 40 40 59 41 48 58 5A    ?fnFINAL@@YAHXZ
0002CFB0   00 3F 6E 46 49 4E 41 4C  40 40 33 48 41 00 6E 54    ?nFINAL@@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   V0@ABV0@@Z ?fnFI
0002DFB0   4E 41 4C 40 40 59 41 48  58 5A 00 3F 6E 46 49 4E   NAL@@YAHXZ ?nFIN
0002DFC0   41 4C 40 40 33 48 41 00  6E 54 65 6D 70 00 73 75   AL@@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" (__imp_?nTemp@@3HA)
Debug/mydll_test.exe : fatal error LNK1120: 1 unresolved externals
执行 link.exe 时出

其实这个错误就是说找不到符号,上面实验做完应该知道我们文件是.cpp ,那么就会按照c++的方式去找:
1. 首先,编译器将nTemp 转化为C++下的符号?nTemp@@3HA
2. 然后 ,通过PE文件结构找到符号?nTemp@@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,[__imp_?nTemp@@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 就出错,说明绝对是宏定义了。
那么到底是在哪里宏定义的呢,编译器自动搞的??????????

从DLL中导出变量学习

标签:dll

原文地址:http://blog.csdn.net/bugmeout/article/details/45828839

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!