科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网安全频道使用安全的C#代码跳出CLR沙箱

使用安全的C#代码跳出CLR沙箱

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

使用安全的C#代码跳出CLR沙箱

作者:云舒 2008年10月29日

关键字: CLR沙箱 黑客

  • 评论
  • 分享微博
  • 分享邮件

  起因

  一个老外写了一篇文章<<Writing a .NET Security Exploit POC>>,他给出的代码地址为http://www.frijters.net/TypeSafetyExploitPoC.cs.txt。KJ看到后觉得可以用在SQL Server 2005里面,通过加载C#代码绕过沙箱的限制来获取shell。他测试之后代码无法运行,我就进行了一些初步调试,发现那段代码在内存中暴力搜索WinExec函数的地址时失败,返回为0,因此无法顺利执行。

  这时候刺也加了进来,我们分头同时想到不需要在C#里面做内存搜索,既然可以写入机器码,那么直接写入一段shellcode就可以了。于是我在http://metasploit.com找了一段shellcode,修改原始POC,刺也同时作出了可以成功运行的修改版POC代码。

  晚上KJ拿回家在SQL Server 2005中测试,确实可以,但是限制条件还是比较多,所以基本上是鸡肋了,可以参看他的文章,地址为http://blog.csdn.net/kj021320/archive/2008/09/17/2944371.aspx。但是事情没有完,这个代码不复杂,但是原理是什么?他们都不太熟悉C#,因此责任就落在我身上了,开始了痛苦的虚拟机中运行的代码的调试之旅。至于这段POC代码,我发到我们的邮件列表了,地址是http://groups.google.com/group/ph4nt0m/browse_thread/thread/1ee957d07b33931f/6a0e158cb1deb078?show_docid=6a0e158cb1deb078,在我的回复里面,这里就不写出来了。

  原理

  要了解这个POC的原理,首先需要知道一些CLR的基础。这些没找到文档资料,是我跟踪分析出来的,不一定对,姑妄听之吧。在C#中直接定义数组,是在栈中保存元素的。但是如果数组是一个类中的成员,那么实例化这个类并且初始化这个数组的时候,在栈中仅仅保存一个指针,这个指针指向真正存放元素的地方。被指向的内存,第一个四字节保存着MethodTable,第二个四字节保存着元素的个数,第三个四字节才开始是保存着真实的数组元素。

  在POC里,类Union2中有一个arr数组作为其成员,另一个o成员在后面被一个委托赋值,其实就是一个函数指针,4字节。而拥有u1和u2实例的UnsafeUnion结构体,申明了[StructLayout(LayoutKind.Explicit)]和[FieldOffset(0)],表示u1和u2在内存中的偏移都是0字节。由于u1是两个int,均是4字节,而u2的o成员和arr都是指针,也都是4字节。因此,u1的i元素和u2的o元素在内存中重合,u1的j元素和u2的arr元素在内存中重合,u1和u2本身在内存中也完全融合。

  POC中的del委托指向DummyMethod函数,在CLR语言中无法通过安全的代码来获取函数指针,因此只能通过将del赋值给object o来间接的传递函数地址。最终通过修改int型j的值,间接的修改了arr指向的地址,将arr指向了DummyMethod函数中。最终导致修改arr,其实是修改了函数DummyMethod的代码,通过委托执行函数的时候就执行了我们自定义的shellcode,跳出了sandbox了。

  那个POC的作者熟悉C#而不太熟悉安全,所以代码长而且不够可靠,修改了下就好了。原理说得有些绕,下面我来用动态跟踪证实这一点。

  调试

  C#代码是在CLR虚拟机里面运行的,因此基本无法用OllyDBG来做调试,如果有人有办法,还请指点一下。经过在微软一番搜索,使用了Visual Studio 2008加载SOS.dll来做动态跟踪,具体的文档可以搜索SOS.dll就行了。下面简单介绍一下我的调试过程,因为觉得这个很有意思。当然这个是摸索过后的情形,所以看起来很顺利,其实我从学习SOS.dll到跟踪分析完成花了两天多时间。

  首先在加载我修改过的代码工程,在Main函数的第一行下断点,并打开Memory窗口,再在右下角的Immediate Window窗口中输入命令.load sos加载sos.dll模块,整个界面如下图所示,希望这是唯一的一个截图。

.load sos

  extension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded

  可以看到,成功的加载了sos.dll,可以使用!help命令查看sos模块提供的调试命令帮助信息,更多的请自己翻阅手册。

  单步走一步,查看CLR虚拟机的栈信息,结果如下:

  !clrstack -a

  OS Thread Id: 0x8f4 (2292)

  ESP EIP

  0012f418 00e500e4 TypeSafetyExploitPoC.Main(System.String[])

  PARAMETERS:

  args = 0x01301628

  LOCALS:

  0x0012f440 = 0x00000000

  0x0012f43c = 0x01301664

  0x0012f438 = 0x00000000

  0x0012f434 = 0x00000000

  0x0012f430 = 0x00000000

  0x0012f42c = 0x00000000

  0x0012f428 = 0x00000000

  0x0012f424 = 0x00000000

  0x0012f420 = 0x00000000

  0x0012f41c = 0x00000000

  0x0012f418 = 0x00000000

  0012f69c 79e7c74b [GCFrame: 0012f69c]

  可以看到,变量u2已经被定义了,在栈里面的地址为0x01301664。再单步走到u2.o = del上面的语句,查看一下栈,会发现u1,u2,以及del都定义了,而且u1和u2指向的内存和我上面说的一样,是重合的,都是0x01301664——当然,具体的内存地址取决于机器。Del的值为0x01301674。

  在Memory窗口,查看0x01301664地址处的内存,单步走完u2.o = del这条语句,再查看内存,会发现0x01301664 + 4处的内存变成了del的值,即0x01301674,需要注意的是,u1和u2在内存上完全重合,因此u1.i的值就也变成了0x01301674。为什么是0x01301664 + 4处的内存而不是0x01301664处?查看一下u2的结构,如下:

  !dumpobj 0x01301664

  Name: Union2

  MethodTable: 009830fc

  EEClass: 00981344

  Size: 16(0x10) bytes

  (E:\TestExp\bin\Debug\TestExp.exe)

  Fields:

  MT Field Offset Type VT Attr Value Name

  790fd0f0 4000003 4 System.Object 0 instance 01301674 o

  7912d7c0 4000004 8 System.Int32[] 0 instance 00000000 arr

  可以看到,u2的第一个元素偏移量确实是4,偏移为0的地方,是存放着MethodTable的地址,记录了一些内部信息。

  单步走过u1.j = u1.i,可以看到内存0x01301664 + 8处的值改变了。还是由于u1和u2内存重合的原因,u1.i,u1.j,以及u2.o,u2.arr都变成了相同的值,即del的值,也就是那个函数指针的值了。

  下面的一条指令u1.j = u2.arr[2] – 12是POC最关键的一条,还是由于内存重合,这一条改变u1.j的值的指令,间接的修改了arr指向的地址,使arr指向了函数指针指向的内存。因此在后面对arr进行修改的时候,其实是修改了函数指针指向的内存,也就是修改了委托del指向的函数DummyMethod的指令。这条代码执行后查看arr的值,如下:

  !dumpobj 0x01301664

  <Note: this object has an invalid CLASS field>

  Name: Union2

  MethodTable: 009830fc

  EEClass: 00981344

  Size: 16(0x10) bytes

  (E:\TestExp\bin\Debug\TestExp.exe)

  Fields:

  MT Field Offset Type VT Attr Value Name

  790fd0f0 4000003 4 System.Object 0 instance 01301674 o

  7912d7c0 4000004 8 System.Int32[] 0 instance 0098c054 arr

  可以看到,数组的数据已经是保存在地址0x0098c054处了。我们看一下函数指针0x01301674的内容。

  !dumpobj 0x01301674

  Name: System.Threading.ThreadStart

  MethodTable: 791249e8

  EEClass: 790c57b8

  Size: 32(0x20) bytes

  (C:\WINDOWS\assembly\GAC_32\mscorlib.0.0.0__b77a5c561934e089\mscorlib.dll)

  Fields:

  MT Field Offset Type VT Attr Value Name

  790fd0f0 40000ff 4 System.Object 0 instance 01301674 _target

  7910ebc8 4000100 8 ...ection.MethodBase 0 instance 0130182c _methodBase

  791016bc 4000101 c System.IntPtr 1 instance 003B20C4 _methodPtr

  791016bc 4000102 10 System.IntPtr 1 instance 0098C060 _methodPtrAux

  790fd0f0 400010c 14 System.Object 0 instance 00000000 _invocationList

  791016bc 400010d 18 System.IntPtr 1 instance 00000000 _invocationCount

  注意其中的_methodPtrAux的值,对比一下上文的arr指向的内存,发现确实落在其中了。其实上面的arr[2]取到的值就是这个_methodPtrAux的内容。后面的事情就简单了,将shellcode拷贝到arr中,也就是拷贝到了DummyMethod函数的内存中,改写了DummyMethod函数。最后调用del委托间接的调用DummyMethod,就执行了我们的shellcode,突破sandbox了。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章