关于python的逆向之前碰到过几次,是关于pyc字节码文件的。这次拿到exe后,在没有提示的情况下还是用IDA打开,发现非常繁琐而且分析起来有点困难。
后来参考了别人的wp,看到描述里说“py2exe的逆向”,在网上找到了一个脚本可以把py和exe文件相互转换。
https://sourceforge.net/projects/pyinstallerextractor/
首先用工具脚本将exe转回python文件,应该是有一个文件包被打包成exe文件。 得到文件夹 可以找到Reverse3是python源代码
data = \
"\x1c\x7a\x16\x77\x10\x2a\x51\x1f\x4c\x0f\x5b\x1d\x42\x2f\x4b\x7e\x4a\x7a\x4a\x7b" +\
"\x49\x7f\x4a\x7f\x1e\x78\x4c\x75\x10\x28\x18\x2b\x48\x7e\x46\x23\x12\x24\x11\x72" +\
"\x4b\x2e\x1b\x7e\x4f\x2b\x12\x76\x0b"
'''
char buf[] = "flag:{NSCTF_md5098f6bcd4621d373cade4e832627b4f6}";
int _tmain(int argc, _TCHAR* argv[])
{
printf("%d\n", strlen(buf));
char key = '\x0b';
buf[47] ^= key;
for (int i = 1; i < 48; i++)
{
buf[48 - i - 1] ^= buf[48 - i];
}
return 0;
}
'''
print "Revese it?????????"
第二部分显然是一段C/C++代码,将其运行一下看看结果。
#include <stdio.h>
#include <tchar.h>
#include <cstring>
char buf[] = "flag:{NSCTF_md5098f6bcd4621d373cade4e832627b4f6}";
int _tmain(int argc, _TCHAR* argv[])
{
printf("%d\n", strlen(buf));
char key = '\x0b';
buf[47] ^= key;
for (int i = 1; i < 48; i++)
{
buf[48 - i - 1] ^= buf[48 - i];
}
printf("%s",buf);
return 0;
}
运行结果为 看起来是一堆乱码,这时候想起来那段Python代码开头的data数据,将其输入一下对比。 虽然有点乱但是开头大致上是一样的,所以可以基本判定data和buf字符数组经过编码后的数据类似,就写出逆着的解码程序,将data 解码即可。
data = \
"\x1c\x7a\x16\x77\x10\x2a\x51\x1f\x4c\x0f\x5b\x1d\x42\x2f\x4b\x7e\x4a\x7a\x4a\x7b" +\
"\x49\x7f\x4a\x7f\x1e\x78\x4c\x75\x10\x28\x18\x2b\x48\x7e\x46\x23\x12\x24\x11\x72" +\
"\x4b\x2e\x1b\x7e\x4f\x2b\x12\x76\x0b"
flag = ''
for i in range(len(data)-1):
flag += chr(ord(data[i])^ord(data[i+1]))
print flag
flag:{NSCTF_md540012655af49e803c68e165c9e5e1d9d}
[Finished in 0.4s]
这是第一次接触这种类型的题目,因此记录一下。把python脚本打包成exe感觉还是挺有意思的,还能给还原回去……
再附一个把pyc字节码文件反编译成py文件的工具
https://sourceforge.net/projects/easypythondecompiler/
刚记录完上一个题目,这个题目就是前面提到的pyc字节码文件类型了。废话不多说。
使用easypythondecompiler工具反编译pyc,结果报错了。
# Embedded file name: decrypt.py
--- This code section failed: ---
0 NOP None
1 LOAD_CONST "M,\x1d-\x18}E'\x1ezN~\x1b*\x19+\x12%\x1d-"
4 LOAD_CONST "M,\x1d-\x18}E'\x1ezN~\x1b*\x19+\x12%\x1d-"
7 NOP None
8 LOAD_CONST "M,\x1d-\x18}E'\x1ezN~\x1b*\x19+\x12%\x1d-"
11 LOAD_CONST 'I\x7fM(I{I\x7fJ.\x16wWcRj\x0e6\x0fn'
14 BINARY_ADD None
15 LOAD_CONST 'Zo\nn\x0fk\t1R7\x03g\x067\x00eUb\x043'
18 BINARY_ADD None
19 LOAD_CONST '\x014\x071Rr\x14x\x19~D?q"a5s,A%'
22 BINARY_ADD None
23 LOAD_CONST "\x10'\x11uLyA%\x1d|DrFv\x12t\x11#B&"
26 BINARY_ADD None
27 LOAD_CONST 'GsKzK*O)\x1c%GuC>\x1e\x7f\x1b+\x19*'
30 BINARY_ADD None
31 LOAD_CONST '\x1e&\x14-\x1f/\x1axAqBq@yO-LtE}'
34 BINARY_ADD None
35 LOAD_CONST '\x1b,MuBp\x12'
38 BINARY_ADD None
39 STORE_GLOBAL 'data'
42 LOAD_CONST -1
45 LOAD_CONST None
48 IMPORT_NAME 'os'
51 STORE_NAME 'os'
54 LOAD_CONST -1
57 LOAD_CONST None
60 IMPORT_NAME 'sys'
63 STORE_NAME 'sys'
66 LOAD_CONST -1
69 LOAD_CONST None
72 IMPORT_NAME 'struct'
75 STORE_NAME 'struct'
78 LOAD_CONST -1
81 LOAD_CONST None
84 IMPORT_NAME 'cStringIO'
87 STORE_NAME 'cStringIO'
90 LOAD_CONST -1
93 LOAD_CONST None
96 IMPORT_NAME 'string'
99 STORE_NAME 'string'
102 LOAD_CONST -1
105 LOAD_CONST None
108 IMPORT_NAME 'dis'
111 STORE_NAME 'dis'
114 LOAD_CONST -1
117 LOAD_CONST None
120 IMPORT_NAME 'marshal'
123 STORE_NAME 'marshal'
126 LOAD_CONST -1
129 LOAD_CONST None
132 IMPORT_NAME 'types'
135 STORE_NAME 'types'
138 LOAD_CONST -1
141 LOAD_CONST None
144 IMPORT_NAME 'random'
147 STORE_NAME 'random'
150 LOAD_CONST 0
153 STORE_GLOBAL 'count'
156 LOAD_CONST '<code_object reverse>'
159 MAKE_FUNCTION_0 None
162 STORE_NAME 'reverse'
165 LOAD_NAME 'list'
168 LOAD_NAME 'reverse'
171 LOAD_GLOBAL 'data'
174 CALL_FUNCTION_1 None
177 LOAD_CONST 1
180 SLICE+1 None
181 CALL_FUNCTION_1 None
184 STORE_GLOBAL 'data_list'
187 LOAD_CONST '<code_object decrpyt>'
190 MAKE_FUNCTION_0 None
193 STORE_NAME 'decrpyt'
196 LOAD_CONST '<code_object GetFlag1>'
199 MAKE_FUNCTION_0 None
202 STORE_NAME 'GetFlag1'
205 LOAD_CONST '<code_object GetFlag2>'
208 MAKE_FUNCTION_0 None
211 STORE_NAME 'GetFlag2'
214 LOAD_CONST '<code_object GetFlag3>'
217 MAKE_FUNCTION_0 None
220 STORE_NAME 'GetFlag3'
223 LOAD_CONST '<code_object GetFlag4>'
226 MAKE_FUNCTION_0 None
229 STORE_NAME 'GetFlag4'
232 LOAD_CONST '<code_object GetFlag5>'
235 MAKE_FUNCTION_0 None
238 STORE_NAME 'GetFlag5'
241 LOAD_NAME 'GetFlag1'
244 CALL_FUNCTION_0 None
247 POP_TOP None
Syntax error at or near `NOP' token at offset 0
应该是pyc文件被人修改过或者损坏了吧,这下就比较难搞了……只能大概猜出是关于NOP指令的错误,并不知道该如何修改……
换了个反编译的工具pycdc
https://tool.lu/pyc/
得到反编译后代码
#!/usr/bin/env python
# visit http://tool.lu/pyc/ for more information
data = "M,\x1d-\x18}E'\x1ezN~\x1b*\x19+\x12%\x1d-" + 'I\x7fM(I{I\x7fJ.\x16wWcRj\x0e6\x0fn' + 'Zo\nn\x0fk\t1R7\x03g\x067\x00eUb\x043' + '\x014\x071Rr\x14x\x19~D?q"a5s,A%' + "\x10'\x11uLyA%\x1d|DrFv\x12t\x11#B&" + 'GsKzK*O)\x1c%GuC>\x1e\x7f\x1b+\x19*' + '\x1e&\x14-\x1f/\x1axAqBq@yO-LtE}' + '\x1b,MuBp\x12'
import os
import sys
import struct
import cStringIO
import string
import dis
import marshal
import types
import random
count = 0
def reverse(string):
return string[::-1]
data_list = list(reverse(data)[1:])
def decrpyt(c, key2):
global count
data_list[count] = c ^ key2
count += 1
def GetFlag1():
key = struct.unpack('B', data[len(data) - 8])[0]
for c in data_list:
if count == 0:
decrpyt(struct.unpack('B', c)[0], key)
continue
key = struct.unpack('B', data[len(data) - 3])[0]
decrpyt(struct.unpack('B', c)[0], key)
for c in data_list[::-1]:
print chr(c),
def GetFlag2():
key = struct.unpack('B', data[len(data) - 11])[0]
for c in data_list:
if count == 0:
decrpyt(struct.unpack('B', c)[0], key)
continue
key = struct.unpack('B', data[len(data) - 4 - count])[0]
decrpyt(struct.unpack('B', c)[0], key)
for c in data_list[::-1]:
print chr(c),
def GetFlag3():
key = struct.unpack('B', data[len(data) - 5])[0]
for c in data_list:
if count == 0:
decrpyt(struct.unpack('B', c)[0], key)
continue
key = struct.unpack('B', data[len(data) - 2 - count])[0]
decrpyt(struct.unpack('B', c)[0], key)
for c in data_list[::-1]:
print chr(c),
def GetFlag4():
global count
key = struct.unpack('B', data[len(data) - 1])[0]
for c in data_list:
if count == 0:
decrpyt(struct.unpack('B', c)[0], key)
continue
key = struct.unpack('B', data[len(data) - 1 - count])[0]
decrpyt(struct.unpack('B', c)[0], key)
count = 0
for c in data_list[::-1]:
print chr(c),
def GetFlag5():
key = struct.unpack('B', data[len(data) - 9])[0]
for c in data_list:
if count == 0:
decrpyt(struct.unpack('B', c)[0], key)
continue
key = struct.unpack('B', data[len(data) - 3 - count])[0]
decrpyt(struct.unpack('B', c)[0], key)
for c in data_list[::-12]:
print chr(c),
GetFlag1()
手动添加上GETFLAG其他四个函数的调用,运行 结果在第二个函数就报错了。
error: unpack requires a string argument of length 1
百度得知该种错误一般是字节没有对齐,就是在处理补充字节的时候会有错误。看了很多该问题的解决方法,最简单的还是自己去掉各种连接符把字符串抠出来……这样就不会出现这种问题了……
但是如果最后的调用单独只写一个函数,就不会报错。 比如只写
GetFlag2()
验证到第四个函数就可以得到flag
q a 1 0 5 e 8 b 9 d 4 0 e 1 3 2 9 7 8 0 d 6 2 e a 2 2 6 5 d 8 a 4 1 8 d 8 9 a 4 5 e d a d b 8 c e 4 d a 1 7 e 0 7 f 7 2 5 3 6 c f l a g : { N S C T F _ m d 5 7 6 d 9 5 8 d 8 a 8 6 4 0 d f e 2 a d a 4 8 1 1 a e f 5 9 b 2 6 } a d 0 2 3 4 8 2 9 2 0 5 b 9 0 3 3 1 9 6 b a 8 1 8 f 7 a 8 7 2 b
flag:{NSCTF_md576d958d8a8640dfe2ada4811aef59b26}
看了大佬的wp,在使用pycdc工具之前有一些操作虽然失败了,但是有尝试的意义。
可以看到是 NOP 指令的解析上出了问题,根据 Python 字节码指令集 提供的列
表找到 NOP 指令对应的 Bytecode 是 09,于是⽤⼆进制编辑软件打开,将对
应位置的 09 随意修改为另⼀单字节码指令值,例如 01。根据报错信息以此定
位 NOP 的位置,总共出现了 4 次,可⽤ 09 64 以及 09 74 去定位。
另外还有个Python 2.6.2的.pyc文件格式的相关文章。 http://rednaxelafx.iteye.com/blog/382423
参考的大佬wp链接 http://blog.nsfocus.net/wp-content/uploads/2015/09/reverse_500.pdf