题目数量挺多,质量也不错,而且首次在比赛接触Kernel题目,学到新知识。
Beauty_of_Changechunk
程序分析
首先题目每个libc
,我通过 strings
查二进制是 16.04
,我以为这libc
应该是 2.23
了吧,结果做完之后一打远程根本不行。后面不断试,才确定至少是2.29
以上,不能直接double free
。
1 | unsigned __int64 __fastcall sub_114B(__int64 a1, __int64 a2) |
Delete
函数,释放堆块时,只将 chunk_size
的最低一字节设为了0。而 Edit
函数是通过 chunk_size
来判断是否可以修改该chunk
。那么很明显了,如果申请 0x100
的堆块,释放后其 size
仍然为 0x100
,存在 UAF
漏洞。
1 | unsigned __int64 sub_121D() |
利用分析
程序里,有一个可以输出 flag
的函数。但是需要比较输入的chunk
的内容和 mem_ptr
的初始位置放置的 随机数相等,才能输出。
那么思路很明显了,需要利用任意地址写数据修改 mem+ptr
为一个我们知道的数据。这如果在 2.27
以下是可以直接通过 unsortedbin attack
来实现,但是这题高于 2.29
,所以 unsortedbin
攻击就失效了。
在 高于 2.29
版本时,有两种方法可以替代 unsortedbin attack
,一是 tcache stash unlink
,另一种是 largebin attack
。
这里就直接用 tcache stash unlink
即可。
布置 0x100
tcache
为 6个,布置 两个 small bin
,试 第二个的 chunk
的 bk
指向 mem_ptr-0x10
。
然后使用 calloc
分配第一个 smallbin
,此时 mem_ptr
被写上一个 libc
地址,该地址我们可以知道。
EXP
1 | from pwn import * |
影流之主
程序分析
这题通过strings
查也是 16.04
的,我吸取上一题教训,我按照2.31的做,结果是16.04
,我心态已崩。以后先查清libc
,再做题。
1 | unsigned __int64 Delete() |
漏洞很明显,一个 UAF
漏洞.
利用分析
这题由于只能申请fastbin chunk
,所以难点就是如何泄露 libc
地址。常规思路就是利用堆合并。
之前学到一个tips
,利用输入大量字符串,使得 scanf
申请大量堆块来堆合并。但这题不行。
于是,只有研究 glob
函数。看源码可以发现里面有很大 malloc
。但是由于时间原因,没有具体分析。
开始随便输入路径,进行测试。测了很多次,发现 /*
,可以刚好将 fastbin chunk
合并放入 unsortedbin
中。
EXP
1 | from pwn import * |
garden
程序分析
1 | void DeleteVul() |
有一个 UAF
后门。
利用分析
2.30 glibc
,虽然有 uaf
,但是直接double_free
行不通。这道题卡了很久,就是不知道 malloc(0x20)
的用法。
首先申请 9个块,然后逆序依次释放,为了防止进入 Unsortedbin
的块合并入 top chunk
。同时再释放chunk1
时使用 UAF
的 释放函数。
然后此时,可以泄露Libc
地址。同时也发生了 堆块合并。
利用 malloc(0x20)
从 unsortedbin
中分配出一小块,使堆头改变。
然后再依次申请堆块。 此时chunk1
的堆头就会是 chunk8+0xb0
处。
然后释放2到7之间任意几个块,腾出空闲id。 再依次释放 chunk8
和 chunk1
。再申请 chunk8
,利用chunk8
堆覆盖 chunk1
的 next
指向 free_hook
。
最终 getshell
。
EXP
1 | from pwn import * |
babydev
正好最近在学kernel
,看到这道题 就很想把他做出来,做了很久才出,但是总算是学习了有一点成果吧。
程序分析
整个内核应该是实现了一个类似文件读写偏移操作的过程,有 write\read\llseek
函数:
1 | __int64 __fastcall mychrdev_write(__int64 a1, __int64 a2, __int64 size1, _QWORD *off) |
write
函数通过 off
来当一个文件写指针,注意: 这个 off
经过调试是一个 全局变量。首先判断 off
不能超过 0xffff
,且不能超过当前文件的 size
即 mydata+0x10008
的值。随后 size+off
不能超过 0x10000
。符合条件,则将用户输入写入 *(mydata+0x10000)+offset+mydata
处,写入 数据为 size
。最后更新文件大小,*(my_data+0x10008) += size
。
1 | __int64 __fastcall mychrdev_llseek(__int64 a1, __int64 a2, int a3) |
llseek
函数主要功能就是实现文件读写指针的偏移。和 C语言的 lseek
是类似的,我们主要关注 set
模式,它能够直接将 off
设置为用户设置的值,只要 满足小于文件size
和 大于0。
那么此处,就存在一个组合漏洞。
当我们第一次执行write(fd, buf, 0xf000)
时,此时 off
的值为 0xf000
,size
为 0xf000
。
然后我们利用 llseek(fd, 0, 0)
,将 off
的值设为 0。
然后再次执行write(fd, buf, 0xf000)
,此时 由于 off
为0,我们是能够成功绕过 write
的检测,实现 size
为 0x1e000
,那么此时,我们就已经能够成功实现溢出。相当于拥有了任意地址读写的能力。
利用分析
Kernel
里任意地址读写漏洞提权的方法,还挺多的,这里主要参考 P4nda 和 xmzyshypnc两位学长的博客。以后关于这类洞提权的,先立个flag
,以后要写一篇更详细的。
首先讲一种xmzyshypnc说得又快又稳的方法,虽然我最后都没成功,但是这种原理是行得通的。具体可以参考我之前的 Kernel进阶
一篇。
主要原理就是修改 modprobe_path
,该地址存储着内核发生异常时会调用的文件。而由于这道题,没有开启Kaslr
,使得我们想要找到 modprobe_path
十分简单:
1 | / |
可以发现上图中,modprobe_path
的地址是 0xffffffff824445e0
,而且这个地址是固定的。我们只需要利用我们上面的任意地址写漏洞就能修改 modprobe_path
的值为我们自己的脚本。
但是,这道题,当我改完这个值后。qemu
直接卡住了,我内核调试,可以看到已经被我修改了。但是不知道为啥卡住了。
EXP(stuck)
1 |
|
修改cred结构提升权限
这种思路,很早之前 P4nda学长就已经详细分析过,这里我就算做个学习笔记吧。
每个线程在内核中都对应一个线程栈、一个线程结构块 thread_info
去调度,结构体里包含了线程的一系列信息:
该 thread_info
结构体存放在线程栈的最低地址,对应的结构体定义:
1 | struct thread_info { |
thread_info
中最重要的结构体是 task_struct
结构体,如下:
1 | struct task_struct { |
可以看到其中有一个 cred
结构体,其表示的就是当前线程的权限,只要将这个结构的uid-fsgid
全部覆写为0,就可以把这个线程权限提升为 root(root uid 为 0)
1 | struct cred { |
而其中,cred
结构体表示的是这个线程的权限,只要将这个结构的uid-fsgid
全部覆写为0就可以把这个线程权限提升为 root(root uid为0)
1 | struct cred { |
那么一旦我们有了任意地址读写漏洞,那么就有一个很直接的利用方法,就是找到 cred
结构体的位置,将用于表示权限的数据位写为0,即完成提权。
在 task_struct
里有一个 comm[TASK_COMM_LEN]
结构,这个结构可以通过 prctl
函数中的 PR_SET_NAME
功能,设置为 小于 16字节的字符串。
那么,可以通过先使用该函数,将 comm
设为一个字符串,然后我们遍历查找该字符串,即可找到 task_struct
结构体,进入找到 cred
结构体。
由于 task_struct
是由 kmem_cache_alloc_node
分配,因此 task_struct
应该存在内核的动态分配区域。这里放一个内存映射图,以后可能会用。爆破范围应该在:
0xffff880000000000~0xffffc80000000000
1 | 0xffffffffffffffff ---+-----------+-----------------------------------------------+-------------+ |
EXP(Success)
参考 P4nda
的EXP,我改写了一个本题的EXP。这个 EXP
写了巨久,主要是 C 语言太差了,改了好多次,tcl了。
这里有一个注意点,就是我们之前的EXP
只用向后读写即可。但是当我找 cred
结构体时,向后找了很久都找不到。所以猜测可能是在 my_data
之前。所以,我又想了很久怎么向前任意读写。后面的方法是 覆盖 0x10001
处开始的地址,则是计算 size=0x10000-0x10001=-1
,由于 size
是 unsigned int16
,所以 size
将会变成 0xffff
。而我们写得位置 0x10001
刚好是 begin_ptr+1
的位置,这样相等于我们就能够修改 begin_ptr
除最低一字节的其他位。而最低一字节让他等于 ‘\x00’,对我们的影响就很小了。这样我们就可以向 begin_ptr
中写入 负数,然后程序读写的时候 是通过my_data + begin_ptr+offset
来计算需要 读写指针,如果 让 begin_ptr
为负数,让 offset
为0,那么就能往 my_data
之前读写了。
1 |
|
babypwn
这题真是太简单了,我比赛的时候竟然没做。这真就被c++吓住了呗,(:以后不能这样了。
程序分析
1 | case 1u: |
程序使用 C++ 实现了一个简单的菜单题。漏洞存在于 重复执行 init
函数时,会将之前的 init_chunk
删掉,申请新的 init_chunk
。但是,此时的 create_chunk
中存的 init_chunk_ptr
仍然是指向旧的 init_chunk
。而后续执行 set
函数时,是使用 create_chunk
中存的 init_chunk_ptr
指针找到init_chunk
,然后从其中读出 需要操作的chunk_ptr
和size
, 也就是此处存在一个 UAF
漏洞。
利用分析
我们先利用 重复执行 Init
,实现释放 0x90
到unsortedbin
中,然后泄露地址。
然后再 Add
一个 0x80
的块,将 old_int_chunk
申请回来。那么此时 old_init_chunk
的 +8
位置处存储了 heap
地址。可以再次泄露 Heap
地址。
随后利用 create_chunk
指向了 old_init_chunk
,修改 old_init_chunk
中的 chunk_ptr
和 size
实现任意写。
EXP
1 | from pwn import * |
把嘴闭上
程序分析
1 | unsigned __int64 Magic() |
有一个关注点,就是使用了 mallopt
函数。 这道题的漏洞应该是出现在这里。
利用分析
mallopt
在 glibc2.23
存在一个如下漏洞。当我们先释放一个chunk
到 unsortedbin
中后。再使用mallopt
函数修改 global_max_fast
的值为0。然后再使用 mallopt
修改 global_max_fast
,来触发堆合并。当完成堆合并后,mallopt
会重新再 Libc
中开辟一段内存空间作为新的堆结构。
1 |
|
此时,可以看到新生成的堆空间包含了 free_hook
的地址。所以,我们可以再次不断分配,来覆写 free_hook
的值为 system_addr
。
而且这题,应该就是根据这个exp
改编而来,所以其执行顺序和大小都有规定。
EXP
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/11/23/2020祥云杯/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!