检查
1 | $ file re-alloc |
Partial RELRO 意味着got表可改
1 | $ strings libc-realloc.so | grep glibc |
前置知识
realloc函数
函数原型:
1 | void *realloc(void *ptr, size_t size); |
在参数 ptr 、size 的各种取值情况下,等效于:
ptr == 0
: malloc(size)ptr != 0 && size == 0
: free(ptr)ptr != 0 && size != old_size
: 释放之前的块再重新分配一个(保存数据)
libc 2.29 对 tcache 新增的检查
1 | // glibc-2.29 |
主要检查为,当一个chunk 被 free 到 tcache 中时,其 fd 的下一个字长不再空余,而是被置为 key ,存放保存 tcache 结构体的 chunk 的地址,这里会对tcache
链表上的所有chunk进行对比,检测是否有重复。
绕过此检查的方法例如借助 UAF 等漏洞,将 tcache chunk 的 key 覆盖成不为存放 tcache 的内存地址即可。
配置题目运行环境
glibc-all-in-one
https://github.com/matrix1001/glibc-all-in-one
1 | $ git clone https://github.com/matrix1001/glibc-all-in-one.git |
查看list文件
1 | $ cat list |
下载glibc
例如需要下载2.29-0ubuntu2_amd64
1 | $ ./download_old 2.29-0ubuntu2_amd64 |
patchelf
1 | $ sudo apt-get install patchelf |
例如我们需要让 re-alloc 程序指向glibc-all-in-one中下载的libc-2.29
1 | $ ldd re-alloc |
分析
运行程序,是一个经典的菜单题
1 | $ ./re-alloc |
alloc
1 | int allocate() |
先读取index为long型,且值只能为0或1,size必须<=0x78,此处realloc就相当于malloc,然后读取data并在最后置零
realloc
1 | int reallocate() |
编号,大小,数据,也很正常
free
1 | int rfree() |
同样使用realloc()函数进行free操作,并对指针清零
漏洞点
程序的 rfree
函数做了清空指针操作,看似杜绝了 UAF ,实际上在 reallocate
函数中调用 realloc
时,将其参数 size 置为 0,就等效于调用了 free(ptr)
,且此时是没有清空 heap[2] 数组中的指针的。由此造成 UAF。
利用
由于程序的 got 表可写,故思路为:通过修改某参数为可控内容的内存指针的函数的 got 表项为 system 函数地址,传入 "/bin/sh\x00"
即可 get shell。
- 利用上述 UAF 漏洞,在两处 tcache 项上放置 atoll@got ,准备对其进行篡改
1 | # UAF 放置 atoll@got 至 tcache@0x20 |
- 第二次放置也一样
1 | # UAF 放置 atoll@got 至 tcache@0x40 |
利用其中一个指向
atoll_got
的 chunk 更改atoll_got
为printf_plt
,这样在调用atoll
时,就会调用printf
从而构造出一个格式化字符串漏洞,利用这个漏洞可以 leak 出栈上的libc地址,这里选择 leak__libc_start_main
补充:这里利用格式化字符串的常量为调试所得,例如%21$llx,例如0xeb(即235)因为获得的地址是<__libc_start_main+235>利用另一个指向
atoll_got
的 chunk 将atoll_got
再改成system
,注意因为此时atoll
是printf
,所以在调用 alloc 时,需要输入的 Index 和 Size 不是直接输入数字,而是通过输入的 string 的长度来通过 printf 返回的值间接传给Index和Size。最后再输入
/bin/sh\x00
调用atoll
来执行system("/bin/sh");
即可 get shell。
exp
1 | #!/usr/bin/env python3 |
参考: