爆破就是通过修改软件机器码(汇编代码)达到绕过注册验证环节的目的
爆破中修改机器码通常都是修改跳转指令,而跳转指令实现与否多和标志寄存器相关(jmp,call除外),所以这里不得不复习下关于标志寄存器的内容:
标志位寄存器
标志位寄存器能够设置各位标志位,但某个位是否被置位或复位依赖于计算结果或别的事件的发生。我并不想在这里讨论标志寄存器中所有的标志位,我只讨论一些重要的位:
ZF(0标志位)
当计算结果是0的时候,就会将它置位(“比较”实际上是一个不保存结果的减法运算,它只改变ZF)
SF(符号位)
如果被置位,那么就标志计算结果是一个负数
CF(进位标志位)
CF会保存计算过程中在最左边多出来的位(译者注:著者描述得并不完全正确,只要计算结果的最高位产生了一个进位或借位,CF就会被置位)
OF(溢出标志位)
它被置位说明计算过程中发生了溢出。机算结果超过当前计算位数所能表示的范围称为“溢出”。
其实还有很多的标志位,它们暂时对我们没什么用处,这里就不讨论它们了。
下面就是常用的跳转指令了:
条件跳转总汇
下面是所有的条件跳转指令,它们执行与否都依赖于相关标志位的状态。大部分条件跳转指令都拥有很容易记忆的名字,让你不用强行去记忆哪个位被置位时它才发生跳转。
例如:“如果大于等于就跳转”(jge)和“符号标志=溢出标志”一样,而“如果零就跳转”和“如果零标志=1就跳转”一样。
在下表中,“意思”指的是什么样的计算结果该跳转。“如果大于就跳转”意为:
cmp x, y
jmp 如果x比于y
-------------------------------------------------------------------
操 作 码 | 意 思 | 条 件 |
------------------------------------------------------------------
ja | 大于则跳转 | CF=0 且 ZF=0
-----------------------------------------------------------------
jae | 大于或等于则跳转 | CF=0
----------------------------------------------------------------
jb | 小于则跳转 | CF=1
----------------------------------------------------------------
jbe | 小于或等于则跳转 | CF=1 或 ZF=1
----------------------------------------------------------------
jc | 如果进位位被置位则跳转 | CF=1
----------------------------------------------------------------
jcxz | 如果cx的值为0则跳转 | CX=0
----------------------------------------------------------------
je | 相等则跳转 | ZF=1
----------------------------------------------------------------
jg | 大于则跳转(有符号数) | ZF=0 且 SF=OF
----------------------------------------------------------------
jge | 大于或等于则跳转(有符号数) | SF=OF
----------------------------------------------------------------
jl | 小于则跳转(有符号数) | SF != OF
----------------------------------------------------------------
jle | 小于或等于则跳转(有符号数) | ZF=1 或 SF!=OF
----------------------------------------------------------------
jmp | 无条件跳转 | -
----------------------------------------------------------------
jna | 不大于则跳转 | CF=1 或 ZF=1
----------------------------------------------------------------
jnae | 不大于或不等于则跳转 | CF=1
----------------------------------------------------------------
jnb | 不小于则跳转 | CF=0
----------------------------------------------------------------
jnbe | 不小于或不等于则跳转 | CF=1 且 ZF=0
----------------------------------------------------------------
jnc | 进位位没有被置位则跳转 | CF=0
----------------------------------------------------------------
jne | 不相等则跳转 | ZF=0
----------------------------------------------------------------
jng | 不大于则跳转(有符号数) | ZF=1 或 SF!=OF
----------------------------------------------------------------
jnge | 不大于或不等于则跳转(有符号数)| SF!=OF
----------------------------------------------------------------
jnl | 不小于则跳转(有符号数) | SF=OF
----------------------------------------------------------------
jnle | 不小于或不等于则跳转(有符号数)| ZF=0 且 SF=OF
----------------------------------------------------------------
jno | 未溢出则跳转(有符号数) | OF=0
----------------------------------------------------------------
jnp | 奇偶校验位被复位则跳转 | PF=0
----------------------------------------------------------------
jns | 符号位为0则跳转(有符号数) | SF=0
----------------------------------------------------------------
jnz | 不等于0则跳转 | ZF=0
----------------------------------------------------------------
jo | 溢出则跳转(有符号数) | OF=1
----------------------------------------------------------------
jp | 奇偶校验位被置位则跳转 | PF=1
----------------------------------------------------------------
jpe | 奇偶校验位被置位则跳转 | PF=1
----------------------------------------------------------------
jpo | 奇偶校验位被复位则跳转 | PF=0
----------------------------------------------------------------
js | 符号位为1则跳转(有符号数) | SF=1
----------------------------------------------------------------
jz | 等于0则跳转 | ZF=1
----------------------------------------------------------------
所有的跳转指令都需要一个操作数:即要跳转到的内存单元的偏移量。
比较指令
问题又来了,是生么影响标志寄存器呢,因为破解中通常在关键跳前面都有一个比较,正是这些比较影响(置位)了相关标志位。当然不是只有比较才能影响标志位,还有因为算术运算的进位,借位也会影响,但在破解中常见的还是比较。
1)test
test与逻辑与(译者注:即and操作码)的工作几乎相同,将两个操作数进行“与”运算并设置标志寄存器相应的位。但test不保存运算结果。test常用来像下面这个例子一样测试某个位:
test eax, 100b ;(后缀b表示这是一个二进制数)
jnz 跳转目的地
如果eax的右边第3位是1的话,jnz将会发生跳转。test操作码常常被用来测试一个寄存器的值是否为0:
test ecx, ecx
jz 跳转目的地
当ecx为0时,jz就会被执行而发生条转。
2)cmp
cmp的意思是“比较”。它会比较两个值(来自寄存器、内存或立即数)并且当它们相等时将ZF(0标志位)置位。与CF一样,ZF也是标志寄存器中的一个位。
好了理论知识已经完备了,下面实战破解一款小外挂程序(本例无壳)
1,载入OD-->搜索ASCII(查找所有参考字符串,查找UNICODE也可以尝试),
2,来到有注册成功或失败字样的地方,双击就来到调用该字符串的地方了,这时如果感觉代码有点乱(不规则)可以右键分析下代码
我们可以看到在“软件已注册”上面有这样两句代码:
test al,al ;这里就是通过设置标志位从而影响下面一句跳转是否实现的比较了
je short QQ火拼斗.004580C1 ; 怀疑是一处(功能)关键跳
mov edx,QQ火拼斗.004580EC ; 软件已注册
可以看到如果je实现了的话,含有“软件已注册”的代码段就不会被执行了,也就意味着可能就是软件注册失败了,所以我们可以尝试着把这处je该为相反的条件跳转:JNZ
强制让它执行到含有“软件已注册”的代码。
上面我们看到的是两个“软件以注册”的字符串,那么我们是不是两处都要修改呢?我也没有肯定的答案,改一处不行那就只有全改了
双击第二个字符串来到这里:
呵呵,是不是又看到了熟悉的比较指令:test 和紧接着的的条件跳转指令jnz了,和上一处不同的是:这处要是满足条件才跳到并执行含有“软件已注册”的代码,也就意味这不执行就可能是注册失败,所以无论如何我们都想让它跳过来(实现),所以我们可以尝试这该条件跳转jnz为无条件跳转jmp。
然后右键--->复制可执行文件--->选择所有修改--->保存文件即可!
注意:这里是只有两个“软件已注册”,可能有的软件有很多像这样的提示,那么你是不是要一个一个的去改呢?据我的理解,之所以会有多个成功的字样,有可能是功能的限制,一个对应一个功能是否注册成功,所以我们也称这中挨个改跳的为功能爆破。当然如果功能字符串太多了的话将是一个很大的工作量,这是我们就需要找到影响整个注册机制的关键位置(关键跳),这样才能完美爆破。