Spis treści:

Kategoria:PowerShellWindows


Informacja o pliku wykonywalnym 32/64 bit - PowerShell

Zarządzanie bitowością aplikacji

Ikona mikroprocesora

Procesory 64-bitowe pojawiły się już dawno. Dawno też zaczęły się pojawiać aplikacje 64 bitowe. Na tyle dawno, że obsługa przez systemy operacyjne 64-bitowych procesorów oraz aplikacje kompilowane na 64-bitowe systemy wydaje się naturalna. Ale...

Świat informatyki nie jest jednak tak prosty jak to się architektom wydaje. Aby zapewnić wsparcie starszych aplikacji 32-bitowych 64-bitowe systemy operacyjne pozwalają uruchamiać aplikacje w trybie zgodności i jest to jedna strona. Druga strona to twórcy aplikacji, którzy celowo tworzą aplikacje 32-bitowe aby dało się je uruchamiać również na starszych systemach operacyjnych. W komputerach domowych problem może pozostać niezauważony, a nawet zauważony - zwykle jest ignorowany. W ostateczności pobiera się z Internetu inną wersję pliku. W dużych rozwiązaniach informatycznych tak łatwo nie jest. Jeden plik wykonywalny o nieprawidłowej "bitowości" może sprawić, że całe, nierzadko skomplikowane rozwiązanie, nie uruchomi się. Otrzymamy zwykle dziwne komunikaty postaci Bad image format lub wyjątki BadImageFormatException lub (Murphy był optymistą) jakieś numerki wyglądające jak wytworzone generatorem liczb pseudolosowych.

Nie jest to najczęściej wina systemu operacyjnego tylko konkretnej aplikacji. Plik wykonywalny może być 32-bitowy a jedna z bibliotek 64-bitowa, plik wykonywalny może być 64-bitowy, kilkadziesiąt bibliotek również 64-bitowych a jakis jeden, jedyny 32-bitowy. Nie ma to znaczenia. To nie demokracja, że decyduje większość. Tu liczy się jednomyślność.

Co możemy z tym zrobić? Musimy do tej jedności doprowadzić. Zanim jednak doprowadzimy, musimy takie pliki zlokalizować.

Pobieranie informacji z nagłówka pliku

Pliki *.exe i *.dll nie są tylko ciągiem instrukcji procesora połączonych z danymiTaką strukturę ma plik .com.. Pliki wykonywalne w Windows mają swoją określoną strukturę, która zdefiniowana jest przez format zwany PE (Portable Executable). I znów - wszystko nie jest takie łatwe jak się na początku wydaje. Aby zapewnić zgodnośc wstecz, format ten osadzony jest w innym formacie zwanym MZ (Mark Zbikowski, jeden z twórców DOS). W czasie pisania tego tekstu obecnie wspierane wersje Windows prawie całkowicie ignorują część zawartą w sekcji MZ rozpoczynając interpretację od sekcji PEW praktyce należy zweryfikować poprawność nagłówka (w szczególności dwa bajty zawierające znaki M i Z) oraz wartość wskazującą położenie nagłówka PE (zapisany na czterech bitach i rozpoczynający się na 60 bajcie nagłówka MZ)..

Nagłówek PE rozpoczyna się, a jakże, od dwóch liter P i E, po których występują dwa zerowe bajty. Kolejne dwa bajty określają architekturę procesora:

Bajty (szesnastkowo)Architektura
0x014cx86
0x0200Intel Itanium
0x8664x64

Mogą się tam pojawić jeszcze inne wartości (ARM, Hitachi - odsyłam do specyfikacji formatu PE), ale w środowisku Windows raczej się nie pojawią i nie będą uruchamiane.

To absolutne minimum jakie trzeba wiedzieć, aby dobrać się do informacji o tym, czy plik skompilowany został jako 64-bitowy czy 32-bitowy. Informacja oczywiście dotyczy plików wykonywalnych w środowisku Windows.

Przykładowa implementacja w PowerShell

Uznałem, że takie badania "bitowości" bardziej kwalifikują się do prac administracyjnych. Stąd wybór narzędzia. Przepisanie tego na C++, C# i inne języki powinno być łatwe. Pełny listing zaprezentowano poniżej:

function GetMachine($filePath) 
{
    $bytes = Get-Content -Path $filePath -Encoding Byte -TotalCount 64;
    #check MZ header
    if ($bytes.Count -ge 64 -and $bytes[0] -eq [char]'M' -and $bytes[1] -eq [char]'Z') {
        $headOffset = [BitConverter]::ToUInt32($bytes, 60);
        $PEheaderBytes = Get-Content -Path $filePath -Encoding Byte -TotalCount ($headOffset+6);
        #check PE header
        if ([BitConverter]::ToUInt32($PEheaderBytes, $headOffset) -eq 0x00004550) {
            $machine = [BitConverter]::ToUInt16($PEheaderBytes, $headOffset+4);
            @{0x014c = "x86"; 0x0200 = "Intel Itanium"; 0x8664 = "x64"}[[int]$machine];
        }
    }
}

Aby sprawdzić wszystkie pliki *.exe w katalogu bieżącym i podkatalogach wystarczy teraz napisać:

ls *.exe -Recurse | select FullName,@{N="Machine";E={GetMachine($_.FullName)}}

Wykonanie tego skryptu w katalogu Windows/system32 na 64-bitowym systemie dało u mnie taki wynik:

FullName                             Machine
--------                             -------
...
C:\windows\System32\TSWbPrxy.exe     x64    
C:\windows\System32\TsWpfWrp.exe     x86    
C:\windows\System32\typeperf.exe     x64    
C:\windows\System32\tzutil.exe       x64    
C:\windows\System32\ucsvc.exe        x64    
C:\windows\System32\UI0Detect.exe    x64
...

Znaczna część wyników została pominięta.

Podsumowanie

Pokazany powyżej skrypt wydobywa tylko ułamek tego, co może być wydobyte z pliku wykonywalnego systemu Windows. Jako ciekawostkę mogę podać fakt, że pliki wykonywalne .NET również korzystają z formatu PE. Jak to działa? W sekcji z kodem pliku PE umieszczone jest wywołanie metody uruchamiającej maszynę wirtualną. Ta maszyna dobrze wie co dalej z tym plikiem zrobić. Co istotne, sekcja odpowiedzialna za bitowość jest poprawnie ustawiona.

Kategoria:PowerShellWindows

, 2017-11-10

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !
a z innej strony - co gdybym ciąg znaków chciał mieć rozbity nie na wiersze a na kolumny? Czyli ciąg ABCD: 1. kolumna: A, 2. kolumna: B, 3. kolumna: C, 4 kolumna: D?