记一次PyInstaller逆向

#起因

看到坛友求助的一个python破解问题:

大佬们帮忙看下这个软件汇编的思路 - 吾爱破解 - 52pojie.cn

首先说明,我不熟悉pyInstaller,这是一次尝试,期间也查了很多资料和教程,如果有更好的方法还请诸位大佬赐教!

#简单分析

#查壳

使用Die查壳,结果如下:

image ​可以看到,程序是使用python编写、PyInstaller打包的

#解包

既然是使用PyInstaller打包的,那么我们可以尝试解包它,看看能不能反编译出python代码

解包网站:PyInstaller Extractor WEB

上传AML.exe解包,网站会返回一个压缩包

image

解压压缩包,我们会得到一堆文件

image

找到AML.pyc,通过网站反编译为py代码

image

注意选择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文件夹

image

反编译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算法,没有私钥没法写注册机,所以只能另辟蹊径

#修改内存

既然公钥是直接写在代码中的,那么我们可以将内置的公钥替换为我们自己的公钥

image

随便生成一对公私钥,注意密钥长度为1024(这里对比程序内置的公钥可以发现)

将公钥转为hex,记下来

image

打开Cheat Engine,选择AML.exe(一般是下面一个,是后出现的)

image

image

扫描的值就是程序内置的公钥:

2D2D2D2D2D424547494E205055424C4943204B45592D2D2D2D2D0A4D4947664D413047435371475349623344514542415155414134474E4144434269514B42675143564D6C396E635659395A616259496D3862583268756F504F620A4B617950334165704A47355774765A544F6C416F3859304737665A6541693941636A4745696A4A63547A465932773977347033454A5676767939464977744D4A0A3455353157613334647653524F683673356C59574B4F59716A2F74414868624A507650705873612F58416139467A376A7569735769772B52725458564A4A744C0A6C7652514F50444434616B44592B34796F514944415141420A2D2D2D2D2D454E44205055424C4943204B45592D2D2D2D2D

image ​点击确定后就修改成功了

好了,接下来我们就可以用私钥 加密 校验码 生成 注册码了

机器码就是程序上显示的:

image

校验码需要加上到期时间,随便构造一个:1136987320:20251213​

用rsa加密校验码:

image

注意选择私钥加密

输入到程序里,注册成功

image

标签: 无

许可协议

本文作者 , 采用 CC-BY-SA-4.0 许可协议,转载请注明出处。

添加新评论