niedziela, 10 grudnia 2017

Złamany wąż. Część Pierwsza.

Hardwired wymiata, to tak tytułem wstępu. A skoro powiedziało się 0x41 to wypadałoby powiedzieć i 0x42....


Drogi Czytelniku, napewno nie raz zdarzytło Ci się korzystać z tak zwanych KeyGenów (od ang. Key Generator - generatora kluczy),
dla tych co nie wiedzą czymże jest owy generator spieszę z tłumaczeniem; ale najpierw krótka bajeczka:

Dawno, dawno temu. Za siódmą rzeką i dziewiątą górą, w odległej krainie żyli sobie dobrzy programiści, którzy poświęcali czas na tworzenie swoich
aplikacji. Czas upływał im radośnie i beztrosko - wypuszczali oni swoje aplikacje , jednak ze względu na to ile czasu poświęcili na ich stworzenie
nie oddawali ich tak poprostu, o nie! Co to, to nie! Ze względu na czas, który włożyli w proces ich kreacji chcieli dostać coś w zamian -
- nie koniecznie połowę królestwa i nerkę księzniczki - coś bardziej trywialnego, jednak równie porządanego. Chcieli oni bowiem srebrników.
Marzyły im się garnce pełne złota (z pewnością tak było - gwarantuję). I tutaj pojawił się problem; bowiem jak przekonać lud do tego aby
owe garnce im napełnił? Hmm... trzeba było zaprezentować im swoje dzieło - ale jak to zrobić, żeby lud rzeczywiści chciał zaszeleścić banknotami?
Pewnego dnia pewien stary mentor programistów rzekł : patrz, ale nie dotykaj, smakuj, ale nie połykaj. Wśród programistów zawrzało - jak bowiem
urzeczywistnić taką wizję i przekompilować ją do programu? (: Główkowali oni długo i długo, aż w końcu jeden z nich wpadł na pomysł, ażeby
działanie ich tworów ograniczyć - jednak w taki sposób, żeby użytkownik odczuł wspaniałość owego dzieła (taste, but don't swallow [: ),
mógł zobaczyć co tak naprawdę program potrafi i po jakimś czasie wyświetlić mu komunikat w stylu: "Hola hola użytkowniku!
Jeśli jesteś zadowolony z naszej aplikacji to spraw, ażebyśmy byli zadowoleni z Ciebie!". I w taki oto sposób narodziła się idea
oprogramowania shareware/trial, która to obecna jest po dziś dzień.

Gdzieś w zasięgu działania dobrych programistów, żyli jeszcze inni programiści, znacznie lepiej
obeznani ze swoim rzemiosłem. Kiedy doszły ich słuchy o działaniach ich kolegów - postanowili działać.
Trudno powiedzieć, co tak naprawdę nimi kierowało i jakie były ich pobudki. Może po prostu
chcieli oni udowodnić swoją wyższość? Trzebaby było ich samych zapytać. Ich działania
przyniosły efekty - ze szkodą dla dobrych programistów oczywiście. Nastała wojna, bowiem
niewiadomo jak bardzo wymyślne i egzotyczne metody do zabezpieczenia programów były stosowane
w końcu i tak musiały się poddać. A im lepsze i silniejsze było dane zabezpieczenie
tym większy szacunek zyskiwał programista, który je powstrzymał. Wytworzył się swoisty
trend, na to kto pierwszy pokona daną aplikację. W ten sposób
powstała idea Cracku - pozbawionej zabezpieczeń aplikacji i dostępnej całkowicie
za darmo - która to obecna jest po dziś dzień.

Pomysłów na ograniczanie działalności jest chyba tyle co samych programistów - nikt nie zna wszytkich, bowiem codzień powstają nowe.
Jednym z nich (tym, na którym skupiam się w tym artykule) było oddanie w 100% oryginalnej kopii aplikacjii jednak aby cieszyć się jej
funkcjonalnością trzeba ją zarejestrować. Aby to zrobić potrzebne nam będą nazwa oraz odpowiedni klucz.
Najpierw prześledzimy sobie jak wygląda cały proces rejestracji tj. jak aplikacja "stwierdza" czy wprowadziliśmy
odpowiedni klucz do naszej nazwa użytkownika(?), a następnie postaramy się odtworzyć ten proces i na podstawie
tej wiedzy wygenerować własny klucz do wprowadzonej przez nas nazwy użytkownika. Pure Reverse Engineering :D


 Oto jakie możliwości na rejestrację daje nam jej producent (skopiowane z readme.htm) :

------------START-----------------------------------------[README.HTM]--------------------------------------------------
[...]
4. REGISTRATION

4.1 Registration Overview
    AxySnake is distributed on a shareware basis. You can register your copy of AxySnake at a cost of $14.95 (US). On payment approval, we will send you the registration key that will remove all the limitations.
    Your registration will be valid for all future versions of AxySnake. The registration fee will license AxySnake for use on one computer in your home, and on one computer at your workplace.
    If you wish to use AxySnake on several computers, you should purchase several single licenses.
See also SOFTWARE LICENSE AGREEMENT.


4.2 Order Online ($14.95)
    You can purchase AxySnake via the World Wide Web. This is the fastest and the easiest way. All registrations use SECURE protocols. It is impossible for a third party to intercept your credit card information. Important: when you are filling in the order form, please check twice that your e-mail address is correct! If your e-mail address is entered incorrectly, we will be unable to send you the registration key. We do not give your personal information or email address to anyone; it is for our own use only. 

    Click here to purchase AxySnake online.


4.3 Order by Phone ($17.95)
   You can purchase AxySnake by phone. "Register Now!" has an additional $3.00 (US) order handling fee for the phone order.
   Please DO NOT use these numbers for the technical support questions, they are for the ordering purpose only.

    Please call "Register Now!" :

    877-353-7297 (U.S. and Canada; toll-free) 
    425-392-2294 (Other countries; regular)
    Have your credit card information ready.
    Product name: AxySnake
    Product ID: 4174-5

4.4 Order by Fax ($17.45)
    You can order AxySnake by fax. 
    "Register Now!" has an additional $2.50 (US) order handling fee for fax orders.

    Proceed to the AxySnake order form:
    https://www.regnow.com/softsell/nph-softsell.cgi?item=4174-5

    Fill in the form and choose the "Payment Method:" as:   "Fax Order - Credit Card ( $ 2.50)" 

    Press "Finalize Order". 

    Write down the number of your order.

    On your fax transmission, please reference your order number, and include the following:

    Order Number
    Type of Credit Card 
    Name on Credit Card
    Credit Card Number    /* trzeba być skończonym debilem, żeby komuś wysłać takie dane (:  */
    Expiration Month and Year
    Fax numbers :
        888-353-7276 (U.S. and Canada; toll-free) 
        425-392-0223 (Other countries; regular)

4.5 Order by Mail  ($17.45)
    You can order AxySnake by mail. 
    "Register Now!" has an additional $2.50 (US) order handling fee for mail orders.
    Proceed to the AxySnake order form:
    https://www.regnow.com/softsell/nph-softsell.cgi?item=4174-5

    Fill in the form and choose the "Payment Method:" as:   "Mail Order - Check/Money Order ($ 2.50)" 

    Press "Finalize Order". 

    Write down the number of your order.

    Please make your check or money order out to "Register Now" for the amount of  $17.45 USD.
    The funds must be in US Dollars and drawn on a US bank. Please include your order number on your check, and send it to:

    Register Now!
    PO Box 1816
    Issaquah, WA 98027
    United States of America

------------END--------------------------------------------[/README.HTM]--------------------------------------------------


Trzeba przyznać, że z taką wyobraźnią pan Vladimir powinien pisać książki, a nie programy :)
Sam proces rejestracji jest chyba bardziej skomplikowany, niż to co opisuję w tym artykule.

A zatem do dzieła :

Co będzie nam potrzebne, aby "zarejestrować" AxySnake-a ?
Otórz potrzebować będziesz Czytelniku następujących programów:
 -OllyDbg (debugger, który w klarowny sposób odkryje przed nami meandry aplikacji, osobiście przezemnie polecany - dowolna wersja)
 -DevCpp  (kompilator języka C [właściwie to gcc, ale nie czepiajmy się] - dowolna wersja)
 -Python  (interpreter, opcjonalnie do wykorzytania przykładu w tym języku - dowolna wersja)

Wszystkie przykłady prezentowane przezemnie kompilowane były na Windowsie XP SP3.

Po uruchomieniu aplikacji ukazuje się nam taki oto widok:


Pierwsze co rzuca się w oczy to czerwony wyboldowany, brzydki napis "UNREGISTERED !" - postaramy się go pozbyć (:
Następną rzeczą która rzuca nam się w oczy jest pole poniżej z napisem "Registration" i to właśnie od tego zaczniemy naszą zabawę.
Po kliknięciu w owy "przycisk" ukazuje się nam coś takiego :

Czymś co powinno zwrócić naszą uwagę są EditBoxy obok napisu : "Registration Name" i "Registration Code".
Otwieramy AxySnak-a w Ollym i oto co nam się ukazuje (piękności [: ) :
Spróbujmy wpisać coś w/w pola :
u mnie jest to 'VLN' w Registration Name i '1234567890' w Registration Code.

Dziwne... pojawia się okno informujące nas o tym że wpisaliśmy błędne dane, jak możemy zauważyć nie jest to MessageBox,
więc raczej breakpoint na tą funkcję nic nam nieda, zresztą wywołanie takie nie występuje w głownym module,
a zatem będzie to strata czasu.

Co można sprawdzić poprzez:
RMB -> Search for -> All intermodular calls


Spróbujmy inaczej - trochę "na czuja" - jak możemy zauważyć w folderze z grą znajduje się DLLka o nazwie 'proton.dll'
w Callach widzimy dosyć sporo odwołań do niej,
ciekawe w jaki sposób program z niej korzysta... i co w niej siedzi, a zatem klikamy na Alt+E i wybieramy:


teraz zobaczmy cóż za napisy się w niej kryją; moją uwagę szczególnie przykuły te oto perełki :

RMB -> Search from -> All referenced text strings

1000FA0C   PUSH proton.1002CB4C                      ASCII "REG_DIALOG"
1000FA41   PUSH proton.1002CB40                      ASCII "REG_FAULT"
1000FA5F   PUSH proton.1002CB4C                      ASCII "REG_DIALOG"
1000FA97   PUSH proton.1002CB38                      ASCII "REG_OK"
[...]
1000FCD6   PUSH proton.1002CB4C                      ASCII "REG_DIALOG"
1000FD0D   PUSH proton.1002CB40                      ASCII "REG_FAULT"
1000FD2A   PUSH proton.1002CB4C                      ASCII "REG_DIALOG"
1000FD6C   PUSH proton.1002CB38                      ASCII "REG_OK"
[...]
1001069A   PUSH proton.1002CBA8                      ASCII "RegName"
100106EB   PUSH proton.1002CBA0                      ASCII "RegCode"
10010778   PUSH proton.1002CBA8                      ASCII "RegName"
10010795   PUSH proton.1002CBA0                      ASCII "RegCode"

Na każdą z nich dajemy BreakPoint (F2) i startujemy program (F9):

Na początku program zatrzymuje się pod 1001069A. I co my tu mamy?

1001068D  |. 52                         PUSH EDX                                                           ; /pBufSize
1001068E  |. 8B5424 10           MOV EDX,DWORD PTR SS:[ESP+10]             ; |
10010692  |. 8D4C24 14           LEA ECX,DWORD PTR SS:[ESP+14]              ; |
10010696  |. 50                          PUSH EAX                                                          ; |Buffer
10010697  |. 51                          PUSH ECX                                                          ; |pValueType
10010698  |. 6A 00                    PUSH 0                                                                ; |Reserved = NULL
1001069A  |. 68 A8CB0210      PUSH proton.1002CBA8                                    ; |ValueName = "RegName"
1001069F  |. 52                          PUSH EDX                                                         ; |hKey
100106A0  |. C74424 70 3F00>MOV DWORD PTR SS:[ESP+70],3F               ; |
100106A8  |. FFD3                    CALL EBX                                                        ; \RegQueryValueExA

Co jest odpowiednikiem C:
RegQueryValueEx(hKey(3F),"RegName",0,ECX,EAX

Jest to wywołanie funkcji, która sprawdza czy istnieje klucz w rejestrze, po niej mamy:

100106AA  |. 85C0           TEST EAX,EAX
100106AC  |. 75 28          JNZ SHORT proton.100106D6

Jeśli funkcja zwróci 0 to nic się nie dzieje , funkcja zwraca EAX = 00000002 a zatem skok pod proton.100106D6 jest wykonywany.

Prawdopodobnie program po wprowadzeniu poprawnych danych rejestracyjnych utworzy klucz w rejestrze o nazwie RegName i RegCode,
narazie takich danych nie ma, a zatem wykonywany jest skok, a zatem sam crack do tej aplikacji ograniczałby się do utworzenia
dwóch kluczy w rejestrze (: Narazie nas to nie interesuje i nie jest to celem tego artykułu, a zatem możemy pozbyć się BreakPointów spod
1001069A,100106EB,10010778,1001795 (Alt+B i spacją ustawiamy na Disabled).

Taraz F9 i klikamy na Registration i...  jesteśmy w domu :]

Teraz ostrożnie.... na piechotkę...

Po zatrzymaniu na pierwszym bp którym jest odłożeniem na stosie napisu "REG_DIALOG"
1000FA0C   . 68 4CCB0210    PUSH proton.1002CB4C                     ;  ASCII "REG_DIALOG"

używamy F8, żeby zrobić krok i przejść do następnej instrukcji, przy instrukcjii CALL  EDI () program startuje normalnie
pozwalająć nam na uzupełnienie dwóch DialogBoxów z rejestracji. Wypadałoby je uzupełnić; u mnie są to ponownie:
'VLN' i '0123456789', klikamy na 'Registration' i spowrotem zostajemy przeniesieni do Ollego. W tym momencie
możemy przypuszczać, że gdzieś w Zrzucie Pamięci program będzie przechowywał te dane. Kroczymy dalej.

Nasępnie do EBX wpisywany jest adres funkcji Sleep.
Czymś co powinno zwrócić naszą uwagę jest ten fragment kodu:

1000FA23   > E8 580E0000    CALL proton.10010880
1000FA28   . 85C0           TEST EAX,EAX
1000FA2A   . 75 42          JNZ SHORT proton.1000FA6E

A co my tutaj mamy?
Otóż widzimy wywołanie jakiejś funkcji, która coś zwraca (to co dajemy po return w C tutaj znajduje się w EAX)
a zatem sprawdzamy co zwróciła funkcja spod offsetu 10010880 i czy przypadkiem nie jest to zero,\
jeśli nie jest to wykonywany jest skok pod 1000FA6E. Taka funkcja(już wiemy, że to nie jest procedura)
w okolicy wprowadzania danych jest warta uwagi. Rzućmy więc na nią okiem :


Bingo! Widzimy tutaj, że ewidentnie coś jest robione ze wprowadzonymi przez nas danymi, a zatem tą funkcję trzeba prześledzić
bardzo dokładnie. No to zaczynamy (to jest to, co tygryski lubią najbardziej :) ),
postaramy się napisać odwzorowanie tej funkcji w C, żeby dokładnie prześledzić jak przebiega proces rejestracji.
[będę robił to w ten sposób, że najpierw będę wstawiał kod w asemblerze, a poźniej go tłumaczył na C,
 jeżeli któraś instrukcja nie będzie miała odpowiednika w C, wytłumaczenie będzie odkomentowane w ASMie]

-----------------------------------------------------------------------------------------------------

[ASM]
10010880  /$ A1 98A40310      MOV EAX,DWORD PTR DS:[1003A498]
10010885  |. 85C0                      TEST EAX,EAX
10010887  |. 0F84 E1000000     JE proton.1001096E                    ;Pierwszy_Skok
1001088D  |. 8B0D 9CA40310  MOV ECX,DWORD PTR DS:[1003A49C]
10010893  |. 85C9                       TEST ECX,ECX
10010895  |. 0F84 D3000000      JE proton.1001096E                  ;Drugi_Skok

[C]
void EAX = 0x1003A498;
void ECX = EAX+0x04;

if((short*)*EAX==0) goto Pierwszy_Skok;
if((short*)*ECX==0) goto Drugi_Skok;

--------------------------------------------------------------------------------------------------------

Po 'prze-kroczeniu' tego fragmentu kodu, zauważamy, że żaden skok nie został wykonany.
Dziwnym zbiegiem okoliczności, zauważamy, że wartości w EAX i ECX ustawiane są kolejno na 0x03 i 0x0A hmm...
zupełnie jak długości wprowadzonych przeze mnie danych... ciekawe.
A zatem na początku naszego odwzorowania powinno znaleźć się coś takiego :

--------------------------------------------------------------------------------------------------------

char *Reg_Name = "VLN";
char *Reg_Key = "0123456789";

unsigned short NameLen = strlen(Reg_Name);
unsigned short KeyLen = strlen(Reg_Key);

if(NameLen==0) goto Pierwszy_Skok;
if(KeyLen==0) goto Drugi_Skok;

--------------------------------------------------------------------------------------------------------

Nasepnie naszym oczom ukazuje się taki oto fragment kodu:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

1001089B  |. 68 18F91210              PUSH proton.1012F918                                    ; null-string
100108A0  |. 68 38A40310             PUSH proton.1003A438                                   ; ASCII "VLN"
100108A5  |. C680 38A40310 >     MOV BYTE PTR DS:[EAX+1003A438],0        
100108AC  |. E8 4FFFFFFF           CALL proton.10010800                                    ; LoginCheck(Login,null-string);
100108B1  |. 83C4 08                     ADD ESP,8                                                        ; Usuwamy ze stosu bufor i Login
100108B4  |. 85C0                         TEST EAX,EAX
100108B6  |. 0F84 B2000000        JE proton.1001096E                                           ; Pierwszy_Skok

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Na początku widzimy, że na stosie lądują dwa offsety i wywoływana jest jakaś funkcja.
Dobrze byłoby wiedzieć co znajduje się pod 1012F918, a zatem RMB->Follow in Dump->Immediate Constant
Jak widzimy w Memory Dump, jest to pusty ciąg znaków. A zatem na stosie odkładany jest pusty ciąg znaków i login.
Następnie widzimy, do tuż za loginem ląduje 0, a zatem null-byte na koniec loginu. I wywoływana jest
funkcja spod offsetu 10010800, której zwracana wartość jest przyrównywana do zera i jeśli jest to zero
to wykonywany jest skok. Funkcję nazwałem sobie roboczo LoginCheck (wszak robi ona coś z loginem) czyli w C wygląda to mniej więcej tak :

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

char* OffsetLogin = 0x1003A438;
char* NullString = 0x1012F918;
OffsetLogin[EAX] = 0x00;
LoginCheck(OffsetLoginu,NullString);

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


W naszym odwzorowaniu powinno znaleźć się zatem:

----------------------------------------------------------------
char NullString[128] = {0};  /*Roboczo dałem tutaj 128 bitów*/
RegName[NameLen]=0x00;
if(CheckLogin(RegName,NullString)==0) goto Pierwszy_Skok;
----------------------------------------------------------------

Przyjrzyjmy się zatem, co kryje się pod proton.10010800:


Mamy tutaj coś takiego (początek funkcji[do return]) :

10010800  /$ 55                   PUSH EBP                                                  ; Zachowujemy EBP
10010801  |. 8B6C24 08      MOV EBP,DWORD PTR SS:[ESP+8]      ; Do EBP leci wartość ze stosuspod wartości [ESP+8]
                  |                                                                                              ; [EBP]=EBP         [EBP+4] = Return to 100108B1
                  |                                                                                             ; [EBP+8] = "VLN"  [EBP+0x12] = null-string
10010805  |. 8BCD            MOV ECX,EBP                                           ; [ECX] = "VLN"
10010807  |. 8A45 00        MOV AL,BYTE PTR SS:[EBP]                  ; do AL 1 bit spod EBP,czyli poprostu 'V'
1001080A  |. 84C0            TEST AL,AL                                                ;
1001080C  |. 74 10           JE SHORT proton.1001081E                        ; jeśli ECX[0]==0x0 to skok do LoginEnd
                   |                                                                                            ; do {
1001080E  |> 3C 20          /CMP AL,20                                                 ; czy AL==0x20 (0x20 to kod ASCII ' ')
10010810  |. 74 04            |JE SHORT proton.10010816                       ; jeśli tak to skok pod A | continue; skok do A
10010812  |. A8 80           |TEST AL,80                                                ; [raczej jest tutaj sprawdzenie czy to nie przypadkiem znak] Druga_Czesc
10010814  |. 74 0C           |JE SHORT proton.10010822                       ; jeśli tak to skok
10010816  |> 8A41 01     |MOV AL,BYTE PTR DS:[ECX+1]            ; do AL znak spod adresu ECX+1 (Kolejny bit z loginu do AL) A:
10010819   |. 41               |INC ECX                                                      ; ECX = ECX+1
1001081A  |. 84C0           |TEST AL,AL                                               ; Czy AL równy 0
1001081C  |.^75 F0          \JNZ SHORT proton.1001080E                   ; jeśli tak to skok pod 1001080E
                  |                                                                                            ; while(AL==0){
1001081E  |> 33C0        XOR EAX,EAX                                             ; zerujemy EAX   ;LoginEnd
10010820  |. 5D             POP EBP                                                         ; przywracamy EBP
10010821  |. C3             RETN                                                              ; Return (CALL EBP)


A zatem do naszego odwzorowania trzeba dodać funkcję LoginCheck która zaczyna się w następujący sposób :

----------------------------------------------------------------------------------
int LoginCheck(char* Login,char* buffer) /*EAX ma 4 bity, dlatego funkcja jest typu int*/
{
    int i = 0x00;
    char a = Login[0];
    if(a==0x00) LoginEnd;
    do
    {
        if(a==0x20) goto A;
        if(a!=0x80) goto B;
        A:
        a=Login[i+1];
        ++i;
    }
    while(a==0);
   
    LoginEnd:
    return 0;

-----------------------------------------------------------------------------------


Druga część tej funkcji jest o wiele ciekawsza, bowiem na podstawie loginu
generowany jest specjalny 16 bitowy hash.





10010822  |> 53                   PUSH EBX                                                ;Zachowanie EBX (licznik głownej pętli)
10010823  |. 56                    PUSH ESI                                                  ;Zachowanie ESI (licznik null-stringa)
10010824  |. 57                    PUSH EDI                                                 ;Zachowanie EDI
10010825  |. 8B7C24 18      MOV EDI,DWORD PTR SS:[ESP+18]   ;null-string do EDI
10010829  |. 8BCF               MOV ECX,EDI                                         ;null-string(jego offset) do EDX
1001082B  |. 33C0               XOR EAX,EAX                                         ;zerujemy EAX
1001082D  |. 33F6               XOR ESI,ESI                                             ;zerujemy ESI
1001082F   |. 8BD5              MOV EDX,EBP                                        ;do EDX wędruje offset loginu
10010831   |. 8901                MOV DWORD PTR DS:[ECX],EAX      ;zerowanie 4 bajtów spod DataSegment[null-string]=\x00\x00\x00\x00
10010833   |. 33DB              XOR EBX,EBX                                           ;zerowanie EBX
10010835   |. 8941 04           MOV DWORD PTR DS:[ECX+4],EAX    ;kolejne 4 bajty null-stringa wyzerowane
10010838   |. 8941 08         MOV DWORD PTR DS:[ECX+8],EAX    ;następne
1001083B  |. 8941 0C         MOV DWORD PTR DS:[ECX+C],EAX    ;i kolejne, to pozwala przypuszczać, że nullstring ma 16 bajtów
1001083E  |. 8BC7             MOV EAX,EDI                                            ;null-string do EAX
                  |;                                                                                             [EDX = "VLN"] , [EAX = "0000000000000000"] , [EDI=null-string] , EAX=EDI
                  |;                                                                                             do{
10010840  |> 803A 00        /CMP BYTE PTR DS:[EDX],0                  ;czy bit loginu jest równy 0
10010843  |. 75 03             |JNZ SHORT proton.10010848                   ;jeśli tak, to skok
10010845  |. 40                  |INC EAX                                                    ;EAX=EAX+1
10010846  |. 8BD5             |MOV EDX,EBP                                        ;wracamy na początek loginu
10010848  |> 8A0A           |MOV CL,BYTE PTR DS:[EDX]              ;do CL leci znak z loginu
1001084A  |. 80F9 20        |CMP CL,20                                                ;jeśli to spacja
1001084D  |. 74 14            |JE SHORT proton.10010863                       ;to skok do NEXT
1001084F  |. F6C1 80        |TEST CL,80                                               ;jeśli to nie litera,
10010852  |. 75 0F             |JNZ SHORT proton.10010863                  ;to skok do NEXT
10010854  |. 0008              |ADD BYTE PTR DS:[EAX],CL               ;dodaj do null-stringa spod indeksu EAX, wartosć spod CL (znaku z loginu)
10010856  |. 40                  |INC EAX                                                  ;EAX=EAX+1
10010857  |. 46                  |INC ESI                                                    ;ESI=ESI+1
10010858  |. 83FE 10        |CMP ESI,10                                             ;jeśli ESI!=0x16 to skok (a zatem null-string ma 16 bitów)
1001085B  |. 75 07            |JNZ SHORT proton.10010864               ;skok do NEXT
1001085D  |. 33F6            |XOR ESI,ESI                                          ;zerujemy ESI
1001085F   |. 8BC7            |MOV EAX,EDI                                     ;wracamy na początek null-stringa
10010861   |. EB 01           |JMP SHORT proton.10010864              ;skok bezwarunkowy do
                   | NEXT:
10010863   |> 4B               |DEC EBX                                                ;EBX=EBX-1
10010864   |> 42               |INC EDX                                                 ;EDX=EDX+1
10010865   |. 43                |INC EBX                                                 ;EBX=EBX+1
10010866   |. 83FB 6F      |CMP EBX,6F                                          ;porównaj EBX i 0x006F
10010869   |.^7C D5          \JL SHORT proton.10010840                 ;jeśli niższe to skok
                   |;                                                                                       }while(EBX<0x6f);
1001086B  |. 5F             POP EDI                                                      ;ściągamy ze stosu EDI
1001086C  |. 5E             POP ESI                                                      ;ściągamy ze stosu ESI
1001086D  |. 5B             POP EBX                                                   ;ściągamy ze stosu EBX
1001086E  |. B8 01000000    MOV EAX,1                                      ;funkcja zwróci 1
10010873  |. 5D             POP EBP                                                    ;oryginalna ramka stosu
10010874  \. C3             RETN                                                         ;skok do return (:


A zatem po przeanalizowaniu funkicji LoginCheck wiemy już jakie powieżono jej zadania:
1) Pomija ona wszystkie spacje i znaki które nie są literami A-Z i cyframi 2-9
2) Generuje unikatowy 16 bitowy hash na podstawie loginu
3) W przypadku powodzenia (z punktu widzenia programu) funkcja zwraca 1 / w przypadku błędu 0

Nasze odwzorowanie funkcji LoginCheck powinno zatem zostać uzupełnione o odpowiednie instrukcje.
Cała funkcja LoginCheck po odwzorowaniu prezentuje się więc następująco :


---------------------------------------------------------------------------------
int LoginCheck(char *Login,char *Hash)
{
    unsigned short i_loop = 0;
    unsigned short i_hash = 0;
    unsigned short i_login = 0;
    unsigned short esi =0 ;
    unsigned char CL;
   
    int i = 0x00;
    char a = Login[0];
    if(a==0x00) goto LoginEnd;
    do
    {
        if(a==0x20) goto A;
        goto B;
        A:
        a=Login[i+1];
        ++i;
    }
    while(a==0);
   
    LoginEnd:
    return 0;
   
    B:
   
    /* Zerowanie buffera pomijamy ze względu zdeklarowania tego obszaru pamięci na same zera(\x00)
       jeśli jednak komuś ma spędzać to sen z powiek, to zalecam użycie np. ZeroMemory :)*/
   
    do
    {
          if(Login[i_login]==0)
          {
             i_hash=i_hash+1;
             i_login=0;
         
          }
          CL = Login[i_login];  
          if(CL==' ') goto C;
         
          Hash[i_hash] = Hash[i_hash]+CL;
          i_hash = i_hash+1;
          esi=esi+1;
         
          if(esi==0x10)
          {
              esi=0;
              i_hash=0;
              goto CONTINUE;
          }
          else goto CONTINUE;
    C:
          i_loop=i_loop-1;
    CONTINUE:
          i_login=i_login+1;
          ++i_loop;
    }while(i_loop<0x006F);
   
    return 1;
}
------------------------------------------------------------------------------

Aby móc określić, czy nasza funkcja działa w 100% tak samo jak oryginał trzeba porównać wyniki obu.
W tym przypadku będzie to sprawdzenie czy oba Hashe są identyczne, żeby to zrobić posłużymy się
napisaną przeze mnie procedurą, której zadaniem będzie wypisanie Hashu w postaci hexagonalnej,
o jakże sprytnej nazwie HexPrint, powinna się ona znaleźć w naszym odwzorowaniu.

/* Funkcja ta jeszcze nie raz może się okazać pomocna, niekoniecznie w tym przykładzie.
   Myślę zatem, że warto zachować ją gdzieś w kuluarach swojego dysku i... czekać :) */

  
Jej argumenty to; adres do obszaru pamięci z którego czytamy, długość czytanego obszaru i separator którym
oddzielamy poszczególne znaki.

-------------------------------------------------------------------------------

void HexPrint(unsigned char *ToPrint,unsigned short len,char sep)
{
     printf("________________________________________________________________\n");
     printf("\t[Hex Print]\t[0x%08X]\t[%d bytes]\n",ToPrint,len);
     printf("----------------------------------------------------------------\n");
     printf(" Offset\t%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c\n",sep,0,sep,1,sep,2,sep,3,sep,4,sep,5,sep,6,sep,7,sep,8,sep,9,sep);
     printf("----------------------------------------------------------------");
     unsigned short i,j;
    
     for(i=0,j=0;i<len;++i)
     {
         if(i%10==0)
         {
            printf("\n\r %c%04X%c\t%c",sep,j,sep,sep);
            j=j+0x000A;
         }
         printf("0x%02X%c",ToPrint[i],sep);
        
        
     }
     printf("\n----------------------------------------------------------------");
     printf("\n________________________________________________________________\n");
}

--------------------------------------------------------------------------------


Całość powinna prezentować się następująco:
------------START-----------------------------------------[LoginCheckTest.c]--------------------------------------------------
#include <stdio.h>
#include <stdlib.h>

void HexPrint(unsigned char *ToPrint,unsigned short len,char sep)
{
     printf("________________________________________________________________\n");
     printf("\t[Hex Print]\t[0x%08X]\t[%d bytes]\n",ToPrint,len);
     printf("----------------------------------------------------------------\n");
     printf(" Offset\t%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c\n",sep,0,sep,1,sep,2,sep,3,sep,4,sep,5,sep,6,sep,7,sep,8,sep,9,sep);
     printf("----------------------------------------------------------------");
     unsigned short i,j;
    
     for(i=0,j=0;i<len;++i)
     {
         if(i%10==0)
         {
            printf("\n\r %c%04X%c\t%c",sep,j,sep,sep);
            j=j+0x000A;
         }
         printf("0x%02X%c",ToPrint[i],sep);
        
        
     }
     printf("\n----------------------------------------------------------------");
     printf("\n________________________________________________________________\n");
}

int LoginCheck(char *Login,char *Hash)
{
    unsigned short i_loop = 0;
    unsigned short i_hash = 0;
    unsigned short i_login = 0;
    unsigned short esi =0 ;
    unsigned char CL;
   
    int i = 0x00;
    char a = Login[0];
    if(a==0x00) goto LoginEnd;
    do
    {
        if(a==0x20) goto A;
        goto B;
        A:
        a=Login[i+1];
        ++i;
    }
    while(a==0);
   
    LoginEnd:
    return 0;
   
    B:
   
    /* Zerowanie buffera pomijamy ze względu zdeklarowania tego obszaru pamięci na same zera(\x00)
       jeśli jednak komuś ma spędzać to sen z powiek, to zalecam użycie np. ZeroMemory :)*/
   
    do
    {
          if(Login[i_login]==0)
          {
             i_hash=i_hash+1;
             i_login=0;
         
          }
          CL = Login[i_login];  
          if(CL==' ') goto C;
         
          Hash[i_hash] = Hash[i_hash]+CL;
          i_hash = i_hash+1;
          esi=esi+1;
         
          if(esi==0x10)
          {
              esi=0;
              i_hash=0;
              goto CONTINUE;
          }
          else goto CONTINUE;
    C:
          i_loop=i_loop-1;
    CONTINUE:
          i_login=i_login+1;
          ++i_loop;
    }while(i_loop<0x006F);
   
    return 1;
}

int main(void)
{
    char LoginBuffer[16]={0};
    LoginCheck("VLN\0",LoginBuffer); /*Login może być dowolny, należy tylko pamiętać o null-byte na końcu */
    HexPrint(LoginBuffer,16,'|');
    system("pause");
    return 0;
}
------------END-------------------------------------------[/LoginCheckTest.c]--------------------------------------------------

Jak widać na poniższym obrazku oba hashe są identyczne, a zatem możemy uznać odwzorowanie
za dokładne (chociaż w oryginalnej funkcji można doszukać się pewnych błędów).

Teraz pora przeanalizować fragment z przed skoku i zobaczyć co jeszcze pan Vladimir nam tutaj zgotował :)


100108B1  |. 83C4 08               ADD ESP,8                                              ; czyścimy stos z argumentów LoginCheck
100108B4  |. 85C0                    TEST EAX,EAX                                      ; sprawdzamy co zwróciła funkcja LoginCheck
100108B6  |. 0F84 B2000000   JE proton.1001096E                                 ; jeśli zero to skaczemy (:
100108BC  |. 68 78A40310      PUSH proton.1003A478                           ;  ASCII "0123456789" ;Klucz ląduje na stosie
100108C1  |. E8 EAFEFFFF    CALL proton.100107B0                            ; i wywołanie kolejnej funkcji, ze względu na jej argumenty nazwiemy ją KeyCheck










A teraz pora na analizę :)

100107B0  /$ 8B4C24 04          MOV ECX,DWORD PTR SS:[ESP+4]                            ;  Do ECX leci nasz klucz / [ESP=RET]
100107B4  |. 56                           PUSH ESI                                                                         ;  zachowujemy ESI
100107B5  |. 33F6                       XOR ESI,ESI                                                                    ;  zerujemy ESI
100107B7  |. 8039 00                  CMP BYTE PTR DS:[ECX],0                                         ;  if(Klucz[0]==0)
100107BA  |. 74 2A                    JE SHORT proton.100107E6                                            ;    goto Exit
100107BC  |. 53                          PUSH EBX                                                                        ;  zachowujemy EBX
100107BD  |> 33C0                    /XOR EAX,EAX                                                                ;  zerujemy EAX               do{
100107BF  |> 8A90 B0CB0210  |/MOV DL,BYTE PTR DS:[EAX+1002CBB0]               ; do dwóch dolnych bajtów przenieś bajt spod EAX+1002CBB0                                                           ; [*CZYTAJ PONIŻEJ TEGO KODU] do {
100107C5  |. 8A19                       ||MOV BL,BYTE PTR DS:[ECX]                                   ; do BL leci znak z naszego klucza
100107C7  |. 3AD3                      ||CMP DL,BL                                                                    ; czy są równe? [**]
100107C9  |. 75 07                       ||JNZ SHORT proton.100107D2                                      ; jeśli nie to skok.
100107CB  |. 8886 F8F81210      ||MOV BYTE PTR DS:[ESI+1012F8F8],AL                  ; do adresu w nawiasie wpisz wartość iteracji EAXa[***]
100107D1  |. 46                            ||INC ESI                                                                          ; zwiększ ESI o jeden
100107D2  |> 40                           ||INC EAX                                                                        ; tak samo postąp z EAX
100107D3  |. 83F8 20                   ||CMP EAX,20                                                                  ;                         
100107D6  |.^7C E7                     |\JL SHORT proton.100107BF                                        ; }while(EAX<20)
100107D8  |. 8A41 01                  |MOV AL,BYTE PTR DS:[ECX+1]                               ; do AL przenieś następny znak
100107DB  |. 41                           |INC ECX                                                                         ; ECX zwiększ o jeden
100107DC  |. 84C0                       |TEST AL,AL                                                                  ; {
100107DE  |.^75 DD                    \JNZ SHORT proton.100107BD                                      ; while(Login[ECX]!=);
100107E0  |. 83FE 14                   CMP ESI,14                                                                      ; Czy ESI jest równe 36 [****]
100107E3  |. 5B                            POP EBX                                                                          ; sciągamy ze stosu EBX
100107E4  |. 74 04                       JE SHORT proton.100107EA                                           ; if(ESI==20) goto SUCCESS;
                  | EXIT:
100107E6  |> 33C0                       XOR EAX,EAX                                                               ; wyzeruj EAX
100107E8  |. 5E                            POP ESI                                                                            ; ściągnij ze stosu ESI
100107E9  |. C3                            RETN                                                                               ; return 0;
          | SUCCESS:
100107EA  |> B8 01000000        MOV EAX,1                                                                     ; EAX=1
100107EF  |. 5E                           POP ESI
100107F0  \. C3                           RETN


*
A cóż to kryje się pod adresem 1002CBB0? Ano mamy tam coś takiego :

Jest to swego rodzaju klucz zawierający cyfry od 2-9 i znaki A-Z, łącznie 32 bity.
A zatem w DL ląduje któryś z w/w, zależnie od iteracji EAX
Nazwiemy go sobie KeyKey :)

**
Pętla ta ma za zadanie sprawdzać czy Login[ECX]==KeyKey[EAX]
tzn. sprawdza czy odpowiedni bajt naszego klucza jest czyfą, czy WIELKĄ LITERĄ, tylko wtedy ESI wzrasta

***
Pod DS:[1012F8F8] znajduje się pusty ciąg znaków.

****
A zatem już wiadomo, że klucz do tego programu musi składać się z cyfr 2-9 i liter A-Z i mieć długość 20 bitów.
Z naszym kluczem '1234567890' nie zostaniemy nigdzie dalej przepuszczeni, bowiem jest on za krótki (a do tego posiada 1)
zmieńmy go zatem na 'ABCDEFGHIJKL23456789' :)

Spójrzmy teraz jak to powinno wyglądać w języku C:
zamieszczam już gotową funkcję wraz z jej wywołaniem i funkcją HexPrint:

/**************************************************************************************************************
<KeyCheckTest.c>
**************************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>

void HexPrint(unsigned char *ToPrint,unsigned short len,char sep)
{
     printf("________________________________________________________________\n");
     printf("\t[Hex Print]\t[0x%08X]\t[%d bytes]\n",ToPrint,len);
     printf("----------------------------------------------------------------\n");
     printf(" Offset\t%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c%04X%c\n",sep,0,sep,1,sep,2,sep,3,sep,4,sep,5,sep,6,sep,7,sep,8,sep,9,sep);
     printf("----------------------------------------------------------------");
     unsigned short i,j;
    
     for(i=0,j=0;i<len;++i)
     {
         if(i%10==0)
         {
            printf("\n\r %c%04X%c\t%c",sep,j,sep,sep);
            j=j+0x000A;
         }
         printf("0x%X%c",ToPrint[i],sep);
        
        
     }
     printf("\n----------------------------------------------------------------");
     printf("\n________________________________________________________________\n");
}


int KeyCheck(char *Key,char *buffer)
{
     unsigned short i_Loop=0;
     unsigned short i_Key=0;
     unsigned short i_KeyKey=0;
     unsigned short i_Buffor=0;
    
     char AL=0;
     char *KeyKey = "23456789ABCDEFGHIJKLMNOPRSTUWXYZ";
     if(Key[AL]==0x00) goto EXIT;
    
     do
     {
         i_KeyKey = 0;
         do
         {
               if(Key[i_Key]==KeyKey[i_KeyKey])
               {
                  buffer[i_Buffor] = i_KeyKey;
                  ++i_Buffor;
               }
               ++i_KeyKey;
              
         }
         while(i_KeyKey<0x20);
        
         AL = Key[i_Key+1];
         ++i_Key;
     }
     while(AL!=0x00);
    
     if(i_Buffor==0x14)
 EXIT:
      return 1;
     else
      return 0;
}

int main(void)
{
    char *buff[20] = {0};
    int ret = KeyCheck("0123456789",buff);
    printf("KeyCheck = %d\n",ret);
    HexPrint(buff,20,'|');
   
    system("pause");
    return 0;
}
/**************************************************************************************************************
</KeyCheckTest.c>
**************************************************************************************************************/

Porównajmy wyniki dla naszego obecnego klucza, którego używaliśmy do tej pory:


i dla nowego, który ustalilśy wcześniej:



Jak widać zgadzają się w 100%, zatem odwzorowanie możemy uznać za dokładne.


Koniec Części Pierwszej.

Brak komentarzy:

Prześlij komentarz