首页
论坛
专栏
课程

[原创]第三方二进制汇编代码在VC中的移植和应用

2007-6-11 16:29 22031

[原创]第三方二进制汇编代码在VC中的移植和应用

2007-6-11 16:29
22031
二进制汇编代码在VC中的移植和应用

作者:老实和尚  coverlove@163.com

1、前言
对于没有源代码的开发库中提供的函数或者关键代码(一般是dll或者exe程序提供)。我们不能分析出其某个函数或者某段代码的实现细节,而又想在自己的工程中进行代码复用,这时我们比较直观的做法就是移植其汇编代码到我们的开发环境中。

        本文主要介绍在如何VC (Microsoft Visual C++ 6.0) 中内联汇编代码及其注意事项。主要涉及的工具有OLLYDBG(修改版本的OLLYICE功能更加强大)、Microsoft Visual C++ 6.0。

2、OLLYDBG简介
OllyDbg 是一种具有可视化界面的 32 位汇编-动态分析调试器。它的特别之处在于可以在没有源代码时解决问题,并且可以处理其它编译器无法解决的难题。

运行环境:
OllyDbg 可以以在任何采用奔腾处理器的 Windows 95、98、ME、NT 或是 XP(未经完全测试)操作系统中工作。

支持的处理器:
OllyDbg 支持所有 80x86、奔腾、MMX、3DNOW!、Athlon 扩展指令集、SSE指令集以及相关的数据格式,但是不支持SSE2指令集。

配置:
有多达百余个选项用来设置 OllyDbg 的外观和运行。

数据格式:
OllyDbg 的数据窗口能够显示的所有数据格式:HEX、ASCII、UNICODE、 16/32位有/无符号/HEX整数、32/64/80位浮点数、地址、反汇编(MASM、IDEAL或是HLA)、PE文件头或线程数据块。

3、应用实例



       
        如上图有一个函数PcInfo.dll,里面导出了两个函数,RegistryCode 和IsValidCheckCode。通过OllyICE我们很容易找到这两个函数的实现过程。



第一个函数RegistryCode有三个参数,第一个压栈的是一个字符串的地址,第二个和第三个是一个整数,通过函数返回后对eax寄存器进行了检查可以断定函数带一个int型的返回值。
函数调用后没有进行堆栈的平衡说明这个函数是一个__stdcall方式调用的,堆栈平衡由被调用者平衡。调用方式确定后就可以还原函数原型如下:

int __stdcall RegistryCode(int a1,int a2,const char * key)



函数内部代码实现如上图,我们可以将汇编实现代码拷贝出来如下:

10002280 >  83EC 10         sub     esp, 10
10002283    8D4424 00       lea     eax, [esp]
10002287    56              push    esi
10002288    57              push    edi
10002289    33FF            xor     edi, edi
1000228B    57              push    edi
1000228C    50              push    eax
1000228D    57              push    edi
1000228E    68 3F000F00     push    0F003F
10002293    57              push    edi
10002294    57              push    edi
10002295    57              push    edi
10002296    68 44910010     push    10009144    ; ASCII "Software\abc\def"
1000229B    68 02000080     push    80000002
100022A0    FF15 00800010  call   [<&ADVAPI32.RegCreateKeyExA>]  ; ADVAPI32.RegCreateKeyExA
100022A6    85C0            test    eax, eax
100022A8    75 56           jnz     short 10002300
100022AA    8B7424 24       mov     esi, [esp+24]
100022AE    8B0D 88910010   mov     ecx, [10009188]
100022B4    8B15 8C910010   mov     edx, [1000918C]
100022BA    A1 90910010     mov     eax, [10009190]
100022BF    85F6            test    esi, esi
100022C1    894C24 0C       mov     [esp+C], ecx
100022C5    895424 10       mov     [esp+10], edx
100022C9    894424 14       mov     [esp+14], eax
100022CD    74 31           je      short 10002300
100022CF    56              push    esi
100022D0    FF15 24800010   call    [<&KERNEL32.lstrlenA>]     ; kernel32.lstrlenA
100022D6    40              inc     eax
100022D7    8D4C24 0C       lea     ecx, [esp+C]
100022DB    8B5424 08       mov     edx, [esp+8]
100022DF    50              push    eax
100022E0    56              push    esi
100022E1    6A 01           push    1
100022E3    57              push    edi
100022E4    51              push    ecx
100022E5    52              push    edx
100022E6    FF15 04800010   call  [<&ADVAPI32.RegSetValueExA>]  ; ADVAPI32.RegSetValueExA
100022EC    85C0            test    eax, eax
100022EE    75 05           jnz     short 100022F5
100022F0    BF 01000000     mov     edi, 1
100022F5    8B4424 08       mov     eax, [esp+8]
100022F9    50              push    eax
100022FA    FF15 08800010   call    [<&ADVAPI32.RegCloseKey>]  ; ADVAPI32.RegCloseKey
10002300    8BC7            mov     eax, edi
10002302    5F              pop     edi
10002303    5E              pop     esi
10002304    83C4 10         add     esp, 10
10002307    C2 0C00         retn    0C

有两种方法可以复用上面这段代码,第一种方法是把这段代码复制到一个汇编编译器中编译成object,然后在VC中链接这个外部函数,但是这种方法需要额外的编译器,使用起来比较复杂。本文主要介绍第二种方法,就是在VC编译器中复用这段汇编代码,然后直接编译调试。
在VC编译器中使用内联汇编,有两种方式:一种是在每一条指令前面加上__asm,另外一种方式比较适合大段汇编代码,就是使用__asm { } 包含汇编代码块。这个不改变变量作用域,只是一个指示符号,注意大部分伪汇编代码VC是不支持的。将以上汇编代码拷贝到VC的函数int __stdcall RegistryCode(int a1,int a2,const char * key)函数内部,这样一个简单函数初步实现了,但是还需要作一些调整。

VC内联外部汇编代码需要注意以下几个事项:
A.        代码的修正
拷贝出来的汇编代码需要进行初步处理才能移植到VC的函数中,上面的汇编代码中地址头需要全部删除掉(这个可以在UltraEdit的列模式中复制),对于代码中的常数全部要加上0x,因为Ollyice拷贝出来的都是16进制数,对应跳转指令可以用原来的标示符号,如jnz     short  100022F5,改成jnz     short  A100022F5,将地址前加上一个字符,否则VC对于这样的跳转会将数字理解成数字而认为是错误代码,而不会根据目标标签自动计算偏移量。

B.        堆栈的平衡
由于VC默认为函数生成了保护寄存器的代码,也就是进行了寄存器压栈操作,这样栈顶就可能改变,因此拷贝过来的汇编代码中基于栈顶取数据的地址就不对了,因为堆栈栈顶已经被VC默认保护堆栈的PUSH指令改变了。最简单的做法就是先平衡堆栈,也就是先弹出压栈的寄存器,这样拷贝过来的汇编代码在函数内部运行的环境就是干净的,无污染的,而VC在DEBUG模式和RELEASE模式为函数生成的保护寄存器的代码是不同的,需要分别处理来平衡堆栈。(注:如果不想VC生成保护寄存器的代码,可以加上关键字:__declspec( naked ),这样就不需要平衡堆栈了,可以连这一步都省了,但是了解编译器的一些行为对内联汇编的成功与否很重要)

C.        地址的处理
对于汇编代码中出现的地址,有些是字符或者整数数组的首地址,需要在全局或者局部建立字符串数组,然后替换成字符数组的地址。对于VC而言,指针类型的变量本身就是地址,因此在汇编代码中直接使用指针名称,而不需要LEA(或者OFFSET伪指令)来计算地址。

D.        函数调用的处理
对于汇编中使用到的函数需要加入其相应的Lib文件,对于Windows Api只需要加入Windows.h文件即可,这样VC才会为使用到的函数加入导入表和函数符号。对于外部DLL中的函数调用需要注意的是,必须使用间接寻址方式调用函数,而不能是立即数地址,也就是使用FirstThunk+RVA的方式来寻址,由PE加载器动态填充的地址代替实际地址,而FirstThunk+RVA的地址在编译过程中就可以惟一确定。对于静态连接的函数如printf等这些函数,则使用直接调用方式,调用地址是立即数。而编译器对于有些外部导出函数,采用一个call(立即数)+ jmp(间接寻址)方式实现。

具体实现过程:

添加需要使用的全局字符串,定义如下:

char *key1="Software\\abc\\def";
char *key2="InstallCode";

由于DEBUG模式VC默认为函数生成了保护寄存器的代码,总共对4个寄存器进行了保护,因此我们在函数的入口处平衡堆栈,代码如下:
__asm
                {
                        pop edi

                        pop esi

                        pop ebx

                        pop ebp
                }
如果是RELEASE模式,则只对3个寄存器进行了压栈保护,因此我们在函数的入口处平衡堆栈,两种模式可以合成写成如下的方式:

//还原堆栈,VC为函数默认生成了保护寄存器的代码

        #ifdef _DEBUG   //DEBUG模式和RELEASE模式生成的保护寄存器的代码不一样

                __asm
                {
                        pop edi

                        pop esi

                        pop ebx

                        pop ebp
                }

        #else
                __asm
                {
                        pop edi

                        pop esi

                        pop ebp
                }

        #endif

上面的一段代码,可以使用在函数前面加上__declspec( naked )的方法来替代。

移植的部分代码加到上面的代码后面即可,具体修改如下:

__asm
        {
                sub     esp, 0x10

                lea     eax, dword ptr [esp]

                push    esi
                push    edi
                xor     edi, edi
                push    edi
                push    eax
                push    edi
                push    0x0F003F
                push    edi
                push    edi
                push    edi

                push    key1   //如果是指针直接使用指针名称,否则使用lea eax, key1;  push eax方式

                push    0x80000002

                call dword ptr [RegCreateKeyExA] //此处不能使用call RegCreateKeyExA,只能进行间接寻址调用

                test    eax, eax

                jnz     short  A10002300 //使用标签让VC自动计算偏移地址,不能使用纯数组标签

                mov     esi, dword ptr [esp+0x24]
               
                mov     eax, key2

                mov     ecx, [eax]

                mov     edx, [eax+4]

                mov     eax, [eax+8]

                test    esi, esi

                mov     dword ptr [esp+0x0C], ecx
                mov     dword ptr [esp+0x10], edx
                mov     dword ptr [esp+0x14], eax

                je      short  A10002300  //使用标签让VC自动计算偏移地址

               
                push    esi

                call dword ptr [lstrlenA]  //此处不能使用call lstrlenA,只能进行间接寻址调用
               
                inc     eax
               
                lea     ecx, dword ptr [esp+0x0C]

                mov     edx, dword ptr [esp+0x08]

                push    eax
                push    esi
                push    1
                push    edi
                push    ecx
                push    edx

                call dword ptr [RegSetValueExA]  //此处不能使用call RegSetValueExA,只能进行间接寻址调用
  
               
                test    eax, eax

                jnz     short  A100022F5

                mov     edi, 1

A100022F5:

                mov     eax, dword ptr [esp+0x08]
                push    eax

                call dword ptr [RegCloseKey]    //此处不能使用call RegCloseKey,只能进行间接寻址调用

A10002300:

                mov     eax, edi
                pop     edi
                pop     esi
                add     esp, 0x10
               
                retn    0x0c
        }

全部源代码如下:

// PCInfoAPI.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"

#define PCInfoAPI_EXPORTS

#ifdef PCInfoAPI_EXPORTS

#define PCInfoAPI_API extern "C" __declspec(dllexport)
#else
#define PCInfoAPI_API __declspec(dllimport)
#endif

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                                         )
{
    return TRUE;
}

char *key1="Software\\abc\\def";

char *key2="InstallCode";

PCInfoAPI_API  int __stdcall RegistryCode(int a1,int a2,const char * key)
{
        //还原堆栈,VC为函数默认生成了保护寄存器的代码

        #ifdef _DEBUG //DEBUG模式和RELEASE模式生成的保护寄存器的代码不一样

                __asm
                {
                        pop edi

                        pop esi

                        pop ebx

                        pop ebp
                }

        #else
                __asm
                {
                        pop edi

                        pop esi

                        pop ebp
                }

        #endif

        __asm
        {
                sub     esp, 0x10

                lea     eax, dword ptr [esp]

                push    esi
                push    edi
                xor     edi, edi
                push    edi
                push    eax
                push    edi
                push    0x0F003F
                push    edi
                push    edi
                push    edi

                push    key1        

                push    0x80000002

                call dword ptr [RegCreateKeyExA]

                test    eax, eax

                jnz     short A10002300

                mov     esi, dword ptr [esp+0x24]

                //mov     esi,  key
               
                mov     eax, key2

                mov     ecx, [eax]

                mov     edx, [eax+4]

                mov     eax, [eax+8]

                test    esi, esi

                mov     dword ptr [esp+0x0C], ecx
                mov     dword ptr [esp+0x10], edx
                mov     dword ptr [esp+0x14], eax

                je      short A10002300
               
                push    esi

                call dword ptr [lstrlenA]
               
                inc     eax
               
                lea     ecx, dword ptr [esp+0x0C]

                mov     edx, dword ptr [esp+0x08]

                push    eax
                push    esi
                push    1
                push    edi
                push    ecx
                push    edx

                call dword ptr [RegSetValueExA]   
               
                test    eax, eax

                jnz     short A100022F5

                mov     edi, 1

A100022F5:

                mov     eax, dword ptr [esp+0x08]
                push    eax

                call dword ptr [RegCloseKey]   

A10002300:

                mov     eax, edi
                pop     edi
                pop     esi
                add     esp, 0x10
               
                retn    0x0c
        }

}

[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

上传的附件:
  • 1.jpg (28.69kb,1439次下载)
  • 2.jpg (56.30kb,1417次下载)
  • 3.jpg (123.15kb,1415次下载)
最新回复 (50)
kanxue 8 2007-6-11 16:35
2
0
编辑了一下你帖,将附件中的图片帖到文章中。
shangzh 6 2007-6-11 16:48
3
0
可惜不能让图片对齐到文本
蓝莲花 2007-6-11 16:51
4
0
[QUOTE=;]...[/QUOTE]
特喜欢这类创造性的文章!我顶呀!
BlueT 2 2007-6-11 19:09
5
0
如果图形多,附带doc文档为好。
alangsos 1 2007-6-11 19:35
6
0
好!!问题是好多函数里面有好多call 那就难办了
HSXiaogang 2007-6-11 19:42
7
0
好,学习了,很有启发性
garasmc 2007-6-11 20:26
8
0
图可有可无,可以用文本形式表现出来,呵呵
文章很好,顶哈!
shangzh 6 2007-6-11 20:39
9
0
复制代码去掉地址的一个简单方法是使用UltraEdit的列模式来复制,就比较干净了;这个很容易用来制作注册机等。。。
wynney 24 2007-6-11 21:48
10
0
好久没来了
一上来就看到好文
支持
不懂算法 2 2007-6-11 23:33
11
0
不错的文章,写注册机时候有用
kagayaki 2007-6-12 00:01
12
0
上来就看到好文
顶!!!!
HSXiaogang 2007-6-12 05:29
13
0
同意楼上所述,这种问题好像确实不好办!

(不过,本贴还是好贴! 提供了一种思路)
aki 2 2007-6-12 08:11
14
0
从od中复制代码的话,有几个插件可以直接处理,方便好用。
对于编译器为了保护加上的压栈处理,可以将函数声明为 naked
shangzh 6 2007-6-12 08:47
15
0
呵呵,刚开始也想到这个关键字,可惜没有用,可以加上关键字:__declspec( naked )
邪恶之眼 2007-6-12 09:16
16
0
刚好昨天写了个注册机,正好用到vc内联asm,第一次写vc内联asm所以疑问还很多。看楼主文章帮助很大,多谢。 还有在vc里  如何才能使这样的语句正常执行?

dword func(dword* num)
{
   _asm
          {
                MOV ESP num //本意是用 ESP保存临时变量
          }
}
liangzi2222 2007-6-12 16:32
17
0
内嵌汇编。。好文章。。
SkyJack 2007-6-12 17:44
18
0
好文,顶一个,学习ing.
yijun8354 12 2007-6-12 20:00
19
0
好文,学习了~~
三根火柴 4 2007-6-12 20:09
20
0
我也学习学习,挺棒的文章!!!
Ajampie 2007-6-12 21:21
21
0
漫漫学习,漫漫进步
yaleond 1 2007-6-12 22:02
22
0
mbaighttt 2007-6-18 12:14
23
0
能否直接用LoadLibrary&GetProcAddress调用那个dll里面的函数,而不是重建新的dll
yplq 2007-6-18 13:31
24
0
so good!!!!
shangzh 6 2007-6-19 14:19
25
0
能否直接用LoadLibrary&GetProcAddress调用那个dll里面的函数,而不是重建新的dll,可以的,只要把原来的dll改一个名称,然后动态加载,再写一个模拟的接口就行,这里这样用是有特殊用途的,比如欺骗上层软件,模拟它的接口,修改原始dll的输入输出。

如果比较简单的dll可以使用dll2lib转换成静态library(不过大部分转换后是不成功的,因为涉及到很多问题),将library中的函数名称全部改名,然后在你的dll工程中把原来的dll生成的静态library连接进去编译成一个dll,相当于扩展原来的dll,还可以使用原来的名称。
cAtEyE 2 2007-6-20 16:23
26
0
支持一下,不错的思路
elance 6 2007-6-20 16:45
27
0
方法不错,有机会实验一下。
chenzping 2 2007-6-28 15:50
28
0
支持,学习了,写的太好了。
eastboy 2007-6-30 15:31
29
0
顶一个,好文章,有创意!
bxm 29 2007-6-30 16:08
30
0
这么好的文章,一定得顶!
swqswq 2007-7-5 07:21
31
0
楼主真乃天才也,文章相当惊人,拜读了,也正好用上,万分感谢,万分感谢
longshsu 2007-8-17 16:26
32
0
这篇文章太赞了
blue_devil_bomb 5 2007-8-17 21:44
33
0
既然已经知道函数的名称,同时也知道在那个dll中,可以在vc中使用LoadLibrary方法,然后调用给出的地址就可以了,这样子不是更简单。不过唯一的问题就是多了一个DLL
shangzh 6 2007-8-29 17:21
34
0
目的是次要的,方法是重要的
小熊饼干 2007-8-29 22:46
35
0
不错

刚好需要这方面的知识...
newsearch 9 2007-8-30 08:47
36
0
技巧型,好文顶一下!
仙剑太郎 2 2007-9-12 13:06
37
0
好文,收藏`
LasmL 2007-9-24 17:45
38
0
有必要搞的这么复杂吗,用IDA啊
Hitman 2007-10-29 14:47
39
0
搞不懂这种文章也能搞成精华.
ljfu 2007-10-30 16:34
40
0
文章很好,想法也很好,只是我等菜鸟看起来还很吃力!!
menting 14 2007-10-30 20:06
41
0
倒了。。这个都精华了,早知道我早发了,我都用了N久了,我难过呀!。。。。以前 写的程序都会用倒的,现在,不写这类程序了!
zhouxl 1 2007-10-30 21:29
42
0
喜欢 。。。
收藏学习了
szdbg 5 2007-11-22 09:35
43
0
很好的文章,顶...
ziyihou 1 2007-11-22 10:14
44
0
学习了,好文章
njmdk 2007-11-28 12:04
45
0
好文章得顶个,很详细,最近正好用到这些
shinechou 2007-11-29 13:43
46
0
险些错过如此好文,非常感谢您的分享!
Kendiv 2007-12-7 17:48
47
0
不错,一定要顶。
尤东 2007-12-11 08:25
48
0
学习..ing
freelion 2007-12-12 12:29
49
0
如果是DLL,动态加载一下,然后调用不就行了
jzhr 2007-12-15 11:45
50
0
非常感谢,大力支持作者继续写好文章
游客
登录 | 注册 方可回帖
返回