Az IDA 5.2 for MacOSX elindításában két nehéz dolgot ismerek. Az első a beszerzése; nem egyszerű, mert nem kering minden csatornán. Ha az első problémát megoldottuk, akkor a mac_server és idal modulon a procmod-ot kell engedélyezni (chmod g+s vagy chgrp procmod).
Ezen túl a nyomkövetésben is némi eltérés mutatkozik. Egy terminálablakban el kell indítani a mac_server-t. Én ezt a következőképpen szoktam.
sudo ./mac_server -Pmypass
Itt most kétféleképpen járhatunk el. Van egy Windows-os számítógépen fejtetjük vissza a binárist vagy egy MacOSX-en. Azért lehet ez, mert mindkét esetben remote debuggolás fog történni. Helyben végezve az IDA-ban a Debug / Process options... menüpontban a következőképpen kell beállítani a paramétereket.
Ha eddig nincs probléma, akkor vessük rá magunkat első áldozatunkra, a Mah Jong Solitaire 2.21 for Mac OS X-et vettem kezelésbe (most találtam rá a 2.55-ös verzióra, sebaj ezért nem kezdek neki megint most).
A visszafejtés elkészülte után az első gondolatom az volt, hogyan találjam meg azt a funcionális részt, ahol az unlock kód ellenőrzése történik. Mivel itt is lokalizált erőforrások vannak, arra számítottam nem lesz könnyű dolgom de egy erőtlen próbálkozást megeresztettem. Szöveget kerestem a visszafejtett forrásban: "Register". Az első találat ez volt.
Nem biztató, de tovább nyomkodtam a ctrl-t gombokat. Nem is olyan sokára érdekes dologra bukkantam.
Online regisztráció? Vizsgáljuk meg közelebbről. Egy referencia látszik, amit követve adatszegmensben egy táblázatban találtam. Azt hittem, hogy ez probélma, de eszembe jutott, amit Windows-ban iTunes debuggolása közben láttam: objektumorientált struktúrák. Vessünk egy pillantást az egész táblázatra.
__inst_meth:00040364 dd 18h
__inst_meth:00040368 dd offset aSaveprefsdata, offset a@12@04@8, offset __RegistrationController_savePrefsData__ ; "@12@0:4@8"
__inst_meth:00040374 dd offset aShowentercodep, offset aV12@04@8, offset __RegistrationController_showEnterCodePanel__ ; "v12@0:4@8"
__inst_meth:00040380 dd offset aFilloutregistr, offset a@8@04, offset __RegistrationController_fillOutRegistration_ ; "@8@0:4"
__inst_meth:0004038C dd offset aCodeentered, offset aV12@04@8, offset __RegistrationController_codeEntered__ ; "v12@0:4@8"
__inst_meth:00040398 dd offset aRegistered, offset aC8@04, offset __RegistrationController_registered_ ; "c8@0:4"
__inst_meth:000403A4 dd offset aGeneratechecks, offset a@12@04@8, offset __RegistrationController_generateChecksum__ ; "@12@0:4@8"
__inst_meth:000403B0 dd offset aRegistered_0, offset aC12@04@8_0, offset __RegistrationController_registered__ ; "c12@0:4@8"
__inst_meth:000403BC dd offset aRemoveohs, offset aV12@048, offset __RegistrationController_removeOhs__ ; "v12@0:4*8"
__inst_meth:000403C8 dd offset aGestaultcheck, offset aC12@04i8, offset __RegistrationController_gestaultCheck__ ; "c12@0:4i8"
__inst_meth:000403D4 dd offset aShortversion, offset a@8@04, offset __RegistrationController_shortVersion_ ; "@8@0:4"
__inst_meth:000403E0 dd offset aDaysowned, offset aI8@04, offset __RegistrationController_daysOwned_ ; "i8@0:4"
__inst_meth:000403EC dd offset aAddtogames, offset a@8@04, offset __RegistrationController_addToGames_ ; "@8@0:4"
__inst_meth:000403F8 dd offset aValiditystring, offset a@12@04@8, offset __RegistrationController_validityString__ ; "@12@0:4@8"
__inst_meth:00040404 dd offset aCapitalisestri, offset a@12@048, offset __RegistrationController_capitaliseString__ ; "@12@0:4*8"
__inst_meth:00040410 dd offset aShiftstring, offset a@16@048c12, offset __RegistrationController_shiftString___ ; "@16@0:4*8c12"
__inst_meth:0004041C dd offset aSetprefsdata, offset a@12@04@8, offset __RegistrationController_setPrefsData__ ; "@12@0:4@8"
__inst_meth:00040428 dd offset aRegistrationst, offset a@8@04, offset __RegistrationController_registrationString_ ; "@8@0:4"
__inst_meth:00040434 dd offset aRegisteronline, offset aV12@04@8, offset __RegistrationController_registerOnline__ ; "v12@0:4@8"
__inst_meth:00040440 dd offset aUnlock, offset aV12@04@8, offset __RegistrationController_unlock__ ; "v12@0:4@8"
__inst_meth:0004044C dd offset aNotyet, offset aV12@04@8, offset __RegistrationController_notyet__ ; "v12@0:4@8"
__inst_meth:00040458 dd offset aShowpanelbytim, offset a@12@04@8, offset __RegistrationController_showPanelByTimer__ ; "@12@0:4@8"
__inst_meth:00040464 dd offset aShowlwygiyr, offset a@12@04@8, offset __RegistrationController_showLWYGIYR__ ; "@12@0:4@8"
__inst_meth:00040470 dd offset aWindowshouldcl, offset aC12@04@8_0, offset __RegistrationController_windowShouldClose__ ; "c12@0:4@8"
__inst_meth:0004047C dd offset aShowaboutpanel, offset aV12@04@8, offset __RegistrationController_showAboutPanel__ ; "v12@0:4@8"
Ez bizony a RegistrationController objektum interface prototípusa, tehát itt vannak a pointerek a metódusok neveire, szignaturájára és metódusimplementációjára. Az érdekesség kedvéért nézzük meg az ő referenciáját is.
__class:0003D420 ; DATA XREF: __symbols:00043080vo
__class:0003D420 offset aRegistration_0, 0, 1, 6Ch, offset dword_42300,\ ;
__class:0003D420 offset dword_40360, 0, 0>
Nahát! Itt az első pointer az objektum ősosztályának ugyanilyen leíró blokkjára, majd az ősosztály neve. A következő mutató az aktuális objektum neve, az utolsó pedig a metódusleíró táblázat címe. Érdekes. Ezt azért mutattam meg, mert a későbbiekben nagyon hasznos lehet.
Most menjünk vissza és nézegessük a Registrationcontroller-t. Két metódusra figyeltem fel: aRegistered és aRegistered_0.
__text:0002453C ; DATA XREF: __inst_meth:00040398vo
__text:0002453C
__text:0002453C var_18 = dword ptr -18h
__text:0002453C var_14 = dword ptr -14h
__text:0002453C var_10 = dword ptr -10h
__text:0002453C arg_0 = dword ptr 8
__text:0002453C
__text:0002453C push ebp
__text:0002453D mov ebp, esp
__text:0002453F sub esp, 18h
__text:00024542 mov edx, [ebp+arg_0]
__text:00024545 mov eax, [edx+54h]
__text:00024548 mov [esp+18h+var_10], eax
__text:0002454C mov eax, ds:off_3CA64 ; registered:
__text:00024551 mov [esp+18h+var_14], eax
__text:00024555 mov [esp+18h+var_18], edx
__text:00024558 call _objc_msgSend
__text:0002455D leave
__text:0002455E retn
__text:0002455E __RegistrationController_registered_ endp
Azt úgy lehetne visszafordítani Objective-C-re:
. Ezek szerint a másik metódus lesz az érdekes, nosza nézzük meg azt is.
A __RegistrationController_registered__ metódus sokkal terjedelmesebb, ezért nem fogom mellékelni, de nézzük meg az értelmezését is.
Ha nem is biztos, hogy pontosan ilyen volt, de értelmét tekintve ez a működés. Mivel már értelmeztem a saját eljárásokat, ezért gondolom, hogy ez a működés: a paraméterül kapott string-ből C-String-et készítünk, az O betüket nullára cseréljük benne és megkeressük az 'M' betüt, majd visszakonvertáljuk NSString-gé.
A következő lépés egy vizsgálat. A string 0. karaketere 'M', az első 'J', az ötödik 'X' és a hatodik 'S' karakter kell legyen. Ha ez nem így van, akkor EAX = 0 értékkel fejeződik be a rutin futása.
if (chksum[0] == param[12] && chksum[1] == param[13] && chksum[2] == param[13]){
return 0;
}
A generateChecksum metódus az előbb előkészített string-et dolgozza fel, ami azt jelenti, hogy az első 11 karakterének a kódját számszerüleg összegzi, ezt XOR-olja 0xF95-tel és AND-olja 0xFFF-fel vagyis az alsó három hexadecimális számjegyet tartja meg. Ha ezek a számjegyek nem egyeznek meg a paraméter utolsó három karakterével, akkor nullás értékkel tér vissza.
Itt a 7-10 karakterből számértéket készítéttetük úgy, mintha azok hexadecimális jegyek lennének. A vizsgálata a következőképpen néz ki.
__text:000244B0 jz short loc_244CE
__text:000244B2 jg short loc_244B9
__text:000244B4 cmp eax, 34h
__text:000244B7 jmp short loc_244C5
__text:000244B9 ; ---------------------------------------------------------------------------
__text:000244B9
__text:000244B9 loc_244B9: ; CODE XREF: __RegistrationController_registered__+140^j
__text:000244B9 cmp eax, 6789h
__text:000244BE jz short loc_244CE
__text:000244C0 cmp eax, 7860h
__text:000244C5
__text:000244C5 loc_244C5: ; CODE XREF: __RegistrationController_registered__+145^j
__text:000244C5 jz short loc_244CE
__text:000244C7 mov eax, 1
__text:000244CC jmp short loc_244D0
__text:000244CE ; ---------------------------------------------------------------------------
__text:000244CE
__text:000244CE loc_244CE: ; CODE XREF: __RegistrationController_registered__+10^j
__text:000244CE ; __RegistrationController_registered__+8D^j ...
__text:000244CE xor eax, eax
__text:000244D0
__text:000244D0 loc_244D0: ; CODE XREF:__RegistrationController_registered__+15A^j
Tehát ha a kapott közbülső érték 0x5568, 0x6789, 0x7860 vagy 0x0034, akkor nulla értékkel lép ki.
Erre mit mondhatnék? Nem ez a legerősebb védelem, amit eddig láttam.
A teljesség igénye nélkül Java-ban a következő rutint állítottam össze ugyanennek az ellenőrzésnek a lebonyolítására.
boolean retval = false;
if (code[0] == 'M' && code[1] == 'J' && code[5] == 'X' && code[6] == 'S'){
int sum = 0;
for (int i = 0; i < 0xb; ++i){
sum += (code[i]&0xff);
}
sum = (sum ^ 0xf95) & 0xfff;
String middle = new String(new byte[]{code[0x7],code[0x8],code[0x9],code[0xA]});
retval = Integer.toHexString(sum).equalsIgnoreCase(new String(new byte[]{code[0xb],code[0xc],code[0xd]})) && !"5568".equalsIgnoreCase(middle) && !"6789".equalsIgnoreCase(middle) && !"7860".equalsIgnoreCase(middle);
if (!retval){
System.out.println("Checksum: "+Integer.toHexString(sum)+" ...or banned middle part (5568,6789,7860)");
}
}
return retval;
}
Tudom, hogy sok ellenőrzés hiányzik,de kísérletezni megteszi. Ha elsőre nem sikerülne eltalálnunk egy érvényes kódot, akkor a helyes checksum-ot megmutatja és a bannolt közteskódokat is.
Búcsúzóul álljon itt egy érvényes kód. Később talán áttekintem a következő verziót is, hátha ott rafináltabb az ellenőrzés.