#起因
看到坛友求助的一个python破解问题:
大佬们帮忙看下这个软件汇编的思路 - 吾爱破解 - 52pojie.cn
首先说明,我不熟悉pyInstaller,这是一次尝试,期间也查了很多资料和教程,如果有更好的方法还请诸位大佬赐教!
#简单分析
#查壳
使用Die查壳,结果如下:
可以看到,程序是使用python编写、PyInstaller打包的
#解包
既然是使用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加密校验码:

注意选择私钥加密
输入到程序里,注册成功