逆向工程之恶意程序第一部分

来源:互联网 发布:apache ab 和jmeter 编辑:程序博客网 时间:2024/06/05 15:56

逆  向  工  程

恶意程序

第一部分

 

作者:Arunpreet Singh

翻译:chence

博客:https://reverse2learn.wordpress.com


 

这个系列有两篇文章,本文是其一。这个恶意程序释放A文件(所有恶意程序经常这么干)所以在这一篇我们只分析释放过程,被释放的文件将在下一篇分析。

 

原始的样本是从KernelMode.info下载下来的对于恶意程序样本和逆向工程,这是一个不错的去处。我将样本上传至了Sendspace,文件的口令是”infected”

 

链接

http://www.sendspace.com/file/to53wo

 

不管怎样先从基础的东西开始ExeInfo/Protection ID检查一下它的壳或者编译器。

 

注意:我是在虚拟机里分析恶意程序的。我建议你也这样做

 

下面是用ExeInfo/Protection ID检查的结果

所以该样本是没有加壳的,微笑(一般情况下恶意程序都是加了壳的)

 

编译器:Visual C++ 2008

 

到这里一切都较好处理

 

Visual C++目标是逆向的理想对象..不像Delphi程序包含恼人的函数调用。VC++目标相对来说容易逆向一些。

 

我们将使用的调试器/反汇编器是

1)      Ollydbg

2)      IDA

我有一个习惯——同时运行IDAOllydbgIDA非常强大,它的一些特性像重命名变量,函数,标记和交叉引用等等..Ollydbg是我个人最喜欢的调试器。

 

单步进入Ollydbg知道WinMain = 00401648或者用IDA..IDA默认从WinMain开始。所以让我们从WinMain开始分析。

00401648 /$  8BFF            mov     edi, edi        //什么也不做

0040164A |.  55              push    ebp            //标准的函数起始——保存帧指针

0040164B |.  8BEC            mov     ebp, esp       //将堆栈指针入EBP

0040164D |.  83EC 1C         sub    esp, 1C                    //为局部变量分配1C(28)比特

00401650 |.  56              push    esi            //在调用函数前保存寄存器

00401651 |.  57              push    edi

00401652 |.  E8 120D0000     call   00402369

 

让我们跟进这个函数..里边的汇编代码看起来像这样子(图是我自己截的):

让我们从有意思的东西开始..有一处调用函数”GetModuleHandleW”,参数传的是0

 

我们都知道GetModuleHandleW(NULL)..EAX寄存器中返回当前加载的可执行模块的基地址..这样这个调用就返回了sample.exe的基地址..下面的几行也很有意思

0040237A |.  8BF0          mov     esi, eax          //现在ESI包含基址

0040237C |.  8B46 3C       mov    eax, dword ptr [esi+3C] //获得NT头部偏移

0040237F |.  8B9C30 800000>mov     ebx, dword ptr [eax+esi+80] //输入表

00402386 |.  03DE          add     ebx, esi  // _IMAGE_IMPORT_DESCRIPTOR结构的地址

00402388 |.  8B43 0C       mov    eax, dword ptr [ebx+C] //指向_IMAGE_IMPORT_DESCRIPTOR结构的名称区域

 

想要懂得上面的代码..你需要对PE格式有个基本的了解

 

首先我们有 = 基地址+3c

 

PE格式中首先有结构IMAGE_DOS_HEADER……让我们一起在Windbg中探究一下IMAGE_DOS_HEADER结构

忽略其它区域..在偏移为0x3C的地方有一个值e_lfanew.

e_lfanew实际上包含PE头的偏移

MOV EAX,DWORD PTR DS:[ESI+3C]

所以上面的指令用于获取NT头偏移

当我们在内存中解析文件的时候还得加上基址..希望现在大家都明了了。

MOV EBX,DWORD PTR DS:[EAX+ESI+80] 

这样我们就将基址+0x80 载入了EBX当中..

每个PE文件包含一个IMAGE_DATA_ DIRECTORY数组,让我们一起来看看IMAGE_DATA_ DIRECTORY内部结构

这样每一个IMAGE_DATA_ DIRECTORY结构包含两个域:Virtual Address Size

NT头部+0x80指向输入表地址..那个目录的值为

00400170   D4320000    DD 000032D4          ; 输入表地址 =0x 32D4

00400174   78000000    DD 00000078          ; 输入表大小 = 0x78 (120.)

这些值我是从Ollydbg的内存窗口里获取的..

 

因此上面的指令操纵就是获取输入表的地址..

 

*输入表是非常重要的概念..它包含输入函数或者动态链接库的基本信息

 

当我们在内存中解析文件的时候要将基址和输入表地址加起来

 

ADD EBX,ESI 

 

下一条指令是

 

MOV EAX,DWORD PTR DS:[EBX+C]

 

这是很有趣的一条指令..输入表实际上是一个IMAGE_IMPORT_DESCRIPTOR数组..每一个IMAGE_ IMPORT_DESCRIPTOR结构包含一个单独dll的信息和一些从该dll输入的函数的信息。所以IMAGE_IMPORT_DESCRIPTOR结构的数量就等于DLL的数量。

 

让我们一起来看看IMAGE_ IMPORT_DESCRIPTOR结构

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

  _ANONYMOUS_UNIONunion {

   DWORD Characteristics;

   DWORD OriginalFirstThunk;

  }DUMMYUNIONNAME;

 DWORD TimeDateStamp;

 DWORD ForwarderChain;

DWORD Name;                           //Offset 0xC

  DWORD FirstThunk;                      //Offset 0x10

} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;

 

因此上面的指令ESI(有误,应为eax)指向IMAGE_IMPORT_DESCRIPTOR结构里的名称

0040238D |.  8975 F8       MOV [LOCAL.2],ESI         //ESI存入局部变量(基址)

 

这样下面是一个循环..我们可以很容易地从Ollydbg看出来..一起来看看这个循环

00402397 |> /03C6          /add     eax, esi                  //在内存中获取DLL的名称

00402399 |. |68 28134000   |push    00401328                        ; /s2 ="user32.dll"

0040239E |. |50            |push    eax                             ; |s1

0040239F |. |FF15 48114000 |call    dwordptr [<&msvcrt._stricmp>]  ;\_stricmp

004023A5 |. |85C0          |test   eax, eax

004023A7 |. |59            |pop     ecx

004023A8 |. |59            |pop     ecx

004023A9 |. |0F85 A0000000 |jnz    0040244F

 

上面的指令所做的操作是

1)      在内存中获取dll的名称

2)      dll名称和”user32.dll”对比

3)      如果名称不相等则跳至下一个IMAGE_IMPORT_DESCRIPTOR结构

4)      跳到第一步

 

这样这个循环一直继续知道dll名称为”user32.dll”

一起来看看当条件成立时会发生什么..我的意思是DLL == ”user32.dll”

 

004023a9处的跳转是一个条件跳转..DLL名称等于”user32.dll”时不跳转.所以让我们一起来看看这个跳转下面的代码——当DLL名称相符时。

 

004023AF |.  8B3B          MOV EDI,DWORD PTR DS:[EBX]     //IATRVAEDI(此处应该是INT

004023B1 |.  03FE          ADD EDI,ESI                     //获得内存地址

004023B3 |.  8B73 10       MOV ESI,DWORD PTR DS:[EBX+10]     // FirstThunkRVA

004023B6 |.  0375 F8       ADD ESI,[LOCAL.2]                     // FirstThunk的内存地址

 

这样上面的代码所做的操作是获取FirstThunk的内存地址..

(认真查看PE格式去了解关于IAT的更多信息)

 

这所有的步骤实际上是解析从该dll输入的函数的名字。

 

004023BE |> /8B4D F8       MOVECX,[LOCAL.2]                     

004023C1 |. |8D4408 02     LEA EAX,DWORD PTRDS:[EAX+ECX+2]     //指向API的名字

 

现在终于指向从User32.dll输入的API函数的名字了

 

004023BE |> /8B4D F8            MOVECX,[LOCAL.2]                      ;  sample.00400000

004023C1 |. |8D4408 02       LEA EAX,DWORDPTR DS:[EAX+ECX+2]

004023C5 |. |68 14134000    PUSH sample.00401314                   ; /s2 ="RegisterClassExW"

004023CA |. |50              PUSH EAX                    ; |s1 ="TranslateMessage"

004023CB |. |FF15 48114000 CALL DWORD PTR DS:[<&msvcrt._stricmp>] ;\_stricmp

 

检查当前API名字是否等于RegisterClassExW(现在这里不相等,因为第一个输入的函数是TranslateMessage

 

004023D1 |. |85C0             TEST EAX,EAX          ; sample.004037D2

004023D3 |. |59               POP ECX               ;  sample.004037D2

004023D4 |. |59               POP ECX                ;  sample.004037D2

004023D5 |. |75 25            JNZ SHORTsample.004023FC

 

如果API名字匹配则不跳转(执行下面的代码),如果不匹配则跳转

 

004023D7 |. |8D45 FC        LEAEAX,[LOCAL.1]

004023DA |. |50            PUSH EAX              ; /pOldProtect = sample.004037D2

004023DB |. |6A 40         PUSH40                ; |NewProtect = PAGE_EXECUTE_READWRITE

004023DD |. |6A 04         PUSH 4                ; |Size = 4

004023DF |. |56            PUSHESI              ; |Address =<&USER32.TranslateMessage>

004023E0 |. |FF15 44104000  CALL DWORD PTRDS:[<&KERNEL32.VirtualP>; \VirtualProtect

004023E6 |. |8D45 FC       LEAEAX,[LOCAL.1]

 

如果API名字匹配的话则用VirtualProtect函数改变那个地址的权限(ESI指向那个API的地址)

 

新的权限 = PAGE_EXECUTE_READWRITE(可执行可读写)

 

让它是可写的

 

大小 = 4

 

这里大小等于4很可能是因为它将改写API的地址(因为我们处于32bit构架,所以地址=4Bytes = 32bits

 

地址 = ESIAPI地址)(目标API地址)

 

004023E9 |. |50                  PUSHEAX            ; /pOldProtect =sample.004037D2

004023EA |. |C706 EF194000 MOV DWORD PTRDS:[ESI],sample.004019EF

004019ef(其它函数地址)重写API地址

 

004023F0 |. |FF75 FC         PUSH[LOCAL.1]      ; |NewProtect =PAGE_READONLY|PAGE_WRITECOPY

004023F3 |. |6A 04            PUSH 4                ; |Size = 4

004023F5 |. |56              PUSH ESI               ; |Address =<&USER32.TranslateMessage>

004023F6 |. |FF15 44104000 CALL DWORD PTR DS:[<&KERNEL32.VirtualP>;\VirtualProtect

 

VirtualProtect函数恢复原来的权限

 

下一部分的LOOP循环也一样..它检查API是否是“CreateWindowExW”.如果名字匹配的话,使用函数VirtualProtect使那片内存可写。然后改写地址,最后又恢复原来的权限。

 

所以让我写一段伪代码来描述一下在这个循环里发生了什么

 

Parse IMAGE_IMPORT_DESCRIPTOR 

If  stricmp(Image_Import_descriptor->Name,”user32.dll)   //Label2

{

      Parse  using  FirstThunk ..Get API NAMES..

       If stricmp(Current_API,”RegisterClassExW”)        //Label1

      {

          VirtualProtect(Address_of_API,Size(4Bytes),PAGE_EXECUTE_READWRITE,PoldProtec)

          //  Make it Writable

          Address_of_API= 004019EF

          VirtualProtect()   //Restore original Permissions

      }

      Else if(Stricmp(Current API,”CreateWIndowExW”)

      {

          VirtualProtect(Address_of_API,Size(4Bytes), PAGE_EXECUTE_READWRITE,PoldP

          //  Make it Writable

          Address_of_API= 00402228

          VirtualProtect()   //Restoreorginal Permissions

      }

      Else

      {

          Get Next  API NAME 

      }      //Start From Label 1

Else

{

Get Next IMAGE_IMPORT_DESCRIPTOR TABLE

}  //Start From Label 2   ..Once TwoFunctions are matched Loop Terminates

 

这样循环结束以后有

地址_RegisterClassExW=004019EF

地址_CreateWindowEx=00402228

 

最后函数结束和返回.. 如此这个函数主要的目的是对IAT作些修改。

 

============================================================================================

下面是我从IDA里得出的伪代码:

int __cdecl sub_402369()

{

  intresult; // eax@1

  intpIID; // ebx@1

 HMODULE hmodule2; // esi@1

 HMODULE hModule; // eax@1

  intOrigianlFirstThunk; // edi@3

 void *FirstThunk; // esi@3

 HMODULE i; // [sp+8h] [bp-8h]@1

 DWORD flOldProtect; // [sp+Ch] [bp-4h]@5

 

 hModule = GetModuleHandleW(0);

 hmodule2 = hModule;

 pIID = (int)((char *)hModule + *(_DWORD *)((char *)hModule + *((_DWORD*)hModule + 15) + 128));//输入表 IID数组指针

 result = *(_DWORD *)(pIID + 0xC);

  for( i = hmodule2; result; pIID += 0x14u )

  {

   if ( !stricmp((const char *)hmodule2 + result, "user32.dll") )

    {

     OrigianlFirstThunk = (int)((char *)hmodule2 + *(_DWORD *)pIID);

     FirstThunk = (char *)i + *(_DWORD *)(pIID + 16);

     while ( *(_DWORD *)OrigianlFirstThunk )

     {

       if ( !stricmp((const char *)i + *(_DWORD *)OrigianlFirstThunk + 2,"RegisterClassExW") )

       {

         VirtualProtect(FirstThunk, 4u, 0x40u, &flOldProtect);//修改内存属性

         *(_DWORD *)FirstThunk = sub_4019EF;  //修改IAT

         VirtualProtect(FirstThunk, 4u, flOldProtect, &flOldProtect);//恢复原内存属性

       }

       if ( !stricmp((const char *)i + *(_DWORD *)OrigianlFirstThunk + 2,"CreateWindowExW") )

       {

         VirtualProtect(FirstThunk, 4u, 0x40u, &flOldProtect);

         *(_DWORD *)FirstThunk = sub_402228;

         VirtualProtect(FirstThunk, 4u, flOldProtect, &flOldProtect);

        }

       OrigianlFirstThunk += 4;

       FirstThunk = (char *)FirstThunk + 4;

     }

     hmodule2 = i;

    }

   result = *(_DWORD *)(pIID + 0x20);

  }

 return result;

}

 

函数调用后.. 这里有一些对资源的函数调用..更像是假的调用..因为访问的资源不存在。

 

00401657 |.  8B7D 08       mov    edi, dword ptr [ebp+8]   ;  sample.00400000 //将基址如EDI

0040165A |.  8B35 C8104000 mov     esi, dword ptr[<&USER32.LoadStringW>]   ;  user32.LoadStringW

00401660 |.  6A 64         push   64                                      ; /Count = 64 (100.)

00401662 |.  68 C04A4000   push   00404AC0                                ; |Buffer = sample.00404AC0

00401667 |.  6A 67         push   67      ; |RsrcID = 67 (103.)   //事实上不存在

00401669 |.  57            push    edi      ; |hInst

0040166A |.  FFD6          call    esi        ; \LoadStringW

 

这个资源ID并不存在。检查OllydbgGetLastError值——ERROR_RESOURCE_TYPE_NOT_FOUND。这看起来像是一个假的调用让程序更加合法而已(也许)。

 

00401678 |.  57            PUSH EDI         ; Arg1 = 00400000        // ImageBase As Parameter

00401679 |.  E8 4DFFFFFF   CALL sample.004015CB 

 

跟进这个函数..再次看见有一些访问资源的函数…LoadIcon,LoadResouce ..没什么重要的,直到一处调用

 

00401637 |.  50            PUSH EAX         ; pWndClassEx = 0006FECC

00401638 |.  FF15 00114000 CALL DWORD PTRDS:[<&USER32.RegisterClassExW>]   ;\ RegisterClassExW

 

记得RegisterClassExW这个函数的地址在开头被改写了..原来是要去访问user32.dll,现在它指向了可执行体的另一个函数..跟进去瞧瞧这个函数

 

 

 

在这个函数里我们可以看到一些有趣的函数调用,例如

GetModuleFileNameA => 这里是要得到当前执行体的完整路径(因为GetModuleHandleW函数带参数NULL是要取得句柄给它)

 

GetTempPathW=> 如名所示,获取临时文件夹的路径

 

这时我们可以看到一个调用函数00401952=>我检查了这个函数..它内部调用了CRT函数 _vsnwprintf…用来处理字符串的(格式化字符串)

 

第一处调用这个函数返回 = TMP1CDFDEBF(这是一个目录名..我知道因为因为我分析了)

译注:这个字符串是用TEM+一个用CPU运行时钟周期数生成的随机字符串,我这里得到的是TMP209EBEE6

 

第二次调用这个函数返回一个字符串=

C: \\ DOCUME~1 \\ ADMINI~1\\ LOCALS~1\\Temp\ \\\ TMP1CDFDEBF

 

这像是一个用来释放文件的地址

 

00401A7D |.  6A 00         push   0                      ; /pSecurity= NULL

00401A7F |.  8D85 F4FDFFFF lea     eax, dword ptr [ebp-20C]    ; |

00401A85 |.  50            push    eax                   ; |Path

00401A86 |.  FF15 08104000 CALL DWORD PTRDS:[<&KERNEL32.CreateDirectoryW>]  

这里是创建一个目录,没什么好解释的..

 

然后再一次调用了函数401952(格式化字符串)来产生文件路径..输出是

C: \\ DOCUME~1 \\ ADMINI~1\\ LOCALS~1\\Temp\ \\\ TMP1CDFDEBF\\sample.exe

所以最后这是一个用来释放文件的路径

 

 

从图片中可以看出..最后调用了函数CopyFileW..所以最后它释放了一个文件到上面的路径中..实际上它复制/释放了正在执行的自身一份拷贝。

 

释放完后这个函数就结束了..

signed int __stdcall sub_4019EF(int a1)

{

  inti; // edi@1

 HMODULE v2; // eax@1

 DWORD v3; // edi@1

 unsigned __int64 v4; // qax@4

 WCHAR Args; // [sp+Ch] [bp-61Ch]@1

 wchar_t Dest; // [sp+214h] [bp-414h]@4

 const WCHAR PathName; // [sp+41Ch] [bp-20Ch]@4

 unsigned int v9; // [sp+624h] [bp-4h]@1

  intv10; // [sp+628h] [bp+0h]@1

 

  v9= (unsigned int)&v10 ^ dword_404000;

  v2= GetModuleHandleW(0);

  v3= GetModuleFileNameW(v2, &Filename, 0x104u);

 GetTempPathW(0x104u, &Args);

  for( i = 2 * v3 + 4212896; *(_WORD *)i != 92; i -= 2 )

    ;

  v4= RDTSC();

 sub_401952(&Dest, 260, L"TMP%.08X%", BYTE4(v4) | v4);

 sub_401952((wchar_t *)&PathName, 260, L"%s\\%s", (unsignedint)&Args);

 CreateDirectoryW(&PathName, 0);

 sub_401952((wchar_t *)&NewFileName, 260, L"%s\\%s",(unsigned int)&PathName);

 CopyFileW(&Filename, &NewFileName, 0);

 return 1;

}

 

所以到现在为止我们分析了函数RegisterClassExW_0..现在我们总跟进入CreateWindowEx_MOD(修改后的CreateWindowEx我称这个函数为CreateWindowEx_MOD因为它内部调用了修改后的CreateWindowExW函数

跟进去看看

 

所有原来的/必须的参数都传递给了RegisterClassExW函数让它看起来更逼真..现在跟进RegisterClassExW

 

RegisterClassExW(实际上是函数00402228)函数里边..我们可以看到一些有趣的API调用,如CreateProcessW,GetThreadContext,SetThreadContext,,WriteProcessMemory…检查一下它到底在干啥..

 

然后有一处调用CreateProcessW(在末尾的W指示着这是Unicode版本)

 

CreateProcessW简单地说用来创建一个进程查看MSDN以获得其它的信息

http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425%28v=vs.85%29.aspx

 

一起来查看一下传递给CreateProcessW的各个参数

00402266 |.  50            push    eax                         ; /pProcessInfo =0006FBD8

00402267 |.  8D85 D8FCFFFF lea     eax, dword ptr [ebp-328]         ; |

0040226D |.  50            push    eax                              ; |pStartupInfo

0040226E |.  56            push    esi                              ; |CurrentDir

0040226F |.  56            push    esi                              ; |pEnvironment

00402270 |.  6A04         push    4                    ; |CreationFlags =CREATE_SUSPENDED

00402272 |.  56            push    esi                              ; |InheritHandles

00402273 |.  56            push    esi                              ;|pThreadSecurity

00402274 |.  56            push    esi                  ; |pProcessSecurity

00402275 |.  56            push    esi                  ; |CommandLine

00402276 |.  68 80464000  push    00404680            ; |ModuleFileName ="C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\\TMP662A5777\sample.exe"

0040227B |.  FF15 40104000 call    dword ptr[<&KERNEL32.CreateProc>; \CreateProcessW

用红色点亮的参数是很重要的让我解释一下

 

00402270 |.  6A 04        PUSH 4                 ;|CreationFlags = CREATE_SUSPENDED 

 

根据MSDN

 

CREATE_SUSPENDED

0x00000004

 

新开启的进程的主线程呈挂起状态,在调用恢复执行的函数前它不会运行。

 

希望现在都清楚了在恶意程序情况下,如果进程在开启时是挂起(SUSPENDED)模式那很有可能说明它将会被修改

 

其它有意思的参数是

 

00402276 |.  68 80464000  push    00404680            ; |ModuleFileName ="C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\\TMP662A5777\sample.exe"

 

这说明我们的样本文件将释放的文件处于挂起模式

*还有你可以想想看释放复制的/相同的文件然后让它运行意味着什么这样做没有意义..释放文件然后运行它然后释放的文件又会做相同的操作(当然你可以想文件可以检查自身的运行的位置然后再改变它的行为,根据这个样本它不是这个情形)所以恶意程序只是释放文件而什么都不做这是很愚蠢的程序..所以这样的哲学思考提供了一些暗示,被释放的进程将会作些修改,而且我们可以看到一些像WriteProcessMemory

这样的API函数。

 

WriteProcessMemory是一种进程间通信的基本方式..用于将给定的数据写入到远程进程的目标区域。

 

这样说来,所做的一切就有意义了。这款恶意程序将对子进程作一些修改,也就是释放的文件进程。让我们继续分析

 

00402287  |. 50            push    eax                              ; /pContext

00402288  |. FFB5 24FDFFFF push    dword ptr[ebp-2DC]              ; |hThread

0040228E  |. C785 30FDFFFF>mov     dword ptr[ebp-2D0], 10007       ; |

00402298  |. FF15 74104000 call    dword ptr[<&KERNEL32.GetThreadC>; \GetThreadContext

 

GetThreadContext=获取指定线程上下文环境(从MSDN上得来的简单明了的定义)

 

pContext=存放CONTEXT结构也就是获取到的寄存器的值这里是006FBE8

 

hThread= 线程的句柄….这里此处包含被释放的文件进程(我将称它为释放进程)的主线程句柄

 

windbg中查看一下CONTEXT结构..Windbg用来查看windows数据结构非常方便..还显示偏移那是真正的有用

正如你看到的EAX的偏移是0xB0..

 

我们的Context结构从006FBE8开始

 

CONGTEXT.EAX= 006FBE8 + BO = 006FC98

 

为什么EAX如此重要进程在SUSPENDED模式下,EAX总是指向入口

 

执行完GetThreadContext

 

我们取得了context.EAX的值

006Fc98= 004029B9..如前所说它是释放进程的入口地址

现在检查一下下面的几行有趣的代码

004022A4  |. 50            push    eax                              ; /pContext

004022A5  |. FFB5 24FDFFFF push    dword ptr[ebp-2DC]     ; |hThread  //被释放进程的线程

004022AB |.  C785 E0FDFFFF>mov     dword ptr [ebp-220], 00401E1F    ; |//改写EAX

004022B5  |. FF15 70104000 call    dword ptr[<&KERNEL32.SetThreadC>; \SetThreadContext

 

SetThreadContext=设定指定线程的CONTEXT…

 

如你所见它指向同样的地址0006FBE8

 

查看一下高亮代码这里LOCAL.136 =006Fc98..所以这条指令所做的就是用值00401E1F改写地址006Fc98处的值..

 

然后再调用SetThreadContext…

 

所以所有这些是通过改写被释放进程的CONTEXT结构的EAX值来改变该进程的入口点。

 

检查下边的快照来更好地理解…IDA的命名特性使得它称为了逆向的完美工具。

 

 

这样入口点就改变了我们看看下边将会发生什么..分析下下面将会发生什么

004022C1  |. 56            push    esi                              ; /pBytesWritten

004022C2  |.  6808020000   push    208                             ;|BytesToWrite = 208 (520.)

004022C7  |.  B8A0484000   mov     eax, 004048A0                    ; |UNICODE"C:\Documents and Settings\Administrator\"

004022CC  |. 50            push    eax                              ; |Buffer =>sample.004048A0

004022CD  |. 50            push    eax                              ; |Address =>4048A0

004022CE  |. FFB5 20FDFFFF push    dword ptr[ebp-2E0]              ; |hProcess

004022D4  |. FFD3          call    ebx                              ; \WriteProcessMemory

 

如我解说的hProcess是释放进程的句柄..所以这里WriteProcessMemory函数所做的就是复制样本的原始路径到释放进程。(4048A0包含本执行体的路径)。你将会明白为什么要复制给释放的进程

调用GetCurrentProcessId=这个函数在EAX寄存器返回当前进程的ID

下一步,OpenProcess这个API被调用,传入的参数是当前进程的ID..这意味着OpenProcess试图以PROCESS_ALL_ACCESS权限打开当前执行进程(红色标记1F0FFF = PROCESS_ALL_ACCESS如果一切正确的话OpenProcess将返回本地进程的句柄。

 

下面我们调用了DuplicateHandle…MSDN里解释的很好..读读它

http://msdn.microsoft.com/en-us/library/windows/desktop/ms724251%28v=vs.85%29.aspx

=======================================摘自百度百科==========================================

VC声明

  Windows NT/2000/XP: Includedin Windows NT 3.1 and later.

  Windows 95/98/Me: Includedin Windows 95 and later.

  Header: Declared inWinbase.h; include Windows.h.

  Library: UseKernel32.lib.

  说明:

  BOOL WINAPI DuplicateHandle(

  __in HANDLE hSourceProcessHandle,

  __in HANDLE hSourceHandle,

  __in HANDLE hTargetProcessHandle,

  __out LPHANDLE lpTargetHandle,

  __in DWORD dwDesiredAccess,

  __in BOOL bInheritHandle,

  __in DWORD dwOptions

  );

  hSourceProcessHandle:源进程内核句柄(即负责传递内核对象句柄的进程句柄)hSourceHandle:要传递的内核对象句柄hTargetProcessHandle:目标进程内核句柄lpTargetHandle:接收内核对象句柄的地址(先随便声明一个HANDLE)dwDesiredAccess:TargetHandle句柄使用何种访问掩码(这个掩码是在句柄表中的一项)bInheritHandle:是否拥有继承dwOptions:当设DUPLICATE_SAME_ACCESS时,表示于源的内核对象所有标志一样,此时wDesiredAccess可标志为0

  当设DUPLICATE_CLOSE_SOURCE时,传输完后,关闭源中的内核对象句柄此函数能否成功调用还要看你是否有足够的权限去操作目标进程通常目标进程的内核句柄是利用OpenProcess()得到的HANDLE WINAPI OpenProcess(

  __in DWORD dwDesiredAccess,

  __in BOOL bInheritHandle,

  __in DWORD dwProcessId

  );

  dwDesiredAccess:决定你拥有该进程的操作权限,如果要成功用到则要填PROCESS_ALL_ACCESS或PROCESS_DUP_HANDLE

  bInheritHandle:是否可继承

  dwProcessId:这个ID可在资源管理器中找到,当然,我不提倡在哪里得到,或者你可以通过进程间通信的方法把PID从目标进程传给源进程

  若DuplicateHandle()能成功执行,则利用进程通信把句柄值TargetHandle传给目标进程,让他知道利用该句柄使用内核对象

注意:不要试图在源进程中利用CloseHandle()关闭TargetHandle,因为这个TargetHandle句柄值并不属于源进程的句柄表中的,若错误关闭了,会产生不可预料的结果

=============================================================================

 

然后我们调用了WriteProcessMemory函数..在这个函数里..我们给释放进程传递了用DuplicateHandle复制的实句柄..我们将句柄写在=404AA8(先记下)

 

如果你读了MSDN..那么你应该清楚WriteProcessMemory函数的意图..

 

然后调用了ResumeThread

 

0040231A  |. FFB5 24FDFFFF PUSH [LOCAL.183]      ; /hThread = 00000048   //DroppedProcess

00402320  |. FF15 1C104000 CALL DWORD PTRDS:[<&KERNEL32.ResumeThread>]     

 

所以最后在对释放进程做完必要的改变之后继续执行释放进程然后释放进程开始执行

 

我现在还没执行ResumeThread…所有我想做的就是用Ollydbg附加释放进程..

 

这里有一种方法如何去做..

 

我们得到了释放进程的入口点=401E1Fh

 

所以接下来我们要做的是在无限循环中跟踪释放进程

 

我将使用PUPE Suite..而且改变401e1f开始的两个字节为EBFE(在改写之前记下原始的字节)

我前面有一篇文章使用了同样的方法,如果你还不知道就查看一下吧

https://reverse2learn.wordpress.com/2011/09/01/unprotecting-the-crypter/

 

现在执行挂起的线程。现在释放进程陷入了无线的循环当中..Ollydbg附加上..用原来的字节替换掉EB FE(原来的字节:8B FF)..

 

*我建议从这个点DUMP进程,因为这里包含了父进程对它所做的所有修改.

 

这就是我们能够从同样的进程中得到的所有的东西..在恢复执行释放进程主线程后它关闭了句柄最后退出了

 

=============================================================================================

下面是我从IDAF5得来的伪函数

signedint __cdecl sub_402228()

{

  DWORD v1; // eax@1

  HANDLE v2; // eax@1

  HANDLE v3; // ST14_4@1

  void *v4; // ST10_4@1

  HANDLE v5; // eax@1

  HANDLE v6; // eax@1

  char Dst; // [sp+Ch] [bp-328h]@1

  HANDLE hObject; // [sp+50h] [bp-2E4h]@1

  struct _PROCESS_INFORMATIONProcessInformation; // [sp+54h] [bp-2E0h]@1

  CONTEXT Context; // [sp+64h] [bp-2D0h]@1

  unsigned int v11; // [sp+330h] [bp-4h]@1

  int v12; // [sp+334h] [bp+0h]@1

 

  v11 = (unsigned int)&v12 ^ dword_404000;

  ProcessInformation.hProcess = 0;

  ProcessInformation.hThread = 0;

  ProcessInformation.dwProcessId = 0;

  ProcessInformation.dwThreadId = 0;

  memset(&Dst, 0, 0x44u);

  CreateProcessW(&NewFileName, 0, 0, 0, 0,4u, 0, 0, (LPSTARTUPINFOW)&Dst, &ProcessInformation);

  Context.ContextFlags = 65543;

  GetThreadContext(ProcessInformation.hThread,&Context);

  Context.Eax = (DWORD)sub_401E1F;

  SetThreadContext(ProcessInformation.hThread,&Context);

 WriteProcessMemory(ProcessInformation.hProcess, &Filename,&Filename, 520u, 0);

  v1 = GetCurrentProcessId();

  v2 = OpenProcess(0x1F0FFFu, 0, v1);

  v3 = ProcessInformation.hProcess;

  hObject = v2;

  v4 = v2;

  v5 = GetCurrentProcess();

  DuplicateHandle(v5, v4, v3, &hHandle, 0,0, 2u);

 WriteProcessMemory(ProcessInformation.hProcess, &hHandle,&hHandle, 4u, 0);

  ResumeThread(ProcessInformation.hThread);

  CloseHandle(hObject);

  CloseHandle(ProcessInformation.hProcess);

  CloseHandle(ProcessInformation.hThread);

  v6 = GetCurrentProcess();

  TerminateProcess(v6, 0);

  return 1;

}

=============================================================================================

 

看看释放进程的汇编代码

 

我们看到了调用Memset函数..Memset之后我们又看到了很有趣的函数

 

WaitForSingleObject(hObject,TimeOut)

 

hObject= 404aa8(记得第二次调用WriteProcessMemery函数,那里我们将从DumlicateHandle函数复制过来的句柄写在了释放进程的404aa8地址处)

TimeOut=INFINETE//等待对象被通知

 

完了以后用CloseHandle关闭句柄

 

然后调用了DeleteFileW..传递的文件路径是样本进程的路径

 

*回忆一下,我们第一次掉用WriteProcessMemory函数是传递的参数是样本的路径

 

所以我认为现在清楚了如何实现自我删除(熔化特性)/删除文件…

 

释放进程等待样本进程的事件当它被通知了以后,它就继续执行然后删除那个文件。

 

因此第一部分到此结束在下一部分我们将要分析释放进程(用Ollydbg附加它再恢复原来的字节之后,我对它进行了DUMP)

 

我殷切希望能得到你的反馈。你可以给我发Email或者在我的博客上评论

 

博客:https://reverse2learn.wordpress.com/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Email :arunpreet90@gmail.com