diff --git a/SUMMARY.md b/SUMMARY.md index 5f6589c..c27dce8 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -65,6 +65,7 @@ GitHub 地址:https://github.com/firmianay/CTF-All-In-One * [3.3.5 Linux 堆利用(上)](doc/3.3.5_heap_exploit_1.md) * [3.3.6 Linux 堆利用(中)](doc/3.3.6_heap_exploit_2.md) * [3.3.7 Linux 堆利用(下)](doc/3.3.7_heap_exploit_3.md) + * [3.3.8 Windows 内核漏洞利用](doc/3.3.8_windows_kernel_exploit.md) * [3.4 Web](doc/3.4_web.md) * [3.4.1 SQL 注入利用](doc/3.4.1_sql_injection.md) * [3.4.2 XSS 漏洞利用](doc/3.4.2_xss.md) diff --git a/doc/3.3.8_windows_kernel_exploit.md b/doc/3.3.8_windows_kernel_exploit.md new file mode 100644 index 0000000..543a124 --- /dev/null +++ b/doc/3.3.8_windows_kernel_exploit.md @@ -0,0 +1,5 @@ +# 3.3.8 Windows 内核漏洞利用 + + +## 参考资料 +- [HackSys Extreme Vulnerable Driver](https://github.com/hacksysteam/HackSysExtremeVulnerableDriver) diff --git a/doc/3_topics.md b/doc/3_topics.md index 3b247f6..a22cc8e 100644 --- a/doc/3_topics.md +++ b/doc/3_topics.md @@ -11,6 +11,7 @@ - [3.3.5 Linux 堆利用(上)](3.3.5_heap_exploit_1.md) - [3.3.6 Linux 堆利用(中)](3.3.6_heap_exploit_2.md) - [3.3.7 Linux 堆利用(下)](3.3.7_heap_exploit_3.md) + - [3.3.8 Windows 内核漏洞利用](3.3.8_windows_kernel_exploit.md) - [3.4 Web](3.4_web.md) - [3.4.1 SQL 注入利用](3.4.1_sql_injection.md) - [3.4.2 XSS 漏洞利用](3.4.2_xss.md) diff --git a/doc/7.1.5_glibc_2018-1000001.md b/doc/7.1.5_glibc_2018-1000001.md index d8c3d27..4df6093 100644 --- a/doc/7.1.5_glibc_2018-1000001.md +++ b/doc/7.1.5_glibc_2018-1000001.md @@ -10,7 +10,7 @@ [下载文件](../src/exploit/7.1.5_glibc_2018-1000001) ## 漏洞描述 -该漏洞涉及到 Linux 内核的 `getcwd` 系统调用和 glibc 的 `realpath()` 函数,可以实现本地提权。漏洞产生的原因是 `getcwd` 系统调用在 Linux-2.6.36 版本发生的一些变化,我们知道 `getcwd` 用于返回当前工作目录的绝对路径,但如果当前目录不属于当前进程的根目录,即从当前根目录不能访问到该目录,如该进程使用 `chroot()` 设置了一个新的文件系统根目录,但没有将当前目录的根目录替换成新地址的时候,`getcwd` 会在返回的路径前加上 `(unreachable)`。通过改变当前目录到另一个挂载的用户空间,普通用户也可以完成这样的操作。然后返回的这个非绝对地址的字符串会在 `realpath()` 函数中发生缓冲区下溢,从而导致任意代码执行,再利用 SUID 程序即可获得目标系统上的 root 权限。 +该漏洞涉及到 Linux 内核的 `getcwd` 系统调用和 glibc 的 `realpath()` 函数,可以实现本地提权。漏洞产生的原因是 `getcwd` 系统调用在 Linux-2.6.36 版本发生的一些变化,我们知道 `getcwd` 用于返回当前工作目录的绝对路径,但如果当前目录不属于当前进程的根目录,即从当前根目录不能访问到该目录,如该进程使用 `chroot()` 设置了一个新的文件系统根目录,但没有将当前目录的根目录替换成新目录的时候,`getcwd` 会在返回的路径前加上 `(unreachable)`。通过改变当前目录到另一个挂载的用户空间,普通用户也可以完成这样的操作。然后返回的这个非绝对地址的字符串会在 `realpath()` 函数中发生缓冲区下溢,从而导致任意代码执行,再利用 SUID 程序即可获得目标系统上的 root 权限。 ## 漏洞复现 @@ -20,7 +20,7 @@ | 调试器 | gdb-peda| 版本号:7.11.1 | | 漏洞软件 | glibc | 版本号:2.23-0ubuntu9 | -漏洞发现者已经公开了漏洞利用代码,需要注意的是其所支持的系统被硬编码进了利用代码中,可看情况进行修改。[地址](https://www.halfdog.net/Security/2017/LibcRealpathBufferUnderflow/RationalLove.c) +漏洞发现者已经公开了漏洞利用代码,需要注意的是其所支持的系统被硬编码进了利用代码中,可看情况进行修改。[exp](https://www.halfdog.net/Security/2017/LibcRealpathBufferUnderflow/RationalLove.c) ``` $ gcc -g exp.c $ id @@ -58,7 +58,7 @@ uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plug char *getcwd(char *buf, size_t size); ``` -它用于得到一个以 null 结尾的字符串,内容是当前进程的当前工作的绝对路径。并以保存到参数 buf 中的形式返回。 +它用于得到一个以 null 结尾的字符串,内容是当前进程的当前工作目录的绝对路径。并以保存到参数 buf 中的形式返回。 首先从 Linux 内核方面来看,在 2.6.36 版本的 [vfs: show unreachable paths in getcwd and proc](https://github.com/torvalds/linux/commit/8df9d1a4142311c084ffeeacb67cd34d190eff74) 这次提交,使得当目录不可到达时,会在返回的目录字符串前面加上 `(unreachable)`: ```c @@ -149,7 +149,7 @@ out: 从 glibc 方面来看,由于它仍然假设 `getcwd` 将返回绝对地址,所以在函数 `realpath()` 中,仅仅依靠 `name[0] != '/'` 就断定参数是一个相对路径,而忽略了以 `(` 开头的不可到达路径。 -`__realpath()` 用于将 `path` 所指向的相对路径转换成绝对路径,其间会将所有的符号链接展开并解析 `/./`、`/../` 和多于的 `/`。然后存放到 `resolved_path` 指向的地址中,具体实现如下: +`__realpath()` 用于将 `path` 所指向的相对路径转换成绝对路径,其间会将所有的符号链接展开并解析 `/./`、`/../` 和多余的 `/`。然后存放到 `resolved_path` 指向的地址中,具体实现如下: ```c // stdlib/canonicalize.c @@ -200,7 +200,7 @@ __realpath (const char *name, char *resolved) } } ``` -当传入的 name 不是一个绝对路径,比如 `../../x`,`realpath()` 将会使用当前工作目录来进行解析,而且默认了它以 `/` 开头。解析过程是从后先前进行的,当遇到 `../` 的时候,就会跳到前一个 `/`,但这里存在一个问题,没有对缓冲区边界进行检查,如果缓冲区不是以 `/` 开头,则函数会越过缓冲区,发生溢出。所以当 `getcwd` 返回的是一个不可到达路径 `(unreachable)/` 时,`../../x` 的第二个 `../` 就已经越过了缓冲区,然后 `x` 就会被复制到这个越界的地址处。 +当传入的 name 不是一个绝对路径,比如 `../../x`,`realpath()` 将会使用当前工作目录来进行解析,而且默认了它以 `/` 开头。解析过程是从后先前进行的,当遇到 `../` 的时候,就会跳到前一个 `/`,但这里存在一个问题,没有对缓冲区边界进行检查,如果缓冲区不是以 `/` 开头,则函数会越过缓冲区,发生溢出。所以当 `getcwd` 返回的是一个不可到达路径 `(unreachable)/` 时,`../../x` 的第二个 `../` 就已经越过了缓冲区,然后 `x` 会被复制到这个越界的地址处。 #### 补丁 漏洞发现者也给出了它自己的补丁,在发生溢出的地方加了一个判断,当 `dest == rpath` 的时候,如果 `*dest != '/'`,则说明该路径不是以 `/` 开头,便触发报错。 @@ -287,7 +287,7 @@ $ file /bin/umount /bin/umount: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2104fb4e2c126b9ac812e611b291e034b3c361f2, not stripped ``` -exp 的主要分成两个部分: +exp 主要分成两个部分: ```c int main(int argc, char **argv) { [...] @@ -321,11 +321,11 @@ escalateOk: 简单地说一下 mount namespace,它用于隔离文件系统的挂载点,使得不同的 mount namespace 拥有自己独立的不会互相影响的挂载点信息,当前进程所在的 mount namespace 里的所有挂载信息在 `/proc/[pid]/mounts`、`/proc/[pid]/mountinfo` 和 `/proc/[pid]/mountstats` 里面。每个 mount namespace 都拥有一份自己的挂载点列表,当用 clone 或者 unshare 函数创建了新的 mount namespace 时,新创建的 namespace 会复制走一份原来 namespace 里的挂载点列表,但从这之后,两个 namespace 就没有关系了。 -首先为了提权,我们需要一个 SUID 程序,mount 和 umount 是比较好的选择,因为它们都依赖于 `realpath()` 来解析路径,而且能被所有用户使用。其中 umount 又最理想,因为它可以一次运行可以操作多个挂载点,从而可以多次触发到漏洞代码。 +首先为了提权,我们需要一个 SUID 程序,mount 和 umount 是比较好的选择,因为它们都依赖于 `realpath()` 来解析路径,而且能被所有用户使用。其中 umount 又最理想,因为它一次运行可以操作多个挂载点,从而可以多次触发到漏洞代码。 -由于 umount 的 `realpath()` 的操作发生在堆上,第一步就得考虑怎样去创造一个可重现的堆布局。通过移除可能造成干扰的环境变量,仅保留 locale 即可做到这一点。locale 在 glibc 或者其它需要本地化的程序和库中被用来解析文本(如时间、日期等),它会在 umount 参数解析之前进行初始化,所以会影响到堆的结构和位于 `realpath()` 函数缓冲区前面的那些低地址的内容。所以漏洞的利用依赖于单个 locale 的可用性,在标准系统中,libc 提供了一个 `/usr/lib/locale/C.UTF-8`,它通过环境变量 `LC_ALL=C.UTF-8` 进行加载。 +由于 umount 的 `realpath()` 的操作发生在堆上,第一步就得考虑怎样去创造一个可重现的堆布局。通过移除可能造成干扰的环境变量,仅保留 locale 即可做到这一点。locale 在 glibc 或者其它需要本地化的程序和库中被用来解析文本(如时间、日期等),它会在 umount 参数解析之前进行初始化,所以会影响到堆的结构和位于 `realpath()` 函数缓冲区前面的那些低地址的内容。漏洞的利用依赖于单个 locale 的可用性,在标准系统中,libc 提供了一个 `/usr/lib/locale/C.UTF-8`,它通过环境变量 `LC_ALL=C.UTF-8` 进行加载。 -在 locale 被设置后,缓冲区下溢将覆盖 locale 中,用于加载 national language support(NLS) 的字符串中的一个 `/`,从而将其更改为相对路径。然后,用户控制的 umount 错误信息的翻译将被加载,使用 fprintf() 函数的 `%n` 格式化字符串,即可对一些内存地址进行写操作。由于 fprintf() 所使用的堆栈布局是固定的,所以可以忽略 ASLR 的影响。于是我们就可以利用该特性覆盖掉 `libmnt_context` 结构体中的 `restricted` 字段: +在 locale 被设置后,缓冲区下溢将覆盖 locale 中用于加载 national language support(NLS) 的字符串中的一个 `/`,进而将其更改为相对路径。然后,用户控制的 umount 错误信息的翻译将被加载,使用 fprintf() 函数的 `%n` 格式化字符串,即可对一些内存地址进行写操作。由于 fprintf() 所使用的堆栈布局是固定的,所以可以忽略 ASLR 的影响。于是我们就可以利用该特性覆盖掉 `libmnt_context` 结构体中的 `restricted` 字段: ```c // util-linux/libmount/src/mountP.h struct libmnt_context @@ -610,7 +610,7 @@ attemptEscalationCleanup: return(escalationSuccess); } ``` -通过栈喷射在内存中放置大量的 "AANGUAGE=X.X" 环境变量,这些变量位于栈的上部,包含了大量的指针。当运行 umount 时,很可能会调用到 `realpath()` 并造成下溢。umount 调用 `realpath()` 的过程是这样的: +通过栈喷射在内存中放置大量的 "AANGUAGE=X.X" 环境变量,这些变量位于栈的上部,包含了大量的指针。当运行 umount 时,很可能会调用到 `realpath()` 并造成下溢。umount 调用 `setlocale` 设置 locale,接着调用 `realpath()` 检查路径的过程如下: ```c /* * Check path -- non-root user should not be able to resolve path which is @@ -677,9 +677,9 @@ char *canonicalize_path_restricted(const char *path) 由于语言发生了改变,umount 将尝试加载另一种语言的 catalogue。此时 umount 会有一个阻塞时间用于创建一个新的 message catalogue,漏洞利用得以同步进行,然后 umount 继续执行。 -更新后的格式化字符串现在包含了当前程序的所有偏移。但是堆栈中却没有合适的指针用于写入,同时因为 fprintf 必须调用相同的格式化字符串,且每次调用需要覆盖不同的内存地址,这里采用一种简化的虚拟机的做法,每次 fprintf 的调用作为时钟,路径名的长度作为指令指针。格式化字符串重复处理的过程将返回地址从主函数转移到了 `getdate()` 和 `execl()` 两个函数中。这两个函数被用于 ROP。 +更新后的格式化字符串现在包含了当前程序的所有偏移。但是堆栈中却没有合适的指针用于写入,同时因为 fprintf 必须调用相同的格式化字符串,且每次调用需要覆盖不同的内存地址,这里采用一种简化的虚拟机的做法,将每次 fprintf 的调用作为时钟,路径名的长度作为指令指针。格式化字符串重复处理的过程将返回地址从主函数转移到了 `getdate()` 和 `execl()` 两个函数中,然后利用这两个函数做 ROP。 -被调用的程序文件中包含一个 shebang(即"#!"),使系统调用了漏洞利用程序作为它的解释器。然后该漏洞利用程序修改了它的所有者和权限,使其变成一个 SUID 程序。当 mount 最初的调用者发现文件的权限发生了变化,它会做一定的清理并调用 SUID 二进制文件的辅助功能,即一个 SUID shell,于是完成提权。 +被调用的程序文件中包含一个 shebang(即"#!"),使系统调用了漏洞利用程序作为它的解释器。然后该漏洞利用程序修改了它的所有者和权限,使其变成一个 SUID 程序。当 umount 最初的调用者发现文件的权限发生了变化,它会做一定的清理并调用 SUID 二进制文件的辅助功能,即一个 SUID shell,完成提权。 ## 参考资料