Windows逆向安全(一)之基础知识(一)

前言

逆向是一种新型的思维模式也是软件开发领域中极为重要的技术,涵盖各种维度去深挖软件架构的本质和操作系统原理,学习逆向后可以在各领域中发挥至关重要的作用,其中包括黑灰色,安全开发,客户端安全,物联网,车联网,游戏安全,红队免杀等行业中绘制出更高的闪光点。

C与汇编的关系

基本语法的学习:

各种进制的转换和原理

  1. 十进制的定义:由十个符号组成,分别是0 1 2 3 4 5 6 7 8 9 逢十进一
  2. 九进制的定义:由九个符号组成,分别是0 1 2 3 4 5 6 7 8 逢九进一
  3. 十六进制的定义:由十六个符号组成,分别是0 1 2 3 4 5 6 7 8 9 A B C D E F
  4. N进制的定义:由N个符号组成 逢N进一

在这里插入图片描述在这里插入图片描述Windows逆向安全(一)之基础知识(一)_第1张图片Windows逆向安全(一)之基础知识(一)_第2张图片

数据类型与逻辑运算

Windows逆向安全(一)之基础知识(一)_第3张图片

在计算机中,由于硬件的制约,数据是有长度限制的,超过数据宽度的数据会被丢弃

同一个数据,表示无符号数和有符号数则其含义不同

  1. 无符号数:正数
  2. 有符号数:正数、负数

例:

  • 当数据宽度为4时,即数据只能存储4位2进制位0000~1111

无符号数:

  1. 数据:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  2. 十六进制:0 1 2 3 4 5 6 7 8 9 A B C D E F
  3. 二进制:0000000100100011010001010110011110001001101010111100110111101111

有符号数:

正数:

  1. 数据:0 1 2 3 4 5 6 7
  2. 十六进制:0 1 2 3 4 5 6 7
  3. 二进制:00001001000110100010101100111

负数:

  1. 数据:-1 -2 -3 -4 -5 -6 -7 -8
    十六进制:F E D C B A 9 8
    二进制: 11111110110111001011101010011000
  2. 可以发现当数据为1011,把数据看作无符号数时,数据表示为B
  3. 把数据看作有符号数时,数据表示为-5
  4. 无符号数的表示范围为0~2^4-1即0~15\\ 有符号数的表示范围为-23~23-1即-8~7

常见的数据类型(重要)

  • BYTE 字节 8BIT 1字节
  • WORD 字 16BIT 2字节
  • DWORD 双字 32BIT 4字节

常见的运算符类型(重要):

或运算(or |):
两个数只要有一个为1则结果为1

与运算(and &):
两个数都是1结果才为1

异或运算(xor ^):
两个数相同为0, 不同为1

非运算(not !):
两个数取反 1是0, 0是1

CPU如何计算2+3?

X:0010

Y:0011

先异或

Windows逆向安全(一)之基础知识(一)_第4张图片
R:0001

异或完以后要判断是否运算结束

将两个数进行与运算 然后左移一位

Windows逆向安全(一)之基础知识(一)_第5张图片
0010<<1 ==0100

如果结果全为0,结果则为我们所要的结果

否则,把上面异或得到的值赋值到X

把左移后的结果赋值到Y

X:0001

Y:0100

重复操作

先异或
Windows逆向安全(一)之基础知识(一)_第6张图片
R:0101

再将两个数进行与运算 然后左移一位

Windows逆向安全(一)之基础知识(一)_第7张图片
左移完结果全是0,结果则为我们所要的

最终结果为0101=5

CPU如何计算2-3?

X:0010

Y:1101

先异或

Windows逆向安全(一)之基础知识(一)_第8张图片
R:1111

将两个数进行与运算 然后左移一位

Windows逆向安全(一)之基础知识(一)_第9张图片

0000<<1=0000

如果结果全为0,结果则为我们所要的结果

最终结果为1111 = -1

如何取某个值的第N位的数值

与操作

如我们想要查看23h这个十六进制数的第3位则可以进行如下运算:

先将23h转化为二进制:0010 0011

Windows逆向安全(一)之基础知识(一)_第10张图片

最简单的加密算法:

通过异或加密数据 再次异或后则解密数据

要加密的数据:2021:0010 0000 0010 0001

密钥:54:0101 0100

Windows逆向安全(一)之基础知识(一)_第11张图片
高位:0111 0100 = 74
在这里插入图片描述
低位:0111 0101 = 75

原本的2021加密成了7475

然后再次进行异或操作进行解密:

Windows逆向安全(一)之基础知识(一)_第12张图片

高位:0010 0000 = 20

Windows逆向安全(一)之基础知识(一)_第13张图片

低位:0010 0100 = 21

解密回了原来的数值2021

通用寄存器和内存读写

32位通用寄存器的指定用途如下:

在这里插入图片描述在这里插入图片描述Windows逆向安全(一)之基础知识(一)_第14张图片堆栈相关汇编指令:

在这里插入图片描述MOV指令

MOV 的语法:

MOV  r/m8,r8
MOV  r/m16,r16
MOV  r/m32,r32
MOV  r8,r/m8
MOV  r16,r/m16
MOV  r32,r/m32
MOV r8,  imm8
MOV r16,  imm16
MOV r32,  imm32

MOV 目标操作数,源操作数

作用:拷贝源操作数到目标操作数

源操作数可以是立即数、通用寄存器、段寄存器、或者内存单元
目标操作数可以是通用寄存器、段寄存器或者内存单元
操作数的宽度必须一样
源操作数和目标操作数不能同时为内存单元

ADD指令

ADD 的语法:

ADD r/m8,  imm8
ADD  r/m16,imm16
ADD  r/m32,imm32
ADD r/m16,  imm8
ADD r/m32,  imm8
ADD r/m8,  r8
ADD r/m16,  r16
ADD r/m32,  r32
ADD r8,  r/m8
ADD r16,  r/m16
ADD r32,  r/m32

ADD 目标操作数,源操作数

作用:将源操作数加到目标操作数上

SUB指令

SUB 的语法:

SUB r/m8, imm8
SUB r/m16,imm16
SUB r/m32,imm32
SUB r/m16, imm8
SUB r/m32, imm8
SUB r/m8, r8
SUB r/m16, r16
SUB r/m32, r32
SUB r8, r/m8
SUB r16, r/m16
SUB r32, r/m32

SUB 目标操作数,源操作数

作用:将源操作数减到目标操作数上

AND指令

AND 的语法:

AND r/m8, imm8
AND r/m16,imm16
AND r/m32,imm32
AND r/m16, imm8
AND r/m32, imm8
AND r/m8, r8
AND r/m16, r16
AND r/m32, r32
AND r8, r/m8
AND r16, r/m16
AND r32, r/m32

AND 目标操作数,源操作数

作用:将源操作数与目标操作数与运算后将结果保存到目标操作数中

OR指令

OR 的语法:

OR r/m8, imm8
OR r/m16,imm16
OR r/m32,imm32
OR r/m16, imm8
OR r/m8, r8
OR r/m16, r16
OR r/m32, r32
OR r8, r/m8
OR r16, r/m16
OR r32, r/m32

OR 目标操作数,源操作数

作用:将源操作数与目标操作数或运算后将结果保存到目标操作数中

XOR指令

XOR 的语法:

XOR r/m8, imm8
XOR r/m16,imm16
XOR r/m32,imm32
XOR r/m16, imm8
XOR r/m8, r8
XOR r/m32, r32
XOR r8, r/m8
XOR r16, r/m16
XOR r32, r/m32

XOR 目标操作数,源操作数

作用:将源操作数与目标操作数异或运算后将结果保存到目标操作数中

NOT指令

NOT 的语法:

NOT r/m8

NOT r/m16

NOT r/m32

NOT 操作数

作用:取反

LEA指令

Windows逆向安全(一)之基础知识(一)_第15张图片lea:Load Effective Address,即装入有效地址的意思,它的操作数就是地址

lea r32,dword ptr ds:[内存编号(地址)]

将内存地址赋值给32位通用寄存器

lea是传址,mov是传值,注意区别

堆栈结构

在这里插入图片描述
Windows逆向安全(一)之基础知识(一)_第16张图片

Windows逆向安全(一)之基础知识(一)_第17张图片

在这里插入图片描述

Windows分配栈时 是从高地址往低地址分配:

  1. MOV EBX,0x13FFDC BASE
  2. MOV EDX,0x13FFDC TOP

栈底和栈顶可以是两个任意的寄存器(Windows采用的是EBP和ESP)

刚开始堆栈为空,栈顶和栈底相同

在这里插入图片描述先将数据压入后再修改栈顶
数据压入

MOV DWORD PTR DS:[EDX-4],0xAAAAAAAA
修改栈顶

SUB EDX,4
先修改栈顶后再将数据压入
修改栈顶

LEA EDX,DWORD PTR DS:[EDX-4] (和上面的SUB一样)
数据压入

MOV DOWRD PTR DS:[EDX],0xAAAAAAAA

在这里插入图片描述
栈顶加偏移读取

MOV ESI,DWORD PTR DS:[EBX-8]
栈底加偏移读取

MOV EDI,DWORD PTR DS:[EDX+4]
在这里插入图片描述
先取出数据再修改栈顶
取出数据

MOV EAX,DOWRD PTR DS:[EDX]
修改栈顶

ADD EDX,4
先修改栈顶再取出数据
修改栈顶

LEA EDX,DWORD PTR DS:[EDX+4]
取出数据

MOV EAX,DOWRD PTR DS:[EDX-4]

在这里插入图片描述在这里插入图片描述

入栈和出栈操作也有对应的指令:

上面我们自己模拟的两个用作栈顶和栈底的寄存器在WINDOWS中分别对应ESP和EBP

并且前面我们自己模拟的入栈和出栈操作也有对应的指令:PUSH 和 POP

就是封装了压入数据和修改栈顶的操作

  • PUSH 和 POP
  • push xxx将 xxx的数据压入堆栈
  • pop xxx将栈顶的数据存储到xxx中

PUSH指令:

PUSH r32

PUSH r16

PUSH m16

PUSH m32

PUSH imm8/imm16/imm32

所有的push都是将esp-4?

压入的数据的数据宽度:

当push的是立即数将esp-4

当push r32如push eax时将esp-4

当push dword ptr ds:[12FFDA]即压入双字内存地址中的数据时将esp-4

当push word ptr ds:[12FFDA]即压入字内存地址中的数据时将esp-2

当push ax,即r16 ,16位通用寄存器时,esp-2

push 不允许压入数据宽度为8的数据 如ah al 和byte ptr ds:[内存编号]

POP指令

POP r32

POP r16

POP m16

POP m32

PUSHAD和POPAD指令

将所有的32位通用寄存器压入堆栈,方便后面随意使用寄存器,用于保护现场

与POPAD对应

PUSHFD和POPFD指令

然后将32位标志寄存器EFLAGS压入堆栈

与POPAD对应

其它相关指令

pusha:将所有的16位通用寄存器压入堆栈

popa:将所有的16位通用寄存器取出堆栈

pushf::将的16位标志寄存器EFLAGS压入堆栈

popf:将16位标志寄存器EFLAGS取出堆栈

栈底和栈顶原理:

  1. 控制栈顶和栈底分别为两个固定的寄存器(EBP 基址指针寄存器 和 ESP 堆栈指针寄存器)
  2. 刚开始堆栈为空,栈顶和栈底相同

标志寄存器

EFLAGS寄存器

Windows逆向安全(一)之基础知识(一)_第18张图片

进位标志CF(Carry Flag)

如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0

例子:

MOV AL,0xFF

ADD AL,1
  • 0x80+0x40

加黑的为最高位

0x80:0 1000 0000

0x40:0 0100 0000

Windows逆向安全(一)之基础知识(一)_第19张图片
结果为1100 0000 最高位并没有发生变化,于是CF位为0

  • 0x80-0x40
    Windows逆向安全(一)之基础知识(一)_第20张图片
    注意这里借位的位是1000 0000中的加黑部分

而非0 1000 0000这里的最高位

结果为0100 0000 最高位并没有发生变化,于是CF位为0

  • 0x80-0x81

0x80:1000 0000

0x81:1000 0001

Windows逆向安全(一)之基础知识(一)_第21张图片
结果为1111 1111= -1,最高位被借位,于是CF位为1

奇偶标志PF(Parity Flag)

奇偶标志PF用于反映运算结果中最低有效字节中“1”的个数的奇偶性

如果“1”的个数为偶数,则PF的值为1,否则其值为0。

指令指令执行后AL的结果PFMOV AL,300111ADD AL,301101ADD AL,210000

例:

MOV AX,803
ADD AX,1

0x803: 0000 1000 0000 0011

执行结果

0x804: 0000 1000 0000 0100 总共2个1 ,PF应为1,但实际运行结果PF为0

因为PF是根据最低有效字节来看,即804后面04的这部分

04: 0000 0100 总共1个1,所以PF为0

辅助进位标志AF(Auxiliary Carry Flag)

在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:

  • 在字操作时,发生低字节向高字节进位或借位时
  • 在字节操作时,发生低4位向高4位进位或借位时

AF与数据宽度相关

32位时 FFFF F FFF

16位时 FF F F

8位时 F F

加黑的字体为AF标志位判断的位置,如果该位置要向前进位则AF为1,否则为0,和CF相似,不过判断的位置不同

32位例:

MOV EAX,55EEFFFF

ADD EAX,2

16位例:

MOV AX,5EFE

ADD AX,2

8位例:

MOV AL,4E

ADD AL,2

零标志ZF(Zero Flag)

零标志ZF用来反映运算结果是否为0

如果运算结果为0,则其值为1,否则其值为0

作用:在判断运算结果是否为0时,可使用此标志位

例子:

XOR EAX,EAX

通过xor将eax清零,会改变zf标志位为1

MOV EAX,0

通过MOV将EAX赋值为0,非运算,不改变zf标志位

符号标志SF(Sign Flag)

符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同

例子:

MOV AL,7F
ADD AL,2

溢出标志OF(Overflow Flag)

溢出标志OF用于反映有符号数加减运算所得结果是否溢出

注意与CF区分!!!

最高位进位与溢出的区别:

进位标志表示无符号数运算结果是否超出范围.

溢出标志表示有符号数运算结果是否超出范围.

溢出主要是给有符号运算使用的,在有符号的运算中,有如下的规律:

  • 正 + 正 = 正 如果结果是负数,则说明有溢出
  • 负 + 负 = 负 如果结果是正数,则说明有溢出
  • 正 + 负 永远都不会有溢出

无符号、有符号都不溢出例

MOV AL,8
ADD AL,8

AL的数据宽度为8,即

无符号数范围为0~FF即0~255

8+8=16在0~255内 不溢出

有符号数的范围为

正数:0~7F 即0~127

负数:80~FF 即 -128~0

8+8=16 在0~127内 两正数相加结果仍为正数,不溢出

无符号溢出、有符号不溢出例

MOV AL,0FF
ADD AL,2

无符号数时

FF+2=255+2=257 在0~255外,溢出

有符号数时

FF+2=-1+2=1

正 + 负 永远都不会有溢出

无符号不溢出、有符号溢出例

MOV AL,7F
ADD AL,2

无符号数时

7F+2=127+2=129 在0~255内 不溢出

有符号数时

7F+2=0x81在80~FF (负数范围)内,两正数相加结果为负数,溢出

无符号、有符号都溢出

MOV AL,0FE
ADD AL,80

无符号数时

FE+2=254+2=256=0x100 在0~255外 溢出

有符号数时

FE+2=0x100在0~FF外,溢出

CPU如何计算OF位

首先引入两个概念:

  • 符号位有进位
  • 最高有效数值位向符号位产生的进位

对于一个有符号数:如0x80和0xC0

符号位有进位

0x80:1 000 0000

0xC0:1 100 0000

最高有效数值位向符号位产生的进位

0x80:1 0 00 0000

0xC0:1 1 00 0000

接下来看一组汇编指令

MOV AL,80
ADD AL,0C0

就是运算0x80+0xc0

0x80:1 0 00 0000

0xC0:1 1 00 0000

符号位1+1有产生进位,于是符号位有进位为1

最高有效数值位向符号位产生的进位0+1没有产生进位,于是最高有效数值位向符号位产生的进位为0

OF = 符号位有进位 xor 最高有效数值位向符号位产生的进位

OF = 1 xor 0 = 1 所以此时OF=1

方向标志DF(Direction Flag)

DF:方向标志位

DF=1时串操作为减地址方式 DF=0为增地址方式

下面的MOVS指令有说明DF的具体应用

在这里插入图片描述

ADC指令:带进位加法

格式:ADC R/M,R/M/IMM 两边不能同时为内存 数据宽度要一样

例:

mov ax,2
mov bx,1
手动修改CF为1
adc ax,1
执行后,(ax)=4.adc执行时,相当于计算:(ax)+1+CF=2+1+1=4

计算结果为4,原本1+2=3,但是现在变成了4,注意与ADD的区别就在于进位

SBB指令:带借位减法

格式:SBB R/M,R/M/IMM 两边不能同时为内存 数据宽度要一样

MOV AL,4
MOV CL,2
手动修改CF为1
SBB AL,CL
执行后,(al)=1,sbb执行时,相当于计算:(al)-2-CF=4-2-1=1

计算结果为1,原本4-2=2,但是现在变成了1,注意与SUB的区别就在于进位

XCHG指令:交换数据

格式:XCHG R/M,R/M 两边不能同时为内存 数据宽度要一样

XCHG AL,CL
XCHG DWORD PTR DS:[12FFC4],EAX
XCHG BYTE PTR DS:[12FFC4],AL

例:

MOV AL,1
MOV CL,2
XCHG AL,CL

执行前:AL=1 CL=2

执行后:AL=2 CL=1

MOVS指令:移动数据 内存-内存

BYTE/WORD/DWORD

MOVS指令常用于复制字符串

MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]        简写为:MOVSB
MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]        简写为:MOVSW
MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]        简写为:MOVSD

例:

MOV EDI,12FFD8
MOV ESI,12FFD0
MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

执行后,EDI内存里的值被修改为ESI内存里的值,且EDI和ESI各加4

为什么各加4?

和DOWRD数据宽度相关,如果为WORD 则各加2

为什么执行完是加而不是减?

由DF(Direction Flag)方向标志位决定,当DF位为1时为减,当DF位为0时,则为加

STOS指令
将Al/AX/EAX的值存储到[EDI]指定的内存单元,和数据宽度相关

STOS BYTE PTR ES:[EDI]                将AL存储到[EDI]
STOS WORD PTR ES:[EDI]                将AX存储到[EDI]
STOS DWORD PTR ES:[EDI]                将EAX存储到[EDI]

注意这里使用的是ES: 之前写的都是DS:

当后面为[EDI]时要使用ES: 这和后面要学的段寄存器有关,先记住

存储完数据后EDI地址的变化方向也受DF标志控制,1减0增

REP指令

按计数寄存器 (ECX) 中指定的次数重复执行指令

MOV ECX,10
REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]        也可以写成REP MOVSD

这里的10为十六进制,也就是0x10=16

代码将会重复执行16次,会不会往同一个地方覆盖?

不会,因为每执行一次EDI和ESI都会变化4,变化方向由DF决定

跳转指令

JCC指令

cc 代表 condition code(状态码)

Jcc不是单个指令,它只是描述了跳转之前检查条件代码的跳转助记符

例如JNE,在跳转之前检查条件代码

典型的情况是进行比较(设置CC),然后使用跳转助记符之一

CMP EAX,0

JNE XXXXX

条件代码也可以用AND、OR、XOR、加法、减法(当然也可以是CMP)等指令来设置

JCC指令用于改变EIP(CPU要读取的指令地址)

JMP指令

JMP指令:修改EIP的值

JMP指令只影响了EIP,不影响堆栈和其它通用寄存器

JMP 寄存器/立即数 相当于 MOV EIP,寄存器/立即数

CALL指令

CALL指令和JMP指令都会修改EIP的值

但CALL指令会将返回地址(CALL指令的下一条指令地址)压入堆栈

因此也会引起esp的变化

RET指令

call调用跳转后执行完相关代码完要返回到call的下一条指令时使用ret指令

ret指令相当于pop eip

比较指令

CMP指令

指令格式:CMP R/M,R/M/IMM

CMP指令只改变标志寄存器的值

该指令是比较两个操作数,实际上,它相当于SUB指令,但是相减的结果并不保存到第一个操作数中

只是根据相减的结果来改变ZF零标志位的,当两个操作数相等的时候,零标志位置1

例:

MOV EAX,100
MOV EBX,200
CMP EAX,EBX
CMP AX,WORD PTR DS:[405000]
CMP AL,BYTE PTR DS:[405000]
CMP EAX,DWORD PTR DS:[405000]

TEST指令

指令格式:TEST R/M,R/M/IMM

该指令在一定程度上和CMP指令时类似的,两个数值进行与操作,结果不保存,但是会改变相应标志位

与的操作表项如下:

在这里插入图片描述

可以看到只要有任一操作数为0时,结果就为0

常见用法:用这个指令,可以确定某寄存器是否等于0

只有当eax=0时 eax and eax才会是0

所以

TEST EAX,EAX

观察ZF(零标志位)就可以判断EAX是否为0

JCC指令表

首先要明确一点,所有的判断跳转指令都是根据标志位来进行判断的

JCC指令也只影响EIP

Windows逆向安全(一)之基础知识(一)_第22张图片

堆栈图

首先给定一段反汇编代码,分析该段代码的堆栈的变化情况,并绘制出堆栈图

函数调用

00401168  |.  6A 02         push 0x2
0040116A  |.  6A 01         push 0x1
0040116C  |.  E8 99FEFFFF   call HelloWor.0040100A
00401171  |.  83C4 08       add esp,0x8

CALL内部

00401040  /> \55            push ebp
00401041  |.  8BEC          mov ebp,esp
00401043  |.  83EC 40       sub esp,0x40
00401046  |.  53            push ebx
00401047  |.  56            push esi
00401048  |.  57            push edi
00401049  |.  8D7D C0       lea edi,dword ptr ss:[ebp-0x40]
0040104C  |.  B9 10000000   mov ecx,0x10
00401051  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC
00401056  |.  F3:AB         rep stos dword ptr es:[edi]
00401058  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]
0040105B  |.  0345 0C       add eax,dword ptr ss:[ebp+0xC]
0040105E  |.  5F            pop edi                                  ;  HelloWor.00401171
0040105F  |.  5E            pop esi                                  ;  HelloWor.00401171
00401060  |.  5B            pop ebx                                  ;  HelloWor.00401171
00401061  |.  8BE5          mov esp,ebp
00401063  |.  5D            pop ebp                                  ;  HelloWor.00401171
00401064  \.  C3            retn

开始分析

分析流程较为冗长,可能会有些乏味,可以先看最后的流程总结,再来看分析的细节

我们现在开始逐语句分析堆栈的变化情况:

Windows逆向安全(一)之基础知识(一)_第23张图片
Windows逆向安全(一)之基础知识(一)_第24张图片

Windows逆向安全(一)之基础知识(一)_第25张图片

初始堆栈图

我们观察堆栈的情况:

此时ESP:0012FF34 EBP:0012FF80

结合寄存器和堆栈内容绘出简易堆栈图

Windows逆向安全(一)之基础知识(一)_第26张图片

在这里插入图片描述
压入参数

00401168  |.  6A 02         push 0x2                        

Windows逆向安全(一)之基础知识(一)_第27张图片

可以看到执行后ESP减少了4=0012FF30 并且0012FF30里的内容为2,这就是所谓的入栈操作

0040116A  |.  6A 01         push 0x1                        

Windows逆向安全(一)之基础知识(一)_第28张图片
可以看到执行后ESP又减少了4=0012FF2C ,并且0012FF2C里的内容为1

上面的两条push语句是将两个立即数 2和1压入到堆栈中,我们可以画出对应的堆栈图:

Windows逆向安全(一)之基础知识(一)_第29张图片
CALL指令

0040116C  |.  E8 99FEFFFF   call HelloWor.0040100A

F7单步步入

Windows逆向安全(一)之基础知识(一)_第30张图片可以看到CALL之后跳转到了0040100A,并且esp又减少了4=0012FF28

而且我们可以注意到此时堆栈中0012FF28存放的内容是:00401171正好是我们call指令的下一行指令的地址

0040116C  |.  E8 99FEFFFF   call HelloWor.0040100A
00401171  |.  83C4 08       add esp,0x8

所以应证了前面所学的call指令会将要返回的地址压入栈中来保存现场

此时的堆栈图为

Windows逆向安全(一)之基础知识(一)_第31张图片
接着我们就跳转到了call的内部

CALL内部指令

在这里插入图片描述

00401040  /> \55            push ebp

在这里插入图片描述Windows逆向安全(一)之基础知识(一)_第32张图片EBP被压入到堆栈中,此时堆栈图为

Windows逆向安全(一)之基础知识(一)_第33张图片

接着执行

00401041  |.  8BEC          mov ebp,esp

ebp赋值为esp,此时堆栈图为

Windows逆向安全(一)之基础知识(一)_第34张图片

接着执行

00401043  |.  83EC 40       sub esp,0x40

在这里插入图片描述
Windows逆向安全(一)之基础知识(一)_第35张图片
将esp的值减去0x40=64,我们这里的相差的数据宽度为4即16,64/4=16,因此堆栈图里多了16格(蓝色部分),这种操作常被叫做提升堆栈,此时堆栈图为:

Windows逆向安全(一)之基础知识(一)_第36张图片

我们可以发现提升完堆栈以后,堆栈的数据有些★意义不明★,这是因为堆栈中存放的是临时的数据,可能是之前使用时没有清理的垃圾数据

接着执行

00401046  |.  53            push ebx
00401047  |.  56            push esi
00401048  |.  57            push edi

将三个通用寄存器压入堆栈,用于保护现场,注意CALL之前和CALL之后,其前后环境要一致,这就是所谓的堆栈平衡

在这里插入图片描述

Windows逆向安全(一)之基础知识(一)_第37张图片根据此时的堆栈内容绘制堆栈图

Windows逆向安全(一)之基础知识(一)_第38张图片
接着执行

00401049  |.  8D7D C0       lea edi,dword ptr ss:[ebp-0x40]

在这里插入图片描述
将ebp-40所指向的内存地址赋给edi

前面我们执行了sub esp,0x40 所以这里其实就是将那时esp的地址传给了edi(就是push ebx esi edi)之前的的esp

此时堆栈图并发生没有变化

接着看下一行

0040104C  |.  B9 10000000   mov ecx,0x10
00401051  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC

分别给ecx和eax赋值,堆栈图依旧没有发生变化

Windows逆向安全(一)之基础知识(一)_第39张图片
接着看下一行

00401056  |.  F3:AB         rep stos dword ptr es:[edi]

这条语句用到了我们前面所学的逆向基础笔记五 标志寄存器中的内容(如有疑惑可前往查看)

rep的作用是,重复执行 stos dword ptr es:[edi],每次执行都会使ecx-1,直到ecx为0再执行下一条语句

前面赋值ecx为0x10=16,正好对应我们堆栈图中蓝色的格子数,所以将会执行16次

stos dword ptr es:[edi]则是将eax的值赋值给edi所指向的内存地址里的值,并且每执行一次edi都会增加4(D标志位为0所以是增加)

结合前面edi==esp,这里其实是将我们提升堆栈的那部分内存区域初始化

此时的堆栈内容为

Windows逆向安全(一)之基础知识(一)_第40张图片
很明显地看到原本的垃圾数据被我们初始化为了CCCCCCCC

堆栈图也变成了

Windows逆向安全(一)之基础知识(一)_第41张图片
实际执行内容

接着看下面的代码

00401058  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]
0040105B  |.  0345 0C       add eax,dword ptr ss:[ebp+0xC]

在这里插入图片描述
根据堆栈图我们可以很清晰地看出

[ebp+0x8]正是我们call外部push的参数:1

[ebp+0xc]正是我们call外部push的参数:2

这里是将eax赋值为1,然后再给eax+2,最终结果eax=3

还原现场并返回

此时堆栈图依旧没有发生变化,接着看下面的语句

0040105E  |.  5F            pop edi                                  ;  HelloWor.00401171
0040105F  |.  5E            pop esi                                  ;  HelloWor.00401171
00401060  |.  5B            pop ebx                                  ;  HelloWor.00401171

出栈,还原现场,堆栈图

Windows逆向安全(一)之基础知识(一)_第42张图片
下一条
在这里插入图片描述
还原esp,前面mov ebp,esp对应也要还原

此时堆栈图为:

Windows逆向安全(一)之基础知识(一)_第43张图片
继续看下一条指令

00401063  |.  5D            pop ebp                                  ;  HelloWor.00401171

在这里插入图片描述
将ebp出栈,恢复现场,此时的堆栈图为

Windows逆向安全(一)之基础知识(一)_第44张图片

最后一句

00401064  \.  C3            retn

此时栈顶为

在这里插入图片描述

返回,相当于于pop eip

执行后

在这里插入图片描述
执行后的堆栈图为

Windows逆向安全(一)之基础知识(一)_第45张图片
执行返回后

此时返回到了

在这里插入图片描述
也就是之前call的下一句指

00401171  |.  83C4 08       add esp,0x8

在这里插入图片描述
此时的堆栈图

Windows逆向安全(一)之基础知识(一)_第46张图片
我们可以发现此时的ESP和EBP又变回到了原本执行前的状态,(寄存器也一样),这就是所谓的堆栈平衡

总结

通过上面的分析,我们可以得出这段代码所处理的大致流程

可分为三个部分:压入参数、调用CALL、CALL返回后

压入参数

压入参数部分十分简单,就是将调用CALL所需的参数压入堆栈,方便CALL内部执行时调用

这里对应的语句为

00401168  |.  6A 02         push 0x2
0040116A  |.  6A 01         push 0x1

即这个CALL得到的参数为2和1

调用CALL

调用CALL又可以分为六个部分:

提升堆栈
保护现场
初始化提升的堆栈
执行实际内容
恢复现场
返回

提升堆栈
对应语句为

00401040  /> \55            push ebp
00401041  |.  8BEC          mov ebp,esp
00401043  |.  83EC 40       sub esp,0x40

将堆栈提升了0x40

保护现场

对应语句为

00401046  |.  53            push ebx
00401047  |.  56            push esi
00401048  |.  57            push edi

将ebx、esi、edi三个通用寄存器保存到堆栈中,前面的push ebp其实也属于保护现场

初始化提升的堆栈

00401049  |.  8D7D C0       lea edi,dword ptr ss:[ebp-0x40]
0040104C  |.  B9 10000000   mov ecx,0x10
00401051  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC
00401056  |.  F3:AB         rep stos dword ptr es:[edi]

这里将我们提升的堆栈中的内容全部初始化为CCCCCCCC

为什么是初始化为CC?防止缓冲溢出

CC的硬编码对应的指令为int 3,即断点

这么做有什么好处呢?当程序执行超过缓冲区时,遇到int 3就会自动停下来

执行实际的内容

对应语句为

00401058  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]
0040105B  |.  0345 0C       add eax,dword ptr ss:[ebp+0xC]

就是将前面压入的参数2和1进行相加得到3

恢复现场
对应语句为

0040105E  |.  5F            pop edi                                  ;  HelloWor.00401171
0040105F  |.  5E            pop esi                                  ;  HelloWor.00401171
00401060  |.  5B            pop ebx                                  ;  HelloWor.00401171
00401061  |.  8BE5          mov esp,ebp
00401063  |.  5D            pop ebp                                  ;  HelloWor.00401171

与前面保护现场相对应

返回

对应语句为

00401064  \.  C3            retn

CALL返回后
对应语句为

00401171  |.  83C4 08       add esp,0x8

作用为平衡堆栈
逆推C语言代码
根据我们前面的分析,我们不难发现这其实就是个简单的加法函数

int add(int x,int y){
    x=x+y;        //这里的x和y分别对应压入的参数
    return x;        //对应RETN 默认采用eax作为返回值的传递载体
}

你可能感兴趣的:(Windows逆向,windows,安全,逆向,网络安全)