freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

简析RE中python的exe文件
2022-09-14 17:04:43
所属地 山东省

0x00. 前置学习

简单了解

python为什么要打包成exe文件?在日常生活中的应用是因为传输源文件以及源代码给他人是需要配置好一定的环境才能进行编译操作,而打包成exe文件就可以跟电脑软件一样打开就可以运行也可以分享给他人。

而对于ctf比赛来说,是为了给新手增加解题难度,增加游戏的难度。

在反编译前,先了解几种常见的python文件格式:

.py: 源代码文件,可以用文本编辑器查看和编辑;

.pyc: 源代码py文件编译后生成的二进制文件,无法用文本编辑器进行编辑,由python的虚拟机来执行,pyc文件的内容跟python版本相关;

.pyo: 源代码py文件优化编译后生成的二进制文件,无法用文本编辑器进行编辑,Python3.5之后,不再使用.pyo文件名,而是使用类似"xxx.opt-n.pyc的文件名;

.pyd: python的动态链接库(dll),允许程序共享执行特殊任务所必需的代码和其他资源;

.pyz: zipapp打包文件(类似于pyinstaller打包成exe文件)

0x01.PyInstaller

这里主要利用了工具有pyinstxtractor.py,还有在线转换工具(http://tools.bugscaner.com/decompyle/)把pyc转为py。

步骤:使用pyinstxtractor.py工具可以将pyinstaller 生成的exe文件解包成pyc文件,将下载的pyinstxtractor.py文件拷到exe相同的目录,执行命令:

python pyinstxtractor.py checkme.exe(文件名)

执行结果将生成"checkme(文件名).exe _extracted"的文件夹

需要的工具:

这里主要利用了工具有pyinstxtractor.py,还有在线转换工具(http://tools.bugscaner.com/decompyle/)把pyc转为py。

步骤:使用pyinstxtractor.py工具可以将pyinstaller 生成的exe文件解包成pyc文件,将下载的pyinstxtractor.py文件拷到exe相同的目录,执行命令:

python pyinstxtractor.py checkme.exe(文件名)

执行结果将生成"checkme(文件名).exe _extracted"的文件夹

image

在生成的文件夹中找到在根目录下,包括checkme二进制文件(对应原来checkme.py)

image

我们将checkme 与strust文件用010edit(winhex也可)打开,对比,发现pyc文件缺少前12个字节,补充上另存为checkme.pyc

image

因为pyc是二进制文件,无法直接查看和编译,需要转换成py文件。可以使用在线的工具:http://tools.bugscaner.com/decompyle/

PyInstaller的原理

1.PyInstaller工具可以把python解析器和脚本打包成一个可执行的文件,并不是编译成真正的机器码,打包成一个可执行文件后运行效率可能会降低,好处就是在使用者的机器上可以不用安装python和你的脚本依赖的库。2.利用PyInstaller对指定的的脚本打包时,会先分析脚本所依赖的其他脚本,然后根据导包路径去查找,把所有相关的脚本收集起来,包括Python解析器,然后根据你的命令参数可分别生成文件夹,或者打包成一个可执行文件。3.无论是生成的文件夹里的可执行文件或者只打包成一个可执行文件都可以直接运行,前者需要把整个文件夹都给别人。

常用参数:

-F 指只生成一个exe文件,不生成其他dll文件

-w 不弹出交互窗口,如果你想程序运行的时候,与程序进行交互,则不加该参数

-i 设定程序图标 ,其后面的xxx.ico文件就是程序小图标

dev.py 要打包的程序,如果你不是在dev.py同一级目录下执行的打包命令,这里得写上dev.py的路径地址

–hidden-import=pandas._libs.tslibs.timedeltas 隐藏相关模块的引用

使用pyinstaller加密打包

现在执行如下命令就能加密打包了。key后面为密钥可以随便输。

pyinstaller.exe -F --key 123456 xxx.py

尝试解包

python pyinstxtractor.py .\main-encrypt.exe
  import imp
[*] Processing .\main-encrypt.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 37
[*] Length of package: 5787283 bytes
[*] Found 63 files in CArchive
[*] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap
[+] Possible entry point: main
[*] Found 136 files in PYZ archive
[!] Error: Failed to decompress Crypto, probably encrypted. Extracting as is.
[!] Error: Failed to decompress Crypto.Cipher, probably encrypted. Extracting as is.
[!] Error: Failed to decompress __future__, probably encrypted. Extracting as is.
[!] Error: Failed to decompress _compat_pickle, probably encrypted. Extracting as is.
[!] Error: Failed to decompress argparse, probably encrypted. Extracting as is.
[!] Error: Failed to decompress ast, probably encrypted. Extracting as is.
[!] Error: Failed to decompress base64, probably encrypted. Extracting as is.
[!] Error: Failed to decompress bdb, probably encrypted. Extracting as is.
[!] Error: Failed to decompress bisect, probably encrypted. Extracting as is.
[!] Error: Failed to decompress bz2, probably encrypted. Extracting as is.
[!] Error: Failed to decompress calendar, probably encrypted. Extracting as is.
[!] Error: Failed to decompress cmd, probably encrypted. Extracting as is.
[!] Error: Failed to decompress code, probably encrypted. Extracting as is.
[!] Error: Failed to decompress codeop, probably encrypted. Extracting as is.

尝试解包无果,看来是无了。

那个Base_Library.zip里面都是库文件的pyc,然后我们在pyinstaller库中找到archieve这么一个文件夹,里面有一个pyz_crypto.py文件,是对pyz文件的加密代码。
image

源码法解包

此方法参照 github上的文章,会在文章末尾放上出处。

因为此工具是开源的,我们已经知道源码,应该就可以尝试编写解密脚本,我们可以通过archive.pyc文件得到加密过程,crypto_key文件得到具体key参数。这个时候就只需要按照加密的方式进行解密就可以了,加密部分为Cipher脚本来源于archive.pyc的反编译)

import tinyaes
import zlib
 
CRYPT_BLOCK_SIZE = 16
 
key = bytes('MySup3rS3cr3tK3y', 'utf-8')
 
inf = open('baby_core.pyc.encrypted', 'rb') # 打开加密文件
outf = open('baby_core.pyc', 'wb') # 输出文件
 
iv = inf.read(CRYPT_BLOCK_SIZE)
 
cipher = tinyaes.AES(key, iv)
 
#主程序
plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))
 
outf.write(b'\x55\x0d\x00\x00\0\0\0\0\0\0\0\0\0\0\0\0') #补pyc文件头
 
outf.write(plaintext)
 
inf.close()
outf.close()

0x02 实战中应用

1.简单应用:

CTFshow内部赛:来一个python

我们经过上边的步骤之后,得到了反编译后的python文件:

#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.6


def  58encode(tmp = None):
    tmp = list(map(ord, tmp))
    temp = tmp[0]
    base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
    for i in range(len(tmp) - 1):
        temp = temp * 256 + tmp[i + 1]
    
    tmp = []
    while None:
        temp = temp // 58
        if temp == 0:
            break
        temp = ''
        for i in tmp:
            temp += base58[i]
        
    tmp = []
    for i in range(len(temp)):
        tmp.append(chr(ord(temp[i]) ^ i))
    
    check = [
        'A',
        '5',
        'q',
        'O',
        'g',
        'q',
        'd',
        '%7f',
        '[',
        '%7f',
        's',
        '{',
        'G',
        'A',
        'x',
        '`',
        'D',
        '@',
        'K',
        'c',
        '-',
        'c',
        ' ',
        'G',
        '+',
        '+',
        '|',
        'x',
        '}',
        'J',
        'h',
        '\\',
        'l']
    if tmp == check:
        return 1

flag = input('%e8%be%93%e5%85%a5flag%ef%bc%9a')
if 58encode(flag):
    print('you win')
else:
    print('try again')

这个程序就是把flag进行base58编码再作个异或,然后与check比较,所以将check异或后再base58解码即可

check = ['A','5','q','O','g','q','d','\x7f','[','\x7f','s','{','G','A','x','`','D','@','K','c','-','c',' ','G','+','+','|','x','}','J','h','\\','l']
a = ''
for i in range(len(check)):
    a+=chr(ord(check[i])^i)
 
print(a)

这里输出a后再base58解码即可

image

结果为:

ctfshow{zhe_bu_shi_flag}

2.结合其他算法的应用

sdpc杯,茶马古道

这个题是利用了TXTEA加密算法,和这个一起的结合起来出的题。

tea简介

TEA算法由剑桥大学计算机实验室的David Wheeler和Roger Needham于1994年发明。它是一种分组密码算法,其明文密文块为64比特,密钥长度为128比特。TEA算法利用不断增加的Delta(黄金分割率)值作为变化,使得每轮的加密是不同,该加密算法的迭代次数可以改变,建议的迭代次数为32轮。QQ使用此加密技术,加密轮数为16轮。
Tea算法是一种分组加密算法,以原文以8字节(64bite)为一组,密钥16字节(128bite)为一组,(char为1字节,int为4字节,double为8字节),该算法加密轮次可变,建议为32轮。(最低为16轮)
Tea算法的特征是存在0x9e3779b9.

#include <stdint.h>

void encrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0, i;
    uint32_t delta=0x9e3779b9;
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];
    for (i=0; i < 16; i++) {
        sum += delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
    }
    v[0]=v0; v[1]=v1;
}

上述是加密过程

解密脚本为:

void decrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], i;  /* set up */
    uint32_t delta=0x9e3779b9;
    uint32_t sum = delta << 4;
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];
    for (i=0; i<16; i++) {
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;
    }
    v[0]=v0; v[1]=v1;
}
解题

拿到题,咱们还是进行上边的操作,那这个EXE文件变成python文件,

源码如下:

# uncompyle6 version 3.8.0
# Python bytecode 3.8.0 (3413)
# Decompiled from: Python 3.9.2 (default, Feb 28 2021, 17:03:44) 
# [GCC 10.2.1 20210110]
# Embedded file name: easyre.py
# Size of source mod 2**32: 227 bytes
from ctypes import *
import binascii

def encrypt(v, k):
    v0, v1 = c_uint32(v[0]), c_uint32(v[1])
    delta = 2654435769
    k0, k1, k2, k3 = (k[0], k[1], k[2], k[3])
    total = c_uint32(0)
    for i in range(32):
        total.value += delta
        v0.value += total.value ^ (v1.value << 4) + k0 ^ v1.value + total.value ^ (v1.value >> 5) + k1
        v1.value += total.value ^ (v0.value << 4) + k2 ^ v0.value + total.value ^ (v0.value >> 5) + k3
    else:
        return (
         v0.value, v1.value)


print('这是一个flag校验文件^_^')
xx = input('请输入你的flag:')
vvalue = [212553138, 1725195397, 218496566, 3614246156, 254733149, 2117214528]
while True:
    if len(xx) != 24:
        print('flag错误')
        break
    comppare = []
    for i in range(0, 24, 8):
        value = []
        xxx = xx[i:i + 8]
        xxx1 = xxx[0:4].encode()
        xxx2 = xxx[4:].encode()
        hexvalue1 = binascii.b2a_hex(xxx1).decode()
        hexvalue2 = binascii.b2a_hex(xxx2).decode()
        value.append(int(hexvalue1, 16))
        value.append(int(hexvalue2, 16))
        Arglist = [1263159624, 1330531660, 1397903696, 1465275732]
        res = encrypt(value, Arglist)
        comppare.append(res[0])
        comppare.append(res[1])

    if comppare == vvalue:
        print('flag正确')
        break
    else:
        print('flag错误')
        break

上边的运算逻辑是进行一个一次变种tea然后下边就是简单的运算,直接开逆。

解题脚本如下:

from calendar import c
from ctypes import *
import libnum

def decrypt(v, k):
    v0, v1 = c_uint32(v[0]), c_uint32(v[1])
    delta = 2654435769
    k0, k1, k2, k3 = (k[0], k[1], k[2], k[3])
    total = c_uint32(delta * 32)
    for i in range(32):
        v1.value -= total.value ^ (v0.value << 4) + k[2] ^ v0.value + total.value ^ (v0.value >> 5) + k[3]
        v0.value -= total.value ^ (v1.value << 4) + k[0] ^ v1.value + total.value ^ (v1.value >> 5) + k[1]
        total.value -= delta
    else:
        return (
         v0.value, v1.value)


if __name__ == '__main__':
    flag=b''
    l=[212553138, 1725195397, 218496566, 3614246156, 254733149, 2117214528]
    for i in range(0,6,2):
        a = [l[i],l[i+1]]
        k = [1263159624, 1330531660, 1397903696, 1465275732]
        res = decrypt(a, k)
        flag += libnum.n2s(res[0])
        flag += libnum.n2s(res[1])
    print(flag)

image

0x03 总结:

关于python打包文件的类型的题目在最近ctf比赛中属于的比较门题型,他可以和其他的加密算法结合进行考察,通过融合其他的算法可以大大增加题目的难度,增加解题的繁琐程度。这一块儿还是非常值得我们进行学习的。

# CTF # CTF writeup
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录