1 namespace Example_2_1_1 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 Console.WriteLine("Hello World"); 8 Console.ReadLine(); 9 } 10 } 11 }
1 0:000> g 2 ModLoad: 74ec0000 74f39000 C:\Windows\SysWOW64\ADVAPI32.dll 3 ModLoad: 771f0000 772af000 C:\Windows\SysWOW64\msvcrt.dll 4 ModLoad: 757e0000 75855000 C:\Windows\SysWOW64\sechost.dll 5 ModLoad: 753c0000 7547a000 C:\Windows\SysWOW64\RPCRT4.dll 6 ModLoad: 711c0000 7124d000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll 7 ModLoad: 771a0000 771e5000 C:\Windows\SysWOW64\SHLWAPI.dll 8 ModLoad: 757d0000 757df000 C:\Windows\SysWOW64\kernel.appcore.dll 9 ModLoad: 74eb0000 74eb8000 C:\Windows\SysWOW64\VERSION.dll 10 ModLoad: 70a10000 711c0000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll(CLR元件,載入的起始地址:70a10000) 11 ModLoad: 75870000 75a04000 C:\Windows\SysWOW64\USER32.dll 12 ModLoad: 77070000 77088000 C:\Windows\SysWOW64\win32u.dll 13 ModLoad: 70940000 709eb000 C:\Windows\SysWOW64\ucrtbase_clr0400.dll 14 ModLoad: 75fb0000 75fd3000 C:\Windows\SysWOW64\GDI32.dll 15 ModLoad: 709f0000 70a04000 C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll 16 ModLoad: 750c0000 7519b000 C:\Windows\SysWOW64\gdi32full.dll 17 ModLoad: 76390000 7640b000 C:\Windows\SysWOW64\msvcp_win.dll 18 ModLoad: 75550000 75670000 C:\Windows\SysWOW64\ucrtbase.dll 19 ModLoad: 74fa0000 74fc5000 C:\Windows\SysWOW64\IMM32.DLL 20 ModLoad: 75ff0000 76270000 C:\Windows\SysWOW64\combase.dll 21 (7fc.b18): Unknown exception - code 04242420 (first chance) 22 ModLoad: 6f530000 7093e000 C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\218db16dceaef380c6daf35c6a48f313\mscorlib.ni.dll 23 ModLoad: 76490000 76573000 C:\Windows\SysWOW64\ole32.dll 24 ModLoad: 75ff0000 76270000 C:\Windows\SysWOW64\combase.dll 25 ModLoad: 752a0000 752fc000 C:\Windows\SysWOW64\bcryptPrimitives.dll 26 ModLoad: 6f4a0000 6f52a000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll(這個就是JIT編譯器元件,在程序中的起始地址:6f4a0000) 27 ModLoad: 751a0000 7523b000 C:\Windows\SysWOW64\OLEAUT32.dll
2.2、證明 Windows 載入器如何載入一個 Net 的程式集。
驗證程式碼:Example_2_1_1
PPEE是可攜式軟體,不用安裝,直接下載就可以使用。開啟 PPEE 軟體,開啟 Example_2_1_1.exe,就可以看到 PE 檔案。
當我們雙加一個 Net 的Exe應用程式的時候,作業系統會做很多工作,比如:在核心態生成進行的地址空間,地址空間生成成功後,然後在生成一個Process 的程序,再給這個程序生成一個主執行緒,在核心態還要針對程序生成 EProcess 的資料結構,針對執行緒生成一個 ETHREAD 的資料結構。當所有的準備工作都完成後,要開始執行程式,從哪裡開始執行程式呢?我們來證明一下,或者說熟悉一下,這個過程就和 PE 標頭檔案有關了。
a、Windows 載入器會讀取 PE 檔案頭裡面的資料,來確定從哪裡開始執行,第一步,我們通過 PPEE 檢視 Example_2_1_1.exe PE 檔案,在 PE 頭裡依次點選【NT Header】-->【Optional Header】,在表單的右側就可以看到,有一項【AddressOfEntryPoint】,它的值是:00002782,這個地址是相對地址,是相對程式程序起始地址來說的。
如圖:
我們有了入口程式的相對起始地址,我們找一下應用程式的程序起始地址,二者相加,就是 Windows 載入器要執行的地址。想要檢視 Example_2_1_1.exe 程序地址,需要藉助 Windbg,紅色部分就是 Example_2_1_1.exe 程序的起始地址。
程式碼如下:
1 Executable search path is: 2 ModLoad: 00ca0000 00ca8000 Example_2_1_1.exe 3 ModLoad: 770d0000 77272000 ntdll.dll 4 ModLoad: 71050000 710a2000 C:\Windows\SysWOW64\MSCOREE.DLL 5 ModLoad: 74cc0000 74db0000 C:\Windows\SysWOW64\KERNEL32.dll 6 ModLoad: 767a0000 769b3000 C:\Windows\SysWOW64\KERNELBASE.dll
二者相加就是WIndows 載入器的入口點地址,還不是我們的 Program.Main的地址,00ca0000(Example_2_1_1程序起始地址),00002782 是 PE 頭告訴的入口點地址,我們通過 U 命令,可以檢視組合程式碼。通過程式碼我們可以看到,執行了 jmp 指令,跳轉的地址是:402000h。
1 0:000> u 00ca0000+00002782 2 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2782(PE 頭裡的值)): 3 00ca2782 ff2500204000 jmp(執行跳轉指令) dword ptr ds:[402000h] 4 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2788): 5 00ca2788 0000 add byte ptr [eax],al 6 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x278a): 7 00ca278a 0000 add byte ptr [eax],al 8 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x278c): 9 00ca278c 0000 add byte ptr [eax],al 10 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x278e): 11 00ca278e 0000 add byte ptr [eax],al 12 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2790): 13 00ca2790 0000 add byte ptr [eax],al 14 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2792): 15 00ca2792 0000 add byte ptr [eax],al 16 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2794): 17 00ca2794 0000 add byte ptr [eax],al
接下來,我們看看 402000h 這個地址有什麼東西。
再次執行 Windbg,重新載入 Example_2_1_1.exe,通過【g】命令繼續執行,暫停時,【break】開始偵錯狀態,必須切換到主執行緒,也就是 0號執行緒。
1 //g 繼續執行 2 0:000> g 3 ModLoad: 76c30000 76ca9000 C:\Windows\SysWOW64\ADVAPI32.dll 4 ModLoad: 765b0000 7666f000 C:\Windows\SysWOW64\msvcrt.dll 5 ModLoad: 76e30000 76ea5000 C:\Windows\SysWOW64\sechost.dll 6 ModLoad: 75c40000 75cfa000 C:\Windows\SysWOW64\RPCRT4.dll 7 ModLoad: 70fc0000 7104d000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll 8 ModLoad: 75d20000 75d65000 C:\Windows\SysWOW64\SHLWAPI.dll 9 ModLoad: 75530000 7553f000 C:\Windows\SysWOW64\kernel.appcore.dll 10 ModLoad: 74cb0000 74cb8000 C:\Windows\SysWOW64\VERSION.dll 11 ModLoad: 70810000 70fc0000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll 12 ModLoad: 76410000 765a4000 C:\Windows\SysWOW64\USER32.dll 13 ModLoad: 707f0000 70804000 C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll 14 ModLoad: 76180000 76198000 C:\Windows\SysWOW64\win32u.dll 15 ModLoad: 70740000 707eb000 C:\Windows\SysWOW64\ucrtbase_clr0400.dll 16 ModLoad: 75b80000 75ba3000 C:\Windows\SysWOW64\GDI32.dll 17 ModLoad: 76670000 7674b000 C:\Windows\SysWOW64\gdi32full.dll 18 ModLoad: 76b30000 76bab000 C:\Windows\SysWOW64\msvcp_win.dll 19 ModLoad: 75550000 75670000 C:\Windows\SysWOW64\ucrtbase.dll 20 ModLoad: 754f0000 75515000 C:\Windows\SysWOW64\IMM32.DLL 21 Breakpoints 3 and 0 match 22 ModLoad: 75f00000 76180000 C:\Windows\SysWOW64\combase.dll 23 (3624.36d8): Unknown exception - code 04242420 (first chance) 24 ModLoad: 6f330000 7073e000 C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\218db16dceaef380c6daf35c6a48f313\mscorlib.ni.dll 25 ModLoad: 76cb0000 76d93000 C:\Windows\SysWOW64\ole32.dll 26 ModLoad: 75f00000 76180000 C:\Windows\SysWOW64\combase.dll 27 ModLoad: 76fa0000 76ffc000 C:\Windows\SysWOW64\bcryptPrimitives.dll 28 ModLoad: 6f2a0000 6f32a000 C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll 29 ModLoad: 76eb0000 76f4b000 C:\Windows\SysWOW64\OLEAUT32.dll 30 (3624.20f0): Break instruction exception - code 80000003 (first chance) 31 eax=0106b000 ebx=00000000 ecx=7717cee0 edx=7717cee0 esi=7717cee0 edi=7717cee0 32 eip=77143410 esp=05a2fa9c ebp=05a2fac8 iopl=0 nv up ei pl zr na pe nc 33 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 34 ntdll!DbgBreakPoint: 35 77143410 cc int 3 36 37 //切換到主執行緒 38 0:006> ~0s 39 eax=00000000 ebx=0000009c ecx=00000000 edx=00000000 esi=0138ee3c edi=00000000 40 eip=771410fc esp=0138ed24 ebp=0138ed84 iopl=0 nv up ei pl nz na pe nc 41 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 42 ntdll!NtReadFile+0xc: 43 771410fc c22400 ret 24h
我們通過【k】命令,檢視顯示呼叫棧
1 0:000> k 2 # ChildEBP RetAddr 3 00 0138ed84 768af25c ntdll!NtReadFile+0xc 4 01 0138ed84 6f7e9b71 KERNELBASE!ReadFile+0xec 5 02 0138edf4 6ff1b275 mscorlib_ni+0x4b9b71 6 03 0138ee20 6ff1b17b mscorlib_ni!System.IO.__ConsoleStream.ReadFileNative+0x89 [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205] 7 04 0138ee4c 6f7ce6a3 mscorlib_ni!System.IO.__ConsoleStream.Read+0x9f [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134] 8 05 0138ee64 6f7ceb5b mscorlib_ni!System.IO.StreamReader.ReadBuffer+0x33 [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595] 9 06 0138ee80 70063786 mscorlib_ni!System.IO.StreamReader.ReadLine+0xe3 [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748] 10 07 0138ee90 6fec1845 mscorlib_ni!System.IO.TextReader.SyncTextReader.ReadLine+0x1a [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363] 11 08 0138ee98 03250876 mscorlib_ni!System.Console.ReadLine+0x15 [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984] 12 WARNING: Frame IP not in any known module. Following frames may be wrong. 13 09 0138eea8 7081f036 0x3250876 14 0a 0138eeb4 708222da clr!CallDescrWorkerInternal+0x34 15 0b 0138ef08 7082859b clr!CallDescrWorkerWithHandler+0x6b 16 0c 0138ef7c 709cb11b clr!MethodDescCallSite::CallTargetWorker+0x16a 17 0d 0138f0a0 709cb7fa clr!RunMain+0x1b3 18 0e 0138f30c 709cb727 clr!Assembly::ExecuteMainMethod+0xf7 19 0f 0138f7f0 709cb8a8 clr!SystemDomain::ExecuteMainMethod+0x5ef 20 10 0138f848 709cb9ce clr!ExecuteEXE+0x4c 21 11 0138f888 709c7305 clr!_CorExeMainInternal+0xdc 22 12 0138f8c4 70fcfa84 clr!_CorExeMain+0x4d 23 13 0138f8fc 7105e81e mscoreei!_CorExeMain+0xd6 24 14 0138f90c 71064338 MSCOREE!ShellShim__CorExeMain+0x9e 25 15 0138f924 74cdf989 MSCOREE!_CorExeMain_Exported+0x8 26 16 0138f924 77137084 KERNEL32!BaseThreadInitThunk+0x19 27 17 0138f980 77137054 ntdll!__RtlUserThreadStart+0x2f 28 18 0138f990 00000000 ntdll!_RtlUserThreadStart+0x1b
以上就是執行緒的呼叫棧,我們查詢一下 _CorExeMain,這個方法,可以在 PPEE 軟體裡,在檢視 Example_2_1_1.exe 的PE 標頭檔案 DIRECTORY_ENTRY_IAT 裡,效果如圖:
接著上面的說,我們在【k】命令的結果中查詢 _CorExeMain 方法。這裡的執行結果是一部分,紅色部分是重點,MSCOREE 就是 mscoree.dll。
1 0a 0138eeb4 708222da clr!CallDescrWorkerInternal+0x34 2 0b 0138ef08 7082859b clr!CallDescrWorkerWithHandler+0x6b 3 0c 0138ef7c 709cb11b clr!MethodDescCallSite::CallTargetWorker+0x16a 4 0d 0138f0a0 709cb7fa clr!RunMain+0x1b3(執行 Main 方法) 5 0e 0138f30c 709cb727 clr!Assembly::ExecuteMainMethod+0xf7(載入必須的 dll 程式集) 6 0f 0138f7f0 709cb8a8 clr!SystemDomain::ExecuteMainMethod+0x5ef(初始化系統程式域) 7 10 0138f848 709cb9ce clr!ExecuteEXE+0x4c(開始執行 exe) 8 11 0138f888 709c7305 clr!_CorExeMainInternal+0xdc 9 12 0138f8c4 70fcfa84 clr!_CorExeMain+0x4d(從這裡開始執行入口地址的方法) 10 13 0138f8fc 7105e81e mscoreei!_CorExeMain+0xd6(載入 CLR) 11 14 0138f90c 71064338 MSCOREE!ShellShim__CorExeMain+0x9e 12 15 0138f924 74cdf989 MSCOREE!_CorExeMain_Exported+0x8(這裡就是在 PPEE 看到的入口地址 AddressOfEntryPoint:00002782) 13 16 0138f924 77137084 KERNEL32!BaseThreadInitThunk+0x19 14 17 0138f980 77137054 ntdll!__RtlUserThreadStart+0x2f 15 18 0138f990 00000000 ntdll!_RtlUserThreadStart+0x1b(ntdll是windows 32位元的API)
第12行程式碼就是 mscoree.dll 執行 _CorExeMain 方法,初始化環境,10 行程式碼載入 CLR,CLR 從第9 行執行入口函數,知道最後進入我們的託管層,我們可以使用 !clrstack 命令檢視託管棧。
1 0:000> !clrstack 2 OS Thread Id: 0x36d8 (0) 3 Child SP IP Call Site 4 0138eda4 771410fc [InlinedCallFrame: 0138eda4] 5 0138eda0 6f7e9b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 6 0138eda4 6ff1b275 [InlinedCallFrame: 0138eda4] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 7 0138ee08 6ff1b275 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205] 8 0138ee3c 6ff1b17b System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134] 9 0138ee5c 6f7ce6a3 System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595] 10 0138ee6c 6f7ceb5b System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748] 11 0138ee88 70063786 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363] 12 0138ee98 6fec1845 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984] 13 0138eea0 03250876 Example_2_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_2_1_1\Program.cs @ 10] 14 0138f020 7081f036 [GCFrame: 0138f020]
系統進入了託管棧,那它怎麼知道要執行程式的 Main 方法呢?其實,在 PE 標頭檔案裡也有說明,在【DIRECTORY_ENTRY_COM_DESCRIPTOR】設定項裡,我們點選該節點,在右側顯示詳情,請注意【EntryPointToken】,值是:06000001,這個標記就是 Main方法。
效果如圖:
我們為了證明 06000001就是 Main 方法,我們使用 ILSpy 或者 DnSpy 檢視程式集的後設資料,效果如圖:
其實,我們在 Main 方法的 IL 程式碼裡也有標記,紅色部分就是,注意。
1 .method private hidebysig static 2 void Main ( 3 string[] args 4 ) cil managed 5 { 6 // Method begins at RVA 0x2050 7 // Header size: 1 8 // Code size: 19 (0x13) 9 .maxstack 8 10 .entrypoint 11 12 IL_0000: nop 13 IL_0001: ldstr "Hello World" 14 IL_0006: call void [mscorlib]System.Console::WriteLine(string) 15 IL_000b: nop 16 IL_000c: call string [mscorlib]System.Console::ReadLine() 17 IL_0011: pop 18 IL_0012: ret 19 } // end of method Program::Main
2.3、檢視應用程式域
驗證程式碼:Example_2_1_1
EECLASS 存放在 LowFrequencyHeap ,MethodTable 存放在 HighFrequencyHeap
1 0:000> !dumpdomain(執行的命令) 2 -------------------------------------- 3 System Domain: 7115caf8(系統級程式域) 4 LowFrequencyHeap: 7115ce1c(低頻堆) 5 HighFrequencyHeap: 7115ce68(高頻堆) 6 StubHeap: 7115ceb4(樁堆) 7 Stage: OPEN 8 Name: None 9 -------------------------------------- 10 Shared Domain: 7115c7a8(共用程式域) 11 LowFrequencyHeap: 7115ce1c(低頻堆) 12 HighFrequencyHeap: 7115ce68(高頻堆) 13 StubHeap: 7115ceb4(樁堆) 14 Stage: OPEN 15 Name: None 16 Assembly: 00b4f3d8 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll] 17 ClassLoader: 00b4de08 18 Module Name 19 6f531000 C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 20 21 -------------------------------------- 22 Domain 1: 00b01518(應用程式域) 23 LowFrequencyHeap: 00b01984(低頻堆) 24 HighFrequencyHeap: 00b019d0(高頻堆) 25 StubHeap: 00b01a1c(樁堆) 26 Stage: OPEN 27 SecurityDescriptor: 00b02a58 28 Name: Example_2_1_1.exe 29 Assembly: 00b4f3d8 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll] 30 ClassLoader: 00b4de08 31 SecurityDescriptor: 00b4f340 32 Module Name 33 6f531000 C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 34 35 Assembly: 00b5b4d8 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_2_1_1\bin\Debug\Example_2_1_1.exe] 36 ClassLoader: 00b5afa8 37 SecurityDescriptor: 00b5aea0 38 Module Name 39 00d44044 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_2_1_1\bin\Debug\Example_2_1_1.exe
0:000> ? $exentry Evaluate expression: 5711746 = 00572782