XCTF-TPCTF2025

鄙人萌新,第一次搭博客上传的这份wp,如有不足之处还望多多包含

题目来源:https://github.com/tp-ctf/TPCTF2025/tree/main

Re

文章复现转载涉及链接:

https://blog.xmcve.com/2025/03/11/TPCTF2025-Writeup/

https://patr1ck-s.github.io/2025/03/10/2025-TPCTF-RE-wp/

chase

第一段用ce修改得分直接通关,注意得分数据是一个字节的

在ppu查看器里看见第二段flag

本机是debug里,有的在tool里

这里能看到第三段flag,ppu里的每个字符都有显示不同的Tile值

原理就是tile值映射可见输出字符,找flag(262c2127)对应tile值去二进制内查看

字符最大到3F截至,有00隔开,内容容易区分,两段内容对应flag2 3

写脚本映射即可

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
table = {0xd0: '0', 0xd1: '1', 0xd2: '2', 0xd3: '3', 0xd4: '4', 0xd5: '5', 0xd6: '6', 0xd7: '7', 0xd8: '8', 0xd9: '9',
33: 'A', 34: 'B', 35: 'C', 36: 'D', 37: 'E', 38: 'F', 39: 'G', 40: 'H', 41: 'I', 42: 'J', 43: 'K', 44: 'L',
45: 'M', 46: 'N', 47: 'O', 48: 'P', 49: 'Q', 50: 'R', 51: 'S', 52: 'T', 53: 'U', 54: 'V', 55: 'W', 56: 'X',
57: 'Y', 58: 'Z', 0x3d: '_', 0x1a: ':', 0x1b: ';', 0x1c: '<', 0x1d: '=', 0x1e: '>', 0x20: '@', 0x00: ' ',
0x0e: '.',
0x3b: '{', 0x3c: '}'}
enc = [
0x26, 0x2C, 0x21, 0x27, 0x00, 0x30, 0x34, 0x0E, 0xD2, 0x00, 0x26, 0x2F,
0x32, 0x00, 0x39, 0x2F, 0x35, 0x00, 0x29, 0x33, 0x00, 0x01, 0x12, 0xA4,
0x00, 0x01, 0x18, 0x30, 0x2C, 0x21, 0x39, 0xD1, 0x2E, 0xD9, 0x3D, 0xD6,
0x20, 0x2D, 0xD3, 0x33, 0x3D
]

flag2 = ''.join(table.get(i, '*') for i in enc)
print(flag2)
enc1 = [
0x26, 0x2C, 0x21, 0x27, 0x00, 0x30, 0x34, 0x0E, 0xD1, 0x00, 0x26, 0x2F,
0x32, 0x00, 0x39, 0x2F, 0x35, 0x00, 0x29, 0x33, 0x00, 0x02, 0x2A, 0x34,
0x30, 0x23, 0x34, 0x26, 0x3B, 0x24, 0xD0, 0x3D, 0x39, 0xD0, 0x35, 0x3D,
0x2C, 0xD1, 0x2B, 0x25, 0x3D
]
flag3 = ''.join(table.get(i, '*') for i in enc1)
print(flag3)
# FLAG PT.2 FOR YOU IS *** **PLAY1N9_6@M3S_
# FLAG PT.1 FOR YOU IS *JTPCTF{D0_Y0U_L1KE_

TPCTF{D0_Y0U_L1KE_PLAY1N9_6@M3S_ON_Y0UR_N3S?}

有个类似的题目,思路跟这题不一样:https://www.cnblogs.com/harmonica11/p/13456725.html

这明明是misc题

linuxpdf

pdf文件,需要在chrome下运行,edge不行

原主运行和题目运行的内容有所区别

flag的出现跟a9文件加载有关

可以看到相关base64加密内容

用脚本提取

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
import base64
import zlib


def base64_decode_and_inflate(encoded_data):
try:

compressed_data = base64.b64decode(encoded_data)

try:
decompressed_data = zlib.decompress(compressed_data)
print("数据格式: zlib")
except zlib.error:

try:
decompressed_data = zlib.decompress(compressed_data, 16 + zlib.MAX_WBITS)
print("数据格式: gzip")
except zlib.error:

try:
decompressed_data = zlib.decompress(compressed_data, -zlib.MAX_WBITS)
print("数据格式: raw deflate")
except zlib.error as e:
print(f"解压缩失败: {e}")
return None

return decompressed_data
except Exception as e:
print(f"解压缩失败: {e}")
return None


with open(r"D:\CTF\ctftimu\tpctf\linuxpdf\linux.pdf", 'r') as f:
res = f.read()
start_index = res.find("\"vm_64.cfg\"")
ret = res[start_index:res.find("for \\(let filename i")][:-4]

list_1 = ret.split(", ")
for i in range(len(list_1)):
ret = list_1[i].split(": ")[0].replace("\"", "")

data = list_1[i].split(": ")[1].replace("\"", "")

res = base64_decode_and_inflate(data)

with open(f"{ret}", "wb") as f1:
f1.write(res)
ida打开

ida分析a9文件为md5加密

unk_4008标签找到数据(ida9伪c或者低版本直接看)

16位为一组,第一个解出来为F,以此为FDP,倒着解密

wp

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
from hashlib import md5
enc = [56, 248, 138, 59, 197, 112, 33, 15, 138, 141, 149, 88, 91, 70, 176, 101, 0, 131, 5, 90, 232, 12, 220, 139, 213, 147, 120, 184, 98, 141, 115, 63, 203, 0, 250, 125, 175, 251, 215, 172, 236, 19, 176, 105, 93, 147, 90, 4,
188, 15, 0, 194, 156, 192, 253, 56, 1, 199, 253, 211, 21, 199, 130, 153, 155, 212,
203, 0, 43, 162, 208, 26, 241, 45, 155, 227, 26, 43, 68, 50, 60, 26, 79,
71, 0, 221, 238, 186, 240, 2, 82, 122, 158, 173, 120, 189, 22,
104, 69, 115, 204, 0, 191, 149, 184, 153, 52, 161, 181, 85, 225,
9, 15, 236, 223, 211, 218, 159, 0, 182, 66, 44, 48, 176, 41, 56,
83, 95, 142, 100, 141, 96, 168, 123, 148, 0, 8, 193, 183, 102, 67,
175, 141, 213, 12, 176, 109, 127, 221, 60, 248, 237, 0, 66, 214, 151,
25, 249, 112, 136, 240, 101, 64, 244, 18, 220, 23, 6, 251, 0, 161, 242,
61, 166, 22, 21, 64, 14, 123, 217, 234, 114, 214, 53, 103, 235, 0, 78, 36,
111, 10, 93, 211, 206, 89, 70, 95, 243, 208, 46, 196, 249, 132, 0, 184, 207,
37, 249, 99, 232, 233, 244, 195, 253, 218, 52, 246, 240, 26, 53, 0, 45, 152, 216,
32, 131, 92, 117, 169, 249, 129, 173, 77, 184, 38, 191, 142, 0, 112, 46, 173, 8, 163,
221, 86, 179, 19, 76, 124, 56, 65, 166, 82, 170, 0, 210, 213, 87, 182, 19, 102, 43,
146, 243, 153, 214, 18, 251, 145, 89, 30, 0, 228, 66, 43, 99, 32, 237, 152, 158, 126,
60, 185, 127, 54, 156, 186, 56, 0, 113, 128, 53, 134, 198, 112, 89, 221, 163, 37,
37, 206, 132, 76, 80, 121, 0, 131, 179, 113, 128, 29, 10, 222, 7, 181, 196, 245,
30, 140, 98, 21, 226, 0, 176, 209, 180, 136, 91, 194, 253, 197, 166, 101, 38,
105, 36, 72, 108, 95, 0, 121, 44, 158, 127, 5, 196, 7, 197, 111, 59, 236, 76,
167, 229, 193, 113, 0, 56, 85, 229, 165, 187, 193, 203, 225, 138, 110, 171,
93, 217, 124, 6, 60, 0, 136, 109, 69, 224, 69, 27, 187, 167, 192, 52, 31,
233, 10, 149, 79, 52, 0, 58, 67, 124, 190, 101, 145, 234, 52, 137, 100,
37, 133, 110, 174, 123, 101, 0, 52, 48, 73, 103, 160, 103, 48, 138, 118,
112, 31, 5, 192, 102, 133, 81, 0, 214, 175, 124, 79, 237, 207, 43, 103,
119, 223, 142, 131, 201, 50, 248, 131, 0, 223, 136, 147, 30, 126, 239,
223, 204, 43, 184, 13, 74, 79, 87, 16, 251, 0, 203, 15, 200, 19, 117, 90,
69, 206, 89, 132, 191, 186, 21, 132, 124, 30]

data = [[0] * 16 for i in range(28)]

for i in range(28):
for j in range(16):
data[i][j] = enc[i * 17 + j]
str = "F}"
for j in range(26,-1,-1):
for t in range(32,127):
temp = chr(t) + str
if md5(temp.encode()).digest() == bytes(data[j]): #哈希值校验b'\x5d'16字节二进制格式
str = temp
print(str)

magicfile

文件给的主要是libmagic编译的程序 其中加载了自定应的二进制类型的mgc文件

可以参考2023 *CTF的一个类似题目

从程序提取出mgc文件 第二个参数是地址 第三个参数是size

写脚本解析 输出所有规则后 找0x2F为}的

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import struct
import string
f = open('./dump', 'rb')

#f = open('./test.magic.mgc', 'rb')

b = f.read(0x178)
indexes = []
table = ''
while True:
b = f.read(0x178)
if len(b) != 0x178:
break
line = b[:0x30]
_type = line[6]
text = b[0xa0:].decode()
if("Try again" in text):
text = "Try again"
else:
text = ""
off = struct.unpack_from('<I', line, 0x0c)[0]
s = f'type: {_type:02X}, off: {off:02X}, text = '+text.strip()
if _type == 5:
s += ', str: '+line[0x20:].decode()
print(s)
elif _type == 1:
n1, n2 = line[0x18], line[0x20]
v = n1 ^ n2
s += f', byte: {n1:02X} {n2:02X} {v:02X} {chr(v)}'
table += chr(v)
if("Try again" in text):
print(s)
# elif _type==3:
# if("Try again" not in text):
# print(s)
# s+=f', default '
# print(s)

flag = """type: 01, off: 07, text = , byte: 00 6F 6F o
type: 01, off: 08, text = , byte: 00 55 55 U
type: 01, off: 09, text = , byte: 00 5F 5F _
type: 01, off: 0A, text = , byte: 00 41 41 A
type: 01, off: 0B, text = , byte: 00 52 52 R
type: 01, off: 0C, text = , byte: 00 33 33 3
type: 01, off: 0D, text = , byte: 00 5F 5F _
type: 01, off: 0E, text = , byte: 00 53 53 S
type: 01, off: 0F, text = , byte: 00 4F 4F O
type: 01, off: 10, text = , byte: 00 5F 5F _
type: 01, off: 11, text = , byte: 00 35 35 5
type: 01, off: 12, text = , byte: 00 6D 6D m
type: 01, off: 13, text = , byte: 00 40 40 @
type: 01, off: 14, text = , byte: 00 52 52 R
type: 01, off: 15, text = , byte: 00 37 37 7
type: 01, off: 16, text = , byte: 00 5F 5F _
type: 01, off: 17, text = , byte: 00 54 54 T
type: 01, off: 18, text = , byte: 00 4F 4F O
type: 01, off: 19, text = , byte: 00 5F 5F _
type: 01, off: 1A, text = , byte: 00 63 63 c
type: 01, off: 1B, text = , byte: 00 52 52 R
type: 01, off: 1C, text = , byte: 00 41 41 A
type: 01, off: 1D, text = , byte: 00 43 43 C
type: 01, off: 1E, text = , byte: 00 6B 6B k
type: 01, off: 1F, text = , byte: 00 5F 5F _
type: 01, off: 20, text = , byte: 00 54 54 T
type: 01, off: 21, text = , byte: 00 68 68 h
type: 01, off: 22, text = , byte: 00 31 31 1
type: 01, off: 23, text = , byte: 00 24 24 $
type: 01, off: 24, text = , byte: 00 5F 5F _
type: 01, off: 25, text = , byte: 00 6D 6D m
type: 01, off: 26, text = , byte: 00 40 40 @
type: 01, off: 27, text = , byte: 00 39 39 9
type: 01, off: 28, text = , byte: 00 69 69 i
type: 01, off: 29, text = , byte: 00 43 43 C
type: 01, off: 2A, text = , byte: 00 5F 5F _
type: 01, off: 2B, text = , byte: 00 66 66 f
type: 01, off: 2C, text = , byte: 00 31 31 1
type: 01, off: 2D, text = , byte: 00 6C 6C l
type: 01, off: 2E, text = , byte: 00 65 65 e
type: 01, off: 2F, text = , byte: 00 7D 7D }
"""

for i in flag.split("\n"):
print(i[-1],end='')

#TPCTF{oU_AR3_SO_5m@R7_TO_cRACk_Th1$_m@9iC_f1le}}
#少了一位 猜一下是 Y

本地跑不了,不知道什么编码方式,其实找到congratulation往上翻就是flag

每隔几行出一个

portable

MBR程序

包含pe和elf,写脚本提取

1
2
3
4
5
6
7
8
9
f = open("portable","rb").read()
exe_data= f[0x9C000:]
open("dump","wb").write(exe_data)
print("end!!!!!!!")


exe_data= f[0x10000:]
open("dump.exe","wb").write(exe_data)
print("end!!!!!!!")

分析ELF文件 根据字符串交叉引用发现关键(8.3f5不了,7.7可以??)

写脚本解密即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
data =[     0x34, 0x2A, 0x42, 0x0E, 0x00, 0x1D, 0x5C, 0x33, 0x5E, 0x44, 
0x3E, 0x1A, 0x0B, 0x5C, 0x2C, 0x3A, 0x5F, 0x22, 0x03, 0x28,
0x36, 0x1B, 0x07, 0x31, 0x8D, 0xDE, 0x10, 0xA2, 0xEB, 0xB2,
0xDA, 0xA2, 0xD8, 0x18, 0x0D, 0x17, 0x1C, 0x1F, 0xBD, 0xD9,
0x1D, 0xBF, 0xEB, 0xA2, 0xD8, 0x16, 0x0D, 0xA0, 0xF6, 0x30,
0xBD, 0xD8, 0x17, 0xBE, 0xDA, 0x0F, 0xAB, 0xC1, 0xAE, 0xEA,
0x8D, 0xDE, 0x11, 0x01, 0xA1, 0xC5
]

flag = []
print(len(data))
key = b"Cosmopolitan"
for i in range(66):
flag.append(data[i]^key[i%12])
flag = b"TPCTF{"+bytes(flag)+b"}"


# 要运行的程序路径
program_path = "./portable_ef3c5f3a7155c5a4c6f0b08606dea575.exe" # 替换为你的程序路径

# 要发送给程序的输入数据
input_data = b"Hello, World!\n" # 替换为你的输入数据

print(flag.decode())

stone-game

一个捡石子游戏AI脑袋尖尖的,规则为谁最先捡完所有的石头谁赢,题目给了源码py打包成macos的可执行文件,不过没啥用,玩了一下发现第一清空四堆石子AI每次都会清空其余三堆中的任意一堆,那么我们只需要清除最后两堆即可。安装这个逻辑写pwntools交互脚本即可。

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from pwn import *
import sys
context(log_level="debug")
# 服务器地址和端口
HOST = "1.95.128.179"
PORT = 3040

# 游戏逻辑常量
MAX_STONES = 100
TOTAL_ROUNDS = 100
WIN_THRESHOLD = 90

def get_data(io):
# 获取当前游戏状态
io.recvuntil(b"Current stone count:\n")
stones = []
for i in range(7):
line = io.recvline().decode().strip()
print(line)
stones.append(int(line.split(":")[1].split()[0]))
return stones

def get_str(stones):
move = ""
for i in range(4):
move += str(stones[i]) + " "
move += "0 0 0"
return move

def play_game(io):
# 初始化游戏
io.recvuntil(b"Press Enter to start...")
io.sendline(b"\n") # 开始游戏

player_wins = 0

for round_num in range(1, TOTAL_ROUNDS + 1):
stones = get_data(io)

move = get_str(stones)

io.recvuntil(b"0 1 0 2 0 0 0):\n")
io.sendline(move.encode() + b"\n")


io.recvuntil(b"AI is thinking...")
ai_move_line = io.recvline().decode().strip()
log.info(f"AI move: {ai_move_line}")

stones = get_data(io)
move1 = ""
for i in range(7):
move1 += str(stones[i]) + " "
move1 = move1[:-1]
io.sendline(move1.encode() + b"\n")
# 检查游戏结果
io.recvuntil(b"Current player:")
if b"You won this round!" in io.recvline():
player_wins += 1
log.info("Player won this round!")
else:
log.info("AI won this round!")

log.info(f"Score after round {round_num}: {player_wins}/{round_num}")

if round_num < TOTAL_ROUNDS:
io.recvuntil(b"Next round starting in 2 seconds...")
time.sleep(2)


io.recvuntil(b"=== Challenge Complete ===")
final_score = io.recvline().decode().strip()
log.info(f"Final score: {final_score}")

if player_wins >= WIN_THRESHOLD:
flag = io.recvline().decode().strip()
log.success(f"Flag: {flag}")
else:
log.failure("Failed to win enough rounds to get the flag.")

def main():

io = remote(HOST, PORT)

try:
play_game(io)
except Exception as e:
log.error(f"Error: {e}")
finally:
io.close()

if __name__ == "__main__":
main()

XCTF-TPCTF2025
https://alenirving.github.io/2025/03/25/XCTF-TPCTF2025/
作者
Ma5k
发布于
2025年3月25日
许可协议