工具安装

程序分析

image-20240421234051364

点击help,可以看到提示:破解目标是使所有的按钮消失,让程序下面的蓝色logo完整显现出来,不允许爆破。

image-20240422105549546

逆向分析

脱壳

用Exeinfo PE工具查看该程序,得到信息如下图,可知该程序没有加壳,用Delphi语言编写。

image-20240422110526986

并不需要脱壳处理

DarkDe查看事件控件

因为是delphi的程序,先用Delphi的专用反编译工具DarkDe搜索涉及到的控件和事件函数信息

事件信息:

image-20240422111325644

控件信息:

image-20240422111332284

可以看到程序有以下几个事件:

  • Cancella按钮的点击事件
  • About按钮的点击事件
  • Registerz按钮的点击事件
  • Again按钮的点击事件

总共四个按钮事件。

在程序最开始并没有看到Again按钮,猜测为一个隐藏按钮。最开始只有一个Register按钮,因此我们后续就从Registerz按钮的点击事件开始分析

爆破

将程序拖入ollydbg

分别在前面发现的事件对应的位置:00442B98,00442F28,004430BC上下断点

首先在Registerz按钮点击事件的位置 00442F28 下断点

image-20240422113737449

运行程序,随便输入name = 123,Codice = 123,F8向下单步执行。

可以看到这里有两个calll,调用了两个函数,去对应的地址看一下,第一个函数为获取codice,第二个函数检测codice是否为纯数字。

image-20240422155744890

继续单步执行,走到下面的 je 跳转代码后,跳转到了 00442F9D

分析下面的 je 跳转代码,可以知道这里对codice进行了验证,如果codice是纯数字则跳转到 00442F9D 继续执行,如果不是纯数字则会弹窗 "You MUST insert a valid Long Integer Value in the Code Editor... Thank you :)"

image-20240422161158972

所以正常情况下应该执行 00442F9D ,观察 00442F9D 后的代码,可以看到走到 00442F9F 又会跳转到 00442FFB ,但是这个跳转处的注释为 “Please… The Code Must be >0”,很明显这是一个异常提示的跳转,所以在这里未发生跳转为程序正常执行

image-20240422161337070

继续查看未发生跳转部分的汇编代码,可以看到 00442FC0 处存在第三个跳转到 00442FF2 ,从 00442FF2 处向下看经过了 00442FFB 处也就是错误提示的字符串,所以在 00442FC0 处我们不让程序发生跳转,将 je 改为NOP

image-20240422162604439

F9继续运行程序,可以看到register按钮消失,出现了新的按钮again,直接点击again,可以看到停在了我们前面打的断点 004430BC 处。

image-20240422163242089

向下翻看代码发现和前面的register按钮函数类似,我们执行同样的操作,将找到的位置 00443159 改为NOPs。

F9继续运行,可以看到已经成功实现了logo的还原,成功爆破。

image-20240422163618513

序号生成算法分析

根据前面的爆破可以知道,register按钮和again按钮消失的逻辑分别出现在 00442F9F00443159 之后。

所以首先对register按钮的 00442F9F 进行分析,分析后面的代码可以知道 00442FB9 为算法函数

image-20240422164900113

crtl + G 跟进到 00442FB9 查看算法函数,可以看到要求用户名要大于4个字符

image-20240422165807004

在算法后续的计算中涉及到了 eax ,回到前面可以发现在00442FB9 的前面有一个对 eax进行赋值的操作: 00442FB4位置对eax进行了赋值,将地址445830的数值赋给了eax。

image-20240423143154927

再检查在什么地方对地址445830进行了赋值,在地址442FB4处[右键]-[查找参考]-[地址常量],可以查找到程序中所有涉及到这个地址常量的位置。

可以看到这个地址前面存在一个对445830进行赋值的操作,双击查看

image-20240423145554207

可以看到,当Codice输入的不全是数字时才对[0x445830] 赋值操作,而所赋的值为 00442F81 处调用函数 00442A8C 的返回值

image-20240423145736385

对00442A8C进行进一步分析,可以知道对应的[0x445830] 赋值方法

image-20240423150158348

发现存在逻辑:

第一次输入的密码不为纯数字的时候,程序会根据密码生成一个值 [0x445830],后续密码不为纯数字时需要根据这个值来进行运算,所以第一次输入的时候,密码一定要为纯字母,不然无法注册成功。

接下来进行了两层循环,计算的是用户名的第一位和最后一位的乘积,然后再乘以 [0x445830] 。外层循环变换用户名最后一位,每次往前移动一位。内层循环变换用户名第一位,每次往后移动一位。接着将结果保存到eax。

image-20240422170314520

  • 将eax对 0xA2C2A 取模,记为结果1
  • 将输入的密码除以 0x59 加上密码mod0x50,结果再加1,记为结果2
  • 比较结果1和结果2是否相等。相等则返回1,消失按钮。不相等则返回0。

image-20240422170812472

again 按钮的函数算法类似

注册机

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
34
35
36
37
38
39
import sys

szBuff = bytearray(30)
CoBuff = bytearray(30)
Regcode = 0
a = 1
Temp = 0x37B
Sum = 0

print("CrackeMe007注册机使用说明:")
print("输入纯字母的codice,关闭报错弹窗后填入计算出的正确codice")
print("请输入不少于6位的全字母codice:")
szBuff = input().encode('utf-8')

for i in range(len(szBuff) - 1):
Temp = Temp + ((szBuff[i + 1] % 0x11 + 1) * szBuff[i])

Temp = Temp % 0x7148

print("请输入用户名:不少于5位的纯数字")
CoBuff = input().encode('utf-8')

for i in range(len(CoBuff)):
Sum = Sum + CoBuff[i]

Sum = (Sum * Sum * Temp) % 0XA2C2A

for i in range(0X50):
Regcode = (Sum - i - 1) * 0X59
for j in range(0X50):
Temp = (Regcode + j) % 0X50
if Temp == i:
print("用户名:%s对应的第%d个可用的注册码:%d" % (CoBuff.decode('utf-8'), a, Regcode + j))
a = a + 1
Temp = 0x60

input("Press Enter to exit...")
sys.exit()

随便构造纯数字的name和全字母的codice

image-20240423140221413

产生报错,用上面的注册机生成密码

image-20240423140316807

输入生成的第一个codice,可以看到register按钮消失

image-20240423140341510

再输入前面固定的字符串,点击again按钮

image-20240423140629271

再输入前面注册机得到的codice,成功显现所有的Logo,完成破解

image-20240423140701596

参考文章