逆向学习笔记(一)

本人从开始学习逆向已经有一段时间了,感觉逆向是一门很深的学问,需要长时间的积累和足够的耐心和细心。
下面是我个人的一些总结,还请大家指点。学习逆向是需要一定的汇编基础,学习汇编就想是学习一门外语,它的指令就像是单词一样,只有理解了这些单词的意思才可以理解汇编的代码的含义,由于汇编器/反汇编器的不同,现在x86汇编代码主要分为Intel和AT&T汇编,这两者在语法方面存在一定的差异,建议先从Intel汇编开始,它主要是在PC端,而且参考的资料较多。(本文部分内容来自网络)

一、基础的单位和字节序的介绍

bit 位(指的是 0 or 1)
byte 字节 1byte = 8 bit
word 字 1 word = 2 byte
dword (即double word)
1KB=1024B
1MB=1024KB
1GB=1024MB
1TB=1024GB

字节序:

大端序(Big endain):内存地址低位储存数据的高位,

小端序(Little endain):内存地址高位储存数据的高位(x86是基于Intel8086处理器的小端体系结构)

这里写图片描述
MZ 对应的是5A 4D,这里是十六进制,每两位代表一个字节。

二、寄存器

2、寄存器(Register):CPU 内部用来存放数据的一些小型储存区域,暂存指令、数据和地址。

一开始寄存器是8位的(也就是1个字节),到了8086的时期变成16位(像ax,bx),再到80386的时期发展为32位(eax,ebx等),再到现在主流的64位cpu采用的是两个32位的寄存器一起使用。它的规律可见下图

这里写图片描述

扫描二维码关注公众号,回复: 1640963 查看本文章

由这张图,我们可以发现寄存器是兼容的从 * al–>ax–>eax*

寄存器有分以下几类:

2.1、通用寄存器:(寄存器前面的E,代表extended,扩展)

在windows保护模式下的x86体系结构有8个通用寄存器

EBP,ESI,EDI,ESP:主要用作保存内存地址的指针

EAX,EBX,ECX,EDX(0-FFFFFFFF):主要用在算数运算指令中,常用来保存常量与变量的值。

部分通用寄存器的用途:

ECX:循环计数器
ESI:字符串/内存操作的源`
EDI:字符串/内存操作的目标
EBP:栈帧基准
ESP:(栈顶指针)指示栈区域的栈顶地址,指向当前进程的栈空间地址`。

这里写图片描述

EIP(instuction pointer):扩展的指令指针寄存器,总是指向下一条要被执行的指寄存针器。

2.1、标志位寄存器(Flag Register)

(IA-32)事实上所有的标志位归并与一个32位的标志位寄存器,也就是说有32个不同的标志位。
每个标志位都有两个属性:置1或置0

这里写图片描述

在逆向分析的过程中,你真正需要关心的标志位只有三个,也就是cmp指令能修改的那三个:Z/O/C。
因为跳转指令是否成立是于这三个标志寄存器的值有关的

Z标志位(zero flag),这个标志位是最常用的,运算结果为0时候,Z标志位置1,否则置0。
O标志位(溢出标志 overflow flag),在运行过程中,有符号整数溢出时,OF置为1。
C标志位(进位标志 carry flag),无符号整数溢出时,CF置为1,记录运算时从最高有效位产生的进位值。例如执行加法指令时,最高有效位有进位时置1,否则置0。

通常在选择、循环等语句中需要用到cmp和text指令去改变标志寄存器的值,以此来判断是否跳转

2.3、段寄存器(Segment)

CPU访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间。我们将这个唯一的地址称为物理地址。

8086CPU(外部)有20位地址总线,可传送20位地址,寻址能力为1M。8086内部为16位结构,它只能传送16位的地址,表现出的寻址能力却只有64KB。

8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址
地址加法器合成物理地址的方法是:

     **物理地址=段地址×16+偏移地址**
     段地址和偏移地址都是16位的,'段地址 x 16'即向左移了4位(2^4).

一个数据的X进制形式左移1位,相当于乘以X。

由6种寄存器组成,分别为

CS(code segment)
SS(stack segment)
DS(data segment)
ES(extra segment)
FS(data segment)
GS( data segment)`

ES,FS,GS存放程序使用的附加数据段的段基址

三、基础的指令(汇编指令不区分大小写)

几个概念
立即数:以常量的形式出现在指令中,只能作为源操作数。
寄存器数:将数据存放在寄存器中,指令直接使用寄存器名。
内存操作数:将数据放在内存中,指令中使用内存地址。、如:[BX]

3.1算数运算指令

ADD DEST,SRC    //右加到左
SUB DEST,SRC    //DEST = DEST - SRC
INC DEST       //加一
DEC DEST       //减一
NOG          //NULL
imul src      //带符号乘
idiv src      //带符号除
mul src     乘
div src   除
dec dest   自减
inc dest   自加
int 中断指令 

指令有很多,不用死记,用到时查就好了

3.2.逻辑运算和关系运算指令

AND dest,src      //按位与运算,后值赋给dest
OR   dest,src     //按位或运算
XOR   dest,src    //按位异或
NOT   dest        //按位取反
text dest, src    //这个指令和and指令差不多,对两个操作数进行按位的‘与’运算,但在逻辑与操作后,对两个操作数的内容均不进行修改,仅对标志位重新置位。
CMP  dest,src       //  和SUB指令相似,只是不将dest-src的值放入dest    

3.3 基础的指令

 CALL  XXXX               //调用XXXX地址处的函数
 JMP   XXXX               // 跳转到XXXX地址处  
 PUSH   XXXX              //保存XXXX到栈(入栈)
 POP   XXXX               //弹出XXXX地址(出栈)
 RETN  XXXX               //跳转到栈中保持的地址础的结构.

条件跳转指令有很多,通常情况下,都与CMP和TEXT匹配出现,但条件跳转指令是看标志位的值的,具体的条件跳转指令表可以自行百度

操作符 offert :取得标号的偏移地址

mov ax,offert start ;相当于 mov ax,0

x = [abc] 是指取abc 所在的地址的值,赋给x;

四、基础的结构

4.1、栈(Stack)

(1)暂时保存函数内的局部变量(2)调用函数时传递参数(3)保存函数返回后的地址

          FILO,由下向上扩展
          向栈压入数据,栈顶指针减小,向低地址移动

这里写图片描述

4.2、栈帧(Stack Frame)技术:(每个函数的每次调用,都有它自己独立的一个栈帧)

用EBP表示栈区域的基地址,函数被调用是保存ESP的值,函数返回时再把值返回ESP,保证栈不会崩溃

栈帧的基本结构如下

PUSH EBP     //函数开始 (使用EBP前先把已有值保存到栈中)

MOV  EBP,ESP    //保存当前ESP到EBP中


...           //这是函数体部分

…          //ESP的变化与EBP无关,可以安全访问函数的局部变量、参数

MOV  ESP, EBP     //将函数的起始地址返回到ESP中

POP  EBP           //函数返回前弹出保存在栈中的EBP值

RETN          //函数终止

一段简单的代码/汇编代码

1:    #include<stdio.h>
2:    long add(long a,long b)
3:    {

00401020   push ebp                     //栈帧开始
00401021   mov ebp,esp
00401023   sub esp,48h
00401026   push ebx
00401027   push esi
00401028   push edi
00401029   lea edi,[ebp-48h]           //取出此函数可用栈空间首地址放入edi
0040102C   mov ecx,12h
00401031   mov eax,0CCCCCCCCh         //局部变量初始化
00401036   rep stos dword ptr [edi]     //根据ecx的值,将eax中的内容,以dw为单位写到edi指向的内存中

4:long x = a,y = b;

00401038   mov eax,dword ptr [ebp+8]
0040103B   mov dword ptr [ebp-4],eax
0040103E   mov ecx,dword ptr [ebp+0Ch]
00401041   mov dword ptr [ebp-8],ecx

5:return (x+y);

00401044   mov eax,dword ptr [ebp-4]
00401047   add eax,dword ptr [ebp-8]     //x+y

6:}
0040104A   pop edi
0040104B   pop esi
0040104C   pop ebx
0040104D   mov esp,ebp
0040104F   pop ebp
00401050   ret
/*
...
...
*/
7:int main()
8:{

00401060   push ebp                    //栈帧
00401061   mov ebp,esp
00401063   sub esp,48h
00401066   push ebx
00401067   push esi
00401068   push edi
00401069   lea edi,[ebp-48h]
0040106C   mov ecx,12h
00401071   mov eax,0CCCCCCCCh
00401076   rep stos dword ptr [edi]

9:long a = 1,b = 2;

00401078   mov dword ptr [ebp-4],1    
0040107F   mov dword ptr [ebp-8],2

10:   printf("%d\n",add(a,b));

00401086   mov eax,dword ptr [ebp-8]
00401089   push eax                        //eax = 2 = [ebp-8]
0040108A   mov ecx,dword ptr [ebp-4]
0040108D   push ecx                        //ecx = 1 = [ebp-4]
0040108E   call @ILT+0(add) (00401005)     //调用add()函数
00401093   add esp,8
00401096   push eax
00401097   push offset string "%d\n" (0042201c)
0040109C   call printf (004010d0)
004010A1   add esp,8

11:   return 0;

004010A4   xor eax,eax                   //将eax清零
12:
13:   }
004010A6   pop  edi
004010A7   pop  esi
004010A8   pop  ebx
004010A9   add esp,48h                    //降低栈顶esp,释放局部变量空间
004010AC   cmp ebp,esp                    //检测栈平衡,
004010AE   call  __chkesp (00401150)      //进入栈平衡错误检测函数
004010B3   mov esp,ebp
004010B5   pop ebp
004010B6   ret

od中的代码

栈计算机中常见的结构,但是也存在着一些问题,如栈的溢出,常常会被人利用。

断点:设置断点后,调试运行到断点处将会暂停

返回地址:执行CALL命令进入被调用的函数之前,CPU会先把函数的返回地址压入栈

XOR:两个相同的值进行XOR运算结果为0,常用于寄存器的初始化操作。(相同的值连续执行2次XOR运算即变回原值)

以上所述的只是一些在逆向的过程中常见的部分基础内容,还有许多不足,在开始学习逆向的时候是较困难的,由于有大量陌生的东西,但是只要去多接触,是可以掌握的,就拿基础的汇编的一些语句来说,根本不用去死记,熟悉了就好了,就我自己来说就是平时多去写程序,先看汇编代码,然后在用工具去逆向。

猜你喜欢

转载自blog.csdn.net/life_hes_az/article/details/78531542