精华内容
下载资源
问答
  • 这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的...,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入...

    欢迎使用Markdown编辑器

    你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

    新的改变

    我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

    1. 全新的界面设计 ,将会带来全新的写作体验;
    2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
    3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
    4. 全新的 KaTeX数学公式 语法;
    5. 增加了支持甘特图的mermaid语法1 功能;
    6. 增加了 多屏幕编辑 Markdown文章功能;
    7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
    8. 增加了 检查列表 功能。

    功能快捷键

    撤销:Ctrl/Command + Z
    重做:Ctrl/Command + Y
    加粗:Ctrl/Command + B
    斜体:Ctrl/Command + I
    标题:Ctrl/Command + Shift + H
    无序列表:Ctrl/Command + Shift + U
    有序列表:Ctrl/Command + Shift + O
    检查列表:Ctrl/Command + Shift + C
    插入代码:Ctrl/Command + Shift + K
    插入链接:Ctrl/Command + Shift + L
    插入图片:Ctrl/Command + Shift + G
    查找:Ctrl/Command + F
    替换:Ctrl/Command + G

    合理的创建标题,有助于目录的生成

    直接输入1次#,并按下space后,将生成1级标题。
    输入2次#,并按下space后,将生成2级标题。
    以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

    如何改变文本的样式

    强调文本 强调文本

    加粗文本 加粗文本

    标记文本

    删除文本

    引用文本

    H2O is是液体。

    210 运算结果是 1024.

    插入链接与图片

    链接: link.

    图片: Alt

    带尺寸的图片: Alt

    居中的图片: Alt

    居中并且带尺寸的图片: Alt

    当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

    如何插入一段漂亮的代码片

    博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

    // An highlighted block
    var foo = 'bar';
    

    生成一个适合你的列表

    • 项目
      • 项目
        • 项目
    1. 项目1
    2. 项目2
    3. 项目3
    • 计划任务
    • 完成任务

    创建一个表格

    一个简单的表格是这么创建的:

    项目Value
    电脑$1600
    手机$12
    导管$1

    设定内容居中、居左、居右

    使用:---------:居中
    使用:----------居左
    使用----------:居右

    第一列第二列第三列
    第一列文本居中第二列文本居右第三列文本居左

    SmartyPants

    SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

    TYPEASCIIHTML
    Single backticks'Isn't this fun?'‘Isn’t this fun?’
    Quotes"Isn't this fun?"“Isn’t this fun?”
    Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

    创建一个自定义列表

    Markdown
    Text-to- HTML conversion tool
    Authors
    John
    Luke

    如何创建一个注脚

    一个具有注脚的文本。2

    注释也是必不可少的

    Markdown将文本转换为 HTML

    KaTeX数学公式

    您可以使用渲染LaTeX数学表达式 KaTeX:

    Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

    Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

    你可以找到更多关于的信息 LaTeX 数学表达式here.

    新的甘特图功能,丰富你的文章

    Mon 06 Mon 13 Mon 20 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
    • 关于 甘特图 语法,参考 这儿,

    UML 图表

    可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

    张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

    这将产生一个流程图。:

    链接
    长方形
    圆角长方形
    菱形
    • 关于 Mermaid 语法,参考 这儿,

    FLowchart流程图

    我们依旧会支持flowchart的流程图:

    Created with Raphaël 2.2.0 开始 我的操作 确认? 结束 yes no
    • 关于 Flowchart流程图 语法,参考 这儿.

    导出与导入

    导出

    如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

    导入

    如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
    继续你的创作。


    1. mermaid语法说明 ↩︎

    2. 注脚的解释 ↩︎

    展开全文
  • 读取PE文件导入表

    千次阅读 2015-12-25 09:42:28
    Image: PE格式镜像文件,这通常就是我们的exe,dll文件。    下面我们定义一些地址相关的概念,因为PE文件位于磁盘上,同时文件又可以被映射到虚拟内存中,在运行PE文件时它也被系统的Loader加载到内存中。...

     Image: PE格式镜像文件,这通常就是我们的exe,dll文件。

     

        下面我们定义一些地址相关的概念,因为PE文件位于磁盘上,同时文件又可以被映射到虚拟内存中,在运行PE文件时它也被系统的Loader加载到内存中。所以这里就有了三个空间,如果我们不做一个清楚的说明,在后面我们很容易混淆。

     

        (1)磁盘空间:这里我们使用的地址叫做文件地址(距离文件头部的偏移)。在PE头的相关属性名称中,文件中的数据称为原始数据 (rawData),文件中的数据使用的对齐称为 FileAlignment。

     

        (2)虚拟内存空间:在这里的地址称为虚拟地址(Virtual Address)。同时PE文件的数据装载/映射到内存中后又分以下两种情况:

     

          (a)PE文件的内存视图,即PE文件被映射到内存(MapViewOfFile):

          BaseAddress:内存映射文件的基础地址,从这里看过去,就和从编辑器打开看到的文件内容完全一致。

          内存映射通常是处理大文件的一种有效方式。映射后我们在内存中看到的内容就是磁盘文件的一个视图。

          因此我们吧PE文件映射到内存以后,通过某个数据的RVA,调用 ImageRvaToVa 可得到某个数据的VA,再减去映射文件的起始地址,就是文件地址。

     

          (b) 进程空间,即在被调度之前,被loader装载到内存的时刻(例如双击执行一个exe)。

          这里和前者的视图方式不同,属于一种地址映射关系,文件中的节内容根据NT文件头的信息被加载到进程空间的相应位置。

          ImageBase:映射到进程空间的基础地址。

          RVA:相对ImageOfBase的偏移。它加上ImageBase就是进程空间的VA。

          在PE文件中的DataEntry,Section表中的VirutalAddress基本都属于RVA。

     

        下面介绍以下这个函数:ImageRvaToVa:(注意这个函数要求XP和win2000系统以上,在VC6自带的SDK中没有。。。)

        PVOID ImageRvaToVa(
            PIMAGE_NT_HEADERS NtHeaders,
            PVOID Base,
            ULONG Rva,
            PIMAGE_SECTION_HEADER* LastRvaSection
          );

     

        这个函数在(2).(a)即内存映射文件后使用,它把RVA根据NT头的信息,换算成内存映射文件中的实际VA。看起来不是在进程空间使用的,因为在进程空间中,ImageBase + Rva 就是VA了,装载后NT头等信息也不再重要了。最后一个参数是可选的,或许是因为如果调用方主动提供,此 API 可提高一定效率(可以直接在节中的地址信息去判断)。第二个参数也可以提供一个假的地址给这个函数,这个函数也能计算。即这个函数不去校验Base是否是一个有效地址,因此实际上我们可以读取出NtHeader的信息后,用一个假地址传给这个函数,再把结果减去这个假地址,即可换算出文件地址。

     

        好了,下面介绍下导入表的定位,这方面的资料和文章在看雪论坛的文集里面有很多。我再这里只做一个比较简洁的介绍。导入表本质上就是位于某个节中的一些数据,这些数据主要是一些C字符串(以0字符为结尾的dll和函数名称)以及一些指针(RVA地址),所以我们主要是需要了解如何定位到导入表,从而打印出导入表的信息。

        首先导入表的RVA地址,就在optional Header的DataDirectory的第二个元素中。通过它我们定位到导入表。

        导入表类似一个二级索引。一级是一个模块目录(IMAGE_IMPORT_DESCRIPTOR数组,这里把目录理解为一个以全0字节为结束的数组),它的每个元素代表了一个DLL。二级是导入地址表IAT(即 IMAGE_THUNK_DATA 数组,一个指针数组),每个元素指向一个 IMAGE_IMPORT_BY_NAME结构(该结构含有一个函数序号和一个函数名称字符串)。

        总结一下,我们的定位过程:

        (1)通过 NtHeaders.OptionalHeader.DataDirectory[1].VirtualBase --> 定位到导入表(IID Table)。

        (2)遍历每个 IID,直到遇到全0为止。

            通过 IID.Name -> 定位到 DLL 名字。

            通过 IID.OriginalFirstThunk 或者 FirstThunk -> 定位到IAT ( image_thunk_data32[] );

              遍历指针目录,知道遇到NULL为止。

                通过 thunk_data.AddressOfData -> 定位到一个 IMAGE_IMPORT_BY_NAME 的地址,再根据它寻址到真正的函数序号和函数名称。

     

        为什么存在二级指针呢,这是很容易解释的。所以我们需要一个DLL目录,由于DLL名称的长度和函数个数不固定,所以向下扩展了一级,而每个函数的函数名称又是不固定的,所以又要向下扩展一级,这样要找到真正的函数名称必须经过这样两级定向。

     

        因此这个二级索引的导入表就是这样的定位方式(每个数组都是一个高地址方向半开口的样子, C字符串也是这样的字符数组),如下图所示(注意每个数组的元素的size是固定的,但由于数组是半开口,所以数组本身属于size不固定):

          

        

     

        特点就是,每次遇到长度无法预测的成员,就用指针把它从元素中扩展出去(用一个指针指向它),这样我们就保证每个数组的元素都是固定的size,这样它才能成为线性表结构(满足用指针的加减或者[]操作符进行元素读取)。例如DLL的名称是可变长度的,因此它被扔到元素定义的外面去,在元素中保留为一个指针。每个DLL的函数目录也是可变长度的(函数个数是不确定的),因此它在元素中也是一个指针。而函数目录中函数信息又被扔出去,用指针指向它。

     

        下面看一下相关代码,很短,通过上面的讲解,下面的代码就很明了,很简单了:

     

     

    复制代码
    //  ImageRvaToVa.cpp : Defines the entry point for the console application.
    //

    #include 
    " stdafx.h "
    #include 
    < stdio.h >
    #include 
    < windows.h >
    #include 
    < Dbghelp.h >   // ImageRvaToVa

    int  main( int  argc,  char *  argv[])
    {
        
    int  i, j;
        HANDLE hFile 
    =  CreateFile(
            
    " E:\\RfCard.exe " // PE文件名
            GENERIC_READ, 
            FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);

        
    if (hFile  ==  INVALID_HANDLE_VALUE)
        {
            printf(
    " Create File Failed.\n " );
            
    return   0 ;
        }

        HANDLE hFileMapping 
    =  CreateFileMapping(hFile, NULL, PAGE_READONLY,     0 0 , NULL);

        
    if  (hFileMapping  ==  NULL  ||  hFileMapping  ==  INVALID_HANDLE_VALUE) 
        { 
            printf(
    " Could not create file mapping object (%d).\n " , GetLastError());
            
    return   0 ;
        }

        LPBYTE lpBaseAddress 
    =  (LPBYTE)MapViewOfFile(hFileMapping,    //  handle to map object
            FILE_MAP_READ,  0 0 0 );
     
        
    if  (lpBaseAddress  ==  NULL) 
        { 
            printf(
    " Could not map view of file (%d).\n " , GetLastError()); 
            
    return   0 ;
        }

        PIMAGE_DOS_HEADER pDosHeader 
    =  (PIMAGE_DOS_HEADER)lpBaseAddress;
        PIMAGE_NT_HEADERS pNtHeaders 
    =  (PIMAGE_NT_HEADERS)(lpBaseAddress  +  pDosHeader -> e_lfanew);
        
        
    // 导入表的rva:0x2a000;
        DWORD Rva_import_table  =  pNtHeaders -> OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

        
    if (Rva_import_table  ==   0 )
        {
            printf(
    " no import table! " );
            
    goto  UNMAP_AND_EXIT;
        }

        
    // 这个虽然是内存地址,但是减去文件开头的地址,就是文件地址了
        
    // 这个地址可以直接从里面读取你想要的东西了
        PIMAGE_IMPORT_DESCRIPTOR pImportTable  =  (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(
            pNtHeaders, 
            lpBaseAddress, 
            Rva_import_table,
            NULL
            );

        
    // 减去内存映射的首地址,就是文件地址了。。(很简单吧)
        printf( " FileAddress Of ImportTable: %p\n " , ((DWORD)pImportTable  -  (DWORD)lpBaseAddress));

        
    // 现在来到了导入表的面前:IMAGE_IMPORT_DESCRIPTOR 数组(以0元素为终止)
        
    // 定义表示数组结尾的null元素!
        IMAGE_IMPORT_DESCRIPTOR null_iid;
        IMAGE_THUNK_DATA null_thunk;
        memset(
    & null_iid,  0 sizeof (null_iid));
        memset(
    & null_thunk,  0 sizeof (null_thunk));

        
    // 每个元素代表了一个引入的DLL。
         for (i = 0 ; memcmp(pImportTable  +  i,  & null_iid,  sizeof (null_iid)) != 0 ; i ++ )
        {
            
    // LPCSTR: 就是 const char*
            LPCSTR szDllName  =  (LPCSTR)ImageRvaToVa(
                pNtHeaders, lpBaseAddress, 
                pImportTable[i].Name, 
    // DLL名称的RVA
                NULL);

            
    // 拿到了DLL的名字
            printf( " -----------------------------------------\n " );
            printf(
    " [%d]: %s\n " , i, szDllName);
            printf(
    " -----------------------------------------\n " );

            
    // 现在去看看从该DLL中引入了哪些函数
            
    // 我们来到该DLL的 IMAGE_TRUNK_DATA 数组(IAT:导入地址表)前面
            PIMAGE_THUNK_DATA32 pThunk  =  (PIMAGE_THUNK_DATA32)ImageRvaToVa(
                pNtHeaders, lpBaseAddress,
                pImportTable[i].OriginalFirstThunk//【注意】这里使用的是OriginalFirstThunk
                NULL);

            
    for (j = 0 ; memcmp(pThunk + j,  & null_thunk,  sizeof (null_thunk)) != 0 ; j ++ )
            {
                
    // 这里通过RVA的最高位判断函数的导入方式,
                
    // 如果最高位为1,按序号导入,否则按名称导入
                 if (pThunk[j].u1.AddressOfData  &   IMAGE_ORDINAL_FLAG32 )
                {
                    printf(
    " \t [%d] \t %ld \t 按序号导入\n " , j, pThunk[j].u1.AddressOfData  &   0xffff );
                }
                
    else
                {
                    
    // 按名称导入,我们再次定向到函数序号和名称
                    
    // 注意其地址不能直接用,因为仍然是RVA!
                    PIMAGE_IMPORT_BY_NAME pFuncName  =  (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(
                        pNtHeaders,    lpBaseAddress,
                        pThunk[j].u1.AddressOfData,
                        NULL);
                    
                    printf(
    " \t [%d] \t %ld \t %s\n " , j, pFuncName -> Hint, pFuncName -> Name);
                }
            }
        }


    UNMAP_AND_EXIT:

        
    // 关闭文件,句柄。。
        UnmapViewOfFile(lpBaseAddress);
        CloseHandle(hFileMapping);
        CloseHandle(hFile);
        getchar();
        
    return   0 ;
    }
    复制代码

     

         【注意】在上面的代码中我标示为高亮红色字体的部分,我使用的是 OriginalFirstThunk ,对于没有事先绑定的PE文件来说,OriginalFirstThunk 和 FirstThunk 是并行的内容相同的数组。但是对于已经经过绑定的PE文件来说, FirstThunk 数组中的元素会被设置成真正的函数地址(VA)!因此如果这时用 FirstThunk 数组尝试获取函数名称是得不到的。所以上面的代码应该使用 OriginalFirstThunk ,这样无论对于绑定还是未绑定的PE文件,都能够定向到相应的函数名称。

     

        【注意】:

        (1)请在项目属性中的链接选项中,添加 Dbghelp.lib;

        (2)上面对PE文件的名称采用了硬编码,如果为了灵活期间,可以从命令行获取PE文件的路径。

     

        下面是我用某个PE文件所产生的输出:

     

    复制代码
    FileAddress Of ImportTable:  00028000
    -----------------------------------------
    [
    0 ]: USER32.dll
    -----------------------------------------
             [
    0 ]      198      EndDialog
             [
    1 ]      158      DialogBoxParamA
    -----------------------------------------
    [
    1 ]: KERNEL32.dll
    -----------------------------------------
             [
    0 ]      265      GetEnvironmentVariableA
             [
    1 ]      27       CloseHandle
             [
    2 ]      170      FlushFileBuffers
             [
    3 ]      294      GetModuleHandleA
             [
    4 ]      336      GetStartupInfoA
             [
    5 ]      202      GetCommandLineA
             [
    6 ]      372      GetVersion
             [
    7 ]      125      ExitProcess
             [
    8 ]      81       DebugBreak
             [
    9 ]      338      GetStdHandle
             [
    10 ]     735      WriteFile
             [
    11 ]     429      InterlockedDecrement
             [
    12 ]     501      OutputDebugStringA
             [
    13 ]     318      GetProcAddress
             [
    14 ]     450      LoadLibraryA
             [
    15 ]     432      InterlockedIncrement
             [
    16 ]     292      GetModuleFileNameA
             [
    17 ]     670      TerminateProcess
             [
    18 ]     247      GetCurrentProcess
             [
    19 ]     685      UnhandledExceptionFilter
             [
    20 ]     178      FreeEnvironmentStringsA
             [
    21 ]     179      FreeEnvironmentStringsW
             [
    22 ]     722      WideCharToMultiByte
             [
    23 ]     262      GetEnvironmentStrings
             [
    24 ]     264      GetEnvironmentStringsW
             [
    25 ]     621      SetHandleCount
             [
    26 ]     277      GetFileType
             [
    27 ]     373      GetVersionExA
             [
    28 ]     413      HeapDestroy
             [
    29 ]     411      HeapCreate
             [
    30 ]     415      HeapFree
             [
    31 ]     703      VirtualFree
             [
    32 ]     559      RtlUnwind
             [
    33 ]     282      GetLastError
             [
    34 ]     577      SetConsoleCtrlHandler
             [
    35 ]     440      IsBadWritePtr
             [
    36 ]     437      IsBadReadPtr
             [
    37 ]     423      HeapValidate
             [
    38 ]     191      GetCPInfo
             [
    39 ]     185      GetACP
             [
    40 ]     305      GetOEMCP
             [
    41 ]     409      HeapAlloc
             [
    42 ]     699      VirtualAlloc
             [
    43 ]     418      HeapReAlloc
             [
    44 ]     484      MultiByteToWideChar
             [
    45 ]     447      LCMapStringA
             [
    46 ]     448      LCMapStringW
             [
    47 ]     339      GetStringTypeA
             [
    48 ]     342      GetStringTypeW
             [
    49 ]     618      SetFilePointer
             [
    50 ]     636      SetStdHandle
    复制代码

     

        到这里,我们基本算了解了import table。不过还需要额外解释一下的是,在iid中含有两个指向thunk数组(即IAT:导入地址表)的指针(orignal first thunk 和 first thunk),两个指针指向不同的地址,但这两个thunk数组的元素值是相同的,最终指向的是同一个地址(函数序号和名称元素)。也就是说,thunk数组在PE文件中有两份,他们的地址不同,但最终指向完全相同。画出图形的话是类似下面这样(注意最终指向相同,4个指针画出来类似一个菱形):

     

        IID.OrignalFirstThunk---> thunk1[] -------\

        ---------------------------------------------   > IMAGE_IMPORT_BY_NAME ( hint, name [] )

        IID.FirstThunk-----------> thunk2[] -------/

     

        即在上面的代码里,用 iid 的无论哪一个thunk指针最终得到的都是相同的结果。PE文件中OrignalFirstThunk是一个对thunk表的原始备份,它始终是不变的。而FirstThunk在系统装载PE以后,将会修改,把他们替换成函数的真正地址(即进行绑定),这时它们就不再是指向函数序号和名称了,而是真正的函数地址(映射到进程空间之内的),你对某个DLL函数的调用在代码中本质上是调用一个存根(因为编译器不知道导入函数的实际地址),在存根处跳转到 FirstThunk 数组指明的实际函数地址。程序一旦运行起来,导入表将不再那么关键了,重要的是实际IAT(FirstThunk)。这时OrignalFirstThunk 数组就成为了一个被替换前的原始备份,当需要重新绑定时(例如DLL版本不对)需要用到它,这是“Orignal” 的含义。First的含义是,指向的是IAT的第一个元素(thunk)。Thunk可能是新造出来的词,没有确切的解释,大概是是被加载后被系统替换掉了值,有真真假假,虚虚实实的语义,同时也有一种说法是有“预先想到”的含义(备注:在ATL中也有Thunk这个词)。

     

        现在我们回头在看下IID和Thunk的定义,可以很好的理解那些注释,IID的OriginalFirstThunk指向的是未绑定之前的IAT(导入地址表),这个IAT表示的是应该导入该DLL的哪些名称(序号)的函数。而FirstThunk在绑定(被替换成实际函数地址)后指向的实际的函数地址。所以Thunk里面是一个union,里面的 AddressOfData 指是绑定之前它指向函数的hint和名称,Function 指的得是绑定后它就成了实际函数地址。

     

    typedef_iid_and_thunk 

     

        假如引用的 DLL 是确定的,那么在加载时的函数绑定工作就可以事先完成,在加载时只需要核对以下DLL的时间戳是否吻合(如果不能吻合则和没有绑定一样处理,再次人工绑定),这样就可以节省程序加载的时间,随操作系统提供的一些工具比如记事本,计算器等工具都经过了事先绑定。通过 VC 提供的 Dependency 工具可以查看到你链接的 DLL 是否经过了事先绑定。如果没有绑定,那些导入的函数的地址将显示未绑定(not bound)。借助 IDE 提供的 Bind 工具可以进行预先绑定,从而减少你的程序启动时间。或者通过编程方式,用 BindImageEx 函数去绑定一个 Image 文件。

        例如,如果你开发好了应用程序,在打包安装程序之前,可以用这两个命令行工具进行优化,从而加快程序启动的速度。

     

        (1)优化DLL的首选ImageBase:

     

        ReBase.EXE -b 10000000 *.dll

     

        (2)事先绑定DLL:(在客户端上,系统DLL未必是有效的绑定,但是对于你自己提供的DLL,该绑定一定有效)

     

        Bind.EXE -o -u -v 你的可执行文件.exe

     

        经过 Rebase 和 Bind 以后,可以提高你的程序的启动速度。系统提供的DLL都经过了Rebase,这样它们在被加载时几乎总能成功加载到首选的ImageBase。系统提供的工具也基本都经过事先绑定。

     

        IID中的ForwardChain,是函数转发器,即一个DLL文件可以导出一个假定的函数,这个函数是实际上位于另一个DLL中的函数,这样在逻辑上就成为一个“链式结构”,系统在加载时需要分析转发器,把引用的实际DLL映射到进程空间。在操作系统的 Kernel32.dll 中可以看到转发器,比如 Kernel32.dll 中的 HeapAlloc 被转发到 NTDLL 中的 RtlAllocateHeap。但转发器在实际应用中则比较少见。如果要指定一个转发器,需要在代码中使用形式如下面的语句:

     

        #pragma comment ( linker, "/export:FuncName = DllName.SomeOtherFuncName" )

     

        【参考资料】

        关于PE文件相关的知识,主要参考:

        (1)《看雪论坛五周年精华》(1~9)。

     

        关于函数转发器部分,主要参考:

        (2)《Window核心编程》第20章:DLL的高级操作技术。

     

        使用 Rebase 命令行工具重设 DLL 的首选ImageBase:

        (3)ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/tools/tools/rebase.htm

     

        使用 Bind 命令工具进行预先绑定:

        (4)ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/tools/tools/bind.htm

    展开全文
  • PE文件导入表的代码注入

    千次阅读 2008-05-05 10:27:00
    PE文件导入表的代码注入 试想一下,如果通过修改导入,能PE格式文件中的函数入口点,重定向到自己的程序中来,是不是很酷!这样,在自己在程序中,可以过滤掉对某些函数的调用,或者,设置自己的处理程序,...
      
    
    PE 文件导入表的代码注入
     
     
             试想一下,如果通过修改导入表,能把PE格式文件中的函数入口点,重定向到自己的程序中来,是不是很酷!这样,在自己在程序中,可以过滤掉对某些函数的调用,或者,设置自己的处理程序,Professional Portable Executable (PE) Protector也就是这样做的。另外,某些rootkit也使用了此方法把恶意代码嵌入到正常程序中。在逆向工程的概念里,均称为API重定向技术,让我们一起进入这个神奇的世界吧。
     
     
             1 、导入表简介
             PE文件由MS-DOS头、NT头、节头、节映像组成,如图1所示。MS-DOS头在从DOS至现今Windows的所有微软可执行文件中都存在;NT头的概念抽象自Unix系统的可执行与链接文件格式(ELF)。实际上,PE格式可以说是Linux可执行与链接格式(ELF)的兄弟,PE格式头由PE签名、通用对象文件格式(COFF)头、PE最优化头、节头组成。
     
     
    图1:PE文件格式结构
     
     
             NT头的定义可在Visual C++的<winnt.h>头文件中找到,也可使用DbgHelp.dll中的ImageNtHeader()函数得到相关信息,另外,还可以使用DOS头来获取NT头,因为DOS头的最后位置e_lfanew,代表了NT头的偏移量。
     
     
    typedef struct _IMAGE_NT_HEADERS {
       DWORD Signature;
       IMAGE_FILE_HEADER FileHeader;
       IMAGE_OPTIONAL_HEADER OptionalHeader;
    } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
     
     
             在PE最优化头中,有一些数据目录指明了当前进程虚拟内存中,主信息表的相对位置及大小,这些表可保存资源、导入、导出、重定位、调试、线程本地存储及COM运行时的有关信息。没有导入表的PE可执行文件是不可能存在的,这张表包含了DLL名及函数名,这些都是程序通过虚拟地址调用它们时所必不可少的;控制台可执行文件中没有资源表,然而,对图形用户界面的Windows可执行文件来说,资源表却是至关重要的部分;当某个动态链接库导出了它的函数,此时就需要导出表了,在OLE Active-X容器中也同样;而.NET虚拟机缺少了COM+运行时头则不能被执行。PE格式的详细说明见表1:
     
     
    数据目录
    0 Export Table(导出表)
    1 Import Table(导入表)
    2 Resource Table(资源表)
    3 Exception Table(异常表)
    4 Certificate File(凭证文件)
    5 Relocation Table(重定位表)
    6 Debug Data(调试数据)
    7 Architecture Data(架构数据)
    8 Global Ptr(全局指针)
    9 Thread Local Storage Table(线程本地存储表)
    10 Load Config Table(加载配置表)
    11 Bound Import Table(边界导入表)
    12 Import Address Table(导入地址表)
    13 Delay Import Descriptor(延误导入描述符)
    14 COM+ Runtime Header(COM+运行时头)
    15 Reserved(保留)
        表1:数据目录
     
     
    // <winnt.h>
     
    #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES     16
     
    //可选头格式
     
    typedef struct _IMAGE_OPTIONAL_HEADER {
     
       ...
     
       IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
     
     
    //目录项
    #define IMAGE_DIRECTORY_ENTRY_EXPORT      0 //导出目录
    #define IMAGE_DIRECTORY_ENTRY_IMPORT      1 //导入目录
    #define IMAGE_DIRECTORY_ENTRY_RESOURCE    2 //资源目录
    #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 //基重定位表
    #define IMAGE_DIRECTORY_ENTRY_DEBUG       6 //调试目录
    #define IMAGE_DIRECTORY_ENTRY_TLS         9 //TLS目录
     
     
             可通过简单的几行代码获得导入表的位置及大小,知道了导入表的位置后,就可知道DLL名及函数名了,这将在后面进行讨论。
     
     
    PIMAGE_NT_HEADERS pimage_nt_headers = ImageNtHeader(pImageBase);
    DWORD it_voffset = pimage_nt_headers->OptionalHeader.
       DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
     
    PIMAGE_DOS_HEADER pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);
    PIMAGE_NT_HEADERS pimage_nt_headers = (PIMAGE_NT_HEADERS)
       (pImageBase + pimage_dos_header->e_lfanew);
    DWORD it_voffset = pimage_nt_headers->OptionalHeader.
       DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
     
     
             2 、导入描述符概览
             通过导入表的导入目录项,可获知文件映像内部导入表的位置,且对每个导入的DLL、导入描述符,都有一个相应的容器,其包含了首个thunk(转换程序)地址、原始首个thunk的地址,还有指向DLL名的指针。FirstThunk指向首个thunk的位置,这些thunk在程序运行时,由Windows的PE加载器初始化,如图5所示。OriginalFirstThunk指向这些thunk的第一个存储位置,对每个函数而言,这也是提供Hint(提示)数据地址及函数名数据之处,见图4。在本例中,OriginalFirstThunk不存在,而FirstThunk则指向了提示数据及函数名数据位置之处,见图3。
             IMAGE_IMPORT_DESCRIPTOR结构代表了导入描述符,以下是其定义:
     
     
    typedef struct _IMAGE_IMPORT_DESCRIPTOR {
      DWORD    OriginalFirstThunk;
       DWORD   TimeDateStamp;
       DWORD   ForwarderChain;
       DWORD   Name;
       DWORD   FirstThunk;
    } IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
     
     
             OriginalFirstThunk指向第一个thunk(IMAGE_THUNK_DATA),thunk中保存了提示数据地址及函数名。
             TimeDateStamp包含了绑定的时间日期戳,如果它为0,表示在导入的DLL没有任何绑定。在将来,会设为0xFFFFFFFF以表明有绑定。
             ForwarderChain指向API的第一个转发链,设为0xFFFFFFFF表示没有转发。
             Name指明了DLL名的相对虚拟地址。
             FirstThunk包含了由IMAGE_THUNK_DATA定义的首个thunk数组的虚拟地址,而thunk由加载器用函数虚拟地址初始化。如果OrignalFirstThunk不存在,它指向了第一个thunk、提示(Hint)thunk及函数名。
     
     
    typedef struct _IMAGE_IMPORT_BY_NAME {
       WORD    Hint;
       BYTE    Name[1];
    } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
     
    typedef struct _IMAGE_THUNK_DATA {
       union {
          PDWORD    Function;
          PIMAGE_IMPORT_BY_NAME AddressOfData;
       } u1;
    } IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;
     
     
    图2:导入表一览
     
     
    图3:带有Orignal First Thunk的导入表视图
     
     
             这两张导入表(图2、3)清楚地说明了带有及不带Orignal First Thunk时的区别。
     
     
    图4:被PE加载器覆写后的导入表
     
     
             可用Dependency Walker,见图5,来查看导入表的所有信息,另外,还有一个小工具Import Table viewer,见图6,也可用来查看此类信息。
     
     
    图5:Dependency Walker——Visual Studio自带的工具
     
     
    图6:Import Table viewer
     
     
             另外,也可利用下面这段代码,在自己的程序中显示导入DLL及导入函数(只适用于控制台模式的程序)。
     
     
    PCHAR        pThunk;
    PCHAR        pHintName;
    DWORD        dwAPIaddress;
    PCHAR        pDllName;
    PCHAR        pAPIName;
    //----------------------------------------
    DWORD dwImportDirectory= RVA2Offset(pImageBase, pimage_nt_headers->
       OptionalHeader.
       DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
    //----------------------------------------
    PIMAGE_IMPORT_DESCRIPTOR pimage_import_descriptor=
       (PIMAGE_IMPORT_DESCRIPTOR) (pImageBase+dwImportDirectory);
    //----------------------------------------
    while(pimage_import_descriptor->Name!=0)
    {
       pThunk= pImageBase+pimage_import_descriptor->FirstThunk;
       pHintName= pImageBase;
       if(pimage_import_descriptor->OriginalFirstThunk!=0)
       {
            pHintName+= RVA2Offset(pImageBase,
               pimage_import_descriptor->OriginalFirstThunk);
       }
       else
       {
          pHintName+= RVA2Offset(pImageBase, pimage_import_descriptor->FirstThunk);
       }
       pDllName= pImageBase + RVA2Offset(pImageBase,pimage_import_descriptor->Name);
       printf(" DLL Name: %s First Thunk: 0x%x", pDllName,
              pimage_import_descriptor->FirstThunk);
     
       PIMAGE_THUNK_DATA pimage_thunk_data= (PIMAGE_THUNK_DATA) pHintName;
       while(pimage_thunk_data->u1.AddressOfData!=0)
       {
          dwAPIaddress= pimage_thunk_data->u1.AddressOfData;
          if((dwAPIaddress&0x80000000)==0x80000000)
          {
             dwAPIaddress&= 0x7FFFFFFF;
             printf("Proccess: 0x%x", dwAPIaddress);
          }
          else
          {
             pAPIName= pImageBase+RVA2Offset(pImageBase, dwAPIaddress)+2;
             printf("Proccess: %s", pAPIName);
          }
          pThunk+= 4;
          pHintName+= 4;
          pimage_thunk_data++;
       }
       pimage_import_descriptor++;
    }
     
     
             3 、API 重定向技术
             在了解了导入表的所有基础知识后,现在就要来看看重定向方法了,其算法非常简单,在当前进程的虚拟内存内部创建一个额外的虚拟空间,并生成相应指令通过JMP重定向到原始函数位置。在这里,可使用绝对跳转或相对跳转,在使用绝对跳转时要非常小心,不能像图7中那么简单地进行,应首先把虚拟地址存到EAX中,接着由JMP EAX跳转。
     
     
    图7:绝对跳转指令的API重定向
     
     
             当然了,也可以使用相对跳转,下面的代码就是这样做的,但要清楚,不能对所有DLL模块都进行API重定向,比如,拿“计算器”CALC.EXE来说,MSVCRT.DLL的某些thunk在运行时初始化期间,是从CALC.EXE代码节内部访问的,因此,重定向的话则不能正常工作。
     
     
    _it_fixup_1:
       push ebp
       mov ebp,esp
       add esp,-14h
       push PAGE_READWRITE
       push MEM_COMMIT
       push 01D000h
       push 0
       call _jmp_VirtualAlloc
       //NewITaddress=VirtualAlloc(NULL, 0x01D000,
       //                          MEM_COMMIT, PAGE_READWRITE);
       mov [ebp-04h],eax
       mov ebx,[ebp+0ch]
       test ebx,ebx
       jz _it_fixup_1_end
       mov esi,[ebp+08h]
       add ebx,esi                        // dwImageBase + dwImportVirtualAddress
     
    _it_fixup_1_get_lib_address_loop:
          mov eax,[ebx+0ch]               // image_import_descriptor.Name
          test eax,eax
          jz _it_fixup_1_end
     
          mov ecx,[ebx+10h]               // image_import_descriptor.FirstThunk
          add ecx,esi
          mov [ebp-08h],ecx               // dwThunk
          mov ecx,[ebx]                   // image_import_descriptor.Characteristics
          test ecx,ecx
          jnz _it_fixup_1_table
          mov ecx,[ebx+10h]
     
    _it_fixup_1_table:
          add ecx,esi
          mov [ebp-0ch],ecx               // dwHintName
          add eax,esi                     // image_import_descriptor.Name
                                        // + dwImageBase = ModuleName
          push eax                        // lpLibFileName
          mov [ebp-10h],eax
          call _jmp_LoadLibrary             // LoadLibrary(lpLibFileName);
     
          test eax,eax
          jz _it_fixup_1_end
          mov edi,eax
     
    _it_fixup_1_get_proc_address_loop:
             mov ecx,[ebp-0ch]            // dwHintName
             mov edx,[ecx]                // image_thunk_data.Ordinal
             test edx,edx
             jz _it_fixup_1_next_module
             test edx,080000000h          //是否按顺序导入
             jz _it_fixup_1_by_name
             and edx,07FFFFFFFh          //取得顺序
             jmp _it_fixup_1_get_addr
     
    _it_fixup_1_by_name:
             add edx,esi                  // image_thunk_data.Ordinal +
                                        // dwImageBase = OrdinalName
             inc edx
             inc edx                      // OrdinalName.Name
    _it_fixup_1_get_addr:
             push edx                     // lpProcName
             push edi                     // hModule
             call _jmp_GetProcAddress      // GetProcAddress(hModule,
                                        //                      lpProcName);
             mov [ebp-14h],eax            //_p_dwAPIaddress
             //=========================================================
             //            重定向引擎
             push edi
             push esi
             push ebx
     
             mov ebx,[ebp-10h]
             push ebx
             push ebx
             call _char_upper
     
             mov esi,[ebp-10h]
             mov edi,[ebp+010h]
     
    _it_fixup_1_check_dll_redirected:
                push edi
                call __strlen
                add esp, 4
     
                mov ebx,eax
                mov ecx,eax
                 push edi
                push esi
                repe cmps
                jz _it_fixup_1_do_normal_it_0
                pop esi
                pop edi
                add edi,ebx
             cmp byte ptr [edi],0
             jnz _it_fixup_1_check_dll_redirected
                mov ecx,[ebp-08h]
                mov eax,[ebp-014h]
                mov [ecx],eax
                jmp _it_fixup_1_do_normal_it_1
     
    _it_fixup_1_do_normal_it_0:
                pop esi
                pop edi
                mov edi,[ebp-04h]
                mov byte ptr [edi], 0e9h    // JMP指令
                mov eax,[ebp-14h]
                sub eax, edi
                sub eax, 05h
                mov [edi+1],eax             //相对JMP值
                mov word ptr [edi+05], 0c08bh
                mov ecx,[ebp-08h]
                mov [ecx],edi               // -> Thunk
                add dword ptr [ebp-04h],07h
     
    _it_fixup_1_do_normal_it_1:
             pop ebx
             pop esi
             pop edi
            //===================================================
             add dword ptr [ebp-08h],004h    // dwThunk => next dwThunk
             add dword ptr [ebp-0ch],004h   // dwHintName =>
                                             // next dwHintName
          jmp _it_fixup_1_get_proc_address_loop
     
    _it_fixup_1_next_module:
          add ebx,014h              // sizeof(IMAGE_IMPORT_DESCRIPTOR)
       jmp _it_fixup_1_get_lib_address_loop
     
    _it_fixup_1_end:
       mov esp,ebp
       pop ebp
       ret 0ch
     
     
             说点题外话,千万不要认为这样就可以绕过Professional EXE Protector了,这个软件中有一套自己的x86指令生成引擎用于创建重定向代码,有时,这个引擎还带有一个扰乱变形引擎,以使它复杂到难以跟踪分析。
     
     
             在工作原理上,上述代码依照了下列算法:
     
    1、 创建一个单独的空间以存储由VirtualAlloc()生成的指令。
    2、 通过LoadLibrary()和GerProcAddress()找到函数的虚拟地址。
    3、 检查DLL名是否匹配有效DLL列表。在本例中,识别出KERNEL32.DLL、USER32.DLL、GDI32.DLL、ADVAPI32.DLL、SHELL32.DLL为可重定向的有效DLL名。
    4、 如果DLL名有效,转到重定向部分;否则,用原始函数虚拟地址初始化thunk。
    5、 为重定向API,生成JMP(0xE9)指令并计算函数的相对地址以确定相对跳转。
    6、 把生成的指令存储在单独的空间中,并把thunk引用为这些指令的首地址。
    7、 对其他函数及DLL重复以上步骤。
     
     
    如果对CALC.EXE执行了以上步骤,并以OllyDbg进行跟踪,将会看到类似以下的代码:
     
     
    008E0000 - E9 E6F8177C     JMP SHELL32.ShellAboutW
    008E0005     8BC0           MOV EAX,EAX
    008E0007 - E9 0F764F77     JMP ADVAPI32.RegOpenKeyExA
    008E000C     8BC0           MOV EAX,EAX
    008E000E - E9 70784F77     JMP ADVAPI32.RegQueryValueExA
    008E0013     8BC0           MOV EAX,EAX
    008E0015 - E9 D66B4F77     JMP ADVAPI32.RegCloseKey
    008E001A     8BC0           MOV EAX,EAX
    008E001C - E9 08B5F27B     JMP kernel32.GetModuleHandleA
    008E0021     8BC0           MOV EAX,EAX
    008E0023 - E9 4F1DF27B     JMP kernel32.LoadLibraryA
    008E0028     8BC0           MOV EAX,EAX
    008E002A - E9 F9ABF27B     JMP kernel32.GetProcAddress
    008E002F     8BC0           MOV EAX,EAX
    008E0031 - E9 1AE4F77B     JMP kernel32.LocalCompact
    008E0036     8BC0           MOV EAX,EAX
    008E0038 - E9 F0FEF27B     JMP kernel32.GlobalAlloc
    008E003D     8BC0           MOV EAX,EAX
    008E003F - E9 EBFDF27B     JMP kernel32.GlobalFree
    008E0044     8BC0           MOV EAX,EAX
    008E0046 - E9 7E25F37B     JMP kernel32.GlobalReAlloc
    008E004B     8BC0           MOV EAX,EAX
    008E004D - E9 07A8F27B     JMP kernel32.lstrcmpW
    008E0052     8BC0           MOV EAX,EAX
     
     
             而绝对跳转时的代码如下:
     
     
    008E0000 - B8 EBF8A57C     MOV EAX,7CA5F8EBh
     //SHELL32.ShellAboutW的地址
    008E0005     FFE0           JMP EAX
     
     
             下面,就要用重定向技术来改变某个API的功能了,在本例中,将把CALC.EXE的ShellAbout()对话框重定向到“Hello World!”消息框,且只需对前述代码作稍许改动:
     
     
    ...
       //==============================================================
       push edi
       push esi
       push ebx
     
       mov ebx,[ebp-10h]
       push ebx
       push ebx
       call _char_upper
     
       mov esi,[ebp-10h]
       mov edi,[ebp+010h]        // [ebp+_p_szShell32]
     
    _it_fixup_1_check_dll_redirected:
          push edi
          call __strlen
          add esp, 4
     
          mov ebx,eax
          mov ecx,eax
          push edi
          push esi
          repe cmps             // byte ptr [edi], byte ptr [esi]
          jz _it_fixup_1_check_func_name
          jmp _it_fixup_1_no_check_func_name
     
    _it_fixup_1_check_func_name:
          mov edi,[ebp+014h]    // [ebp+_p_szShellAbout]
          push edi
          call __strlen
          add esp, 4
          mov ecx,eax
          mov esi,[ebp-18h]
          mov edi,[ebp+014h]    // [ebp+_p_szShellAbout]
          repe cmps //byte ptr [edi], byte ptr [esi]
          jz _it_fixup_1_do_normal_it_0
     
    _it_fixup_1_no_check_func_name:
          pop esi
          pop edi
          add edi,ebx
       cmp byte ptr [edi],0
       jnz _it_fixup_1_check_dll_redirected
       mov ecx,[ebp-08h]
       mov eax,[ebp-014h]
       mov [ecx],eax
       jmp _it_fixup_1_do_normal_it_1
     
    _it_fixup_1_do_normal_it_0:
          pop esi
          pop edi
          mov ecx,[ebp-08h]
          mov edi,[ebp+18h]
          mov [ecx],edi    //把新函数的地址存到thunk中
     
    _it_fixup_1_do_normal_it_1:
       pop ebx
       pop esi
       pop edi
       //==============================================================
       ...
     
     
             先检查DLL是否为“Shell32.dll”,再检查函数名是否为“ShellAboutW”,如果这两个条件为true,把ShellAbout()的thunk重定向到新的函数。
             新的函数只是一个简单的消息框:
     
     
    _ShellAbout_NewCode:
    _local_0:
       pushad    //把寄存器上下文保存在堆栈中
       call _local_1
    _local_1:
       pop ebp
       sub ebp,offset _local_1 //得到基ebp
       push MB_OK | MB_ICONINFORMATION
       lea eax,[ebp+_p_szCaption]
       push eax
       lea eax,[ebp+_p_szText]
       push eax
       push NULL
       call _jmp_MessageBox
       // MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION) ;
       popad    //从堆栈中恢复第一个寄存器上下文
       ret 10h
     
     
             当想要用一个新的函数取代某个API时,应考虑以下两个重点部分:
     
             一是不要因丢失堆栈点而破坏了堆栈内存(Stack memory),因此,最终必需以ADD ESP,xxx或RET xxx恢复原始堆栈点。
             二是通过PUSHAD、POPAD,尽量保证除了EAX之外的大多数线程寄存器的安全。
     
     
             大家可以看到,代码中也使用了PUSHAD及POPAD回收线程寄存器,对本例来说,ShellAbout()有4个DWORD成员,因此当返回时,堆栈点增长了0x10。
             重定向ShellAbout()之后,点击计算器(CALC.EXE)“帮助”菜单中的“关于计算器”,就可看到结果了。
     
     
    图8:“关于计算器”被重定向到一个消息框
     
     
             4 、保护防止逆向工程
             要使用复杂的API重定向技术来重新构造一个导入表简直是难上加难,有时,像Import REConstructor这样的工具(图10)在重建导入表时也会被搞糊涂,尤其是当重定向是通过多态代码映像来实现时。在逆向工程界,Import REConstructor是一个非常有名的工具,它会挂起目标进程以捕捉导入表信息。如果是像简单的JMP这样实现的重定向,使用此工具当然可以被重建,不过,如果加密了函数名并在内存中与多态变形代码进行绑定,它就不能得到正确的导入表了。Native Security Engine 6就是这样的一个打包器,它有一个x86代码生成器外加一个扰乱变形引擎,两者都有助于实现一个复杂的重定向结构。
     
     
    图9:Import REConstructor
     
     
             图10中列明了导入表保护的主要策略,它们中有些重定向至虚拟的Win32库,例如,分别有Kernel32、User32、AdvApi32的虚拟库,使用自己的库可防止被他人破解或安装自己的虚拟机。
     
     
    图10:导入表保护
     
     
             利用此方法,也可切断对外界的访问,如MoleBox就是这样,它过滤了FindFirstFile()及FindNextFile()以把TEXT及JPEG文件合并在压缩文件内部,当程序找到硬盘上的一个文件时,它将会被重定向到内存中。
     
     
             5 、导入表的运行时注入
             在这一节,最重要的问题是,怎样注入到运行时进程的导入表中呢,其实可通过重写内存数据及从外部重定向导入表来实现。
             (1)、WindowFromPoint()可获取特定点的窗口句柄,GetWindowThreadProcessId()可有助于了解此窗口句柄的进程ID及线程ID。
     
    POINT point;
    HWND hWindowUnderTheMouse = WindowFromPoint(point);
     
    DWORD     dwProcessId;
    DWORD     dwThreadId;
    dwThreadId=GetWindowThreadProcessId(hSeekedWindow, &dwProcessId);
     
     
             (2)、进程及线程句柄可由OpenProcess()及OpenThread()获得。
     
    HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE,
                                   dwProcessId );
    HANDLE hThread = OpenThread( THREAD_ALL_ACCESS, FALSE,
                                  dwThreadId);
     
     
             (3)、要操纵进程内存,先要挂起主线程来冻结进程。
     
    SuspendThread(hThread);
     
     
             (4)、线程环境块(Thread Environment Block TEB)的位置可由FS:[18]获得,而使用GetThreadContext()与GetThreadSelectorEntry()则可以知道FS段的基址值。
     
    CONTEXT         Context;
    LDT_ENTRY       SelEntry;
     
    Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(hThread,&Context);
     
    //计算FS的基址
    GetThreadSelectorEntry(hThread, Context.SegFs, &SelEntry);
    DWORD dwFSBase = ( SelEntry.HighWord.Bits.BaseHi << 24) |
                     (SelEntry.HighWord.Bits.BaseMid << 16) |
                      SelEntry.BaseLow;
     
     
             (5)、线程环境块可在目标进程虚拟内存内部读取获得,线程与进程环境块的具体形式,请见图11。
     
    PTEB pteb = new TEB;
    PPEB ppeb = new PEB;
    DWORD        dwBytes;
     
    ReadProcessMemory( hProcess, (LPCVOID)dwFSBase, pteb,
                       sizeof(TEB), &dwBytes);
    ReadProcessMemory( hProcess, (LPCVOID)pteb->Peb, ppeb,
                       sizeof(PEB), &dwBytes);
     
     
    图11:线程环境块(Thread Environment Block)及进程环境块(Process Environment Block)
     
     
             (6)、当前进程内存的PE映像的映像基址可由进程环境块中信息获得。
     
    DWORD dwImageBase = (DWORD)ppeb->ImageBaseAddress;
     
     
             (7)、ReadProcessMemory()可用于读取PE文件的整个映像。
     
    PIMAGE_DOS_HEADER pimage_dos_header = new IMAGE_DOS_HEADER;
    PIMAGE_NT_HEADERS pimage_nt_headers = new IMAGE_NT_HEADERS;
     
    ReadProcessMemory( hProcess,
                      (LPCVOID)dwImageBase,
                       pimage_dos_header,
                       sizeof(IMAGE_DOS_HEADER),
                      &dwBytes);
    ReadProcessMemory( hProcess,
                      (LPCVOID)(dwImageBase+pimage_dos_header->
                                e_lfanew),
                       pimage_nt_headers, sizeof(IMAGE_NT_HEADERS),
                      &dwBytes);
     
    PCHAR pMem = (PCHAR)GlobalAlloc(
                       GMEM_FIXED | GMEM_ZEROINIT,
                       pimage_nt_headers->
                       OptionalHeader.SizeOfImage);
     
    ReadProcessMemory( hProcess,
                      (LPCVOID)(dwImageBase),
                       pMem,
                       pimage_nt_headers->
                       OptionalHeader.SizeOfImage,
                       &dwBytes);
     
     
             (8)、查找DLL名及thunk值确定目标以便对它进行重定向,在本例中,DLL名是Shell32.dll,而thunk是ShellAbout()的虚拟地址。
     
    HMODULE hModule = LoadLibrary("Shell32.dll");
    DWORD dwShellAbout= (DWORD)GetProcAddress(hModule, "ShellAboutW");
     
    DWORD dwRedirectMem = (DWORD)VirtualAllocEx(
                       hProcess,
                       NULL,
                       0x01D000,
                       MEM_COMMIT,
                       PAGE_EXECUTE_READWRITE);
     
    RedirectAPI(pMem, dwShellAbout, dwRedirectMem);
     
    ...
     
    int RedirectAPI(PCHAR pMem, DWORD API_voffset,
                    DWORD NEW_voffset)
    {
       PCHAR     pThunk;
       PCHAR     pHintName;
       DWORD     dwAPIaddress;
       PCHAR     pDllName;
       DWORD     dwImportDirectory;
     
       DWORD     dwAPI;
     
       PCHAR pImageBase = pMem;
       //----------------------------------------
       PIMAGE_IMPORT_DESCRIPTOR    pimage_import_descriptor;
       PIMAGE_THUNK_DATA           pimage_thunk_data;
       //----------------------------------------
       PIMAGE_DOS_HEADER pimage_dos_header;
       PIMAGE_NT_HEADERS pimage_nt_headers;
       pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);
       pimage_nt_headers = (PIMAGE_NT_HEADERS)
                           (pImageBase+pimage_dos_header-> e_lfanew);
       //----------------------------------------
       dwImportDirectory=pimage_nt_headers->OptionalHeader
                   .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
                   .VirtualAddress;
       if(dwImportDirectory==0)
       {
            return -1;
       }
       //----------------------------------------
       pimage_import_descriptor=(PIMAGE_IMPORT_DESCRIPTOR)
                                (pImageBase+dwImportDirectory);
       //----------------------------------------
       while(pimage_import_descriptor->Name!=0)
       {
          pThunk=pImageBase+pimage_import_descriptor->
             FirstThunk;
          pHintName=pImageBase;
          if(pimage_import_descriptor->OriginalFirstThunk!=0)
          {
             pHintName+=pimage_import_descriptor->
                OriginalFirstThunk;
          }
          else
          {
             pHintName+=pimage_import_descriptor->FirstThunk;
          }
          pDllName=pImageBase+pimage_import_descriptor->Name;
     
          StrUpper(pDllName);
          if(strcmp(pDllName,"SHELL32.DLL")==0)
          {
             pimage_thunk_data=PIMAGE_THUNK_DATA(pHintName);
             while(pimage_thunk_data->u1.AddressOfData!=0)
             {
                //----------------------------------------
                memcpy(&dwAPI, pThunk, 4);
                if(dwAPI==API_voffset)
                {
                   memcpy(pThunk, &NEW_voffset, 4);
                   return 0;
                }
                //----------------------------------------
                pThunk+=4;
                pHintName+=4;
                pimage_thunk_data++;
             }
          }
          pimage_import_descriptor++;
       }
       //----------------------------------------
       return -1;
    }
     
     
             (9)、重定向中的额外内存由VirtualProtectEx()分配,在此只是生成代码,并把它写到新的空间中。
     
    DWORD dwRedirectMem = (DWORD)VirtualAllocEx(
                          hProcess,
                          NULL,
                          0x01D000,
                          MEM_COMMIT,
                          PAGE_EXECUTE_READWRITE);
     
    ...
     
    PCHAR pLdr;
    DWORD Ldr_rsize;
    GetLdrCode(pLdr, Ldr_rsize);
     
    WriteProcessMemory( hProcess,
                       (LPVOID)(dwRedirectMem),
                       pLdr,
                       Ldr_rsize,
                       &dwBytes);
     
     
             (10)、额外的内存中是加载代码,它显示一个消息框。
     
    void GetLdrCode(PCHAR &pLdr, DWORD &rsize)
    {
       HMODULE     hModule;
       DWORD       dwMessageBox;
     
       PCHAR       ch_temp;
       DWORD       dwCodeSize;
       ch_temp=(PCHAR)DWORD(ReturnToBytePtr(DynLoader,
          DYN_LOADER_START_MAGIC))+4;
       dwCodeSize=DWORD(ReturnToBytePtr(DynLoader,
          DYN_LOADER_END_MAGIC))-DWORD(ch_temp);
       rsize= dwCodeSize;
       pLdr = (PCHAR)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, dwCodeSize);
       memcpy(pLdr, ch_temp, dwCodeSize);
     
       ch_temp=(PCHAR)ReturnToBytePtr(pLdr,
          DYN_LOADER_START_DATA1);
     
       hModule = LoadLibrary("User32.dll");
       dwMessageBox= (DWORD)GetProcAddress(hModule, "MessageBoxA");
       memcpy(ch_temp+4, &dwMessageBox, 4);
    }
     
       ...
    _ShellAbout_NewCode:
    _local_0:
       pushad    //在堆栈中保存寄存器上下文
       call _local_1
    _local_1:
       pop ebp
       sub ebp,offset _local_1    //取得ebp基址
       push MB_OK | MB_ICONINFORMATION
       lea eax,[ebp+_p_szCaption]
       push eax
       lea eax,[ebp+_p_szText]
       push eax
       push NULL
       mov eax, [ebp+_p_MessageBox]
       call eax
       // MessageBox(NULL, szText, szCaption,
       /             MB_OK | MB_ICONINFORMATION) ;
       popad    //从堆栈中恢复第一个寄存器上下文
       ret 10h
       ...
     
     
             (11)、在修改之后,可执行映像就写到内存中了,记得在写之前,不要忘了把内存设为完全访问。
     
    VirtualProtectEx( hProcess,
                     (LPVOID)(dwImageBase),
                       pimage_nt_headers->
                      OptionalHeader.SizeOfImage,
                      PAGE_EXECUTE_READWRITE,
                      &OldProtect);
     
    WriteProcessMemory( hProcess,
                       (LPVOID)(dwImageBase),
                        pMem,
                        pimage_nt_headers->
                        OptionalHeader.SizeOfImage,
                        &dwBytes);
     
             VirtualProtectEx()设置页访问为PAGE_EXECUTE_READWRITE保护类型,当使用WriteProcessMemory()且可执行页中有PAGE_EXECUTE时,必须有PAGE_READWRITE访问权限。
     
     
             (12)、现在,可以释放前面挂起的进程了,点击“关于”菜单,将会看到如图12所示的界面。
     
    ResumeThread(hThread);
     
     
    图12:ShellAbout() thunk的运行时注入
     
     
             6 、对程序的挂钩
             利用此方法,可挂钩所有Windows控件并过滤API,也就是通常所说的用户级rootkit,下面就来看一下对Yahoo Messenger的挂钩是怎样实现的。
     
             (1)、使用FindWindow()通过类名取得Yahoo Messenger的句柄。
     
    HWND hWnd = FindWindow("YahooBuddyMain", NULL);
     
             (2)、用前面描述的方法实现对进程的注入。
     
             (3)、在GetDlgItemText()的导入thunk上进行注入,以过滤它的参数。
     
    UINT GetDlgItemText( HWND hDlg,
                         int nIDDlgItem,
                         LPTSTR lpString,
                         int nMaxCount);
     
             (4)、比较对话框ID、nIDDlgItem,以确定当前哪个项目在使用,找到之后,对原始GetDlgItemText()进行挂钩。
     
    CHAR pYahooID[127];
    CHAR pPassword[127];
     
    switch(nIDDlgItem)
    {
    case 211:     // Yahoo ID
       //
       GetDlgItemText(hDlg, nIDDlgItem, pYahooID, 127);
       // ...
       //
       GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);
       break;
     
    case 212:     //密码
       //
       GetDlgItemText(hDlg, nIDDlgItem, pPassword, 127);
       // ...
       //
       GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);
       break;
     
    default:
       //
       GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);
    }
     
     
    图13:对Yahoo Messenger的挂钩
     
     
             7 、结论
             导入表是Windows可执行文件中非常重要的部分,相关知识可助于理解程序在运行时,是怎样请求某个API的。你也可重定向导入表到当前进程的另一个可执行内存中,通过自己的PE加载器防止别人对程序进行逆向工程,或者仅是挂钩API函数;也可从外部在运行时挂起某个进程从而修改它的导入表。更进一步来说,在理解了这个概念之后,还可以创建自己的虚拟机,在Windows或Liunx内部的一个单独环境中,运行Windows可执行文件,也就是说,甚至可以不需要Windows就能运行Windows EXE文件了。
    展开全文
  • 图解SSIS循环导入Excel工作表

    万次阅读 热门讨论 2008-07-28 11:12:00
    将一个excel文件中的所有相同结构的工作表导入到sql2005的同一张表中。下面用大量图片完全说明整个过程.1、测试环境为一张excel中的三个sheet2、打开Microsoft Visual Studio 2005或者随sql2005安装的SQL Server ...

     将一个excel文件中的所有相同结构的工作表导入到sql2005的同一张表中。下面用大量图片完全说明整个过程.
    1、测试环境为一张excel中的三个sheet


    2、打开Microsoft Visual Studio 2005或者随sql2005安装的SQL Server Business Intelligence Development Studio,新建一个商业智能项目。


    3、连接管理器中新建一个ADO.Net连接


    4、新建连接

    5、选择jet Ole Db数据源


    6、选择一个excel文件,他默认是mdb的,你需要显示所有文件(*.*)才能选择Excel

    7、选择全部-红色标记的地方选择excel5.0


    这样用来遍历excel架构的链接就建立好了

    8、新建一个循环容器


    9、循环编辑器配置如下


    10、新建变量映射,用来保存遍历到表名



    11、变量如图




    循环容器就ok了

    12、容器中添加数据流任务


    13、设置excel源


    14、设置Excel连接和工作表


    15、设置oledb目标


    16、绿色箭头连起来


    17、编辑oledb目标,选择一个sqlserver数据表,这个表必须是已经存在的,这里我们建立一个ssistest数据库,生成一个和excel结构一样的表tt
    create table tt(a varchar(100),b varchar(100),c varchar(100),d varchar(100))
    然后用oledb去连接


    18、编辑映射,关系,默认的就可以了


    19、最后需要将刚刚选定的excel源用循环变量来代替,在高级设置中(我也找了好久)


    20、配置如下


    21、完成,可以按下调试按钮,来启动任务测试



    同理你可以设置目标的高级,来导入sheet不同结构的表格,并且可以结合我的前面一片文章,批量导入文件夹下所有excel文件来完成批量导入所有excel的所有sheet。

    相关文章:

     图解SSIS批量导入Excel文件


    参考msdn
    http://msdn.microsoft.com/en-us/library/ms345182.aspx

    展开全文
  • PE文件学习笔记(一)---导入导出

    千次阅读 2018-10-07 14:43:35
    最近在看《黑卡免杀攻防》,对讲解的PE文件导入表、导出的作用与原理有了更深刻的理解,特此记录。 首先,要知道什么是导入? 导入机制是PE文件从其他第三方程序(一般是DLL动态链接库)中导入API,以提供本...
  • 原因:csv文件里面的文本内容里面有太多的换行符 '\n' ,直接用notepad换行符替换成其他的文本 eg:“ |||”等
  • csv默认的文件换行符号是"\n"(换行LF),而excel默认的文件换行符号是"\r"(回车CR) + “\n”(换行LF),所以csv文件里的数据对于excel来说是没有换行的,因此会因为列数过多导致报错,正确的做法是将csv文件用...
  • 博图V15将DB块/变量数据存为CSV文件导入MCGS,简化西门子PLC与第三方触摸屏交互数据的繁琐过程,在降低电气成本上简化工程师重复劳动的工作内容 二、操作步骤 (1)打开MCGS组态软件-打开设备帮助查看MCGS支持多种...
  • orcle导入导出dmp文件并更改空间

    千次阅读 2018-01-09 19:06:32
    1.导入dmp文件但不用修改空间 imp user/password file=C:\a.dmp log=C:\a.log full=y ignore=y 2.导出dmp文件 用户模式:导出该用户的 exp user/password@orcl file=d:\spm_adm.dmp OWNER=user exp ...
  • 1. 前言:l 针对采用exp方式导出的数据库文件,在imp导入的时候几点注意事项总结(expdb导出的可忽略)l 本文档的书写顺序是按照操作流程步骤书写的,按文档顺序进行相关操作即可l 该文档的所有sql等都是以...
  • java实现excel表格导入数据库

    万次阅读 多人点赞 2018-11-07 15:07:14
    导入excel就是一个上传excel文件,然后获取excel文件数据,然后处理数据并插入到数据库的过程 一、上传excel 前端jsp页面,我的是index.jsp 在页面中我自己加入了一个下载上传文件的功能,其中超链接就是下载 ...
  • 使用的工具是:MicrosoftOffice Excel 2013、Notepad++ v7.5.8 Spark数据处理任务生成了CSV格式的数据文件,...“自文本”导入数据进excel文件,剩下的操作参考百度经验教程。然后提示报错“此文本文件包含的数据...
  • 用shp2pgsqlshp文件导入到数据库

    千次阅读 2012-07-05 16:57:37
    shp文件导入空间数据库中,用postgresql图形化界面pgAdmin中的插件可以很方便的导入,但有时候会提示dbf文件无法打开或导入错误(有的是生成SQL语句过程中科学计数法问题造成),这时候我们用PostGis自带的shp2pgsql...
  • 今天从拉勾网爬取职位信息准备作数据分析,因为想用Excel做数据可视化,遂决定将爬取信息保存为csv文件,再导入excel中。scrapy 自带的 exporters 类下的方法 CsvItemExporter 可以用来保存Item数据为csv文件。...
  • springboot中Excel文件导入导出

    千次阅读 2019-11-06 19:30:53
    Java学习大纲(持续更新):https://blog.csdn.net/weixin_39778570/article/details/94667501 更多IT学习资源:...Excel文件导入 从前端传递excel文件到后端,通过ajax 这里使用的是lay-ui的控件 ...
  • 上一篇文章Power BI Power Query 批量导入1-单Excel工作簿中的所有工作表数据,我讲了如何将单个工作簿中所有工作表数据汇总,那么如果想要将某个文件夹下的所有工作簿中的所有工作表汇总该如何操作了? 我现在有某...
  • 文件(或者提前copy一个副本)转为DOS文件后再打开: 可以看出,换行符变了,行末,比较暗,并且怎么是这种形状,UE有的地方也不太友好,用notepad++打开文件看看对比: 可以看出:主要是因为...
  • 1. sqoop数据迁移1.1 概述sqoop是apache旗下一款“Hadoop和关系数据库服务器之间传送数据”的工具。 导入数据:MySQL,...1.2 工作机制将导入或导出命令翻译成mapreduce程序来实现 在翻译出的mapreduce中主要是对
  • 最近帮一个老师处理数据,结果他发给我的是一堆excel文件,而且每个excel文件又包含很多sheet,所以想这些sheet整合成一个csv文件,但excel的“另存为”只支持当前sheet的操作,如果一个个文件一个个sheet的操作...
  • 有时候需要在一个工作簿中建立多个工作表,并且需要自定义工作表的名称,手动的一个个双击表名去修改非常麻烦,特别是这次我需要建立一百多个表的时候。。。。 方法一:使用数据透视表功能 方法二 使用VB写模块...
  • 工具--导入表 但是,导入可执行文件执行时会出现闪退的情况,很是讨厌。果断放弃这个方法,选择更加高大上的方法--第二种方法。     二、命令 Ctrl+r打开控制台,输入cmd打开命令界面 1.输入imp(导入) ...
  • 对于导入Excel文件这类操作,因为用户提供的xls文件中工作簿的名字不一定是默认的“Sheet1”,检测Excel文件工作表的名称往往需要调用Excel的com对象来获得,比较麻烦。 另外有个办法可以获得工作表名称,以下是...
  • create or replace directory xml as 'e:\app\xmls'; grant read,write on directory xml to UserName;...将已知格式xml文件中的数据导入Oracle数据库、数据库中的数据导出成xml文件的过程。 已知格式xml文件如下
  • 如何将数据库中已有表导入到powerDesigner生成pdm文件   1、create new PDM; 2、select database menu; 3、click Reverse Engineer database ; 4、then choose your script file ; 5、...
  • JavaWeb项目excel文件导入

    千次阅读 2017-12-01 15:27:48
    项目期间做过excel文件导入 今天来整理一下 1. 首先在web页面添加一个button按钮 “导入Excel” 注意: input 的类型必须是file才可以 2. html页面:$(function() { $("#inputExcel").change(function(){ ...
  • R语言-数据文件导入导出

    万次阅读 2018-03-20 16:39:45
    一、导入CSV文件: 先执行:bankloan&lt;-read.csv('d:/用户目录/下载/01-R语言数据科学入门/data/bankloan.csv',header = TRUE) 第一次执行上面代码一般会报错: 所以我们要解决问题,安装readr包: 每次...
  • PB9将数据窗口导出到一个EXCEL文件的多个工作表

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 194,768
精华内容 77,907
关键字:

怎么把文件导入工作表