首页
社区
课程
招聘
[原创]协程切换的临界区块控制不当而引发的UAF血案
发表于: 2018-2-17 05:54 8530

[原创]协程切换的临界区块控制不当而引发的UAF血案

2018-2-17 05:54
8530

首先祝大家新年快乐,最近做了一道pwn题,挺有意思的,是利用协程切换时临界区控制不当而导致的UAF,这题做了我很久,两天多。(可能是因为我菜)所以感觉很有收获,写这个不仅是分享,也是把我自己做题的心路历程记录下来,总结经验。

协程,简单的说,就是用户态的,程序自己所控制的线程。我们知道,线程的管理与调度,一般是操作系统所控制的,但是协程,是用户自己所控制的。在操作系统看来,无论你建立了多少个协程,都只把它看作是一个线程。

一个linux帮我们定义好的结构体,记录了一个协程的状态,比如寄存器信息,等等。。。

linux协程的具体内容我不会细讲了。。毕竟这是一道pwn的writeup不是linux协程开发教程。。。上面只是大概地说了一下。。。详细内容可以看看 linux的man http://man7.org 或者https://segmentfault.com/p/1210000009166339/read#2-1-_getcontext_u5B9E_u73B0

这道pwn是开源的,以下是源代码

题目提供了一个crc32计算服务,我们可以请求crc32计算,添加协程,切换到协程,暂停协程,收集计算结果并显示。这个服务使用的是非抢占式的协程切换,需要yield主动切换到其他协程。如果对线程切换原理有过了解,看这个代码应该不难。。如果没有,呃,好好学习大学的操作系统课程再来打CTF。。。!(逃

这个漏洞点可真是难找,我当时对着代码看了好久都没发现任何显而易见问题(溢出或者格式化字符串漏洞或者UAF Double Free什么的)。找了好久,终于发现,本应该是临界区的pop操作,被分开了!

注释是我自己加的,意思是,这个yield可以被利用,如果操作得当,*stack可以被设置为一个dangling pointer。

但是又有一个问题,他每次切换回来的时候,都会检查*stack和old_head是否相等,如果不等,那么会重新加载一次new_head和old_head并且再次yield。这给利用造成了一些困难。不过,我们可以想想堆运行原理:被free后的内存会被insert到fastbin中,再malloc的话会直接从fastbin里面取,这样会导致内存地址是一样的。如果对堆的运行机制不了解,可以看看这篇文章https://jaq.alibaba.com/community/art/show?articleid=315

这样我们就有利用的思路了。

接下来就是想,该怎么利用这个UAF了。

与劫持C++虚表的UAF利用的套路不同,这个不是C++程序,所以利用只有另求方法。

这个时候job_stack的值在fastbin中,但是我们对node没有直接写入的权限,不能像一些套路一样,改写fd的值,使malloc返回自定义的地址。如果我们再分配一个128字节的job,新的job node和旧的job node会指向同一个地址。所以栈这个单向链表会形成一个环,想了一想,发现不好利用。

那么,既然job数据的大小是可控的,那么我们为什么不能让这个分配到一个0x10的fastbin呢?我们来看看malloc的顺序:

可见,是先malloc输入的缓冲区,再malloc存结果的缓冲区,最后push里面再malloc node的缓冲区。

此时,0x10的fastbin中有两个chunk,如果size < 8的话,input会拿第一个,而result就拿第二个。而result,是存放计算crc32结果的地方,同时也是*job_stack此时的值!既然是存放我们输入数据的crc32计算结果的地方,我们相当于可以控制他的值!那么,如果我们构造一个4字节的payload,使得crc32的计算结果是我们某个可控的chunk的地址(比方说,第一个job内容的地址),便可以伪造一个job struct,其中result指向某个地址,input指向crc32是这个地址的payload。这个时候再算这个job,便可以实现任意地址DWORD SHOOT!

如图所示:

具体步骤为:(接上面的)

现在,我们有枪了,但是还没有确定我们要射的目标。DWORD SHOOT,射在哪里,射什么,是一门艺术。当时我想到了几个方案:

本来想通过一些gadget实现获取got表free地址并计算出system地址然后call的,然而这题ALU相关的gadget真是少的可怜。。。好吧,你算不出来,我帮你算。即,先调用puts (printf占用的栈空间实在太大,要0x2000多。。。实在是坑。。。),然后脚本来算出system地址,scanf把他存到ROP的后面的某个位置上。(本来我是通过fread的,其中FILE*直接给的就是__bss_start的地址,然而这样不行。。因为fread所需要的是__bss_start里面的内容,不是他的地址。。。)

下面就是ROP的具体内容

+0x00:
+0x10 ebp
0x8048520 puts addr
0x8048618 leave/ret addr
0x804A5E8 got addr of free
+0x10:
+0x24 ebp
0x8048590 scanf addr
0x8048618 leave/ret addr
"%x" addr
+0x28 ; address to be modified to system address
+0x24:
0 ;ebp
+0x28:
0 ;to be modified to address of system
0
+0x34 "/bin/sh" addr
+0x34:
"/bin/sh"
+0x3c\:
"%x\x00\x00"
+0x40:

很明显,执行到leave的时候,会把esp设为+0x00处地址,然后pop ebp,可以接着继续控制ROP。这是一个ROP技巧,只要能通过这种方式控制ebp,就可以一直通过将返回地址设为leave/ret,实现几个函数的连续调用(注意,根据调用约定,任何libc的函数都不会改变ebp的值)。

所以最后一步:(接着上面的)

还有一点,堆分配出来的地址,在一定情况下,是确定的。即,只要确定堆基址,然后堆分配操作顺序一定,出来的相对堆基址的偏移必然一样。

不过我在调试的时候,出现了问题:就是当我直接命令行运行程序或者用gdb调试,与用pwntools运行程序,堆分配的偏移会不一样。但是在服务器上,是一样的。不知道是什么原因,如果有大神知道,可以一起讨论。

好了,接着,我们用gdb调试,获取到主协程ucontext_t,第一个job和第二个job输入的地址,然后记录下worker1和worker2的地址,然后readelf找到system和free的偏移,就可以上手写exp了。

顺便说一下,这个题目是给一个shell,但是权限不够cat flag,所以要通过get这个程序的shell拿到flag。因为这个原因,我们可以用这个shell,下载并开启gdb peda,获取到以上的信息,写入exp

exp如下:

顺便提一下,在服务器的shell中直接python运行这个脚本的话,from pwn import *这里会卡死,不知道为什么,有大神知道的话,可以讨论一下。所以我说先python打开交互式界面,然后手动import我的exp,就可以getshell了。。。

其中,crack_crc32是用来爆破crc32的程序,因为是爆破,所以exp的运行时间可能会有些长。代码如下:

哈哈,我其实是在这个训练平台上第一个做出来这道题的,可以。最后,再次祝大家苟年大吉,万事如意,新的一年挖到更多0day!
图片描述

 
 
 
 
 
 
 
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2018-2-17 08:48 被holing编辑 ,原因: 修改错误,补充内容
上传的附件:
收藏
免费 1
支持
分享
最新回复 (10)
雪    币: 285
活跃值: (1095)
能力值: ( LV13,RANK:405 )
在线值:
发帖
回帖
粉丝
2
学习了,感谢分享
2018-2-17 10:12
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
2018-2-17 21:32
0
雪    币: 1319
活跃值: (1330)
能力值: ( LV8,RANK:140 )
在线值:
发帖
回帖
粉丝
4
因为正常的思维是使用gdb调试目标程序,然后查看内存来确定shellcode的位置。但当你真的执行exp的时候你会发现shellcode压根就不在这个地址上!这是为什么呢?原因是gdb的调试环境会影响buf在内存中的位置,虽然我们关闭了ASLR,但这只能保证buf的地址在gdb的调试环境中不变,但当我们直接执行./level1的时候,buf的位置会固定在别的地址上。怎么解决这个问题呢?

最简单的方法就是开启core  dump这个功能。

来源:一步一步学  ROP  之  Linux_x86  篇
2018-2-20 10:27
0
雪    币: 699
活跃值: (444)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
5
发现CTF学习群里的师傅0.0
最后于 2018-2-21 23:05 被sakura零编辑 ,原因:
2018-2-21 22:39
0
雪    币: 5676
活跃值: (1303)
能力值: ( LV17,RANK:1185 )
在线值:
发帖
回帖
粉丝
6
inquisiter 因为正常的思维是使用gdb调试目标程序,然后查看内存来确定shellcode的位置。但当你真的执行exp的时候你会发现shellcode压根就不在这个地址上!这是为什么呢?原因是gdb的调试环境会影响 ...
不不不,这是普通运行和pwntool运行不一样,gdb和./xxx运行是一样的。。。(因为pwndbg会还原正常运行的环境好像
最后于 2018-2-23 04:37 被holing编辑 ,原因:
2018-2-23 04:35
0
雪    币: 1319
活跃值: (1330)
能力值: ( LV8,RANK:140 )
在线值:
发帖
回帖
粉丝
7

最后于 2018-2-24 09:39 被inquisiter编辑 ,原因:
2018-2-24 09:38
0
雪    币: 2
活跃值: (57)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
大佬这是啥训练平台
2018-2-25 19:25
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
大神,请问原题目在哪里啊???
2018-2-26 11:06
0
雪    币: 5676
活跃值: (1303)
能力值: ( LV17,RANK:1185 )
在线值:
发帖
回帖
粉丝
10
nullddd 大佬这是啥训练平台
hackcenter
2018-2-26 22:50
0
雪    币: 5676
活跃值: (1303)
能力值: ( LV17,RANK:1185 )
在线值:
发帖
回帖
粉丝
11
kanxueluguo 大神,请问原题目在哪里啊???
我放附件了啊...
2018-2-26 22:51
0
游客
登录 | 注册 方可回帖
返回
//