续之前的 HelloUser
,向第二层出发。
Kernel分析
由于第一层对 User
已经成功,现在就是对第二层的 Kernel
进行分析。说实话这个如果完全靠自己逆的话,我觉得代码量有点大,但是我觉得出题人一般为了简化逆向难度,是会把漏洞放在几个特定的套路地点里的,还需要以后多做题摸索摸索吧。
backdoor分析
可以看到在 syscall_handler
里处理系统调用号时,会检查系统调用号是否超过了 0x2333
,这个大小是比较奇怪的,然后我们可以去系统调用表里找到最后一个 0x2333
系统调用函数,我把它命名为 backdoor
:
进入 backdoor
函数后,我们就能够发现一些有点熟悉的场景了:
这就是一个典型的菜单题环境,出题人还是很友好的。实现了4个功能,调用 kmalloc
来创建chunk,将chunk_ptr
和size
放到内存里的一个地址,我们定义为 chunk_list
。通过 kfree
实现了释放chunk,这里就存在漏洞,释放后未对 chunk_list
里的 chunk_ptr
和size
清空,存在 UAF
或者 double free
漏洞。先通过 get_addr
函数获取 chunk
的物理地址,再通过 hp_write
函数实现了 对chunk
数据的读写,这里也存在漏洞,未对size
进行检测,可以实现越界读取。通过 qmemcpy
对实现对chunk
的写入,需要从一个 srcbuf
传入数据。
Kmalloc 和 Kfree分析
可以参照上一节所用源码,继续分析,差别不是太大。首先分析 Kmalloc
,我们申请的 len
必须大于等于0x80
,且为 0x80
的倍数
我们申请的 size
必须与 0x10
对齐
这两点是 申请 chunk时需要注意的限制。
Kfree
的逻辑更好分析一点,首先检查 size
是否与 0x10
对齐,然后是清空 user_data
,如果 chunk_addr
与 MEMORY
相邻直接合并,类似于 malloc
合并到 top chunk
。
如果与 MEMORY
不相邻,则执行一个插入到空闲chunk
链表操作,这里插入是按照大小排序,从小到大,然后使用 next
指针连接。可以看到 free
是没有做类似于 _int_free
里的各种检查。
那么,我们可以初步猜测 free_chunk
的结构如下:
1 | chunk_header: | size | nop| |
Kernel内存分析
还需要对 Kernel
内存有一个详细的了解,才能更好的帮助我们利用堆漏洞。
上面 执行读取功能时,会先利用 get_addr
来获取 chunk
的物理地址:
在 get_addr
函数里,是通过 与 0x8000000000
异或获得物理地址。
这一点与我们上一节分析 Kernelmain
函数中 init_alloctor
函数一致。
在 initalloctor
函数中,设置了 MEMORY
也就是 arena.top
类似于 top chunk
的地址为 0x8000000000
,以及size
,并对其进行了 memset(0)
的操作。
如果进一步分析 initpagetable
函数是能够发现 Kernel
所作的内存映射,将 0x8000000000~0x8002000000 映射到0x0~0x2000000 物理空间。
我们现在就已经知道 虚拟地址的基址为 0x8000000000
,可以通过加上相对地址获取到变量的虚拟地址,从而获取到 chunklist
和 sizelist
在 hellokernel
中的虚拟地址。
利用分析
经过对 Kernel
的分析,我们已经能够大致清楚Kernel
漏洞,但是还需要思考一点,如何利用这个漏洞?这道题的思路我觉得tql
了,xxrw 真是带我走进了 Kernel
和虚拟化的大门呀,膜一下。
首先按照我们之前分析,可以发现 virtual
应该是将 内核代码和用户代码 映射到了如下0x2000000
的内存上。
- 构建
double free
分配到chunk_list
首先,我们需要构建一个 double free
,然后分配chunk
到 chunk_list
。
我们先申请 0x30
的堆块,将系统里原有的 chunk
碎片整理一下。
然后申请一个 0x100
的chunk
,此时 会在 chunk_list
的size
留下 0x100
,为我们后续伪造堆块做好了 fake_chunk_header
的准备
随后申请2个 0xf0
的 chunk2, chunk3
。并释放2次 chunk2
,此时 double_free
漏洞已经形成:
随后,我们修改 chunk2
的 next
指针,指向 0x159d8
,也就是 size
为 0x100
的地址处。但是此前,我们需要先将我们需要的数据布在程序内存中,这里我们就布置在第一节中的 stack
处。如下图所示,next
指针已经修改。
- 劫持
chunk_list
,修改sys_open
接下来,我们就可以劫持chunk_list
,并实现任意地址修改了,我们选择修改修改 之前的 sys_open
函数。我们只需要将 sys_open
函数中0xb73
这个 jnz
跳转两字节使用 x90
patch掉,我们后续就可以读取任意文件了。
所以,我们先修改 chunk_list
上的chunk_ptr
使其指向 0xb73
,然后再调用 editKernel
修改2字节,即可。如下图已经成功patch
掉 jnz
命令。
- 打开
flag
,并输出
随后我们就可以执行 ORW
来打开flag
并输出。
EXP
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/10/27/TSCTF-helloKernel/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!