用 Python 反编译 Python 软件

【文章标题】: 用 Python 反编译 Python 软件
【文章作者】: Ptero
【软件名称】: ****
【下载地址】: ****
【加壳方式】: UPX
【保护方式】: 序列号,重启验证
【使用工具】: 7-zip, LordPE, Python, WinHex
【操作平台】: Windows, Linux
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
——————————————————————————–
【版权声明】: 本文原创于看雪软件安全论坛, 转载请注明作者并保持文章的完整, 谢谢!

论坛讨论 Python 的文章似乎比较少。此文权当抛砖引玉,不当之处请多指教。

关于 Py 的反编译,网上流传的说法是,2.4 版本以后的比较困难。因为针对低版本,有一些开源代码可以实现反编译。到 2.4 以后,原作者要么不更新了,要么开始收费了。只有少数牛人能修改旧版的代码继续反编译下去。
本文提供了一种方法,使得在没有反编译器的情况下,也能分析用 Python 写的软件。

下面开始。

试炼软件是用 upx 加壳的,upx -d 简单脱掉。
再用 peid 查看, 发现如下信息:
Microsoft Visual C++ 7.0 Method2 [ZIP SFX]。
很少见。再用OD加载,查看字符串,发现是 py2exe 生成的。
这种文件,可以用 7zip 当作压缩包打开,里面可以看到一堆编译好的 pyc 和 pyo 文件。从名称来看,那些应该是 Python 自带的库文件,与程序无关。这里从库文件的名称可以看出是 Python 2.4 版本。

程序自带了一个 pyo 文件,200 多 K。网上找到能免费反编译 Python 2.4 以后版本的,只有 decompyler 2.3 的一个修改版(2.3 开源,后续版本收费了), 还有就是 UnPyc。
decompyler 的修改版,我没有找到。UnPyc,貌似只支持 Linux 的。在学校的 Linux 服务器上安装上,反编译却得到一堆错误。反汇编(生成 bytecode 的助记符,相当于汇编代码)倒是可以。

在反汇编当中查找关键字符串,没找到。我把所有的函数名称看了一遍,也没有可疑的。看来那个 200 多 K 的文件,不是关键代码所在。还得另谋出路。

关键代码不在附带的库里面,那还能在哪里呢?只能在程序自身了!一定是 py2exe 把那些代码编到 exe 里面了。
有2种可能:1、编成 native code。2、编成 Python bytecode,通过 Python 虚拟机执行。在OD中跟踪一下,发现执行到关键代码的时候,已经处在虚拟机的代码之中了。所以排除 1 的可能性。

下面,要找出关键代码的藏身之处。代码会藏在哪呢?.text 段?不大可能,因为那里都是 sfx 的代码。.data 段?也不可能,因为那里的数据只会被 sfx 用到。在 zip 压缩包里面?没找到可疑文件。那么,只剩下 .rsrc 了!

用 LordPE 查看资源段(这里不用 Reshacker,因为它 dump 下来的不正确),找到 “Python24.dll”, “PZIB.PYD”,还有就是 “PYTHONSCRIPT”。光是看名字就很可疑了。把它 dump 下来,在 WinHex 当中查找关键字符串,果然找到了。
下面就是要如何反编译这个 script 了。

网上没有找到相关资料。于是下载了一份 py2exe 源码,找到这里:

# We create a list of code objects, and write it as a marshaled
# stream. The framework code then just exec’s these in order.
# First is our common boot script.
boot = self.get_boot_script(“common”)
boot_code = compile(file(boot, “U”).read(),
os.path.abspath(boot), “exec”)
code_objects = [boot_code]
if self.bundle_files < 3:
code_objects.append(
compile(“import zipextimporter; zipextimporter.install()”,
“<install zipextimporter>”, “exec”))
for var_name, var_val in vars.items():
code_objects.append(
compile(“%s=%r\n” % (var_name, var_val), var_name, “exec”)
)
if self.custom_boot_script:
code_object = compile(file(self.custom_boot_script, “U”).read() + “\n”,
os.path.abspath(self.custom_boot_script), “exec”)
code_objects.append(code_object)
if script:
code_object = compile(open(script, “U”).read() + “\n”,
os.path.basename(script), “exec”)
code_objects.append(code_object)
code_bytes = marshal.dumps(code_objects)

if self.distribution.zipfile is None:
relative_arcname = “”

si = struct.pack(“iiii”,
0x78563412, # a magic value,
self.optimize,
self.unbuffered,
len(code_bytes),
) + relative_arcname + “\000”

script_bytes = si + code_bytes + ‘\000\000’
self.announce(“add script resource, %d bytes” % len(script_bytes))
if not self.dry_run:
add_resource(ensure_unicode(exe_path), script_bytes, u”PYTHONSCRIPT”, 1, True) 可以看出,是由几个 py 文件编译后,添加到一个 list 里面,然后直接 dump 下来的,当然前后还加了一堆东西。

用 WinHex 修改 dump 下来的文件,把添加的东西都去掉,这样就只剩下了 code_bytes。

现在,本文的主人公要华丽地出场了!有请 Python!(掌声)

在 Python 中输入:

>>>import marshal
>>>mylist=marshal.load(open(“dumpfile”, “r”))目的是为了把 dump 下来的文件加载到内存当中,成为 Python 的一个对象。

注:加载dump下来的对象,Python 版本一定要和 dump 时候的版本兼容才行。这个例子中,dump 时用的 2.4 ,我用 2.5 load,完全可以。用 3.1 load,就出错了。现在可以看看我们的这个对象了:

>>>mylist
[<code object ? at 0xb75df650, file “D:\python24\lib\site-packages\py2exe\boot_common.py”, line 44>, <code object ? at 0xb75df698, file “<install zipextimporter>”, line 1>, <code object ? at 0xb75f4ad0, file “****.py”, line 2>]包含了 3 个 code object 对象。第一个是 py2exe 初始化用的,第二个是解压 zip 用的,第三个就是我们的关键脚本了。

这里简单介绍一下 py, pyc, pyo, bytecode, code object 之间的关系。py 是 Python 的源代码文件,纯文本文件。用 Python 可以编译成二进制伪代码,也就是 bytecode。code object 实际上就是这些伪代码。把 code object 前面加一个 header,写成文件,就是 pyc 了,也就是编译过的 py 文件。如果在编译的时候加上优化选项,则会生成 pyo 文件,也就是优化过的 py 文件,本质上和 pyc 是一样的。
如果说 py 相当于 java 文件,那么 pyc, pyo, bytecode, code object 就相当于 class 文件了。

下面言归正传。
Python 有一个很好很强大的库:dis,里面有一个很好很强大的同名函数:dis()。这个函数就是实现反汇编功能了。它能把 code object 生成可读的代码(类似于汇编)。
对这个函数加以简单扩展,可以让其变得更好更强大。(参见 http://blog.csdn.net/balabalamerobert/archive/2007/06/22/1662025.aspx)

import dis as pydis
import types

code = None
def read(filename):
f = open(filename)
content = f.read()
global code
code = compile(content, filename, ‘exec’)
f.close()

def find_code(code, name):
for item in code.co_consts:
if isinstance(item, types.CodeType):
if item.co_name == name:
return item
return None

def dis(code_name=None):
if code_name is None:
co = code
pydis.dis(co)
return co
names = code_name.split(“.”)
co = code
for name in names:
co = find_code(co, name)
if not co:
print ‘%s is not a valid name’ % code_name
if co:
print (” byte code for %s ” % code_name).center(60, ‘*’)
pydis.dis(co) 下面就可以慢慢找出关键模块了。

首先看一下模块包含哪些常量:

>>>mylist[2].co_consts:
(1, None, (‘gdi’,), (‘EnumProcesses’,), (‘Button’,), (‘Editbox’,), (‘Textin’,), (‘LOWORD’, ‘HIWORD’, ‘RGB’, ‘RECT’), (‘Msg’,), (‘Listview’,), (‘Listbox’,), (‘Combobox’,), (‘Checkbox’,), (‘Radiobox’,), (‘Treeview’,), (‘StaticText’,), (‘Groupbox’,), (‘ContextMenu’,), (‘Menu’,), (‘SystemCursor’,), (‘GetVirtualScreenSize’,), (‘static’,), (‘Queue’,), (‘latin_1’, ‘gbk’, ‘utf_8’, ‘ascii’, ‘gb2312’, ‘gb18030’), (‘StringIO’,), (‘dbapi2’,), (‘WinExec’,), (‘CF_TEXT’, ‘GHND’), ‘Display_REG_Dialog’, <code object Display_REG_Dialog at 0xb7cc9f50, file “****.py”, line 50>, ‘Display_INPUT_Dialog’, <code object Display_INPUT_Dialog at 0xb7cd7188, file “****.py”, line 96>, ‘Display_INPUT_TIME_Dialog’, <code object Display_INPUT_TIME_Dialog at 0xb7cd7380, file “****.py”, line 134>, ‘Setup_Find_Dialog’, <code object Setup_Find_Dialog at 0xb7cd7530, file “****.py”, line 168>, ‘Get_yo2_user_Dialog’, <code object Get_yo2_user_Dialog at 0xb7cd7770, file “****.py”, line 209>, ‘Edit_User_Dialog’, <code object Edit_User_Dialog at 0xb7cd79b0, file “****.py”, line 279>, ‘Add_User_Dialog’, <code object Add_User_Dialog at 0xb7cd7d10, file “****.py”, line 332>, ‘Add_Cate_Dialog’, <code object Add_Cate_Dialog at 0xb7cd7f50, file “****.py”, line 546>, ‘Get_Pic_Dialog’, <code object Get_Pic_Dialog at 0xb7cdd260, file “****.py”, line 602>, ‘Get_MPic_Dialog’, <code object Get_MPic_Dialog at 0xb7cdd530, file “****.py”, line 934>, ‘Select_ExportType_Dialog’, <code object Select_ExportType_Dialog at 0xb7cdd9b0, file “****.py”, line 1276>, ‘Setup_PROXY_Dialog’, <code object Setup_PROXY_Dialog at 0xb7cddad0, file “****.py”, line 1412>, ‘Update_Dialog’, <code object Update_Dialog at 0xb7cddba8, file “****.py”, line 1564>, ‘MyWindow’, <code object MyWindow at 0xb7cef2f0, file “****.py”, line 1698>, ”)Display_REG_Dialog 这个比较可疑,但是跟进去发现只是保存了注册码就返回了。相关代码省略。
因为软件是在启动时检测注册码的,所以窗口的启动代码也比较可疑。

>>>import sdis
>>>sdis.code = mylist[2]
>>>sdis.dis(“MyWindow.__init__”)
************ byte code for MyWindow.__init__ ************
(省略部分代码)
1732 439 LOAD_CONST 5 (0)
442 STORE_FAST 8 (conf_user_tag)

1733 445 LOAD_GLOBAL 40 (os)
448 LOAD_ATTR 35 (path)
451 LOAD_ATTR 41 (isfile)
454 LOAD_CONST 21 (‘****.ini’)
457 CALL_FUNCTION 1
460 JUMP_IF_FALSE 48 (to 511)
463 POP_TOP

1734 464 SETUP_EXCEPT 28 (to 495)

1735 467 LOAD_GLOBAL 42 (dict4ini)
470 LOAD_ATTR 43 (DictIni)
473 LOAD_CONST 21 (‘****.ini’)
476 CALL_FUNCTION 1
479 LOAD_FAST 0 (self)
482 STORE_ATTR 44 (conf_user)

1736 485 LOAD_CONST 22 (1)
488 STORE_FAST 8 (conf_user_tag)
491 POP_BLOCK
492 JUMP_ABSOLUTE 518

1737 >> 495 POP_TOP
496 POP_TOP
497 POP_TOP

1738 498 LOAD_CONST 5 (0)
501 STORE_FAST 8 (conf_user_tag)
504 JUMP_ABSOLUTE 518
507 END_FINALLY
508 JUMP_FORWARD 7 (to 518)
>> 511 POP_TOP

1740 512 LOAD_CONST 5 (0)
515 STORE_FAST 8 (conf_user_tag)

1741 >> 518 LOAD_FAST 8 (conf_user_tag)
521 LOAD_CONST 5 (0)
524 COMPARE_OP 2 (==)
527 JUMP_IF_FALSE 50 (to 580)
530 POP_TOP

1742 531 LOAD_GLOBAL 42 (dict4ini)
534 LOAD_ATTR 43 (DictIni)
537 LOAD_CONST 21 (‘****.ini’)
540 CALL_FUNCTION 1
543 LOAD_FAST 0 (self)
546 STORE_ATTR 44 (conf_user)

1743 549 LOAD_CONST 23 (”)
552 LOAD_FAST 0 (self)
555 LOAD_ATTR 44 (conf_user)
558 LOAD_ATTR 45 (config)
561 STORE_ATTR 46 (regnum)

1744 564 LOAD_FAST 0 (self)
567 LOAD_ATTR 44 (conf_user)
570 LOAD_ATTR 47 (save)
573 CALL_FUNCTION 0
576 POP_TOP
577 JUMP_FORWARD 1 (to 581)
>> 580 POP_TOP

1745 >> 581 LOAD_FAST 0 (self)
584 LOAD_ATTR 44 (conf_user)
587 LOAD_ATTR 48 (has_key)
590 LOAD_CONST 24 (‘config’)
593 CALL_FUNCTION 1
596 JUMP_IF_TRUE 32 (to 631)
599 POP_TOP

1746 600 LOAD_CONST 23 (”)
603 LOAD_FAST 0 (self)
606 LOAD_ATTR 44 (conf_user)
609 LOAD_ATTR 45 (config)
612 STORE_ATTR 46 (regnum)

1747 615 LOAD_FAST 0 (self)
618 LOAD_ATTR 44 (conf_user)
621 LOAD_ATTR 47 (save)
624 CALL_FUNCTION 0
627 POP_TOP
628 JUMP_FORWARD 1 (to 632)
>> 631 POP_TOP

1748 >> 632 LOAD_FAST 0 (self)
635 LOAD_ATTR 44 (conf_user)
638 LOAD_ATTR 45 (config)
641 LOAD_ATTR 48 (has_key)
644 LOAD_CONST 25 (‘regnum’)
647 CALL_FUNCTION 1
650 JUMP_IF_TRUE 32 (to 685)
653 POP_TOP

1749 654 LOAD_CONST 23 (”)
657 LOAD_FAST 0 (self)
660 LOAD_ATTR 44 (conf_user)
663 LOAD_ATTR 45 (config)
666 STORE_ATTR 46 (regnum)

1750 669 LOAD_FAST 0 (self)
672 LOAD_ATTR 44 (conf_user)
675 LOAD_ATTR 47 (save)
678 CALL_FUNCTION 0
681 POP_TOP
682 JUMP_FORWARD 1 (to 686)
>> 685 POP_TOP

1751 >> 686 LOAD_CONST 23 (”)
689 LOAD_FAST 0 (self)
692 STORE_ATTR 49 (regno)

1752 695 LOAD_CONST 23 (”)
698 LOAD_FAST 0 (self)
701 STORE_ATTR 50 (regno2)

1753 704 LOAD_FAST 0 (self)
707 LOAD_ATTR 51 (get_reg_no_true)
710 CALL_FUNCTION 0
713 LOAD_FAST 0 (self)
716 STORE_ATTR 52 (reg_true)上面是取注册码,并且调用 get_reg_no_true() 函数来验证,然后把结果保存在 reg_true 变量里面。再往下看:

2110 >> 4879 LOAD_FAST 0 (self)
4882 LOAD_ATTR 52 (reg_true)
4885 LOAD_CONST 214 (‘YES’)
4888 COMPARE_OP 3 (!=)
4891 JUMP_IF_FALSE 29 (to 4923)
4894 POP_TOP

2111 4895 LOAD_GLOBAL 155 (Msg)
4898 LOAD_FAST 0 (self)
4901 LOAD_ATTR 156 (Hwnd)
4904 LOAD_CONST 228 (‘\xb5\xb1\xc7\xb0****\xce\xaa\xce\xb4\xd7\xa2\xb2\xe1\xb0\xe6\xb1\xbe\xa3\xac\xb5\xbc\xb3\xf6\xb9\xa6\xc4\xdc\xbb\xe1\xca\xdc\xb5\xbd\xcf\xde\xd6\xc6\xa3\xac\xc7\xeb\xb5\xbd****.com\xb8\xb6\xb7\xd1\xd7\xa2\xb2\xe1\xa3\xa1’)
4907 LOAD_CONST 229 (‘\xcc\xe1\xca\xbe’)
4910 LOAD_CONST 225 (‘ok’)
4913 LOAD_CONST 226 (‘defbutton1’)
4916 CALL_FUNCTION 5
4919 POP_TOP
4920 JUMP_FORWARD 1 (to 4924)
>> 4923 POP_TOP 如果 reg_true 不等于 ‘YES’,就要弹出提示注册的对话框了。
关键 call 就是 get_reg_no_true() 了。只要让其返回 ‘YES’ 便可。

这里可以修改 get_reg_no_true() 爆破了,但既然已经走了这么远了,索性再走一程,深入关键 call 去看个究竟。

>>>sdis.dis(“MyWindow.get_reg_no_true”)
******** byte code for MyWindow.get_reg_no_true *********
2117 0 SETUP_EXCEPT 638 (to 641)

2118 3 SETUP_EXCEPT 173 (to 179)

2119 6 LOAD_CONST 1 (”)
9 STORE_FAST 9 (mac_address)

2120 12 LOAD_CONST 2 (‘.’)
15 STORE_FAST 6 (strComputer)

2121 18 LOAD_GLOBAL 2 (win32com)
21 LOAD_ATTR 3 (client)
24 LOAD_ATTR 4 (Dispatch)
27 LOAD_CONST 3 (‘WbemScripting.SWbemLocator’)
30 CALL_FUNCTION 1
33 STORE_FAST 3 (objWMIService)

2122 36 LOAD_FAST 3 (objWMIService)
39 LOAD_ATTR 6 (ConnectServer)
42 LOAD_FAST 6 (strComputer)
45 LOAD_CONST 4 (‘root\\cimv2’)
48 CALL_FUNCTION 2
51 STORE_FAST 8 (objSWbemServices)

2123 54 LOAD_FAST 8 (objSWbemServices)
57 LOAD_ATTR 8 (ExecQuery)
60 LOAD_CONST 5 (‘Select * from Win32_NetworkAdapter’)
63 CALL_FUNCTION 1
66 STORE_FAST 10 (colItems)

2124 69 SETUP_LOOP 80 (to 152)
72 LOAD_FAST 10 (colItems)
75 GET_ITER
>> 76 FOR_ITER 72 (to 151)
79 STORE_FAST 4 (objItem)

2125 82 LOAD_FAST 4 (objItem)
85 LOAD_ATTR 11 (MACAddress)
88 LOAD_CONST 0 (None)
91 COMPARE_OP 3 (!=)
94 JUMP_IF_FALSE 50 (to 147)
97 POP_TOP

2142 98 LOAD_CONST 6 (‘VEN_’)
101 LOAD_FAST 4 (objItem)
104 LOAD_ATTR 13 (PNPDeviceID)
107 COMPARE_OP 6 (in)
110 JUMP_IF_FALSE 30 (to 143)
113 POP_TOP
114 LOAD_CONST 7 (‘DEV_’)
117 LOAD_FAST 4 (objItem)
120 LOAD_ATTR 13 (PNPDeviceID)
123 COMPARE_OP 6 (in)
126 JUMP_IF_FALSE 14 (to 143)
129 POP_TOP

2143 130 LOAD_FAST 4 (objItem)
133 LOAD_ATTR 11 (MACAddress)
136 STORE_FAST 9 (mac_address)

2145 139 BREAK_LOOP
140 JUMP_ABSOLUTE 148
>> 143 POP_TOP
144 JUMP_ABSOLUTE 76
>> 147 POP_TOP
>> 148 JUMP_ABSOLUTE 76
>> 151 POP_BLOCK

2146 >> 152 LOAD_FAST 9 (mac_address)
155 LOAD_CONST 1 (”)
158 COMPARE_OP 2 (==)
161 JUMP_IF_FALSE 10 (to 174)
164 POP_TOP

2147 165 LOAD_CONST 8 (’00:0A:EB:F5:D4:14′)
168 STORE_FAST 9 (mac_address)
171 JUMP_FORWARD 1 (to 175)
>> 174 POP_TOP
>> 175 POP_BLOCK
176 JUMP_FORWARD 13 (to 192)

2148 >> 179 POP_TOP
180 POP_TOP
181 POP_TOP

2149 182 LOAD_CONST 8 (’00:0A:EB:F5:D4:14′)
185 STORE_FAST 9 (mac_address)
188 JUMP_FORWARD 1 (to 192)
191 END_FINALLY

2150 >> 192 SETUP_EXCEPT 68 (to 263)

2151 195 LOAD_GLOBAL 14 (md5)
198 LOAD_ATTR 15 (new)
201 LOAD_FAST 9 (mac_address)
204 CALL_FUNCTION 1
207 LOAD_ATTR 16 (hexdigest)
210 CALL_FUNCTION 0
213 STORE_FAST 7 (s)

2152 216 LOAD_GLOBAL 14 (md5)
219 LOAD_ATTR 15 (new)
222 LOAD_FAST 7 (s)
225 LOAD_CONST 9 (16)
228 SLICE+1
229 LOAD_FAST 7 (s)
232 LOAD_CONST 9 (16)
235 SLICE+2
236 BINARY_ADD
237 CALL_FUNCTION 1
240 LOAD_ATTR 16 (hexdigest)
243 CALL_FUNCTION 0
246 LOAD_CONST 10 (6)
249 LOAD_CONST 11 (-6)
252 SLICE+3
253 LOAD_FAST 0 (self)
256 STORE_ATTR 19 (regno)
259 POP_BLOCK
260 JUMP_FORWARD 16 (to 279)

2153 >> 263 POP_TOP
264 POP_TOP
265 POP_TOP

2154 266 LOAD_CONST 12 (’00e6e95aff213b8e40ff’)
269 LOAD_FAST 0 (self)
272 STORE_ATTR 19 (regno)

2155 275 JUMP_FORWARD 1 (to 279)
278 END_FINALLY

2156 >> 279 SETUP_EXCEPT 323 (to 605)

2157 282 LOAD_CONST 1 (”)
285 STORE_FAST 2 (tmp_result)

2158 288 LOAD_FAST 0 (self)
291 LOAD_ATTR 21 (conf_user)
294 LOAD_ATTR 22 (config)
297 LOAD_ATTR 23 (regnum)
300 LOAD_FAST 0 (self)
303 STORE_ATTR 24 (regno2)

2159 306 LOAD_GLOBAL 14 (md5)
309 LOAD_ATTR 15 (new)
312 LOAD_FAST 0 (self)
315 LOAD_ATTR 19 (regno)
318 CALL_FUNCTION 1
321 LOAD_ATTR 16 (hexdigest)
324 CALL_FUNCTION 0
327 STORE_FAST 7 (s)

2160 330 LOAD_GLOBAL 14 (md5)
333 LOAD_ATTR 15 (new)
336 LOAD_FAST 7 (s)
339 LOAD_CONST 13 (14)
342 SLICE+1
343 LOAD_CONST 14 (‘bb2’)
346 BINARY_ADD
347 LOAD_FAST 7 (s)
350 LOAD_CONST 13 (14)
353 SLICE+2
354 BINARY_ADD
355 CALL_FUNCTION 1
358 LOAD_ATTR 16 (hexdigest)
361 CALL_FUNCTION 0
364 STORE_FAST 1 (md5_tmp)

2161 367 LOAD_FAST 1 (md5_tmp)
370 LOAD_CONST 15 (28)
373 SLICE+1
374 LOAD_FAST 1 (md5_tmp)
377 LOAD_CONST 9 (16)
380 LOAD_CONST 16 (20)
383 SLICE+3
384 BINARY_ADD
385 LOAD_FAST 1 (md5_tmp)
388 LOAD_CONST 17 (0)
391 LOAD_CONST 18 (5)
394 SLICE+3
395 BINARY_ADD
396 LOAD_FAST 1 (md5_tmp)
399 LOAD_CONST 19 (7)
402 LOAD_CONST 20 (9)
405 SLICE+3
406 BINARY_ADD
407 LOAD_FAST 1 (md5_tmp)
410 LOAD_CONST 18 (5)
413 LOAD_CONST 19 (7)
416 SLICE+3
417 BINARY_ADD
418 LOAD_FAST 1 (md5_tmp)
421 LOAD_CONST 20 (9)
424 LOAD_CONST 9 (16)
427 SLICE+3
428 BINARY_ADD
429 LOAD_FAST 1 (md5_tmp)
432 LOAD_CONST 16 (20)
435 LOAD_CONST 15 (28)
438 SLICE+3
439 BINARY_ADD
440 LOAD_FAST 1 (md5_tmp)
443 LOAD_CONST 21 (8)
446 LOAD_CONST 22 (12)
449 SLICE+3
450 BINARY_ADD
451 LOAD_FAST 1 (md5_tmp)
454 LOAD_CONST 23 (17)
457 LOAD_CONST 24 (21)
460 SLICE+3
461 BINARY_ADD
462 STORE_FAST 5 (reg_tmp)

2162 465 LOAD_FAST 0 (self)
468 LOAD_ATTR 24 (regno2)
471 LOAD_CONST 17 (0)
474 LOAD_CONST 25 (4)
477 SLICE+3
478 LOAD_FAST 5 (reg_tmp)
481 LOAD_CONST 17 (0)
484 LOAD_CONST 25 (4)
487 SLICE+3
488 COMPARE_OP 2 (==)
491 JUMP_IF_FALSE 106 (to 600)
494 POP_TOP

2163 495 LOAD_FAST 0 (self)
498 LOAD_ATTR 24 (regno2)
501 LOAD_CONST 25 (4)
504 LOAD_CONST 21 (8)
507 SLICE+3
508 LOAD_FAST 5 (reg_tmp)
511 LOAD_CONST 25 (4)
514 LOAD_CONST 21 (8)
517 SLICE+3
518 COMPARE_OP 2 (==)
521 JUMP_IF_FALSE 72 (to 596)
524 POP_TOP

2164 525 LOAD_FAST 0 (self)
528 LOAD_ATTR 24 (regno2)
531 LOAD_CONST 19 (7)
534 LOAD_CONST 26 (15)
537 SLICE+3
538 LOAD_FAST 5 (reg_tmp)
541 LOAD_CONST 19 (7)
544 LOAD_CONST 26 (15)
547 SLICE+3
548 COMPARE_OP 2 (==)
551 JUMP_IF_FALSE 38 (to 592)
554 POP_TOP

2165 555 LOAD_FAST 0 (self)
558 LOAD_ATTR 24 (regno2)
561 LOAD_CONST 13 (14)
564 SLICE+1
565 LOAD_FAST 5 (reg_tmp)
568 LOAD_CONST 13 (14)
571 SLICE+1
572 COMPARE_OP 2 (==)
575 JUMP_IF_FALSE 10 (to 588)
578 POP_TOP

2166 579 LOAD_CONST 27 (‘ok’)
582 STORE_FAST 2 (tmp_result)
585 JUMP_ABSOLUTE 593
>> 588 POP_TOP
589 JUMP_ABSOLUTE 597
>> 592 POP_TOP
>> 593 JUMP_ABSOLUTE 601
>> 596 POP_TOP
>> 597 JUMP_FORWARD 1 (to 601)
>> 600 POP_TOP
>> 601 POP_BLOCK
602 JUMP_FORWARD 7 (to 612)

2167 >> 605 POP_TOP
606 POP_TOP
607 POP_TOP

2168 608 JUMP_FORWARD 1 (to 612)
611 END_FINALLY

2169 >> 612 LOAD_FAST 2 (tmp_result)
615 LOAD_CONST 27 (‘ok’)
618 COMPARE_OP 2 (==)
621 JUMP_IF_FALSE 8 (to 632)
624 POP_TOP

2170 625 LOAD_CONST 28 (‘YES’)
628 RETURN_VALUE
629 JUMP_FORWARD 5 (to 637)
>> 632 POP_TOP

2172 633 LOA代码有点长,看着看着就迷失在代码的丛林里了。这里可以讨个巧,借助免费的反编译引擎来帮忙。

首先获得 get_reg_no_true() 这个函数的 code object,然后使用 marshal.dump() 保存成文件(从 py2exe 的源代码哪里学来的)。然后用 WinHex 加上 8 个字节的 file header。前 4 个字节代表 Python 版本号,2.4 是 6DF20D0A。后 4 个字节是 timestamp,随便写就是。
接着来到这里:http://www.depython.net/
这个据说是 team509 (http://www.team509.com/) 做的,它可以免费反编译小于 5KB 的文件。

反编译出的结果:

try:
try:
mac_address = ”
strComputer = ‘.’
objWMIService = win32com.client.Dispatch(‘WbemScripting.SWbemLocator’)
objSWbemServices = objWMIService.ConnectServer(strComputer, ‘root\\cimv2’)
colItems = objSWbemServices.ExecQuery(‘Select * from Win32_NetworkAdapter’)
for objItem in colItems:
if (objItem.MACAddress != None):
if ((‘VEN_’ in objItem.PNPDeviceID) and (‘DEV_’ in objItem.PNPDeviceID)):
mac_address = objItem.MACAddress
break

if (mac_address == ”):
mac_address = ’00:0A:EB:F5:D4:14′
except:
mac_address = ’00:0A:EB:F5:D4:14′
try:
s = md5.new(mac_address).hexdigest()
self.regno = md5.new((s[16:] + s[:16])).hexdigest()[6:-6]
except:
self.regno = ’00e6e95aff213b8e40ff’
try:
tmp_result = ”
self.regno2 = self.conf_user.config.regnum
s = md5.new(self.regno).hexdigest()
md5_tmp = md5.new(((s[14:] + ‘bb2’) + s[:14])).hexdigest()
reg_tmp = ((((((((md5_tmp[28:] + md5_tmp[16:20]) + md5_tmp[0:5]) + md5_tmp[7:9]) + md5_tmp[5:7]) + md5_tmp[9:16]) + md5_tmp[20:28]) + md5_tmp[8:12]) + md5_tmp[17:21])
if (self.regno2[0:4] == reg_tmp[0:4]):
if (self.regno2[4:8] == reg_tmp[4:8]):
if (self.regno2[7:15] == reg_tmp[7:15]):
if (self.regno2[14:] == reg_tmp[14:]):
tmp_result = ‘ok’
except:
pass
if (tmp_result == ‘ok’):
return ‘YES’
else:
return ‘ERR’
except:
return ‘ERR’算法一目了然!稍微改一下,注册机就可以出炉了。

综上,使用 Python 开发的商业软件,其安全性还值得商榷。抵御攻击的做法是使用第三方库编译成 native code,使用代码混淆器,或者修改 Python 源代码防止被反汇编。

发表评论

电子邮件地址不会被公开。 必填项已用*标注