在学习pwn的过程中难免会遇到形形色色的有关逆向的问题,且二进制与逆向互通性相对来说比较高,也可以说逆向是二进制的铺垫,因此,在此栏专门记录自己学习逆向的过程,争取两天一更(但愿自己不会成为鸽子)。

汇编

指令

功能 命令 功能 命令
加法 add and
减法 sub or
乘法 mul/imul not
除法 div/idiv 异或 xor
比较大小 cmp 测试 test
调用 call 返回 ret
系统调用 sysenter/syscall 中断返回 iret
系统调用返回 sysexit/sysret
压栈 push 寄存器批量入栈 pushad
出栈 pop 寄存器批量出栈 popad
普通赋值 mov 单字节赋值 movsb
双字节赋值 movsw 四字节赋值 movsd
取地址 lea

跳转类型的指令

功能 命令 功能 命令
等于转移 JE/JZ 不等于时转移 JNE/JNZ
有进位时转移 JC 无进位时转移 JNC
不溢出时转移 JNO 奇偶性为奇数时转移 JNP/JPO
符号位为"0"时转移 JNS 溢出转移 JO
奇偶性为偶数时转移 JP/JPE 符号位为"1"时转移 JS
功能 命令 功能 命令
大于转移 JA/JNBE 大于或等于转移 JAE/JNB
小于转移 JB/JNAE 小于或等于转移 JBE/JNA

以上四条,测试无符号整数运算的结果(标志C和Z).

功能 命令 功能 命令
大于转移 JG/JNLE 大于或等于转移 JGE/JNL
小于转移 JL/JNGE 小于或等于转移 JLE/JNG

以上四条,测试带符号整数运算的结果(标志S,O和Z).

寄存器

通用寄存器

8bit 16bit 32bit 64bit 功能
AL AH/AX EAX RAX 累加器(Extended Accumulator)乘除指令默认用EAX
CL CH/CX ECX RCX 循环计数器
DL DH/DX EDX RDX I/O指针
BL BH/BX EBX RBX DS段的数据指针
SP ESP RSP 堆栈指针(Extended Stack Poniter)
BP EBP RBP 用于保存当前堆栈帧地址的堆栈基指针
SI ESI RSI 字符串操作的源指针;SS段的数据指针(Extended Source Index)
DI EDI RDI 字符串操作的目标指针;ES段的数据指针(Extended Destinantion Index)
IP EIP RIP 指令指针。保存程序计数器和下一条指令的地址。

段寄存器

register 功能
CS (Code Segment)代码段寄存器
DS (Data Segment)数据段寄存器
SS (Stack Segment)堆栈段寄存器
ES(FS/GS) (Extra Segment)附加段寄存器

注意: CS,DS,SS在调试方面基本上已经失去意义

标志寄存器

运算结果标志位
FLAGS寄存器是cpu8086处理器中的一种标志寄存器,和我们常见的其他寄存器相比有如下特点

  • 每一个标志寄存器的大小都只有一个字节,(基本无法存储任何数据,除了0和1)
  • 其内容会收到cpu运行的指令的影响,在特定指令运行后,有一个或几个flag的值更具运行结果发生改变
  • 会对一些条件判断指令产生影响

FLAGS寄存器的格式如下

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF
CF(进位标志)

FLAGS的0位是CF,进位标志位
当无符号数在运算中发生进位,或者向前借位时,cpu将其置1。

  • 如0x0f + 0x01=0x10,这时就发生了进位,CF被置1。
  • 在减法中0x10-0x01=0xE,这里就发生借位(这里使用的是十六进制)

虽然加法和减法中一个看的是进位,一个看的是借位,但其实两者是一个道理,记录的都是进位。
我们不妨把减法看成一个正数加上一个负数,并换算成补码。0x01的补码是0xff,这个数加上0x10必定会发生进位,因此,CF置1。
这也解释了为什么,CF叫进位标志而不是借位标志。其实减法本质上也可以看成加法

PF(奇偶标志)

FLAGS的2位是PF,奇偶标志位。
PF的主要职责就是判断,指令执行的结果的所有位数中1的个数是奇数还是偶数,如果是偶数,置1,如果是奇数,置0。
这里的结果主要包括:加、减、乘、除运算,和逻辑运算等。规则如下

1
2
mov ax, 0x1
add ax, 0x10 ;ax 0x11, PF=1
AF(调整标志位)

FLAGS的第4位是AF标志位,调整标志位。
其主要作用是判断低半字节有没有向高半字节进位或者借位,如果有AF=1,否则AF=0。

低半字节和高半字节:
我们进行数据计算时,表示一个数的位数都是有限的,如ax寄存器位16位,而这16位又可以分为高8位和低八位,这里的高8位就是高半字节,低8位就是低半字节。

当我们用ax寄存器进行运算时,如果低8位向高8位有进位或借位的情况,AF=1,否则AF=0

ZF(零标志位)

FLAGS第6位是ZF,称为零标志位。
顾名思义,ZF标志位的作用就是判断运算结果是否为0,如果为0,ZF=1,否则ZF=0。
如下

1
2
mov al, 0x1
sub al, 0x1

al=0,zf=1
结果可以来自:加减乘除,和逻辑运算。也包括CMP运算。

SF(符号标志位)

SF,判断指令执行的结果是否为负,如果为负,SF=1,否则SF=0。
例如:

1
2
mov al, 0xF1
add al, 0x1

这里al的结果是0xF2,换算成二进制11110010b,-14,SF=1

这看起来“好像”是,当结果为负时,SF置1,但如果我们把这个运算看成是无符号原算时会怎么样?
这时al的结果是,242,但这时SF还是置1,这看起来好像是个问题?

在指令执行过程中,CPU是无法判断一个数是正数还是负数的,它所能判断的只有,结果的最高位是否为1,如果为1无论是有符号运算还是无符号运算,SF都会置1。

所以,最终的结果就是,当我们把运算看成有符号运算时SF有意义,而当成无符号运算时SF没有意义

当然,如果说一定要把两者区分,那我们该怎么做呢,我的答案是,没有必要,因为在进行无符号原算时,SF根本用不到,只有在进行有符号运算时,SF才可能会用到。

OF溢出标志位

顾名思义,OF标志位判断的就是,运算结果是否溢出。如果溢出OF=1,否则OF=0。
OF的判断公式为:OF=Cn 异或 Cn-1
Cn:最高有效位进位
Cn-1:此稿有效位进位

如何定义溢出

1
2
mov al, 0x70
add al,al

从二进制的角度来看,01110000 + 01110000 = 11100000,两个整数相加,变成了一个负数,这就是溢出。
最高有效位没有进位,即Cn=0,而次高有效位进位,即Cn-1=1,因此,CF=1。

这里CPU用于判断,有符号运算的结果是否溢出的方法非常巧妙。不止两正数相加为负的情况,两负数相加为正的情况也同样可以判断出来。
如:

1
2
mov al, 0x90
add al, al

10010000+10010000=0010 0000,两个负数计算后变成了一个正数,溢出
Cn=1,Cn-1=0,CF=0,和实际情况完美对应。

1
2
mov al, 1h
sub al, 1h

状态控制标志位

TF(跟踪标志位)

flag的第8位是TF,跟踪标志位用于标识CPU是否允许单步中断,以进行程序调试。TF=0时,8086CPU处于正常状态;TF=1时,8086CPU处于单步状态,每执行一条指令就自动产生一次单步中断。

8086的debug功能依赖于8086CPU的单步调试功能。

IF(中断允许标志位)

flags的第9位是IF,中断允许标志位
IF置为0,禁止其他的可屏蔽中断;如果允许处理可屏蔽中断,则将IF置为1。

这是一个可以有用户控制的标志位,控制指令没有操作数

1
2
STI			;将IF设置为1,允许可屏蔽中断。
CLI ;将IF设置为0,禁止可屏蔽中断。
DF(方向标志位)

flags的第10为,是方向标志位。可以控制数据读取的方向
这个标志位主要用于配合movsb,movsw,进行批量数据传输。

和IF标志位一样,其值可以由用户控制

1
2
cld		;OF置0,进行正向传递,从低地址向高地址读
std ;OF置1,进行反向传递,从高地址向低地址读

使用方式如下

1
2
3
4
5
6
;显示Label Offset:
cld
mov si, mytext ;ds:di,起始地址
mov di, 0 ;es:di,目标地址
mov cx, 13 ;循环的次数
rep movsw ;movsw和movsb指令会自动使用es作为目的数据段,不用可以拼接

PE文件

全称Portable Executable
是基于Unix的coff文件发展来的,是可移植的执⾏体,是Windows平台组织程序代码的⼀种⽂件格式。常⻅的exe、dll、
sys、ocx、com都属于PE⽂件。

具体详解

DOS头

DOS头为0x4D 5A也就是英文大写MZ

一般可以作为搜索内存PE文件的特征

PE头(NT头)

文件开头为0x50 45 00 00也就是ASCII字符的PE

导入导出表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef struct  _ IMAGE_OPTIONAL_ HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorlinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD Size0fUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD Base0fData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_ DATA_ DIRECTORY DataDirectory [IMAGE_ NUMBEROF_ DIRECTORY_ ENTRIES];
} IMAGE_ _OPTIONAL _HEADER32,*PIMAGE_ _OPTIONAL_ HEADER32;

SectionAlignment
内存中加载的节的对齐方式(以字节为单位)。 此值必须大于或
等于 FileAlignment 成员。 默认值是系统的页大小。

FileAlignment
图像文件中各部分的原始数据的对齐方式(以字节为单位)。 该
值应为介于 512 和 64K 之间的幂 ((含) )。 默认值为 512。 如
果 SectionAlignment 成员小于系统页面大小,则此成员必须与
SectionAlignment 相同。