Cracking4neWBies - Lekce č.16

Zdroj: SOOM.cz [ISSN 1804-7270]
Autor: DjH
Datum: 20.10.2007
Hodnocení/Hlasovalo: 2.33/3

Cracking pro začátečníky...pošestnácté :-)

Cracking4neWBies

Lekce č.16

Trocha programování v Delphi – komponenta DjH DÍL[1]



Balík zde

Tento díl je celý určen jen programátorům v Delphi, takže pokud nejste programátor v Delphi, s klidným srdcem můžete tuto lekci přeskočit. Pokud se chcete přiučit tak nemusíte, čtěte dále :). V této nepravidelné řadě budeme vyvíjet komponentu „DjH“, která obsahuje hodně používané funkce, ale ty jsou bohužel v unitách, které se na binárce poznamenají i několika desítkami nebo stovkami kilobajtů, nebo funkce, které nikde jinde nejsou. I přesto se použitá unita v binárce snaží nepřesáhnout 3 kB.
Znáte to, děláte keygen bez pomoci VCL, ale přece jen někde musíte použít funkci „IntToStr“, „IntToHex“, „StrToInt“ atp… Tyto funkce Vám bez přidané Unit „SysUtils“ v Uses nepůjdou. Jenže s přidáním SysUtils do Uses, se binárka zvětší asi o 30kB, protože v SysUtils jsou i funkce pro práci se soubory (FileCreate, FileSeek…) a různé funkce, které VŮBEC v keygenu nejsou potřeba! Bohužel Borland pro nás nenechal ani nezkompilovaný soubor „SysUtils.pas“, abychom se mrkli, jak se takový integer převádí na string, místo toho nám už nechal zkompilovanou unitu „SysUtils.dcu“. Ještě jsem neřekl co mám v plánu…
Mám v plánu vytvořit Unitu, podobnou SysUtils, ale jen s vybranými funkcemi (StrToInt. IntToStr…).
Takže, zaprvé, jak dostat ze zkompilované unity kód? Přece pomocí DeDe :), ano, DeDe umí dekompilovat i DCU soubory, opět Vám jen ukáže ASM kód, ale to nám vůbec nevadí :)…
Otevřete si DeDe, zvolte z menu „Dumpers“ -> „DCU dumper“ a vyberte soubor SysUtils.dcu a klikněte na Process. Potom uložte z „Mema“ kód nebo označte RadioButton „Save output to file“. Chvíli to potrvá, ale dočkáte se. Uloží se Vám asi soubor s 25000 řádky :). Ten si otevřete v nějakém programu, co zvýrazňuje syntaxi, třeba opensource „Notepad ++“ nebo PSPad. Moc nedoporučuju otevírat to přímo v Delphi.
Hon na IntToHex…
Vyhledejte si string „IntToHex“ a asi třetí z vrchu je procedura v asm. Pod tímto (4. z vrchu) je funkce IntToHex, která importuje Int64 (což je výhoda, integer podporuje int64). Takže se zaměříme na něj:
function IntToHex (Value: Int64; Digits: Integer): AnsiString; overload;
var
    result Result: AnsiString;
begin
    00000000 : 55 PUSH EBP
    00000001 : 8B EC MOV EBP,ESP
    00000003 : 83 F8 20 CMP EAX,32
    00000006 : 7E 02 JLE +2; (0xA)
    00000008 : 31 C0 XOR EAX,EAX
    0000000A : 56 PUSH ESI
    0000000B : 89 E6 MOV ESI,ESP
    0000000D : 83 EC 20 SUB ESP,32
    00000010 : B9 10 00 00 00 MOV ECX,$00000010
    00000015 : 52 PUSH EDX
    00000016 : 89 C2 MOV EDX,EAX
    00000018 : 8D 45 08 LEA EAX,DWORD PTR [EBP+8{Value}]
    0000001B : E8(00 00 00 00 CALL CvtInt64{0x41A}
    00000020 : 89 F2 MOV EDX,ESI
    00000022 : 58 POP EAX
    00000023 : E8(00 00 00 00 CALL @LStrFromPCharLen{0x18B}
    00000028 : 83 C4 20 ADD ESP,32
    0000002B : 5E POP ESI
    0000002C : 5D POP EBP
    0000002D : C2 08 00 RET NEAR,8
end;

Myslím si, že není co dodat, a můžeme ihned zapisovat. První dva řádky, Push EBP a MOV EBP, ESP vynecháme, Delphi je přidává automaticky a poslední dva, Pop ebp a Return taky. Za JLE je adresa +2 (0A), to znamená že skáče v tomto kódu na adresu 0A, kde je příkaz PUSH ESI… Vytvoříme si tedy label a bude to vypadat asi takto:
function IntToHex(Value: Int64; Digits: Integer): string;
asm
    CMP EAX, 32
// Digits < buffer length?
    JLE @A1
    XOR EAX, EAX
    @A1: PUSH ESI
    MOV ESI, ESP

    SUB ESP, 32 // 32 chars
    MOV ECX, 16 // base 10
    PUSH EDX // result ptr
    MOV EDX, EAX // zero filled field width: 0 for no leading zeros
    LEA EAX, Value;
    CALL CvtInt64

    MOV EDX, ESI
    POP EAX
// result ptr
    CALL System.@LStrFromPCharLen
    ADD ESP, 32
    POP ESI
end;

Funkci LstrFromPCharLen má v Delphi každý program, proč to tedy nepoužít, že? Ještě k řádku 18h, kde je LEA EAX, Value… Snad Vám došlo, že PTR v tom DCU dumpu měl v {závorce} hodnotu Value, tudíž takhle to jde použít. Větší problém je přeložit CvtInt64, což je funkce v unitě SysUtils, která převádí Int64 na text. Není to přesně IntToStr, ale je to kód, který „předpřevede“ integer na text (ne na string). Přeložit ji musíme :)… Dump je:
procedure CvtInt64;
begin
    00000000 : 08 C9 OR CL,CL
    00000002 : 75 30 JNE +48; (0x34)
    00000004 : B9 0A 00 00 00 MOV ECX,$0000000A
    00000009 : F7 40 04 00 00 00 80 TEST DWORD PTR [EAX+4],$80000000
    00000010 : 74 22 JE +34; (0x34)
    00000012 : FF 70 04 PUSH DWORD PTR [EAX+4]
    00000015 : FF 30 PUSH DWORD PTR [EAX]
    00000017 : 89 E0 MOV EAX,ESP
    00000019 : F7 1C 24 NEG DWORD PTR [ESP]
    0000001C : 83 54 24 04 00 ADC DWORD PTR [ESP+4],0
    00000021 : F7 5C 24 04 NEG DWORD PTR [ESP+4]
    00000025 : E8(34 00 00 00 CALL +CvtInt64{0x41A}+52
    0000002A : C6 46 FF 2D MOV BYTE PTR [ESI-1],$2D
    0000002E : 4E DEC ESI
    0000002F : 41 INC ECX
    00000030 : 83 C4 08 ADD ESP,8
    00000033 : C3 RET NEAR
    00000034 : 56 PUSH ESI
    00000035 : 83 EC 04 SUB ESP,4
    00000038 : D9 7C 24 02 FSTCW [ESP+2]
    0000003C : D9 3C 24 FSTCW [ESP]
    0000003F : 66 81 0C 24 00 0F OR WORD PTR [ESP],$0F00
    00000045 : D9 2C 24 FLDCW [ESP]
    00000048 : 66 89 0C 24 MOV WORD PTR [ESP],CX
    0000004C : D9 E8 FLD1
    0000004E : F7 40 04 00 00 00 80 TEST DWORD PTR [EAX+4],$80000000
    00000055 : 74 27 JE +39; (0x7E)
    00000057 : FF 70 04 PUSH DWORD PTR [EAX+4]
    0000005A : FF 30 PUSH DWORD PTR [EAX]
    0000005C : 81 64 24 04 FF FF FF 7F AND DWORD PTR [ESP+4],$7FFFFFFF
    00000064 : 68 FF FF FF 7F PUSH $7FFFFFFF
    00000069 : 68 FF FF FF FF PUSH $FFFFFFFF
    0000006E : DF 6C 24 08 FILD TWORD PTR [ESP+8]
    00000072 : DF 2C 24 FILD TWORD PTR [ESP]
    00000075 : D8 C2 FADD ST,ST2
    00000077 : DE C1 FADDP ST1,ST
    00000079 : 83 C4 10 ADD ESP,16
    0000007C : EB 02 JMP +2; (0x80)
    0000007E : DF 28 FILD TWORD PTR [EAX]
    00000080 : DF 04 24 FILD WORD PTR [ESP]
    00000083 : D9 C1 FLD ST1
    00000085 : 4E DEC ESI
    00000086 : D9 F8 FPREM
    00000088 : DF 1C 24 FISTP WORD PTR [ESP]
    0000008B : DC F9 FDIVR ST1,ST
    0000008D : 8A 04 24 MOV AL,BYTE PTR [ESP]
    00000090 : 04 30 ADD AL,$30
    00000092 : 3C 3A CMP AL,$3A
    00000094 : 72 02 JB +2; (0x98)
    00000096 : 04 07 ADD AL,$07
    00000098 : 88 06 MOV BYTE PTR [ESI],AL
    0000009A : D9 C1 FLD ST1
    0000009C : D8 D3 FCOM ST3
    0000009E : 9B WAIT
    0000009F : DF E0 FSTSW AX
    000000A1 : 9E SAHF
    000000A2 : 73 E1 JNB -31; (0x85)
    000000A4 : D9 6C 24 02 FLDCW [ESP+2]
    000000A8 : 83 C4 04 ADD ESP,4
    000000AB : DD C3 FFREE ST3
    000000AD : DD C2 FFREE ST2
    000000AF : DD C1 FFREE ST1
    000000B1 : DD C0 FFREE ST
    000000B3 : 59 POP ECX
    000000B4 : 29 F1 SUB ECX,ESI
    000000B6 : 29 CA SUB EDX,ECX
    000000B8 : 76 10 JBE +16; (0xCA)
    000000BA : 29 D6 SUB ESI,EDX
    000000BC : B0 30 MOV AL,$30
    000000BE : 01 D1 ADD ECX,EDX
    000000C0 : EB 03 JMP +3; (0xC5)
    000000C2 : 88 04 32 MOV BYTE PTR [EDX+ESI],AL
    000000C5 : 4A DEC EDX
    000000C6 : 75 FA JNE -6; (0xC2)
    000000C8 : 88 06 MOV BYTE PTR [ESI],AL
    000000CA : C3 RET NEAR
end;

Nelekejte se, je to přece jen o trošičku delší, ale postup je úplně stejný :). Tam kde jsou jumpy, vytvářejte labely a poslední return vypusťte. Po chvilce trápení byste měli vykouzlit asi přibližně toto:
procedure CvtInt64;
{ IN:
EAX: Address of the int64 value to be converted to text
ESI: Ptr to the right-hand side of the output buffer: LEA ESI, StrBuf[32]
ECX: Base for conversion: 0 for signed decimal, or 10 or 16 for unsigned
EDX: Precision: zero padded minimum field width
OUT:
ESI: Ptr to start of converted text (not start of buffer)
ECX: Byte length of converted text
}
asm
    OR CL, CL
    JNZ @start
// CL = 0 => signed integer conversion
    MOV ECX, 10
    TEST [EAX + 4], $80000000
    JZ @start
    PUSH [EAX + 4]
    PUSH [EAX]
    MOV EAX, ESP
    NEG [ESP]
// negate the value
    ADC [ESP + 4],0
    NEG [ESP + 4]

    CALL @start // perform unsigned conversion
    MOV [ESI-1].Byte, '-' // tack on the negative sign
    DEC ESI
    INC ECX
    ADD ESP, 8
    RET

@start:
// perform unsigned conversion
    PUSH ESI
    SUB ESP, 4
    FNSTCW [ESP+2].Word
// save
    FNSTCW [ESP].Word // scratch
  
  OR [ESP].Word, $0F00 // trunc toward zero, full precision
    FLDCW [ESP].Word

    MOV [ESP].Word, CX
    FLD1
    TEST [EAX + 4], $80000000
// test for negative
   
JZ @ld1 // FPU doesn't understand unsigned ints
 
   PUSH [EAX + 4] // copy value before modifying
 
   PUSH [EAX]
    AND [ESP + 4], $7FFFFFFF
// clear the sign bit
   
PUSH $7FFFFFFF
    PUSH $FFFFFFFF
    FILD [ESP + 8].QWord
// load value
    FILD [ESP].QWord
    FADD ST(0), ST(2)
// Add 1. Produces unsigned $80000000 in ST(0)
    FADDP ST(1), ST(0) // Add $80000000 to value to replace the sign bit
    ADD ESP, 16
    JMP @ld2
@ld1:
    FILD [EAX].QWord
// value
@ld2:
    FILD [ESP].Word
// base
    FLD ST(1)
@loop:
    DEC ESI

FPREM // accumulator mod base
    FISTP [ESP].Word
    FDIV ST(1), ST(0)
// accumulator := acumulator / base
MOV AL, [ESP].Byte // overlap long FPU division op with int ops
    ADD AL, '0'
    CMP AL, '0'+10
    JB @store
    ADD AL, ('A'-'0')-10
@store:
    MOV [ESI].Byte, AL

    FLD ST(1) // copy accumulator
    FCOM ST(3) // if accumulator >= 1.0 then loop
    FSTSW AX
    SAHF
    JAE @loop

FLDCW [ESP+2].Word
ADD ESP,4

    FFREE ST(3)
    FFREE ST(2)
    FFREE ST(1);
    FFREE ST(0);

    POP ECX
// original ESI
    SUB ECX, ESI // ECX = length of converted string
    SUB EDX,ECX
    JBE @done
// output longer than field width = no pad
    SUB ESI,EDX
    MOV AL,'0'
    ADD ECX,EDX
    JMP @z
@zloop: MOV [ESI+EDX].Byte,AL
@z: DEC EDX
    JNZ @zloop
    MOV [ESI].Byte,AL
@done:
end;


Všimněte si, že v kódu už jsou převedené „hexhodnoty“ na „chary“ ($2D -> ‚-‘). Nyní už jen vytvořit komponentu, nebo ještě lépe, package :). Když už jste v tom PSPadu, nebo kde vlastně (já v Notepadu++), vytvoříme si tam tu komponentu. Po chvíli čachrování byste měli napsat asi něco takového:

unit DjH;

interface
function IntToHex (Value: Int64; Digits: Integer): String; overload;

implementation

uses Windows,Messages;

//####################################################################//
procedure CvtInt64;
asm
………
end;
//#######################INTTOHEX################################//
function IntToHex (Value: Int64; Digits: Integer): String;
asm
………
end;

Za „………“ si doplňte výše uvedený kód :). Stačí komponentu už jen nakopčit do …\Borland\DelphiX\Lib (pod názvem „DjH.pas“) nebo ji zaregistrujte přes menu „Component“ -> „Install component…“. Nyní si vytvořte aplikaci (bez VCL) a místo SysUtils napište DjH (SysUtils vymažte). Třeba si udělejte takovouto jednoduchou aplikaci:


program DjHExample;
uses
Windows,DjH;

begin
    Randomize;
    MessageBox(0,PChar(IntToHex((Random(100000)+1),8)),'Náhodné číslo <0-100000>',0);
end.


Zkompilujte, a hle! Program má jen 14.5 kB! Pro zajímavost: Po zkomprimování Upackem jen 7.87 kB! Nyní si místo DjH v uses, napište SysUtils a zkompilujte [Ctrl + F9], a co se nestalo, aplikace má rázem 38,5 kB! Po zásahu Upacku má soubor sice míň, ale 18.2 kB je stejně „moc“. Toto (a ještě míň) my máme s komponentou DjH i bez komprimace :).
Tak, a běžte machrovat s touto komponentou! =), cože, že je to málo? Ok, fajn, uděláme si ještě „IntToStr“, „StrToInt“ a „StrToHex“. Třeba funkci StrToHex nikde nenajdete, a ta naše funkce bude pracovat na tomto principu:
function StrToHex(S:String; Value:Integer):String;
begin
    result := IntToHex(StrToInt(S), Value);
end;


Prosté… A třeba maníky v Borlandu to (asi) nenapadlo, aby nám takto usnadnili práci…
Ale nepředbíhejme, jdeme si vyštrachovat funkci IntToStr.
Našli jsme a výpis je zde:
function IntToStr (Value: Int64): AnsiString; overload;
var
result Result: AnsiString;
begin
    00000000 : 55 PUSH EBP
    00000001 : 8B EC MOV EBP,ESP
    00000003 : 56 PUSH ESI
    00000004 : 89 E6 MOV ESI,ESP
    00000006 : 83 EC 20 SUB ESP,32
    00000009 : 31 C9 XOR ECX,ECX
    0000000B : 50 PUSH EAX
    0000000C : 31 D2 XOR EDX,EDX
    0000000E : 8D 45 08 LEA EAX,DWORD PTR [EBP+8{Value}]
    00000011 : E8(00 00 00 00 CALL CvtInt64{0x41A}
    00000016 : 89 F2 MOV EDX,ESI
    00000018 : 58 POP EAX
    00000019 : E8(00 00 00 00 CALL @LStrFromPCharLen{0x18B}
    0000001E : 83 C4 20 ADD ESP,32
    00000021 : 5E POP ESI
    00000022 : 5D POP EBP
    00000023 : C2 08 00 RET NEAR,8
end;


Opět je výhodnější počítat s Int64. Vypustíme první dva a poslední dva řádky a zbyde nám toto:
function IntToStr(Value: Int64): String;
begin
asm
    PUSH ESI
    MOV ESI, ESP
    SUB ESP,32
    XOR ECX,ECX
    PUSH EAX
    XOR EDX,EDX
    LEA EAX,Value
    CALL CvtInt64
    MOV EDX,ESI
    POP EAX
    CALL System.@LStrFromPCharLen
    ADD ESP,32
    POP ESI
end;
end;

Vlastně už nic neupravujeme, akorát přidáme před LstrFromPCharLen string „System.“, vymažeme úplně nesmyslný „Var“ a místo „AnsiString“ použijeme jen „String“.
Hon na StrToInt:
Zde bude potřeba menší úprava…
Dump:
function StrToInt (S: AnsiString): Integer;
var
    Result: Integer;
    E: Integer;
begin
    00000000 : 53 PUSH EBX
    00000001 : 56 PUSH ESI
    00000002 : 83 C4 F4 ADD ESP,-12
    00000005 : 8B D8 MOV EBX,EAX
    00000007 : 8B D4 MOV EDX,ESP
    00000009 : 8B C3 MOV EAX,EBX
    0000000B : E8(00 00 00 00 CALL @ValLong{0x1A4}
    00000010 : 8B F0 MOV ESI,EAX
    00000012 : 83 3C 24 00 CMP DWORD PTR [ESP],0
    00000016 : 74 19 JE +25; (0x31)
    00000018 : 89 5C 24 04 MOV DWORD PTR [ESP+4],EBX
    0000001C : C6 44 24 08 0B MOV BYTE PTR [ESP+8],$0B
    00000021 : 8D 54 24 04 LEA EDX,DWORD PTR [ESP+4]
    00000025 : A1(00 00 00 00 MOV EAX,DWORD PTR [SInvalidInteger{0x4}[0]]
    0000002A : 33 C9 XOR ECX,ECX
    0000002C : E8(00 00 00 00 CALL ConvertErrorFmt{0x317}
    00000031 : 8B C6 MOV EAX,ESI
    00000033 : 83 C4 0C ADD ESP,12
    00000036 : 5E POP ESI
    00000037 : 5B POP EBX
    00000038 : C3 RET NEAR
end;


Na adrese „12“ se kontroluje délka stringu, který se má převést na integer, pokud je délka 0, tedy string = „“, je skok na chybu, ta se ale volá na adrese „2C“ (ještě k tomu z podcallu). Můžeme si to nechat být, a celou podmínku zrušit, a při zavolání funkce s prázdným („“) stringem bude result „0“, nebo funkci JE přepsat tak, aby skákala někam na námi udělaný MessageBox, který říká, že „“ není platný integer. Naopak když se zavolá funkce s „Integerem“ kde je neplatný znak, třeba StrToInt(‚123TR54‘);, tak nám SysUtils řekne, že ‚123TR54‘ je neplatný integer. Pokud my neuděláme podmínku, převede se string na integer do prvního neplatného znaku, v tomto případě tedy „123“. Já si zvolím cestu první, tedy že i neplatný string se bude konvertovat:
function StrToInt (S: String): Integer;
asm
    PUSH EBX
    PUSH ESI
    ADD ESP,-12
    MOV EBX,EAX
    MOV EDX,ESP
    MOV EAX,EBX
    CALL System.@ValLong
    ADD ESP,12
    POP ESI
    POP EBX
end;

Nyní už funkce StrToHex:
function StrToHex(S:String; Value:Integer):String;
begin
    result := IntToHex(StrToInt(S), Value);
end;


Funkce IntToHex, IntToStr, StrToInt, StrToHex. Myslím že naše komponenta už je alespoň k něčemu a na rozdíl od SysUtils, zabere v binárce méně než 1 kB (SysUtils asi 25 kB). Příště si do ní přidáme pár funkcí a průběžně (nepravidelně) si tuto komponentu budeme vylepšovat. Budu ji vydávat jak ve verzi „All in 1“, tak v dílech – „DjHStringUtils“. Příště se podíváme na práci jako je třeba otevírání a zavírání mechaniky a práce se soubory, bude se to jmenovat třeba „DjHDriveUtils“ a „DjHFileUtils“. V „All In 1“ komponentě budou všechny unity spojené :). Je to jen pro to, že až toho v All in 1 bude hodně, budou třeba 3 kB použity v binárce úplně na nic. Proto si jen vyberete, jaké unity chcete :)

PS: Borland nám nedal zdrojáky k SysUtils!? My se nedáme! =), zde jste mohli vidět, jak jsme trochu Borlándka setřeli… Všechno jde… Jen, když se chce…

PS2: Takovýmto způsobem si můžete vytvořit vlastní komponentu, která má jen funkce, které vážně chcete, ale pozor! Když budete chtít takovéto zdrojové kódy dávat do světa, nezapomeňte do balíku se zdrojáky, přidat i tu Vámi vyrobenou komponentu, poněvač tu si počítač nedomyslí =), tu musíte FAKT přidat :