PROLOG
Notatnik - jeden z najlepszych programów firmy M$. Niestety jak większość aplikacji rzeczonej firmy tak i Notatnik nie jest wolny od wad. Najbardziej uciążliwą (a zarazem najmniej ciekawą i najprostszą do usunięcia) jest brak skrótów klawiszowych (coprawda w wersji na WinXP już są). Drugą funkcją jaką postanowiłem dodać do tego nieocenionego programu będzie numer linii, w której znajduje się kursor (hmmm... to też chyba się pojawiło w XP). Na koniec zmienimy okienko About, aby wskazywało na to, kto dokonał zmian.
a. Ingredients:
- Notatnik (najlepiej wersja z Win98SE)
- IDA
- Hiew
- Resource Hacker (lub inny edytor zasobów)
- OLLY Debugger (do testowania najlepszy)
- LordPE (opcjonalnie)
EPISODE I: "Inside Resources"
Każdy, kto kiedyś wykorzystywał skróty klawiszowe w swoich programach (wyjątkiem jest Delphi, podejrzewam, że również VB ale tym się nigdy nie bawiłem) zna mechanizm na jakim są oparte. Gdzieś na początku programu wywołujemy funkcję LoadAccelerators:
HACCEL LoadAccelerators(
HINSTANCE hInstance, // handle of application instance
LPCTSTR lpTableName // address of table-name string
);
Natomiast później w message loop'ie (pętla komunikatów) musimy umieścić funkcję TranslateAccelerator:
int TranslateAccelerator(
HWND hWnd, // handle of destination window
HACCEL hAccTable, // handle of accelerator table
LPMSG lpMsg // address of structure with message
);
Czyli gdybyśmy chcieli dodać obsługę skrótów do Notatnika to mogłoby być trochę zabawy. No cóż, rzeczywistość wygląda trochę mniej ciekawie. Obie wyżej wymienione funkcje egzystują sobie w tabeli importów, mało tego są w opisany przeze mnie sposób zaimplementowane. Hmmm... Największa zagadka Micro$oftu: skoro zrobiliśmy obsługę skrótów to po co podpinać skróty pod elementarne funkcję programu ??? (Otwórz, Zapisz, Nowy itp.) Niezbadane są ścieżki programistów. Z tego wynika, że pierwszy etap naszej pracy sprowadzi się tylko do modyfikacji zasobów. Wystarczy w tablicy akceleratorów dodać według własnego uznania odpowiednie opcje i zmodyfikować menu.
EPISODE II: "Enter the Matrix"
W tej części ważną rolę odgrywa logistyczne myślenie. Czyli mówiąc prostszym językiem dobre rozplanowanie danych i kodu, który zostanie dodany. Najbardziej odpowiednim miejscem do wyświetlenia numeru lini byłaby belka statusu (STATUSBAR). Wersja którą ja omawiam takowej nie posiada, a moim celem nie jest modyfikacja tabeli importów. Zgodnie z popularnymprzysłowiem, "Jak się nie ma co się lubi, to się lubi co się ma", trzeba wykombinować coś innego. Po krótkim namyśle powinien na nas spłynąć odpowiedni pomysł - belka tytułowa. Wiemy już co chcemy zrobić wiemy gdzie no to IDA w dłoń. Proponuję poświęcić parę minut na podziwianie dośc ładnie napisanego kodu. Pierwszym krokiem będzie znalezienie miejsca, w którym dopiszemy nasz kod. Idziemy na koniec sekcji .text i widzimy ładny zapis:
.text:00404E9C align 200h
Sytuacja komfortowa - 200h (512d) bajtów na nowy kod - nie będzie problemów z brakiem miejsca. Teraz znajdziemy miejsce na nowe dane (nie będziemy przecież umieszczać zmiennych i string'ów w sekcji kodu). Podobnie jak w przypadku kodu idziemy na koniec sekcji .data i zachwyt nasz sięga zanitu, a to za sprawą poniższego:
.data:0040584C align 800h
Kolejnym wyborem jaki przed nami stoi jest decyzja, w którym miejscu program będzie przeskakiwał do naszego kodu. Musi to być tak zorganizowane aby w miarę zmiany lini na belce tytułowej była pokazywana aktualne wartość. Moją propozycją jest modyfikacja wcześniej wspomnianej pętli obsługującej wiadomości:
oryginalny kod .text:0040213B push eax ; lpMsg .text:0040213C call ds:GetMessageA .text:00402142 test eax, eax .text:00402144 jnz short loc_4020EB .text:00402146 call sub_401150
zmienimy na:
.text:0040213B push eax .text:0040213C jmp loc_404EA0 ; skok do miejsca .text:00402141 nop ; w którym rozpoczyna .text:00402142 nop ; się nasz kod .text:00402143 nop .text:00402144 nop .text:00402145 nop .text:00402146 call sub_401150 .text:00404EA0 loc_404EA0: .text:00404EA0 nop .text:00404EA1 call ds:GetMessageA ; pobranie widomości .text:00404EA7 test eax, eax ; czy WM_QUIT .text:00404EA9 jnz short loc_404EB0 ; jeżeli nie to nasz kod .text:00404EAB jmp loc_402146 ; jeżeli tak to koniec
Jak widać zmodyfikowany message loop działa tak jak poprzednio z tą różnicą, że jeżeli dostanie inną wiadomość niż WM_QUIT to wykonuje nasz kod (trochę duży ale postaram się wszystko opisać i objaśnić):
.text:00404EB0 loc_404EB0:
.text:00404EB0 pusha ;zapamiętanie rejestrów
.text:00404EB1 push offset dword_405A30
.text:00404EB6 push offset unk_405A34
.text:00404EBB push 0B0h ;EM_GETSEL
.text:00404EC0 push hWnd ;dword ptr [405004]
.text:00404EC6 call ds:SendMessageA
.text:00404ECC push 0
.text:00404ECE push dword_405A30
.text:00404ED4 push 0C9h ;EM_LINEFROMCHAR
.text:00404ED9 push hWnd ;dword ptr [405004]
.text:00404EDF call ds:SendMessageA
.text:00404EE5 inc eax
.text:00404EE6 cmp eax, dword_4058EB ;sprawdza czy zmienił
;się nr lini
.text:00404EEC jz loc_404FB3 ;jeżeli nie to
;kontynuuje msg loop
.text:00404EF2 mov dword_4058EB, eax ;jeżeli tak to
;zapamiętuje nową
;wartość
.text:00404EF7 push eax
.text:00404EF8 push offset unk_405A41
.text:00404EFD call sub_404FB9 ;_Int2DecStr
.text:00404F02 push 135h
.text:00404F07 push offset byte_4058F0 ;bufor na nazwę pliku
.text:00404F0C push offset FileName ;[4056A0] FileNameBuffer
.text:00404F11 call GetFileTitleA
.text:00404F16 test eax, eax
.text:00404F18 jz short loc_404F21
.text:00404F1A mov byte_4058F0, 0
.text:00404F21
.text:00404F21 loc_404F21:
.text:00404F21 xor eax, eax
.text:00404F23 xor ecx, ecx
.text:00404F25 dec ecx
.text:00404F26 mov edi, offset byte_4058F0
.text:00404F2B repne scasb
.text:00404F2D dec edi
.text:00404F2E mov esi, offset aNotatnikLine
;" - Notatnik - Line: "
.text:00404F33 mov ecx, 14h
.text:00404F38 rep movsb
.text:00404F3A mov esi, offset unk_405A41
.text:00404F3F mov ecx, 0Ah
.text:00404F44 rep movsb
.text:00404F46 push offset byte_4058F0
.text:00404F4B push hWndMain
.text:00404F51 call ds:SetWindowTextA
.text:00404F57 jmp short loc_404FB3
.text:00404F57 ; --------------------------------------------------------
.text:00404F59 dd 16h dup(0)
.text:00404FB1 db 2 dup(0)
.text:00404FB3 ; --------------------------------------------------------
.text:00404FB3
.text:00404FB3 loc_404FB3:
.text:00404FB3 popa
.text:00404FB4 jmp loc_4020EB
.text:00404FB9
.text:00404FB9 ; --------------- S U B R O U T I N E --------------------
.text:00404FB9
.text:00404FB9
.text:00404FB9 sub_404FB9 proc near ; CODE XREF: .text:00404EFDp
.text:00404FB9
.text:00404FB9 arg_0 = dword ptr 18h
.text:00404FB9 arg_4 = dword ptr 1Ch
.text:00404FB9
.text:00404FB9 push eax
.text:00404FBA push ebx
.text:00404FBB push ecx
.text:00404FBC push edx
.text:00404FBD push edi
.text:00404FBE mov eax, [esp+arg_4]
.text:00404FC2 mov edi, [esp+arg_0]
.text:00404FC6 mov byte ptr [edi+0Ah], 0
.text:00404FCA xor ecx, ecx
.text:00404FCC mov cl, 0Ah
.text:00404FCE xor ebx, ebx
.text:00404FD0
.text:00404FD0 loc_404FD0: ; CODE XREF: sub_404FB9+26j
.text:00404FD0 xor edx, edx
.text:00404FD2 div ecx
.text:00404FD4 add dl, 30h
.text:00404FD7 mov [edi+9], dl
.text:00404FDA dec edi
.text:00404FDB inc ebx
.text:00404FDC cmp bl, 0Ah
.text:00404FDF jnz short loc_404FD0
.text:00404FE1 xor ecx, ecx
.text:00404FE3 dec ecx
.text:00404FE4 mov al, 30h
.text:00404FE6 mov edi, [esp+arg_0]
.text:00404FEA repe scasb
.text:00404FEC
.text:00404FEC loc_404FEC: ; CODE XREF: sub_404FB9+3Dj
.text:00404FEC mov bl, [edi-1]
.text:00404FEF mov [ecx+edi+1], bl
.text:00404FF3 inc edi
.text:00404FF4 test bl, bl
.text:00404FF6 jnz short loc_404FEC
.text:00404FF8 pop edi
.text:00404FF9 pop edx
.text:00404FFA pop ecx
.text:00404FFB pop ebx
.text:00404FFC pop eax
.text:00404FFD retn 8
.text:00404FFD sub_404FB9 endp
Zanim omówię ten fragment kodu muszę nadmienić, że uchwyt głównego okna jest przechowywany w dword'zie pod adresem 405000, natomiast uchwyt kontrolki EDIT znajduje się pod adresem 405004. Najpierw pobieramy pozycję kursora względem początku textu za pomocą EM_GETSEL, pozycja jest zapisywana do pamięci pod adresem 405A30. Później owa pozycja kursora wędruje na stos jako argument dla SendMessage'a EM_LINEFORMCHAR. W rejestrze eax mamy teraz numer linii, w której znajduje się kursor licząc od 0, tak więc podnosimy wartość eax o 1. Kolejnym krokiem jest sprawdzenie czy numer, lini jest różny od numeru zapisanego w pamięci pod adresem 4058EB jeżeli tak to następuje skok do miejsca, w którym są przewracane rejestry. Krok ten jest podyktowany tym aby niepotrzebnie nie update'ować belki tytułowej przy każdym obiegu message loop'a. Jeżeli wartości są różne to nowa wartość jest zapamiętywana, a następnie konwertowana na string'a. Do konwersji użyłem może nie jakiejś super optymalnej ale własnej procedury sub_404FB9. Do tej procedury parametry przekazywane są przez stos, najpierw dword, a później offset bufora (11-znaków) w któym zostanie zapamiętany string. Następnie wywołujemy GetFileTitleA, która zwraca nam nazwę pliku wyodrębnioną ze ścieżki. Kolejne kroki mają na celu połączenie nazwy pliku frazy " - Notatnik - Line: " i numeru lini. Poniżej przytoczę rozkład zmiennych w pamięci:
Wprowadzone przeze mnie:
00405A30 dd <- przechowuje pozycję kursora względem początku textu
00405A34 dd <- j.w.
004058EB dd <- przechowuje numer linii, w której znajduje się kursor
00405A41 db 0Bh DUP (0) <- bufor przechowujący numer przekonwertowny
na strnig
004058F0 db 135h DUP (0) <- bufor, w którym następuje łączenie nowgo
tytułu okna
Wewnętrzne Notatnika:
00405000 dd <- uchwyt głównego okna
00405004 dd <- uchwyt kontrolki EDIT
004056A0 db ??? DUP <- bufor w którym jest przechowywana ścieżka do
aktualnie otwartego pliku
Pozostał nam już tylko ostatni krok czyli modyfikacja About. Notatnik korzysta z funkcji ShellAbout:
int ShellAbout (
HWND hWnd, // handle of parent window
LPCTSTR szApp, // title bar and first line text
LPCTSTR szOtherStuff, // other dialog text
HICON hIcon // icon to display
);
Sprawa prosta odnajdujemy jej wywołanie w i zmieniamy parametry szApp i szOtherStuff na offset'y do string'ów które chcemy aby były wyświetlane.
EPILOG
1. Jeżeli czegoś nie rozumiesz to napisz o tym do mnie (mail na dole).
2. Jeżeli uważasz, że to bez sensu to lepiej nie pisz.
3. Jeżeli uważasz, że to bez sensu ponieważ wersja z WinXP ma te wszystkie funkcje (i parę innych) to nie rozumiesz w czym tkwi istota rzeczy.
4. Tekst ten powstał przy pomocy zmodyfikowanej wersji Notanika, co jest dowodem, że te wszystkie zmiany działają.
5. Do tekstu powinna być dołączona zmodyfikowana wersja Notatnika (żeby każdy mógł ją sobie zdeassemblować).
6. Greetz to all members of HTB tEAM
----------------
ReWolf
e-mail:rewolf@poczta.onet.pl
www:http://rewolf.pl
Załącznik: rewolf_rozne_notatnik_text.zip