首页
社区
课程
招聘
[原创]利用Powershell击败.NET恶意样本
发表于: 2023-8-4 09:15 12963

[原创]利用Powershell击败.NET恶意样本

2023-8-4 09:15
12963

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

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

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

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

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

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

享受战果:

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

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

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

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

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

在具体说明这个优雅的方式如何实现之前,我们不得不来看一看"dnlib"这个相当nice项目,它让这种优雅的方式成为现实,让我们可以摆脱正则表达式,同时还能够直接修改.NET程序,使我们的分析过程变得更加丝滑。
可以看到,该项目的功能是"读写.NET文件的程序集和模块",具体的用法把"readme"看一看,例子看一看,再配合ps1的Get-Member,我们就能动手了。

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



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

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

$AgentTesla = [System.Reflection.Assembly]::LoadFile("C:\Users\g0mx\Desktop\RCData1.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"
}
$AgentTesla = [System.Reflection.Assembly]::LoadFile("C:\Users\g0mx\Desktop\RCData1.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"
}
$assemblyPath = "C:\Users\g0mx\Desktop\RCData1.bin"
$methodToken = "0x0600026F"
 
$filePath = "C:\Users\g0mx\Desktop\AT.il"
$pattern = "\s+(IL_\w+):\s+ldc\.i4\s+([0-9a-fx]+)\r?\n\s+IL_\w+:\s+call\s+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:\Users\g0mx\Desktop\Output.csv" -NoTypeInformation
$results | Out-GridView
$assemblyPath = "C:\Users\g0mx\Desktop\RCData1.bin"
$methodToken = "0x0600026F"
 
$filePath = "C:\Users\g0mx\Desktop\AT.il"
$pattern = "\s+(IL_\w+):\s+ldc\.i4\s+([0-9a-fx]+)\r?\n\s+IL_\w+:\s+call\s+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:\Users\g0mx\Desktop\Output.csv" -NoTypeInformation
$results | Out-GridView
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)
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)
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
}
foreach($method in $methods)
{

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-8-4 17:41 被g0mx编辑 ,原因:
收藏
免费 7
支持
分享
最新回复 (4)
雪    币: 533
活跃值: (1028)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2

给安服仔和刚入行的不久的提个醒:

asm.loadfile……
不在隔离、断网、虚拟机里面跑,就等着被通报开除吧

最后于 2023-8-4 15:22 被btmanbtman编辑 ,原因:
2023-8-4 12:01
0
雪    币: 2034
活跃值: (3561)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
btmanbtman asm.loadfile…… 不在隔离、断网、虚拟机里面跑,就等着被通报开除吧[em_2]
本来就要在虚拟机里面啊,“.NET恶意样本”,不太理解为什么你会有在物理机里跑的想法
2023-8-4 13:54
0
雪    币: 533
活跃值: (1028)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
g0mx 本来就要在虚拟机里面啊,“.NET恶意样本”,不太理解为什么你会有在物理机里跑的想法[em_38]

编辑了,加了个说明,我是想给那些没人带的工具人小孩提个醒。
每年HVV都要出类似的事,甚至几个月前某威胁情报也有个例子
认为是常识的东西,它真就有人犯错,甚至不止一次

最后于 2023-8-4 15:27 被btmanbtman编辑 ,原因:
2023-8-4 15:26
0
雪    币: 2034
活跃值: (3561)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
5
btmanbtman g0mx 本来就要在虚拟机里面啊,“.NET恶意样本”,不太理解为什么你会有在物理机里跑的想法[em_38] 编辑了,加了个说明,我是想给那些没 ...
OK了,在最前面加了提醒
2023-8-4 17:42
0
游客
登录 | 注册 方可回帖
返回
//