《天书夜读:从汇编语言到windows内核编程》三 练习反汇编C语言程序

时间:2022-09-04 09:12:01

1) Debug版本算法反汇编,现有如下3×3矩阵相乘的程序:

 #define SIZE 3
 int MyFunction(int a[SIZE][SIZE],int b[SIZE][SIZE],int c[SIZE][SIZE])
 {
     int i,j;
      ; i <  ; i++ )
     {
          ; j <  ; j++ )
         {
             c[i][j] = a[i][]*b[][j] + a[i][]*b[][j] + a[i][]*b[][j];
         }
     }
     ;
 }  

Debug版本汇编后为:

 #define SIZE
 int MyFunction(int a[SIZE][SIZE],int b[SIZE][SIZE],int c[SIZE][SIZE])
 {
     push    ebp
     mov     ebp,esp
     sub     esp,48h                  ;48H字节局部变量存储区
     push    ebx
     push    esi
     push    edi
     lea     edi,[ebp-48h]
 0040102C    mov     ecx,12h
     mov     eax,0CCCCCCCCh
     rep stos dword ptr [edi]
     int i,j;
     for ( i =  ; i < 3 ; i++ )
     ],       ;ebp – 4 为局部变量i
 0040103F    jmp     MyFunction+2Ah (0040104a)
     ]  

     ],eax
 ],
 0040104E    jge     MyFunction+0AAh (004010ca)  ;标准for循环语句
     {
         for ( j =  ; j < 3 ; j++ )
     ],       ;ebp – 8 为局部变量j
     )
     ]  

 ],ecx
     ],
     jge     MyFunction+0A5h (004010c5)  ;标准for循环语句
         {
             c[i][j] = a[i][]*b[][j] + a[i][]*b[][j] + a[i][]*b[][j];
     ]       ;取i值
 0040106B    imul    edx,edx,0Ch              ;一级偏移:第一下标[i]
 ]       ;ebp+8为第1参数:数组a
     ]       ;取j值
     mov     esi,dword ptr [ebp+0Ch]     ;ebp+C为第2参数:数组b
     mov     edx,dword ptr [eax+edx]     ;a[i][0]
 ]   ;a[i][0] * b[0][j] -> edx
 ]
     imul    eax,eax,0Ch
     ]
     ]
 0040108A    mov     edi,dword ptr [ebp+0Ch]
 ]   ;a[i][1]
     +0Ch]; a[i][1] * b[1][j] ->eax
                             ;这里注意:0CH = b[1]–b[0] = ( 1- 0 ) * SIZE * sizeof( int )
     add     edx,eax            ;edx + eax -> edx
                             ;即edx = a[i][0] * b[0][j] + a[i][1] * b[1][j]
     ]
 0040109B    imul    ecx,ecx,0Ch
 ]
 ]
 004010A4    mov     edi,dword ptr [ebp+0Ch]
 ]   ; a[i][2]
 +18h]; a[i][2] * b[2][j] ->ecx
                                                  ;同上:018H = b[2]–b[0] = ( 2- 0 ) * SIZE * sizeof( int )
 004010B0    add     edx,ecx                  ;edx + eax -> edx
                                           ;即edx = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]
 ]
 004010B5    imul    eax,eax,0Ch
 004010B8    mov     ecx,dword ptr [ebp+10h]     ;ebp+10第3参数:数组c
 004010BB    add     ecx,eax                  ;c[i] -> ecx
 ]
 ],edx   ;edx -> c[i][j]
         }
 )   ;内层循环
     }
 )    ;外层循环
     return ;
 004010CA    xor     eax,eax                      ;返回0
 }
 004010CC    …
 //省略现场恢复代码  

再看看这个函数的调用,使用如下代码:

 int main(void)
 {
     ,,,,,,,,};
     ,,,,,,,,};
     int c[SIZE][SIZE];
     MyFunction(a,b,c);
     ;
 }  

对应的汇编代码为:

 int main(void)
 {
     push    ebp                    ;将看到标准现场保护
     mov     ebp,esp                  ;ebp为栈基址指针
     sub     esp,0Ach                  ;临时取堆栈大小0ACH
     push    ebx
 0040110A    push    esi
 0040110B    push    edi
 0040110C    lea     edi,[ebp-0ACh]             ;起点
     mov     ecx,2Bh                  ;循环次数
     mov     eax,0CCCCCCCCh             ;所赋的值
 0040111C    rep stos dword ptr [edi]           ;初始化临时区
     ,,,,,,,,};    ;以下为局部变量初始化
        ;a数组起始地址epb – 24H
            ;ebp为栈基址指针
        ;故这里的地址是逐渐增大  

 ],
     ],        ;赋值
     ,,,,,,,,};
        ;b数组起始地址ebp – 48H  

     int c[SIZE][SIZE];
     MyFunction(a,b,c);
 0040119C    lea     eax,[ebp-6Ch]               ;c数组起始地址ebp – 6CH
 0040119F    push    eax                     ;传递第3个参数(c)
 004011A0    lea     ecx,[ebp-48h]
 004011A3    push    ecx                     ;传递第2个参数(b)
 004011A4    lea     edx,[ebp-24h]
 004011A7    push    edx                     ;传递第1个参数(a)
 (MyFunction) ()
 004011AD    add     esp,0Ch                   ;恢复堆栈
     return ;
 004011B0    xor     eax,eax
 }
 004011B2    …
 //省略现场恢复  

第一:C语言在函数入口时在栈区开辟临时存储区来存储局部变量,这些局部变量的生存周期一直到函数返回;

第二:局部变量的声明并不会对其进行赋值,只有在赋值语句出现时,才对内存进行读写;

第三:C语言中不管以什么方式传递数组,传递的永远是指针值(它在MyFunction函数中并没有对传递过来的数组实行一次拷贝);

第四:所有的下标运算符同点操作符一样被转为偏移量的形式;

第五:读Debug版汇编很简单,写Debug版汇编是一个挑战

2) 阅读汇编代码技巧:先分清楚控制流程代码与数值计算代码。对数值计算的代码判断输入与输出(被读的内部变量为输入,被写的内部变量为输出),还原成C语言。任何一段中间不带跳转的连续MOV和加减乘除指令均可还原为一个C表达式。

3) 对于二(2)中的for循环函数:

 int MyFunction(int a,int b)
 {
     int c = a + b;
     int i;
      ; i <  ; i ++ )
     {
         c = c + i;
     }
     return c;
 }  

在Release版本中反汇编为(我采用的是OllyICE反汇编,其中RETN及为RET):

《天书夜读:从汇编语言到windows内核编程》三 练习反汇编C语言程序

可以看到与Debug版本的汇编代码大相径庭:第一:没有现场保护与现场恢复代码;第二:并没有为参数以及自定义的局部变量分配临时存储区域;第三:for循环的结构显然被改写。

先看看这个函数是怎么被调用的:

《天书夜读:从汇编语言到windows内核编程》三 练习反汇编C语言程序

可以看到,调用代码与Debug版本并没有区别,先把参数1,2反序压入堆栈,随后执行调用函数,最后恢复堆栈指针(这里的返回0不用理会,Release汇编以后,在函数入口(主函数)中的函数调用会另外生成一个函数来执行,即上面所看到的代码段)。

综上:

第一:Release版本会对代码执行优化,甚至不保存EBP,完全不做现场保护与恢复工作;

第二:对于数量较少的局部变量,Release版本并不会为它们在堆栈上分配临时变量存储区域,这意味着ESP也不需要进行操作(RET指令除外),取参数直接用ESP加上偏移来取(第一个参数偏移4,是因为执行CALL调用的时候实现了一次压栈);

第三:既然不分配临时变量存储区域,那么临时变量将使用寄存器来存放,这与C语言中指定的寄存器变量等同?目前我还不清楚。不过对于频繁操作的变量(如本例的计数器局部变量i)会自动优化为使用寄存器;

第四:内部的结构可能会被部分篡改或者完全改变,无法和原C语言一一对应(如本例优化后的for循环使用了相对简单的do循环方式,把判断和跳转放到了最后)

下面看看三(1)中矩阵相乘的例子,先简单看下调用代码是否有区别:

《天书夜读:从汇编语言到windows内核编程》三 练习反汇编C语言程序

简要分析如下:

第1行:分配局部变量栈区06CH( = 108 )字节(32位下相当于27个int型数值)
第2~4行:将常数7、8、9移动到寄存器
第5~11行(排除第9行):对数组a、b赋值7,8,9
第12~16行(外加第9行):取数组c、b、a地址并压入堆栈实现参数传递
第17~22行:对数组b其余单元进行赋值
第23~28行:对数组a其余单元进行赋值
第29行:执行函数调用
第30行:清EAX,即设置返回值为0
第31行:恢复堆栈,078H = 06CH(局部变量堆栈区) + 08H(3次PUSH操作)

对与赋值的与参数取址传递的过程很不和谐,直到函数调用前,堆栈区情况如下:

堆栈地址

值(HEX)

描述

改变代码行号

非局部变量堆栈区

0012FF0C

0012FF3C

第1个参数,a数组起始地址

16

0012FF10

0012FF18

第2个参数,b数组起始地址

15

0012FF14

0012FF60

第3个参数,c数组起始地址

13

0012FF18

00000009

b[0][0]:b数组起始地址

6

0012FF1C

00000008

b[0][1]

8

0012FF20

00000007

b[0][2]

11

0012FF24

00000006

b[1][0]

23

0012FF28

00000005

b[1][1]

24

0012FF2C

00000004

b[1][2]

25

0012FF30

00000003

b[2][0]

26

0012FF34

00000002

b[2][1]

27

0012FF38

00000001

b[2][2]

28

0012FF3C

00000001

a[0][0]:a数组起始地址

17

0012FF40

00000002

a[0][1]

18

0012FF44

00000003

a[0][2]

19

0012FF48

00000004

a[1][0]

20

0012FF4C

00000005

a[1][1]

21

0012FF50

00000006

a[1][2]

22

0012FF54

00000007

a[2][0]

10

0012FF58

00000008

a[2][1]

7

0012FF5C

00000009

a[2][2]

5

0012FF60

随机值

c[0][0]:c数组起始地址

0012FF64

随机值

c[0][1]

0012FF68

随机值

c[0][2]

0012FF6C

随机值

c[1][0]

0012FF70

随机值

c[1][1]

0012FF74

随机值

c[1][2]

0012FF78

随机值

c[2][0]

0012FF7C

随机值

c[2][1]

0012FF80

随机值

c[2][2]

0012FF84

非局部变量堆栈区

 

综上:

第一:Release总是最大限度的使用寄存器(如本例的赋值拆成了两部分,把7、8、9放入寄存器后赋值比使用立即数赋值更高效?暂时不得而知);

第二:Release汇编总是使用尽量少的临时堆栈区(如这里使用3个大小为9的二维int型数组,刚好108个字节,编译器并没有进行多余的分配,这与Debug版本不同,Debug下一般都会保证一定余量);

第三:Release汇编代码与C语句并没有一一对应的关系,顺序结构完全可能被打乱,必须从操作的结果来分析操作的目的(如这里是对数组的初始化与函数调用,但初始化被参数压栈“打断”)

可见,Release版本虽然汇编代码与Debug版本很多差别,但是完成的功能类似,下面看看MyFunction的反汇编代码

《天书夜读:从汇编语言到windows内核编程》三 练习反汇编C语言程序

详细分析如下(这里数据段DS与堆栈段基址SS相等):

第1行:取第1个参数(数组a起始地址)到EAX
第2行:取第3个参数(数组c起始地址)到EDX
第3~6行:现场保护
第7行:ECX = a[0][1]
第8行:寄存器EDI作为外层循环计数器,赋初值EDI = 3
第9行:函数被调用以后,ESP被移动了5次(一次CALL+4次PUSH),则这里的ESP+18H实际上指向的是调用前的ESP + 18H – 20 = ESP + 4,即指向第2个函数参数。故此处为取第2个参数(数组b起始地址)到EAX
第10行:ESI作为内层循环计数器,赋初值3
第11~13行:EAX = b[1][0],EBX = b[2][0],EBP = b[0][0],即取b数组的一列(第一次循环是第1列,下内层循环一次,移动到下一列)
第14~15行:EBX = a[0][2] * b[2][0],EBP = a[0][0] * b[0][0],a的第1行,与上类似
第16行:EBX = a[0][2] * b[2][0] + a[0][0] * b[0][0]
第17~19行:EBX = a[0][2] * b[2][0] + a[0][1] * b[1][0] + a[0][0] * b[0][0]
第20行:EXA = EXA + 4,即指向下一列
第21~22行:将结果EBX写入数组c,c向后移动一个单位
第23~24行:内层循环计数器减1,循环判断
第25行:a数组移动到下一行
第26~27行:外层循环计数器减1,循环判断
第28~32行:置返回值为0,恢复现场

综上:

第一:两个for循环均采用了do-while循环的形式;

第二:打乱了c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j]从左到右的相加次序;

第三:与Debug下汇编相比,大量使用寄存器来存储变量,数组下标的偏移变的隐晦;

第四:并非所有Release版反汇编都可以还原为编译之前的C语言代码,但是还原成功能等价的C语言代码还是可能的

总述:读Release版汇编很蛋疼,写Release版汇编是不可能!

说明:这里看到的汇编源代码与原书并不相同,原书仅仅作为引导,我以实际为准自行分析与学习。所有如有错误,与原书无关。

4) 阅读Release汇编代码时,如2)所言先分清楚控制流程代码与数值计算代码。对数值计算代码的阅读可以使用逆向的方法:先判断输出,如在3*3矩阵乘法Release版本汇编代码时,先标记出所有控制流程的代码,判断为双层嵌套循环,形式如:

 int i,j;
  ; i <  ; i ++ )
 {
     //dosomething
      ; j <  ; j ++ )
     {
         //dosomething
     }
 }  

然后进行数值计算代码分析时,先找到输出变量,这里由语句找到语句MOV PTR DWORD DS:[EDX],EBX可以判断,DS:[EDX]是输出变量,而且整段代码只有这一处对它进行了修改,然后逆向追踪EBX,从而最终得到由已知输入变量计算得到输出变量的计算表达式。

《天书夜读:从汇编语言到windows内核编程》三 练习反汇编C语言程序的更多相关文章

  1. 《天书夜读:从汇编语言到windows内核编程》五 WDM驱动开发环境搭建

    (原书)所有内核空间共享,DriverEntery是内核程序入口,在内核程序被加载时,这个函数被调用,加载入的进程为system进程,xp下它的pid是4.内核程序的编写有一定的规则: 不能调用win ...

  2. 《天书夜读:从汇编语言到windows内核编程》八 文件操作与注册表操作

    1)Windows运用程序的文件与注册表操作进入R0层之后,都有对应的内核函数实现.在windows内核中,无论打开的是文件.注册表或者设备,都需要使用InitializeObjectAttribut ...

  3. 《天书夜读:从汇编语言到windows内核编程》十一 用C&plus;&plus;编写内核程序

    ---恢复内容开始--- 1) C++的"高级"特性,是它的优点也是它的缺点,微软对于使用C++写内核程序即不推崇也不排斥,使用C++写驱动需注意: a)New等操作符不能直接使用 ...

  4. 《天书夜读:从汇编语言到windows内核编程》六 驱动、设备、与请求

    1)跳入到基础篇的内核编程第7章,驱动入口函数DriverEnter的返回值决定驱动程序是否加载成功,当打算反汇编阅读驱动内核程序时,可寻找该位置. 2)DRIVER_OBJECT下的派遣函数(分发函 ...

  5. 《天书夜读:从汇编语言到windows内核编程》四 windows内核调试环境搭建

    1) 基础篇是讲理论的,先跳过去,看不到代码运行的效果要去记代码是一个痛苦的事情.这里先跳入探索篇.其实今天的确也很痛苦,这作者对驱动开发的编译与调试环境介绍得太模糊了,我是各种尝试,对这个环境的搭建 ...

  6. 《天书夜读:从汇编语言到windows内核编程》十 线程与事件

    1)驱动中使用到的线程是系统线程,在system进程中.创建线程API函数:PsCreateSystemThread:结束线程(线程内自行调用)API函数:PsTerminateSystemThrea ...

  7. 《天书夜读:从汇编语言到windows内核编程》九 时间与定时器

    1)使用如下自定义函数获取自系统启动后经历的毫秒数:KeQueryTimeIncrement.KeQueryTickCount void MyGetTickCount(PULONG msec) { L ...

  8. 《天书夜读:从汇编语言到windows内核编程》七 内核字符串与内存

    1)驱动中的字符串使用如下结构: typedef struct _UNICODE_STRING{ USHORT Length; //字符串的长度(字节数) USHORT MaximumLength; ...

  9. 《天书夜读:从汇编语言到windows内核编程》二 C语言的流程与处理

    1) Debug与Release的区别:前者称调试版,后者称发行版.调试版基本不优化,而发行版会经过编译器的极致优化,往往与优化前的高级语言执行流程会大相径庭,但是实现的功能是等价的. 2) 如下fo ...

随机推荐

  1. Windows下的Eclipse启动出现:a java runtime environment&lpar;JRE&rpar; or java development kit&lpar;JDK&rpar; must be

    打开eclipse的时候回遇到这种情况 解决方案: 进入eclipse.exe所在的目录,在eclipse.ini文件中加入以下两行: -vm <your path to jdk|jre> ...

  2. Java知多少(111)数据库之修改记录

    修改数据表记录也有3种方案. 一.使用Statement对象 实现修改数据表记录的SQL语句的语法是:    update表名 set 字段名1 = 字段值1,字段名2 = 字段值2,……where特 ...

  3. Qt学习总结-ui篇&lpar;二&rpar;

    qccs定义圆角   border-radius:10px; 如果想给特定位置定义圆角,如: 左上角:border-left-top-radius:10px; 右下角色:border-right-bo ...

  4. 我是如何理解ThreadLocal

    ThreadLocal的概念 ThreadLocal从英文的角度看,可以看成thread和local的组合,就是线程本地的意思,我们都知道,看过jvm内存分配的人都知道在jvm虚拟机中对每一个线程都分 ...

  5. TabTopAutoLayout【自定义顶部选项卡区域(带下划线)(动态选项卡数据且可滑动)】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 自定义顶部选项卡布局LinearLayout类,实现带下划线且可滑动效果.[实际情况中建议使用RecyclerView] 备注:如果 ...

  6. Android Wear 2&period;0 AlarmManager 后台定时任务

    以前在Android 4.0时,alarmManager 没什么问题.后来android为了优化系统耗电情况,引入了doze模式,参见此页 https://developer.android.com/ ...

  7. &lt&semi;Dare To Dream&gt&semi; 第四次作业:基于原型的团队项目需求调研与分析

    任务1:实施团队项目软件用户调研活动. (1)真实的用户调研对象:生科院大三学生 (2)利用实验七所开发的软件原型:网站原型链接 (3)要有除原型法之外的其他需求获取手段: 访谈法 开会研讨法 (4) ...

  8. UMEditor&lpar;Ueditor mini&rpar;修改图片上传路径

    UMEditor(Ueditor mini)修改图片上传路径 imageUp.ashx string pathbase = "/UpLoad/images/"; //保存文件夹在网 ...

  9. TCP拥塞控制及连接管理

    在阅读此篇之前,博主强烈建议先看看TCP可靠传输及流量控制. 一.TCP拥塞控制 在某段时间,若对网络中某资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏——产生拥塞(congestion ...

  10. Vue&period;js的小例子--随便写的

    1.领导安排明天给同事们科普下vue 2.简单写了两个小例子 3.话不多说直接上代码 <!DOCTYPE html> <html> <head> <meta ...