如何从PB写的程序中提取注册算法,写出注册机
目标软件:机动车驾驶员模拟考试系统
加密方式:机器码+注册码,未注册有功能限制
作 者: 雷电(lzrlzr)
破解时间:2006-04-08
解密工具:PBKiller Powerbuild10
破解说明:主要是说明如何去分析PB程序,并找出注册算法,Powerbuild的编程方法就不提了吧。
太忙,好久没写了,把此文送给我的朋友。
朋友发来一个程序:机动车驾驶员模拟考试系统,说让我试试,在安装时就发现安装进度上提示正在安装PB*.dll,我想肯定是个
Powerbuild写的程序。安装完以后,在安装目录中发现有PBVM80.DLL,确认了这个程序是用PB8.0写的软件。
软件一启动主程序jdriver.exe,就会弹出一个要求注册的窗口,窗口的标题是:软件注册,上面有两个按钮,一个是:注册确认
,一个是:继续试用。随意输入一个注册码,就会提示当前注册码错误。
于是打开PBKiller(感谢PBKiller的作者kivens,写出这个超强的反编译PB程序的工具,作者好象也在论坛中),对软件的安装目
录中的主程序和 dll 进行反编译,最后发现在private.dll 中有一个窗体w_registe,这个窗体的propertier(属性)中有
string title = "软件注册"
在窗体的controls(控制)中有几个按钮,
按钮cb_1 的属性是: string text = "注册确认"
按钮cb_2 的属性是: string text = "继续试用"
那么从这个可以判断出w_registe窗体就是软件启动时的注册界面,按钮cb_1就是注册确认。那么在按钮cb_1的events(事件)
中可以找到clicked事件,事件中的代码如下(为了减少长度,我删掉了代码中的注释):
integer li_row
string ls_reg
if tag <> "" then
return
end if
parent.sle_2.text = upper(trim(parent.sle_2.text))
if parent.sle_2.text = "" then
msg("注册码不能为空,请重新输入!")
parent.sle_2.setfocus()
return
end if
SELECT count ( *) FROM 系统设置表 WHERE 编号 =0 using sqlca;
if li_row = 1 then
update 系统设置表 SET 机器码 =' ' , 注册码 =' ' WHERE 编号 =0 using sqlca;
else
DELETE FROM 系统设置表 WHERE 编号 =0 using sqlca;
INSERT INTO 系统设置表 ( 编号 , 机器码 , 注册码 , 启用时间 , 剩余次数 ) VALUES ( 0 , ' ' , ' ' , getdate
( ) , 0 ) using sqlca;
end if
if sqlca.sqlcode <> 0 then
rollback using sqlca;
msg("保存注册码到系统中失败,请重启后再次注册!")
return
end if
commit using sqlca;
if not gnv_app.of_reged() then ;如果返回值不为真时,注册不成功
msg("当前注册码错误,如果您申请注册码使用的是“" + iff
(parent.rb_2.checked,parent.rb_1.text,parent.rb_2.text) + "”,请先选中“" + iff
(parent.rb_2.checked,parent.rb_1.text,parent.rb_2.text) + "”再注册确认!")
parent.sle_2.setfocus()
return
end if
msg("感谢您对我们软件的注册使用。~n" + "您无私的帮助将是我们发展的最大动力!~n" + "为使您的注册立即生效,系统将退出
并请重新运行!")
parent.cb_2.tag = "T"
close(parent)
halt close
return
这段代码比较容易读懂,注册成功的关键就是gnv_app.of_reged(),这个过程反回值为真时,注册就会成功。
于是我继续反编译找,在jdriver.dll的nvo_app的function中找到如下代码of_reged () returns boolean
string ls_code
string ls_reg
string ls_cpu
string ls_disk
SELECT 注册码 FROM 系统设置表 WHERE 编号 =0 ORDER BY 编号 ASC using sqlca;
if isnull(ls_reg) then
ls_reg = ""
end if
ls_reg = trim(ls_reg)
ls_cpu = trim(of_reg_disk())
ls_disk = trim(of_reg_cpu())
if ls_cpu = "" and ls_disk = "" then
msg("读取系统硬件序列号出错,可能原因如下:~n~n" + "1. 安装目录中文件 GetCpu.DLL 丢失或错误;~n" + "2. 安装
目录中文件 Md5.DLL 丢失或错误;~n" + "3. 本系统在当前操作系统中不能读取硬件数据;~n" + "4. 本机CPU序列号非法(此原因
需更换CPU);~n" + "5. 本机硬盘序列号非法(此原因需更换硬盘);~n" + "6. 其它原因......;")
return false
end if
gnv_info.reg_code_registe = ls_reg
if ls_reg <> "" and upper(ls_reg) = upper(of_reg_arithmetic(ls_disk)) then
gnv_info.reg_code_machine = ls_disk
return true
end if
if ls_reg <> "" and upper(ls_reg) = upper(of_reg_arithmetic(ls_cpu)) then
gnv_info.reg_code_machine = ls_cpu
return true
end if
gnv_info.reg_code_machine = ls_disk + "," + ls_cpu
return false
从这个过程中可能看到,注册成功的关键条件就是
upper(ls_reg) = upper(of_reg_arithmetic(ls_disk))
或者是
upper(ls_reg) = upper(of_reg_arithmetic(ls_cpu))
这也就是作者设计的,注册码可以和本机的cpu绑定,也可以和本机的硬盘号绑定。
呵呵,继续找,在jdriver.dll的nvo_app的function中找到如下代码:
of_reg_arithmetic (string as_code) returns string
char lc_key1[]
char lc_key2[]
char lc_base_char
integer li_key1
integer li_key2
integer li_key
string ls_base
string ls_usercode
integer li_len
integer li_loop
integer li_tmp
integer li_right_mod
integer li_strlen
if as_code = "" then
return as_code
end if
ls_base = gnv_info.basestr
li_len = 50
if len(as_code) < 50 then
li_len = len(as_code)
end if
for li_loop = 1 to li_len
lc_key1[li_loop] = mid(as_code,li_loop,1)
li_key1 += asc(lc_key1[li_loop])
next
for li_loop = 1 to li_len step 2
lc_key2[li_loop] = mid(as_code,li_loop,1)
li_key2 += asc(lc_key2[li_loop])
next
li_key = li_key1 + li_key2 + 50
li_strlen = 25
if gnv_info.ib_project then
li_strlen = 13
end if
for li_loop = 1 to li_strlen
li_len = len(ls_base)
li_tmp = mod(li_key,li_len)
li_tmp = li_tmp + li_right_mod
if li_tmp > li_len then
li_tmp = mod(li_tmp,li_len)
end if
if li_tmp = 0 then
li_tmp = 1
end if
lc_base_char = char(mid(ls_base,li_tmp,1))
ls_usercode = ls_usercode + string(lc_base_char)
li_right_mod = li_tmp
ls_base = left(ls_base,li_tmp - 1) + right(ls_base,li_len - li_tmp)
next
return f_public_replaceall(upper(ls_usercode),"O","0") ;把字母“O”,全部用数字“0”代替
呵呵,OK,这就是根据机器码生成注册码的字符串变换算法了,用这个就可以写出注册机了。
算法总结:
这个程序根据机器的cpu,硬盘号,经过变换得到机器码(机器码的生成算法可以在of_reg_cpu () ,of_reg_disk ()中得到
,就是把机器的cpuid或硬盘id分成两部分,分别进行md5变换,再组合成机器码。),根据机码变换得到注册码,再和输入的注册码
字符串进行比较,如果相同就注册成功。作者在验证注册码是否正确时使用的注册算法是正向算法,不需要写出逆算法就可以写出注
册机。作者在算法中设计的比较人性化的一点就是注册码中不使用字母“O”,全部用数字“0”代替。
到此,在 pb中新建一个工程,增加一个窗体,写出注册机,我用的是Powerbuild10(pb我用的也不熟,只会作注册机,呵)。
(算法中用到的f_public_replaceall()定义如下,
f_public_replaceall (string as_string1,string as_string2,string as_string3) returns string
存在于OBJECT.dll中)
最后,就此程序加密方法的改进,给作者一点建议(呵呵,不好意思啊):
1、程序中不要包含注册机生成器窗体部分(private.dll 中有一个窗体w_register,就是作者的注册码生成器的窗体,更要命的是包
含完整的注册码生成算法,和我写的注册机代码几乎是一样的,呵呵)
2、取机器 cpuid和硬盘id时,最好放到一个比较好的程序中,最好包含在程序中,不要使用单独的dll文件(我想作者可能是使用了
别人提供的标准函数),这种dll自身就不安全,可以对dll进行修改,让它在所有的机器上生成一模一样的机器码,只需注册一套,
就可以达到复制分发的目的。
3、注册算法的使用要合理,尽理使用一些非对称的加密算法,注册码的生成算法和校验算法不要同时出现在发布的程序中,这样很难
逆出加密算法。
最后,请大家支持国产软件,如果经济条件可以,对软件有需要,还是可以注册一下。
附作者的注册机生成算法(我的就不写出来了,和这个差不多):
parent.sle_2.text = trim(parent.sle_2.text)
if tag = "" then
parent.cb_1.triggerevent(clicked!)
return
end if
parent.mle_1.text = ""
if len(parent.sle_2.text) <> 13 then
msg("机器码长度必须为13位!")
return
end if
parent.sle_2.text = f_public_replaceall(parent.sle_2.text,"O","0")
parent.sle_3.text = upper(gnv_app.of_reg_arithmetic(parent.sle_2.text))
if gnv_info.ib_project then
return
end if
parent.mle_1.text = "尊敬的客户,您好:~r~n" + "感谢你注册软件《" + gnv_info.soft_name_cn + "》成功!~r~n" + "你
的机器码:" + parent.sle_2.text + "~r~n" + "您的注册码:" + parent.sle_3.text + "~r~n" + "注册时间:" + string
(today(),"YYYY年MM月DD日") + "~r~n~r~n" + "希望本软件能为你的驾车增添更多的乐趣!~r~n~r~n" + "软件升级与题库更新请
访问:" + gnv_info.comp_http + "~r~n" + "如有软件疑问请发邮件到:" + gnv_info.comp_mail + "~r~n" + "再次感谢你对
本软件的支持,祝你顺利过关!~r~n" + " 劲维科技~r~n" + " " + string
(today(),"yyyy-mm-dd")
return
[培训]《安卓高级研修班(网课)》月薪三万计划,掌
握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法