Vägen till att bli en reverse engineer? Reverse engineering för nybörjare. Skydda Android-applikationer från reverse engineering Vem är Voytyuk reverse engineer


Ibland vill någon se vad fyllningen är i specifikt program? Då måste han använda reverse engineering. Vad det är? Hur fungerar det? Hur sker denna process? Du kan lära dig allt detta från den här artikeln.

Vad är omvänd konstruktion av programvara?

Detta är processen att demontera en applikation för att förstå hur den fungerar för att återskapa denna process i framtiden med nödvändiga ändringar. Vanligtvis används en debugger och en assembler för dessa ändamål. Beroende på vilken kvalitet som används programvara resultatet och den tid som behöver läggas på att få den i form kommer att skilja sig åt normalt utseende. Det bästa sättet att förklara reverse engineering för nybörjare är med ett exempel. Detta kommer att vara en applikation skriven för Android. Låt oss nu ta reda på vad och hur.

Arbeta med Android-applikationer

Först måste vi klargöra några punkter. Applikationerna använder bytecode och LogCat. Dessa är lokala analoger till den tidigare nämnda debuggern och assemblern. Det är också nödvändigt att förstå strukturen i själva applikationerna. Så, varje program är en fil med apk-tillägg. Den är packad med dragkedja. Vi är intresserade av dess innehåll - applikationsresurser, classes.dex och AndroidManifest.xml. Om du programmerar på Android bör det inte finnas några frågor med den första och sista. Men classes.dex är bytekoden för programmet, som är kompilerad specifikt för den virtuella maskinen. Fiska upp det källa java med hjälp av verktygen som finns på Internet kommer inte att fungera. Men det är möjligt att erhålla dalvik opcodes - en speciell uppsättning kommandon som används för den virtuella maskinen. Som en analogi kan vi säga att detta är en montör av ett lokalt spill. Även classes.dex kan omvandlas till en fil med en jar-tillägg. Redan i det här fallet, efter att ha dekompilerat den, kan du få java-kod som kommer att vara mer eller mindre läsbar. Detta är den väg vi kommer att ta.

Dekompilering

Denna process kommer att utföras med hjälp av programmet Apk Manger. Innan du börjar arbeta måste du se till att det finns nödvändiga förare för driften av enheten och har även ett USB-felsökningsläge. Inledningsvis måste vi flytta filen som kommer att analyseras till direktivet apk_manager\place-apk-here-for-modding. Efter detta bör du köra Script.bat. Om det inte finns några problem kommer konsolen att starta, på vilken det kommer att finnas gröna inskriptioner. Välj artikel nummer nio - "Dekompilera". Efter att processen har startat får du inte stänga konsolen. Sedan bör du öppna apk-filen av intresse med hjälp av ett arkiv och extrahera classes.dex från den, som måste bearbetas av programmet dex2jar. För resultatet vi behöver måste vi flytta det till ett objekt med .bat-tillägget. En fil kommer att visas som slutar med .jar. Vi stänger inte fönstret ännu.

Analyserar data

För att få information om en applikation måste du öppna dess manifest. Med hjälp av det bestämmer vi vad som fungerar som huvudaktivitet. Det är det som är av största vikt för oss nu. Det är också lämpligt att titta längst ner i programmet. Om det finns information om licenshanteraren längst ner kommer detta att försvåra reverse engineering avsevärt. Om vi ​​byter till jd-gui och utökar trädet kommer vi att se flera namnrymder. Låt oss säga att det finns tre av dem. Den första innehåller filer relaterade till reklam. Den andra kommer att innehålla licenshanterarklasser. Den tredje innehåller de uppgifter vi behöver. Det är dit vi går. Här måste du hitta och ta bort nyckeln, och sedan de återstående raderna som kontrollerar om den fungerande versionen är licensierad. Allt detta måste rengöras. Sedan letar vi i vår Apk Manager efter platsen där det indikeras att placera bytekoden. Låt oss nu göra en liten avvikelse och kommentera de kommandon som potentiellt kan uppstå problem med. Efter det är allt vi behöver göra att kompilera programmet.

Bygger applikationen

Samma Apk Manager hjälper oss med detta. I konsolfönstret, som vi inte stängde, välj objekt nr 14. Nästa är en fråga om teknik. Om applikationen är ganska komplex kan den, när den startas, helt eller delvis förlora sin funktionalitet. Var inte upprörd, det betyder att vi bara är halvvägs och det finns fortfarande utrymme att gå. Vi fortsätter att omvända Android-applikationer. Tyvärr är det omöjligt att generellt säga vad som behöver göras i ett särskilt fall. Därför måste du leta efter problemområdet själv. Så om programfönstret är blockerat måste du titta på koden och ta bort den del som är ansvarig för den här dialogrutan. jd-gui kan hjälpa till med detta. Som du kan se är reverse engineering inte lätt, och det kräver en betydande mängd kunskap. Även om allt startade utan problem, kommer det att vara nödvändigt att testa applikationens funktionalitet. Det vill säga, reverse engineering är också en tidskrävande aktivitet. Vi fortsätter att arbeta tills alla problem är identifierade.

Säkerhet

Vad händer om vi behöver skydda Android-applikationer från reverse engineering? I det här fallet finns det två alternativ: använd specialprogram eller skapa en kodstruktur som kommer att störa analysen av det som har skrivits. Det sista alternativet är bara lämpligt erfarna specialister, så vi kommer bara att överväga den första skyddsmetoden. Vi använder ProGuard som specialiserad programvara. Detta är en applikation som används för att förkorta, fördunkla och optimera kod. Om vi ​​kör programmet genom det får vi en fil med filtillägget *.apk som är mindre i storlek än den var. I det här fallet kommer det att vara mycket svårare att demontera. Dessutom är fördelen med detta program att det till och med introducerades i Android-applikationsbyggsystemet med r9-uppdateringen. Därför kan alla utvecklare som har standardverktyg skapande och utveckling.

Slutsats

Därmed inte sagt att reverse engineering kan presenteras som något enhetligt dåligt eller bra. Naturligtvis, från utvecklarnas synvinkel som skapade applikationen, är detta inte en lycklig händelse alls. Men å andra sidan, i många fall skrivande av erfarna programmerare nödvändiga filer kan vara mindre tidskrävande än att använda liknande verktyg. Även om reverse engineering kan vara till stor tjänst för nybörjarutvecklare, om det inte finns någon aning om hur man ska implementera något, kan även grova och inte helt tydliga skisser hjälpa till att uppnå målet.

Receptet är otroligt enkelt:
om du jag vill förstå varje funktion, mixtra med varje nytt program, analysera dess filformat, var och en nytt spel försök att hacka den, skriv en bot för den, fuska, etc. Så den är din, fortsätt bara att göra det du gör.

Om inte, så hjälper inga böcker. Denna verksamhet kräver passion och stort tålamod.

Ingen behöver Matan i backen. Som mest kommer det att krävas att lösa system av linjära ekvationer.
Vad som är viktigt är snarare out-of-the-box-tänkande, förmågan att brute force många alternativ och tillvägagångssätt i ditt huvud. För att göra detta behöver du känna till teknik. Det vill säga, bokstavligen, du behöver veta så mycket som möjligt. Ju mer du vet, desto snabbare kommer problemet att lösas. Dessa är helt olika områden: OS, nätverk, metoder för kryptering, komprimering, hash, serialisering; kunskap om databaser och deras frågespråk; kunskap om kompilatorer i termer av hur de genererar kod; kunskap om genomförandet av densamma standardbibliotek, förstå hur samma kod kompileras av olika kompilatorer, förstå hur bytekodtolkare fungerar, virtuella maskiner, och så vidare.

Detta gäller generell teknik. Och det finns också något sådant som arkitektoniska mönster. De används vanligtvis i programapplikationer; skadlig programvara använder detta sällan. Det vill säga, du behöver se i koden, till exempel, händelsemönstret, olika alternativ MVC-mönster, etc. Till exempel kommer du att vända en produkt till Qt. För att förstå det behöver du veta... Qt, och kunna utveckla på det, läsa dess källkod, veta vad metaobjekt är, hur de lagras, används, kallas. Och om den plötsligt använder något tolkat, som Python eller Lua, behöver du inte bara känna till språken själva, utan också implementeringen av deras tolkar. Och det finns också JIT...

Du måste fortfarande bestämma vad du vill vända. Skadlig programvara och applikationsapplikationer är lite annorlunda. I malware behöver du veta mer icke-standardiserade saker. Olika alternativ anti-debugging, döljande aktivitet, buggar operativ system, antivirusbeteende. Skadlig programvara kan till exempel vara ett botnät. Botnät har vanligtvis kommandoserver, vilket är ganska svårt att beräkna, det förändras dynamiskt och låter sig på något sätt inte upptäckas. För att göra detta måste du veta hur Internet fungerar, hur DNS fungerar och förstå nätverksprotokoll.

Kort sagt, för backen måste du lära dig Allt. Det finns inget behov av att filtrera bort vissa tekniker, du kommer att behöva dem alla utan undantag. För allt som skapades för datorsystem, de används, och följaktligen du du måste veta detta att backa.

Förresten, jag glömde nästan.

Den bästa boken på omvänd på ryska.

Och det finns också en klassisk kurs med artiklar från Ricardo Narvaja: "Introduktion till att knäcka från grunden med OllyDbg." Googla det. Om du behärskar Yurichevs bok och den här kursen kan du lugnt intervjua på Kaspersky. Även om, tro mig, det finns saker som är mer intressanta än Kaspersky.

Vi har samlat flera utmärkta böcker om reverse engineering som passar både nybörjare och de som vill prova något nytt, oavsett om det är iOS eller Xbox.

Reverse Engineering för nybörjare

"Nu när Denis Yurichev har gjort den här boken gratis, är den ett bidrag till en värld av fri kunskap och gratis utbildning." - Richard Stallman, grundare av GNU, fri mjukvaruaktivist.

"Reverse Engineering for Beginners" är inte bara en lärobok i omvänd ingenjörskonst, utan också en utmärkt lärobok i grunderna i programmering, som lämpar sig både för att lära sig djupet i C++ och Java, och för bättre förståelse hur en dator fungerar.

BIOS DEMONTERING NINJUTSU UPPTÄCKT

I många år har det funnits en myt bland datorentusiaster och utövare som BIOS modifiering(Basic Input Output System) är en sorts svart magi och endast ett fåtal kan det eller att bara tillverkaren moderkort kan utföra en sådan uppgift. Den här boken visar att när rätt verktyg och ett systematiskt tillvägagångssätt för reverse engineering, kan vem som helst förstå och modifiera BIOS för att passa deras behov utan att ha källkoden.

iOS App Reverse Engineering

Boken är skriven i "lager" - teori, praktik, teori och åter praktik. Den består av 4 delar:

— Koncept
- Verktyg
— Teori
- Öva

Den första delen tar upp det grundläggande iOS-koncept, hierarki filsystem, filtyper dolda för applikationsutvecklare men nödvändiga för systemforskare. Den andra delen täcker de viktigaste verktygen för systemreverse engineering, såsom Theos, Cycript, Reveal, IDA och LLDB. Därefter diskuteras teorin om iOS reverse engineering med hjälp av Objective-C och metoderna förklaras. Och den sista delen diskuterar 4 praktiker av reverse engineering system, utvecklade på basis av teori och praktik från de tidigare delarna av boken.

Hacka Xbox: An Introduction to Reverse Engineering

Xbox-konsolen är en underbar enhet, inte bara för att den kan spela alla möjliga nya spel. Kraftfull, men relativt billig, enheten har potential som en mångsidig multiplayer, PC och till och med webbserver. Men bristen på litteratur som ger kunskap och en praktisk grund för att modifiera Xbox hindrar den från att avslöja sin fulla potential. Den här boken skapades för att täcka denna brist till viss del.

  • Handledning

Det här inlägget kommer verkligen att vara intressant för dem som precis börjat vara intresserade av detta ämne. För personer med erfarenhet kommer det förmodligen bara att orsaka gäspningar. Förutom kanske...
Reverse engineering, i den mindre lagliga delen där det inte handlar om felsökning och optimering av den egna produkten, handlar också om följande uppgift: "ta reda på hur det fungerar för dem." Med andra ord, återställa den ursprungliga algoritmen för programmet, med dess körbara fil i handen.
För att hålla oss till grunderna och undvika vissa problem kommer vi att "hacka" inte bara vad som helst, utan... en keygen. I 90 % av fallen kommer den inte att packas, krypteras eller på annat sätt skyddas - inklusive av internationell lag...

I början fanns ordet. Dubbel
Så vi behöver en nyckelgen och en demonterare. När det gäller den andra, låt oss anta att det blir Ida Pro. Experimentell namnlös nyckel som hittats på Internet:

Efter att ha öppnat keygen-filen i Ida ser vi en lista med funktioner.

Efter att ha analyserat den här listan ser vi flera standardfunktioner (WinMain, start, DialogFunc) och ett gäng extra systemfunktioner. Allt detta standardfunktioner, som utgör ramen.
Användardefinierade funktioner som representerar implementeringen av programuppgifter, och inte dess omslag från API:er och systemsamtal, demonteraren känner inte igen det och anropar helt enkelt sub_digits. Med tanke på att det bara finns en sådan funktion här borde den dra till sig vår uppmärksamhet eftersom den med största sannolikhet innehåller algoritmen eller en del av den som intresserar oss.

Låt oss köra keygen. Den ber dig att ange två 4-siffriga strängar. Anta att åtta tecken skickas till nyckelberäkningsfunktionen samtidigt. Låt oss analysera koden för funktionen sub_401100. Svaret på hypotesen finns i de två första raderna:

var_4= dword ptr -4
arg_0= dword ptr 8

Den andra raden antyder tydligt att man tar emot funktionsargumentet vid offset 8. Storleken på argumentet är dock ett dubbelord lika med 4 byte, inte 8. Detta betyder att funktionen med största sannolikhet bearbetar en sträng med fyra tecken i ett pass, och det kallas två gånger.
En fråga som säkert kan uppstå är: varför är en offset på 8 byte reserverad för att ta emot ett funktionsargument, men pekar på 4, eftersom det bara finns ett argument? Som vi minns växer stapeln nedåt; När ett värde läggs till stacken, minskas stackpekaren med motsvarande antal byte. Därför, efter att en funktions argument har lagts till i stacken och innan den börjar köras, läggs något annat till i stacken. Detta är uppenbarligen returadressen som skjuts upp i stacken efter samtalet systemfunktion ring upp.

Låt oss hitta platser i programmet där anrop till sub401100-funktionen förekommer. Det visar sig att det verkligen finns två av dem: på adressen DialogFunc+97 och DialogFunc+113. Instruktionerna vi är intresserade av börjar här:

Relativt långt stycke kod

loc_401196: mov esi, mov edi, ds:SendDlgItemMessageA lea ecx, push ecx ; lParam tryck 0Ah; wParam push 0Dh ; Msg push 3E8h ; nIDDlgItem push esi ; hDlg anrop edi ; SendDlgItemMessageA lea edx, push edx ; lParam tryck 0Ah; wParam push 0Dh ; Medd tryck 3E9h ; nIDDlgItem push esi ; hDlg anrop edi ; SendDlgItemMessageA pusha movsx ecx, byte ptr movsx edx, byte ptr movsx eax, byte ptr shl eax, 8 or eax, ecx movsx ecx, byte ptr shl eax, 8 or eax, eax, eax, eax, eax a mov eax, push eax call sub_401100

Först anropas två SendDlgItemMessageA-funktioner i rad. Denna funktion tar elementets handtag och skickar systemet till det Meddelande. I vårt fall är Msg lika med 0Dh i båda fallen, vilket är den hexadecimala ekvivalenten till WM_GETTEXT-konstanten. Detta hämtar värdena för två textfält där användaren skrev in "två 4-teckensträngar". Bokstaven A i funktionsnamnet indikerar att ASCII-formatet används - en byte per tecken.
Den första raden skrivs vid offset lParam, den andra, uppenbarligen, vid offset var_1C.
Så efter exekvering av SendDlgItemMessageA-funktionerna lagras registrens nuvarande tillstånd i stacken med hjälp av pusha-kommandot, sedan skrivs en byte av en av raderna till ecx-, edx- och eax-registren. Som ett resultat har vart och ett av registren formen: 000000##. Sedan:

  1. SHL-instruktionen skiftar bitinnehållet i eax-registret med 1 byte, eller med andra ord multiplicerar det aritmetiska innehållet med 100 i hexadecimal eller 256 i decimal. Som ett resultat får eax formen 0000##00 (till exempel 00001200).
  2. En ELLER-operation utförs mellan det mottagna eax-värdet och ecx-registret i formen 000000## (låt det vara 00000034). Som ett resultat kommer eakh att se ut så här: 00001234.
  3. Den sista, fjärde byten av raden skrivs till den "frigjorda" ESH.
  4. Innehållet i eax flyttas återigen med en byte, vilket frigör utrymme i den låga byten för nästa kommando ELLER. Nu ser eakh ut så här: 00123400.
  5. ELLER-instruktionen exekveras, denna gång mellan eax och edx, som innehåller, säg, 00000056. Nu är eax 00123456.
  6. De två SHL-stegen eax,8 och OR upprepas, vilket resulterar i att det nya innehållet ecx (00000078) läggs till i "slutet" av eax. Som ett resultat lagrar eax värdet 12345678.
Detta värde lagras sedan i en "variabel" - en minnesplats vid offset arg_4. Tillståndet för registren (deras tidigare värden), som tidigare lagrats i stacken, tas bort från stacken och distribueras till registren. Sedan skrivs eax-registret igen till värdet vid offset arg_4 och detta värde skjuts från registret till stacken. Detta följs av ett anrop till funktionen sub_401100.

Vad är poängen med dessa operationer? Det är väldigt lätt att ta reda på även i praktiken, utan teori. Låt oss ställa in en brytpunkt i debuggern, till exempel på push eax-instruktionen (precis innan vi anropar underfunktionen) och kör programmet för exekvering. Keygen startar och ber dig att ange strängar. Genom att skriva in qwer och tyui och stanna vid brytpunkten tittar vi på värdet på eax: 72657771. Vi avkodar det till text: rewq. Det är fysisk mening av dessa operationer - stränginversion.

Nu vet vi att sub_401100 sänder en av de ursprungliga strängarna, vänd bakåt, i storleken av ett dubbelord, helt och hållet som passar in i vilket som helst av standardregistren. Du kanske kan ta en titt på instruktionerna sub_401100.

Ännu en relativt lång kod

sub_401100 proc nära var_4= dword ptr -4 arg_0= dword ptr 8 push ebp mov ebp, esp push ecx push ebx push esi push edi pusha mov ecx, mov eax, ecx shl eax, 10h ec mov e, ax shr eax, 5 xor eax, ecx lea ecx, mov edx, ecx shr edx, 0Dh xor ecx, edx mov eax, ecx shl eax, 9 inte eax lägg till ecx, eax mov eax, ecx shr ax, ecx shr eax, ecx shr eax , eax popa mov eax, pop edi pop esi pop ebx mov esp, ebp pop ebp retn sub_401100 endp


I början finns det inget intressant här - registrens tillstånd lagras noggrant på stapeln. Men det första kommandot som är intressant för oss är det som följer PUSHA-instruktionen. Den skriver funktionsargumentet lagrat vid offset arg_0 till exx. Därefter överförs detta värde till eakh. Och det skärs av med hälften: som vi minns, i vårt exempel, sänds 72657771 i sub_401100; en logisk vänsterförskjutning på 10h (16 i decimaler) förvandlar registervärdet till 77710000.
Därefter inverteras registervärdet med NOT-instruktionen. Detta betyder att i den binära representationen av registret förvandlas alla nollor till ettor och ettor till nollor. Registret efter att ha utfört denna instruktion innehåller 888EFFFF.
ADD-instruktionen lägger till (lägger till, plus, etc.) det resulterande värdet till det ursprungliga argumentvärdet, som fortfarande finns i ecx-registret (nu är det klart varför det skrevs först i ecx och sedan i eax?). Resultatet sparas i ex. Låt oss kolla hur esx kommer att se ut efter att ha utfört denna operation: FAF47770.
Detta resultat kopieras från exx till eax, varefter SHR-instruktionen appliceras på innehållet i eax. Denna operation är motsatsen till SHL - medan den senare flyttar bitar åt vänster, flyttar den förra dem åt höger. Precis som den logiska växlingsoperationen åt vänster är ekvivalent med att multiplicera med två potenser, är den logiska växlingsoperationen åt höger ekvivalent med samma division. Låt oss se vilket värde resultatet av denna operation blir: 7D7A3BB.
Låt oss nu göra ytterligare ett våld mot innehållet i eax och exx: XOR-instruktionen är addition modulo 2 eller "exklusiv OR". Kärnan i denna operation, grovt sett, är att som ett resultat lika med ett(sant) endast om dess operander är olika. Till exempel, i fallet med 0 xor 1 blir resultatet sant, eller ett. I fallet med 0 xor 0 eller 1 xor 1 blir resultatet falskt eller noll. I vårt fall kommer värdet FD23D4CB att skrivas till eax-registret som ett resultat av exekvering av denna instruktion i förhållande till eax (7D7A3BB) och exx (FAF47770) register.

Följande kommando, LEA ecx, multiplicerar elegant och enkelt eax med 9 och skriver resultatet till ecx. Detta värde kopieras sedan till edx och skiftas åt höger med 13 bitar: vi får 73213 i edx och E6427B23 i ecx. Sedan - igen kopierar vi ecx och edx och skriver E6454930 till esx. Vi kopierar detta till eax, flyttar det åt vänster 9 bitar: 8A926000, inverterar det sedan och får 756D9FFF. Vi lägger till detta värde till ESH-registret - vi har 5BB2E92F. Vi kopierar detta till eax, flyttar det åt höger med så mycket som 17 bitar - 2DD9 - och kopierar det från exx. Vi slutar med 5BB2C4F6. Sen... då... vad har vi där? Vad allt?..
Så vi sparar detta värde i minnesområdet vid offset var_4, laddar registertillstånden från stacken, tar slutvärdet från minnet igen och tar slutligen bort de återstående registertillstånden som lagrats i början från stacken. Vi avslutar funktionen. Hurra!.. dock är det för tidigt att glädjas, än så länge har vi vid utgången av det första funktionsanropet max fyra halvutskrivbara tecken, och ändå har vi fortfarande en hel obearbetad sträng, och den här måste fortfarande vara förde till en gudomlig form.

Låt oss gå vidare till fler hög nivå analys - från disassembler till decompiler. Låt oss representera hela DialogFunc-funktionen, som innehåller anrop till sub_401100, i form av C-liknande pseudokod. Faktum är att demonteraren kallar det "pseudokod" i själva verket är det praktiskt taget C-kod, bara ful. Låt oss se:

Behöver mer kod. Vi måste bygga en ziggurat.

SendDlgItemMessageA(hDlg, 1000, 0xDu, 0xAu, (LPARAM)&lParam); SendDlgItemMessageA(hDlg, 1001, 0xDu, 0xAu, (LPARAM)&v15); v5 = sub_401100((char)lParam | ((SBYTE1(lParam) | ((SBYTE2(lParam) | (SBYTE3(lParam)<< 8)) << 8)) << 8)); v6 = 0; do { v21 = v5 % 0x24; v7 = v21; v5 /= 0x24u; if (v7 >= 10) v8 = v7 + 55; annars v8 = v7 + 48; v21 = v8; ) medan (v6< 4); v22 = 0; v9 = sub_401100(v15 | ((v16 | ((v17 | (v18 << 8)) << 8)) << 8)); v10 = 0; do { v19 = v9 % 0x24; v11 = v19; v9 /= 0x24u; if (v11 >= 10) v12 = vll + 55; annars v12 = v11 + 48; v19 = v12; ) medan (v10< 4); v20 = 0; wsprintfA(&v13, "%s-%s-%s-%s", &lParam, &v15, v21, v19); SendDlgItemMessageA(hDlg, 1002, 0xCu, 0, (LPARAM)&v13);

Detta är redan lättare att läsa än monteringslistan. Men inte i alla fall kan du lita på en dekompilator: du måste vara beredd att spendera timmar på att övervaka tråden av assemblerlogik, tillstånden för register och stacken i debuggern... och sedan ge skriftliga förklaringar till FSB eller FBI anställda. På kvällen drar jag speciellt roliga skämt.
Som sagt, det är lättare att läsa, men ändå långt ifrån perfekt. Låt oss analysera koden och ge variablerna mer läsbara namn. Låt oss ge tydliga och logiska namn till nyckelvariabler och enklare namn till räknare och tillfälliga.

Samma sak, bara översatt från kinesiska till indiska.

SendDlgItemMessageA(hDlg, 1000, 0xDu, 0xAu, (LPARAM)&first_given_string); SendDlgItemMessageA(hDlg, 1001, 0xDu, 0xAu, (LPARAM)&second_given_string); first_given_string_encoded = sub_401100((char)first_given_string | ((SBYTE1(first_given_string) | ((SBYTE2(first_given_string) | (SBYTE3(first_given_string)<< 8)) << 8)) << 8)); i = 0; do { first_result_string[i] = first_string_encoded % 0x24; temp_char = first_result_string[i]; first_string_encoded /= 0x24u; if (temp_char >= 10) next_char = temp_char + 55; else next_char = temp_char + 48; första_resultatsträng = nästa_tecken; ) medan jag< 4); some_kind_of_data = 0; second_string_encoded = sub_401100(byte1 | ((byte2 | ((byte3 | (byte4 << 8)) << 8)) << 8)); j = 0; do { second_result_string[j] = second_string_encoded % 0x24; temp_char2 = second_result_string[j]; second_string_encoded /= 0x24u; if (temp_char2 >= 10) next_char2 = temp_char2 + 55; else next_char2 = temp_char2 + 48; andra_resultatsträng = nästa_tecken2; ) medan (j< 4); yet_another_some_kind_of_data = 0; wsprintfA(&buffer, "%s-%s-%s-%s", &first_given_string, &second_given_string, first_result_string, second_result_string); SendDlgItemMessageA(hDlg, 1002, 0xCu, 0, (LPARAM)&buffer);

  • Handledning

Det här inlägget kommer verkligen att vara intressant för dem som precis börjat vara intresserade av detta ämne. För personer med erfarenhet kommer det förmodligen bara att orsaka gäspningar. Förutom kanske...
Reverse engineering, i den mindre lagliga delen där det inte handlar om felsökning och optimering av den egna produkten, handlar också om följande uppgift: "ta reda på hur det fungerar för dem." Med andra ord, återställa den ursprungliga algoritmen för programmet, med dess körbara fil i handen.
För att hålla oss till grunderna och undvika vissa problem kommer vi att "hacka" inte bara vad som helst, utan... en keygen. I 90 % av fallen kommer den inte att packas, krypteras eller på annat sätt skyddas - inklusive av internationell lag...

I början fanns ordet. Dubbel
Så vi behöver en nyckelgen och en demonterare. När det gäller den andra, låt oss anta att det blir Ida Pro. Experimentell namnlös nyckel som hittats på Internet:

Efter att ha öppnat keygen-filen i Ida ser vi en lista med funktioner.

Efter att ha analyserat den här listan ser vi flera standardfunktioner (WinMain, start, DialogFunc) och ett gäng extra systemfunktioner. Dessa är alla standardfunktioner som utgör ramverket.
Disassembleraren känner inte igen användarfunktioner som representerar implementeringen av programuppgifter, och inte dess omslag av API- och systemanrop, och kallar dem helt enkelt sub_digits. Med tanke på att det bara finns en sådan funktion här borde den dra till sig vår uppmärksamhet eftersom den med största sannolikhet innehåller algoritmen eller en del av den som intresserar oss.

Låt oss köra keygen. Den ber dig att ange två 4-siffriga strängar. Anta att åtta tecken skickas till nyckelberäkningsfunktionen samtidigt. Låt oss analysera koden för funktionen sub_401100. Svaret på hypotesen finns i de två första raderna:

var_4= dword ptr -4
arg_0= dword ptr 8

Den andra raden antyder tydligt att man tar emot funktionsargumentet vid offset 8. Storleken på argumentet är dock ett dubbelord lika med 4 byte, inte 8. Detta betyder att funktionen med största sannolikhet bearbetar en sträng med fyra tecken i ett pass, och det kallas två gånger.
En fråga som säkert kan uppstå är: varför är en offset på 8 byte reserverad för att ta emot ett funktionsargument, men pekar på 4, eftersom det bara finns ett argument? Som vi minns växer stapeln nedåt; När ett värde läggs till stacken, minskas stackpekaren med motsvarande antal byte. Därför, efter att en funktions argument har lagts till i stacken och innan den börjar köras, läggs något annat till i stacken. Detta är uppenbarligen returadressen som skjuts upp i stacken efter anrop av samtalssystemfunktionen.

Låt oss hitta platser i programmet där anrop till sub401100-funktionen förekommer. Det visar sig att det verkligen finns två av dem: på adressen DialogFunc+97 och DialogFunc+113. Instruktionerna vi är intresserade av börjar här:

Relativt långt stycke kod

loc_401196: mov esi, mov edi, ds:SendDlgItemMessageA lea ecx, push ecx ; lParam tryck 0Ah; wParam push 0Dh ; Msg push 3E8h ; nIDDlgItem push esi ; hDlg anrop edi ; SendDlgItemMessageA lea edx, push edx ; lParam tryck 0Ah; wParam push 0Dh ; Medd tryck 3E9h ; nIDDlgItem push esi ; hDlg anrop edi ; SendDlgItemMessageA pusha movsx ecx, byte ptr movsx edx, byte ptr movsx eax, byte ptr shl eax, 8 or eax, ecx movsx ecx, byte ptr shl eax, 8 or eax, eax, eax, eax, eax a mov eax, push eax call sub_401100

Först anropas två SendDlgItemMessageA-funktioner i rad. Denna funktion tar elementets handtag och skickar det Systemmeddelande Medd. I vårt fall är Msg lika med 0Dh i båda fallen, vilket är den hexadecimala ekvivalenten till WM_GETTEXT-konstanten. Detta hämtar värdena för två textfält där användaren skrev in "två 4-teckensträngar". Bokstaven A i funktionsnamnet indikerar att ASCII-formatet används - en byte per tecken.
Den första raden skrivs vid offset lParam, den andra, uppenbarligen, vid offset var_1C.
Så efter exekvering av SendDlgItemMessageA-funktionerna lagras registrens nuvarande tillstånd i stacken med hjälp av pusha-kommandot, sedan skrivs en byte av en av raderna till ecx-, edx- och eax-registren. Som ett resultat har vart och ett av registren formen: 000000##. Sedan:

  1. SHL-instruktionen skiftar bitinnehållet i eax-registret med 1 byte, eller med andra ord multiplicerar det aritmetiska innehållet med 100 i hexadecimal eller 256 i decimal. Som ett resultat får eax formen 0000##00 (till exempel 00001200).
  2. En ELLER-operation utförs mellan det mottagna eax-värdet och ecx-registret i formen 000000## (låt det vara 00000034). Som ett resultat kommer eakh att se ut så här: 00001234.
  3. Den sista, fjärde byten av raden skrivs till den "frigjorda" ESH.
  4. Innehållet i eax skiftas med en byte igen, vilket ger plats i den låga byten för nästa ELLER-instruktion. Nu ser eakh ut så här: 00123400.
  5. ELLER-instruktionen exekveras, denna gång mellan eax och edx, som innehåller, säg, 00000056. Nu är eax 00123456.
  6. De två SHL-stegen eax,8 och OR upprepas, vilket resulterar i att det nya innehållet ecx (00000078) läggs till i "slutet" av eax. Som ett resultat lagrar eax värdet 12345678.
Detta värde lagras sedan i en "variabel" - en minnesplats vid offset arg_4. Tillståndet för registren (deras tidigare värden), som tidigare lagrats i stacken, tas bort från stacken och distribueras till registren. Sedan skrivs eax-registret igen till värdet vid offset arg_4 och detta värde skjuts från registret till stacken. Detta följs av ett anrop till funktionen sub_401100.

Vad är poängen med dessa operationer? Det är väldigt lätt att ta reda på även i praktiken, utan teori. Låt oss ställa in en brytpunkt i debuggern, till exempel på push eax-instruktionen (precis innan vi anropar underfunktionen) och kör programmet för exekvering. Keygen startar och ber dig att ange strängar. Genom att skriva in qwer och tyui och stanna vid brytpunkten tittar vi på värdet på eax: 72657771. Vi avkodar det till text: rewq. Det vill säga, den fysiska innebörden av dessa operationer är stränginversion.

Nu vet vi att sub_401100 sänder en av de ursprungliga strängarna, vänd bakåt, i storleken av ett dubbelord, helt och hållet som passar in i vilket som helst av standardregistren. Du kanske kan ta en titt på instruktionerna sub_401100.

Ännu en relativt lång kod

sub_401100 proc nära var_4= dword ptr -4 arg_0= dword ptr 8 push ebp mov ebp, esp push ecx push ebx push esi push edi pusha mov ecx, mov eax, ecx shl eax, 10h ec mov e, ax shr eax, 5 xor eax, ecx lea ecx, mov edx, ecx shr edx, 0Dh xor ecx, edx mov eax, ecx shl eax, 9 inte eax lägg till ecx, eax mov eax, ecx shr ax, ecx shr eax, ecx shr eax , eax popa mov eax, pop edi pop esi pop ebx mov esp, ebp pop ebp retn sub_401100 endp


I början finns det inget intressant här - registrens tillstånd lagras noggrant på stapeln. Men det första kommandot som är intressant för oss är det som följer PUSHA-instruktionen. Den skriver funktionsargumentet lagrat vid offset arg_0 till exx. Därefter överförs detta värde till eakh. Och det skärs av med hälften: som vi minns, i vårt exempel, sänds 72657771 i sub_401100; en logisk vänsterförskjutning på 10h (16 i decimaler) förvandlar registervärdet till 77710000.
Därefter inverteras registervärdet med NOT-instruktionen. Detta betyder att i den binära representationen av registret förvandlas alla nollor till ettor och ettor till nollor. Registret efter att ha utfört denna instruktion innehåller 888EFFFF.
ADD-instruktionen lägger till (lägger till, plus, etc.) det resulterande värdet till det ursprungliga argumentvärdet, som fortfarande finns i ecx-registret (nu är det klart varför det skrevs först i ecx och sedan i eax?). Resultatet sparas i ex. Låt oss kolla hur esx kommer att se ut efter att ha utfört denna operation: FAF47770.
Detta resultat kopieras från exx till eax, varefter SHR-instruktionen appliceras på innehållet i eax. Denna operation är motsatsen till SHL - medan den senare flyttar bitar åt vänster, flyttar den förra dem åt höger. Precis som den logiska växlingsoperationen åt vänster är ekvivalent med att multiplicera med två potenser, är den logiska växlingsoperationen åt höger ekvivalent med samma division. Låt oss se vilket värde resultatet av denna operation blir: 7D7A3BB.
Låt oss nu göra ytterligare ett våld mot innehållet i eax och exx: XOR-instruktionen är addition modulo 2 eller "exklusiv OR". Kärnan i denna operation, grovt sett, är att dess resultat är lika med ett (sant) endast om dess operander är olika. Till exempel, i fallet med 0 xor 1 blir resultatet sant, eller ett. I fallet med 0 xor 0 eller 1 xor 1 blir resultatet falskt eller noll. I vårt fall kommer värdet FD23D4CB att skrivas till eax-registret som ett resultat av exekvering av denna instruktion i förhållande till eax (7D7A3BB) och exx (FAF47770) register.

Följande kommando, LEA ecx, multiplicerar elegant och enkelt eax med 9 och skriver resultatet till ecx. Detta värde kopieras sedan till edx och skiftas åt höger med 13 bitar: vi får 73213 i edx och E6427B23 i ecx. Sedan - igen kopierar vi ecx och edx och skriver E6454930 till esx. Vi kopierar detta till eax, flyttar det åt vänster 9 bitar: 8A926000, inverterar det sedan och får 756D9FFF. Vi lägger till detta värde till ESH-registret - vi har 5BB2E92F. Vi kopierar detta till eax, flyttar det åt höger med så mycket som 17 bitar - 2DD9 - och kopierar det från exx. Vi slutar med 5BB2C4F6. Sen... då... vad har vi där? Vad allt?..
Så vi sparar detta värde i minnesområdet vid offset var_4, laddar registertillstånden från stacken, tar slutvärdet från minnet igen och tar slutligen bort de återstående registertillstånden som lagrats i början från stacken. Vi avslutar funktionen. Hurra!.. dock är det för tidigt att glädjas, än så länge har vi vid utgången av det första funktionsanropet max fyra halvutskrivbara tecken, och ändå har vi fortfarande en hel obearbetad sträng, och den här måste fortfarande vara förde till en gudomlig form.

Låt oss gå vidare till en högre analysnivå - från en disassembler till en dekompilator. Låt oss representera hela DialogFunc-funktionen, som innehåller anrop till sub_401100, i form av C-liknande pseudokod. Faktum är att demonteraren kallar det "pseudokod" i själva verket är det praktiskt taget C-kod, bara ful. Låt oss se:

Behöver mer kod. Vi måste bygga en ziggurat.

SendDlgItemMessageA(hDlg, 1000, 0xDu, 0xAu, (LPARAM)&lParam); SendDlgItemMessageA(hDlg, 1001, 0xDu, 0xAu, (LPARAM)&v15); v5 = sub_401100((char)lParam | ((SBYTE1(lParam) | ((SBYTE2(lParam) | (SBYTE3(lParam)<< 8)) << 8)) << 8)); v6 = 0; do { v21 = v5 % 0x24; v7 = v21; v5 /= 0x24u; if (v7 >= 10) v8 = v7 + 55; annars v8 = v7 + 48; v21 = v8; ) medan (v6< 4); v22 = 0; v9 = sub_401100(v15 | ((v16 | ((v17 | (v18 << 8)) << 8)) << 8)); v10 = 0; do { v19 = v9 % 0x24; v11 = v19; v9 /= 0x24u; if (v11 >= 10) v12 = vll + 55; annars v12 = v11 + 48; v19 = v12; ) medan (v10< 4); v20 = 0; wsprintfA(&v13, "%s-%s-%s-%s", &lParam, &v15, v21, v19); SendDlgItemMessageA(hDlg, 1002, 0xCu, 0, (LPARAM)&v13);

Detta är redan lättare att läsa än monteringslistan. Men inte i alla fall kan du lita på en dekompilator: du måste vara beredd att spendera timmar på att övervaka tråden av assemblerlogik, tillstånden för register och stacken i debuggern... och sedan ge skriftliga förklaringar till FSB eller FBI anställda. På kvällen drar jag speciellt roliga skämt.
Som sagt, det är lättare att läsa, men ändå långt ifrån perfekt. Låt oss analysera koden och ge variablerna mer läsbara namn. Låt oss ge tydliga och logiska namn till nyckelvariabler och enklare namn till räknare och tillfälliga.

Samma sak, bara översatt från kinesiska till indiska.

SendDlgItemMessageA(hDlg, 1000, 0xDu, 0xAu, (LPARAM)&first_given_string); SendDlgItemMessageA(hDlg, 1001, 0xDu, 0xAu, (LPARAM)&second_given_string); first_given_string_encoded = sub_401100((char)first_given_string | ((SBYTE1(first_given_string) | ((SBYTE2(first_given_string) | (SBYTE3(first_given_string)<< 8)) << 8)) << 8)); i = 0; do { first_result_string[i] = first_string_encoded % 0x24; temp_char = first_result_string[i]; first_string_encoded /= 0x24u; if (temp_char >= 10) next_char = temp_char + 55; else next_char = temp_char + 48; första_resultatsträng = nästa_tecken; ) medan jag< 4); some_kind_of_data = 0; second_string_encoded = sub_401100(byte1 | ((byte2 | ((byte3 | (byte4 << 8)) << 8)) << 8)); j = 0; do { second_result_string[j] = second_string_encoded % 0x24; temp_char2 = second_result_string[j]; second_string_encoded /= 0x24u; if (temp_char2 >= 10) next_char2 = temp_char2 + 55; else next_char2 = temp_char2 + 48; andra_resultatsträng = nästa_tecken2; ) medan (j< 4); yet_another_some_kind_of_data = 0; wsprintfA(&buffer, "%s-%s-%s-%s", &first_given_string, &second_given_string, first_result_string, second_result_string); SendDlgItemMessageA(hDlg, 1002, 0xCu, 0, (LPARAM)&buffer);







2024 gtavrl.ru.