《软件调试分析技术》学习笔记

《软件调试分析技术》学习笔记(一)

今天开始写写一些心得体验。

《软件调试分析技术》是好友Monster的处女作品。作为一直以的好伙伴,他是我看着长大的,(*^__^*) 嘻嘻……之所以有今天这样的成绩,是与他的努力和天赋脱不了关系的。他大方地给了我PDF版的,我也大方的给了我们全班。但我们班有同学说,“这是撒子呦,看不看不懂”。我决心写一些学习笔记,和我班的同学一起来多交流,让更多热爱次行业的人都进入软件调试这个神殿。

好了,闲话不说。

1)逆向工程(reverse engineering)简介

逆向工程是通过对现有的二进制可执行文件进过反汇编、反编译、调试模拟程序运行等手段,分析出程序的执行流程、数据结构等。

逆向工程不是简单的复制和模仿,而是运用相关手段对产品进行分析再设计等创新处理,从而使程序表现出更加优良的性能、缩短新程序的开发周期、提高设计开发效率。

2)学习前提

建议在学习逆向分析技术之前有一定的编程功底。具有一定的代码逻辑能力,最好还做过一些Windows应用程序,了解一些常用的API,PE文件格式、COM原理、Windows的消息处理机制、异常处理机制等。需要和二进程代码打交道,所以一定要掌握好汇编语言(更重要的是反汇编)

由此可以看出来,同学们不是智商不够,而是基础知识比较薄弱

3)常用工具

调试工具OllyDBG、SoftICE、WinDBG、Syser Debugger,分析工具PEID、ExeInfo、FI、FFI…… 等,常见的PE工具有:LoadPE、PETools、Stud_PE、PEditor等,常见的反汇编工具有:IDA pro、W32dASM、C32ASM等。

M运用了很多篇幅简单介绍了各个工具的使用,我认为,这些工具的使用要在平常实战中渐渐熟悉,所以没必要将此章目看的过细。本人也只用过OllyDBG。

4)windows消息处理机制

5)PE文件格式

PE 的意思就是Portable Executable(可移植的执行体)。它是Win32环境自身所带的执行体文件格式。它的一些特性继承自UnixCoff (common object file format)文件格式。PE文件格式给了我们洞悉Windows结构的良机。

DOS MZ header
DOS stub
PE header
Section table
Section 1
Section 2
Section ...
Section n

上图是 PE文件结构的总体层次分布。所有PE文件(甚至32位的DLLs)必须以一个简单的DOS MZ header开始。我们通常对此结构没有太大兴趣。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随MZ header 之后的DOS stubDOS stub实际上是个有效的EXE,在不支持PE文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串"This program requires Windows"或者程序员可根据自己的意图实现完整的 DOS代码。通常我们也不对DOS stub太感兴趣:因为大多数情况下它是由汇编器/编译器自动生成。通常,它简单调用中断21h服务9来显示字符串"This program cannot run in DOS mode"

紧接着 DOS stub的是PE headerPE headerPE相关结构IMAGE_NT_HEADERS的简称,其中包含了许多PE装载器用到的重要域。当我们更加深入研究PE文件格式后,将对这些重要域耳目能详。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从DOS MZ header 中找到PE header 的起始偏移量。因而跳过了DOS stub直接定位到真正的文件头PE headerPE文件的真正内容划分成块,称之为sections(节)。每节是一块拥有共同属性的数据,比如代码/数据、读/写等。我们可以把PE文件想象成一逻辑磁盘,PE header 是磁盘的boot扇区,而sections就是各种文件,每种文件自然就有不同属性如只读、系统、隐藏、文档等等。

上述为摘抄,翻译的不是很好

PE文件最前面紧随DOS MZ文件头的是一个DOS可执行文件(Stub)。这使得PE文件成为一个合法的MS-DOS可执行文件。DOS MZ文件头后面是一个32位的PE文件标志0x50450000(IMAGE_NT_SIGNATURE),即PE00。接下来的是PE的映像文件头,包含的信息有该程序的运行平台,有多少个节,文件链接的时间,文件命名格式,后面还紧跟一个可选映像头,包含PE文件的逻辑分布信息,程序加载信息,开始地址,保留的堆栈数量,数据段大小等。可选头还有一个重要的域,称为“数据目录表”的数组,表的每一项都是指向某一节的指针。可选映像头后面紧跟的是节表和节。节通过节表来实现索引。实际上,节的内容才是真正执行的数据和程序。每一个节都有相关的标志。每一个节会被一个或多个目录表指向,目录表可通过可选头的“数据目录表”的入口找到。就像输出函数表或基址重定位表。也存在没有目录表指向的节。

具体PE还有很多,这里只是一个初步的探究,如果想深入,网上找资料。


《软件调试分析技术》学习笔记(二)

1.寄存器

寄存器M讲的比较透彻。寄存器是中央处理器CPU的组成部分,是有限存贮容量的高速存贮部件,它们可用来暂存指
令、数据和位址,是内存阶层中的最顶端,也是系统获得操作资料的最快速途径。

1.1数据寄存器
数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问
存储器的时间。这些 低16位寄存器分别命名为:AX、BX、CX和DX,它和先前的CPU中的寄存器
相一致。在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,
但在32位CPU中,其32位寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑
运算结果,而且也可作为指针寄存器, 所以,这些32位寄存器更具有通用性。

1.2变址寄存器
寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内
的偏移量, 用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供
方便。它们可作一般的存储器指针使用。在字符串操作指令的执行过程中,对它们有特定的要求,
而且还具有特 殊的功能。

1.3指针寄存器
寄存器EBP、ESP、BP和SP称为指针寄存器(Pointer Register),主要用于存放堆栈内存储单元
的偏移量, 用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供
方便。指针寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运
算结果

它们主要用于访问堆栈内的存储单元,并且规定:
BP为基指针(Base Pointer)寄存器,用它可直接存取堆栈中的数据;
SP为堆栈指针(Stack Pointer)寄存器,用它只可访问栈顶。

1.4段寄存器
段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值和一个偏
移量组合而成 的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址

1.5指令指针寄存器
指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。在具有预
取指令功能的系统中,下次要执行的指令通常已被预取到指令队列中,除非发生转移情况。
1.6标志寄存器
标志寄存器(Flags Register,FR)又称程序状态字(Program Status Word,PSW)。这是一个存
放条件标志、控制标志寄存器,主要用于反映处理器的状态和运算结果的某些特征及控制指令的执
行。

其中有些要有一定的汇编基础,希望大家好好在实践里体验。

2 汇编指令
汇编指令是汇编语言中使用的一些操作符和助记符,还包括一些伪指令(如assume,end)。用
于告诉汇编程序如何进行汇编的指令,它既不控制机器的操作也不被汇编成机器代码,只能为汇编
程序所识别并指导汇编如何进行。汇编指令多到数不过来。这里来介绍些常用到的:
一、数据传输指令
MOV 传送字或字节
PUSH 把字压入堆栈
POP 把字弹出堆栈
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈
POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈
PUSHF 标志入栈
POPF 标志出栈
LEA 装入有效地址
LDS 传送目标指针,把指针内容装入DS
LES 传送目标指针,把指针内容装入ES
LAHF 标志寄存器传送,把标志装入AH
SAHF 标志寄存器传送,把AH内容装入标志寄存器
二、算术运算指令
ADD 加法
ADC 带进位加法
INC 加 1
SUB 减法
SBB 带借位减法
DEC 减 1
NEC 求相反数(以 0 减之)
CMP 比较(两操作数作减法,仅修改标志位,不回送结果)
MUL 无符号乘法
IMUL 整数乘法
DIV 无符号除法
IDIV 整数除法
三、逻辑运算指令
OR 或运算
AND 与运算
XOR 异或运算
NOT 取反
TEST 测试,两操作数作与运算,仅修改标志位,不回送结果
SHL 逻辑左移
SHR 逻辑右移
ROL 循环左移
ROR 循环右移
RCL 通过进位的循环左移
RCR 通过进位的循环右移
四、串指令
MOVSX 先符号扩展,再传送
MOVZX 先零扩展,再传送
MOVS 串传送
CMPS 串比较
五、程序转移指令
JMP 无条件转移指令
CALL 过程调用
RET 过程返回
JG/JNLE 大于转移
JGE/JNL 大于或等于转移
JL/JNGE 小于转移
JLE/JNG 小于或等于转移
JE/JZ 等于转移
JNE/JNZ 不等于时转移
LOOP CX不为零时循环


《软件调试分析技术》学习笔记(三)

M给出一个C程序

[cpp] view plain copy
  1. #include
  2. #include
  3. inta;
  4. intmain()
  5. {
  6. intb;
  7. int*c;
  8. c=(int*)malloc(sizeof(int));
  9. a=1;
  10. b=2;
  11. *c=3;
  12. free(c);
  13. return0;
  14. }


这段代码定义了一个整型全局变量a,在主函数main()中定义了一个整型局部变量b和一个整形指针变量c,然后调用malloc()函数申请大小为1个整形变量的内存并把申请到的内存地址赋值给指针变量c,再依次给变量a、b和c指向的内存赋值1、2、3,接下来释放刚才申请到的堆内存,释放后退出主函数main()。

用OD加载

[plain] view plain copy
  1. .text:00401000pushebp
  2. .text:00401001movebp,esp
  3. .text:00401003subesp,8

这里把栈顶向下压8个字节,为整型变量b和指针变量c开辟空间。它们都是局部变量。

[plain] view plain copy
  1. .text:00401006push4;Size
  2. .text:00401008callds:__imp__malloc
  3. .text:0040100Eaddesp,4
  4. .text:00401011mov[ebp+c],eax

一个整型变量占用的空间为4个字节,这里调用函数malloc()申请大小为4个字节的堆空间,然
后把申请到的内存空间地址赋值给变量c。

[plain] view plain copy
  1. .text:00401014mov?a@@3HA,1;inta
  2. .text:0040101Emov[ebp+b],2
  3. .text:00401025moveax,[ebp+c]
  4. .text:00401028movdwordptr[eax],3


这里分别给三个变量赋值1、2、3。可以看到变量a所使用的内存空间地址是一个常量,它存在于程序的数据段中;变量b所使用到的内存空间地址是ebp+b,它位于栈区;指针变量c储存的数据是刚才由函数malloc()申请到的堆空间地址。

[plain] view plain copy
  1. .text:0040102Emovecx,[ebp+c]
  2. .text:00401031pushecx;Memory
  3. .text:00401032callds:__imp__free
  4. .text:00401038addesp,4
  5. .text:0040103Bxoreax,eax
  6. .text:0040103Dmovesp,ebp
  7. .text:0040103Fpopebp
这里调用函数free()来释放刚才由函数malloc()申请到的堆空间地址。



《软件调试分析技术》学习笔记(四)

[plain] view plain copy
  1. 一起看看数组在程序中的使用。C语言代码:
[cpp] view plain copy
  1. #include
  2. intmain()
  3. {
  4. inta[2];
  5. a[1]=0;
  6. a[a[1]]=1;
  7. return0;
  8. }


这段代码定义了一个大小为2的整型数组,给数组下标为1的变量赋值0,然后取数组下标为1的变量的值作为新的下标,给该变量赋值1,这里数组下标为1的变量的值为0,就是说给数组下标为0的变量赋值1,最后退出主函数main()。

载入OD看看反汇编代码:

[plain] view plain copy
  1. .text:00401000pushebp
  2. .text:00401001movebp,esp
  3. .text:00401003subesp,8


这里为数组a开辟内存空间。一个整型变量所占的内存空间为4字节,数组a的大小为2,因此把栈顶向下压8个字节。

[plain] view plain copy
  1. text:00401006mov[ebp+a+1*4],0


只要知道数组就是一个指针,这句代码就很容易理解了。OD是一个好工具,这里它已经分析出a是一个数组了,而ebp+a是数组的基地址。a是一个整型数组,其中的每一个变量占用的大小都是4字节,因此在数组中下标为i的变量的地址就可以表示为数组的基地址加上数组下标为i的变量与数组下标为0的变量的偏移量,即ebp+a+i*4,这一句代码中的地址ebp+a+1*4就是数组下标为1的变量的地址。

[plain] view plain copy
  1. text:0040100Dmoveax,[ebp+a+1*4]
  2. .text:00401010mov[ebp+eax*4+a],1

这两句代码,获取数组中下标为1的变量的值,把这个值作为新的数组下标eax,并给数组下标为eax的变量赋值1。

[plain] view plain copy
  1. .text:00401018xoreax,eax
  2. .text:0040101Amovesp,ebp
  3. .text:0040101Cpopebp
  4. .text:0040101Dretn

《软件调试分析技术》学习笔记(五)

在了解了变量的使用方式之后,本节来讲讲数在反汇编代码中的简单运算。
计算机在使用数的时候一般会用到二进制,十进制和十六进制。

二进制是计算机技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。现在的CPU使用的基本都是二进程数,用高电平表示1,低电平表示0。为了方便区别,一般在二进程数后追加一个字母B,例如二进制数10则表示为10B。B是二进制的英文binary的首字母。
在人们的日常生活使用的数一般都是十进制数。人类的各种古文明都很有默契地使用十进制数,这与人类有十根手指有密切的关系。十进制数使用十个数码0123456789来表示,逢十进一。为了方便区别,一般在十进程数后追加一个字母D,例如十进制数10则表示为10D。D是十进制的英文decimal的首字母。
在计算机中使用的十进制数一般是BCD码,BCD码用4位二进制数来表示1位十进制数。例如在二进制数10000B表示十进制数的16D,而BCD码10000却表示十进制数10D,它们在内存中的储存形式完全相同,但表示的数却不同。
虽然计算机在处理数据时使用的是二进制数,但是二进制数表示起来是很不方便的,例如一个十进制数1000000D要表示成二进制数则为11110100001001000000B。4位二进制数能表示的最小的数为0000B,能表示的最大的数是1111B,即十进制数15D,这样就可以用4位二进制数来表示16个数字,把这十六个数字分别用一个数码来表示,于是就行成了十六进制数。十六进制数用01234567890ABCDEF这十六个数码来表示0-15D这十六个数,每逢十六进一。为了方便表示,为了方便区别,一般在十六进程数后追加一个字母H,例如十六进制数10则表示为10H。H是十六进制的英文hexadecimal的首字母。一般在程序中用到的十六进制数的表示方法是在数之前加前缀0x,例如十六进制数10则表示为0x10。
通常这三种进制的数之间还需要互相转化。一位十六进程数和四位二进制数是互相对应的,对应关系如下:
十六进制数 二进制数 十六进制数 二进制数
0 0 8 1000
1 1 9 1001
2 10 A 1010
3 11 B 1011
4 100 C 1100
5 101 D 1101
6 110 E 1110
7 111 F 1111
了解了这个转化方式那要转化就很容易了,例如十六进制数0xBC就可以用二进制数10111100B;相反地,二进制数1001010B也可以表示为十六进制数0x4A。二进程数与十进制数之间的转换也是比较容易的。我记得当年学《微型计算机原理》的时候课本上有一句口诀:十进制数转化二进制数,除二取余;二进制数转化十进制数,乘二取整。听起来可能不太好理解,但这是转化方法的精髓。把十进制数转换成二进制数,是一个连续除2的过程,把要转换的十进制数数,除以2,得到一组商和余数;再用计算得到的商来计算,除以2,又得到另一组商和余数。这样一直计算到得到的商为零为止,计算结束后把每一次计算的余数从后往前连起来就得到了这个十进制数对应的二进程数。例如现在有一个十进制11D,转化过程如下:
11 / 2 商:5 余:1
5 / 2 商:2 余:1
2 / 2 商:1 余:0
1 / 2 商:0 余:1
把每一次计算的余数从后往前连起来则为1011,这样就计算出了十进制数11D对应的二进制数1011B。二进制数转化十进制数相对简单一点。二进制数从右往左数第n位表示2的n-1次幂,当第n位为0时忽略,把第n位为1的所有幂加起来就得到了这个十进制数。例如一个二进制数1011B,转化过程如下:
2^3 + 2^1 + 2^0 = 8 + 2 + 1 = 11
那么11D就是二进制数1011B所对应的十进制数。
再来看看另一个问题,机器码的表示方法。机器码的出现主要是为了解决计算机中数的符号和计算问题。这里介绍计算机的原码、反码、补码。
原码是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位),符号位为0表示正数或者0,符号位为1表示负数,其余位表示数值的大小。例如一个int型的十进制数100,用原码表示则为:0000000001100100;同样一个int型的十进制数-100,用原码表示则为:1000000001100100。用原码这样来表示一个数是很直观的,但是它有一个很严重的缺点,就是源码不可以直接进行运算。1 + (-1) = 0,这是很显然的,用源码计算:
0000000000000001 + 1000000000000001 = 1000000000000010
用原码计算的结果为1000000000000010,转换为十进制数则是-2,这显然是错误的。 反码是原码到补码之间的一个过渡产品,它的求法很简单,正数的反码与其原码相同;负数的反码对其绝对值的原码逐位求反例如一个int 型的十进制数100,用原码表示则为:
0000000001100100

同样一个int型的十进制数-100,其绝对值为100,其绝对值的原码为
0000000001100100,逐位求反则得-100的反码为:1111111110011011。
补码是计算机程序在进行运算时统一使用的机器码,他可以将符号位和其它位统一处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。补码的计算方法也很简单,正数的补码和它的原码相等,负数的补码等于它的反码加一。例如一个int型的十进制数-1,其补码计算过程为:首先求出它的绝对值为1,计算它的绝对值1的原码
0000000000000001,

逐位求反则得-1的反码为:1111111111111110,给它加1就变成了:
1111111111111111。

反码可以直接带入运算,例如用反码来计算1 + (-1):
0000000000000001 + 1111111111111111 = 0
然后来看看几种基本的逻辑运算。逻辑常量只有两个,即0和1,用来表示两个对立的逻辑状态。逻辑运算通常用来测试逻辑常量的真假值。大于、大于等于、小于、小于等于、等于、不等于……这些用来比较的逻辑运算就不讲了,上过小学的读者肯定都会的。这里来看看与运算、或运算、异或运算以及非运算。
与运算,把两个操作数的逐位进行比较,若两个操作数的第n位全为1,则运算结果的第n位为
1,否则为0。汇编语言中用and指令来表示与运算,例如:
0010110011010100
1010110010011010
and
-------------------
0010110010010000
或运算,把两个操作数的逐位进行比较,若两个操作数的第n位若有一个为1,则运算结果的第n位为1,否则为0。汇编语言中用or指令来表示或运算,例如:
0010110011010100
1010110010011010
or
-------------------
1010110011011110
异或运算,把两个操作数的逐位进行比较,若两个操作数的第n位有一个为0,而另一个为1,则运算结果的第n位为1,否则为0。汇编语言中用xor指令来表示异或运算,例如:
0010110011010100
1010110010011010
xor
-------------------
1000000001001110
非运算,只有一个操作数,它的计算方法是把该操作数逐位求反。汇编语言中用not指令来表示非运算,例如:
1010110010011010
not
-------------------
0101001101100101
笔者犹记得,当年教的模拟电路老师的几句简单的口令:与运算,有零则零;或运算,有一则

《软件调试分析技术》学习笔记(六)

异或运算,相同则1,不同则0;非运算,零则一,一则零。
明白了机器数和运算的原理以后来看看它们在程序运算中的具体使用方法。C语言代码如下:

[cpp] view plain copy
  1. #include
  2. intmain()
  3. {
  4. inta,b,c;
  5. a=1;
  6. b=2;
  7. c=a+b;
  8. c=a-b;
  9. c=a*b;
  10. c=a/b;
  11. c=a%b;
  12. c=a&b;
  13. c=a|b;
  14. c=a^b;
  15. c=~a;
  16. return0;
  17. }


这段代码定义了三个整型变量a、b、c,给变量a赋值1,给变量b赋值2,然后分别进行加、减、乘、除、模、与、或、异或还有非运算。看反汇编代码:

[plain] view plain copy
  1. .text:00401000pushebp
  2. .text:00401001movebp,esp
  3. .text:00401003subesp,0Ch



这里抬高栈顶,用来为定义的局部变量开辟储存空间。一个整型变量占4字节,那么0x0C字节则可开辟出三个整型变量的空间,用来储存整型变量a、b、c。

[plain] view plain copy
  1. .text:00401006mov[ebp+a],1
  2. .text:0040100Dmov[ebp+b],2


这两句分别对应C语言代码a = 1;和b = 2;。用来给整型变量a、b赋值。

[plain] view plain copy
  1. .text:00401014moveax,[ebp+a]
  2. .text:00401017addeax,[ebp+b]
  3. .text:0040101Amov[ebp+c],eax

这里取变量a到eax中,然后把eax和变量b相加,最后把结果放到变量c里。

[plain] view plain copy
  1. .text:0040101Dmovecx,[ebp+a]
  2. .text:00401020subecx,[ebp+b]
  3. .text:00401023mov[ebp+c],ecx


这里取变量a到ecx中,然后把ecx和变量b相减,最后把结果放到变量c里。

[plain] view plain copy
  1. .text:00401026movedx,[ebp+a]
  2. .text:00401029imuledx,[ebp+b]
  3. .text:0040102Dmov[ebp+c],edx


这里取变量a到edx中,然后把edx和变量b相乘,最后把结果放到变量c里。

[plain] view plain copy
  1. .text:00401030moveax,[ebp+a]
  2. .text:00401033cdq
  3. .text:00401034idiv[ebp+b]
  4. .text:00401037mov[ebp+c],eax

这里取变量a到eax中,接着扩展符号位,然后除以变量b,最后把商放到变量c里。

[plain] view plain copy
  1. .text:0040103Amoveax,[ebp+a]
  2. .text:0040103Dcdq
  3. .text:0040103Eidiv[ebp+b]
  4. .text:00401041mov[ebp+c],edx


这里取变量a到eax中,接着扩展符号位,然后除以变量b,最后把余数放到变量c里,这里和上面的除法运算的代码是很像的,只有最后储存运算结果的时候不同,除法运算储存的是eax的值,而模运算储存的是edx的值。细心的读者可能注意到了,做除法运算的时候多了一条cdq指令,它是干什么的?还有,idiv指令只有一个操作数它是怎么完成除法运算的。
其实dcq指令的作用是将双字符号扩展至8字节,指的是扩展eax的符号位至edx寄存器中,也就是说,当eax小于80000000时使edx为00000000,当eax大于等于80000000时使edx为FFFFFFFF。
idiv指令是有符号除法,被除数没有的指令中标出来,它是edx:eax。当idiv指令有操作数的时候,除数是操作数;如果没有操作数,那么除数就是ecx。指令的运算结果:eax为商,edx是余数。

[plain] view plain copy
  1. .text:00401044moveax,[ebp+a]
  2. .text:00401047andeax,[ebp+b]
  3. .text:0040104Amov[ebp+c],eax


这里取变量a到ecx中,然后把ecx和变量b做与运算,最后把结果放到变量c里。

[plain] view plain copy
  1. .text:0040104Dmovecx,[ebp+a]
  2. .text:00401050orecx,[ebp+b]
  3. .text:00401053mov[ebp+c],ecx

这里取变量a到ecx中,然后把ecx和变量b做或运算,最后把结果放到变量c里。

[plain] view plain copy
  1. .text:00401056movedx,[ebp+a]
  2. .text:00401059xoredx,[ebp+b]
  3. .text:0040105Cmov[ebp+c],edx

这里取变量a到edx中,然后把edx和变量b做异或运算,最后把结果放到变量c里。

[plain] view plain copy
  1. .text:0040105Fmoveax,[ebp+a]
  2. .text:00401062noteax
  3. .text:00401064mov[ebp+c],eax

这里取变量a到eax中,然后把eax和变量b做非运算,最后把结果放到变量c里。

[plain] view plain copy
  1. text:00401067xoreax,eax
  2. .text:00401069movesp,ebp
  3. .text:0040106Bpopebp
  4. .text:0040106Cretn


你可能感兴趣的:(《软件调试分析技术》学习笔记)