linuxpdf

pdf里运行Linux,010打开发现有一个base64解密函数,一些文件名以及文件对应的base64编码,通过正则匹配 ”文件名”: “文件内容“ 获取文件名和文件内容,直接用脚本导出。随便拿个文件看看信息,发现是zlib格式的压缩文件,用python的zlib库解压就行,查看解压出来有许多二进制文件,有一百多个有点不好看,于是寻找看是不是有elf可执行文件,写个函数遍历一下(脚本是AI写的)。

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import os
import re
import base64
import zlib

def extract_and_decode(filename):
# 以二进制方式读取文件内容
with open(filename, 'rb') as f:
content = f.read()

try:
# 尝试 UTF-8 解码,不可解码时忽略错误字符
text_content = content.decode('utf-8', errors='ignore')
except UnicodeDecodeError:
print("文件解码失败,可能包含非 UTF-8 编码数据。")
return

# 正则匹配 "字符串1":"字符串2"
pattern = r'"(.*?)": "(.*?)"'
matches = re.findall(pattern, text_content)

for filename, encoded_data in matches:
try:
# Base64 解码
decoded_data = base64.b64decode(encoded_data)

# 将解码内容以二进制方式写入对应文件
f = open(rf"outdata/{filename.replace('/', '_').replace('\\', '_')}",'wb')
de = zlib.decompress(decoded_data)
f.write(decoded_data)

print(f"已创建文件: {filename}")
except Exception as e:
print(f"处理 {filename} 时出错: {e}")


def dedata():
input_dir = "outdata" # 你的 zlib 文件所在的文件夹
output_dir = "outdata\\dec" # 解压后的文件存放位置

# 确保输出目录存在
os.makedirs(output_dir, exist_ok=True)

# 遍历 zlib 文件
for filename in os.listdir(input_dir):
input_path = os.path.join(input_dir, filename)
output_path = os.path.join(output_dir, filename + ".bin") # 你可以改成合适的扩展名

try:
# 读取文件内容
with open(input_path, "rb") as f:
compressed_data = f.read()

# 尝试解压
decompressed_data = zlib.decompress(compressed_data)

# 写入解压后的文件
with open(output_path, "wb") as f:
f.write(decompressed_data)

print(f"解压成功: {filename} -> {output_path}")

except zlib.error:
print(f"跳过文件(解压失败): {filename}")
except Exception as e:
print(f"其他错误: {filename} - {e}")

print("所有文件处理完成!")

def iself():
input_dir = "outdata\\dec" # 解压后数据的文件夹

for filename in os.listdir(input_dir):
input_path = os.path.join(input_dir, filename)

try:
with open(input_path, "rb") as f:
fdata = f.read()
if b"ELF" in fdata:
print(input_path)
except:
print("error")
if __name__ == "__main__":
input_file = "linux.pdf"
extract_and_decode(input_file)
dedata()
iself()

"""
......
outdata\dec\kernel-riscv64.bin.bin
outdata\dec\root_files_0000000000000002.bin
outdata\dec\root_files_0000000000000003.bin
outdata\dec\root_files_0000000000000039.bin
outdata\dec\root_files_000000000000003c.bin
outdata\dec\root_files_000000000000003d.bin
outdata\dec\root_files_000000000000003e.bin
outdata\dec\root_files_000000000000003f.bin
outdata\dec\root_files_0000000000000040.bin
outdata\dec\root_files_0000000000000041.bin
outdata\dec\root_files_0000000000000042.bin
outdata\dec\root_files_0000000000000043.bin
outdata\dec\root_files_0000000000000044.bin
outdata\dec\root_files_0000000000000045.bin
outdata\dec\root_files_0000000000000046.bin
outdata\dec\root_files_0000000000000047.bin
outdata\dec\root_files_0000000000000048.bin
outdata\dec\root_files_000000000000004a.bin
outdata\dec\root_files_000000000000004b.bin
outdata\dec\root_files_000000000000004c.bin
outdata\dec\root_files_000000000000004d.bin
outdata\dec\root_files_000000000000004e.bin
outdata\dec\root_files_000000000000004f.bin
outdata\dec\root_files_0000000000000050.bin
outdata\dec\root_files_0000000000000051.bin
outdata\dec\root_files_0000000000000052.bin
outdata\dec\root_files_0000000000000089.bin
outdata\dec\root_files_00000000000000a6.bin
outdata\dec\root_files_00000000000000a7.bin
outdata\dec\root_files_00000000000000a9.bin
error
error
error
outdata\dec\root_files_00000000000000aa.bin
"""

最后发现在root_files_00000000000000a9.bin中有flag的逻辑。

MD5的特征值。

逻辑大概就是从前往后比较flag后n位(n从28到1,flag总共28字节)生成的MD5值。

爆破一下就行,注意密文中每组哈希值间有0作为分隔符,所以17字节为一组MD5。

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
import hashlib

hash = [0x38, 0xF8, 0x8A, 0x3B, 0xC5, 0x70, 0x21, 0x0F, 0x8A, 0x8D, 0x95, 0x58, 0x5B, 0x46, 0xB0, 0x65,
0x00, 0x83, 0x05, 0x5A, 0xE8, 0x0C, 0xDC, 0x8B, 0xD5, 0x93, 0x78, 0xB8, 0x62, 0x8D, 0x73, 0x3F,
0xCB, 0x00, 0xFA, 0x7D, 0xAF, 0xFB, 0xD7, 0xAC, 0xEC, 0x13, 0xB0, 0x69, 0x5D, 0x93, 0x5A, 0x04,
0xBC, 0x0F, 0x00, 0xC2, 0x9C, 0xC0, 0xFD, 0x38, 0x01, 0xC7, 0xFD, 0xD3, 0x15, 0xC7, 0x82, 0x99,
0x9B, 0xD4, 0xCB, 0x00, 0x2B, 0xA2, 0xD0, 0x1A, 0xF1, 0x2D, 0x9B, 0xE3, 0x1A, 0x2B, 0x44, 0x32,
0x3C, 0x1A, 0x4F, 0x47, 0x00, 0xDD, 0xEE, 0xBA, 0xF0, 0x02, 0x52, 0x7A, 0x9E, 0xAD, 0x78, 0xBD,
0x16, 0x68, 0x45, 0x73, 0xCC, 0x00, 0xBF, 0x95, 0xB8, 0x99, 0x34, 0xA1, 0xB5, 0x55, 0xE1, 0x09,
0x0F, 0xEC, 0xDF, 0xD3, 0xDA, 0x9F, 0x00, 0xB6, 0x42, 0x2C, 0x30, 0xB0, 0x29, 0x38, 0x53, 0x5F,
0x8E, 0x64, 0x8D, 0x60, 0xA8, 0x7B, 0x94, 0x00, 0x08, 0xC1, 0xB7, 0x66, 0x43, 0xAF, 0x8D, 0xD5,
0x0C, 0xB0, 0x6D, 0x7F, 0xDD, 0x3C, 0xF8, 0xED, 0x00, 0x42, 0xD6, 0x97, 0x19, 0xF9, 0x70, 0x88,
0xF0, 0x65, 0x40, 0xF4, 0x12, 0xDC, 0x17, 0x06, 0xFB, 0x00, 0xA1, 0xF2, 0x3D, 0xA6, 0x16, 0x15,
0x40, 0x0E, 0x7B, 0xD9, 0xEA, 0x72, 0xD6, 0x35, 0x67, 0xEB, 0x00, 0x4E, 0x24, 0x6F, 0x0A, 0x5D,
0xD3, 0xCE, 0x59, 0x46, 0x5F, 0xF3, 0xD0, 0x2E, 0xC4, 0xF9, 0x84, 0x00, 0xB8, 0xCF, 0x25, 0xF9,
0x63, 0xE8, 0xE9, 0xF4, 0xC3, 0xFD, 0xDA, 0x34, 0xF6, 0xF0, 0x1A, 0x35, 0x00, 0x2D, 0x98, 0xD8,
0x20, 0x83, 0x5C, 0x75, 0xA9, 0xF9, 0x81, 0xAD, 0x4D, 0xB8, 0x26, 0xBF, 0x8E, 0x00, 0x70, 0x2E,
0xAD, 0x08, 0xA3, 0xDD, 0x56, 0xB3, 0x13, 0x4C, 0x7C, 0x38, 0x41, 0xA6, 0x52, 0xAA, 0x00, 0xD2,
0xD5, 0x57, 0xB6, 0x13, 0x66, 0x2B, 0x92, 0xF3, 0x99, 0xD6, 0x12, 0xFB, 0x91, 0x59, 0x1E, 0x00,
0xE4, 0x42, 0x2B, 0x63, 0x20, 0xED, 0x98, 0x9E, 0x7E, 0x3C, 0xB9, 0x7F, 0x36, 0x9C, 0xBA, 0x38,
0x00, 0x71, 0x80, 0x35, 0x86, 0xC6, 0x70, 0x59, 0xDD, 0xA3, 0x25, 0x25, 0xCE, 0x84, 0x4C, 0x50,
0x79, 0x00, 0x83, 0xB3, 0x71, 0x80, 0x1D, 0x0A, 0xDE, 0x07, 0xB5, 0xC4, 0xF5, 0x1E, 0x8C, 0x62,
0x15, 0xE2, 0x00, 0xB0, 0xD1, 0xB4, 0x88, 0x5B, 0xC2, 0xFD, 0xC5, 0xA6, 0x65, 0x26, 0x69, 0x24,
0x48, 0x6C, 0x5F, 0x00, 0x79, 0x2C, 0x9E, 0x7F, 0x05, 0xC4, 0x07, 0xC5, 0x6F, 0x3B, 0xEC, 0x4C,
0xA7, 0xE5, 0xC1, 0x71, 0x00, 0x38, 0x55, 0xE5, 0xA5, 0xBB, 0xC1, 0xCB, 0xE1, 0x8A, 0x6E, 0xAB,
0x5D, 0xD9, 0x7C, 0x06, 0x3C, 0x00, 0x88, 0x6D, 0x45, 0xE0, 0x45, 0x1B, 0xBB, 0xA7, 0xC0, 0x34,
0x1F, 0xE9, 0x0A, 0x95, 0x4F, 0x34, 0x00, 0x3A, 0x43, 0x7C, 0xBE, 0x65, 0x91, 0xEA, 0x34, 0x89,
0x64, 0x25, 0x85, 0x6E, 0xAE, 0x7B, 0x65, 0x00, 0x34, 0x30, 0x49, 0x67, 0xA0, 0x67, 0x30, 0x8A,
0x76, 0x70, 0x1F, 0x05, 0xC0, 0x66, 0x85, 0x51, 0x00, 0xD6, 0xAF, 0x7C, 0x4F, 0xED, 0xCF, 0x2B,
0x67, 0x77, 0xDF, 0x8E, 0x83, 0xC9, 0x32, 0xF8, 0x83, 0x00, 0xDF, 0x88, 0x93, 0x1E, 0x7E, 0xEF,
0xDF, 0xCC, 0x2B, 0xB8, 0x0D, 0x4A, 0x4F, 0x57, 0x10, 0xFB, 0x00, 0xCB, 0x0F, 0xC8, 0x13, 0x75,
0x5A, 0x45, 0xCE, 0x59, 0x84, 0xBF, 0xBA, 0x15, 0x84, 0x7C, 0x1E, 0x00]

datalist = [hash[i*17:17*(i+1):] for i in range(len(hash))] #每十七个作为一组哈希值,末尾的0是分隔符

flag ="}"
for i in datalist[::-1]:
tmp=""
for j in i: # 先把哈希值拼成一个字符串
strtmp = hex(j)[2::]
if len(strtmp) <2:
strtmp = "0"*(2-len(strtmp)) + strtmp #补0
tmp+= strtmp

for char in range(32, 127):# 遍历所有可打印字符
has = hashlib.md5((chr(char) + flag).encode()).hexdigest() #拼接字符到flag中生成md5值
print(has,chr(char)+flag)
if tmp.rstrip("0") == has: #去掉结尾的0(分隔符),判断相等,如果相等拼接到flag中生成下一组hash
flag = chr(char) + flag

print(flag)
TPCTF{mag1c_RISC-V_linux-PDF}

chase

用fceux进行调试,amr search在吃豆的时候寻找改变的值,就可以发现0x84的数据是用于存储当前吃豆的数量,0x85好像是当前的关卡,是从0开始的。

用hexeditor修改值可以发现0x83就是目标的豆子数,直接修改关卡值好像会被检测输出hacker,于是继续修改豆子数来逐个通关,通过第五关后可以拿到第一段flag。

得到第一部分flag

在ppu viewer中看到了第三段flag

c4a6dff380cf02c5b1b3e31fffbd1b0d

第三部分

在PPU viewer页面还可以看到字符表。 查询资料可知,NES 游戏中Tile是游戏中所使用一个图案块。游戏的一个画面就是由多个Tile组成的,每个Tile都有一个索引值便于调用显示。

在PPU viewer页面可以看到每个Tile的索引值(左下角)。

例如26就是上面F的值。。。,直接在010中搜索FLAG的Tile值(26 2C 21 27 )就可以找到对应FLAG这四个字符的tile值出现的位置,那么这个位置大概率就是flag在页面上出现的位置,提取下来,创建字符映射表映射回去就行。注意数字有两个表。

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
table = list(range(32,91)) # 获取!~Z的ASCII码
table1 =[123,125,95] # 添加最后的{}_

table.extend(table1)

# for i in table:
# print(chr(i),end='')
# print()

data = [
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, 0x00, 0x01, 0x9A, 0xBD, 0xAD, 0xAD, 0x6D,
0x00, 0x01, 0x03, 0xB9, 0xAD, 0xAD, 0x6E, 0x00, 0x01, 0x03, 0xB9, 0xAD, 0xAD, 0x6E, 0x00, 0x01,
0x03, 0x0D, 0x01, 0x03, 0x00, 0x01, 0x10, 0x00, 0x01, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1C,
0x2C, 0x3C, 0x0F, 0x12, 0x22, 0x32, 0x0F, 0x14, 0x24, 0x34, 0x0F, 0x11, 0x32, 0x30, 0x0F, 0x1C,
0x2C, 0x3C, 0x0F, 0x09, 0x27, 0x38, 0x0F, 0x11, 0x21, 0x31, 0x0F, 0x11, 0x32, 0x30, 0x0F, 0x11,
0x21, 0x31, 0x0F
] #010提取出来的以flag(26,2c,21,27)开头的字节,这里为第二组数据

dict = {i:j for i,j in zip(range(0x0,0x3E),table)} # 创建索引表1
dict2 = {i:j for i,j in zip(range(0xD0,0xDB),range(48,58))} # 额外的数字表

flag =""
for i in data: # 获取映射值
if(i>=0xD0 and i<=0xD9):
flag+= chr(dict2[i])
if(i>0x3E):
continue
flag+=chr(dict[i])
print(flag)

"""
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ{}_
FLAG PT.2 FOR YOU IS !2 !8PLAY1N9_6@M3S_ ! !# !# !#-!# !0 ! /////<L}/2BR/4DT/1RP/<L}/)GX/1AQ/1RP/1AQ/

"""

解密得到flag的第二部分。

PLAY1N9_6@M3S_

最后的flag: TPCTF{D0_Y0U_L1KE_PLAY1N9_6@M3S_ON_Y0UR_N3S?}

magicfile

文件一般存储一个特征值来确定文件类型。linux通过一个libmagic.so来检测文件类型。文件类型被存储在一个magic文件内(magicfile),里面记录了文件类型(要比较的字节)和要比较的字节地址偏移,以及比较的数据类型。检测原理就是读取文件内容然后根据偏移的地址来获取数据与magic存储的内容进行比较。

本题模拟了libmagic.so的原理,我们输入的flag被作为文件内容进行文件检查。即flag与magic内的内容进行比较,正确则输出congratulations。我们的flag为48字节,如果直接存储在一个类型里那么就可以直接字符串找到flag,所以我们的flag一定是被分块了,而且是单字节。所以我们只需要挨个读取magic文件里比较的内容就行了。本题的magicfile已经被保存在数据段了,所以实现了自定义magic的比较。

image-20250318213438858

首先我们需要了解magic文件是怎么存储信息的。我们在github上可以找到libmagic的源码,查看magic结构体

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
struct magic {
/* Word 1 */
uint16_t cont_level; /* level of ">" */
uint8_t flag;
uint8_t factor
/* Word 2 */
uint8_t reln; /* relation (0=eq, '>'=gt, etc) */
uint8_t vallen; /* length of string value, if any */
uint8_t type; /* comparison type (FILE_*) */
uint8_t in_type; /* type of indirection */
/* Word 3 */
uint8_t in_op; /* operator for indirection */
uint8_t mask_op; /* operator for mask */
uint8_t cond; /* conditional type */
uint8_t factor_op;
/* Word 4 */
int32_t offset; /* offset to magic number */
/* Word 5 */
int32_t in_offset; /* offset from indirection */
/* Word 6 */
uint32_t lineno; /* line number in magic file */
/* Word 7,8 */
union {
uint64_t _mask; /* for use with numeric and date types */
struct {
uint32_t _count; /* repeat/line count */
uint32_t _flags; /* modifier flags */
} _s; /* for use with string types */
} _u;
/* Words 9-24 */
union VALUETYPE value; /* either number or string */
/* Words 25-40 */
char desc[MAXDESC]; /* description */
/* Words 41-60 */
char mimetype[MAXMIME]; /* MIME type */
/* Words 61-62 */
char apple[8]; /* APPLE CREATOR/TYPE */
/* Words 63-78 */
char ext[64]; /* Popular extensions */
};

value节点中是需要进行比较的内容,看一下value的定义,联合体的大小是最大类型的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
union VALUETYPE {
uint8_t b;
uint16_t h;
uint32_t l;
uint64_t q;
uint8_t hs[2]; /* 2 bytes of a fixed-endian "short" */
uint8_t hl[4]; /* 4 bytes of a fixed-endian "long" */
uint8_t hq[8]; /* 8 bytes of a fixed-endian "quad" */
char s[MAXstring]; /* the search string or regex pattern */
unsigned char us[MAXstring];
uint64_t guid[2];
float f;
double d;
};

计算一下就可以知道magic结构体大小是376byte。我们可以遍历所有magic结构体,提取所有value位char的内容,flag估计就在里面。。看magic_load的magicfile变量可以看到程序的magic文件存在地址0x21004,后面的magiclen是magic文件大小。

image-20250318231837447

最后的flag要猜一下前面是个Y,最后结果是TPCTF{YoU_AR3_SO_5m@R7_TO_cRACk_Th1$_m@9iC_f1le}

1
2
3
4
5
6
7
8
9
10
11
12
13

data = open("magicfile_c970e3503feebf8274571f09d27cdd2f", "rb").read()
data = data[0x21004:][376:0x116f9f8]

res =''

for i in range(0,0x116f9f8,376):
value = data[i:i+376:][32:96:].decode().replace(chr(0),'')
res += value
if value == '}':break

print(res)
# TPCTF{YoU_AR3_SO_5m@R7_TO_cRACk_Th1$_m@9iC_f1le}