Skoro już każdy zobaczył co zrobimy, pora sprawdzić jakie ŚRODKI będą do tego potrzebne: 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. 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. 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. 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: 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 Pierwszym krokiem jaki wykona nasz kod będzie pobranie adresu funkcji GetLocalTime: Następnie przy pomocy funkcji GetTickCount i kilku pomocniczych działań wyliczymy czas, który upłynął od startu systemu: 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: 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: 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. 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. 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ę :).
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 :).

- 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
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
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
.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
- GetTickCount
- SetDlgItemTextA
- wsprintf
- GetProcAddress
- GetModuleHandle
- GetTickCount = call d,[7FCB1608]
- SetDlgItemTextA = call d,[7FCB13AC]
- wsprintf = call d,[7FCB1398]
- GetProcAddress = call d,[7FCB162C]
- GetModuleHandle = call d,[7FCB1564]
.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
.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
.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
.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
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
.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
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