热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

入侵后阶段:如何调整CLR到所需运行时状态以防止程序集退出

 一、概述在MDSec,经常需要开发定制化的入侵后(Post-exploitation)工具以满足实际需求。这一点对于红队来说也不例外,红队可能经常需要调整用于信息收集和横向移动等任务的技术,以适应目

 

一、概述

在MDSec,经常需要开发定制化的入侵后(Post-exploitation)工具以满足实际需求。这一点对于红队来说也不例外,红队可能经常需要调整用于信息收集和横向移动等任务的技术,以适应目标环境。

我们接触到的大多数入侵后工具都是使用C#语言开发的,例如使用Cobalt Strike的execute-assembly功能的工具。在之前的文章中,我们曾经讨论过这个功能的一些局限性。为了解决这些问题,我们花费了一些时间来创建自己的入侵后工具,该工具允许执行.NET程序集,具有自定义的inproc-execute-assembly扩展,从而在进程中执行CLR,增强在主机上的隐蔽性。

通过使用自定义的CLR harness,我们可以将其调整到最适合当前场景的状态,比如可以禁用System.Management.AutomationTracing.PSEtwLogProviderAmsiUtils之类的功能。在对我们的CLR harness进行测试的过程中,由于一个程序集在信标进程中运行,从而导致信标停止响应。调查显示,该程序集通过调用System.Environment.Exit以错误状态退出,并终止了信标进程。在这篇文章中,我们将主要分析如何将CLR调整到所需运行时状态,以防止程序集退出。希望这篇文章能够对尝试创建类似工具的读者能够有所帮助。

 

二、研究过程

要复现问题非常简单,在C#中创建一个名为Enviroment.Exit的控制台应用程序,然后从一个简单的本地.NET托管工具调用它,就可以复现该托管应用程序过早退出的情况。

控制台应用程序中包含以下代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PrematureExit
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("About to call Environment.Exit");
Environment.Exit(0);
Console.WriteLine("Survived exit");
}
}
}

运行·该应用程序后,我们可以预料到其结果:

要深入理解当前正在发生的Environment.Exit,可以使用ILSpy(.NET反编译器)并检查相关方法的代码。System.Environment类存在于mscorlib.dll文件中。取决于使用的.NET框架的版本,这个类有多个。

通过反编译CLR v4.0.30319的mscorlib.dll并浏览System命名空间,可以迅速发现指向Environment类和相关的Exit方法:

[SecuritySafeCritical]
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public static void Exit(int exitCode)
{
_Exit(exitCode);
}

Exit似乎是mscorlib程序集内部的本地函数_Exit的轻量级包装器:

[DllImport("QCall", CharSet = CharSet.Unicode)]
[SecurityCritical]
[SuppressUnmanagedCodeSecurity]
internal static extern void _Exit(int exitCode);

我们无法使用ILSpy反编译这个本地方法,因此转向WinDBG,在适当配置了符号后开始进一步的研究。我们在ntdll!NtTerminateProcess上放置了一个断点,以确保可以在进程终止时立即捕获执行,从而可以检查调用栈。

在WinDBG中打开ntdll!NtTerminateProcess二进制文件,放置断点,并允许在调试器按照预期中断后继续执行:

0:000> k
# ChildEBP RetAddr
00 006fec78 77aa145d ntdll!NtTerminateProcess
01 006fed50 76835902 ntdll!RtlExitUserProcess+0x6d
02 006fed64 71bd4dab KERNEL32!ExitProcessImplementation+0x12
03 006fefe4 71bd4f13 mscoreei!RuntimeDesc::ShutdownAllActiveRuntimes+0x34c
04 006feff0 6f4a36ef mscoreei!CLRRuntimeHostInternalImpl::ShutdownAllRuntimesThenExit+0x13
05 006ff028 6f4a365a clr!EEPolicy::ExitProcessViaShim+0x79
06 006ff25c 6f4dc594 clr!SafeExitProcess+0x137
07 006ff26c 6f4dc5db clr!HandleExitProcessHelper+0x63
08 006ff280 6f4efe89 clr!EEPolicy::HandleExitProcess+0x50
09 006ff290 6f91b07b clr!ForceEEShutdown+0x31
0a 006ff2c8 6ea69faf clr!SystemNative::Exit+0x4f
0b 006ff300 0097085d mscorlib_ni!System.Environment.Exit(Int32)$##6000E33+0x43
WARNING: Frame IP not in any known module. Following frames may be wrong.
0c 006ff308 6f32f066 0x97085d
0d 006ff314 6f33231a clr!CallDescrWorkerInternal+0x34
0e 006ff368 6f3385bb clr!CallDescrWorkerWithHandler+0x6b
0f 006ff3d8 6f4db08b clr!MethodDescCallSite::CallTargetWorker+0x16a
10 006ff4fc 6f4db76a clr!RunMain+0x1b3
11 006ff768 6f4db697 clr!Assembly::ExecuteMainMethod+0xf7
12 006ffc4c 6f4db818 clr!SystemDomain::ExecuteMainMethod+0x5ef
13 006ffca4 6f4db93e clr!ExecuteEXE+0x4c
14 006ffce4 6f4d7275 clr!_CorExeMainInternal+0xdc
15 006ffd20 71bcfa84 clr!_CorExeMain+0x4d
16 006ffd58 72f8e80e mscoreei!_CorExeMain+0xd6
17 006ffd68 72f94338 MSCOREE!ShellShim__CorExeMain+0x9e
18 006ffd70 76826359 MSCOREE!_CorExeMain_Exported+0x8
19 006ffd80 77ab7c24 KERNEL32!BaseThreadInitThunk+0x19
1a 006ffddc 77ab7bf4 ntdll!__RtlUserThreadStart+0x2f
1b 006ffdec 00000000 ntdll!_RtlUserThreadStart+0x1b

栈跟踪表明大量函数与CLR关闭和进程退出有关。其中,特别引起我们注意的是mscorlib_ni!System.Environment.Exit(Int32)$##6000E33clr!SystemNative::Exit函数,因为这些代码的执行非常接近我们的JIT代码(0xc)。尽管这些似乎都不是_Exit函数所期望的,但可以合理假设——修补或挂钩这两种方法以防止进程执行似乎是一种可行的方案。

因此,要防止过早终止,最简单的方法似乎是确定上述函数是否是DLL导出,可以通过相应模块中的名称来识别,并使用类似于Microsoft Detours之类的库对其进行修补或挂钩。

为了确定这一点,我们可以使用诸如CFF Explorer之类的程序检查每个模块的导出表,但有一种更简单的方法,就是取消解析符号,并再次获取栈跟踪。如果名称仍然被保留,那么就可以证明它们属于导出函数:

0:000> .sympath "c:\\null"
Symbol search path is: c:\\null
Expanded Symbol search path is: c:\\null
Error: Execute .sympath(+) command attempts to access 'c:\\null' failed: 0x2 - The system cannot find the file specified.
************* Path validation summary **************
Response Time (ms) Location
Error c:\\null
0:000> !reload /f
Reloading current modules
.*** WARNING: Unable to verify checksum for PrematureExit.exe
..............................
************* Symbol Loading Error Summary **************
Module name Error
mscorlib.ni The system cannot find the file specified
clr The system cannot find the file specified
...
You can troubleshoot most symbol related issues by turning on symbol loading diagnostics (!sym noisy) and repeating the command that caused symbols to be loaded.
You should also verify that your symbol search path (.sympath) is correct.
0:000> k
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 006fed50 76835903 ntdll!NtTerminateProcess
01 006fed64 71bd4dab KERNEL32!ExitProcess+0x13
02 006fefe4 71bd4f13 mscoreei!ND_WU1+0x75b
03 006feff0 6f4a36ef mscoreei!ND_WU1+0x8c3
04 006ff028 6f4a365a clr!ClrCreateManagedInstance+0xb06f
05 006ff25c 6f4dc594 clr!ClrCreateManagedInstance+0xafda
06 006ff26c 6f4dc5db clr!CorExeMain+0x5344
07 006ff2c8 6ea69faf clr!CorExeMain+0x538b
08 006ff300 0097085d mscorlib_ni+0xb59faf
09 006ff308 6f32f066 0x97085d
0a 006ff314 6f33231a clr+0xf066
0b 006ff368 6f3385bb clr!LogHelp_TerminateOnAssert+0x93a
0c 006ff3d8 6f4db08b clr!LogHelp_TerminateOnAssert+0x6bdb
0d 006ff4fc 6f4db76a clr!CorExeMain+0x3e3b
0e 006ff768 6f4db697 clr!CorExeMain+0x451a
0f 006ffc4c 6f4db818 clr!CorExeMain+0x4447
10 006ffca4 6f4db93e clr!CorExeMain+0x45c8
11 006ffce4 6f4d7275 clr!CorExeMain+0x46ee
12 006ffd20 71bcfa84 clr!CorExeMain+0x25
13 006ffd58 72f8e80e mscoreei!CorExeMain+0x64
14 006ffd68 72f94338 MSCOREE!DllUnregisterServer+0x14e
15 006ffd80 77ab7c24 MSCOREE!CorExeMain+0x8
16 006ffddc 77ab7bf4 ntdll!RtlGetAppContainerNamedObjectPath+0xe4
17 006ffdec 00000000 ntdll!RtlGetAppContainerNamedObjectPath+0xb4

调用栈中,似乎完全没有在引用的程序代码和最终调用kernel32!ExitProcess之间的名称,这表明没有导出任何我们感兴趣的函数。

我们的备选方法是对ntdll!NtTerminateProcess进行挂钩,但经过进一步分析后,很快发现这并非一个合适的选择,因为当NtTerminateProcess函数被调用时,不可逆地导致.NET CLR关闭,并且从NtTerminateProcess返回的结果导致CLR陷入死锁。其他方法(例如终止关联线程,或引发异常)也能导致类似的结果。

由于缺乏容易的挂钩点,我们可以选择一种替代方法,一种可以在CLR环境中而不是CLR环境外执行的方法,这样就可以得到一种更轻松的方式,使用.NET反射来定位与Environment.Exit相关的方法。

我们对PrematureExit程序进行了以下修改,通过使用反射来定位Exit方法,并获得指向依赖本地代码方法的指针:

static void Main(string[] args)
{
var methods = new List(typeof(Environment).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
var exitMethod = methods.Find((MethodInfo mi) => mi.Name == "Exit");
Console.WriteLine("exitMethod = {0}, param count = {1}", exitMethod.Name, exitMethod.GetParameters().Length);
RuntimeHelpers.PrepareMethod(exitMethod.MethodHandle);
var exitMethodPtr = exitMethod.MethodHandle.GetFunctionPointer();
Console.WriteLine("exitMethodPtr = 0x{0}", exitMethodPtr.ToString("x"));
Console.ReadKey();
Console.WriteLine("About to call Environment.Exit");
Environment.Exit(0);
Console.WriteLine("Survived exit");
}

在上面的代码中,使用RuntimeHelpers.PrepareMethod函数来确保依赖的方法是JIT的(如果需要),对exitMethod.MethodHandle.GetFunctionPointer的调用将检索指向被伪装代码的指针。

观察到以下输出:

然后使用WinDBG,检查exitMethodPtr在指定地址处的代码:

0:005> u 0x6ea69f6c
mscorlib_ni!System.Environment.Exit(Int32)$##6000E33:
6ea69f6c 55 push ebp
6ea69f6d 8bec mov ebp,esp
6ea69f6f 57 push edi
6ea69f70 56 push esi
6ea69f71 53 push ebx
6ea69f72 83ec20 sub esp,20h
6ea69f75 33d2 xor edx,edx
6ea69f77 8955f0 mov dword ptr [ebp-10h],edx

这个方法立即被识别为System.Environment.Exit方法的实现,该方法在之前产生的栈跟踪中观察到。这似乎是为防止应用程序而进行修补的一种理想选择。

实施的修补如下所示:

static void Main(string[] args)
{
var methods = new List(typeof(Environment).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
var exitMethod = methods.Find((MethodInfo mi) => mi.Name == "Exit");
Console.WriteLine("exitMethod = {0}, param count = {1}", exitMethod.Name, exitMethod.GetParameters().Length);
RuntimeHelpers.PrepareMethod(exitMethod.MethodHandle);
var exitMethodPtr = exitMethod.MethodHandle.GetFunctionPointer();
Console.WriteLine("exitMethodPtr = 0x{0}", exitMethodPtr.ToString("x"));
Console.ReadKey();
unsafe
{
IntPtr target = exitMethod.MethodHandle.GetFunctionPointer();
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQueryEx((IntPtr)(-1), target, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != 0)
{
if (mbi.Protect == AllocationProtectEnum.PAGE_EXECUTE_READ)
{
// seems to be executable code
uint flOldProtect;
if (VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (IntPtr)1, (uint)AllocationProtectEnum.PAGE_EXECUTE_READWRITE, out flOldProtect))
{
*(byte*)target = 0xc3; // ret
VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (IntPtr)1, flOldProtect, out flOldProtect);
}
}
}
}
Console.WriteLine("About to call Environment.Exit");
Environment.Exit(0);
Console.WriteLine("Survived exit");
}

上面的代码首先确认获得的函数指针是否位于只读的可执行区域(例如DLL)内,如果是,则将这段代码进行修补,从而实现ret (0xc3),而不再是继续退出进程。

修补后的效果如下所示:

调用Environment.Exit时,该进程不再终止。我们还针对.NET框架的2.0-3.5版本编译和测试了同一个程序集,以确保所需的行为可以得以保留,同时我们还在x86和x64进行了测试。

为了解决从进程中CLR执行时会终止Cobalt Strike信标的这个问题,我们在加载并执行要运行的应用程序的程序集之前,预先加载并执行了新创建的PreventExit程序集。

这样,就具有了提前修补Environment.Exit的效果,以确保随后无法通过这个方法来终止信标。

在尝试终止后再允许程序集继续执行,很可能会导致程序集遇到未处理的错误情况。并引发异常,但是这个异常是由CLR处理,通常会导致用于执行程序集的.NET反射API(_MethodInfo::Invoke_*)返回COM错误HRESULT,从而避免发生崩溃,或出现不稳定的情况。

 

三、总结

修补Environment.Exit方法的过程非常简单,这样就可以防止程序集意外终止宿主进程。在不同的.NET框架版本之间,这种方法似乎都是有效的,并且能够有助于降低在进程中执行.NET入侵后工具的风险。

 

四、参考文章

[1] https://reverseengineering.stackexchange.com/questions/20997/c-changing-method-body-in-runtime
[2] https://github.com/icsharpcode/ILSpy


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文讨论了在Spring 3.1中,数据源未能自动连接到@Configuration类的错误原因,并提供了解决方法。作者发现了错误的原因,并在代码中手动定义了PersistenceAnnotationBeanPostProcessor。作者删除了该定义后,问题得到解决。此外,作者还指出了默认的PersistenceAnnotationBeanPostProcessor的注册方式,并提供了自定义该bean定义的方法。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • PDO MySQL
    PDOMySQL如果文章有成千上万篇,该怎样保存?数据保存有多种方式,比如单机文件、单机数据库(SQLite)、网络数据库(MySQL、MariaDB)等等。根据项目来选择,做We ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • EPPlus绘制刻度线的方法及示例代码
    本文介绍了使用EPPlus绘制刻度线的方法,并提供了示例代码。通过ExcelPackage类和List对象,可以实现在Excel中绘制刻度线的功能。具体的方法和示例代码在文章中进行了详细的介绍和演示。 ... [详细]
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文详细介绍了使用C#实现Word模版打印的方案。包括添加COM引用、新建Word操作类、开启Word进程、加载模版文件等步骤。通过该方案可以实现C#对Word文档的打印功能。 ... [详细]
author-avatar
gete
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有