霍雅
2026长城杯决赛RE wp
长城杯决赛RE wp
attachment WP_new
attachment
解压文件,发现有个jar包,一个pcap,先来看看这个pcap。可以看到存在大量http请求,主要通信目标为192.168.117.136
结合另一个movie-review-system-1.0.0.jar文件猜测这个ip是这个服务提供的接口,而且在这个http流量当中发现下面这些接口被大量调用:
·/api/reviews/add
·/api/user/avatar
在reviews这个评论接口,出现了大量特殊格式的内容,例如:
{"content": "data[/payload.enc]"}
{"content": "data[0]"}
{"content": "data[102]"}
{"content": "data[/tmp/payload_run]"}
{"content": "data[chmod +x /tmp/payload_run]"}而头像avatar接口则不断出现类似的内容:
{"emojiAvatarId": 16}
{"emojiAvatarId": 25}
{"emojiAvatarId": 23}这时候就推测这个请求不是正常的用户行为,而是在通过这两个接口传递某种特殊的数据,这里面的data[]猜测是在传递emojiAvatarId。这时候需要对jar包进行分析
使用jadx反编译,翻了一下文件发现有一个playload.enc
还发现了几个比较可疑的类名:
那么首先来看看这个UserBehaviorAnalyticsAspect
这里使用了Spring AOP,将请求当中的数据写入到VM上下文当中,这些数据来自emjiAvatarId,以及评论内容[]当中的数据,就比如说{"content": "data[/payload.enc]"}会向VMContext当中传入/payload.enc这个字符串
接下来看看这个VMContext类:
非常简单啊,这个ctx当中,就是一个list
接下来就是这个AnalyticsReportGenerator 类
可以看到这里就是一个vm的结构了,然后根据这个结构恢复一下对应的opcode的作用:
| opcode | 作用 |
|---|---|
| 10 | 读取资源文件 |
| 11 | 读取byte数组指定下标 |
| 12 | 修改byte数组指定下标 |
| 13 | 异或运算 |
| 15 | 写文件 |
| 16 | 执行系统命令 |
| 17 | 获取byte数组长度 |
| 18 | 字符串转整数 |
| 20 | 交换栈顶元素 |
| 21 | Jmp |
| 22 | Jcc |
| 23 | Sub |
| 24 | Add |
| 25 | 从栈中取出指定位置的元素 |
| 26 | Pop |
大概就是这样的
这里先将https的追踪流从wireshark当中保存下来,然后写个python脚本将vm有关的给单独拎出来:
import re
INPUT_FILE = "https.txt"
OUTPUT_FILE = "vm_stream.txt"
def main():
with open(INPUT_FILE, "r", encoding="utf-8", errors="ignore") as f:
text = f.read()
events = []
# 按 HTTP 请求切开,只处理 POST 请求块
blocks = re.split(r"(?=POST\s+/api/)", text)
for block in blocks:
# 1. 评论接口:提取 data[...] 里面的内容
if block.startswith("POST /api/reviews/add"):
m = re.search(r'"content"\s*:\s*"data\[(.*?)\]"', block, re.S)
if m:
events.append(m.group(1))
# 2. 头像接口:提取 emojiAvatarId,作为 opcode
elif block.startswith("POST /api/user/avatar"):
m = re.search(r'"emojiAvatarId"\s*:\s*(\d+)', block)
if m:
events.append(m.group(1))
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
for e in events:
f.write(str(e) + "\n")
print(f"[+] 提取到 {len(events)} 条 VM 事件")
print(f"[+] 已保存到 {OUTPUT_FILE}")
if __name__ == "__main__":
main()结果如下:
根据dump下来的指令流,根据vm规则恢复一下大概大概是这样的:
key = 102;
for (i = 0; i < len(payload_enc); i++) {
p = (payload_enc[i] ^ key) - i;
payload_enc[i] = p & 0xff;
key = p ^ 55;
}那么此时可以直接根据这个逻辑来解密这个payload_enc了:
enc = open("payload.enc", "rb").read()
key = 102
out = bytearray()
for i, c in enumerate(enc):
p = ((c ^ key) - i) & 0xff
out.append(p)
key = p ^ 55
open("payload_run", "wb").write(out)然后根据vm指令流当中的字符串,大致可以判断这个文件是个elf文件,直接丢ida看看:
分析了一下结构发现这个就是一个aes cbc的结构,然后可以从xmmword_5280,xmmword_5270,byte_6970当中分别存放的是key,iv以及sbox
从内存当中获取一下:
key = 4a7f2c91b35ed816fa4309cc7be5283d
iv = 8e1af65530c974bb2d974e1160daa73c
sbox
发现这个sbox并不是标准的sbox,还需要做一定的修改。
在开头可以发现这里读取了文件,这个文件路径是:
/home/ccb/flag.txt,此外还发现了一些有趣的字符串:
那么猜测就是通过读取flag.txt然后aescbc加密,然后就传到了/api/telementry这个接口当中,那么就通过wireshark查查这个ip和接口
可以发现密文长度是80字节,那么直接扒出来写个解密脚本
sbox = [
0xC5,0xEA,0xB8,0x6C,0x91,0xA2,0x11,0x44,0x05,0xBA,0x76,0x99,0x45,0x53,0xEF,0x54,
0xA5,0xF9,0x90,0x06,0xF6,0x28,0xEB,0x48,0x85,0x66,0x64,0x5C,0x3A,0x0E,0xE7,0x1B,
0xF5,0x70,0xDB,0xA1,0x6F,0xE4,0xCE,0xCF,0xB6,0xE2,0xD9,0xA4,0xD2,0xB2,0xE9,0xC7,
0xE5,0x9D,0xFE,0x2E,0xFF,0x84,0x09,0x50,0xD0,0x41,0x20,0x5F,0xD4,0x4D,0xAA,0x61,
0xDD,0x15,0x1F,0x26,0xCA,0xFD,0x1D,0xBD,0x7A,0x57,0xBF,0x46,0x40,0xB3,0x2A,0x93,
0x96,0x39,0x56,0xBE,0xCB,0x9C,0x9F,0xF1,0x4E,0x49,0x7E,0x8E,0xD3,0xB9,0xC4,0xFA,
0xD5,0x67,0x03,0x1A,0x58,0x55,0x30,0x7F,0x32,0xC3,0x8F,0xDF,0xA3,0xD1,0x0F,0xDA,
0x4F,0x88,0x6D,0xC1,0x37,0xD6,0x62,0x17,0xA7,0x19,0x6B,0x27,0x98,0xA9,0x7D,0x0C,
0x23,0x82,0xAD,0x52,0x42,0x68,0xDE,0x1E,0xA8,0x3B,0x33,0x3D,0x43,0x9B,0x13,0x0B,
0xF0,0xCC,0x8C,0x01,0x12,0x75,0xEE,0x47,0x07,0x8B,0x14,0x2B,0xD8,0xAE,0x04,0x87,
0x86,0xDC,0xBB,0xE8,0xE6,0x3C,0x78,0x77,0xC2,0xE0,0x69,0x29,0x02,0xB1,0x35,0xB5,
0x00,0x7C,0x83,0xC8,0x18,0x8A,0x60,0x36,0x24,0xC9,0xFB,0x38,0xAF,0x80,0xB0,0x31,
0xCD,0x59,0x94,0xC6,0x4C,0xD7,0xC0,0x71,0xE1,0xED,0x8D,0x79,0x3F,0x4B,0x72,0x9E,
0x3E,0x08,0x2C,0x9A,0x0A,0x63,0x22,0x5B,0x1C,0x5A,0x25,0xF8,0x4A,0xF7,0xA0,0xE3,
0x6A,0x2F,0x89,0x74,0x7B,0x5E,0x2D,0x5D,0xBC,0x95,0xA6,0x0D,0x16,0xFC,0xAC,0x34,
0xF4,0x51,0xAB,0x6E,0x92,0xEC,0xB4,0x97,0x81,0x65,0xF3,0xB7,0x73,0x10,0x21,0xF2
]
inv_sbox = [0] * 256
for i, x in enumerate(sbox):
inv_sbox[x] = i
rcon = [0,1,2,4,8,16,32,64,128,27,54]
def xtime(x):
return (((x << 1) ^ 0x1b) & 0xff) if x & 0x80 else ((x << 1) & 0xff)
def gmul(a, b):
r = 0
while b:
if b & 1:
r ^= a
a = xtime(a)
b >>= 1
return r
def expand_key(key):
w = list(key)
for i in range(4, 44):
t = w[4*(i-1):4*i]
if i % 4 == 0:
t = [sbox[t[1]], sbox[t[2]], sbox[t[3]], sbox[t[0]]]
t[0] ^= rcon[i // 4]
for j in range(4):
w.append(w[4*(i-4)+j] ^ t[j])
return [w[16*i:16*i+16] for i in range(11)]
def add_round_key(s, rk):
for r in range(4):
for c in range(4):
s[4*r+c] ^= rk[4*c+r]
def inv_shift_rows(s):
shifts = [0, 3, 1, 2]
for r in range(1, 4):
row = s[4*r:4*r+4]
sh = shifts[r]
for c in range(4):
s[4*r+c] = row[(c - sh) & 3]
def inv_sub_bytes(s):
for i in range(16):
s[i] = inv_sbox[s[i]]
def inv_mix_columns(s):
for c in range(4):
a = [s[c], s[4+c], s[8+c], s[12+c]]
s[c] = gmul(a[0],14) ^ gmul(a[1],11) ^ gmul(a[2],13) ^ gmul(a[3],9)
s[4+c] = gmul(a[0],9) ^ gmul(a[1],14) ^ gmul(a[2],11) ^ gmul(a[3],13)
s[8+c] = gmul(a[0],13) ^ gmul(a[1],9) ^ gmul(a[2],14) ^ gmul(a[3],11)
s[12+c] = gmul(a[0],11) ^ gmul(a[1],13) ^ gmul(a[2],9) ^ gmul(a[3],14)
def decrypt_block(block, keys):
s = [0] * 16
for c in range(4):
for r in range(4):
s[4*r+c] = block[4*c+r]
add_round_key(s, keys[10])
for rnd in range(9, 0, -1):
inv_shift_rows(s)
inv_sub_bytes(s)
add_round_key(s, keys[rnd])
inv_mix_columns(s)
inv_shift_rows(s)
inv_sub_bytes(s)
add_round_key(s, keys[0])
out = [0] * 16
for c in range(4):
for r in range(4):
out[4*c+r] = s[4*r+c]
return bytes(out)
key = bytes.fromhex("4a7f2c91b35ed816fa4309cc7be5283d")
iv = bytes.fromhex("8e1af65530c974bb2d974e1160daa73c")
ct = bytes.fromhex(
"08a76a304f8a7d64baace233c30d8e78"
"9e27ec1ae589e7b36252ea00ecf2a9"
"c274b18754f4758095956a08dc1c6793"
"e07cf91658ae232ac3935aa17c03294a"
"625c90ba2ef4b482ffb145388829ed5554"
)
keys = expand_key(key)
pt = b""
prev = iv
for i in range(0, len(ct), 16):
dec = decrypt_block(ct[i:i+16], keys)
pt += bytes(a ^ b for a, b in zip(dec, prev))
prev = ct[i:i+16]
pad = pt[-1]
pt = pt[:-pad]
print(pt.decode())
# flag{F1n@1ly_Y0u_G0t_Th1s_f1ag_and_f1nd_7h3_TRUTH_D0_Y0u_L1k3_1t?}dokigame
基于 Ren'Py 打包的小游戏。进入目录后可以看到 game/script.rpyc
使用unrpyc解包
打开解包后的文件
可以看到有个PE文件落地并执行了
提出来有个upx
Upx-d 直接拖
回到rpy文件
大概逻辑就是
输入一个值xor 35
和1.exe的输出比较
输出的值
Exp:
a=[0x45, 0x12, 0x14, 0x40, 0x16, 0x10, 0x40, 0x10, 0xe, 0x47, 0x40, 0x11, 0x15, 0xe, 0x17, 0x15, 0x41, 0x12, 0xe, 0x41, 0x10, 0x14, 0x10, 0xe, 0x11, 0x40, 0x42, 0x13, 0x13, 0x42, 0x15, 0x42, 0x15, 0x14, 0x11, 0x12]
flag = ""
for i in range(len(a)):
flag += chr(a[i]^35)
print(flag)
#f17c53c3-dc26-46b1-b373-2ca00a6a6721dumbcpp
这题是一个 Unity IL2CPP 逆向题
我们先用Il2CppDumper
恢复符号和偏移
但是GameAssembly.dll有tmd壳
所以得先拖壳
先用x64dbg调试
然后用virtualprotect多运行几次
直到dll不再是tmd的那种壳结构
可以运行28次,29是别的dll
直接dump
然后恢复符号
查看dbspy
符号名是xyz987
恢复完就是这些了
字段初始化
check_input()
手写一下大概就是
input = input.Trim();
if (input.Length != 42)
ShowWrong();
for (int i = 0; i < 100000; i++)
rand.Next();
realKey = GetRealKey();
cipher = AESEncrypt(input, realKey);
if (cipher == target_cipher && anti_debug_flag == false)
ShowRight();
else
ShowWrong();aes是cbc模式
然后key向右补0
把 key_part1 的整数逐个拼成字符串,再拼上 key_part2 从第 3 位开始的 5 个字符,生成 AES 的真实密钥
回到ctor
在 dump.cs 中,<PrivateImplementationDetails> 给出了静态数组的 Metadata offset。本题需要的两段数据分别是:
target_cipher:偏移 0x2B9890,长度 48
key_part1:偏移 0x2B98E8,长度 20
因此可以直接读取 global-metadata.dat:
import pathlib
import struct
meta = pathlib.Path("dumbcpp_Data/il2cpp_data/Metadata/global-metadata.dat").read_bytes()
target_cipher = meta[0x2B9890:0x2B9890+48]
key_part1 = struct.unpack("<5I", meta[0x2B98E8:0x2B98E8+20])
print(target_cipher.hex())
print(key_part1)将 key_part1 转成字符后得到:
asdfg
而:
key_part2 = "9876543210"
key_part2.Substring(3, 5) = "65432"
因此真实 key 明文为:
asdfg65432
然后像右补0得到asdfg654320000000000000000000000
Iv
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
ct = bytes.fromhex("c6788f4ebc791b7572eca04b2a9ccb23f9a1cee4a0dd3ec4e8d64d119fe3ac1b9cc08893bfba51ee7a3687207b7b3c68")
key = b"asdfg654320000000000000000000000"
iv = b"1234567890ABCDEF"
pt = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(ct), 16)
print(pt.decode())
你爹来了!