首页
社区
课程
招聘
[原创]例说Exe程序作为DLL进行加载
发表于: 2008-7-17 23:05 34479

[原创]例说Exe程序作为DLL进行加载

nbw 活跃值
24
2008-7-17 23:05
34479

例说Exe程序作为DLL进行加载

调用第三方exe程序里面的函数,一直是大家所向往并已经讨论过不少的问题,其方法大体有三类:

1、让第三方exe启动,然后自己程序注入进去调用之;

2、让第三方exe启动,然后远程读入其内容;

3、把第三方exe,当作DLL进行加载,并调用里面的函数。

前2个方法容易实现,但无法摆脱让exe运行的缺点,今天我们讨论第三条思路,把exe向dll一样加载,然后调用里面的函数。

就此,主要面临以下三个问题:

1、导入表修复;

2、重定位dll数据,也就是exe数据。

其实这2个问题归结起来都是重定位问题,这里就不再解释重定位原理和原因了。

有些人认为PE文件重定位,就要搞重定位表,exe要输出函数,就要搞导出表,这些其实是不必要的,只要理解了他们的原理,自己实现反而更方便。当然,另外一点原因是我现在基本忘却PE结构了。以下方法不涉及给exe增加导出表或者重定位表。

举例来说碰到的问题和解决思路,exe里面有如下代码:

        push    425570                           ; /kernel32.dll
        call    dword ptr [425280]               ; \GetModuleHandleA

这个很正常,也很简单, 0x425570 指向一个字符串, [425280] 里面是 GetModuleHandleA 函数指针,也就是导入表内容。但如果把这个exe作为DLL进行LoadLibrary,你会发现这2行代码仍然如此,一点也没变,但却无法执行了,因为这时候涉及到的这2个指针,指向的内容已经不是我们预想的了,需要把它们重定位,才能让他们指向预期目的地。重定位方法也很简单:

1、 0x425570 ,这个地址,用当前自身模块基地址加上 0x25570 ,就是新的地址;

2、 0x425280 ,这个地址,用当前自身模块基地址加上 0x25280 ,就是新的地址;

理解了这一点,就知道我们需要作什么了。

口说无凭,动手为真,下面我们举例来进行说明。就采用壳狼最近写的antidebugger测试程序吧,当然没经过他的同意 :)

目的:加载 AntiDebug.exe,调用它的第一个标签里面的 Find Debugger 功能,里面有20多个选项,我们争取把它调用完!

调用函数:

简单跟踪以下,他这些反调试手段都在一个函数体内,调用方式如下:

00404FCC   .  50            push    eax
00404FCD   .  8BCB          mov     ecx, ebx        //这句无所谓,可以nop
00404FCF   .  E8 ECF1FFFF   call    004041C0

实现方式也很简单:

    hMod = LoadLibrary(ExePath);
   
    DWORD FindDebugerCall = (DWORD)hMod + 0x41C0;
   
    _asm{
        push 0xFFFFFFFF         //这里是调用标记,如果为这个参数,表示所有的反调试功能都选了
        mov  eax, FindDebugerCall
        call eax;
    }      

最终我们的目的就是要让上面这个函数正常运行。为此,我们要作以下大量工作。

一、修复输入表

    思路很简单,首先根据被加载dll的模块基地址,找到其导入表,然后根据其函数名,自己获取导入函数地址,重新填入到正常位置。
   
    在此感谢鸡蛋壳,让我在茫茫网海搜到了他的一些代码并加以改之。
   
    pDosHeader = (PIMAGE_DOS_HEADER)hMod;
    pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE *)hMod + pDosHeader->e_lfanew);
    pOptHeader = (PIMAGE_OPTIONAL_HEADER)&(pNTHeaders->OptionalHeader);

    pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE *)hMod + pOptHeader->DataDirectory[1].VirtualAddress);

    while(pImportDescriptor->FirstThunk)
    {
        //获取dll名称
        char * dllname = (char *)((BYTE *)hMod + pImportDescriptor->Name);

        pThunkData = (PIMAGE_THUNK_DATA)((BYTE *)hMod + pImportDescriptor->OriginalFirstThunk);

        int no = 1;
        while(pThunkData->u1.Function)
        {
            if ((pThunkData->u1.Ordinal &  IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
            {
                //获取函数名称
                char *funname = (char *)((BYTE *)hMod + (DWORD)pThunkData->u1.AddressOfData + 2);
                myaddr = (int*)GetProcAddress(GetModuleHandle(dllname), funname);
            }
            
            PDWORD lpAddr = (DWORD *)((BYTE *)hMod + (DWORD)pImportDescriptor->FirstThunk) +(no-1);

            MEMORY_BASIC_INFORMATION  mbi;
            VirtualQuery(lpAddr,&mbi,sizeof(mbi));
            VirtualProtect(lpAddr,sizeof(DWORD),PAGE_READWRITE,&dwOLD);
            WriteProcessMemory(GetCurrentProcess(),
                lpAddr, &myaddr, sizeof(DWORD), NULL);
            VirtualProtect(lpAddr,sizeof(DWORD),dwOLD,0);

            no++;
            pThunkData++;
        }

        pImportDescriptor++;
    }
   
    这个东西搞完,DLL(也就是加载的exe,后面都叫dll)里面的API和其他导入函数可以正常使用了。
   

二、重定位代码段指针

    重定位意义比较广泛,大体有三类数据需要重定位。
   
    1、代码段指针。比如我上面举例的 push    425570 ,这个0x425570是个字符串指针。再比如 mov     edi, dword ptr [425400] , 这里面的 0x425400,也是指针,也需要重定位;
   
    2、数据段指针。比如这个exe里面,用到了一些SEH结构,SEH结构里面含有指向函数体的指针,比如:
   
    SEH异常处理结构:

    0042BA94  E0 1C 40 00 E6 1C 40 00 00 00 00 00 FE FF FF FF  ?@.?@.....?
    0042BAA4  00 00 00 00 D8 FF FF FF 00 00 00 00 FE FF FF FF  ....?....?
    0042BAB4  28 20 40 00 2E 20 40 00 00 00 00 00 FE FF FF FF  ( @.. @.....?
    0042BAC4  00 00 00 00 D4 FF FF FF 00 00 00 00 FE FF FF FF  ....?....?
    0042BAD4  95 24 40 00 9B 24 40 00 00 00 00 00 FE FF FF FF  ?@.?@.....?
    0042BAE4  00 00 00 00 D8 FF FF FF 00 00 00 00 FE FF FF FF  ....?....?
    0042BAF4  C8 28 40 00 CE 28 40 00 00 00 00 00 E4 FF FF FF  ?@.?@.....?
    0042BB04  00 00 00 00 B8 FF FF FF 00 00 00 00 FE FF FF FF  ....?....?

    这段数据,在数据段,里面有诸如 0x00401CE0 之类的指针,指向函数体,也需要重定位。

    3、其他一些野指针。这个就需要自己去调试找了。
   
   
    如何找上面三类数据,这是个问题,首先看一些需要重定位的例子:
   
            00407D5F   B9 402A4300      mov     ecx, 00432A40
            
            00408137   8B3D 90534200    mov     edi, dword ptr [425390]

            00D88131   FF15 78544200    call    dword ptr [425478]

    上面的三条,开头是代码地址,中间是opcode,后面是指令。我们需要把 00432A40 425390 425478 这3个地址重新转换成新的地址达到重定位的目的。他们的特点,是都是 0x004xxxxx,也就是大小在exe的代码段范围内,也就是exe的基地址加上模块大小。
   
    因此我们可以查找每条mov、call指令,判断一下立即数,发现有在这个范围内的,就认为该条需要重定位。
   
    但所需要处理的汇编指令和类型是在太多了,其他还有cmp je add ....
   
    所以我的思路,就是把程序里面每条指令都搜索以下,发现有类似常量,就把它揪出来供重定位。
   
    不可否认这个思路很挫,但实际也很有效。为此我找到了裸葱裸大虾的OD插件 ustrref 的代码,把这个很挫的思路应用在他的代码上。事实正明任何很挫的方法,只要跟裸大虾联系起来,就不仅不挫反而很有效了。
   
    对其StrFinder.cpp里面的代码进行修改,大体如下:
   
   
            if (    //这里只列举这几条指令情况,不全面
            (0 != memicmp(da.result, "mov", 3)) &&
            (0 != memicmp(da.result, "cmp", 3)) &&
            (0 != memicmp(da.result, "call", 4)) &&
            (0 != memicmp(da.result, "push", 4))
            )
            continue;

        ip1 = 0;
        ip2 = 0;

        for (int j = 0; j < MAXCMDSIZE; j++)
        {
            Readmemory(&pConst, da.ip + j, 4, MM_RESTORE | MM_SILENT);
            if ((pConst> 0x400000) && (pConst < 0x441000))
            {
                if (ip1 == 0)
                {
                    ip1 = da.ip + j;

                    sprintf((char*)pszStr, "0x%X  ,", ip1);

                    pCallBack(nStrIndex++, dwBase, dwOffset, dwSize, StrType, (char *)pszStr);

                    continue;
                }
                if (ip2 ==0)
                {
                    ip2 = da.ip + j;

                    sprintf((char*)pszStr, "0x%X  ,", ip2);

                    pCallBack(nStrIndex++, dwBase, dwOffset, dwSize, StrType, (char *)pszStr);

                    break;
                }
            }
        }
        
        修改后的代码很挫,因此得到的结果也很挫,大体如下:
        
        Address    Disassembly                                          Text String
        00401006   mov     eax, dword ptr [42FB98]                      0x401007  ,
        00401021   push    esi                                          0x40102A  ,
        00401022   push    eax                                          0x40102A  ,
        00401023   call    00412A80                                     0x40102A  ,
        00401028   mov     edi, dword ptr [<&KERNEL32.GetVersionExA>]   0x40102A  ,
        00401120   push    00425570                                     0x401121  ,
        00401120   push    00425570                                     0x401127  ,
        00401125   call    dword ptr [<&KERNEL32.GetModuleHandleA>]     0x401127  ,
        00401125   call    dword ptr [<&KERNEL32.GetModuleHandleA>]     0x40112C  ,
        0040112B   push    0042555C                                     0x40112C  ,
        0040112B   push    0042555C                                     0x401133  ,
                    ...............................
                    
        总共9476条,里面还有很多重复的,过滤一下剩下2803条。后面的 Text String ,是需要重定位的数据地址。以第一条为例:
        
        00401006   mov     eax, dword ptr [42FB98]                      0x401007  ,
   
        表示 0x401007 这个内存处的数据 0x42FB98 需要重新定位以下。
        
        当然这么多数据,里面有很多不能随便修改的,譬如:
        
        0040439A  F7C3 00400000  test    ebx, 4000
        
        按照我上面的思路,把这个地方也搜索出来了,因为里面有 0x04307C,但实际上这个地方不能修改,所以我们手工挑出来删除就可以了。
        
        另外还有比如:
        
        //这个地方是短跳转,也不能修改
        00413ECC   > \E8 BF400000   call    00417F90
        
        其他还有很多,需要自己去调试了。
        
三、重定位数据段数据

    上面说了对付代码段内容,但对付数据段,就没这么简单了。比如我上面提到的SEH结构,这个地方的内存数据是exe运行起来才加载处理的,没有好的办法找出来,只有靠自己去调试。
   
    拿上面SEH例子来说,编译后运行,会出错,错误地点定位在:
   
    004043A2  |.  E8 F9F0FFFF   call    004034A0    //这个函数里面出错
    004043A7  |.  84C0          test    al, al
    004043A9  |.  74 0E         je      short 004043B9
    004043AB  |.  6A 00         push    0
    004043AD  |.  6A 10         push    10
    004043AF  |.  68 24664200   push    00426624                         ;  debugger is found by fd_find_debugger_window!
    004043B4  |.  E8 9A900000   call    0040D453
   
    上面得 call    004034A0,功能是“found by fd_find_debugger_window”,跟踪以下,他主要是强行关闭一个句柄,然后跳到一个异常处理的地方用到了某些SEH结构。跟踪,下面这段代码可能是在设置某个异常处理结构:

    0040E289  /$  8BC1          mov     eax, ecx
    0040E28B  |.  33D2          xor     edx, edx
    0040E28D  |.  33C9          xor     ecx, ecx
    0040E28F  |.  C700 14884200 mov     dword ptr [eax], 00428814  //这里0x00428814指向一个结构,里面含有函数地址指针
    0040E295  |.  8950 34       mov     dword ptr [eax+34], edx
    0040E298  |.  8950 54       mov     dword ptr [eax+54], edx
    0040E29B  |.  8948 4C       mov     dword ptr [eax+4C], ecx
    0040E29E  |.  8950 50       mov     dword ptr [eax+50], edx
    0040E2A1  \.  C3            retn
   
    0x00428814指向内容:
   
    00428814  83 E3 40 00 D8 B1 42 00 C8 E3 40 00 24 B2 42 00  冦@.乇B.茹@.$睟.
    00428824  E6 E4 40 00 70 B2 42 00 25 E7 40 00 BC B2 42 00  驿@.p睟.%鏎.疾B.
    00428834  25 E7 40 00                                      %鏎.c
   
    我们把 0x00428814 这个地方所有看起来貌似地址一样的数据统统重定位就可以了。
   
   
   
    其他还有很多地方,有的是被错误重定位了,有的是没有重定位,这些地方都需要靠人工调试去找出来。由于我们的目标程序本身就是反调试,这无疑让我调试起来有些吃力,最终仍然有2条功能没有实现:
   
    found by fd_parent_process!
    found by fd_find_debugger_window!
   
    这2功能的实现,不仅仅是重定位问题,还涉及到了程序的初始化问题。因为我们直接把exe给LoadLibrary起来他并没有执行本身的初始化部分,导致一些内存数据没有被初始化。
   
    原则上这个问题也很容易解决,我们自己调用函数OEP地方的代码就可以了,当然这肯定要导致我们所谓的dll会像exe一样启动起来出来自己的界面。所以可以在显示界面的地方搞点SMC,让他停留在那个地方不出现就可以了。
   
    话虽这么说,但这个启动过程是如此漫长,我修复了十几条重定位数据,仍然没有正常跑起来,我就放弃了。
   
   
    我附件里面的程序,首先启动LoadExe.exe,然后上面三个按钮分别用来加载exe、修复位导入表、重定位数据。
   
    下面的3个函数用来实现相关功能,都是调用的check.exe里面的函数。check.exe就是壳狼同学写的,我对被调用函数其稍微处理了一下,发现调试器以后直接返回,不弹出他自己的对话框。
   
    如果程序在你系统上执行产生非法、退出,那很正常,可以尝试打开调试器再跑我这个程序,如果运气好的话,能看到点东西,不至于白下载。
   
   
    最后一并感谢海风月影、16的友情测试,以及www.luocong.com


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 7
支持
分享
最新回复 (63)
雪    币: 7309
活跃值: (3788)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
2
不错,很强
能解决大部分的重定位问题
2008-7-17 23:10
0
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
原来是这样的
2008-7-17 23:11
0
雪    币: 2316
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
4
很强大,有时间学习下。
2008-7-17 23:16
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
5
严重学习~~~123465
2008-7-17 23:26
0
雪    币: 223
活跃值: (70)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
6
如果带壳,估计很挫
2008-7-17 23:32
0
雪    币: 82
活跃值: (10)
能力值: (RANK:210 )
在线值:
发帖
回帖
粉丝
7
建议楼主写一个通用的LoadExeAsLibrary
2008-7-18 00:01
0
雪    币: 1946
活跃值: (248)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
8
你搞得太复杂了
2008-7-18 01:12
0
雪    币: 239
活跃值: (160)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
这个思路我以前也试过,但没楼主做得这么完美,多谢牛哥!!
2008-7-18 01:31
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
10
出现可疑const就不行。
2008-7-18 06:05
0
雪    币: 159
活跃值: (339)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
11
顶牛老...
2008-7-18 09:23
0
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
12
既然无法通用,就不如简单处理个别函数

或者为了省事,把自己主程序的imagebase设成其他值,把0x400000留给第三方exe,就免去重定位了
2008-7-18 10:10
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
13
带壳不是挫的问题,而是根本行不通的问题
2008-7-18 14:54
0
雪    币: 97697
活跃值: (200759)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
14
Support.
2008-7-18 15:24
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
这样也可以。。
学习
2008-7-18 15:45
0
雪    币: 223
活跃值: (70)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
16
h一语中的
2008-7-18 17:14
0
雪    币: 104
活跃值: (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
正解。只要自己的EXE避开要调用的EXE 的加载地址即可,简单又实用
2008-7-18 23:35
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
这招有效, 我就曾经成功调用过 WinWord.exe 的函数
2008-7-19 10:03
0
雪    币: 2316
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
19
人为刀俎,我为鱼肉
2008-7-19 13:43
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
20
没那么严重吧哈哈,技术研究请兄弟不要见怪。
2008-7-19 17:12
0
雪    币: 2316
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
21
不怪不怪。尽管拿去解剖。
2008-7-19 22:57
0
雪    币: 209
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
思路不错,虽然需要靠调试来完善重定位,不过必要时还是可以考虑直接将要使用的Exe汇编指令抽取出来重用的
2008-7-21 17:52
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
23
我是进来学习的
2008-7-21 21:40
0
雪    币: 184
活跃值: (108)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
24
我进来学习的。

貌式不错,以前就有这样的想法,但想想麻烦挺多的,就不动手了。你居然玩转实现它,PF
楼上几个思路可以深一点,就是自己的基址可以想点新办法啊,光光让400000又有点生搬硬套了,呵呵。
2008-7-22 09:34
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
25
自己有reloc爱在哪在哪,400000只是举个例子
2008-7-22 09:49
0
游客
登录 | 注册 方可回帖
返回
//