n1cef1sh's Blog

NSCTF Reverse 400

0x00

关于python的逆向之前碰到过几次,是关于pyc字节码文件的。这次拿到exe后,在没有提示的情况下还是用IDA打开,发现非常繁琐而且分析起来有点困难。
后来参考了别人的wp,看到描述里说“py2exe的逆向”,在网上找到了一个脚本可以把py和exe文件相互转换。
https://sourceforge.net/projects/pyinstallerextractor/

0x01

首先用工具脚本将exe转回python文件,应该是有一个文件包被打包成exe文件。 image 得到文件夹 image 可以找到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;
}

运行结果为 image 看起来是一堆乱码,这时候想起来那段Python代码开头的data数据,将其输入一下对比。 image 虽然有点乱但是开头大致上是一样的,所以可以基本判定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]

0x02

这是第一次接触这种类型的题目,因此记录一下。把python脚本打包成exe感觉还是挺有意思的,还能给还原回去……
再附一个把pyc字节码文件反编译成py文件的工具 https://sourceforge.net/projects/easypythondecompiler/

NSCTF Reverse 500

0x00

刚记录完上一个题目,这个题目就是前面提到的pyc字节码文件类型了。废话不多说。

0x01

使用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}

0x02

看了大佬的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