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

来源:互联网 发布:免费幼儿识字软件 编辑:程序博客网 时间:2024/05/16 05:44

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

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

《软件调试分析技术》是好友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 headerDOS stubPE headerSection tableSection 1Section 2Section ...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 plaincopy
  1. #include <stdio.h>   
  2. #include <stdlib.h>   
  3. int a;   
  4. int main()   
  5. {   
  6.   int b;   
  7.   int *c;   
  8.   c = (int*)malloc(sizeof(int));   
  9.   a = 1;   
  10.   b = 2;   
  11.   *c = 3;   
  12.   free(c);   
  13.   return 0;   
  14. }   


 

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

 

用OD加载

[plain] view plaincopy
  1. .text:00401000    push    ebp   
  2. .text:00401001    mov     ebp, esp   
  3. .text:00401003    sub     esp, 8   

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

[plain] view plaincopy
  1. .text:00401006    push    4               ; Size   
  2. .text:00401008    call    ds:__imp__malloc   
  3. .text:0040100E    add     esp, 4   
  4. .text:00401011    mov     [ebp+c], eax  

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

[plain] view plaincopy
  1. .text:00401014    mov     ?a@@3HA, 1      ; int a   
  2. .text:0040101E    mov     [ebp+b], 2   
  3. .text:00401025    mov     eax, [ebp+c]   
  4. .text:00401028    mov     dword ptr [eax], 3   


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

[plain] view plaincopy
  1. .text:0040102E    mov     ecx, [ebp+c]   
  2. .text:00401031    push    ecx             ; Memory   
  3. .text:00401032    call    ds:__imp__free   
  4. .text:00401038    add     esp, 4   
  5. .text:0040103B    xor     eax, eax   
  6. .text:0040103D    mov     esp, ebp   
  7. .text:0040103F    pop     ebp   
这里调用函数free()来释放刚才由函数malloc()申请到的堆空间地址。



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

[plain] view plaincopy
  1. 一起看看数组在程序中的使用。C语言代码:  
[cpp] view plaincopy
  1. #include <stdio.h>   
  2. int main()   
  3. {   
  4.   int a[2];   
  5.   a[1] = 0;   
  6.   a[a[1]] = 1;   
  7.   return 0;   
  8. }   


 

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

载入OD看看反汇编代码:

 

[plain] view plaincopy
  1. .text:00401000    push    ebp   
  2. .text:00401001    mov     ebp, esp   
  3. .text:00401003    sub     esp, 8   


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

 

[plain] view plaincopy
  1. text:00401006    mov     [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 plaincopy
  1. text:0040100D    mov     eax, [ebp+a+1*4]   
  2. .text:00401010    mov     [ebp+eax*4+a], 1   

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

[plain] view plaincopy
  1. .text:00401018    xor     eax, eax   
  2. .text:0040101A    mov     esp, ebp   
  3. .text:0040101C    pop     ebp   
  4. .text:0040101D    retn