最近有点做题感觉进入疲惫期了,每天做不了几道题,而且太难的题,做一会就不想做了。调整一下状态,还是要学习更多呀,实在是差太多了。
WodenBox2
程序分析
在 Edit
函数里,可以随便溢出,这就使得这题很简单了。然后唯一有一点是 删除时,会将 chunk_list
从从开始 往前挪 0x10
,虽然也存在漏洞,但是没想过怎么利用,倒是每次删除会往前覆盖,如果随便删会导致我们有的地址被清空了,所以我每次删除都只删除 第一个。
利用分析
先 利用 unsortedbin attack
在堆块 fd
指针残留 main_arena+88
的地址,然后使用 fastbin attack
修改 指针到 stdout
泄露地址。最后就是同样原理修改 malloc_hook
为 gadget
地址 来 getshell
。
EXP
1 | from pwn import * |
Shortest_path
是最短路径算法,由于将 flag
堆到了内存里,可以通过不断分配堆块接近 flag
的地址,直到把 flag
读入然后输出。我是算法渣渣,最短路径算法看半天看不懂。
EXP
1 | from pwn import * |
lgd
程序分析
这道题含有许多无用代码,不知道是在哪里看到的类似的题也是这样添加很多无用代码,不过都不重要,反正直接忽略就行。
程序漏洞存在 snprintf
函数的返回值,并不是实际写入的字符串长度,而是源字符串的长度。也就是此时的 size
是与我们输入的 buf
字符串的长度有关。那么 size
可以比我们申请的 chunk_size
大,造成堆溢出。
此外,还开了沙箱,不能够直接 执行 execve
。
利用分析
- chunk overlap 实现
实现方式很常见,覆盖 prev_inuse
,伪造 prev_size
实现向上覆盖。
- 利用
unsortedbin attack
泄露地址 - 利用
malloc_hook
来getshell
。这里gadget
可能因为环境的原因并不一定能成功。但是可以利用Unsortedbin attack
向free_hook
前面区域 写入\x7f
,此时就可以分配堆块到free_hook
,改为setcontext+53
地址,再利用orw
来getshell
。
EXP
1 | from pwn import * |
Easy heap
程序分析
程序总体功能是一个常见的菜单题,而且没有常见的各种漏洞。
在 add
函数中,如果我们输入的 size
过大,那么只会申请一个结构体chunk
,但是不会再往下覆盖 chunk
中的数据。如果此时 chunk
中残留了 fd
指针的数据,那么就可以通过该 fd
数据去修改指向的 堆块地址。而如果我们接下来再申请一个堆块,那么第二个 chunk
的头地址就是 第一个chunk
的 fd
指针,也就是说第二个 chunk
的数据可以被我们修改。
漏洞有点难发现,不是常规的各种漏洞,有一点考逻辑漏洞了。
利用分析
先构造 堆块溢出;再常规的改 free_got
泄露地址,改 atoi_got
来 getshell
。
EXP
1 | from pwn import * |
Easy VM
程序分析
程序的重点函数是 start
函数,根据函数里的功能,我们可以大致定清楚程序的结构体的成员变量。这时候我们再来看 start
函数里的功能就比较清楚了。
当 0x80
时,可以通过 EIP+2
的值,改写 ptr
任意成员的值。
当为 0x9
时,可以将 ptr[1]
也就是 EAX
的值改为 dword_305c
的值,而 dword_305c
可以通过选项4 改一个 plt
地址。同时可以通过 输入 0x11
输出 EAX
的值。
此外,在 0x53
时还可以 打印一个字符
0x54
可以读取一个字符:
利用分析
- 泄露
plt
地址
首先选择选项4,将一个 plt
地址赋给 一个变量。然后选择 2,再通过 0x9
和 0x11
选项将 plt
地址输出出来。
- 泄露
libc
地址
知道 plt
表的地址后,我们就可以知道 got
表地址。我们将 got
表地址通过 0x80
赋值到 ptr[3]
里,然后通过 0x53
将 got
里的 libc
地址 依次打印出来。
- 修改
free_hook
为one_gadget
知道 Libc
地址后,我们就可以知道 free_hook
地址。先通过 0x80
将 free_hook
地址放到 ptr[3]
里,然后通过 0x54
依次读取 system
的地址覆盖 free_hook
地址。
EXP
1 | from pwn import * |
musl
程序分析
使用 musl libc
,可以学习一下。
利用分析
EXP
two chunk
程序分析
程序做了很多限定,很多操作都只能做一次。在 Add
函数中,可以看到一个 标志 calloc
,这和之前遇到的 tcache stash unlink
攻击一致。
在 edit
函数里存在一次堆溢出。
有一次机会 能够 malloc
申请 0x88
的堆块。
还可以执行 name
存储的函数指针。那么我们的利用目的很明显,应该是像 name
里写上 system(‘/bin/sh)
函数去执行。
利用分析
- 布置buf地址,填充 0x91
tcache
由于程序中只有两次 malloc
,我们选择 malloc(0x88)
来实现 tcache stashing unlink attack
,所以需要先申请并释放5个 0x91
的chunk
进入 tcache
。然后 malloc(0xe9)
,我们则可以利用他从tcache
链中申请来泄露heap
地址。并且我们想要通过 tcache stashing unlink attack
来泄露 libc
地址,我们将fake_chunk 伪造到 name
地址,并且将 name
的 fd
值设置为 (name+0x30-0x10)
。
- 准备
0x91 chunk1
然后我们开始准备第一个 0x91 chunk1
进入 smallbin
,先申请一个 0x128
大小的chunk1
,填充满 0x130
大小的 tcache
,再释放 chunk1
进入 unsortedbin
中。再申请一个 0x98
的chunk
来划分chunk1
,此时 chunk1
只剩下 0x91
大小并处于 unsortedbin
中。
- 准备堆溢出和泄露
heap
地址的0xe9 chunk
我们再申请 2个 0xe9
的chunk3
,此时 chunk1
已经进入 smallbin
中,并释放使其进入 0x100
的 tcache
中。
- 准备
0x91 chunk2
然后准备第二个 0x91 chunk2
使其进入 smallbin
中,先申请一个 0x138
大小的 chunk2
,再填满 0x140
大小的 tcache
,再释放 chunk2
进入 unsortedbin
中,申请一个 0xa8
的 chunk
划分 chunk2
,此时 chunk2
只剩下 0x91
大小 并处于 unsortedbin
中。
malloc(0xe9)
泄露heap
地址
然后调用 malloc(0xe9)
,会从 0x100 的 tcache 中取出块chunk3,next 域保留了 heap地址,我们再将其输出 泄露出 heap地址。
- 触犯
tcache stashing unlink attack
随后我们申请一个 0x300
的chunk
,将chunk2
也放入smallbin
中,此时 smallbin中 chunk2->chunk1
。然后我们对chunk3
使用堆溢出,由于chunk3
是在chunk2
之前,可以覆盖chunk2
的fd
和 bk
值,我们将 bk
改为 name
地址,此时smllbin
为 name->chunk2->chunk1
。随后,我们调用calloc(0x88)
来触发tcache stashing unlink attack
,那么 此时smallbin
剩下的 name
和chunk2
就会进入 0x91
的tcache
中,并且(name->fd)->bk = smallbin
,那么此时在name+0x30
处就有了 libc
地址,我们打印将其泄露。
malloc(0x88)
取出name
调用 malloc(0x88)
取出 name
块,部署数据,再执行 name
即可。
EXP
1 | from pwn import * |
内核攻击
以下都是从这篇文章总结而来:
基础知识
分级保护域
内核代码和驱动程序放在ring0级,ring3用于用户程序运行。
提权
在内核中想要获得 root 权限不能只是用 system("/bin/sh")
,而是用下面的语句:
1 | commit_creds(prepare_kernel_cred (0)); |
这个函数分配并应用了一个新的凭证结构(uid = 0, gid = 0),从而获得 root 权限。
内核保护措施
SEMP:管理模式执行保护,保护内核使其不允许执行用户空间代码,也即防止 ret2user
攻击。用以下命令检查 smep 是否开启:
1 | cat /proc/cpuinfo | grep smep |
smep 位于 CR4 寄存器的第20位,设置为 1。CR4 寄存器的值:
关闭 SMEP 方法:修改 /etc/default/grub
文件中的 GRUB_CMDLINE_LINUX=""
,加上 nosmep/nosmap/nokaslr
,然后 update-grub
就好。
1 | GRUB_CMDLINE_LINUX="nosmep/nosmap/nokaslr" |
KASLR: 内核地址随机化
内核地址显示限制: 即 kptr_ restrict
指示是否限制通过 /proc 和其他接口暴露内核地址:
- 0:默认情况下,没有任何限制
- 1:使用 %pK 格式说明符打印的内核指针将被替换为0,除非用户具有 CAP_SYSLOG 特权
- 2:使用 %pK 打印的内核指针将被替换为0 而不管特权
也即,不能直接通过 cat /proc/kallsyms
来获得 commit_creds
的地址。使用如下命令禁用该限制:sudo sysctl -w kernel.kptr_restrict = 0
。最后即可用如下命令查看:
ret2user 攻击
ret2usr(return-to-usr) 利用了用户空间进程不能访问内核空间,但是内核空间能访问用户空间这个特性来重定向内核代码或数据流指向用户空间,并在非 root 权限下进行提权。
1 | |----------------------| |----------------------| |
- 找一个函数指针来覆盖
- 通常使用
ptmx_fops->release()
这个指针来指向要重写的内核空间。在内核空间中,ptmx_fops
作为静态变量存在,包含了一个指向/dev/ptmx
的file_operations
结构的指针。file_operations
结构包含一个函数指针,当时文件描述符诸如 读/写操作时,该函数指针被执行。 - 在用户空间中使用 mmap 提权 payload,分配新的凭证结构
1 | int __attribute__((regparm(3))) (*commit_creds)(unsigned long cred); |
struct cred
—— cred
的基本单位prepare_kernel_cred
—— 分配并返回一个新的credcommit_creds
—— 应用新的cred
- 在用户空间创建一个新的结构体 A
- 用提权函数指针来覆盖这个 A 的指针
- 触发提权函数,执行 iretq 返回用户空间,执行
system("/bin/sh")
内核ROP
系统一旦开启 SMEP,就不能使用 ret2usr了,可以使用内核 ROP 技术来绕过 SMEP;
内核空间的 ROP 和用户空间的 ROP其实差不多,但是内核传参一般是通过寄存器而不是栈,而且内核并不和用户空间共用一个栈。
我们构建一个 ROP 链让它执行上面的内核提权操作,但是不执行在用户空间的任何指令。构造ROP链如下:
1 | |----------------------| |
先将函数的第一个参数传入rdi寄存器中,然后ROP链中的第一条指令从堆栈中弹出空值,将这个值传递给prepare_kernel_cred()
函数。然后将指向一个新的凭证结构的指针存储在rax中,并执行mov rdi, rax操作,再把这个rdi作为参数传递给commit_creds()
。这样就实现了一个提权ROP链。
在完成提权后,还需要返回到用户空间里执行 system('/bin/sh')
,用下面两个指令:
1 | swapgs |
使用 iretq
指令返回到用户空间,在执行 Iretq
之前,执行 swapgs
指令,该指令通过用一个 MSR
重点额值交换 GS
寄存器的内容,用来获取指向内核数据结构的指针,然后才能执行系统调用之类的内核空间程序。iretq
的堆栈布局如下:
1 | |----------------------| |
Baby Hacker 2
应该算是一个简单的内核漏洞。
1 | timeout --foreground 15 qemu-system-x86_64 \ |
启动脚本中,可以看到开启了 kaslr、smep 和 smap三项保护,这三项分别是 内核地址随机化、禁止内核访问用户空间数据和禁止内核执行用户空间代码。其中部分参数说明如下:
1 | -initrd initramfs.cpio,使用 initramfs.cpio 作为内核启动的文件系统 |
内核文件 是 initramfs.cpio
,可以使用 cpio -idmv < initramfs.cpio
解压该镜像文件,我们就能够看到该镜像内部执行的细节。可以发现一个启动脚本:
1 | #./etc/init.d/rcs |
使用 insmod
命令加载了 babyhacker.io
驱动,将 dmesg_restrict
和 kptr_restrict
置为0,表示可以通过 cat kallsyms
查看 commit_creds/prepare_kernel_cred
两个函数的地址,通过 commit_creds(prepare_kernel_cred(0))
调用来提权。
程序分析
getsize
中, size
为 unsigned int
,但是 在过第一个比较时,却将其转换为了 int
。并且当 size<11
时,会将 size
的低位 赋给 buffersize
。如果我们初始输入一个 负数,则能通过 <11 的判断,并且将 Buffersize 能够赋值一个大数。
利用分析
- 修改
buffersize
使用上面分析的漏洞,修改 buffersize
的大小,泄露 canary, rbp, ret
的地址。
先打开内核驱动接口,使用 ioctl
来向内核驱动接口传输数据:
1 | fd = open("/dev/babyhacker", O_RDONLY); |
- 从内核空间读取
buffersize
在用户空间分配一个缓冲区,读取内核数据。此时读取的数据是从内核栈空间rsp
开始,如下所示,读取 0xfff
大小的字节,如下图所示。也就是我们能够再输出的 0x2a
个longlong
整型处得到 ret
地址,在 0x28
处得到canary
地址。
我们也可以通过 ret
地址来得到 vmlinux_base
。
1 | long long *buf=(long long *)malloc(0x1000); |
- 执行
rop
来getshell
在内核空间中寻找 gadget
,寻找的方法仍然是对 vmlinux
使用 ropper
获得。我们的 rop
需要有三个功能:1. 修改 cr4
寄存器的值,来关闭 smep
和smap
保护;2. 执行提权函数,即 commit_creds(prepare_kernel_cred(0))
来提权;3. 执行内核态切换到用户态,实现提权后,还需要将程序从内核态返回到用户态;4. 返回用户态后,执行 system('/bin/sh')
获得shell
权限。
我们的 rop
结构如下:
1 | memset(padding, 'a', 0x140); |
最终,在qemu
中我们成功实现提权,能够读取flag:
EXP
在返回用户态前,要注意:在程序开始之前使用 save_status
来保存寄存器的状态
1 | //gcc exp.c -static -o exp |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/09/25/高校战疫网络安全分享赛/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!