Zine.net online

Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc
w Szukaj

mgrzeg.net - Admin on Rails :)

SaveModule a punkt startowy programu

Poprzedni tekst zakończyłem zagadką:
czy wykonując !SaveModule będziemy mieli kod dynamicznej metody DynamicClass.SecureGetKey(Test.Program, Int32) w pliku na dysku?
Nikt nie odpowiedział, więc ja to zrobię: Nie, kod generowany w ramach DynamicMethod nie znajdzie się w zrzucie modułu na dysku. Zdziwieni? Ja trochę, a to jeszcze nie jest ostatnia niespodzianka, jaką serwuje nam SaveModule.Dziś zapoznamy się z następną, ale po kolei.

Uwaga! Wszystkie dalsze rozważania dotyczą aplikacji x86, więc wszystkie kompilacje i debuggowanie wykonane było przy wykorzystaniu środowiska 32-bitowego, chociaż w systemie 64-bit.

Zacznijmy od prostej aplikacyjki konsolowej:

using System;
namespace pl.net.zine.Articles.WinDbg
{
 class SaveModule
 {
    public void Run()
    {
     Console.WriteLine("Dump me!");
     Console.ReadKey();
    }
    public void NotRunYet()
    {
     Console.WriteLine("What about me?");
    }
 }
 class Program
 {
    public static void Main(string[] args)
    {
     SaveModule sm = new SaveModule();
     sm.Run();
    }
 }
}

Rozpoczynamy standardowo: kompilujemy kod, ładujemy aplikację do WinDbg (ctrl+e), pozwalamy na start (g w linii poleceń debuggera, tudzież F5), po czym po dojściu do miejsca, w którym na ekranie wyświetla się tekst "Dump me!", przerywamy działanie (ctrl+c) i przechodzimy do debuggera.
Ładujemy sos, sprawdzamy listę modułów:

0:003> .loadby sos mscorwks
0:003> lm
start    end        module name
00210000 00218000   Articles   (deferred)            
...
5e0b0000 5e641000   mscorwks   (deferred)            

i zapisujemy interesujący nas moduł na dysku

0:003> !SaveModule 00210000 C:\Temp\articles_save.exe
3 sections in file
section 0 - VA=2000, VASize=7c4, FileAddr=200, FileSize=800
section 1 - VA=4000, VASize=588, FileAddr=a00, FileSize=600
section 2 - VA=6000, VASize=c, FileAddr=1000, FileSize=200

I gotowe! W ten oto sposób zapisaliśmy na dysku moduł zawierający naszą aplikację. Oczywiście ciekawi nas to, co też w środku znajdziemy. Sprawdzamy rozmiary obu plików: oryginalnego oraz zrzuconego i zgadzają się co do bajta. Ładujemy do reflectora - identyczne! Jest nawet zawartość metody ‘NotRunYet’, która nie została jeszcze skompilowana przez JIT. Ciekawe zatem, czy uda nam się go uruchomić? :)

>articles_save.exe

i tu niestety dostajemy błąd .NET Framework Initialization Error:

Unable to find a version of the runtime to run this application.

Ups! Program nie uruchamia się! :( Ale dlaczego, u licha?!

Tu mała przerwa na kolejną zagadkę odnośnie magic numbers: Co ma wspólnego pierwsze, całkiem polsko brzmiące nazwisko z drugiej tablicy znajdującej się w campusie MS w Redmond (czyli trzecie, tuż po Gates’ie i Ballmerze z pierwszej tablicy!)


(Flickr: http://www.flickr.com/photos/baxiabhishek/2440051803/in/photostream/)

z pierwszymi dwoma bajtami każdej aplikacji .NET, a właściwie z każdej aplikacji zgodnej z MS-DOS (magic number: 4D 5A)?

Po tym krótkim przerywniku, którego źródło okaże się niebawem ważne dla naszych rozważań, wracamy do naszego problemu: dlaczegóż nie uruchamia się nasza aplikacyjka, zapisana na dysku przy użyciu SaveModule z WinDbg? Sięgnijmy po narzędzie dumpbin i porównajmy zrzuty dla obu aplikacji: oryginalnej i zapisanej z WinDbg.

>dumpbin /all articles_save.exe

Szybko orientujemy się, że istnieje duża różnica w sekcji OPTIONAL HEADER VALUES:

entry point:
 oryginal: 27BE entry point (004027BE)
 dump:    74C27CEF entry point (74E37CEF)
image base:
 oryginal: 400000 image base (00400000 to 00407FFF)
 dump:    210000 image base (00210000 to 00217FFF)

Dochodzimy do miejsca, gdzie ważna jest wiedza odnośnie startu aplikacji .NET, którą zilustrujemy na przykładzie naszej oryginalnej aplikacji.
Po pierwsze: AddressOfEntryPoint, czyli punkt startowy aplikacji zapisany jest pod adresem 0xA8. W naszym przypadku wskazuje na adres 27BE (BE 27 00 00), czyli po uwzględnieniu przesunięcia jest to adres 004027BE.
Okazuje się, że jest to fragment sekcji .text, albowiem format PE został rozszerzony ze względu na aplikacje zarządzane w ten sposób, że w sekcji .text zapisany jest nagłówek CLR pozwalający na załadowanie przez system aplikacji .NET. Przeglądamy zawartość sekcji .text w zrzucie wykonanym przez dumpbin i pod adresem 27BE znajdujemy:

 004027B0: 63 6F 72 65 65 2E 64 6C 6C 00 00 00 00 00 FF 25  coree.dll.....y%
 004027C0: 00 20 40 00                                      . @.

czyli

ff 25 00 20 40 00

co przekłada się na:

jmp 402000

Zaglądając do sekcji importów znajdujemy pod adresem 402000 (tak na marginesie, to jest jedyny import):

 Section contains the following imports:
    mscoree.dll
               402000 Import Address Table
               402798 Import Name Table
                    0 time date stamp
                    0 Index of first forwarder reference
                   0 _CorExeMain

A zatem, ostatecznie możemy to zapisać jako:

jmp mscoree.dll!_CorExeMain

I tu następuje właściwy start aplikacji.
Wrócmy teraz do debuggera i sprawdźmy co też dzieje się pod adresem AddressOfEntryPoint z naszego dumpa:

0:003> u 74E37CEF
MSCOREE!ShellShim__CorExeMain:
74e37cef 8bff            mov     edi,edi
74e37cf1 55              push    ebp
74e37cf2 8bec            mov     ebp,esp
74e37cf4 51              push    ecx
74e37cf5 8365fc00        and     dword ptr [ebp-4],0
74e37cf9 8d45fc          lea     eax,[ebp-4]
74e37cfc 50              push    eax
74e37cfd e83595ffff      call    MSCOREE!GetShimImpl (74e31237)

Czyli to samo, co w przypadku oryginalnej aplikacji, ale 'na skróty' :)

Wygląda zatem na to, że !SaveModule poszedł na skróty i zamiast zapisać moduł .NET zgodnie z zasadami sztuki, zrobił zrzut z pamięci z pominięciem sekcji OPTIONAL HEADER VALUES. No cóż, znając podstawy nie pozostaje nam nic innego, jak poprawić dzieło wykonane przez !SaveModule i sprawdzić, czy program się uruchomi.
Zaglądamy do zrzutu wykonanego przez dumpbin dla articles_save.exe i szukamy pod koniec sekcji .text znany nam kawałek odpowiadający skokowi do mscoree!_CorExeMain. Okazuje się, że w naszym przypadku jest to pod adresem 002127BE, co po uwzględnieniu adresu image base = 210000, daje nam liczbę 27BE.
Ładujemy plik articles_save.exe do hex edytora, przechodzimy do adresu 0xA8 i zmieniamy najbliższe 4 bajty (w naszym przypadku było to EF 7C C2 74) na BE 27 00 00, zapisujemy i uruchamiamy.

Gotowe, aplikacja uruchamia się prawidłowo! :)

Opublikowane 21 lutego 2011 00:57 przez mgrzeg

Powiadamianie o komentarzach

Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj

Subskrybuj komentarze za pomocą RSS

Komentarze:

 

Paweł Łukasik said:

Nie byłem w Redmond więc nie wiem jakie tam nazwisko się pojawia, ale sądząć po pytaniu czyżby Mark Zbikowski był upamiętniony w MS?

Świetny post! Więcej takich!!

Paweł

lutego 21, 2011 07:43
 

dotnetomaniak.pl said:

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

lutego 21, 2011 07:45
 

ucel said:

Fajne! Bedzie jeszcze jakis ciag dalszy?

lutego 21, 2011 09:43
 

mgrzeg said:

@Paweł Łukasik: Dzięki za komentarz, już myślałem, że piszę tylko dla siebie ;). Tak, oczywiście chodziło mi o Marka Zbikowskiego. Zdjęcie co prawda nie jest moje (znajdują się przy wejściu, obok którego cały czas kręci się ktoś z ochrony budynku i jakoś tak nie wiedziałem, czy mogę sobie pozwolić na fotki), ale z mojego pobytu w Redmond dobrze pamiętam tamte tablice i nazwisko 'ojca wszystkich aplikacji MS-DOS' :)

@ucel: Zawsze jest jakiś ciąg dalszy ;) Nie planowałem co prawda wiele więcej, ale może napiszę kilka słów odnośnie 'SaveModule a memory dump'...

BTW - czytałeś poprzedni wpis o DynamicMethods?

lutego 21, 2011 10:30
 

ucel said:

Pewnie ze czytalem.

lutego 22, 2011 11:17

Co o tym myślisz?

(wymagane) 
(opcjonalne)
(wymagane) 

  
Wprowadź kod: (wymagane)
Wyślij

Subskrypcje

W oparciu o Community Server (Personal Edition), Telligent Systems