Zmarnowany czas ??? - nie, nie będę się rozwodził nad trwonieniem czasu przy kompie (może trochę we wstępie), chociaż ten temat mógłby być równie ciekawy. Nie wiem jak wy ale ja staram się sprawdzać ile czasu spędzam przy "maszynce". Do tego celu używam programiku, który właśnie nazywa się Wasted Time, ot taki badziew napisany w Delphi. Niedawno jednak mnie ten program trochę zdenerwował i stwierdziłem, że żadnym problemem nie byłoby napisanie czegoś takiego w ASM'ie, mało tego byłoby to zbyt krótkotrwałe zajęcie :). Dlatego też zintegrujemy to (aż do bólu) z Windowsem.

.:: CEL UŚWIĘCA ŚRODKI ::.

Nic tak nie oddziałuje na umysł jak wizualna prezentacja zamierzonego celu :).

Skoro już każdy zobaczył co zrobimy, pora sprawdzić jakie ŚRODKI będą do tego potrzebne:
- IDA - w tej chwili nie wyobrażam sobie pracy bez niej
- Hiew - lub inny hexedytor z możliwością wpisywania kodu ASM (jest taki ???)
- LordPE - lub inny edytor plików PE
- ResHacker - lub inny edytor zasobów
- Windows 98 - zmian będę dokonywał na pliku Shell32.dll wersji 4.72.3612.1700, ale nie powinno być problemów z przystosowaniem ich do innych wersji.

.:: BEZ PRACY NIE MA KOŁACZY ::.

Poszukiwane okno odpowiadające za zamykanie systemu znajduje się właśnie w bibliotece Shell32.dll, jak większość okien systemowych. W tej wersji ma ono numer ID 1064. W ResHackerze widzimy, że ma ono nie jak to widać na rysunku 4 opcje lecz 7:

 - Przełącz w stan oczekiwania
 - Przełącz w stan oczekiwania (zdarzenia budzenia wyłączone)
 - Uśpij
 - Zamknij
 - Uruchom ponownie
 - Uruchom ponownie w trybie MS-DOS
 - Wyloguj

W zależności od odpowiednich ustawień w rejestrze odpowiednie opcje są usuwane na bieżąco. Ramkę w której pokazywany będzie czas pracy systemu specjalnie umieściłem ponad opcjami, ponieważ gdyby była pod opcjami to musielibyśmy odnaleźć miejsce w kodzie odpowiedzialne za zmianę rozmiaru okna i uaktywnianie odpowiednich opcji. Do okna dodajemy teraz odpowiednie kontrolki i modyfikujemy położenie pozostałych.

elementy które należy dodać:
CONTROL "", 0, BUTTON, BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 3, 3, 245, 41
CONTROL "System start:",0,STATIC,SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP,10,10,45,9
CONTROL "System end:",0, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP,10,20,45,9
CONTROL "Elapsed time:",0,STATIC,SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP,10,30,45,9
CONTROL "strSysStart",666,STATIC,SS_LEFT | WS_CHILD | WS_VISIBLE |WS_GROUP,55,10,190,9
CONTROL "strSysEnd", 667,STATIC,SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP,55,20,190,9
CONTROL "strElapsTime",668,STATIC,SS_LEFT | WS_CHILD | WS_VISIBLE|WS_GROUP,55,30,190,9

Najlepiej to zrobić ResHackerem z racji na wygodę obsługi :). Elementy, które mają ustawiony text str... to trzy labele do których zostaną wpisane odpowiednie dane.

Po tych kilku zabiegach dostosowujących okno do naszych wymagań należy odnaleźć miejsce w kodzie odpowiedzialne za wywoływanie tego okna. Nie do końca pewnym ale w tym przypadku skutecznym sposobem jest wyszukanie ciągu znaków 428h (428h = 1064), czyli ID naszego okna. IDA znalazła tylko jedno takie miejsce pod offsetem 7FD1614A:

.text:7FD1612E             mov     ecx, offset sub_7FD15DD5  ;[1]
.text:7FD16133             mov     eax, [esp+10h+arg_4]
.text:7FD16137             test    eax, eax                  ;[2]
.text:7FD16139             jnz     short loc_7FD16140
.text:7FD1613B             mov     ecx, offset sub_7FD15900  ;[3]
.text:7FD16140 loc_7FD16140:
.text:7FD16140             neg     eax
.text:7FD16142             sbb     eax, eax
.text:7FD16144             push    0               ; dwInitParam
.text:7FD16146             and     eax, 7
.text:7FD16149             push    ecx             ; lpDialogFunc
.text:7FD1614A             add     eax, 428h                 ;[4]
.text:7FD1614F             push    esi             ; hWndParent
.text:7FD16150             push    eax             ; lpTemplateName
.text:7FD16151             push    ds:hModule      ; hInstance
.text:7FD16157             call    ds:DialogBoxParamA

Jak zwykle okazuje się, że szczęście jest nie odzowne przy reversowaniu czegokolwiek :). Wyróżniony fragment [3] jest to ustawianie w ecx offsetu do procedury obsługującej okno, natomiast przy pisaniu tego textu zauważyłem, że wcześniej ustawiany jest inny offset [1] i w zależności od warunku [2] wykonywana jest dopiero instrukcja [3]. W [4] do eax dodawany jest ID naszego okna z tym, że my wiemy iż eax jest równe 0 ponieważ inaczej do ecx powędrowałby offset innej procki. Tak więc rocedura sub_7FD15900 obsługuje okno zamykania systemu. Nie ma ona zbyt skomplikowanej budowy, a co za tym idzie nie przysporzy nam kłopotów jej modyfikacja.

.text:7FD15900             push    ebp
.text:7FD15901             mov     ebp, esp
.text:7FD15903             sub     esp, 0ACh
.text:7FD15909             mov     eax, [ebp+uMsg]
.text:7FD1590C             push    ebx
.text:7FD1590D             push    esi
.text:7FD1590E             sub     eax, 6
.text:7FD15911             push    edi
.text:7FD15912             jz      WM_ACTIVATE         ;7FD15DA9
.text:7FD15918             sub     eax, 10Ah
.text:7FD1591D             jz      WM_INITDIALOG       ;7FD15AEF
.text:7FD15923             dec     eax
.text:7FD15924             jz      short WM_COMMAND    ;7FD15958
.text:7FD15926             dec     eax
.text:7FD15927             jz      short WM_SYSCOMMAND ;7FD15942
.text:7FD15929             sub     eax, 5
.text:7FD1592C             jnz     short loc_7FD1593E
.text:7FD1592E             push    1
.text:7FD15930             push    0F010h
.text:7FD15935             push    dword ptr [ebp+Data]
.text:7FD15938             call    ds:EnableMenuItem
.text:7FD1593E loc_7FD1593E:
.text:7FD1593E             xor     eax, eax
.text:7FD15940             jmp     short loc_7FD15951

Powyższe to fragment obsługujący poszczególne widomości. Przy skokach zaznaczyłem zamiast offsetów nazwy widomości ponieważ jak wiele progów (napisanych w VC++) tak i shell32 sprawdza widomości nie poprzez porównywanie wprost z konkretną wartością, ale poprzez odpowiednie zmniejszanie rejestru eax, w którym znajduje się komunikat (uMsg). Kod generujący odpowiednie informacje dla naszych celów najlepiej podpiąć pod obsługę wiadomości WM_INITDIALOG. Dużo wolnego miejsca, w którym zmieścimy naszą prockę i odpowiednie dane jest w sekcji .data pod adresem 7FD37900. Radzę również zmienić wartość Virtual Size tej sekcji w nagłówku PE na 1000h, ponieważ kiedy tego nie zrobiłem system nie wczytał do pamięci dopisanych fragmentów kodu. Przy normalnym exe'cu można nawet nie zmieniać wartości Virtual Size, ponieważ system wczytuje je normalnie, natomiast w shell32 sekcja .data ma ustawioną flagę shareable i pewnie dlatego dane w nagłówku są traktowane bardziej poważnie (to są tylko moje domysły :( ). Do osiągnięcia celów pokazanych na rysunku wykorzystamy następujące funkcje WinAPI:

 - GetTickCount
 - SetDlgItemTextA
 - wsprintf
 - GetProcAddress
 - GetModuleHandle

Aby w Hiew'ie móc odwoływać się do tych funkcji musimy spisać z IDA'y ich offset'y. Najlepiej wyszukać ich nazwy na zakłedce Imports

 - GetTickCount    = call d,[7FCB1608]
 - SetDlgItemTextA = call d,[7FCB13AC]
 - wsprintf        = call d,[7FCB1398]
 - GetProcAddress  = call d,[7FCB162C]
 - GetModuleHandle = call d,[7FCB1564]

Pierwszym krokiem jaki wykona nasz kod będzie pobranie adresu funkcji GetLocalTime:

.data:7FD37A4F             push    offset aKernel32_0 ; "KERNEL32"
.data:7FD37A54             call    ds:GetModuleHandleA
.data:7FD37A5A             push    offset aGetlocaltime ; "GetLocalTime"
.data:7FD37A5F             push    eax
.data:7FD37A60             call    ds:GetProcAddress
.data:7FD37A66             mov     dword_7FD3797C, eax

Następnie przy pomocy funkcji GetTickCount i kilku pomocniczych działań wyliczymy czas, który upłynął od startu systemu:

.data:7FD37A6B             call    ds:GetTickCount
.data:7FD37A71             xor     edx, edx
.data:7FD37A73             mov     ecx, 36EE80h          ;[1]
.data:7FD37A78             div     ecx
.data:7FD37A7A             mov     dword_7FD37950, eax
.data:7FD37A7F             mov     eax, edx
.data:7FD37A81             xor     edx, edx
.data:7FD37A83             mov     ecx, 0EA60h           ;[2]
.data:7FD37A88             div     ecx
.data:7FD37A8A             mov     dword_7FD37954, eax
.data:7FD37A8F             mov     eax, edx
.data:7FD37A91             xor     edx, edx
.data:7FD37A93             mov     ecx, 3E8h             ;[3]
.data:7FD37A98             div     ecx
.data:7FD37A9A             mov     dword_7FD37958, eax
.data:7FD37A9F             push    eax
.data:7FD37AA0             push    dword_7FD37954
.data:7FD37AA6             push    dword_7FD37950
.data:7FD37AAC             push    offset a02u02u02u ; "%02u:%02u:%02u"
.data:7FD37AB1             push    offset dword_7FD37980
.data:7FD37AB6             call    ds:wsprintfA
.data:7FD37ABC             add     esp, 14h

W punkcie [1] w rejestrze eax mamy ilość milisekund, która upłynęła od startu systemu, wartość ta jest dzielona przez 36EE80h=3600000d, czyli przez liczbę milisekunkd tworzących jedną godzinę (1000*60*60) i zapamiętujemy wynik. Reszta z tego dzielenia wędruje z rejestru edx do rejestru eax. Następnie dzielimy tę resztę przez 0EA60h=60000d (liczba milisekund tworzących jedną minutę). I analogicznie resztę dzielimy przez 3E8h=1000d. Dzięki tym operacjom mamy w pamięci parametry, które możemy przekazać do funkcji wsprintf wraz z łańcuchem formatującym %02u:%02u:%02u. Po sformatowaniu wpisujemy nasz string do odpowiedniego okna:

.data:7FD37ABF             push    offset dword_7FD37980   ;offset do sformatowanego
                                                            string'a
.data:7FD37AC4             push    29Ch                    ;ID static'a
.data:7FD37AC9             push    dword ptr [ebp+8]       ;uchwyt okna
.data:7FD37ACC             call    ds:SetDlgItemTextA

Kolejnym krokiem będzie pobranie aktualnej daty i godziny. Do tego wykorzystamy wcześniej pobrany adres funkcji GetLocalTime, która jako jedyny argument wymaga adres do struktury SYSTEMTIME:

.data:7FD37AD2             push    offset word_7FD3795C
.data:7FD37AD7             call    dword_7FD3797C          ;GetLocalTime
.data:7FD37ADD             push    small 0
.data:7FD37AE0             push    small word_7FD3795C     ;wYear
.data:7FD37AE7             push    small 0
.data:7FD37AEA             push    small word_7FD3795E     ;wMonth
.data:7FD37AF1             push    small 0
.data:7FD37AF4             push    small word_7FD37962     ;wDay
.data:7FD37AFB             push    small 0
.data:7FD37AFE             push    small word_7FD37968     ;wSecond
.data:7FD37B05             push    small 0
.data:7FD37B08             push    small word_7FD37966     ;wMinute
.data:7FD37B0F             push    small 0
.data:7FD37B12             push    small word_7FD37964     ;wHour
.data:7FD37B19             push    offset a02hu02hu02hu02
                                       ; "%02hu:%02hu:%02hu   %02hu-%02hu-%04hu"
.data:7FD37B1E             push    offset dword_7FD37980
.data:7FD37B23             call    ds:wsprintfA
.data:7FD37B29             add     esp, 20h
.data:7FD37B2C             push    offset dword_7FD37980
.data:7FD37B31             push    29Bh
.data:7FD37B36             push    dword ptr [ebp+8]
.data:7FD37B39             call    ds:SetDlgItemTextA

Powyższe odpowiada za pobranie, sformatowanie i wyświetlenie aktualnej daty i godziny. Ponieważ w strukturze SYSTEMTIME wszystkie pola są 16-bitowe (WORD), przed każdym pushem musimy jeszcze odłożyć na stos word równy 0.
Zostało nam jeszcze wyliczenie godziny startu systemu. Można to zapewne zrobić na kilka (naście) sposobów, tutaj przedstawię tylko jeden, ale chyba najprostszy do implementacji. Najpierw zamienimy wcześniej pobraną godzinę na milisekundy:

.data:7FD37B56             xor     edx, edx
.data:7FD37B58             movzx   eax, word_7FD37964
.data:7FD37B5F             mov     ecx, 36EE80h
.data:7FD37B64             mul     ecx
.data:7FD37B66             mov     ebx, eax
.data:7FD37B68             movzx   eax, word_7FD37966
.data:7FD37B6F             xor     edx, edx
.data:7FD37B71             mov     ecx, 0EA60h
.data:7FD37B76             mul     ecx
.data:7FD37B78             add     ebx, eax
.data:7FD37B7A             movzx   eax, word_7FD37968
.data:7FD37B81             xor     edx, edx
.data:7FD37B83             mov     ecx, 3E8h
.data:7FD37B88             mul     ecx
.data:7FD37B8A             add     ebx, eax
.data:7FD37B8C             movzx   edx, word_7FD3796A
.data:7FD37B93             add     ebx, edx

Po tych operacjach w rejestrze ebx znajduje się aktualny czas przekonwertowany na milisekundy. Same operacje wyglądają podobnie jak przy konwersji z milisekund na czas, z tym, że zamiast dzielenia jest mnożenie.

.data:7FD37B95             call    ds:GetTickCount
.data:7FD37B9B             mov     ecx, 5265C00h       ;[1]
.data:7FD37BA0             cmp     eax, ecx
.data:7FD37BA2             jb      short loc_7FD37BB0
.data:7FD37BA4             xor     edx, edx
.data:7FD37BA6             div     ecx
.data:7FD37BA8             sub     word_7FD379A6, ax   ;[2]
.data:7FD37BAF             xchg    eax, edx
.data:7FD37BB0 loc_7FD37BB0:
.data:7FD37BB0             cmp     eax, ebx
.data:7FD37BB2             jb      short loc_7FD37BBD
.data:7FD37BB4             add     ebx, ecx
.data:7FD37BB6             dec     word_7FD379A6       ;[3]
.data:7FD37BBD loc_7FD37BBD:
.data:7FD37BBD             sub     ebx, eax
.data:7FD37BBF             mov     eax, ebx
.data:7FD37BC1             xor     edx, edx
.data:7FD37BC3             mov     ecx, 36EE80h
.data:7FD37BC8             div     ecx
.data:7FD37BCA             mov     word_7FD379A8, ax   ;wHour
.data:7FD37BD0             mov     eax, edx
.data:7FD37BD2             xor     edx, edx
.data:7FD37BD4             mov     ecx, 0EA60h
.data:7FD37BD9             div     ecx
.data:7FD37BDB             mov     word_7FD379AA, ax   ;wMinute
.data:7FD37BE1             mov     eax, edx
.data:7FD37BE3             xor     edx, edx
.data:7FD37BE5             mov     ecx, 3E8h
.data:7FD37BEA             div     ecx
.data:7FD37BEC             mov     word_7FD379AC, ax   ;wSecond

Niektóre z wyżej widocznych fragmentów wymagają chyba wyjaśnienia. Po pierwsze pobieramy ilość milisekund, która upłynęła od startu systemu (GetTickCount), następnie w punkcie [1] do rejestru ecx wędruje wartość 24*3600*1000 (5265C00h) czyli ilość milisekund w jednej dobie. Jeżeli GetTickCount zwróciła wartość większą od liczby milisekund w dobie (ciekawe czy ktoś wytrzymał 24h przy kompie bez zwisu ???), wtedy sprawdzamy ile dni upłynęło od włączenia komputera i w punkcie [2] odejmujemy tę liczbę od pola wDay struktury SYSTEMTIME. W przeciwnym wypadku (7FD37BB0) sprawdzany jest warunek czy liczba milisekund od startu systemu jest większa od aktualnego czasu przekonwertowanego na milisekundy, jeżeli tak to pole wDay struktury SYSTEMTIME jest zmniejszane o jeszcze jeden dzień, w przeciwnym wypadku następuje zwykłe odejmowanie i ponowna konwersja milisekund na zwykły czas. Zdaję sobie sprawę, że to co przed chwilą napisałem jest nieścisłe, zagmatwane i nie oddaje w pełni tego co naprawdę dzieje się w tym kodzie, ale liczę na to, iż czytający zada sobie trochę trudu i przeanalizuje to na własną rękę :).
Na sam koniec wystarczy jeszcze sformatować i wyświetlić obliczone dane, analogicznie jak w poprzednich przypadkach.

Jeszcze dla ścisłości przytoczę rozkład zmiennych w pamięci w tym konkretnym przypadku (dlaczego użyłem dwóch struktur SYSTEMTIME to wyjaśnię w zakończeniu):

7FD37900        fmtElapsedTime db "%02u:%02u:%02u",0
7FD3790F        fmtSysEnd db "%02hu:%02hu:%02hu   %02hu-%02hu-%04hu",0
7FD37935        tmp dw 0
7FD37937        strKernel db "KERNEL32",0
7FD37940        strGetLocalTime db "GetLocalTime",0
7FD37950        ddHours dd (?)
7FD37954        ddMinutes dd (?)
7FD37958        ddSeconds dd ?
7FD3795C        systime SYSTEMTIME <?>
        7FD3795C wYear
        7FD3795E wMonth
        7FD37960 wDayOfWeek
        7FD37962 wDay
        7FD37964 wHour
        7FD37966 wMinute
        7FD37968 wSecond
        7FD3796A wMilliseconds
7FD3796C        tempBuffer db 16d DUP (?)
7FD3797C        ddLocalTimeProc dd ?
7FD37980        destBuffer db 20h DUP (?)
7FD379A0        sttime SYSTEMTIME <?>
        7FD379A0 wYear
        7FD379A2 wMonth
        7FD379A4 wDayOfWeek
        7FD379A6 wDay
        7FD379A8 wHour
        7FD379AA wMinute
        7FD379AC wSecond
        7FD379AE wMilliseconds


.:: COŚ SIĘ KOŃCZY, COŚ SIĘ ZACZYNA ::.

Już myślałem, że w życiu nie skończę tego textu (z braku czsu), ale po dwudziestu dniach pisania średnio 2-3 zdania na dzień w końcu skończyłem. Z tego powodu do textu mogły (aczkolwiek nie musiały :) ) wkraść się pewne nieścisłości, dlatego jak zwykle wszelkie pytania kierować na adres podany u dołu strony :). Właściwie to pierwotnie text miał być większy i przy zamykaniu systemu po kliknięciu "OK" shell32 miał jeszcze zampisywać odpowiednio sformatowane dane do pliku (i właśnie dlatego użyłem dwóch struktur SYSTEMTIME. Niestety z braku czasu i chęci druga częśc na razie nie powstanie, chociaż nie wykluczam, że może w przyszłości (już na pewno nie w tym zinie) powstanie druga część. Do textu powinien być dołączony załącznik z projektem w MASM'ie, w którym cały kod dopisywany do shell'a jest napisany jako samodzielna aplikacja.

--
ReWolf
e-mail:rewolf@poczta.onet.pl
www:http://rewolf.pl

Załącznik: rewolf_reversing_wasted_time.zip