逆向学习笔记

滴水逆向

文章目录

  • 滴水逆向
    • 一:
    • 二:
    • 三:
    • 进制-0与1:
    • 数据宽度:
    • 通用寄存器(内存读取):
    • 内存地址—堆栈:
    • 标志寄存器(EFLAGS):
    • JCC:
    • 堆栈图:
    • V C++6.0
    • 数据结构:
    • C语言-if语句逆向:
    • C语言基础:

一:

​ 信息的存储:一切信息在电脑中的存储方式都是用二进制存储。为了方便显示和,电脑将二进制数以十六进制数显示,八位二进制数为一字节,一字节是系统操作的基本单位,系统通常用两个十六进制数来表示一字节。

​ PE文件结构:pe文件结构是在Windows上运行的可执行文件必须遵守的格式。(程序从哪里开始,数据存在哪里,程序放在哪里)

逆向学习笔记_第1张图片

逆向学习笔记_第2张图片

十六进制的3c转换成十进制就是60,表示从六十以后四个字节就是三十二位信息的起始点,但并不是程序运行的起点。
逆向学习笔记_第3张图片

逆向学习笔记_第4张图片

依照顺序依次查找,最终定位到00000111,这个才是入口点。

计算机中的常用单位:八位二进制数为一字节(8 Bit),十六位为一字(word)(16 Bit)三十二位为双字(dword)(32 Bit)

Windows操作系统存储数据采用地位在前高位在后,所有看到一串二进制数,如:E8 00 00 00 实际上在Windows系统中它是000000E8

PE结构图

二:

​ 逻辑运算:在计算机中所有的运算都是采用逻辑位运算(与、或、异或、非、左移<<)

计算机计算:2+3=?
转换成二进制数	
x:0010
y:0011
第一步异或运算
		0010
xor		0011
--------------
		0001		R:0001

判断是否运算结束,进行与运算
		0010
and		0011
--------------
		0010

对与运算结果进行移位操作后再判断是否运算完毕
		0010	<<	1	==	0100	(左移一位)
如果左移结果等于零,那么这个R就是运算结果
如果左移结果不等于零,则再进行异或运算

将上次异或运算出的结果作为新的运算值
x:0001
将左移得到的结果也作为新的运算值
y:0100
再进行异或运算
		0001
xor		0100
--------------
		0101		R:0101
		
同样进行与运算
		0001
and		0100
--------------
		0000
		0000	<<	1	==	0000
左移结果为0000,所以运算结果就是 R:0101	--->	十进制:5

​ 逻辑运算还可以用来数据加密

客户端发送数据:2015
加密密钥:54
		20		15
20:		0010 0000
54:		0101 0100
xor:	0111 0100			对 2 0 进行加密后得 7 4
再对15进行加密
15:		0001 0101
54:		0101 0100
xor:	0100 0001			加密后传出的数据就是	4	1
服务器端接收到的是:7441
服务器端再利用密钥进行解密
服务端:7441
密钥:54
74:		0111 0100
54:		0101 0100
xor:	0010 0000		20
41:		0100 0001
54:		0101 0100
xor:	0001 0101		15
最后得到的就是2015

​ 存储数据的地方:寄存器与内存

​ 寄存器是再CPU中的用于存储数据的,但是因为CPU主要用于运算,所以寄存器的存储量很小,但是读取速度很快,所以一般是CPU将要处理的数据或程序放入到寄存器中。内存相比起寄存器,存储量更大,对应的读取速度会慢一点,也没那么贵。

32位通用寄存器的指定用途:
EAX		累加器
ECX		计数
EDX		I/O指针
EBX		DS段的数据指针
ESP		堆栈指针
EBP		SS段的数据指针
ESI		字符串操作的原指针;SS段的数据指针
EDI		字符串操作的目标指针;ES段的数据指针
通用寄存器的使用(EAX/ECX/EDX/EBX)
汇编指令 MOV/ADD/SUB

MOV		EAX,	12345678
指令	目标	数据(这个数称为立即数)
MOV:移动
将12345678这个立即数放到EXA这个寄存器中
ADD	EAX,1
ADD:相加
把1与EXA中的数相加(12345678+1)
MOV	ECX,2
将2放入到ECX中
ADD	EAX,ECX
把ECX中的数与EAX的数相加(不止是单纯的数字加,也可以不同区域的寄存器相加,相移)
SUB	EAX,3
SUB:相减
将EAX中的数减3

​ 内存

​ 内存单元宽度为8,能存储8位二进制数,也就是一个内存单元一字节

  • 1kb=1024字节
  • 1Mb=1024Kb
  • 1Gb=1024Mb
以WindowsXP-32位系统为例:
内存的地址编号从:0x00000000~0xFFFFFFFF	
八个十六进制地址位,也就是说一个地址就是32位的地址位(4*8=32)
这个地址位也就是它的地址总线数,地址总线数为32
内存大小:
0~F,十六
总地址数:0x100000000(因为有个0x00000000)转换成十进制后:4294957296
一个地址就是一个地址单元,大小位一个字节
进行换算:4294957296/1024 = 4194304 Kb
继续换算:4194304/1024 = 4096 Mb
继续换算:4096/1024 = 4 Gb
所以32位的操作系统最大能识别的内存就是4Gb

​ 内存的读写

寻址公式一:[立即数]直接写入,读出数据

读取内存的值:
MOV	EAX,DWORD PTR DS:[0X13FFC4]
意思:从内存0X13FFC4中读取一个32bit的数据放到前面EAX这个寄存器中
DWOR代表大小
PTR是固定写法
DS是段寄存器
如果操作的是数就输DS:;
如果操作的是EBP、ESI(寄存器)就输SS:;
如果操作的是EDI就输ES:
MOV 

可以在ESP的地址左右练手,防止32位的系统没有该地址

将数据写入内存:
MOV	DWORD PTR DS:[0X13FFC4],0x87654321
意思:将后面的十六进制数存入到前面的地址中

取出该地址
LEA ECX,DWORD PTR DS:[0X13FFC4]
LEA EAX,DWORD PTR DS:[ESP+8]
意思:把这个地址编号给到ECX寄存器中
寻址公式二:[reg]reg代表寄存器可以是8个通用寄存器中的任意一个

读取内存的值:
MOV ECX,0X13FFD0
MOV EAX,DWORD PTR DS:[ECX]

向内存中写入数据:
MOV EDX,0X13FFD8
MOV DWORD PTR DS:[EDX],0X87654321

获取内存编号:
LEA EAX,DWORD PTR DS:[EDX]
MOV EAX,DWORD PTR DS:[EDX]
寻址公式三:[reg+立即数]

读取内存的值:
MOV ECX,0X13FFD0
MOV EAX,DWORD PTR DS:[ECX+4]

向内存中写入数据:
MOV EDX,0X13FFD8
MOV DWORD PTR DS:[EDX+0XC],0X87654321

获取内存编号:
LEA EAX,DWORD PTR DS:[EDX+4]
MOV EAX,DWORD PTR DS:[EDX+4]
寻址公式四:[reg+reg*{1,2,4,8}]

读取内存的值:
MOV EAX,13FFC4
MOV ECX,2
MOV EDX,DWORD PTR DS:[EAX+ECX*4]

向内存中写入数据:
MOV EAX,13FFC4
MOV ECX,2
MOV DWORD PTR DS:[EAX+ECX*4],87654321

获取内存编号:
LEA EAX,DWORD PTR DS:[EAX+ECX*4]
寻址公式五:[reg+reg*{1,2,4,8}+立即数]

读取内存的值:
MOV EAX,13FFC4
MOV ECX,2
MOV EDX,DWORD PTR DS:[EAX+ECX*4+4]

向内存中写入数据:
MOV EAX,13FFC4
MOV ECX,2
MOV DWORD PTR DS:[EAX+ECX*4+4],87654321

获取内存编号:
LEA EAX,DWORD PTR DS:[EAX+ECX*4+4]

​ 堆栈:

  1. ​ 堆栈的本质就是内存。
  2. ​ 栈是用来存储临时变量,函数传递的中间结果。

ESP存了栈顶的位置,EBP存了栈底的位置。

push向栈存入数据(入栈):push 0X12345678,pop从栈中取出数据(出栈):pop eax。栈底不变,栈顶变(栈的数据结构特点)。

push eax代码相当于:

lea esp,dword ptr ss:[esp-4]
mov dword ptr ss:[esp],eax
或者
mov dword ptr ss:[esp-4],eax
lea eso,dword ptr ss:[esp-4]

lea esp,dword ptr ss:[esp-4] == mov esp,esp-4 == sub esp,4
pop ecx 代码相当于:

mov ecx,dword ptr ss:[esp]
lea esp,dword ptr ss:[esp+4] == mov esp,esp+4 == add esp,4

或者
lea esp,dword ptr ss:[esp+4]
mov ecx,dword ptr ss:[esp-4]

EIP寄存器:cpu当前执行到的位置

CALL指令:将EIP的指针跳到目标地址,并将当前指令的下一行地址放入堆栈中便于执行完跳转后继续回来执行当下的指令

每个指令都对应了相应字节的十六进制机器码,所以系统才能通过当前地址加上指令的大小确定下一条指令的位置,CALL也才能将其放入堆栈中

三:

​ 画堆栈图,能更好的了解数据变化的过程

逆向学习笔记_第5张图片

逆向学习笔记_第6张图片

逆向学习笔记_第7张图片

跳转之后,首先做的是Push之前的所以内容,保留现场,相当于中断。

逆向学习笔记_第8张图片

逆向学习笔记_第9张图片

逆向学习笔记_第10张图片

局部变量是放在堆栈中的,使用完之后如果不回收清理,就会一直留在堆栈中。函数中声明的变量是局部变量。

逆向学习笔记_第11张图片

​ 定义的函数中设置了两个局部变量,这两个局部变量都存放在堆栈中,循环是从0开始,然后到10之后结束。这使得缓冲区溢出,[ESP+4]

进制-0与1:

​ 进制的定义:由几个符号组成,逢几进一。进制的本质是任意符号去表示数据,从一个角度理解算是一种加密。

二进制:0~100:
0	1	10	11	100	101	110	111	1000	1001	1010	1011	1100	1101	1110	1111
10000	10001	10010	10011	10100	10101	10110	10111	11000	11001	11010	11011	11100	11101	11110	11111	100000	100001	100010	100011	100100	100101	100110	100111	101000	101001	101010	101011	101100	101101	101110	101111	110000	110001	110010	110011	110100	110101	110110	110111	111000	111001	111010	111011	111100	111101	111110	111111	1000000	1000001	1000010	1000011	1000100	1000101	1000110	1000111	1001000	1001001	1001010	1001011	1001100	1001101	1001110	1001111	1010000	1010001	1010010	1010011	1010100	1010101	1010110	1010111	1011000	1011001	1011010	1011011	1011100	1011101	1011110	1011111	1100000	1100001	1100010	1100011	1100100

​ 进制的换算可用权重去计算。重点在二进制和十六进制的转换。进制的运算重在查表:如八进制,先把八进制数都写出来列成一张表。然后2+2相当于2之后两位,2*2相当于2+2

数据宽度:

​ 在计算机中,由于受硬件的制约,数据都是有长度限制的(称为数据宽度),超过最大宽度的数据就会被丢弃。

​ 逻辑运算:CPU进行运算的本质。或:or |;与: and &;异或: xor ^;非:not !;

逻辑运算的具体运用:
CPU计算2+3 (从电路的角度)
x:0010
y:0011
第一步异或运算
		0010
xor		0011
--------------
		0001		R:0001

判断是否运算结束,进行与运算
		0010 
and		0011
--------------
		0010

对与运算结果进行移位操作后再判断是否运算完毕
		0010	<<	1	==	0100	(左移一位)
如果左移结果等于零,那么这个R就是运算结果
如果左移结果不等于零,则再进行异或运算

将上次异或运算出的结果作为新的运算值
x:0001
将左移得到的结果也作为新的运算值
y:0100
再进行异或运算
		0001
xor		0100
--------------
		0101		R:0101
		
同样进行与运算
		0001
and		0100
--------------
		0000
		0000	<<	1	==	0000
左移结果为0000,所以运算结果就是 R:0101	--->	十进制:5

最简单的加解密:
客户端发送数据:2015
加密密钥:54
		20		15
20:		0010 0000
54:		0101 0100
xor:	0111 0100			对 2 0 进行加密后得 7 4
再对15进行加密
15:		0001 0101
54:		0101 0100
xor:	0100 0001			加密后传出的数据就是	4	1
服务器端接收到的是:7441
服务器端再利用密钥进行解密
服务端:7441
密钥:54
74:		0111 0100
54:		0101 0100
xor:	0010 0000		20
41:		0100 0001
54:		0101 0100
xor:	0001 0101		15
最后得到的就是2015

​ 小结:计算机中数的存储(二进制与十六进制),计算机数的运算(逻辑)

通用寄存器(内存读取):

寄存器 编号(二进制) 编号(十进制)
32位 16位 8位
EAX AX AL 000 0
ECX CX CL 001 1
EDX DX DL 010 2
EBX BX BL 011 3
ESP SP AH 100 4
EBP BP CH 101 5
ESI SI DH 110 6
EDI DI BH 111 7

​ 寄存器结构理解:

逆向学习笔记_第12张图片

​ 不同位数的寄存器方便存储大小不同的数据,这样能提高利用效率,最后分为高八位和低八位。

代码演示:立即数
EAX 00000000
MOV EAX,0xAAAAAAAA
>>> EAX AAAAAAAA
MOV AX,0xBBBB
>>>	EAX AAAABBBB
MOV AH,0xDD
>>>	EAX AAAADDBB
MOV AL,0xEE
>>>	EAX AAAADDEE

操作数
ECX	00000000
MOV ECX,EAX
>>>	ECX AAAADDEE
MOV 的语法:
1.MOV r/m8,r8
2.MOV r/m16,r16
3.MOV r/m32,r32
4.MOV r8,r/m8
5.MOV r16,r/m16
6.MOV r32,r/m32
7.MOV r8,imm8
8.MOV r16,imm16
9.MOV r32,imm32
r:	通用寄存器
m:	代表内存
imm:代表立即数
r8:	代表8位通用寄存器
m8:	代表8位内存
imm8:代表8位立即数

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

1.源操作数可以是立即数、通用寄存器、段寄存器、或者内存单元

2.目标操作数可以是通用寄存器、段寄存器或者内存单元

3.操作数的宽度必须一样

4.源操作数和目标操作数不能同时为内存单元

!!!注意,在向某地址写入数据时要指定写入数据大小

​ 数据运算:

ADD 的语法:
1.ADD AL,imm8
2.ADD AX,imm16
3.ADD EAX,imm32
4.ADD r/m8,imm8
5.ADD r/m16,imm16
6.ADD r/m32,imm32
7.ADD r/m8,r8
8.ADD r/m16,r16
9.ADD r/m32,r32
10.ADD r8,r/m8
11.ADD r16,r/m16
12.ADD r32,r/m32
SUB 的语法:
1.SUB AL,imm8
2.SUB AX,imm16
3.SUB EAX,imm32
4.SUB r/m8,imm8
5.SUB r/m16,imm16
6.SUB r/m32,imm32
7.SUB r/m8,r8
8.SUB r/m16,r16
9.SUB r/m32,r32
10.SUB r8,r/m8
11.SUB r16,r/m16
12.SUB r32,r/m32

​ 逻辑运算:

AND 的语法:
1.AND AL,imm8
2.AND AX,imm16
3.AND EAX,imm32
4.AND r/m8,imm8
5.AND r/m16,imm16
6.AND r/m32,imm32
7.AND r/m8,r8
8.AND r/m16,r16
9.AND r/m32,r32
10.AND r8,r/m8
11.AND r16,r/m16
12.AND r32,r/m32
OR 的语法:
1.OR AL,imm8
2.OR AX,imm16
3.OR EAX,imm32
4.OR r/m8,imm8
5.OR r/m16,imm16
6.OR r/m32,imm32
7.OR r/m8,r8
8.OR r/m16,r16
9.OR r/m32,r32
10.OR r8,r/m8
11.OR r16,r/m16
12.OR r32,r/m32
XOR 的语法:
1.XOR AL,imm8
2.XOR AX,imm16
3.XOR EAX,imm32
4.XOR r/m8,imm8
5.XOR r/m16,imm16
6.XOR r/m32,imm32
7.XOR r/m8,r8
8.XOR r/m16,r16
9.XOR r/m32,r32
10.XOR r8,r/m8
11.XOR r16,r/m16
12.XOR r32,r/m32
NOT 的语法:
NOT r/m8
NOT r/m16
NOT r/m32

​ 寄存器与内存的区别:

  1. 寄存器位于cPU内部,执行速度快,但比较贵。
  2. 内存速度相对较慢,但成本较低,所以可以做的很大。
  3. 寄存器和内存没有本质区别,都是用于存储数据的容器,都是定宽的。
  4. 寄存器常用的有8个:EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI。
  5. 计算机中的几个常用计量单位:BYTE WORD DWORD
  6. 内存的数量特别庞大,无法每个内存单元都起一个名字,所以用编号来代替,我们称计算机CPU是32位或者64位,有很多书上说之所以叫3z位计算机是因为寄存器的宽度是32位,是不准确的,因为还有很多寄存器是大于32位的。CPU位数取决于寻址总线的线宽,32根线就是三十二位。

常规来说,32位的CPU只能接收4GB的寻址编号,但是可以通过段地址+偏移地址的方法让能够寻址的寻址编号更多。

内存地址—堆栈:

​ 在堆栈区是每四个字节一个地址编号,堆栈中显示出来的是8位16进制数每2位就是一个字节,而内存中每一个地址编号就是一个字节。

mov r,m:相当于把该地址给到了寄存器

mov r,dword ptr ds:[r/m]:这才是将某地址中的值给到寄存器

lea r,dword ptr ds:[r/m]:将目标地址给到寄存器

数据压栈方式:
MOV EBX,0x13FFDC		---->	BASE
MOV EDX,0x13FFDC		---->	TOP
方式一:
MOV	DWORD PTR DS:[EDX-4],0xAAAAAAAA
#这一步是先把元素入栈
SUB	EDX,4
#在调整栈顶指针位置
#Windows系统的特点是堆栈由大到小,所以-4是栈顶上升入栈的过程

方式二:
SUB EDX,4
#先移动栈顶指针位置
MOV	DWORD PTR DS:[EDX],0xBBBBBBBB
#再向当前栈顶指针中放入元素

方式三:
MOV DWORD PTR DS:[EDX-4],0xCCCCCCCC
LEA EDX,DWORD PTR DS:[EDX-4]
#这里采用LEA这个方式直接获取下一个堆栈的地址

方式四:
LEA EDX,DWORD PTR DS:[EDX-4]
MOV DWORD PTR DS:[EDX-4],0xDDDDDDDD
#反过来同理

#入栈(压栈)的原理就是通过栈顶指针找到具体位置,将目标操作数放入栈顶地址,放入后将指针移到当前元素所在位置。
#放入元素、移动指针,两个步骤可交换
读取第N个数
方式一:
#通过BASE加偏移来读取
>>>读第一个压入数据
MOV ESI,DWORD PTR DS:[EBX-4]
>>>读取第四个压入的数据
MOV ESI,DWORD PTR DS:[EBX-0x10]

方式二:
#通过Top加偏移来读取
>>>读取第二个压入数据
MOV EDI,DWORD PTR DS:[EDX+4]
>>>读第三个压入数据
MOV EDI,DWORD PTR DS:[EDX+8]
弹出数据
方式一:
MOV ECX,DWORD PTR DS:[EDX]

方式二:
MOV ESI,DWORD PTR DS:[EDX]
ADD EDX,4

方式三:
LEA EDX,DWORD PTR DS:[EDX+4]
MOV EDI,DWORD PTR DS:[EDX-4]

读取与弹出的不同就在于栈顶指针是否有改变。

PUSH指令:
1,PUSH r32
2,PUSH r16
3,PUSH m16
4,PUSH m32
5,PUSH imm8/imm16/imm32
#将寄存器中的值、某地址的值、立即数压入栈顶
POP指令:
1,POP r32
2,POP r16
3,POP m16
4,POP m32
#将栈顶的值放入寄存器、某地址
PUSHAD指令、POPAD指令
PUSHAD	用来保存通用寄存器当时存储的值,保存现场
POPAD	用来还原存储的寄存器的值,还原现场

标志寄存器(EFLAGS):

逆向学习笔记_第13张图片

标志寄存器中各位代表了不同的消息,EFL 202(0010 0000 0010)一一对位去分析

​ 1、进位标志CF(Carry Flag):如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0。

这里的最高位是 宽度+1。

1000 0000 - 0100 0000 = 0100 0000 这里CF的值是0

1000 0000 - 1000 0001 = 1111 1111 这里CF的值是1

MOV	AL,0xEF
ADD AL,2
>>>PC--->0

MOV AL,0xFE
ADD AL,2
>>>PC--->1

​ 2、奇偶标志PF(Parity Flag):奇偶标志PE用于反映运算结果中“1”的个数的奇偶性。

看最低有效字节,如果“1”的个数为偶数,则PE的值为1,否则其值为0.

MOV AL,3
>>>PE--->2
ADD AL,3
>>>PE--->2
ADDAL,2
>>>PE--->1
#看运算完后结果的二进制数中一的个数

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

看低四位、八位、十六位是否会有向高位进位,有进位才会将AF置为1,其他的都没有变化

MOV EAX,0x55EEFFFF
ADD EAX,2
>>>AF--->1
MOV AX,5EFE
ADD AX,2
>>>AF--->1
MOV AL,4E
ADD AL,2
>>>AF--->1

​ 4、零标志ZF(Zero Flag):零标志ZF用来反映运算结果是否为0.

如果运算结果为0,则其值为1,否则其值为0.再判断运算结果是否为0时,可使用此标志位。

XOR EAX,EAX
>>>ZF--->0
#该条命令用来把EAX清零
MOV EAX,2
SUB EAX,2
>>>ZF--->0

​ 5、符号标志SF(Sign Flag):符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。

MOV AL,7F(0111 1111)
>>>SF--->0
ADD AL,2(1000 0001)
>>>SF--->1

​ 6、溢出标志OF(Overflow Flag):溢出标志OF用于反映有符号数加减运算所得结果是否溢出。

如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0。

C位溢出是无符号位溢出,O位溢出是有符号位的溢出

溢出主要是给有符号运算使用的,再有符号的运算中,有如下的规律:
#正+正=正	如果及结果是负数,则说明有溢出
#负+负=负	如果结果是正数,则说明有溢出
#正+负	永远都不会有溢出
  • 无符号、有符号都不溢出
MOV	AL,8(0000 1000)
ADD AL,8(0000 1000)
>>>OF--->0(0001 0000)
  • 无符号溢出、有符号不溢出
MOV AL,FF(1111 1111)
ADD AL,2(0010)
>>>OF--->(无符号就溢出了,有符号就是:负+正。负数+正数永远都不会溢出)
  • 无符号不溢出、有符号溢出
MOV	AL,7F(0111 1111)
ADD AL,2(0010)
>>>OF--->(无符号位就可以进位,有符号位就会溢出)
  • 无符号、有符号都溢出
MOV	AL,FF(1111 1111)
ADD AL,80(1000 0000)
>>>OF--->(最高位都会有溢出。负数相加为正,则溢出)

​ ADC指令:带进位加法

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

ADC	AL,CL
ADC	BYTE PTR DS:[12FFC4],2
ADC BYTE PTR DS:[12FFC4],AL

ADD 是求两个指定整数的和,而 ADC 除了两个指定整数以外,还会加上 C(进位)状态的值。需要 ADC 指令,是因为如果要加的整数长于微处理器每次能加的位元数,就要分开来加,高位字节的结果取决于低位字节相加时有没有进位。

举例:假如有8位元微处理器每次只能加一个字节,

如果我们要加两个 16 位元整数:00110101 11001010 + 00010100 01111101

先用 ADD 加 11001010 和 01111101,得 01000111,

有进位,状态 C 设为 1 再用 ADC 加 00110101 和 00010100 和 状态C(现在是1),

得 01001010 所以和是 01001010 01000111

​ SBB指令:带借位减法

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

SBB	AL,CL
SBB	BYTE PTR DS:[12FFC4],2
SBB	BYTE PTR DS:[12FFC4],AL

​ XCHG指令:交换数据

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

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

两者交换数据的宽度要一样。

​ MOVS指令:移动数据 内存与内存之间

​ 在正向中很可能是字符串的复制

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

​ 方向标志DF(Direction Flag):决定了ESI和EDI在MOVS完了之后的增长方向。

DF为零就加,为一就减。加减多少取决于移动数的大小。

​ STOS指令:将AL/AX/EAX的值存储到[EDI]指定的内存单元

STOS BYTE PTR ES:[EDI]			简写为:STOSB
STOS WORD PTR ES:[EDI]			简写为:STOSW
STOS DWORD PTR ES:[EDI]			简写为:STOSD

它执行后EDI的值的移动方向也是由DF决定。同样DF为零就加,为一就减

​ REP指令:按计数寄存器(ECX)中指定的次数重复执行字符串指令

MOV ECX,10
REP MOVSD
REP STOSD

JCC:

​ jmp指令:修改EIP寄存器中指针指向下一条指令的位置(修改IP指针,使CPU执行对应指令)。

如果跳转位置离当前指向位置小于128个字节,就会有一个SHORT

JUM只修改EIP的值,寄存器和堆栈都不会发生改变

MOV	EIP,寄存器/立即数
简写为:
JMP	寄存器/立即数

​ call指令:修改EIP指向,和jmp功能一样

call与jmp的区别:call会将一个返回地址压入堆栈中。

返回地址:call指令计算出给指令的字节数后,在当前位置+当前指令的字节数=返回地址

PUSH 地址B
MOV EIP,地址A/寄存器
简写为:
CALL 地址A/寄存器

​ ret指令:与call指令成对出现,到达返回地址

退栈操作

LEA ESP,[ESP+4]
MOV EIP,[ESP-4]
简写为:
RET

​ cmp指令:比较两个操作数

  • ​ 指令格式:cmp R/M,R/M/IMM

实际上相当于SUB指令,但是相减的结构并不保存到第一个操作数中。

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

MOV EAX,100
MOV ECX,100
CMP EAX,ECX
Z标志位:1
>>>表示两个数相等
MOV EAX,100
MOV ECX,200
CMP EAX,ECX
S标志位:1
>>>表示第一个数比第二个数小
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

常用于确定某寄存器是否等于0.

TEST EAX,EAX
z标志位:1
>>>表示EAX的值为零

​ 一些小指令:

JCC指令 中文含义 英文原意 检查符号位 典型C应用
JZ/JE 若为0则跳转;若相等则跳转 jump if zero;jump if equal ZF=1 if (i == j);if (i == 0);
JNZ/JNE 若不为0则跳转;若不相等则跳转 jump if not zero;jump if not equal ZF=0 if (i != j);if (i != 0);
JS 若为负则跳转 jump if sign SF=1 if (i < 0);
JNS 若为正则跳转 jump if not sign SF=0 if (i > 0);
JP/JPE 若1出现次数为偶数则跳转 jump if Parity (Even) PF=1 (null)
JNP/JPO 若1出现次数为奇数则跳转 jump if not parity (odd) PF=0 (null)
JO 若溢出则跳转 jump if overflow OF=1 (null)
JNO 若无溢出则跳转 jump if not overflow OF=0 (null)
JC/JB/JNAE 若进位则跳转;若低于则跳转;若不高于等于则跳转 jump if carry;jump if below;jump if not above equal CF=1 if (i < j);
JNC/JNB/JAE 若无进位则跳转;若不低于则跳转;若高于等于则跳转; jump if not carry;jump if not below;jump if above equal CF=0 if (i >= j);
JBE/JNA 若低于等于则跳转;若不高于则跳转 jump if below equal;jump if not above ZF=1或CF=1 if (i <= j);
JNBE/JA 若不低于等于则跳转;若高于则跳转 jump if not below equal;jump if above ZF=0或CF=0 if (i > j);
JL/JNGE 若小于则跳转;若不大于等于则跳转 jump if less;jump if not greater equal SF != OF if (si < sj);
JNL/JGE 若不小于则跳转;若大于等于则跳转; jump if not less;jump if greater equal SF = OF if (si >= sj);
JLE/JNG 若小于等于则跳转;若不大于则跳转 jump if less equal;jump if not greater ZF != OF 或 ZF=1 if (si <= sj);
JNLE/JG 若不小于等于则跳转;若大于则跳转 jump if not less equal;jump if greater SF=0F 且 ZF=0 if(si>sj)

堆栈图:

堆栈图:

  • ctrl+g:跳转到输入的目标地址
  • F2:选中地址后按F2,设置断点
  • F8:单步调试,会跳过Call指令(单步步过)
  • F7:单步调试,不会跳过Call指令(单步步入)

堆栈图分析步骤:

第一步:观察寄存器(ESP、EBP、EIP)确定运行前的堆栈位置。画出初始堆栈图。
第二步:单步调试,观察栈顶与栈底的变化,以及是否有值入栈或出栈。
第三步:重复第一步,第二步。

函数:
计算机的函数,是一个固定的一个程序段,或称其为一个子程序,它在可以实现固定运算功能的同时还带有一入口和一个出口,所谓的入口,就是函数所带的各个参数,我们可以通过这个入口,把函数的参数值代入子程序,供计算机处理,所谓出口,就是指函数的计算结果,也称为返回值,在计算机求得之后,由此口带回给调用它的程序。
逆向学习笔记_第14张图片逆向学习笔记_第15张图片
Windows堆栈:逆向学习笔记_第16张图片特点: 1、先进后出;2、向低位地址扩展
堆栈平衡:windows中的堆栈,是一块普通的内存,主要用来存储一些临时的数据和参数等。可以把windows中的堆栈想象成是一个公用的书箱,函数就像是使用箱子的人。函数在执行的时候,会用到这个书箱,把一些数据存到里面,但用完的时候一定要记得把书拿走,否则会乱的,也就是说,你放进去几本书,走的时候也要拿走几本书,这个就是堆栈平衡.

V C++6.0

VC++6.0挺好的,但是太旧了,版本不兼容问题有点麻烦,即使克服了版本不兼容成功安好了,使用起来也会不太顺手。Microsoft Visual C++ 2010 Express挺好的,也顺手,相应的功能都有,快捷键也差不多。强行用了一段时间VC++6.0后的感受。

​ 快捷键:

  • F9:设置断点
  • F7:构建
  • F5:运行
  • F10:单步执行
  • F11:进入call语句
  • Registers:调试时的寄存器窗口
  • memory:调试时十六进制窗口(可以把变量拖入就可以看见其值的十六进制)

C语言函数的规则:

  • ​ 基本格式:返回值类型 函数名 (传入的参数值)
  • ​ 函数名、参数名、变量名的命名规则:只能包含字母、数字和下划线,且不能以数字开头,不能时关键字。

​ 裸函数:

  • ​ 结构:返回类型 declspec(naked) 函数名(){}

返回类型、函数名、参数都可以设置

  • ​ 特点:一个普通的函数是会生成汇编代码,裸函数则不会生成汇编代码;普通空函数是能够执行的,但是空的裸函数不能正常执行。(正常函数生成的汇编代码,即使是空函数,它都会有一个ret语句返回。而裸函数如果不编写代码则没有ret。)
#include "stdio.h"

void _declspec(naked) luoluo () 
{
	
}
void Plus ()
{
	
}
int main ()
{
	luoluo();
	Plus();
	return 0;
}

逆向学习笔记_第17张图片
逆向学习笔记_第18张图片
从上面两个定义的裸函数与普通函数就可以看出两者的不同之处。(从反汇编角度来讲,裸函数需要直接去编写所有东西,普通函数定义系统会帮你做好基础指令)

在c语言中写汇编代码:____asm{ret}(两个下划线)

//C代码转汇编代码
//练习一:
int plus(int x, int y){
    return x+y;
}
//练习二:
int plus(){
    int a=2;
    int b=3;
    int c=4;
    return a+b+c;
}
//练习三:
int plus(int x, int y, int z){
    int a=2;
    int b=3;
    int c=4;
    return x+y+z+a+b+c;
}

​ 常见的调用约定:

调用约定 参数压栈顺序 平衡堆栈
____cdecl 从右至左入栈 调用者清理栈(外平衡)
____stdcall 从右至左入栈 自身清理堆栈(内平衡)
____fastcall ECX/EDX传送前两个,剩下的从右至左入栈 自身清理堆栈

默认使用的是____cdecl这种方式进行,在调用完函数后,在函数外进行堆栈的清理(参数去除)。

____srdcall与____cdecl的区别就在于它是在函数内部就完成了堆栈的清理。

____fastcall的特点就是快,快的原因是它将参数暂放到寄存器中,这使得读取数据的速度更快效率也更快。但是它只能将两个参数放入寄存器中,如果参数个数更多,那多出的参数还是会放到堆栈中,这样效率也没多高了。如果就两个或一个参数那就不用考虑清理堆栈消除参数的问题,毕竟是放在寄存器当中的也管不着。如果是多出且放在堆栈中的参数,那就进行自身清理堆栈(函数内部解决)。

通过 ret 8 堆栈平衡来思考参数个数,其结果并不准确。最好还是进入函数内部去仔细分析。
函数:

带参数的函数:
参数会在进入函数前就把值压栈。而且是一个外平栈。

plus1(1,2);
00A914DE  push        2 
00A914E0  push        1 
00A914E2  call        @ILT+115(_plus1) (0A91078h) 
00A914E7  add         esp,8 

​ 估计是C语言编译器的特点,它会把所有的调用函数都放在一起,call语句不是直接跳转到目标函数,像是寄存器间接寻址的方式,先跳转到一个jmp语句再通过jmp语句正式跳转到函数体。
​ 可以看的出来定义的函数都有一个基本的模板。

#这里是一个带参的简单函数的开头:
int plus1(int x, int y){
//保存栈底
00A913C0  push        ebp  
//提升堆栈
00A913C1  mov         ebp,esp  
00A913C3  sub         esp,0C0h  
//保存现场
00A913C9  push        ebx  
00A913CA  push        esi  
00A913CB  push        edi  
00A913CC  lea         edi,[ebp-0C0h]  
//填充堆栈
00A913D2  mov         ecx,30h  
00A913D7  mov         eax,0CCCCCCCCh  
00A913DC  rep stos    dword ptr es:[edi] 
#再看一个空函数的反汇编:
void Plus ()
{
00B013D0  push        ebp  
00B013D1  mov         ebp,esp  
00B013D3  sub         esp,0C0h  
00B013D9  push        ebx  
00B013DA  push        esi  
00B013DB  push        edi  
00B013DC  lea         edi,[ebp-0C0h]  
00B013E2  mov         ecx,30h  
00B013E7  mov         eax,0CCCCCCCCh  
00B013EC  rep stos    dword ptr es:[edi]  
	一摸一样。
#其实我们定义主函数的时候也是一样的 
int main ()
{
00A914C0  push        ebp  
00A914C1  mov         ebp,esp  
00A914C3  sub         esp,0C0h  
00A914C9  push        ebx  
00A914CA  push        esi  
00A914CB  push        edi  
00A914CC  lea         edi,[ebp-0C0h]  
00A914D2  mov         ecx,30h  
00A914D7  mov         eax,0CCCCCCCCh  
00A914DC  rep stos    dword ptr es:[edi] 

函数plus1是的功能就是return x+y;它显示的反汇编代码:

return x+y;

00A913DE  mov         eax,dword ptr [x]  
00A913E1  add         eax,dword ptr [y] 

#这里的[x]和[y]的地址就是之前入栈的参数1和参数2。于是结果就放到了eax这个寄存器中了。

函数的结尾也都是一样的。

//恢复现场
00B013EE  pop         edi  
00B013EF  pop         esi  
00B013F0  pop         ebx  
//恢复堆栈
00B013F1  mov         esp,ebp  
00B013F3  pop         ebp  
00B013F4  ret  

无参有局部变量的函数:
这个就很简洁了,在主函数中,只有一个call语句去调用函数,应该是没有参数的原因,它并没有参数平衡这一步。

plus2();
00A914EA  call        @ILT+120(_plus2) (0A9107Dh) 
#除了操作部分的反汇编代码不一样,其他都是一样的
int plus2(){
00A91400  push        ebp  
00A91401  mov         ebp,esp  
00A91403  sub         esp,0E4h  
00A91409  push        ebx  
00A9140A  push        esi  
00A9140B  push        edi  
00A9140C  lea         edi,[ebp-0E4h]  
00A91412  mov         ecx,39h  
00A91417  mov         eax,0CCCCCCCCh  
00A9141C  rep stos    dword ptr es:[edi]  
    int a=2;
00A9141E  mov         dword ptr [a],2  
    int b=3;
00A91425  mov         dword ptr [b],3  
    int c=4;
00A9142C  mov         dword ptr [c],4  
    return a+b+c;
00A91433  mov         eax,dword ptr [a]  
00A91436  add         eax,dword ptr [b]  
00A91439  add         eax,dword ptr [c]  
}
00A9143C  pop         edi  
00A9143D  pop         esi  
00A9143E  pop         ebx  
00A9143F  mov         esp,ebp  
00A91441  pop         ebp  
00A91442  ret  
#有注意这几个局部变量的地址,发现就是上一个函数地址接着的。可是,,参数是放在ebp下面的呀(ebp+8这个位置开始的),局部变量是放在ebp上面的呀???所以地址怎么会一样呢?

既有参数又有局部变量的函数:
这个没什么特别的,还是那句话,除了操作部分,没什么差别。但是因为这个既有参数又有局部变量,所以可以看到两种数据的地址,他们的十六进制地址有一样的。个人暂时认为局部变量和参数被放在了两个不同的寄存器中。

数据结构:

​ 数据类型的三个要素:

  1. ​ 存储的数据的宽度
  2. ​ 存储的数据的格式
  3. ​ 作用范围(作用域)

​ 整数类型:char,short,int,long

类型 位数 大小 对应宽度
char 8bit 1字节 byte
short 16bit 2字节 word
int 32bit 4字节 dword
long 32bit 4字节 dword

​ 整数类型又分为有符号型(signed)和无符号型(unsigned)

两者间在计算机的存储上是没有差别的,唯一的区别在于读取时将其视为什么数。

符号型的应用:类型转换、比较大小、数学运算。

​ 浮点数的存储:

  • float:32位:31(1位,符号位)30~23(8位,指数部分)22 ~0(23位,尾数部分)
  • double:64位:63(1位,符号位)62~52(11位,指数部分)51 ~0(52位,尾数部分)
    逆向学习笔记_第19张图片
    ​ 将浮点数转换成二进制数存储的方法:整数除二取余,小数乘二取整;整数由下往上取余数,小数由上往下取整数。

​ 浮点数存储步骤:

  1. 先将这个数的绝对值转换为二进制。
  2. 将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。
  3. 从小数点右边第一位开始数出二十三位数字放入第22到第0位。
  4. 如果实数是正的,则在第31位放入“0”,否则放入“1”。
  5. 如果n是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第3o位放入“0”
  6. 如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0 ”补足七位,放入第29到第23位。
    如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位

在VC++ 6.0里面查看,浮点数是十六进制显示。

​ 英文字符存储:

​ ASCII编码:

  • ASCII码使用指定的7位或8位二进制数组合来表示128或256 种可能的字符。
  • 标准 ASCII 码使用7位二进制数来表示所有的大写和小写字母,数字О到9、标点符号,以及在美式英语中使用的字符
  • 扩展ASCII码允许将每个字符的第8位用于确定附加的 128个特殊符号字符、外来语字母和图形符号。

​ 七位ASCII码表:

逆向学习笔记_第20张图片
​ 拓展ASCII码表:

​ 由于标准ASCII码字符集字符数目有限,在实际应用中往往无法满足要求。为此,国际标淮化组织又将ASCII码字符集扩充为8位代码及ASCIl码的扩充。这样,ASCII码的字符集可以扩充128个字符,也就是使用8位扩展ASCII码能为256个字符提供编码这些扩充字符的编码均为高位为1的8位代码(即十进制数128一255),称为扩展ASCII码。扩展ASCII码所增加的字符包括加框文字、圆圈和其他图形符号。

​ 计算机发明之处及后面很长一段时间,只用应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。但是当中国也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数字。

​ 中国专家把那些127号之后的奇异符号们(即EASCII)取消掉,规定:一个小于127的字符的意义与原来相同,
但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节〕从OxA1用到OxFT,后面一个字节(低字节)从0xA1到oxFE,这样我们就可以组合出大约7000多个简体汉字了。

​ 在这些编码里,还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。

上述编码规则就是GB2312或GB2312-80
逆向学习笔记_第21张图片GB2312:
逆向学习笔记_第22张图片
​ 全局变量与局部变量:

​ 在VC++6.0中,函数外定义的是全局变量,函数内部的是局部变量。在声明 了全局变量的情况下,可以直接在函数内部对全局变量进行修改。在函数内部定义一个与全局变量重名的变量,就不会影响全局变量。
逆向学习笔记_第23张图片

#include "stdafx.h"

int x=10;

void Plus()
{
	int x;
	x=11;
	printf("%d",x);
}

void Plus1()
{
	printf("%d",x);
}

void Plus2()
{
	x=12;
	printf("%d",x);
}

int main()
{
	Plus();
	Plus1();
	Plus2();
	getchar();
	return 0;
}

C语言-if语句逆向:

​ 内存图:

操作 区域
存放代码,可读可执行 代码区
存放参数,局部变量,临时数据(函数不执行的时候是没有堆栈的) 堆栈
动态申请的,大小可变的。可读可写
可读可写(int x) 全局变量区
只读 常量区

代码不在堆栈区中,在代码区。函数分在代码区。函数调用的时候才会为其分配空间,用来存临时变量和参数。

​ 全局变量的特点:

  1. 全局变量在程序编译完成后地址就已经确定下来了,只要程序启动全局变量就已经存在。是否有值取决于声明时是否给定了初始值,如果没有,默认为0。
  2. 全局变量的值可以被所有函数所修改,里面存储的是最后一次修改的值。
  3. 全局变量所占内存会一直存在,直到整个进程结束。
  4. 全局变量的反汇编识别:
MOV寄存器, byte/word/ dword ptr ds: [ox12345678](通常直接给出的地址就是全局变量)

通过寄存器的宽度,或者byte/word/ dword来判断全局变量的宽度。

全局变量就是所谓的基址

​ 局部变量的特点:

  1. 局部变量在程序编译完成后并没有分配固定的地址。
  2. 在所属的方法没有被调用时,局部变量并不会分配内存地址,只有调用后才会在堆栈中分配内存。
  3. 当局部变量所属的方法执行完毕后,局部变量所占用的内存将不在使用。
  4. 局部变量只能在方法内部使用,函数A无法使用函数B的局部变量。
  5. 局部变量的反汇编识别:
[ebp-4]			[esp+4]
[ebp-8]			[esp+8]
[ebp-0xC]		[esp+0xC]

​ 函数参数分析(判断函数有几个参数):

​ 步骤一:观察调用处的代码

push	3
push	2
push	1
call	0040100f
#因为这里有三个值入栈,所以可以猜测有三个参数

​ 步骤二:找到平衡堆栈的代码继续论证

call 0040100f
add esp,0Ch
或者函数内部
ret 4/8/0xC/0x10

​ 最后两者综合,函数的参数个数基本确定。

但是如果调用约定如果是____fastcall,且参数是两个,那么它是通过寄存器存储参数,所以也无法判断。

​ 参数个数-小结:

当在外部无法判断参数个数的时候,就进入函数内部进行判断。

  1. 不考虑ebp、esp
  2. 只找给别人赋值的寄存器 eax/ecx/edx/ebx/esi/edi
  3. 找到以后追查其来源,如果,该寄存器中的值不是在函数内部赋值的,那一定是传进来的参数。

公式一:寄存器 + ret 4 = 参数个数

公式二:寄存器 + [ebp+8] + [ebp+0x] = 参数个数

​ if语句逆向分析:

mov	eax,dword ptr [ebp+8]
cmp eax,dword ptr [ebp+0Ch]
jle Function+29h(00401049)

if语句的特点:先改变标志寄存器,然后再执行JCC

​ 函数内部功能分析步骤:

  • 分析参数:
[ebp+8]:x
[ebp+0Ch]:y
  • 分析局部变量:
[ebp-4]
[4bp-8]
  • 分析全局变量:
mov	dword ptr 004225c4,ecx
  • 功能分析:
mov eax,dword ptr [ebp+8]
cmp eax,dword ptr [ebp+0Ch]

​ if…else…语句的分析:
逆向学习笔记_第24张图片

if begin:
	先执行各类影响标志位的指令
	然后jxx ok
	else 
	mov eax,ecx
	add eax,eax
	jmp end
end
ok

​ 特点分析:

  1. ​ 如果在执行完标志位操作后,没有跳转,那么就会顺着往下执行到jmp处,通过jmp直接跳转到end处
  2. ​ 如果跳转,则直接跳转过jmp end处的代码,执行后面的ok处的代码

跳转执行一部分代码,不跳转执行另一部分代码

​ if…else if…else if…else 判断的嵌套语句的分析:
逆向学习笔记_第25张图片

if A:
	影响标志位的指令
	jxx B
	mov ......
	add ......
	jmp out
else if B
	影响标志位的指令
	jxx C
	mov ......
	add ......
	jmp out	
else if C
	影响标志位的指令
	jxx D
	mov ......
	add ......
	jmp out	
else D
	mov ......
	add ......
out

​ 特点分析:

  1. 当每个条件跳转指令要跳转的地址前面都有jmp指令
  2. 这些jmp指令跳转的地址都是一样的
  3. 如果某些个分支没有条件判断,则为else部分

如果判断为假就用jmp,如果判断为真就用jcc。相当于筛选,在筛选过程中如有不符,就直接跳出(out)。

C语言基础:

//基于缓冲区溢出的HelloWord函数:
#include "stdio.h"

void HelloWord(){
    printf("Hello World");
    getchar();
}

void Fun(){
    int arr[5] = {1,2,3,4,5};
    arr[6] = (int)HelloWord;
}

int main(int argc,char *argv[]){
    Fun();
    return 0;
}
//这是可以成功运行的
//永不停止的HelloWorld
#include "stdio.h"

void Fun(){
    int i;
    int arr[5] = {0};
    
    for(i=0;i<=5;i++){
        arr[i] = 0;
        printf("Hello World!");
    }
}

int main(int argc,char *argv[]){
     Fun();
    return 0;
}

​ 变量的声明:

​ 向计算机寻求内存空间,计算机需要知道我们的需求。比如:我们想要的空间大小(宽度),我们想要放什么样的东西(种类),还有这个东西放在哪儿(作用域)。

声明变量就是告诉计算机,我要用一块内存,你给我留着,宽度和存储格式由数据类型决定.

计算机什么时候把这块内存给你,取决于变量的作用范围,如果是全局变量,在程序编译完成就已经分配了空间,如果是局部变量,只有在它所处的这块空间被调用时才会分配,否则是不分配的。

全局变量如果不赋初始值,默认是0,但是局部变量在使用前一定要赋初值。

​ 类型转换:

  • MOVSX 先符号拓展,再传送
  • MOVZX 先零扩展,再传送
MOVSX:有符号位的拓展
	mov al,0x88
#0x88:1000 1000
	movsx cx,al
#拓展后:1111 1111 1000 1000
	mov al,0x77
#0x77:0111 0111
	movsx cx,al
#拓展后:0000 0000 0111 0111

#如果符号位为1,那么拓展位全为1;反之符号位为0,那么拓展位为0.
MOVZX:无符号位的拓展
	mov al,0x11
#0x11:0001 0001
	movzx cx,al
#拓展后:0000 0000 0001 0001
	mov al,0xff
#0xff:1111 1111
	movzx cx,al
#拓展后:0000 0000 1111 1111

#无符号位拓展就是用0占拓展位

数据类型在转换的时候就是用的MOVSX和MOVZX,其中MOVSX用于有符号位的转换,MOVZX用于无符号位的拓展。

在C语言当中,一般数据不加说明都默认为是有符号的。浮点数是自带符号位的,没有无符号的浮点数。

​ 数据类型的转换,本质上是原有内存空间太小装不下数据,所以需要扩展数据存储的内存

​ 大类型的数据放到小类型数据中:

​ 截取:从低位开始截取数据放入内存中。

​ 表达式:

  • 特点一:表达式无论多么复杂,都只有一个结果
  • 特点二:只有表达式,可以编译通过,但并不生成代码,需要与赋值或者其他流程控制语句一起组合的时候才有意义
  • 特点三:当表达式中存在不同宽度的变量时,结果将转换为宽度最大的那个
  • 特点四:当表达式中同时存在有符号和无符号数的时候,表达式的结构将转换为无符号数
//特点三:
void fun (int x, int y){
    char a;
    int b;
    
    a=10;
    b=20;
    
    printf("%d",a+b);
}
//特点四:
void fun(int x, int y){
    unsigned char a;
    char b;
    
    a=0xfe;
    b=1;
    
    printf("%d",a+b);
}
//奇奇怪怪
void fun(){
    char a = 1;
    unsigned int b = 0xffffffff;
    printf("%d",a+b);
}
//	>>>结果为-1

%d:有符号的十进制输出;%u:无符号的十进制输出

存储的数据可能是相同的,但它的显示输出就可能不一样。

​ 语句、程序块

  • 带有分号,且对内存做了操作,就算是一条语句。
  • 花括号所包含起来的部分就是程序块。
//#关系运算符:
void fun(){
    int a=1;
    int b=2
    if(a==b){
        printf("ok");
    }
}

//对应汇编操作:
// cmp eax,dword ptr [ebp-4],ecx
// sete cl(如果cmp相等没救sete cl,将cl设置为1,不相等就跳转)
//逻辑运算符:
void fun(int x, int y, int z){
    if(x>1 && y>1 && z>1){
        printf("ok");
    }
    else{
        printf("no");
    }
}

//对应反汇编操作:
//利用cmp进行判断,与逻辑--判断为真则不跳转,为假就跳出
//或逻辑相识,但是跳出的判断相反

你可能感兴趣的:(逆向,学习)