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