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

  1. #define SIZE 3
  2. int MyFunction(int a[SIZE][SIZE],int b[SIZE][SIZE],int c[SIZE][SIZE])
  3. {
  4. int i,j;
  5. ; i < ; i++ )
  6. {
  7. ; j < ; j++ )
  8. {
  9. c[i][j] = a[i][]*b[][j] + a[i][]*b[][j] + a[i][]*b[][j];
  10. }
  11. }
  12. ;
  13. }

Debug版本汇编后为:

  1. #define SIZE
  2. int MyFunction(int a[SIZE][SIZE],int b[SIZE][SIZE],int c[SIZE][SIZE])
  3. {
  4. push ebp
  5. mov ebp,esp
  6. sub esp,48h      ;48H字节局部变量存储区
  7. push ebx
  8. push esi
  9. push edi
  10. lea edi,[ebp-48h]
  11. 0040102C mov ecx,12h
  12. mov eax,0CCCCCCCCh
  13. rep stos dword ptr [edi]
  14. int i,j;
  15. for ( i = ; i < 3 ; i++ )
  16. ],   ;ebp 4 为局部变量i
  17. 0040103F jmp MyFunction+2Ah (0040104a)
  18. ]
  19.  
  20. ],eax
  21. ],
  22. 0040104E jge MyFunction+0AAh (004010ca) ;标准for循环语句
  23. {
  24. for ( j = ; j < 3 ; j++ )
  25. ],   ;ebp 8 为局部变量j
  26. )
  27. ]
  28.  
  29. ],ecx
  30. ],
  31. jge MyFunction+0A5h (004010c5) ;标准for循环语句
  32. {
  33. c[i][j] = a[i][]*b[][j] + a[i][]*b[][j] + a[i][]*b[][j];
  34. ] ;取i
  35. 0040106B imul edx,edx,0Ch      ;一级偏移:第一下标[i]
  36. ] ;ebp+8为第1参数:数组a
  37. ] ;取j
  38. mov esi,dword ptr [ebp+0Ch] ;ebp+C为第2参数:数组b
  39. mov edx,dword ptr [eax+edx] ;a[i][0]
  40. ] ;a[i][0] * b[0][j] -> edx
  41. ]
  42. imul eax,eax,0Ch
  43. ]
  44. ]
  45. 0040108A mov edi,dword ptr [ebp+0Ch]
  46. ] ;a[i][1]
  47. +0Ch]; a[i][1] * b[1][j] ->eax
  48.                             ;这里注意:0CH = b[1]–b[0] = ( 1- 0 ) * SIZE * sizeof( int )
  49. add edx,eax            ;edx + eax -> edx
  50.                             ;即edx = a[i][0] * b[0][j] + a[i][1] * b[1][j]
  51. ]
  52. 0040109B imul ecx,ecx,0Ch
  53. ]
  54. ]
  55. 004010A4 mov edi,dword ptr [ebp+0Ch]
  56. ] ; a[i][2]
  57. +18h]; a[i][2] * b[2][j] ->ecx
  58. ;同上:018H = b[2]–b[0] = ( 2- 0 ) * SIZE * sizeof( int )
  59. 004010B0 add edx,ecx      ;edx + eax -> edx
  60.           ;即edx = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]
  61. ]
  62. 004010B5 imul eax,eax,0Ch
  63. 004010B8 mov ecx,dword ptr [ebp+10h] ;ebp+103参数:数组c
  64. 004010BB add ecx,eax      ;c[i] -> ecx
  65. ]
  66. ],edx ;edx -> c[i][j]
  67. }
  68. ) ;内层循环
  69. }
  70. ) ;外层循环
  71. return ;
  72. 004010CA xor eax,eax ;返回0
  73. }
  74. 004010CC
  75. //省略现场恢复代码

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

  1. int main(void)
  2. {
  3. ,,,,,,,,};
  4. ,,,,,,,,};
  5. int c[SIZE][SIZE];
  6. MyFunction(a,b,c);
  7. ;
  8. }

对应的汇编代码为:

  1. int main(void)
  2. {
  3. push ebp        ;将看到标准现场保护
  4. mov ebp,esp      ;ebp为栈基址指针
  5. sub esp,0Ach   ;临时取堆栈大小0ACH
  6. push ebx
  7. 0040110A push esi
  8. 0040110B push edi
  9. 0040110C lea edi,[ebp-0ACh]    ;起点
  10. mov ecx,2Bh      ;循环次数
  11. mov eax,0CCCCCCCCh    ;所赋的值
  12. 0040111C rep stos dword ptr [edi]   ;初始化临时区
  13. ,,,,,,,,}; ;以下为局部变量初始化
  14. ;a数组起始地址epb 24H
  15. ;ebp为栈基址指针
  16. ;故这里的地址是逐渐增大
  17.  
  18. ],
  19. ],    ;赋值
  20. ,,,,,,,,};
  21. ;b数组起始地址ebp 48H
  22.  
  23. int c[SIZE][SIZE];
  24. MyFunction(a,b,c);
  25. 0040119C lea eax,[ebp-6Ch] ;c数组起始地址ebp 6CH
  26. 0040119F push eax        ;传递第3个参数(c
  27. 004011A0 lea ecx,[ebp-48h]
  28. 004011A3 push ecx        ;传递第2个参数(b
  29. 004011A4 lea edx,[ebp-24h]
  30. 004011A7 push edx         ;传递第1个参数(a
  31. (MyFunction) ()
  32. 004011AD add esp,0Ch      ;恢复堆栈
  33. return ;
  34. 004011B0 xor eax,eax
  35. }
  36. 004011B2
  37. //省略现场恢复

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

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

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

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

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

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

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

  1. int MyFunction(int a,int b)
  2. {
  3. int c = a + b;
  4. int i;
  5. ; i < ; i ++ )
  6. {
  7. c = c + i;
  8. }
  9. return c;
  10. }

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

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

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

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

综上:

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

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

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

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

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

简要分析如下:

第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的反汇编代码

详细分析如下(这里数据段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版本汇编代码时,先标记出所有控制流程的代码,判断为双层嵌套循环,形式如:

  1. int i,j;
  2. ; i < ; i ++ )
  3. {
  4. //dosomething
  5. ; j < ; j ++ )
  6. {
  7. //dosomething
  8. }
  9. }

然后进行数值计算代码分析时,先找到输出变量,这里由语句找到语句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++编写内核程序

    ---恢复内容开始--- 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. Elixir游戏服设计二

    搞一个例子,而没有实际的目标,做起来真是烦人.几次三番都想放弃. 后来想想,即使最后完成不了完整的服务器,把需要的知识点搞搞,摸熟悉也是好的. 这里没有完整的项目目录,主要是对需要的指点进行整理.要完 ...

  2. ASP.NET没有魔法——为什么使用ASP.NET

    ASP.NET是什么? ASP.NET是一个使用HTML.CSS.Javascript来构建动态网站或者网站应用程序的Web框架,并且也可以使用它来构建web API和实时通信技术如web soket ...

  3. Django学习中遇到的问题(1)django migration No migrations to apply

    C:\Users\Desktop\homeWork\Django_stu_man>python manage.py makemigrations Migrations for 'app01': ...

  4. Druid源码阅读之连接池

    概述 Druid是阿里巴巴开源的一个数据库连接池 源码地址.下面简单分析一下连接池是怎么实现的 怎么开始阅读 如果使用过Druid连接池的都只要在Spring配置中配置jdbc的时候配置Driver是 ...

  5. 【转载】jQuery动画中的queue()函数

    原文链接:http://www.cnblogs.com/hh54188/archive/2011/04/09/1996469.html 原文摘要:当你使用一系列的动画效果(如hide,show),这些 ...

  6. pylot网站压力测试

    windows下使用python进行网站压力测试,有两个必不可少的程序需要安装,一个是python,另一个是pylot.python是一个 安装软 件,用来运行python程序,而pylot则是pyt ...

  7. JDBC工具类实例

    本文以讲解用单利模式实现一个简单的JDBC实用工具类JDBC连接的四个基本步骤:1.加载相应数据库驱动2.建立相应数据库连接3.构建Statement语句,即增删改查SQL语句4.执行Statemen ...

  8. 赋值运算符函数__from <剑指Offer>

    前段时间忙于项目,难得偷得几日闲,为即将到来的就业季做准备.在面试时,应聘者要注意多和考官交流,只有具备良好的沟通能力,才能充分了解面试官的需求,从而有针对性地选择算法解决问题. 题目来源于<剑 ...

  9. LeetCode 560. Subarray Sum Equals K (子数组之和等于K)

    Given an array of integers and an integer k, you need to find the total number of continuous subarra ...

  10. DataTable数据修改,换列

    增加列             DataTable table= new DataTable();             table.Columns.Add("ID", type ...