Our Team:http://www.ph4nt0m.org
Author:雲舒(wustyunshu@hotmail.com)
本文可以任意轉載,但請保持完整,並保留原始出處,謝謝。
起因
一個老外寫了一篇文章<<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模組,整個介面如所示,希望這是唯一的一個。
javascript:DrawImage(this);>
.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 了。