⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 病毒编程技术.txt

📁 windows下的病毒设计技术
💻 TXT
📖 第 1 页 / 共 4 页
字号:
        and ebx,0ffe00000h  ;该地址为Kernel32.dll模块下方的某个地址
                ;先减去0x100000确保该地址处于Kernel32.dll的下方
                ;向高地址搜索如果将来Windows的发行版本中Kernel32.dll
                ;大小和代码结构发生变化,该方法可能无效

  ebx中现在已经是Kernel32.DLL基址之前某个地址了,后续代码可以向高地址搜索其基址。该方法有一个缺点,就是必须明确知道程序入口的堆栈指针值,或间接可计算出该值,对于那些在程序入口获取控制权的病毒代码而言,是可以的,但对于采用EPO技术的病毒而言,该方法则不适用。事实上还有另外一种更加通用的方法,我们知道在Win32程序执行过程中fs段寄存器的基址总是指向进程的TEB,TEB的第一个成员指向SEH链表,该链表每个节点都是一个EXCEPTION_REGISTRATION结构,该结构定义如下:
struct EXCEPTION_REGISTRATION{
    struct EXCEPTION_REGISTRATION *prev;
 void* handler;
};
在Windows下SEH链表最后一个成员的handler指向Kernel32.DLL中函数UnhandledExceptionFilter的起始地址,利用这一特性我们可以写出更通用的代码:
        xor     esi,esi
        lods    dword [fs:esi];取得SEH链表的头指针
      @@:
        inc     eax             ;是否是最后一个SEH节点,检查prev是否为0xFFFFFFFF
        je      @F
        dec     eax
        xchg    esi,eax         
        LODSD                   ;下一个SEH节点
        jmp     near @B
      @@:
        LODSD                   ;取得Kernel32.dll中UnhandledExceptionFilter的地址

         在有的病毒直接以0x7FFDE000作为TEB的指针值,其原因在于在Windows 2003 SP1、Windows XP SP2以前的NT类系统上,该值是固定的,这样的确可以节省一两个字节。但是在Windows 2003 SP1、Windows XP SP2中,情况已经发生了变化,出于安全性的考虑,Windows系统开始动态映射TEB了,也就是说,指向TEB的指针值不再固定,因此这种硬编码的方法也就走到了尽头。此时可以按照前面的方法向低地址搜索判断直到找到Kernel32.dll的基址为止。Elkern中判断是否找到了Kernel32.dll基址的相关代码如下:
search_api_addr_@1:
        add ebx,10000h
        jz short search_api_addr_seh_restore
        cmp word ptr [ebx],'ZM'   ;是否是MZ标志
        jnz short search_api_addr_@1
        mov eax,[ebx+3ch]
        add eax,ebx
        cmp word ptr [eax],'EP'   ;是否具有PE标志
        jnz short search_api_addr_@1
   ;找到了kernel32.dll的基址

方法2:搜索PEB的相关结构获取Kernel32.DLL的基址

        前述TEB偏移0x30处,亦即FS:[0x30]地址处保存着一个重要的指针,该指针指向PEB(进程环境块),PEB成员很多,这里并不介绍PEB的详细结构。我们只需要知道PEB结构的偏移0xC处保存着另外一个重要指针ldr,该指针指向PEB_LDR_DATA结构:
typedef struct _PEB_LDR_DATA 
{
                                                           
  ULONG             Length;                              // +0x00   
  BOOLEAN           Initialized;                         // +0x04   
  PVOID             SsHandle;                            // +0x08   
  LIST_ENTRY        InLoadOrderModuleList;   // +0x0c   
  LIST_ENTRY        InMemoryOrderModuleList;          // +0x14   
  LIST_ENTRY        InInitializationOrderModuleList;// +0x1c 
} PEB_LDR_DATA,*PPEB_LDR_DATA;                        // +0x24       
  该结构的后三个成员是指向LDR_MODULE链表结构中相应三条双向链表头的指针,分别是按照加载顺序、在内存中的地址顺序和初始化顺序排列的模块信息结构的指针。LDR_MODULE结构如下所示:
typedef struct _LDR_MODULE
{
    LIST_ENTRY        InLoadOrderModuleList;             // +0x00
    LIST_ENTRY        InMemoryOrderModuleList;          // +0x08
    LIST_ENTRY        InInitializationOrderModuleList; // +0x10
    PVOID             BaseAddress;                        // +0x18
    PVOID             EntryPoint;                         // +0x1c
    ULONG             SizeOfImage;                        // +0x20
    UNICODE_STRING    FullDllName;                       // +0x24
    UNICODE_STRING    BaseDllName;                       // +0x2c
    ULONG             Flags;                              // +0x34
    SHORT             LoadCount;                          // +0x38
    SHORT             TlsIndex;                           // +0x3a
    LIST_ENTRY        HashTableEntry;                    // +0x3c
    ULONG             TimeDateStamp;                      // +0x44
                                                          // +0x48
} LDR_MODULE, *PLDR_MODULE;

  Peb->Ldr->InInitializationOrderModuleList指向按照初始化顺序排序的第一个LDR_MODULE节点的InInitializationOrderModuleList成员的指针,在WinNT平台(不包含Win9X)下,该链表头节点的LDR_MODULE结构包含的是NTDLL.DLL的相关信息,而链表的下一个节点所包含的就是Kernel32.dll相关的信息了,该节点LDR_MODULE结构中的BaseAddress不正是我们所苦苦寻找的吗。注意InInitializationOrderModuleList是LDR_MODULE的第3个成员,因此要获取BaseAddress的地址,只需将其指针加8再derefrence即可。因此下面的汇编代码即可获取Kernel32.DLL的基址:

 mov eax, dword ptr fs:[30h]     ;获取PEB基址
     mov eax, dword ptr [eax+0ch] ;获取PEB_LDR_DATA结构指针
     mov esi, dword ptr [eax+1ch] 
     ;获取InInitializationOrderModuleList链表头第一个LDR_MODULE节点
     InInitializationOrderModuleList成员的指针
    lodsd                 ;获取双向链表当前节点后继的指针
  mov ebx, dword ptr [eax+08h] ;取其基地址,该结构当前包含的是
                     ;kernel32.dll相关的信息

        该方法在所有的Windows NT(包括Windows 2003 SP1和Windows XP SP2)操作系统上都是有效的,唯一的缺憾是由于PEB结构不同,该方法在Win9X系统上无效。听起来可能比较费解,还是用一张图更加清晰一些:


图6 利用PEB搜索kernel32.dll基地址的过程

* 解析PE文件的导出函数表

  PE文件的函数导出机制是进行模块间动态调用的重要机制,对于正常的程序,相关操作是由系统加载器在程序加载前自动完成的,对用户程序是透明的。但要想在病毒代码中实现函数地址的动态解析以取代加载器,那就有必要了解函数导出表的结构了。在图1中可以看到在PE头结构IMAGE_OPTIONAL_HEADER32结构中包含一个DataDirectory数组结构,该结构包含16个成员,每个成员都是一个IMAGE_DATA_DIRECTORY结构:
  typedef struct _IMAGE_DATA_DIRECTORY {
      DWORD   VirtualAddress;
      DWORD   Size;
  } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
  DataDirectory数组的每个结构都指向一个重要的数据结构,第一个成员指向导出函数表(索引0),第2个成员指向PE文件的引入函数表(索引1)。DataDirectory中的第一个成员指向导出函数表的IMAGE_EXPORT_DIRECTORY结构:
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
 AddressOfFunctions是一个双字数组,包含了所有导出函数的RVA,另外两个成员AddressOfNames也是一个双字数组,包含了指向导出函数名字的字符串的RVA,AddressOfNameOrdinals是一个字数组(16bit),和AddressOfNames数组是并行的,和AddressOfNames数组一起确定了相应引出函数的序号,该序号可直接用于索引AddressOfFunctions数组获取导出函数的地址。因此病毒搜索指定的API就包含了如下步骤:

a)获取NumberOfNames的值以及AddressOfNames、AddressOfNameOrdinals和AddressOfFunctions的数组的地址。
b)搜索AddressOfNames数组,按字符串对比,若找到相应的API,转d
c)若NumberOfNames名字尚未全部搜索完毕,转b继续搜索,若搜索完毕,则表明未找到进行错误处理,这一步通常可以省略,因为我们已经知道相应的DLL中肯定导出了相应的函数。
d)获取当前函数名字指针在AddressOfNames数组中的索引,在AddressOfNameOrdinals数组中取出以该值索引的函数序号,以该序号值作为AddressOfFunctions数组的索引,在AddressOfFunctions数组中取出导出函数的RVA值,加上基址就得到了运行时导出函数的地址。

        看起来似乎比较罗嗦,实际上这是PE设计时为考虑灵活性而做出的牺牲。不过实现起来还是比较简单的,通常汇编代码编译后不到100字节。以下是在Kernel32搜索GetProcAddress的完整代码:

       push     esi
                                ;esi=VA Kernel32.BASE
                                ;edi=RVA K32.pehdr
        mov     ebp,esi
        mov     edi,[ebp+edi+peh.DataDirectory]

        push    edi esi

        mov     eax,[ebp+edi+peexc.AddressOfNames]
        mov     edx,[ebp+edi+peexc.AddressOfNameOrdinals]
        call    @F
        db     "GetProcAddress",0
      @@:
        pop     edi
        mov     ecx,15
        sub     eax,4
     next_:
        add     eax,4
        add     edi,ecx
        sub     edi,15
        mov     esi,[ebp+eax]
        add     esi,ebp
        mov     ecx,15
        repz    cmpsb    ;进行字符串比较,判断是否为要查找的函数
        jnz     next_

        pop     esi edi

        sub     eax,[ebp+edi+peexc.AddressOfNames]
        shr     eax,1

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -