CrackMe&F***Me

  • 来自NSSCTF 4th 2025 的一道需要手动解包exe的题目

最开始是想用pyinstxtractor进行解包,尝试了几下一直报错。

image-20250824193550267

看一眼pyinstxtractor.py源码。发现报错的原因是没有找到MAGIC。

image-20250824193715048

这个MAGIC一般在文件末尾附近,pyinstxtractor 用它定位 TOC 表,从而获取文件入口。看一眼附件的MAGIC,发现MAGIC被修改为了 ‘swI\014\013\012\013\016’ 。

image-20250824231148368

修改回 ‘MEI\014\013\012\013\016’ 然后用pyinstxtractor进行解包。

发现没有解包完全,最重要的内容解压失败了,估计是压缩的时候加密了。

pyinstaller 在打包时提供了加密压缩的功能,不过最新版本中好像取消掉了。查看一下pyinstaller的源码,就可以看到加解密方法,AES。

image-20250824193044008

pyinstxtractor-ng好像有自动解密的功能,尝试解压也失败了,也是解压错误。

image-20250824193403166

这里用pyinstaller源码下提供的一个archive_viewer.py脚本,查看一下压缩文件的信息。可以发现里面确实有一个名为”CrackMe&F***Me”的脚本文件被压缩了。

image-20250824193124277

再次尝试提取,依然解压错误。接下来就只能在解密后提取文件或者获得密钥

image-20250824193322122

IDA分析,main函数进去后调用的就是主逻辑函数。

这里可以发现MAGIC的检验被修改了。等下动调的时候要把MAGIC改回 ‘swI\014\013\012\013\016’ 不然会闪退。

image-20250824233214578

逻辑有很多,但是每一步都有字符串,于是从字符串入手。

搜索”archive” 会看到一个脚本解压失败的字符串。

image-20250824233511802

引用过去,发现这里有个marshal,那么这里就是python字节码被转为code对象的地方,我们只需要在marshal函数下断点就能拿到python机器码。分析这个函数前面的逻辑,大概就是在遍历文件,判断类型是不是 ‘s’ 然后解压,获取这个脚本文件。解密代码就在getFile函数里面,不过也不需要逆解密代码了,直接获取python机器码即可。

image-20250824234357903

下断点动调,发现程序竟然没有执行到断点位置?于是单步运行,看看在这个函数执行前的函数有什么猫腻。

发现运行到下面这个函数python机器码就被执行了。继续跟进去。

image-20250824235303272

会发现里面调用CreateProcessW创建了一个子进程,子进程执行的程序正是CreackMe&&FxxMe.exe。

原来是子进程运行到了python机器码码执行的位置。接下来我们要调试子进程来获取机器码的内容。

image-20250824235518654

这里用x64dbg的dbgchild插件来调试子进程。先计算一下内存地址,在mashal函数下断点。继续运行后弹出子进程的调试界面,并断在marshal函数处。第一次断在这里是bootstrap的机器码,第二次才是CrackMe&F***Me的字节码。看到机器码长度正好是,解压后CrackMe&F**Me的长度。

image-20250824193853827

用Scylla导出内存数据,写脚本反序列化,再用dis模块反编译一下即可看到逻辑。注意使用的python版本应为3.8。

1
2
3
4
5
6
7
8
import dis
import marshal

with open('MEM_000001B5EC17E2B0_000003B9.mem', 'rb') as f:
data = f.read()
print("len: ",len(data))
a = marshal.loads(data)
dis.dis(a)

密文”d29b81e136efc517c2967b863f584baf4b82f710f8869f5a56185cb22a9a25fc”

密钥”NSSCTF”

image-20250825000731696

加密函数看似是一个RC4,猜测没有魔改

image-20250825000811611

如果不确定,可以直接丢给AI反写出python脚本。

解密

image-20250825001000273


PyArmerMini

  • 来自 Lilctf 2025 的预热赛题,pyarmermini逆向,一个贴近真实生产环境的题目

pip 安装一个同版本的pyarmor_mini,去安装的软件包下寻找pyarmor_mini.pyd分析。在sub_62401600函数中可以看到反序列化字节码的位置PyMarshal_ReadObjectFromString。之后调用PyImport_ExecCodeModuleObject执行python字节码。

image-20250915231327900

动调看PyMarshal_ReadObjectFromString的参数发现就是提取了0x7e个字节后面的数据进行反序列化(即偏移0x7e是 code object 开始的位置)。提出字节码,手动反序列化后用dis反汇编。

1
2
3
4
5
6
import dis
import marshal
data = b'PYARMIN\x01\x03\x0bP`o\x10\x0f\x0f~\x00\x00\x00\xde\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\xd1\xb3s\xe1\x93\xdb\xd9\xe4-\xa9)\xea \xfai\xa6D\xf6\x19\xbb\x89\x83u1\xc3[\xaa~\xa9pu\x8e\xd87\x03\xb3s,ai\xfe\x01#O\xf0\xee\xc7\xb7S,?~\xfa\xbe\xa2\x97\xd0\xd5\xd9g\x04\xdcl\x96\xaf\xd2\xfa\xcc\x97\xe3\x01\xceYQ\xbf\xb9\xc3\x98\xd1\xb4\t\x0e\xb7Y\x83\xec\xa7\x04\xec\x95\x8cj\xfc\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\xf3\x00\x03\x00\x00\x97\x00\x02\x00d\x00g\x01d\x01\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x02\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x03\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\\\x01\x00\x00Z\x00\x02\x00d\x00g\x01d\x01\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x02\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\\\x01\x00\x00Z\x01\t\x00\x02\x00e\x02d\x05\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00Z\x03e\x04\xa0\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00e\x03d\x06d\x07d\x08\x85\x03\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00d\t\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00d\nz\x06\x00\x00\x02\x00e\x04d\x0b\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00k\x02\x00\x00\x00\x00\x02\x00e\x00e\x03d\x0cd\x07\x85\x02\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\r\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00e\x01d\x0e\x84\x00\x02\x00e\te\ne\x03\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00e\x04d\x0f\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00k\x02\x00\x00\x00\x00g\x03Z\x0b\x02\x00e\x0ce\x0b\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00s\x16\x02\x00e\rd\x10\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00e\x0ed\x11\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x01Z\x0fe\x03D\x00]$Z\x10d\x12\x02\x00e\ne\x10\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00z\n\x00\x00d\x13z\x0c\x00\x00Z\x10e\x0fe\x10z\x14\x00\x00Z\x0fe\x0fe\x10d\x11z\x01\x00\x00r\x02d\x14n\x01d\x15z\x10\x00\x00Z\x0f\x8c%\x02\x00e\x04d\x16\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00e\x0fk\x02\x00\x00\x00\x00r\r\x02\x00e\rd\x17\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x07S\x00\x02\x00e\rd\x10\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x07S\x00#\x00\x01\x00\x02\x00e\rd\x18\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00e\x0ed\x11\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00Y\x00d\x07S\x00x\x03Y\x00w\x01)\x19\xa9\x03s\x10\x00\x00\x00\xb7S,?~\xfa\xbe\xa2\x97\xd0\xd5\xd9g\x04\xdcls\x0f\x00\x00\x00\x96\xaf\xd2\xfa\xcc\x97\xe3\x01\xceYQ\xbf\xb9\xc3\x98s\x0f\x00\x00\x00\xd1\xb4\t\x0e\xb7Y\x83\xec\xa7\x04\xec\x95\x8cj\xfc\xe9\x00\x00\x00\x00\xe9\x02\x00\x00\x00\xf3!\x00\x00\x00^\xac\x9eI\x89\xd5\xeb\xfc\xa5\xd5\n$\xd1\xf3\xfb\xf1\x1d\xb8\x1a\x89@>\xd3\xfe]\xc0\x8f5\xf9\x97:\x01\x00\xf3!\x00\x00\x00\x89\x03\xfe\xeaf\x12\xb3\xb2\x05\xe4\xfa\xaa\x95e\xe9\xabK\xd1_UqS+\xe7\xaa\x81\x88x\x9b\xa5\xbd\xf5\x00\xfa\x13Tell me your flag: \xe9\x03\x00\x00\x00N\xe9\x04\x00\x00\x00\xda\x03big\xe9\xb2\xc4|\x17\xda\x0880309898i\xf0\xff\xff\xff\xda08475869b2b6434ba59b53079e1ea334aae9483bf87e890b7c\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\xf3\x0c\x00\x00\x00\x97\x00|\x00|\x01z\x0c\x00\x00S\x00)\x01N\xa9\x00)\x02\xda\x01x\xda\x01ys\x02\x00\x00\x00 \xfa\x11<frozen plain.py>\xfa\x08<lambda>r\x13\x00\x00\x00\x0b\x00\x00\x00s\n\x00\x00\x00\x80\x00\x98A\xa0\x01\x99E\x80\x00\xf3\x00\x00\x00\x00\xda\x0239\xda\x05Wrong\xe9\x01\x00\x00\x00\xe9\x91\x00\x00\x00\xe9[\x00\x00\x00\xe9\x07\x00\x00\x00\xe9\x08\x00\x00\x00\xda]490113086473394987304775875938824284212507397345393304550549845726940286289777973973840177024\xda\x05Right\xfa\x16Exception occurred QwQ)\x11\xda\x06sha256\xda\x06reduce\xda\x05input\xda\x04flag\xda\x03int\xda\nfrom_bytes\xda\x06encode\xda\thexdigest\xda\x08endswith\xda\x03map\xda\x03ord\xda\x07group_1\xda\x03all\xda\x05print\xda\x04exit\xda\x03acc\xda\x01ir\x0f\x00\x00\x00r\x14\x00\x00\x00r\x12\x00\x00\x00\xfa\x08<module>r0\x00\x00\x00\x01\x00\x00\x00s\xf4\x01\x00\x00\xf0\x03\x01\x01\x01\xd8\x00\x1a\xd0\x00\x1a\xd0\x00\x1a\xd0\x00\x1a\xd4\x00\x1a\xd0\x00\x1a\xd4\x00\x1a\xd0\x00\x1a\xd1\x00\x1a\xd4\x00\x1a\xd1\x00\x1a\xd0\x00\x1a\xd8\x00\x1c\xf0\x03\x00\x01\x1b\xd0\x00\x1a\xd0\x00\x1a\xd4\x00\x1a\xd8\x00\x1c\xd4\x00\x1c\xd0\x00\x1c\xd1\x00\x1c\xd4\x00\x1c\xd1\x00\x1c\xd0\x00\x1c\xf0\x04\x1b\x01\x0c\xd8\x0b\x10\x885\xd0\x11&\xd1\x0b\'\xd4\x0b\'\x80D\xf0\x06\x00\t\x0c\x8f\x0e\x8a\x0e\x90t\x98A\x98D\x98q\x98D\x94z\xd7\x17(\xd2\x17(\xd1\x17*\xd4\x17*\xa8E\xd1\x082\xd4\x082\xb0Y\xd1\x08>\xc0#\xc0#\xd8\x0c\x1b\xf1\x03\x01C\x01\x1d\xf4\x00\x01C\x01\x1d\xf2\x00\x01\t\x1d\xe0\x08\x0e\x88\x06\x88t\x90C\x90D\x90D\x8cz\xd7\x0f \xd2\x0f \xd1\x0f"\xd4\x0f"\xd1\x08#\xd4\x08#\xd7\x08-\xd2\x08-\xd1\x08/\xd4\x08/\xd7\x088\xd2\x088\xb8\x1f\xd1\x08I\xd4\x08I\xd8\x08\x0e\x88\x06\xd0\x0f!\xd0\x0f!\xa03\xa03\xa0s\xa8D\xa1>\xa4>\xd1\x082\xd4\x082\xb0c\xb0c\xb8/\xd16J\xd46J\xd2\x08J\xf0\t\x05\x0f\x06\x80G\xf0\x0e\x00\x0c\x0f\x883\x88w\x89<\x8c<\xf0\x00\x02\x05\x10\xd8\x08\r\x88\x05\x88g\x89\x0e\x8c\x0e\x88\x0e\xd8\x08\x0c\x88\x04\x88Q\x89\x07\x8c\x07\x88\x07\xe0\n\x0b\x80C\xd8\r\x11\xf0\x00\x03\x05$\xf0\x00\x03\x05$\x88\x01\xd8\r\x10\x903\x903\x90q\x916\x946\x89\\\x98T\xd1\x0c!\x88\x01\xd8\x08\x0b\x88q\x89\x08\x88\x03\xd8\x08\x0b\x90a\x98!\x91e\xd0\x11"\x90\x11\x90\x11\xa0\x11\xd1\x08#\x88\x03\x88\x03\xe0\x07\n\x80s\x88?\xd1\x07\x1b\xd4\x07\x1b\x98s\xd2\x07"\xd0\x07"\xd8\x08\r\x88\x05\x88g\x89\x0e\x8c\x0e\x88\x0e\x88\x0e\x88\x0e\xe0\x08\r\x88\x05\x88g\x89\x0e\x8c\x0e\x88\x0e\x88\x0e\x88\x0e\xf8\xf0\x04\x02\x01\x0c\xd8\x04\t\x80E\xd0\n"\xd1\x04#\xd4\x04#\xd0\x04#\xd8\x04\x08\x80D\x88\x11\x81G\x84G\x80G\x80G\x80G\x80G\xf8\xf8\xf8s\x12\x00\x00\x00\xb6D\x1dE"\x00\xc5\x15\x0bE"\x00\xc5"\x18E=\x03'

a = marshal.loads(data[0x7e::])
dis.dis(a)

然后恢复逻辑,编写解密脚本

实测用z3解只需要解最后的和校验即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from z3 import *
from functools import reduce

def solve_for_length(length):
s = Solver()

var = [BitVec(f'flag_{i}', 8) for i in range(length)]

s.add(var[0] == ord('L'))
s.add(var[1] == ord('I'))
s.add(var[2] == ord('L'))
s.add(var[3] == ord('C'))
s.add(var[4] == ord('T'))
s.add(var[5] == ord('F'))
s.add(var[6] == ord('{'))
s.add(var[length-1] == ord('}'))

for char in var:
s.add(And(char >= 32, char <= 126))

# 可忽视条件
# xor_sum = reduce(lambda x, y: x ^ y, var)
# s.add(xor_sum == 39)

# sliced = [var[i] for i in range(3, length, 4)]
# intvec = Concat(sliced)
# intdata = ZeroExt(256 - intvec.size(), intvec)
# s.add(URem(intdata, BitVecVal(394052786, 256)) == BitVecVal(80309898, 256))

# acc = BitVecVal(0, 1024)

for char in var:
i = (BitVecVal(145, 8) - char) ^ BitVecVal(91, 8)
acc1 = acc | ZeroExt(acc.size() - i.size(), i)
shift_amount = If((i & 1) == 1, BitVecVal(7, 1024), BitVecVal(8, 1024))
acc = acc1 << shift_amount

s.add(acc == BitVecVal(490113086473394987304775875938824284212507397345393304550549845726940286289777973973840177024, 1024))

flag = []

while s.check() == sat:
m = s.model()
flag.append(''.join([chr(m[var[i]].as_long()) for i in range(length)]))
block = []
for i in range(length):
block.append(m[var[i]]!= var[i])
s.add(Or(block))

print(flag) if len(flag) > 0 else None

for i in range(32, 48):
solve_for_length(i)

#LILCTF{As5uMIN6_thE_m@j3$tY_#1_the_716er}