0x00 前言
最近做了一道18年swpuctf的题,分析了一个病毒,正巧都用到了傀儡进程,就想着把傀儡进程学习一下。本文权当个人的学习总结了一些网上的文章,如有错误,还请路过的大佬斧正。
0x01 SWPUCTF -- GAME
进入main函数
先获取当前目录,再拼上GAME.EXE
进入sub_4011D0
首先寻找资源,做准备工作,进入sub_4012C0
1)检测PE结构
2) CreateProcessA 创建进程
3)GetThreadContext 得到进程上下文信息,用于下文计算基地址
4)sub_4016F0
到ntdll.dll里找到NtUnmapViewOfSection函数
5)VirtualAllocEx 跨进程,在目标进程申请空间
6)写入文件
7)SetThreadContext 恢复现场
8) 运行傀儡进程
我们来找找注入的程序
把GAME.EXE载入到010editor里,搜索"MZ"
dump出来,就是刚刚注入到傀儡进程的程序了。
我们把它命名为 game2.exe
载到IDA里分析
发现是D3D绘制
之后的解题与本文关系不大,这里直接把官方的WP搬运来了https://www.anquanke.com/post/id/168338#h3-16
通过字符串[Enter]可以跟踪到获取输入以及返回上一层的地
这里用了’ – ’符来分割string,然后保存到vector中。并且判断vector中string的个数是否是4以及每一个string的长度是否是4
接着传入前面两部分进行一次加密,可以根据常量识别出这是DES算法,这里把DES的subkeys进行了一次移位,并且修改了sbox3开头的5个字节,然后把结尾结果减去0x10,之后再进行一个简单的方程check。解方程可以得到另外两部分是个常量。
DES部分可以网上找个标准的DES把这几部分改一下就能解出FLAG:HOPE-UCAN-GOOD-GAME
小结
本题的sub_4012C0,就是在进行傀儡进程的编写,有必要仔细的说说傀儡进程
0x02 傀儡进程
文章: https://blog.csdn.net/darcy_123/article/details/102532411
首先使用CreateProcess传入CREATE_SUSPENDED
创建一个挂起的进程,以下称为傀儡进程,然后使用GetThreadContext读取傀儡进程的上下文信息,通过DWORD
指针指向CONTEXT的EBX,DWORD + 8 字节可以读取到傀儡进程的基地址,然后计算傀儡进程的镜像大小
然后把自己的数据读入到傀儡进程内,设置CONTEXT上下文入口点信息EAX,并恢复进程主线程
上文已经写得很详细了,个人觉得重要的点
1)在CreateProcess时,注意环境问题 lpCurrentDirectory
BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
2) 计算基地址
context.Ebx + 8 = 基地址,因此从context.Ebx + 8的地址读取4字节的内容并转化为DWORD类型,即是进程加载的基地址。
3) VirtualAllocEx时的权限问题
4)VirtualAllocEx重定位的问题
一般情况下,在写入傀儡进程对应的文件按照申请空间的首地址作为基地址进行“重定位”,这样才能保证傀儡进程的正常运行。为了避免这一步操作,可以以傀儡进程PE文件头部的建议加载基地址作为VirtualAllocEx的lpAddress参数,申请与之对应的内存空间,然后以此地址作为基地址将傀儡进程写入目标程序,就不会存在重定位问题。关于“重定位”的原理可以自行网络查找相关资料
我们来看看实际上病毒用到的傀儡进程
0x03 病毒分析
1.virusTotal
https://www.virustotal.com/gui/file/32db88982a0d0f92804c4c53ffd8555935871e23b35a9d362e037353cb6b44c5/details
MD5 ba46b18f05ab2b24df26e343dd32946b
SHA-1 34f07e131d4f147af96d262eee761582c6f0b1a4
SHA-256 32db88982a0d0f92804c4c53ffd8555935871e23b35a9d362e037353cb6b44c5
Vhash bf09954b6ff12217cd0df0f93c7488e4
SSDEEP 24576:yij8jlUoiuFhypnWmUOJ4H0zMY/lspoBbRzRrT9MUF5jawb:yDhUoMFUcvo6FlrT2wb
File type RAR
Magic RAR archive data, v1d, os: Win32
File size 893.13 KB (914570 bytes)
环境win7 x64
是一个.rar文件
在虚拟机里改后缀为.rar
解压:
可以简单的发现一个伪装成文件夹的.exe
2.对18XXXXXXXXXXXX.exe分析
查壳
没有
然后GUI,有界面的程序
用LoadPE去看一下区段,资源等信息
看一下重定位
和资源
关注一下导入表
0x0311 "WriteFile"
0x0038 "CreateFileA"
0x005F "DeleteFileA"
0x0240 "ReadFile"
0x0128 "GetFileSize"
0x020D "OpenFile"
0x0298 "SetFilePointer"
0x0048 "CreateProcessA
0x002B "CopyFileA"
0x00CC "GetACP"
可能会新建新的文件
IDA详细分析
先搜字符串
pavfnsvr.exe
sfctlcom.exe
后来看别人的分析,这两个文件是安全程序
IDA开始分析
开始时,一些准备工作,之后检测版本号
申请堆,
释放后,把申请的堆的句柄,放到40E968的位置
调用4057AE
检测文件的PE结构
主要是检测PE结构,然后验证目录项要比14项多
之后回到start函数 sub_40624F
检测的就是下图红框圈出来的那两个
之后:
与SEH的高级使用有关
通过GetCommandLineA获取当前文件的路径
"C:\Users\Administrator\Desktop\bingdu\1\bingdu\1\18xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.EXE" 的地址存到了410268(00271F50)
sub_408D2C函数获取环境信息
获取"ALLUSERSPROFILE=C:\ProgramData",
在将宽字符转成多字节
之后进入
V13为0x271fa6 ..(\洳..C.:.\.W.i.n.d.o.w.s.\.s.y.s.t.e.m.3.2.;的指针
V6为10
进入函数:
首先会获取当前进程已加载模块的文件的完整路径
然后加载到缓冲区
把当前的目录传入402120
进入402120
会调用两次401450
看一下401450
rep stosd:
在网上查了相关资料显示:
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
rep指令的目的是重复其上面的指令.ECX的值是重复的次数.
STOS指令的作用是将eax中的值拷贝到ES:EDI指向的地址.
首先获取当前的路径
打开文件读取
获取windows的目录
在源目录生成.bak文件
生成文件夹
把文件备份到"C:\Windows\Help\18xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.bak"
在把原来的.bak删除
进入402680
首先找到生成的空文件夹
在内存里读取文件 1.jpg
将1.jpg和.archd作为参数进入4046A0
拼出路径
将j.jpg换成1.liz
按照路径写入文件,再将1.liz换成1.jpg
循环7次生成7张图片
第8次,生成Thumbs.db文件
之后把C:/Windows/Help里的备份删除
进入第二个401450
先找到程序获取临时目录文件路径
然后拼出路径
"C:\Users\ADMINI~1\AppData\Local\Temp\\rat.EXE"
如果存在直接打开,不存在就创建
之后createprocess
创建进程,运行rat.EXE
先拼出来byeyou.tmp的路径
之后 遍历所有进程,保证没有sftlcom.exe与pavfnsur.exe
把原文件(18XXXX.EXE)移动到byeyou.tmp
分析 rat.exe
没有壳
自启动(嫖的图)
注册表相关(嫖的图)
去找ctfmon.exe的目录
拼路径 将rat.exe copy成ctfmon.exe
之后寻找资源
拼出"C:\Windows\system32\alg.exe"
作为参数扔到sub_4016D0运行
进入傀儡进程函数
创建挂起进程 && 保存现场,收集信息
之后像上述ctf一样找到注入到傀儡进程的.exe
Dump出来起名为abc.def
分析 abc.def
看一下字符串
进入WinMain函数
首先进入sub_405BD0
先解密字符串从advapt32.dl,调用需要的函数
提权
之后进入 sub_401720解密接下来需要的字符串
回到WinMain
从KERNEL32.DLL找需要的函数
之后进入sub_405960
进入远控函数
首先登入一个网站
保存返回的数据
之后就没办法动调了
把返回的数据按照自己定义的字符切割
进入405DB0函数,依旧是解密
接着分配套接口,连上
进入403190
开始从.Dll里提取需要的函数
1.
2.
3.
4.
5&6
之后传入域名参数
获得本机的ip
能不能ping得通
检测系统版本
CPU信息
操作系统版本
获取磁盘信息
对文件操作
创建,读
删除,重命名
移动
复制
列出进程 &&杀死进程
卸载
关机重启
截屏
接着还会在传入其他的两个域名
cn.fetftp.nu "rt.softseek.org"
进入403190控制函数
0x04 代码实现
https://blog.csdn.net/superchickenchicken/article/details/102552787?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.nonecase
稍微修改了点去做别的用了
#include<stdio.h>
#include<windows.h>
#include<winbase.h>
struct _PROCESS_INFORMATION ProcessInfomation;
int main()
{
LPVOID pAlloc1;
LPVOID pAlloc2;
HANDLE hfile;
PIMAGE_NT_HEADERS pPeHeader;
PIMAGE_SECTION_HEADER pSectionHeader;
int lastError, ReadInfo = 0;
DWORD BytesRead = 0;
CONTEXT Context = { 0 };
Context.ContextFlags = CONTEXT_ALL;
char lpName[260];
//准备路径
char Patch[] = "C:\\play.exe";//注入傀儡进程的.exe的路径
LPCSTR p = (const char*)Patch;
//转移文件
//以挂起的方式创建傀儡进程,并获取进程基址
STARTUPINFOA StartupInfo = { 0 };
PROCESS_INFORMATION ProcessInfomation = { 0 };
StartupInfo.cb = sizeof(StartupInfo);
GetModuleFileName(0, lpName, 520);
if (!CreateProcess(lpName, NULL, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &StartupInfo, &ProcessInfomation))。
{
lastError = GetLastError();
printf("CreateProcess fail LastError:%d\n", lastError);
};
if (!GetThreadContext(ProcessInfomation.hThread, &Context))
{
lastError = GetLastError();
printf("GetThreadContext fail LastError:%d\n", lastError);
};
if (!ReadProcessMemory(ProcessInfomation.hProcess, (LPCVOID)(Context.Ebx + 0x8), &ReadInfo, 4, 0))
{
lastError = GetLastError();
printf("GetProcessImageBase fail LastError:%d\n", lastError);
//printf("%d\n", dwIO);
};
printf("ProcessImageBase:address 0x%x\n", ReadInfo);
// 把准备注入到傀儡进程的程序读进内存
hfile = CreateFileA(p, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
//lastError = GetLastError();
//printf("%d\n", lastError);
pAlloc1 = VirtualAlloc(NULL, 0x70000, 0x3000, 4);//sucessful
//printf("%d", pAlloc1);
ReadFile(hfile, pAlloc1, 0x70000, &BytesRead, 0);
pPeHeader = (PIMAGE_NT_HEADERS)((PBYTE)pAlloc1 + ((PIMAGE_DOS_HEADER)pAlloc1)->e_lfanew);//sucessful
//printf("%d", pPeHeader);
pSectionHeader = (IMAGE_SECTION_HEADER*)((char*)&pPeHeader->OptionalHeader + pPeHeader->FileHeader.SizeOfOptionalHeader);//sucessful
//printf("%d", pSectionHeader);
//向傀儡进程申请内存空间
SetLastError(0);
pAlloc2 = VirtualAllocEx(ProcessInfomation.hProcess, (LPVOID)pPeHeader->OptionalHeader.ImageBase, pPeHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, 64);
//printf("%d\n", pAlloc2);
if (!pAlloc2)
{
lastError = GetLastError();
printf("VirtualAllocEx fail LastError:%d\n", lastError);
TerminateProcess(ProcessInfomation.hProcess, 0);
return 0;
}
printf("AllocExBase: %x\n", pAlloc2);
//写入PE头
if (WriteProcessMemory(ProcessInfomation.hProcess, pAlloc2, pAlloc1, pPeHeader->OptionalHeader.SizeOfHeaders, 0))
{
printf("write PeHeader Success !\n");
}
//写入节表,分节表写入会使得程序展开在进程中
int NumofSection = pPeHeader->FileHeader.NumberOfSections;
for (int i = 0;i < NumofSection;i++)
{
LPVOID pAllocRawAddressSection = (char*)pAlloc1 + pSectionHeader->PointerToRawData;
LPVOID pAllocVirtualAddressSection = (char*)pPeHeader->OptionalHeader.ImageBase + pSectionHeader->VirtualAddress;
if (WriteProcessMemory(ProcessInfomation.hProcess, pAllocVirtualAddressSection, pAllocRawAddressSection, pSectionHeader->SizeOfRawData, 0))
{
printf("write the %d section success !\n", i + 1);
}
else
{
lastError = GetLastError();
printf("write the %d section fail ! lastError:%d\n", i + 1, lastError);
}
pSectionHeader++;
}
//设置傀儡进程的进程基址0x400000
if (WriteProcessMemory(ProcessInfomation.hProcess, (char*)Context.Ebx + 8, &pPeHeader->OptionalHeader.ImageBase, 4, 0))
{
printf("set Process ImageBase is 0x400000 success !\n");
}
//设置傀儡进程的进程OEP 为注入程序的OEP
Context.Eax = pPeHeader->OptionalHeader.ImageBase + pPeHeader->OptionalHeader.AddressOfEntryPoint;//设置入口点地址 一定别忘了加基址
if (SetThreadContext(ProcessInfomation.hThread, &Context))
{
printf("set Thread Context success !\n");
}
//恢复线程运行
if (ResumeThread(ProcessInfomation.hThread) != (DWORD)-1)
{
lastError = GetLastError();
printf("Resume Thread success ! \n");
}
system("pause");
return 0;
}
0x04总结
emmmm不知道说啥,溜了