综述
2001年9月5日,Qualys发布了一个关于基于Linux病毒的安全报警。这个病毒叫作"远程shell特洛伊木马(Remote Shell Trojan)",攻击Linux ELF格式的二进制可执行文件。它具有复制自己的能力:当被运行时,它就会感染所有/bin目录和当前目录下的所有二进制文件。除此之外,这个病毒还产生一个进程,在5503 UDP端口上监听。当这个进程收到一个特制的报文之后,就通过一个系统调用连接到源地址。
细节
通常,在UNIX系统中病毒不算是一种真正的威胁。一个病毒工作在普通用户权限下的病毒是不能感染没有写权限的二进制文件的。不过每次执行被感染的可执行文件,它就会感染当前目录下的二进制可执行文件,因此还是有一定的威胁的。一旦在root权限下,不小心执行了被病毒感染的文件,它就会感染这个/bin目录下的文件。一旦执行了象ls之类常用的系统命令,当前目录下的所有目录中的二进制可执行文件都会被感染。攻击者还可以通过RST的后门进程获得更高的权限。
来源
RST是处于研究目的开发的,只在内部使用。研究它的目标是分析如何使病毒在非root权限条件下,影响通常的Linux工作环境。然而,事情却并没有按照研究人员设想的方向发展。一个被感染的二进制可执行文件意外地把这个病毒泄露出实验室。
现在最关心的问题是如何阻止这个病毒的传播,以及尽可能地从被感染的主机中清除这个病毒。现在公众还不清楚发送到后门进程的报文格式。然而,将来通过逆向工程,人们可以获得这些技术细节。
解决方案
最好能够使二进制可执行文件将来能够具有对RST的免疫力。把ELF文件正文段增加4096字节,是正文段和数据段之间的空洞(hole)消失,可以提高系统对RST病毒的免疫力。采取了这种措施以后,RST病毒就没有空间在二进制可执行文件中写入自身的代码了。
这段清除病毒的代码非常简单易用。用户能够决定是否递归地自动检测清除RST病毒。或者对二进制可执行文件一个一个地使用这个清除程序,在这种模式下,系统管理者可以知道二进制可执行文件是否感染,是否具有免疫能力。
清除程序的简单用法:
% perl Recurse.pl remove
结论
再次建议所有使用Linux系统的人运行这个检测程序检查系统是否被感染。即使系统没有被感染,也应该采取措施使系统具有免疫能力。只有这样才可以使系统免受感染。
代码
Recurse.pl
#!/usr/bin/perl
use strict;
sub RecursiveDeinfect($); my $path_to_cleaner = "./kill"; my $options_to_cleaner = ""; my $verbose;
if($ARGV[0] eq "-v") { $verbose = shift; }
$_ = $ARGV[0]; if(/detect/) { $options_to_cleaner = "1"; print "Recursively detecting trojan starting in: ", $ARGV[1] "/", " "; } elsif(/remove/) { $options_to_cleaner = "2"; print "Recursively removing trojan starting in: ", $ARGV[1] "/", " "; } elsif(/immune/) { $options_to_cleaner = "4"; print "Recursively making all binaries starting in: ", $ARGV[1] "/", "immune "; } else { print "usage: $0 [-v] mode [startdir] ". "where mode is one of the following: ". " detect : Recursively detect trojan ". " remove : Recursively remove trojan if trojan is present. ". " immune : Recursively remove trojan if trojan is present. ". " Make all innocent binaries immune for future infection ". "The default startdir is / "; exit(0); }
RecursiveDeinfect($ARGV[1] "/");
sub RecursiveDeinfect($) { my $startdir = shift; my $filename; my $ret;
return unless(opendir(my $DH, $startdir));
print "Checking $startdir for infected binaries.. " if($verbose);
while($filename = readdir($DH)) { next if($filename eq "." or $filename eq ".."); next if(-l "$startdir/$filename"); stat("$startdir/$filename"); RecursiveDeinfect("$startdir/$filename") && next if(-d _); next unless(-f _ && -x _); $ret = qx($path_to_cleaner $options_to_cleaner $startdir/$filename 2>/dev/null); if($options_to_cleaner eq "1" && $ret =~ /much alive/m) { warn "$startdir/$filename: Trojan Detected. "; } elsif($options_to_cleaner eq "2" && $ret =~ /trojan disabled/m) { warn "$startdir/$filename: Trojan Detected and Removed "; } }
closedir($DH); }
kill.c
/* RST trojan manipulator program * * this is an RST-remover code, it has the following features: * 1. Detect RST trojans on the specified executable * 2. Disable RST on the specified binary and during that process make * the binary immune to further infection attempts * 3. Make an innocent, not-infected binary immune to infection attempts */ #include #include #include #include #include #include #include
/* Disable_trojan() * disables a trojan in an infected binary, making the binary immune to * any further infection attempts */ int Disable_trojan(FILE *fp) { unsigned int i, padding, poffset, psize, oldentry; Elf32_Phdr textseg, dataseg; Elf32_Ehdr ehdr;
// move to the beginning of the file if (fseek(fp, 0, SEEK_SET) != 0) goto err;
// read ELF binary header if (fread(&ehdr, sizeof(ehdr), 1, fp) != 1) goto err; if (ehdr.e_type != ET_EXEC) goto err;
// find text segment and data segment headers if (fseek(fp, ehdr.e_phoff, SEEK_SET) != 0) goto err; for (i=0;i { if (fread(&textseg, sizeof(textseg), 1, fp) != 1) goto err; if (textseg.p_offset == 0) break; } if (fread(&dataseg, sizeof(dataseg), 1, fp) != 1) goto err; if (textseg.p_offset != 0) { /* wtf?! no text segment ??? */ goto err; }
// do calculations padding = dataseg.p_vaddr - (textseg.p_vaddr + textseg.p_filesz); if (padding >= 4096) return 0; /* Sheww, binary is not infected */ psize = (textseg.p_vaddr + textseg.p_filesz) - ehdr.e_entry; poffset = (textseg.p_offset + textseg.p_filesz) - psize; if (psize != 4096) return 0; /* Binary already cleaned */
// read original entry point, that according to my reverse engineering // is stored on the parasite at offset 1 if (fseek(fp, poffset+1, SEEK_SET)!=0) goto err; if (fread(&oldentry, 4, 1, fp) != 1) goto err;
// restore the binarys entry point to point to the real program again, // avoiding the execution of the parasite code. // this pernamently disables the parasite code and makes the binary immune // to further infection attempts. ehdr.e_entry = oldentry; if (fseek(fp, 0, SEEK_SET) != 0) goto err; if (fwrite(&ehdr, sizeof(ehdr), 1, fp) != 1) goto err;
return 1; /* all done */
err: ; return -1; }
/* DisplayStatus() * display the infection status of the specified binary */ int DisplayStatus(FILE *fp) { unsigned int i, padding, poffset, psize, oldentry; Elf32_Phdr textseg, dataseg; Elf32_Ehdr ehdr;
// move to the beginning of the file if (fseek(fp, 0, SEEK_SET) != 0) goto err1;
// read ELF binary header if (fread(&ehdr, sizeof(ehdr), 1, fp) != 1) goto err1; if (ehdr.e_type != ET_EXEC) goto err1;
// find text segment and data segment headers if (fseek(fp, ehdr.e_phoff, SEEK_SET) != 0) goto err1; for (i=0;i { if (fread(&textseg, sizeof(textseg), 1, fp) != 1) goto err1; if (textseg.p_offset == 0) break; } if (fread(&dataseg, sizeof(dataseg), 1, fp) != 1) goto err1; if (textseg.p_offset != 0) { /* wtf?! no text segment ??? */ goto err1; }
// do calculations padding = dataseg.p_vaddr - (textseg.p_vaddr + textseg.p_filesz); psize = (textseg.p_vaddr + textseg.p_filesz) - ehdr.e_entry; poffset = (textseg.p_offset + textseg.p_filesz) - psize;
printf("Estimated parasite offset : %d ",poffset); printf("Binary infection status : "); if (padding >= 4096) printf("Not infected. not immune. "); else if (psize != 4096) printf("Trojan disabled, immune. "); else printf("Trojan is very much alive! "); fflush(stdout);
return 0; err1: ; return -1; }
int movedata(FILE *fp, int src_offset, int dst_offset, const int size) { char data[size]; int sloc;
// save current location sloc = ftell(fp); // move to source offset and read data if (fseek(fp, src_offset, SEEK_SET) != 0) return -1; fread(&data,size,1,fp); // write data to destination offset if (fseek(fp, dst_offset, SEEK_SET) != 0) return -1; fwrite(&data,size,1,fp); // return to original position fseek(fp,sloc,SEEK_SET);
return 0; }
/* make_immune() * turns an innocent binary into an uninfectable binary */ int make_immune(FILE *fp) { unsigned int i, padding, poffset, psize, oldentry; Elf32_Phdr textseg, dataseg; struct stat stat; Elf32_Ehdr ehdr;
// get file status if (fstat(fileno(fp), &stat)<0) return -1;
// move to the beginning of the file if (fseek(fp, 0, SEEK_SET) != 0) return -1;
// read ELF binary header if (fread(&ehdr, sizeof(ehdr), 1, fp) != 1) return -1; if (ehdr.e_type != ET_EXEC) return -1;
// find text segment and data segment headers if (fseek(fp, ehdr.e_phoff, SEEK_SET) != 0) return -1; for (i=0;i { if (fread(&textseg, sizeof(textseg), 1, fp) != 1) return -1; if (textseg.p_offset == 0) break; } if (fread(&dataseg, sizeof(dataseg), 1, fp) != 1) return -1; if (textseg.p_offset != 0) { /* wtf?! no text segment ??? */ return -1; }
// do calculations padding = dataseg.p_vaddr - (textseg.p_vaddr + textseg.p_filesz); psize = (textseg.p_vaddr + textseg.p_filesz) - ehdr.e_entry; poffset = textseg.p_offset + textseg.p_filesz;
if (padding < 4096) return 0; /* Already infected or already immune */
// Update segment header table if (fseek(fp, ehdr.e_phoff, SEEK_SET) != 0) return -1; for (i=0;i { Elf32_Phdr phdr;
if (fread(&phdr, sizeof(phdr), 1, fp) != 1) return -1; if (phdr.p_offset >= poffset) { phdr.p_offset += 4096; goto writedown; } if (phdr.p_offset == 0) { phdr.p_filesz += 4096; phdr.p_memsz += 4096; goto writedown; }
continue; writedown: ; if (fseek(fp, -1 * sizeof(phdr), SEEK_CUR) != 0) return -1; if (fwrite(&phdr, sizeof(phdr), 1, fp) != 1) return -1; }
// update section header table if (fseek(fp, ehdr.e_shoff, SEEK_SET) != 0) return -1; for (i=0;i { Elf32_Shdr section;
if (fread(§ion, sizeof(section), 1, fp) != 1) return -1; if (section.sh_offset > poffset) { section.sh_offset += 4096; if (fseek(fp, -1 * sizeof(section), SEEK_CUR) != 0) return -1; if (fwrite(§ion,sizeof(section),1,fp) != 1) return -1; } }
// physically move data from (poffset->eof), 4096 bytes forward if (movedata(fp, poffset, poffset+4096, stat.st_size - poffset) != 0) return -1; /* Fuck! */
// all done!
return 1; }
int main(int argc, char *argv[]) { FILE *fp; int op;
if (argc < 3) { printf("usage: %s ",argv[0]); printf("Available operations: "); printf("1 display trojan status on the specified binary "); printf("2 remove the trojan from a specified binary "); printf("3 make an innocent binary immune "); printf("4 remove trojan/if innocent binary then make immune "); printf("5 check if trojan is currently running on the system "); printf(" "); return -1; }
op = atoi(argv[1]);
/* Run status check if operation 5 was selected */ if (op == 5) { struct flock lock; int fd;
// try to open the trojans lockfile if ((fd = open("/tmp/982235016-gtkrc-429249277", O_RDWR)) < 0) { printf("Trojan lockfile does not exist. "); goto testok; } // try to lock the trojans lockfile memset(&lock, 0, sizeof(lock)); lock.l_type = F_WRLCK; if (fcntl(fd, F_SETLK, &lock) < 0) { perror("fcntl"); printf("ALERT! A Trojan remote access process is currently running!! "); return 0; } printf("Trojan process is not currently running however trojan traces were discovered. "); return 0; testok: ; printf("Trojan is not currently running on this system. "); return 0; }
/* Otherwise, open target binary file */ if (!(fp = fopen(argv[2], "r+b"))) { fprintf(stderr, "Unable to open %s for reading/writing (%s) ",argv[2],strerror(errno)); return -1; }
/* Implement the rest of the operations */ switch(op) { case 2: /* Remove trojan */ { switch(Disable_trojan(fp)) { case -1: fprintf(stderr, "An error has occured (%s) ",strerror(errno)); return -1; break; case 0: printf("%s: trojan not present ",argv[2]); break; case 1: printf("%s: trojan disabled ",argv[2]); break; } } break; case 1: /* Display infection status */ DisplayStatus(fp); break; case 3: /* Make an innocent binary immune */ switch (make_immune(fp)) { case -1: fprintf(stderr, "An error has occured (%s) ",strerror(errno)); return -1; break; case 0: printf("%s: already immune. ",argv[2]); return 0; break; case 1: printf("%s: binary is now immune. ",argv[2]); break; } break; case 4: /* Remove trojan and make innocent binaries immune */ { switch(Disable_trojan(fp)) { case -1: fprintf(stderr, "An error has occured (%s) ",strerror(errno)); return -1; break; case 0: if (make_immune(fp) == 1) printf("%s: trojan not present. innocent binary turned immune. ",argv[2]); else printf("%s: immune binary. ",argv[2]); break; case 1: printf("%s: trojan disabled ",argv[2]); break; } } break; default: printf("Operation %d not implemented ",op); return -1; break; } }
makefile all: # clear ##################################### # Compiling antivirus program ## ##################################### gcc kill.c -o temp
########################################################## ## Making the antivirus binary immune to RST infections ## ########################################################## cp temp kill ; ./temp 4 kill ; rm -f temp
########################################################## ## All done, run perl Recurse.pl for RST detection, ## ## and removal options. ## ## -- anonymous research team ## ##########################################################
|