利用Powershell击败.NET恶意样本

admin 2023年8月17日15:47:19评论17 views字数 6253阅读20分50秒阅读模式

如想复现本项目,请在虚拟机中操作!





概述


".NET"因其原生于Windows操作系统,同时具有极高的开发效率,以及大量混淆手段(便于免杀),使其成为了众多Windows恶意软件开发者的首选语言,且其中绝大部分都为窃密木马,本文将以市面上非常常见的"AgnetTesla"为例,详细介绍如何利用powershell处理.NET程序中的加密函数,以减轻样本分析人员的工作量,开心干活,愉快狩猎!





语言 & 环境


◆代码 & 样本:https://github.com/g0mxxm/Malware_Analysis/tree/main/.NET

◆编辑器: VSCode

◆语言: ps1 & C#,本文选择了ps1

◆工具 & 依赖: ildasm | dnlib


PS: 本文将不再论述那些基础的.NET恶意样本分析流程,因为各位分析师都有自己的分析习惯,大家按自己喜欢的方法就好,此外本文的去混淆方法也仅是抛砖引玉,大家有什么常用的方法和改进意见,欢迎一起讨论!!!





原理 & 实现


通常来讲,字符串的存储方式无非两种,一种是以变量方式存储,而另一种则是将其直接存放在内存中。且这两种方式常常会同时出现,因而我们需要对这两种方式都进行相应的处理。


存储在变量中


下图便是"存储在变量中"的情况:


利用Powershell击败.NET恶意样本

面对这种情况,可以进行如下操作:

◆先反射加载对应的.NET程序
◆再利用GetType方法根据类名检索对应的Type对象
◆再利用GetFields来获取目标类中的所有对象,同时可以利用BingdingFlags来限制对象的属性
◆随后用GetValue来获取每个field中的所有字段值。


$AgentTesla = [System.Reflection.Assembly]::LoadFile("C:Usersg0mxDesktopRCData1.bin")
$class = $AgentTesla.GetType("A.b")
$fields = $class.GetFields([System.Reflection.BindingFlags]::Static -bor [System.Reflection.BindingFlags]::Public) | Where-Object {$_.MetadataToken -in (0x04000005..0x0400002B)}
foreach ($field in $fields) {
$fieldValue = $field.GetValue($null)
$fieldValueString = [System.Convert]::ToString($fieldValue)
Write-Host "The decrypted value is: $fieldValueString"
}


享受战果:

◆"hxxps://api.ipify.org",常常被用于获取受害者IP;
◆"[email protected]",该邮箱地址很可能被用于接收受害者信息;
◆"PrdPg.exe",大概率是创建了一个名字是这个的文件。其有可能是第二阶段文件,也可能是自拷贝用于持久化操作;
◆"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0",用于服务端识别的"UA"标识。


利用Powershell击败.NET恶意样本


存储在内存中


该样本中,又包含了大量的存储在内存中的字符串,如下图所示:


利用Powershell击败.NET恶意样本

面对这种情况,往往我们会选择进行动态调试,但是当遇到如此大量的调用时,会浪费我们大量的时间和经历,才能找到我们感兴趣的地方,本人也被此困扰,因而急需一种自动化的解决方案。


利用Powershell击败.NET恶意样本

通过查看IL,可以发现这种情况下存在相同的IL切片。随后,又把微软官方文档和官方论坛翻了又翻,本人决定通过将IL和ps1相结合的方式,来实现自动化处理。


步骤如下:


◆通过ildasm来获取.NET程序的全部IL


利用Powershell击败.NET恶意样本


◆找到对应的调用切片,并保存所传入的立即数


利用Powershell击败.NET恶意样本


◆利用ps1实现

  • 利用正则找到所有目标切片

  • 将"ldc.i4"对应的立即数进行存储

  • 调用"String A.V::A(int32)"

  • 保存到csv,并输出

  • $assemblyPath = "C:Usersg0mxDesktopRCData1.bin"
    $methodToken = "0x0600026F"

    $filePath = "C:Usersg0mxDesktopAT.il"
    $pattern = "s+(IL_w+):s+ldc.i4s+([0-9a-fx]+)r?ns+IL_w+:s+calls+string A.V::A(int32)"

    try {
    $assembly = [System.Reflection.Assembly]::LoadFile($assemblyPath)
    }
    catch {
    Write-Host "Failed to load assembly: $_"
    return
    }
    try {
    $method = $assembly.ManifestModule.ResolveMethod([int]$methodToken)
    }
    catch {
    Write-Host "Failed to resolve method: $_"
    return
    }

    $matches = @()
    Get-Content $filePath -Raw | Select-String -Pattern $pattern -AllMatches | ForEach-Object {
    $_.Matches | ForEach-Object {
    $key = $_.Groups[1].Value
    $value = $_.Groups[2].Value
    $matches += [PSCustomObject]@{Key = $key; Value = $value}
    }
    }
    # $matches

    $results = $matches | ForEach-Object {
    $value = [int]$_.Value
    $hexValue = "0x{0:x}" -f $value
    $string = $method.invoke($null, $value)
    [PSCustomObject]@{
    Value = $hexValue
    String = $string
    }
    }
    $results | Export-Csv -Path "C:Usersg0mxDesktopOutput.csv" -NoTypeInformation
    $results | Out-GridView


◆享受战果


记录受害者信息


利用Powershell击败.NET恶意样本


键盘记录器


利用Powershell击败.NET恶意样本


窃密


利用Powershell击败.NET恶意样本


还有很多此处就不再一一列举了,对应的CSV文件本人也会发出来,到时大家有兴趣的话慢慢看就好了。到这也能非常轻松的看出来这是一个窃密样本了。


小结

以上两种方法,解决了我们在开头所提出了"需要进行大量重复操作"的痛点,但是这两种方法真的完美吗,真的优雅吗?答案一定是否定的。首先,其需要将"存储在变量中"和"存储在内存中"这两种情况分开处理,并且分析的时候需要根据所保存的立即数去定位相应的方法,再进行详细分析; 其次,第二个脚本中用了一个非常非常冗长的正则表达式,其一,虽然正则表达式非常强大,但他经常给人一种非常杂乱,并且难以阅读的感觉,对于本人而言能不用就不用,因为在我眼里这个手法太不优雅了,甚至有点丑陋; 其二,本人这个正则水平着实不高,为了匹配上上文中小小的代码切片,写了快半小时。基于上面所说的两点问题,下面我们来说一说优雅的解决方案。


优雅的方式


dnlib


在具体说明这个优雅的方式如何实现之前,我们不得不来看一看"dnlib"(https://github.com/0xd4d/dnlib)这个相当nice项目,它让这种优雅的方式成为现实,让我们可以摆脱正则表达式,同时还能够直接修改.NET程序,使我们的分析过程变得更加丝滑。


可以看到,该项目的功能是"读写.NET文件的程序集和模块",具体的用法把"readme"看一看,例子看一看,再配合ps1的Get-Member,我们就能动手了。


利用Powershell击败.NET恶意样本


原理 & 实现


◆载入dnlib & .NET程序

通过反射加载,载入dnlib.dll; 同时,再以两种方式载入.NET文件。通过反射加载可以实现,通过获取到的参数直接调用相应方法; 通过dnlib载入可以实现,寻找目标方法和所需参数。


利用Powershell击败.NET恶意样本


◆寻找目标方法

通过dnlib给我们的"GetTypes()",我们可以获取到目标.NET文件的中的所有方法,随后我们遍历所有方法,根据目标方法特点找到,对应的方法。该样本中的目标方法已在上文中提及,可以看到其仅包含一个参数,类型为"System.Int32",且其返回值类型为"System.String"。


function FindDecryptMethod($methods)
{
foreach($method in $methods)
{
if (-not $method.HasBody){continue}
if ($method.Parameters.Count -eq 1 -and $method.Parameters[0].Type.FullName -eq "System.Int32" -and $method.ReturnType.FullName -eq "System.String")
{
return $method
}
}
return $null
}

$methods = $module_defmd.GetTypes().foreach{$_.Methods}
$decrypt_method = FindDecryptMethod -methods $methods
$Global:remove_method = @($decrypt_method)


通过这些简单的特征,我们找到了想要的方法:


利用Powershell击败.NET恶意样本


◆找到目标方法的所有交叉引用 & patch IL


再次遍历所有方法,并在他们的IL中找到所有"call $decrypt_method",而这次我们将不再需要丑陋的正则表达式,可以借助dnlib提供的"MethodBody.Instructions",通过名称实现全局搜索。


每次找到目标方法后,我们将ldc.i4所对应的立即数保存下来,再获取目标方法的MDToken,用反射注入提供的接口,将获取到的立即数传入,随后invoke目标方法,我们就能得到解密后的字符串。

利用Powershell击败.NET恶意样本

利用Powershell击败.NET恶意样本

随后修改相应的"调用IL",将红框中这类方法调用直接替换为解密后得到的字符串。修改非常简单,我们只需将对应的IL删去一行,并将另一行替换为"Ldstr $decrypted_string"即可。

利用Powershell击败.NET恶意样本

foreach($method in $methods){    if (-not $method.HasBody){continue}    foreach($instr in $method.MethodBody.Instructions.ToArray())    {        if ($instr.OpCode.Name -like "call" -and $instr.Operand -eq $decrypt_method)        {            $index = $method.MethodBody.Instructions.IndexOf($instr)            $para_1_instr = $method.MethodBody.Instructions[$index - 1]            if (-not $para_1)            {                Write-Host "Someting went wrong, para was not found!" -ForegroundColor Red;                Exit            }            $decrypted_string = $module_refl.ResolveMethod($instr.Operand.MDToken.ToInt32()).Invoke($null, $para_1_instr.Operand)            $method.MethodBody.Instructions[$index - 1].OpCode = [dnlib.DotNet.Emit.OpCodes]::Ldstr            $method.MethodBody.Instructions[$index - 1].Operand = $decrypted_string            $method.MethodBody.Instructions.RemoveAt($index)        }    }    $method.MethodBody.UpdateInstructionOffsets() | Out-Null}


成果

随便举两个例子,可以看到,我们想要的效果已经实现了,这样分析起来就非常的流畅并且舒适了,最关键的是还可以正常进行动态调试。


利用Powershell击败.NET恶意样本

利用Powershell击败.NET恶意样本

利用Powershell击败.NET恶意样本

但这真的完美了吗,还没有!我们可以更近一步,我们可以将"ldsfld $VAR",即"b.R"以及"b.s"这种变量直接替换为字符串,这样可以更加方便我们阅读,也同时解决了上文中提到的"以变量形式存储"和"以内存形式存储"这两种情况,这样才是一个趋近于完美的解决方案。


利用Powershell击败.NET恶意样本


利用Powershell击败.NET恶意样本


◆找到目标赋值的所有交叉引用 & patch IL
其中原理和上一步相同,本文便不再过多赘述,直接查看下面的ps1代码即可。


foreach($method in $methods)
{
if (-not $method.HasBody){continue}
foreach($instr in $method.MethodBody.Instructions.ToArray())
{
if (-not ($instr.OpCode.Name -like "ldsfld" -and $instr.Operand.IsField)){continue}
$index = $method.MethodBody.Instructions.IndexOf($instr)
$field = $instr.Operand.FullName
if ($field | Select-String -Pattern "A.b")
{
$str = $module_refl.ResolveField($instr.Operand.MDToken.ToInt32()).GetValue($null)
if (-not $str){continue}
if (-not ($str.GetType().Name -eq "String")){continue}
$method.MethodBody.Instructions[$index].OpCode = [dnlib.DotNet.Emit.OpCodes]::Ldstr
$method.MethodBody.Instructions[$index].Operand = $str
}
}
$method.MethodBody.UpdateInstructionOffsets() | Out-Null
}


成果


再来看效果,Perfect!!!,这就是我们想要的,并且还能正常调试,完美收工!


利用Powershell击败.NET恶意样本
利用Powershell击败.NET恶意样本





总结


本文提供了三种利用powershell自动化处理.NET样本混淆的方案,可以极大的提高分析效率,降低重复工作,希望大家有所收获,读的开心,也欢迎大家讨论和分享自己平时所用的分析方法和技巧。最后,祝大家生活愉快!!!




利用Powershell击败.NET恶意样本


看雪ID:g0mx

https://bbs.kanxue.com/user-home-944577.htm

*本文为看雪论坛优秀文章,由 g0mx 原创,转载请注明来自看雪社区

利用Powershell击败.NET恶意样本

# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复


利用Powershell击败.NET恶意样本


利用Powershell击败.NET恶意样本

球分享

利用Powershell击败.NET恶意样本

球点赞

利用Powershell击败.NET恶意样本

球在看

原文始发于微信公众号(看雪学苑):利用Powershell击败.NET恶意样本

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年8月17日15:47:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   利用Powershell击败.NET恶意样本http://cn-sec.com/archives/1961920.html

发表评论

匿名网友 填写信息