数据宽度
或许你曾经有这样的疑惑,有符号数在二进制的表示中,以1开头的表示为负数,那么10000000是表示的-1还是128呢 ?
在这里引入数据宽度的概念,即8位二进制数能够存储的数据范围为-128~127,很显然,上面存储的数,在数据宽度为8位时,表示-1,同理-127表示为10000001,-2表示为11111110,这里关于原码、补码的概念不加以赘述,其本质都是为了表示数值。
基于x86架构的CPU的数据宽度为32位,表示正负范围为:0~7FFFFFFF 80000000~FFFFFFFF
计量单位有:byte、word、dword、qword,分别表示字节,字,双字,四字,单字节表示8位。
逻辑运算
| 运算符 |
说明 |
实例 |
| &(and) |
按位与,同时为1,才能得1 |
10110011 & 10110101 = 10110001 |
| |(or) |
按位或,存在1时,结果为1 |
10110011 | 10110101 = 10110011 |
| ^(xor) |
按位异或,不同时,结果为1 |
10110011 ^ 10110101 = 00000110 |
| !(not) |
按位非,单运算符,结果取反 |
!10110011=01001100 |
异或加密解密
加密数据:00110001
密钥:00010101
加密结果:00110001 ^ 00010101 = 00100100
解密:00100100 ^ 00010101 = 00110001
基本指令
mov
格式:mov 目标操作数,源操作数
作用:拷贝源操作数到目标操作数。
注意:
1、源操作数可以是立即数、通用寄存器、段寄存器、或者内存单元.
2、目标操作数可以是通用寄存器、段寄存器或者内存单元.
3、操作数的宽度必须一样.
4、源操作数和目标操作数不能同时为内存单元.
| 指令 |
说明 |
| MOV r/m8,r8 |
将8位寄存器的内容拷贝到8位寄存器或8位的内存中。 |
| MOV r/m16,r16 |
将16位寄存器的内容拷贝到16位寄存器或16位的内存中。 |
| MOV r/m32,r32 |
将32位寄存器的内容拷贝到32位寄存器或32位的内存中。 |
| MOV r8,r/m8 |
将8位寄存器或8位的内存操作数拷贝到8位寄存器中。 |
| MOV r16,r/m16 |
将16位寄存器或16位的内存操作数拷贝到16位寄存器中。 |
| MOV r32,r/m32 |
将32位寄存器或32位的内存操作数拷贝到32位寄存器中。 |
| MOV r8, imm8 |
将8位立即数的内容拷贝到8位寄存器中。 |
| MOV r16, imm16 |
将16位立即数的内容拷贝到16位寄存器中。 |
| MOV r32, imm32 |
将32位立即数的内容拷贝到32位寄存器中。 |
以上指令取自Intel白皮书,释义如下:
r 通用寄存器
m 代表内存
imm 代表立即数
r8 代表8位通用寄存器
m8 代表8位内存
imm8 代表8位立即数

lea
LEA指令将存储器操作数mem的4位16进制偏移地址送到指定的寄存器,这里,源操作数必须是存储器操作数,目标操作数必须是通用寄存器。
格式:lea r,m。
功能:获取内存编号而非内存中的内容。

别问,问就是背
| add |
and |
or |
xor |
not |
sub |
| ADD AL, imm8 |
AND AL, imm8 |
OR AL, imm8 |
XOR AL, imm8 |
NOT r/m8 |
SUB AL, imm8 |
| ADD AX, imm16 |
AND AX, imm16 |
OR AX, imm16 |
XOR AX, imm16 |
NOT r/m16 |
SUB AX, imm16 |
| ADD EAX, imm32 |
AND EAX, imm32 |
OR EAX, imm32 |
XOR EAX, imm32 |
NOT r/m32 |
SUB EAX, imm32 |
| ADD r/m8, imm8 |
AND r/m8, imm8 |
OR r/m8, imm8 |
XOR r/m8, imm8 |
|
SUB r/m8, imm8 |
| ADD r/m16,imm16 |
AND r/m16,imm16 |
OR r/m16,imm16 |
XOR r/m16,imm16 |
|
SUB r/m16,imm16 |
| ADD r/m32,imm32 |
AND r/m32,imm32 |
OR r/m32,imm32 |
XOR r/m32,imm32 |
|
SUB r/m32,imm32 |
| ADD r/m16, imm8 |
AND r/m16, imm8 |
OR r/m16, imm8 |
XOR r/m16, imm8 |
|
SUB r/m16, imm8 |
| ADD r/m32, imm8 |
AND r/m32, imm8 |
OR r/m32, imm8 |
XOR r/m32, imm8 |
|
SUB r/m32, imm8 |
| ADD r/m8, r8 |
AND r/m8, r8 |
OR r/m8, r8 |
XOR r/m8, r8 |
|
SUB r/m8, r8 |
| ADD r/m16, r16 |
AND r/m16, r16 |
OR r/m16, r16 |
XOR r/m16, r16 |
|
SUB r/m16, r16 |
| ADD r/m32, r32 |
AND r/m32, r32 |
OR r/m32, r32 |
XOR r/m32, r32 |
|
SUB r/m32, r32 |
| ADD r8, r/m8 |
AND r8, r/m8 |
OR r8, r/m8 |
XOR r8, r/m8 |
|
SUB r8, r/m8 |
| ADD r16, r/m16 |
AND r16, r/m16 |
OR r16, r/m16 |
XOR r16, r/m16 |
|
SUB r16, r/m16 |
| ADD r32, r/m32 |
AND r32, r/m32 |
OR r32, r/m32 |
XOR r32, r/m32 |
|
SUB r32, r/m32 |

OD技巧
快捷键
ctrl+g : 跳转到指定输入地址
ctrl+f:搜索命令的位置
f2 : 设置断点,切换断点
f8: 单步步过
f9:运行
f12:暂停
窗口布局

- 数据窗口和其他窗口的数据位置相反,为小端序,即高位数据存放在低位地址。
- 命令接口常用命令:
- dd、dw、db,按四字、双字、字节显示对应内存数据。
缓存清理
有时OD打开之后,会出现反汇编窗口一段代码变灰或者提示哪里下了断点的情况,这是OD同级目录下的udd文件夹下的缓存文件导致的,删掉里面的文件即可解决该问题。
寄存器
|
通用寄存器 |
|
主要用途 |
编号(二进制) |
| 32位 |
16位 |
8位 |
|
|
| EAX |
AX |
AL |
累加器 |
000 |
| ECX |
CX |
CL |
计数 |
001 |
| EDX |
DX |
DL |
I/O指针 |
010 |
| EBX |
BX |
BL |
DS段的数据指针 |
011 |
| ESP |
SP |
AH |
堆栈指针 |
100 |
| EBP |
BP |
CH |
SS段的数据指针 |
101 |
| ESI |
SI |
DH |
字符串操作的源指针;SS段的数据指针 |
110 |
| EDI |
DI |
BH |
字符串操作的目标指针;ES段的数据指针 |
111 |
| 32位专用寄存器 |
主要用途 |
存储数据的范围 |
| EIP |
保存当前下一条运行指针地址 |
0 - 0xFFFFFFFF |

辅以神兵OD,逆向美哉!



内存
基本概念
寄存器位于CPU内部,执行速度快,但比较贵;而内存通过引脚与CPU相连,速度相对较慢,但成本较低,所以可以做的很大。寄存器和内存没有本质区别,都是用于存储数据的容器,都是定宽的。
内存的数量特别庞大,无法每个内存单元都起一个名字,所以用编号来代替,我们称计算机CPU是32位或者64位,主要指的就是内存编号的宽度(寻址宽度),而不是寄存器的宽度。
计算机内存的每一个字节会有一个编号(即内存编号的单位是字节),编号从0开始,32位计算机的编号最大是32位,也就是FFFFFFFF,也就是说,32位计算机内存寻址的最大范围是FFFFFFFF+1 , 即4GB,这也是为什么我们在一个XP的系统上面如果物理内存超过4G是没有意义的原因。
[内存编号]表示地址,每个内存单元的宽度为8,超过8位的数据会溢出到下一个内存单元。
内存读写
- 内存读:
mov eax,dword ptr ds:[0x0012FF34]
- 内存写:
mov dword ptr ds:[0x0012FF34],0x12345678
- ptr: Point 代表后面是一个指针 (指针的意思就是里面存的不是普通的值,而是个地址)
- 注意:地址编号不要随便写,因为内存是有保护的,并不是所有的内存都可以直接读写(需要特别处理)

寻址公式
1. [立即数]
#内存读
mov eax,dword ptr ds:[0x19FF74]
#内存写
mov dword ptr ds:[0x19FF74],0xAABBCCDD
#获取内存编号
lea ecx,dword ptr ds:[0x19FF74]

2. [reg]
#内存读
mov ecx,0x19ff74
mov eax,dword ptr ds:[ecx]
#内存写
mov dword ptr ds:[ecx],0x87654321
#获取内存编号
lea eax,dword ptr ds:[ecx]

3. [reg+立即数]
#内存读
mov ecx,0x19ff74
mov eax,dword ptr ds:[ecx+4]
#内存写
mov dword ptr ds:[ecx+0xd],0x87654321
#获取内存编号
lea eax,dword ptr ds:[ecx+4]

*4. [reg+reg{1,2,4,8}]**:注意,这里只能取1,2,3,4中的一个。
#内存读
mov ecx,0x19ff74
mov edx,2
mov eax,dword ptr ds:[ecx+edx*4]
#内存写
mov dword ptr ds:[ecx+edx*4],0x87654321
#获取内存编号
lea eax,dword ptr ds:[ecx+edx*4]

*5. [reg+reg{1,2,4,8}+立即数]**
#内存读
mov ecx,0x19ff74
mov edx,2
mov eax,dword ptr ds:[ecx+edx*4+4]
#内存写
mov dword ptr ds:[ecx+edx*4+4],0x87654321
#获取内存编号
lea eax,dword ptr ds:[ecx+edx*4+4]

堆栈操作
堆栈模型:

压入数据:
#模拟栈顶栈底
mov ebx,0x13ffdc #base
mov edx,0x13ffdc #top
#方式1
mov dword ptr ds:[edx-4],0xaaaaaaaa
sub edx,4
#方式2
sub edx,4
mov dword ptr ds:[edx],0xaaaaaaaa
#方式3
mov mov dword ptr ds:[edx-4],0xaaaaaaaa
lea edx,dword ptr ds:[edx-4]
#方式4
lea edx,dword ptr ds:[edx-4]
mov mov dword ptr ds:[edx],0xaaaaaaaa
读取数据:
#通过base加偏移读取第n个压入的数,n需指定
mov esi,dword ptr ds:[ebx-4*n]
#通过top加偏移读取
mov esi,dword ptr ds:[ebx+4*(n-1)]
弹出数据:
#方法1
mov ecx,dword ptr ds:[edx]
lea edx,dword ptr ds:[edx+4]
#方法2
mov esi,dword ptr ds:[edx]
add edx,4
#方法3
lea edx,dword ptr ds:[edx+4]
mov edi,dword ptr ds:[edx-4]
push指令:完成上述模拟的压入数据操作,ebx和esp分别为占地和栈顶,由cpu调配,随着压栈操作而自动变化,与容器宽度有关加减2或4。
- push r32:esp-4
- push r16:esp-2
- push m16:esp-2
- push m32:esp-4
- push imm8/imm16/imm32:esp-4

pop指令:完成上述模拟的弹出数据操作,ebx和esp分别为占地和栈顶,由cpu调配,随着出栈操作而自动变化。
- pop r32:esp+4
- pop r16:esp+2
- pop m16:esp+2
- pop m32:esp+4

pushad指令:将8个通用寄存器的内容压栈,现场保护。

popad指令:pushad的逆操作,恢复现场。
标志寄存器
别问,问就是背

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

PF
奇偶标志PF(Parity Flag):奇偶标志PF用于反映运算结果中最后一个字节中“1”的个数的奇偶性,如果“1”的个数为偶数,则PF的值为1,否则其值为0。
00010011 00010010
mov ax,0x1312
00010011 00010011 偶数 ,最后一字节为奇数-->p=0
add ax,1
00010011 00010100 奇数,最后一字节为偶数-->p=1
add ax,1


AF
辅助进位标志(Auxiliary Carry Flag),在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:
- 在字操作时,发生低字节向高字节进位或借位时;
- 在字节操作时,发生低4位向高4位进位或借位时。
MOV AX,0x100f
ADD AX,0x4

ZF
零标志(Zero Flag):零标志ZF用来反映运算结果是否为0,如果运算结果为0,则其值为1,否则其值为0。

SF
符号标志(Sign Flag):符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。
mov al,0x82
add al,1

OF
溢出标志(Overflow Flag):溢出标志OF用于反映有符号数加减运算所得结果是否溢;如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0。
最高位进位与溢出的区别:
- 进位标志表示无符号数运算结果是否超出范围.
- 溢出标志表示有符号数运算结果是否超出范围.
是否溢出的判别规律:

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

DF
方向标志位,默认从右操作数到左操作数,可以通过CLD和STD指令修改DF的值。
清方向位指令CLD(Clear Direction Flag):DF←0
置方向位指令STD(Set Direction Flag):DF←1
相关指令
ADC
带进位加法(C位参与运算),两边不能同时为内存,且数据宽度要一致。
格式:ADC R/M,R/M/IMM
ADC AL,CL
ADC BYTE PTR DS:[19ff74],2
ADC BYTE PTR DS:[19ff74],AL

SBB
带借位减法(C位参与运算),两边不能同时为内存,且数据宽度要一致。
格式:SBB R/M,R/M
SBB AL,CL
SBB BYTE PTR DS:[19ff74],2
SBB BYTE PTR DS:[19ff74],AL

XCHG
交换数据,两边不能同时为内存,且数据宽度要一致。
格式:XCHG R/M,R/M
XCHG AL,CL
XCHG DWORD PTR DS:[19ff74],EAX
XCHG BYTE PTR DS:[19ff74],AL

MOVS
移动数据,把ESI存储的内存地址中的数据复制到EDI存储的内存地址中,每复制一次,两个操作数均向上或下偏移4/2/1个字节(地址递增或递减,便于复制下一串内容),当D=0时递增,D=1时递减。
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


STOS
将AL/AX/EAX的值存储到[EDI]指定的内存单元,D=0时地址递增,D=1时地址递减。
STOS BYTE PTR ES:[EDI],简写为STOSB
STOS WORD PTR ES:[EDI],简写为STOSW
STOS DWORD PTR ES:[EDI],简写为STOSD

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

JCC
比较指令
cmp
格式:CMP R/M,R/M/IMM
该指令是比较两个操作数,实际上,它相当于SUB指令,但是相减的结果并不保存到第一个操作数中;只是根据相减的结果来改变零标志位的(其他标志位也可以更改),当两个操作数相等的时候,零标志位置1。

test
指令格式:TEST R/M,R/M/IMM
该指令在一定程序上和CMP指令时类似的,两个数值进行与操作,结果不保存,但是会改变相应标志位;用这个指令,可以确定某寄存器是否等于0。

jmp
无条件转移指令,仅可以修改EIP的值,相当于mov eip,立即数/寄存器。
格式:jmp 寄存器/立即数
call
call指令用于调用子程序,先将当前的EIP压栈保存,然后jmp 目标地址,相当于:
PUSH eip
mov eip,地址A/寄存器
格式:call 地址A/寄存器
ret
ret指令用于回到程序调用点,与call配套使用,先将eip出栈,然后jmp eip,相当于:
lea esp,[esp+4]
mov eip,[esp-4]
格式:ret
别问,问就是背
| 指令 |
解释 |
条件 |
例子 |
JE、JZ |
结果为0时跳转(相等时跳转) |
ZF=1 |
cmp al,al jz 0x19ff74 |
JNE, JNZ |
结果不为零则跳转(不相等时跳转) |
ZF=0 |
cmp al,cl jnz 0x19ff74 |
JS |
结果为负则跳转 |
SF=1 |
mov al,1 sub al,3 js 0x19ff74 |
JNS |
结果为非负则跳转 |
SF=0 |
mov al,2 sub al,1 jns 0x19ff74 |
JP, JPE |
结果中1的个数为偶数则跳转 |
PF=1 |
cmp al,al jp 0x19ff74 |
JNP, JPO |
结果中1的个数为偶数则跳转 |
PF=0 |
mov al,1 cmp al,0xff jnp 0x19ff94 |
JO |
结果溢出了则跳转 |
OF=1 |
mov al,07f add al,4 jo 0x19ff74 |
JNO |
结果没有溢出则跳转 |
OF=0 |
mov al,07f add al,0 jno 0x19ff74 |
JB, JNAE |
小于则跳转 (无符号数) |
CF=1 |
mov al,2 mov cl,1 cmp cl,al jb 0x19ff90 |
JNB, JAE |
大于等于则跳转 (无符号数) |
CF=0 |
mov al,2 mov cl,1 test cl,al jb 0x19ff90 |
JBE, JNA |
小于等于则跳转 (无符号数) |
CF=1 or ZF=1 |
cmp al,al jbe 0x19ff90 |
JNBE, JA |
大于则跳转(无符号数) |
CF=0 and ZF=0 |
mov al,2 mov cl,1 cmp al,cl jnbe 0x19ff90 |
JL, JNGE |
小于则跳转 (有符号数) |
SF≠ OF |
mov al,1 mov cl,2 cmp al,cl jl 0x19ff74 |
JNL, JGE |
大于等于则跳转 (有符号数) |
SF=OF |
mov al,1 mov cl,2 cmp cl,al jnl 0x19ff74 |
JLE, JNG |
小于等于则跳转 (有符号数) |
ZF=1 or SF≠ OF |
mov al,2 mov cl,1 cmp cl,al jle 0x19ff90 |
JNLE, JG |
大于则跳转(有符号数) |
ZF=0 and SF=OF |
mov al,2 mov cl,1 cmp cl,al jnle 0x19ff90 |
first crackme
程序链接
信息收集

设置断点
在Win32弹窗接口下断点:bp MessageBoxA

运行程序


跟踪堆栈
右键堆栈窗口栈顶处[0019FDB4],Follow In Disassember,观察反汇编窗口,如下:

破解尾声
禁用掉之前win32的断点后,再次点击小三角运行程序,并输入注册信息,点击确定,观察反汇编窗口,如下:

跟踪堆栈,回溯上判断程序的入口,在jcc指令处设置断点,并清除其它断点,然后修改jcc指令为相反逻辑,如下:


保存程序
先将断点取消,然后右键反汇编窗口,选择Copy to executable->All Modifications,这是跳转到保存结束的界面,右键反汇编窗口选择save即可。

新的征程
运行破解之后的程序CRACKME233.EXE,如果输入注册信息包含数字的话,第一次弹窗仍然为判断失败的提示,点击确定后才弹出判断成功的提示,接下来想办法干掉第一次判断失败的提示!
|