样本是从看雪上扒下来的,原文作者在动态分析的时候提到这个样本在运行的过程中,会有隐藏进程的操作,分析学习一下。
样本信息
32位程序
查壳:无壳
静态分析
样本进去之后逻辑相对简单:
首先对执行参数进行判断,接着拼接目标进程的路径,之后对资源段的数据进行处理。
通过程序逻辑,不难判断出,通过定位资源段的数据,在进行解密操作,再结合之前获取svchost进程的路径,不难猜测,资源段的数据揭秘出来应该是一个PE文件,在之后的操作里边会对svchost进程进行注入操作。
查看资源段数据解密算法:
把资源段的数据复制出来手动进行解密:
解密代码:(将资源段的数据复制出来,之后进行解密操作,解密结果放在另一个文件里)
#include <stdio.h>
#include <Windows.h>
void main()
{
HANDLE pFile,hFile;
DWORD fileSize;
char *buffer, *tmpBuf;
DWORD dwBytesRead, dwBytesToRead, tmpLen;
pFile = CreateFileA("C:\\Users\\sam\\Desktop\\1.txt", GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING, //打开已存在的文件
FILE_ATTRIBUTE_NORMAL,
NULL);
if (pFile == INVALID_HANDLE_VALUE)
{
printf("open file error!\n");
CloseHandle(pFile);
}
fileSize = GetFileSize(pFile, NULL); //得到文件的大小
buffer = (char *)malloc(fileSize);
memset(buffer, 0, fileSize);
dwBytesToRead = fileSize;
dwBytesRead = 0;
tmpBuf = buffer;
ReadFile(pFile, tmpBuf, dwBytesToRead, &dwBytesRead, NULL);
for (int i = 0; i < fileSize; i++)
{
*(BYTE *)(i + tmpBuf) ^= 0x41;
}
hFile = CreateFileA("C:\\Users\\sam\\Desktop\\2.txt", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS, //打开已存在的文件
FILE_ATTRIBUTE_NORMAL,
NULL);
WriteFile(hFile, tmpBuf, fileSize, &dwBytesRead, NULL);
CloseHandle(pFile);
}
解密结果:(很明显是一个PE文件)
解密之后的PE文件之后再来分析,接下来查看对解密出来PE文件的下一步操作,也是整个进程隐藏的关键部分:
开始还是正常的PE文件的校验以及创建进程,获取线程上下文的操作:
之后将进程映像基址存储到缓冲区中,执行NtUnmapViewOfSection来卸载映像(此处将模块从内存空间卸载,以达到进程隐藏的目的)
之后将整个PE文件循环写入到申请的内存空间
最后,将新的进程映像基址存储修正到线程上下文结构中,之后修正入口点,重新恢复线程,完成程序的创建的隐藏
在分析资源段数据解密出的PE文件之前,还有一个问题需要解决:
Context->Ebx+8 中问什么会存储映像基址
这是因为Context->Ebx
指向的是当前进程的PEB结构,PEB偏移8的地方即为映像基址:
验证过程:
验证Demo:(在进程创建的时候,采用挂起的方式创建)
#include <stdio.h>
#include <WINDOWS.H>
int main(int argc, char* argv[])
{
// 挂起方式创建进程
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
CreateProcessW(L"C:\\Tools\\Editor\\Notepad++\\notepad++.exe",NULL,NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL,(LPSTARTUPINFOW) &si, &pi);
// 获取线程上下文
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &context);
// 获取入口点
DWORD dwEntryPoint = context.Eax;
printf("入口点: %x\n", dwEntryPoint);
// 获取ImageBase
DWORD baseAddress = (context.Ebx + 8);
printf("入口点: %x\n", baseAddress);
}
验证过程:
1、Windbg双机调试
2、进入双机调试环境之后,用OD附加之后调试到进程创建,这个时候可以用Proexp查看进程列表,同时也可以在Windbg中查看系统中近程列表。
在Windbg中执行
!Process 0 0
查看系统进程列表,其中会包含目标进程的PEB信息:
和上边的调试信息相对比,可以相对应。
Context->Ebx -----》PEB
接下来继续分析,解密出来的PE文件
进去之后可以看出来是具有键盘记录功能的文件
之后将相关的击键记录写入到文件里边:
目标文件:
其实释放出来的PE文件,功能相对比较简单。可以直观的看出该PE文件的功能就是实现简单的键盘记录。
样本功能总结:从静态分析的角度来看,该样本在运行之后会从资源段的数据解密释放出一个PE文件(释放到内存空间,文件不落地),释放的目标PE文件会注入到以挂起方式创建的svchost进程(替换),之后采用进程隐藏技术将PE文件从内存中卸载(以此来达到隐藏进程的目的)。
PS:原文作者提到了动态分析的结果,但是这个样本我拿到之后,直接运行会产生报错。
总结:
这个样本里边使用的进程隐藏的技巧如下:
1:以挂起的方式创建进程
2:获取挂起进程的线程上下文
3:执行NtUnmapViewOfSection将映像从内存中卸载
4:申请内存空间之后将PE文件重新写入内存空间
5:之后修正线程入口点以及线程上下文中的映像基址
6: 恢复线程