起因
看到坛友求助的一个python破解问题:
大佬们帮忙看下这个软件汇编的思路 – 吾爱破解 – 52pojie.cn
首先说明,我不熟悉pyInstaller,这是一次尝试,期间也查了很多资料和教程,如果有更好的方法还请诸位大佬赐教!
简单分析
查壳
使用Die查壳,结果如下:
解包
既然是使用PyInstaller打包的,那么我们可以尝试解包它,看看能不能反编译出python代码
解包网站:PyInstaller Extractor WEB
上传AML.exe解包,网站会返回一个压缩包
解压压缩包,我们会得到一堆文件
找到AML.pyc,通过网站反编译为py代码
注意选择uncompyle6引擎,因为相较于pycdc,反编译的代码更完整。
下面是反编译AML.pyc的代码(代码较多,只放出关键部分):
import importlib, os, sys, threading, time
from time import sleep
from tkinter import *
from tkinter import messagebox
from tkinter.ttk import *
from BootUSB import BootUSB
from pyamlboot.aml_verifysn_s905lsb_ import isVerify, reg #导入pyamlboot
from pyamlboot.rkamlboot import RKAmlogicSoC
from usb_manager import USBManager
from six.moves.http_cookies import SimpleCookie
num = 0
while isVerify() == False: #授权验证
if num > 0:
sys.exit()
else:
reg()
num += 1
if __name__ == "__main__":
win = WinGUI()
win.mainloop()
如果isVerify()返回False,说明验证失败,程序会尝试注册一次(num从0开始,第一次调用reg(),然后num变为1,下次循环时num>0就会退出)
可以发现授权验证部分在pyamlboot包中,找到对应的pyamlboot文件夹
反编译aml_verifysn_s905lsb_.pyc,关键代码如下(可以使用ai分析):
import time
import tkinter
import winreg
from tkinter import END
import M2Crypto
from pyamlboot import sn
REG_KEY = '_aml_s905l3_sb' # 注册表键名
PEM = '2D2D2D2D2D424547494E205055424C4943204B45592D2D2D2D2D0A4D4947664D413047435371475349623344514542415155414134474E4144434269514B42675143564D6C396E635659395A616259496D3862583268756F504F620A4B617950334165704A47355774765A544F6C416F3859304737665A6541693941636A4745696A4A63547A465932773977347033454A5676767939464977744D4A0A3455353157613334647653524F683673356C59574B4F59716A2F74414868624A507650705873612F58416139467A376A7569735769772B52725458564A4A744C0A6C7652514F50444434616B44592B34796F514944415141420A2D2D2D2D2D454E44205055424C4943204B45592D2D2D2D2D' #十六进制编码的RSA公钥
key_path = 'SOFTWARE\\Software' # 注册表位置
def decrypt(msg):
key = bytes.fromhex(PEM) # 将十六进制公钥转字节
bio = M2Crypto.BIO.MemoryBuffer(key)
rsa_pub = M2Crypto.RSA.load_pub_key_bio(bio) # 加载公钥
return rsa_pub.public_decrypt(msg, M2Crypto.RSA.pkcs1_padding) # 公钥解密
def verify_reg(code):
try:
if not code: return False
# 解密注册码(HEX转字节->解密->解码)
plain = decrypt(bytes.fromhex(code)).decode()
# 格式校验(需包含冒号分隔符)
if ':' not in plain: return False
sn_part, date_part = plain.split(':',1)
# 硬件校验(比对磁盘序列号)
if sn.get_disk_info() != sn_part: return False
# 有效期校验(日期格式YYYYMMDD)
expire_date = int(date_part.replace('-',''))
current_date = int(time.strftime('%Y%m%d'))
return expire_date > current_date
except Exception:
return False
def reg():
import tkinter as tk
ScrolledText = ScrolledText
import tkinter.scrolledtext
root = tk.Tk()
curWidth = 400
curHight = 320
(scn_w, scn_h) = root.maxsize()
cen_x = (scn_w - curWidth) / 2
cen_y = (scn_h - curHight) / 2
size_xy = '%dx%d+%d+%d' % (curWidth, curHight, cen_x, cen_y)
root.geometry(size_xy)
root.resizable(0, 0)
root.title('软件注册')
label_frame = tk.LabelFrame(root, '机器码', **('text',))
label_frame.grid(0, 0, 10, 10, **('row', 'column', 'padx', 'pady'))
_sn = sn.get_disk_info()
isVerify = verify_reg(verify_register_info(REG_KEY))
machine_code = tk.StringVar(root, _sn, **('value',))
entryMachineCode = tk.Entry(label_frame, machine_code, 30, **('textvariable', 'width'))
if isVerify:
entryMachineCode.configure('disabled', **('state',))
entryMachineCode.grid(0, 0, **('row', 'column'))
result_frame = tk.LabelFrame(root, '注册码', **('text',))
result_frame.grid(1, 0, 10, 10, **('row', 'column', 'padx', 'pady'))
text_widget = ScrolledText(result_frame, 50, 10, **('width', 'height'))
if isVerify:
text_widget.insert(END, '软件已注册')
text_widget.configure('disable', **('state',))
text_widget.pack(True, 'both', **('expand', 'fill'))
def callbackMachineCode(event = None):
entryMachineCode.event_generate('<<Copy>>')
menuPackage = tk.Menu(root, False, **('tearoff',))
menuPackage.add_command('复制', callbackMachineCode, **('label', 'command'))
def popupMachineCode(event = None):
menuPackage.post(event.x_root, event.y_root)
entryMachineCode.bind('<Button-3>', popupMachineCode)
def register():
code_input = text_widget.get('1.0', 'end-1c')
isVerify = verify_reg(code_input)
if isVerify:
write_register_info(REG_KEY, code_input) # 写入注册表
tk.messagebox.showinfo('成功', '恭喜', **('message',))
root.destroy()
return True
None.messagebox.showinfo('警告!', '不是合法的注册码', **('message',))
return False
button = tk.Button(root, '注册', register, 'disabled' if isVerify else 'active', **('text', 'command', 'state'))
button.grid(2, 0, **('row', 'column'))
root.mainloop()
def isVerify():
if not verify_reg(verify_register_info(REG_KEY)):
del_winreg() # 验证失败时删除注册表项
return False
return True
def delete_key_value():
pass
# WARNING: Decompyle incomplete
def del_winreg():
delete_key_value()
def write_register_info(key, value):
pass
# WARNING: Decompyle incomplete
def verify_register_info(key):
registry_key = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
# WARNING: Decompyle incomplete
有一部分没反编译成功,但不影响分析
经分析,程序通过公钥解密注册码,将解密后的数据与校验码对比,如果一致就注册成功
程序的校验码应该为:”机器码:到期时间”
程序使用了RSA算法,没有私钥没法写注册机,所以只能另辟蹊径
修改内存
既然公钥是直接写在代码中的,那么我们可以将内置的公钥替换为我们自己的公钥
随便生成一对公私钥,注意密钥长度为1024(这里对比程序内置的公钥可以发现)
将公钥转为hex,记下来
打开Cheat Engine,选择AML.exe(一般是下面一个,是后出现的)
扫描的值就是程序内置的公钥:
2D2D2D2D2D424547494E205055424C4943204B45592D2D2D2D2D0A4D4947664D413047435371475349623344514542415155414134474E4144434269514B42675143564D6C396E635659395A616259496D3862583268756F504F620A4B617950334165704A47355774765A544F6C416F3859304737665A6541693941636A4745696A4A63547A465932773977347033454A5676767939464977744D4A0A3455353157613334647653524F683673356C59574B4F59716A2F74414868624A507650705873612F58416139467A376A7569735769772B52725458564A4A744C0A6C7652514F50444434616B44592B34796F514944415141420A2D2D2D2D2D454E44205055424C4943204B45592D2D2D2D2D
好了,接下来我们就可以用私钥 加密 校验码 生成 注册码了
机器码就是程序上显示的:
校验码需要加上到期时间,随便构造一个:1136987320:20251213
用rsa加密校验码:
注意选择私钥加密
输入到程序里,注册成功