La Nuova Guida al Cracking
From UIC
La Nuova Guida al Cracking
Contents |
| Infos | |
|---|---|
| Author: | Ntoskrnl |
| Email: | ntoskrnl@gmail.com |
| Website: | |
| Date: | ??/01/2005 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | |
Corre l'anno 2005 (01/2005) e ne è passato di tempo da quando si è vista l'ultima guida al cracking in Italia. Nel frattempo abbiamo attraversato tante cose, nuovi sistemi operativi, nuovi metodi di protezione ecc. Adesso credo che sia giunto il momento di una nuova guida perché si stanno avvicinando grandi cambiamenti. La guida di Xoa è stata scritta quasi 10 anni fa e da allora abbiamo visto come i programmatori sono divenuti sempre più smaliziati e abbiamo visto il proliferarsi di Packers e Crypters. Anche il mondo del cracking è molto cambiato da allora, le crew e i singoli cracker hanno perso totalmente la visibilità. E su ciò qualche considerazione secondo me va fatta, infatti mi sembra interessante riportare qualche spezzone delle mie innumerevoli conversazioni con Xoanon, dato che lui veramente ha potuto vedere l'evolversi della scena. In verità non è una conversazione preparata né tantomeno pensata per esser pubblicata, quindi non è che segua un vero filo logico. I vari - corrispondono a nuova riga.
Xoanon: bah - il reversing è una cosa meccanica è solo il fatto d'averci tempo da buttarci via - non c'è niente di inventivo - purtroppo di sti tempi con i p2p diventà qualcuno nella scena è praticamente impossibile - ai miei tempi si poteva perché c'era + visibilità per i gruppi - oggi chi vuole qualcosa se lo trova in 5 minuti ... è troppo inflazionato oggi - io son diventato qualcuno nei primi anni 90 - ma era facilissimo - non c'era concorrenza - non è paragonabile assolutamente a oggi - beh c'è troppa inflazione oggi - e è stato già scoperto e fatto tutto quello che c'era da fa - indi - è solo una questione di pazienza nel reversing - la bravura ormai non serve + - visto che è solo un fatto di pazienza ... dei vecchi - dei miei tempi - difatti hanno smesso tutti - il gioco non vale la candela - prima crakkavi per avere gli account sugli ftp 0day - oggi con il p2p non serve + - e poi effettivamente prima diventavi qualcuno ... io quando crakkai il bleem - mi idolatravano - oggi pure penso se crakki starforce - un ti caa nessuno (livornese: caga, ndnt) - non c'è + la cultura del crakkà coi p2p - qualcuno crakka,sì - ma non ha la visibilità di prima - prima per trovà la roba crakkata dovevi essè nella scena - avè gli acc sugli ftp - oggi non c'è + questa cosa - causa p2p - indi la gente ignora tutto quello che c'è dietro ... io mi son fatto mezza scena amiga e tutta la scena pc - oggi la scena come s'intendeva prima non esiste più - prima eri forzato a conoscè la gente che crakkava,se non la conoscevi non c'avevi accesso alla roba - oggi non è + così - beh se vuoi avè un po' di visibilità la cosa che rimane oggi - piglia explorer e trova qualche exploit - e facci un worm - così c'hai visibilità ... e cmq,pure scrivè su frack - ripeto,non c'hai + la visibilità di prima - oggi chiunque apre emule e trova qualunque cosa - la maniera per avè visibilità oggi è fare un worm
In effetti i tempi di Xoanon, Fravia, Kill3xx e persino di personaggi come Pietrek, Russinovich ecc. sono passati. In ogni caso i cambiamenti che stanno avvenendo nell'ultimo periodo sono molto grandi e mi hanno convinto a scrivere una nuova guida perché si sta procedendo in una direzione totalmente diversa da quella vista finora. In questa guida non spiegherò roba che si trova già in migliaia di articoli... No, qua vedremo come avvicinarci al futuro del cracking: il .NET.
Introduzione
In effetti mi pare che gli ambienti di cracking siano ancora molto restii a convertirsi, ma d'altronde il .NET è il futuro. Fra due o tre anni la maggior parte delle applicazioni (e persino alcuni driver) saranno compilati in MSIL (MicroSoft Intermediate Language). Certo questo non significa che dobbiamo disimparare il normale assembly (anzi conviene avvantaggiarsi sui processori sull'ia64), ma come ben presto vedrete vi sarà una sempre maggiore proliferazione di applicativi codati in C#, VB.NET e Managed C++. Senza contare tutti gli altri linguaggi che possono aggiungersi alla tecnologia .NET. Grazie al namespace System.Reflection.Emit, infatti, è possibile creare propri compilatori IL per il linguaggio che vogliamo. Insomma il .NET è multipiattaforma, non dipende né dal processore né dal sistema operativo in uso. Tutto quel che gli occorre è che sia installato il framework tramite il quale funziona. Persino le applicazioni dei palmari e dei telefonini con Windows Mobile installato hanno subìto una forte crescita di applicativi .NET (infatti anche io a paragone fatto tra l'Embedded VC++ e il .NET, ho scelto di programmare in .NET per il mio palmare (e appena uscito il compact framework 2.0 sarà veramente molto potente)), ed è facilmente comprensibile il perché: non è necessario compilare il proprio programma per tutti i diversi processori sul mercato (bisogna poi dire che anche sul Symbian è possibile installare un framework .NET). Oltretutto c'è da dire che il .NET farà il suo vero debutto con longhorn e le migliaia di nuove classi che dovrebbe apportare il nuovo sistema operativo. Classi che renderanno il Win32 obsoleto, questo è certo. Come prima cosa quindi prima di passare al cracking, dobbiamo vedere cosa crackare: ovvero l'IL.
MicroSoft Intermediate Language
L'IL è un linguaggio intermedio che sta tra il linguaggio nel quale abbiamo programmato e il codice macchina. Generalmente siamo abituati a scrivere un programma, compilarlo e trovarci di fronte a un eseguibile con all'interno codice macchina. Al posto del codice macchina avremmo appunto l'IL. La differenza tra codice macchina e IL è che quest'ultimo non dipende appunto dalla macchina sulla quale viene eseguito dato che si tratta di un'astrazione (pseudo assembly) facilmente convertibile nel codice macchina del processore sul quale viene eseguito. Il .NET usa in maniera molto efficiente un meccanismo di conversione da pseudo-assembly a assembly chiamato JIT (Just In Time) che traduce il codice solo quando è necessario, questo riduce drasticamente il tempo di caricamento, oltretutto una volta convertita una parte di codice non vi sarà bisogno di riconvertirla durante l'esecuzione dell'eseguibile. Se siete interessati a sapere di più riguardo all'architettura .NET vi consiglio un libro apposito (anzi sarebbe il caso, dato che probabilmente capirete a fondo l'IL solo se conoscete anche il framework su cui gira), adesso passeremo direttamente ad occuparci di codice IL. Nonostante il fatto che si possa anche programmare in IL (compilando coll'utility ilasm.exe, fornita dalla MS), noi partiremo disassemblando (è per questo che siamo qui). Tanto poi, programmando o analizzando codice, l'IL s'impara comunque. Però prima di disassemblare ci serve un programma compilato in IL, siccome dobbiamo partire da qualcosa di facile mi sono fatto un piccolo programma in C#. Ecco il codice:
namespace SimpleApplication
{
class SimpleClass
{
static void Main()
{
}
}
}
Compiliamolo e otterremo un programma che non fa niente, ma che ci basterà per dare un primo sguardo all'IL. Adesso per disassemblare possiamo sia usare ildasm.exe (fornito dalla MS e situato nella cartella del SDK) oppure usare il nostro solito IDA. Comincio con lo spiegare ildasm dato che probabilmente è quello per la maggior parte di voi meno familiare. Apriamo il prog compilato con ildasm e troveremo una cosa di questo genere:

Il significato dei vari simboli ve lo pasto direttamente dal MSDN.

Se clickiamo sul metodo main due volte otterremo una finestra che ci mostra il suo codice:
{
.entrypoint
// Code size 1 (0x1)
.maxstack 0
IL_0000: ret
} // end of method SimpleClass::Main
Tutto ciò che inizia con un "." in IL sta per una direttiva, in questo caso indichiamo un metodo, tutto ciò, invece, che non è preceduto da punti, sono le istruzioni da eseguire. La direttiva entrypoint indica appunto l'entrypoint del codice, è una direttiva che in un assembly può esser usata una sola volta. Se nello stesso assembly la usate più volte e provate a compilare con ilasm.exe, quest'ultimo vi darà errore. I metodi sono delimitati dalle parentesi graffe, ciò nonostante è opportuno concludere il metodo con ret. L'attributo hidebysig nasconde il metodo a classi derivate. Se adesso clickiamo sul triangolino rosso della classe vedremo:
extends [mscorlib]System.Object
{
} // end of class SimpleClass
Da qui vediamo che la nostra classe è un'estensione della classe System.Object. Ciò comporta l'esistenza dovuta del costruttore ctor. Ora prima di continuare una tabella coll'instruction set (framework 1.1) e relativi opcodes.
| Instruction | Opcode (Hex) | Short Description |
| add |
| Adds two values and pushes the result onto the evaluation stack. |
| add.ovf |
| Adds two integers, performs an overflow check, and pushes the result onto the evaluation stack. |
| add.ovf.un |
| Adds two unsigned integer values, performs an overflow check, and pushes the result onto the evaluation stack. |
| and |
| Computes the bitwise AND of two values and pushes the result onto the evalution stack. |
| arglist |
| Returns an unmanaged pointer to the argument list of the current method. |
| beq |
| Transfers control to a target instruction if two values are equal. |
| beq.s |
| Transfers control to a target instruction (short form) if two values are equal. |
| bge |
| Transfers control to a target instruction if the first value is greater than or equal to the second value. |
| bge.s |
| Transfers control to a target instruction (short form) if the first value is greater than or equal to the second value. |
| bge.un |
| Transfers control to a target instruction if the the first value is greather than the second value, when comparing unsigned integer values or unordered float values. |
| bge.un.s |
| Transfers control to a target instruction (short form) if if the the first value is greather than the second value, when comparing unsigned integer values or unordered float values. |
| bgt |
| Transfers control to a target instruction if the first value is greater than the second value. |
| bgt.s |
| Transfers control to a target instruction (short form) if the first value is greater than the second value. |
| bgt.un |
| Transfers control to a target instruction if the first value is greater than the second value, when comparing unsigned integer values or unordered float values. |
| bgt.un.s |
| Transfers control to a target instruction (short form) if the first value is greater than the second value, when comparing unsigned integer values or unordered float values. |
| ble |
| Transfers control to a target instruction if the first value is less than or equal to the second value. |
| ble.s |
| Transfers control to a target instruction (short form) if the first value is less than or equal to the second value. |
| ble.un |
| Transfers control to a target instruction if the first value is less than or equal to the second value, when comparing unsigned integer values or unordered float values. |
| ble.un.s |
| Transfers control to a target instruction (short form) if the first value is less than or equal to the second value, when comparing unsigned integer values or unordered float values. |
| blt |
| Transfers control to a target instruction if the first value is less than the second value. |
| blt.s |
| Transfers control to a target instruction (short form) if the first value is less than the second value. |
| blt.un |
| Transfers control to a target instruction if the first value is less than the second value, when comparing unsigned integer values or unordered float values. |
| blt.un.s |
| Transfers control to a target instruction (short form) if the first value is less than the second value, when comparing unsigned integer values or unordered float values. |
| bne.un |
| Transfers control to a target instruction when two unsigned integer values or unordered float values are not equal. |
| bne.un.s |
| Transfers control to a target instruction (short form) when two unsigned integer values or unordered float values are not equal. |
| box |
| Converts a value type to an object reference (type O). |
| br |
| Unconditionally transfers control to a target instruction. |
| break |
| Signals the Common Language Infrastructure (CLI) to inform the debugger that a break point has been tripped. |
| brfalse |
| Transfers control to a target instruction if value is false, a null reference (Nothing in Visual Basic), or zero. |
| brfalse.s |
| Transfers control to a target instruction if value is false, a null reference, or zero. |
| brtrue |
| Transfers control to a target instruction if value is true, not null, or non-zero. |
| brtrue.s |
| Transfers control to a target instruction (short form) if value is true, not null, or non-zero. |
| br.s |
| Unconditionally transfers control to a target instruction (short form). |
| call |
| Calls the method indicated by the passed method descriptor. |
| calli |
| Calls the method indicated on the evaluation stack (as a pointer to an entry point) with arguments described by a calling convention. |
| callvirt |
| Calls a late-bound method on an object, pushing the return value onto the evaluation stack. |
| castclass |
| Attempts to cast an object passed by reference to the specified class. |
| ceq |
| Compares two values. If they are equal, the integer value 1 (int32) is pushed onto the evaluation stack; otherwise 0 (int32) is pushed onto the evaluation stack. |
| cgt |
| Compares two values. If the first value is greater than the second, the integer value 1 (int32) is pushed onto the evaluation stack; otherwise 0 (int32) is pushed onto the evaluation stack. |
| cgt.un |
| Compares two unsigned or unordered values. If the first value is greater than the second, the integer value 1 (int32) is pushed onto the evaluation stack; otherwise 0 (int32) is pushed onto the evaluation stack. |
| ckfinite |
| Throws ArithmeticException if value is not a finite number. |
| clt |
| Compares two values. If the first value is less than the second, the integer value 1 (int32) is pushed onto the evaluation stack; otherwise 0 (int32) is pushed onto the evaluation stack. |
| clt.un |
| Compares the unsigned or unordered values value1 and value2. If value1 is less than value2, then the integer value 1 (int32) is pushed onto the evaluation stack; otherwise 0 (int32) is pushed onto the evaluation stack. |
| conv.i |
| Converts the value on top of the evaluation stack to natural int. |
| conv.i1 |
| Converts the value on top of the evaluation stack to int8, then extends (pads) it to int32. |
| conv.i2 |
| Converts the value on top of the evaluation stack to int16, then extends (pads) it to int32. |
| conv.i4 |
| Converts the value on top of the evaluation stack to int32. |
| conv.i8 |
| Converts the value on top of the evaluation stack to int64. |
| conv.ovf.i |
| Converts the signed value on top of the evaluation stack to signed natural int, throwing OverflowException on overflow. |
| conv.ovf.i1 |
| Converts the signed value on top of the evaluation stack to signed int8 and extends it to int32, throwing OverflowException on overflow. |
| conv.ovf.i1.un |
| Converts the unsigned value on top of the evaluation stack to signed int8 and extends it to int32, throwing OverflowException on overflow. |
| conv.ovf.i2 |
| Converts the signed value on top of the evaluation stack to signed int16 and extending it to int32, throwing OverflowException on overflow. |
| conv.ovf.i2.un |
| Converts the unsigned value on top of the evaluation stack to signed int16 and extends it to int32, throwing OverflowException on overflow. |
| conv.ovf.i4 |
| Converts the signed value on top of the evaluation tack to signed int32, throwing OverflowException on overflow. |
| conv.ovf.i4.un |
| Converts the unsigned value on top of the evaluation stack to signed int32, throwing OverflowException on overflow. |
| conv.ovf.i8 |
| Converts the signed value on top of the evaluation stack to signed int64, throwing OverflowException on overflow. |
| conv.ovf.i8.un |
| Converts the unsigned value on top of the evaluation stack to signed int64, throwing OverflowException on overflow. |
| conv.ovf.i.un |
| Converts the unsigned value on top of the evaluation stack to signed natural int, throwing OverflowException on overflow. |
| conv.ovf.u |
| Converts the signed value on top of the evaluation stack to unsigned natural int, throwing OverflowException on overflow. |
| conv.ovf.u1 |
| Converts the signed value on top of the evaluation stack to unsigned int8 and extends it to int32, throwing OverflowException on overflow. |
| conv.ovf.u1.un |
| Converts the unsigned value on top of the evaluation stack to unsigned int8 and extends it to int32, throwing OverflowException on overflow. |
| conv.ovf.u2 |
| Converts the signed value on top of the evaluation stack to unsigned int16 and extends it to int32, throwing OverflowException on overflow. |
| conv.ovf.u2.un |
| Converts the unsigned value on top of the evaluation stack to unsigned int16 and extends it to int32, throwing OverflowException on overflow. |
| conv.ovf.u4 |
| Converts the signed value on top of the evaluation stack to unsigned int32, throwing OverflowException on overflow. |
| conv.ovf.u4.un |
| Converts the unsigned value on top of the evaluation stack to unsigned int32, throwing OverflowException on overflow. |
| conv.ovf.u8 |
| Converts the signed value on top of the evaluation stack to unsigned int64, throwing OverflowException on overflow. |
| conv.ovf.u8.un |
| Converts the unsigned value on top of the evaluation stack to unsigned int64, throwing OverflowException on overflow. |
| conv.ovf.u.un |
| Converts the unsigned value on top of the evaluation stack to unsigned natural int, throwing OverflowException on overflow. |
| conv.r4 |
| Converts the value on top of the evaluation stack to float32. |
| conv.r8 |
| Converts the value on top of the evaluation stack to float64. |
| conv.r.un |
| Converts the unsigned integer value on top of the evaluation stack to float32. |
| conv.u |
| Converts the value on top of the evaluation stack to unsigned natural int, and extends it to natural int. |
| conv.u1 |
| Converts the value on top of the evaluation stack to unsigned int8, and extends it to int32. |
| conv.u2 |
| Converts the value on top of the evaluation stack to unsigned int16, and extends it to int32. |
| conv.u4 |
| Converts the value on top of the evaluation stack to unsigned int32, and extends it to int32. |
| conv.u8 |
| Converts the value on top of the evaluation stack to unsigned int64, and extends it to int64. |
| cpblk |
| Copies a specified number bytes from a source address to a destination address. |
| cpobj |
| Copies the value type located at the address of an object (type &, * or natural int) to the address of the destination object (type &, * or natural int). |
| div |
| Divides two values and pushes the result as a floating-point (type F) or quotient (type int32) onto the evaluation stack. |
| div.un |
| Divides two unsigned integer values and pushes the result (int32) onto the evaluation stack. |
| dup |
| Copies the current topmost value on the evaluation stack, and then pushes the copy onto the evaluation stack. |
| endfilter |
| Transfers control from the filter clause of an exception back to the Common Language Infrastructure (CLI) exception handler. |
| endfinally |
| Transfers control from the fault or finally clause of an exception block back to the Common Language Infrastructure (CLI) exception handler. |
| initblk |
| Initializes a specified block of memory at a specific address to a given size and initial value. |
| initobj |
| Initializes all the fields of the object at a specific address to a null reference or a 0 of the appropriate primitive type. |
| isinst |
| Tests whether an object reference (type O) is an instance of a particular class. |
| jmp |
| Exits current method and jumps to specified method. |
| ldarg |
| Loads an argument (referenced by a specified index value) onto the stack. |
| ldarga |
| Load an argument address onto the evaluation stack. |
| ldarga.s |
| Load an argument address, in short form, onto the evaluation stack. |
| ldarg.0 |
| Loads the argument at index 0 onto the evaluation stack. |
| ldarg.1 |
| Loads the argument at index 1 onto the evaluation stack. |
| ldarg.2 |
| Loads the argument at index 2 onto the evaluation stack. |
| ldarg.3 |
| Loads the argument at index 3 onto the evaluation stack. |
| ldarg.s |
| Loads the argument (referenced by a specified short form index) onto the evaluation stack. |
| ldc.i4 |
| Pushes a supplied value of type int32 onto the evaluation stack as an int32. |
| ldc.i4.0 |
| Pushes the integer value of 0 onto the evaluation stack as an int32. |
| ldc.i4.1 |
| Pushes the integer value of 1 onto the evaluation stack as an int32. |
| ldc.i4.2 |
| Pushes the integer value of 2 onto the evaluation stack as an int32. |
| ldc.i4.3 |
| Pushes the integer value of 3 onto the evaluation stack as an int32. |
| ldc.i4.4 |
| Pushes the integer value of 4 onto the evaluation stack as an int32. |
| ldc.i4.5 |
| Pushes the integer value of 5 onto the evaluation stack as an int32. |
| ldc.i4.6 |
| Pushes the integer value of 6 onto the evaluation stack as an int32. |
| ldc.i4.7 |
| Pushes the integer value of 7 onto the evaluation stack as an int32. |
| ldc.i4.8 |
| Pushes the integer value of 8 onto the evaluation stack as an int32. |
| ldc.i4.m1 |
| Pushes the integer value of -1 onto the evaluation stack as an int32. |
| ldc.i4.s |
| Pushes the supplied int8 value onto the evaluation stack as an int32, short form. |
| ldc.i8 |
| Pushes a supplied value of type int64 onto the evaluation stack as an int64. |
| ldc.r4 |
| Pushes a supplied value of type float32 onto the evaluation stack as type F (float). |
| ldc.r8 |
| Pushes a supplied value of type float64 onto the evaluation stack as type F (float). |
| ldelema |
| Loads the address of the array element at a specified array index onto the top of the evaluation stack as type & (managed pointer). |
| ldelem.i |
| Loads the element with type natural int at a specified array index onto the top of the evaluation stack as a natural int. |
| ldelem.i1 |
| Loads the element with type int8 at a specified array index onto the top of the evaluation stack as an int32. |
| ldelem.i2 |
| Loads the element with type int16 at a specified array index onto the top of the evaluation stack as an int32. |
| ldelem.i4 |
| Loads the element with type int32 at a specified array index onto the top of the evaluation stack as an int32. |
| ldelem.i8 |
| Loads the element with type int64 at a specified array index onto the top of the evaluation stack as an int64. |
| ldelem.r4 |
| Loads the element with type float32 at a specified array index onto the top of the evaluation stack as type F (float). |
| ldelem.r8 |
| Loads the element with type float64 at a specified array index onto the top of the evaluation stack as type F (float). |
| ldelem.ref |
| Loads the element containing an object reference at a specified array index onto the top of the evaluation stack as type O (object reference). |
| ldelem.u1 |
| Loads the element with type unsigned int8 at a specified array index onto the top of the evaluation stack as an int32. |
| ldelem.u2 |
| Loads the element with type unsigned int16 at a specified array index onto the top of the evaluation stack as an int32. |
| ldelem.u4 |
| Loads the element with type unsigned int32 at a specified array index onto the top of the evaluation stack as an int32. |
| ldfld |
| Finds the value of a field in the object whose reference is currently on the evaluation stack. |
| ldflda |
| Finds the address of a field in the object whose reference is currently on the evaluation stack. |
| ldftn |
| Pushes an unmanaged pointer (type natural int) to the native code implementing a specific method onto the evaluation stack. |
| ldind.i |
| Loads a value of type natural int as a natural int onto the evaluation stack indirectly. |
| ldind.i1 |
| Loads a value of type int8 as an int32 onto the evaluation stack indirectly. |
| ldind.i2 |
| Loads a value of type int16 as an int32 onto the evaluation stack indirectly. |
| ldind.i4 |
| Loads a value of type int32 as an int32 onto the evaluation stack indirectly. |
| ldind.i8 |
| Loads a value of type int64 as an int64 onto the evaluation stack indirectly. |
| ldind.r4 |
| Loads a value of type float32 as a type F (float) onto the evaluation stack indirectly. |
| ldind.r8 |
| Loads a value of type float64 as a type F (float) onto the evaluation stack indirectly. |
| ldind.ref |
| Loads an object reference as a type O (object reference) onto the evaluation stack indirectly. |
| ldind.u1 |
| Loads a value of type unsigned int8 as an int32 onto the evaluation stack indirectly. |
| ldind.u2 |
| Loads a value of type unsigned int16 as an int32 onto the evaluation stack indirectly. |
| ldind.u4 |
| Loads a value of type unsigned int32 as an int32 onto the evaluation stack indirectly. |
| ldlen |
| Pushes the number of elements of a zero-based, one-dimensional array onto the evaluation stack. |
| ldloc |
| Loads the local variable at a specific index onto the evaluation stack. |
| ldloca |
| Loads the address of the local variable at a specific index onto the evaluation stack. |
| ldloca.s |
| Loads the address of the local variable at a specific index onto the evaluation stack, short form. |
| ldloc.0 |
| Loads the local variable at index 0 onto the evaluation stack. |
| ldloc.1 |
| Loads the local variable at index 1 onto the evaluation stack. |
| ldloc.2 |
| Loads the local variable at index 2 onto the evaluation stack. |
| ldloc.3 |
| Loads the local variable at index 3 onto the evaluation stack. |
| ldloc.s |
| Loads the local variable at a specific index onto the evaluation stack, short form. |
| ldnull |
| Pushes a null reference (type O) onto the evaluation stack. |
| ldobj |
| Copies the value type object pointed to by an address to the top of the evaluation stack. |
| ldsfld |
| Pushes the value of a static field onto the evaluation stack. |
| ldsflda |
| Pushes the address of a static field onto the evaluation stack. |
| ldstr |
| Pushes a new object reference to a string literal stored in the metadata. |
| ldtoken |
| Converts a metadata token to its runtime representation, pushing it onto the evaluation stack. |
| ldvirtftn |
| Pushes an unmanaged pointer (type natural int) to the native code implementing a particular virtual method associated with a specified object onto the evaluation stack. |
| leave |
| Exits a protected region of code, unconditionally tranferring control to a specific target instruction. |
| leave.s |
| Exits a protected region of code, unconditionally tranferring control to a target instruction (short form). |
| localloc |
| Allocates a certain number of bytes from the local dynamic memory pool and pushes the address (a transient pointer, type *) of the first allocated byte onto the evaluation stack. |
| mkrefany |
| Pushes a typed reference to an instance of a specific type onto the evaluation stack. |
| mul |
| Multiplies two values and pushes the result on the evaluation stack. |
| mul.ovf |
| Multiplies two integer values, performs an overflow check, and pushes the result onto the evaluation stack. |
| mul.ovf.un |
| Multiplies two unsigned integer values, performs an overflow check, and pushes the result onto the evaluation stack. |
| neg |
| Negates a value and pushes the result onto the evaluation stack. |
| newarr |
| Pushes an object reference to a new zero-based, one-dimensional array whose elements are of a specific type onto the evaluation stack. |
| newobj |
| Creates a new object or a new instance of a value type, pushing an object reference (type O) onto the evaluation stack. |
| nop |
| Fills space if opcodes are patched. No meaningful operation is performed although a processing cycle can be consumed. |
| not |
| Computes the bitwise complement of the integer value on top of the stack and pushes the result onto the evaluation stack as the same type. |
| or |
| Compute the bitwise complement of the two integer values on top of the stack and pushes the result onto the evaluation stack. |
| pop |
| Removes the value currently on top of the evaluation stack. |
| refanytype |
| Retrieves the type token embedded in a typed reference. |
| refanyval |
| Retrieves the address (type &) embedded in a typed reference. |
| rem |
| Divides two values and pushes the remainder onto the evaluation stack. |
| rem.un |
| Divides two unsigned values and pushes the remainder onto the evaluation stack. |
| ret |
| Returns from the current method, pushing a return value (if present) from the caller's evaluation stack onto the callee's evaluation stack. |
| rethrow |
| Rethrows the current exception. |
| shl |
| Shifts an integer value to the left (in zeroes) by a specified number of bits, pushing the result onto the evaluation stack. |
| shr |
| Shifts an integer value (in sign) to the right by a specified number of bits, pushing the result onto the evaluation stack. |
| shr.un |
| Shifts an unsigned integer value (in zeroes) to the right by a specified number of bits, pushing the result onto the evaluation stack. |
| sizeof |
| Pushes the size, in bytes, of a supplied value type onto the evaluation stack. |
| starg |
| Stores the value on top of the evaluation stack in the argument slot at a specified index. |
| starg.s |
| Stores the value on top of the evaluation stack in the argument slot at a specified index, short form. |
| stelem.i |
| Replaces the array element at a given index with the natural int value on the evaluation stack. |
| stelem.i1 |
| Replaces the array element at a given index with the int8 value on the evaluation stack. |
| stelem.i2 |
| Replaces the array element at a given index with the int16 value on the evaluation stack. |
| stelem.i4 |
| Replaces the array element at a given index with the int32 value on the evaluation stack. |
| stelem.i8 |
| Replaces the array element at a given index with the int64 value on the evaluation stack. |
| stelem.r4 |
| Replaces the array element at a given index with the float32 value on the evaluation stack. |
| stelem.r8 |
| Replaces the array element at a given index with the float64 value on the evaluation stack. |
| stelem.ref |
| Replaces the array element at a given index with the object ref value (type O) on the evaluation stack. |
| stfld |
| Replaces the value stored in the field of an object reference or pointer with a new value. |
| stind.i |
| Stores a value of type natural int at a supplied address. |
| stind.i1 |
| Stores a value of type int8 at a supplied address. |
| stind.i2 |
| Stores a value of type int16 at a supplied address. |
| stind.i4 |
| Stores a value of type int32 at a supplied address. |
| stind.i8 |
| Stores a value of type int64 at a supplied address. |
| stind.r4 |
| Stores a value of type float32 at a supplied address. |
| stind.r8 |
| Stores a value of type float64 at a supplied address. |
| stind.ref |
| Stores a object reference value at a supplied address. |
| stloc |
| Pops the current value from the top of the evaluation stack and stores it in a the local variable list at a specified index. |
| stloc.0 |
| Pops the current value from the top of the evaluation stack and stores it in a the local variable list at index 0. |
| stloc.1 |
| Pops the current value from the top of the evaluation stack and stores it in a the local variable list at index 1. |
| stloc.2 |
| Pops the current value from the top of the evaluation stack and stores it in a the local variable list at index 2. |
| stloc.3 |
| Pops the current value from the top of the evaluation stack and stores it in a the local variable list at index 3. |
| stloc.s |
| Pops the current value from the top of the evaluation stack and stores it in a the local variable list at index (short form). |
| stobj |
| Copies a value of a specified type from the evaluation stack into a supplied memory address. |
| stsfld |
| Replaces the value of a static field with a value from the evaluation stack. |
| sub |
| Subtracts one value from another and pushes the result onto the evaluation stack. |
| sub.ovf |
| Subtracts one integer value from another, performs an overflow check, and pushes the result onto the evaluation stack. |
| sub.ovf.un |
| Subtracts one unsigned integer value from another, performs an overflow check, and pushes the result onto the evaluation stack. |
| switch |
| Implements a jump table. |
| tail. |
| Performs a postfixed method call instruction such that the current method's stack frame is removed before the actual call instruction is executed. |
| throw |
| Throws the exception object currently on the evaluation stack. |
| unaligned. |
| Indicates that an address currently atop the evaluation stack might not be aligned to the natural size of the immediately following ldind, stind, ldfld, stfld, ldobj, stobj, initblk, or cpblk instruction. |
| unbox |
| Converts the boxed representation of a value type to its unboxed form. |
| volatile. |
| Specifies that an address currently atop the evaluation stack might be volatile, and the results of reading that location cannot be cached or that multiple stores to that location cannot be suppressed. |
| xor |
| Computes the bitwise XOR of the top two values on the evaluation stack, pushing the result onto the evaluation stack. |
Se vi servono informazioni aggiuntive riguardo alle specifiche dell'instruction set potete consultare l'MSDN oppure il documento "Partition III CIL.doc" nella cartella "\SDK\v1.1\Tool Developers Guide\docs" del vostro Visual Studio.
Sì, lo so, il linguaggio vi pare ancora molto estraneo, ma vedrete che vi ci abituerete presto, anzi l'IL come vedrete non è nemmeno difficile. E poi considerate un vantaggio: potrete reversare applicazioni per ogni dispositivo che supporta .NET, quindi, oltre al pc, palmari e telefoni, senza dover imparare una nuova sintassi. Come piccolo schemettino di gruppi principali di istruzioni possiamo dire che:
- i vari bgt, ble, bne, br ecc. sono i jump.
- i conv servono per convertire grandezze diverse di valori (tipo da byte a qword ecc.).
- i vari ldc, ldarg, ldelem ecc. corrispondono ai push (prendete per buono che tutte le istruzioni che cominciano con ld sono dei push).
- i vari stloc, starg, stelem ecc corrispondo ai pop (st*).
Per il resto molte istruzioni sono le stesse (quelle logico/matematiche ad esempio (xor, and, mul, sub ecc.)), anche se esistono anche dei falsi amici (il pop in IL non corrisponde esattamente al pop in asm). Ad ogni modo queste cose le vedrete strada facendo. Se avete un dubbio su un'istruzione, ricorrete alla tabella.
Vediamo una nuova applicazione che fa qualcosina in più rispetto a quella che abbiamo visto prima. Per adesso affianco il codice C# al disassemblato per rendere le cose più chiare, fra poco però smetterò di farlo e passeremo direttamente a vedere il codice in IL cercando di capire cosa fa.
namespace SimpleApplication
{
class SimpleClass
{
static void Main()
{
int Res = Calc(65535, 3, 5);
Console.Write("Result: ");
Console.WriteLine(Res);
Console.ReadLine();
}
static int Calc(int OriginalNumber, Byte a, Byte b)
{
return (OriginalNumber >> (a + b));
}
}
}
Questo codice non fa altro che chiamare la funzione Calc dalla Main, questa funzione ha tre parametri, un intero e due byte, i bit dell'intero vengono shiftati di un numero pari ad a più b. Dopodiché nella main viene stampato il risultato. In questo caso immettendo 65535 (FFFFh) come numero originale e shiftando di 8 bit otterremo 255 (FFh) come risultato. La ReadLine all'interno del codice l'ho messa affinché il programma non si chiuda subito se eseguito ma aspetti un input da parte dell'utente. Sì il codice non serve a nulla ma basta a noi per dare un occhio al linguaggio. Vediamo adesso il codice coll'ildasm. Partiamo dalla main:
{
.entrypoint
// Code size 36 (0x24)
.maxstack 3
.locals init (int32 V_0)
IL_0000: ldc.i4 0xffff
IL_0005: ldc.i4.3
IL_0006: ldc.i4.5
IL_0007: call int32 SimpleApplication.SimpleClass::Calc(int32,
unsigned int8,
unsigned int8)
IL_000c: stloc.0
IL_000d: ldstr "Result: "
IL_0012: call void [mscorlib]System.Console::Write(string)
IL_0017: ldloc.0
IL_0018: call void [mscorlib]System.Console::WriteLine(int32)
IL_001d: call string [mscorlib]System.Console::ReadLine()
IL_0022: pop
IL_0023: ret
} // end of method SimpleClass::Main
Allora l'IL è stack based come fra poco potremmo ben vedere, .maxstack ci dice che all'interno di questo metodo non vengono caricati sullo stack (virtual stack ovviamente) mai di più di 3 valori. La lista e l'inizializzazione delle variabili locali del metodo viene effettuata tramite .locals init, in questo caso ne abbiamo una sola (Res che è un Int32). Analizziamo la riga di codice che immediatamente segue.
Questa istruzione pusha un Int32 sull'evaluation stack, sarebbe il nostro 65535. A seguire vi sono le istruzioni ldc.i4.3 e ldc.i4.5 che pushano i valori 3 e 5 (se il numero è piccolo (tra 0 e 8) è possibile usare ldc.i4 seguito da un punto e il numero da pushare, ricordate che non è che si può fare per ogni valore ciò). Come potete notare lo stack nell'IL non è come quello a cui siamo abituati, ovvero LIFO (Last In, First Out). Nell'IL i parametri vengono pushati nell'ordine che la funzione prevede.
unsigned int8,
unsigned int8)
Viene chiamata la funzione. L'ildasm ci fornisce anche namespace e classe del metodo chiamato. Vediamo poi l'istruzione:
Questa istruzione poppa (da pop) il valore corrente sulla cima dello stack e lo mette nella lista delle variabili locali all'index 0. In pratica mette il valore di ritorno della funzione che sta in cima allo stack dentro a Res (variabile locale).
Pusha sullo stack la stringa "Result: ". In verità pusha un reference alla stringa che si trova nella metadata dell'eseguibile.
Chiama l'overload per stringhe della funzione Write.
Pusha la variabile locale a index 0 (Res) sullo stack.
Chiama l'overload della funzione WriteLine per interi a 32 bit. Dopodiché chiama la ReadLine.
Rimuove il valore corrente in cima allo stack (sarebbe il valore di ritorno di ReadLine che in ogni caso non mettiamo in nessuna variabile, quindi basta toglierlo di mezzo con pop senza usare stloc). Segue il ret che conclude il metodo. Bene, adesso passiamo alla funzione Calc.
unsigned int8 a,
unsigned int8 b) cil managed
{
// Code size 9 (0x9)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: add
IL_0004: ldc.i4.s 31
IL_0006: and
IL_0007: shr
IL_0008: ret
} // end of method SimpleClass::Calc
Dove:
IL_0001: ldarg.1
IL_0002: ldarg.2
Caricano sullo stack i tre argomenti passati al metodo. Il numero che segue ldarg specifica la posizione dell'argomento. Il primo è OriginalNumber (0) seguono a e b.
Somma i due valori in cima allo stack, in questo caso a e b, e pusha il risulato in cima allo stack. Dopo questa operazione il nostro stack avrà questo aspetto:
OriginalNumber ResultOfAddBetweenA_And_B ; cima dello stack
Poi:
Pusha sullo stack in forma di int32 il valore di 8bit che segue l'istruzione, in questo caso 31d (ildasm ci mostra un numero decimale, attenzione). Quindi il nostro stack adesso avrà questo aspetto:
OriginalNumber ResultOfAddBetweenA_And_B NumberOf31Decimal ; cima dello stack
Quindi:
questo and viene effettuato tra la somma di a-b e il numero 31. In pratica questo and serve ad assicurarsi che il nostro numero non superi 31. Vi chiederete come mai. È presto detto, l'and fa in modo che possiamo usare lo shift anche come rotate. Se per esempio vogliamo shiftare di 32 posizioni, lui fa l'and con 31 e torna 0, quindi shifta di 0, dato che col rotate shiftando di 32 posizioni ci ritroveremmo il numero non modificato in mano. Ad ogni modo, essendo il nostro numero 8, esso resta come è e ci ritroviamo con questo stack:
OriginalNumber NumberOf8 ; cima dello stack
Shifta a destra l'original number di 8 posizioni e pusha il risultato sullo stack (essendo esso il valore di ritorno segue il ret. Insomma come abbiamo già visto nel main il valore di ritorno di un metodo si trova in cima allo stack e non in eax (vabbe' ovvio non abbiamo registri) come siamo abituati.
Siccome so che limitarsi ad analizzare codice non è molto divertente, iniziamo col cracking. Tanto le istruzioni che ancora non avete visto le imparerete via via. Per ora basta che siate entrati nella logica dell'Intermediate Language.
Cracking
Partiamo immediatamente da un esempio facilissimo. Stavolta non vi faccio vedere prima il codice, dato che adesso siamo nel paragrafo di cracking e poi l'esempio è veramente banale. Dato che tutto il codice sta nel main vi incollo solo quello:
{
.entrypoint
// Code size 58 (0x3a)
.maxstack 2
.locals init (string V_0,
int32 V_1)
IL_0000: call string [mscorlib]System.Console::ReadLine()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: call int32 [mscorlib]System.Convert::ToInt32(string)
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: ldc.i4 0x29a
IL_0013: xor
IL_0014: stloc.1
IL_0015: ldloc.1
IL_0016: ldc.i4 0x539
IL_001b: beq.s IL_0029
IL_001d: ldstr "Invalid Serial Number"
IL_0022: call void [mscorlib]System.Console::WriteLine(string)
IL_0027: br.s IL_0033
IL_0029: ldstr "Thank You For Registering"
IL_002e: call void [mscorlib]System.Console::WriteLine(string)
IL_0033: call string [mscorlib]System.Console::ReadLine()
IL_0038: pop
IL_0039: ret
} // end of method SimpleClass::Main
Allora:
Ci chiede di immettere una stringa.
IL_0006: ldloc.0
Prende la stringa (valore di ritorno della funzione ReadLine) e la pusha sullo stack.
Converte la stringa in un intero a 32bit.
IL_000d: ldloc.1
Prende il valore di ritorno, ovvero il numero convertito e lo pusha sullo stack.
pusha sullo stack 29Ah (666d).
Fa lo xor tra 666d e il numero da noi immesso. Ci ritroviamo ovviamente il risultato sullo stack.
IL_0015: ldloc.1
Prende il risultato dallo stack (mettendolo nella stessa variabile del numero immesso) e lo pusha sullo stack.
Pusha sullo stack il valore 539h (1337d).
Eccoci arrivati al salto condizionale. Questa istruzione salta all'istruzione all'offset IL_0029 se i due valori sullo stack sono uguali. Se il salto non viene eseguito (ovvero se i valori non coincidono) ci troviamo di fronte questo codice:
IL_0022: call void [mscorlib]System.Console::WriteLine(string)
IL_0027: br.s IL_0033
ldstr e call mi sembrano chiari. br.s però non l'avete ancora visto, si tratta del salto incondizionale (jmp) che ci porta al termine del programma. Se invece i valori coincidono e il salto viene eseguito, ci troviamo qua:
IL_002e: call void [mscorlib]System.Console::WriteLine(string)
Ovvero il programma risulta registrato. Ora, è chiaro che l'algo è molto banale:
E la risoluzione è evidente, basta inserire come seriale 1955. Proviamo però adesso a crackare il programma, cioè modificare le istruzioni, visto che tecnicamente per averlo crackato è quello che ci manca. Per fare tutto ciò sarebbe però utile che il nostro amatissimo disassembler ci desse anche gli opcode delle varie istruzioni (nonché l'indirizzo da dove inizia il metodo in questione). Niente di più semplice, è sufficiente andare sul menu dell'ildasm sotto la voce view e clickare su "Show Bytes". Riapriamo adesso il codice del main:
// SIG: 00 00 01
{
.entrypoint
// Method begins at RVA 0x2050
// Code size 58 (0x3a)
.maxstack 2
.locals init (string V_0,
int32 V_1)
IL_0000: /* 28 | (0A)000001 */ call string [mscorlib]System.Console::ReadLine()
IL_0005: /* 0A | */ stloc.0
IL_0006: /* 06 | */ ldloc.0
IL_0007: /* 28 | (0A)000002 */ call int32 [mscorlib]System.Convert::ToInt32(string)
IL_000c: /* 0B | */ stloc.1
IL_000d: /* 07 | */ ldloc.1
IL_000e: /* 20 | 9A020000 */ ldc.i4 0x29a
IL_0013: /* 61 | */ xor
IL_0014: /* 0B | */ stloc.1
IL_0015: /* 07 | */ ldloc.1
IL_0016: /* 20 | 39050000 */ ldc.i4 0x539
IL_001b: /* 2E | 0C */ beq.s IL_0029
IL_001d: /* 72 | (70)000001 */ ldstr "Invalid Serial Number"
IL_0022: /* 28 | (0A)000003 */ call void [mscorlib]System.Console::WriteLine(string)
IL_0027: /* 2B | 0A */ br.s IL_0033
IL_0029: /* 72 | (70)00002D */ ldstr "Thank You For Registering"
IL_002e: /* 28 | (0A)000003 */ call void [mscorlib]System.Console::WriteLine(string)
IL_0033: /* 28 | (0A)000001 */ call string [mscorlib]System.Console::ReadLine()
IL_0038: /* 26 | */ pop
IL_0039: /* 2A | */ ret
} // end of method SimpleClass::Main
Come ci dice il disassembler sotto la direttiva .entrypoint il metodo inizia al RVA 2050h, che nel nostro caso corrisponde al file offset 1050h. Il salto condizionale da patchare è:
E lo possiamo invertire, oppure convertire in salto incondizionale. In verità in IL non ha molto senso invertire il salto dato che gli opcode hanno la stessa lunghezza, quindi è meglio renderlo incondizionale. Per fare ciò dobbiamo sostituire l'opcode 2Eh con 2Bh. Però Alt, in IL le cose sono un po' diverse dall'assembly, e dobbiamo fare alcune considerazioni in più. Queste considerazioni riguardano lo stack. Noi siamo abituati al cmp che non fa uso di alcuno stack, ma in IL i valori da confrontare sono sullo stack, quindi se trasformiamo un salto condizionale in uno incondizionale (che ovviamente non prenderà nessun valore dallo stack) dobbiamo assicurarci di non impegnare lo stack. Quindi:
IL_0016: /* 20 | 39050000 */ ldc.i4 0x539
IL_001b: /* 2E | 0C */ beq.s IL_0029
È necessario patchare col nop (00h) i due ld e poi rimpiazzare 2Eh con 2Bh. Prendiamo un hex editor e patchiamo, fatto ciò ci ritroviamo di fronte questo codice:
// SIG: 00 00 01
{
.entrypoint
// Method begins at RVA 0x2050
// Code size 58 (0x3a)
.maxstack 2
.locals init (string V_0,
int32 V_1)
IL_0000: /* 28 | (0A)000001 */ call string [mscorlib]System.Console::ReadLine()
IL_0005: /* 0A | */ stloc.0
IL_0006: /* 06 | */ ldloc.0
IL_0007: /* 28 | (0A)000002 */ call int32 [mscorlib]System.Convert::ToInt32(string)
IL_000c: /* 0B | */ stloc.1
IL_000d: /* 07 | */ ldloc.1
IL_000e: /* 20 | 9A020000 */ ldc.i4 0x29a
IL_0013: /* 61 | */ xor
IL_0014: /* 0B | */ stloc.1
IL_0015: /* 00 | */ nop
IL_0016: /* 00 | */ nop
IL_0017: /* 00 | */ nop
IL_0018: /* 00 | */ nop
IL_0019: /* 00 | */ nop
IL_001a: /* 00 | */ nop
IL_001b: /* 2B | 0C */ br.s IL_0029
IL_001d: /* 72 | (70)000001 */ ldstr "Invalid Serial Number"
IL_0022: /* 28 | (0A)000003 */ call void [mscorlib]System.Console::WriteLine(string)
IL_0027: /* 2B | 0A */ br.s IL_0033
IL_0029: /* 72 | (70)00002D */ ldstr "Thank You For Registering"
IL_002e: /* 28 | (0A)000003 */ call void [mscorlib]System.Console::WriteLine(string)
IL_0033: /* 28 | (0A)000001 */ call string [mscorlib]System.Console::ReadLine()
IL_0038: /* 26 | */ pop
IL_0039: /* 2A | */ ret
} // end of method SimpleClass::Main
Adesso il programma, se eseguito, si registra con qualsiasi valore che immettiamo. Adesso facciamo il reversing di qualcosa di più complesso (a livello di IL s'intende), tanto patchare lo sappiamo già fare. Stavolta si lavora su un programma con finestre. Apriamo l'ildasm e ci troviamo di fronte a questo:

In questo caso, come vedete ho messo solo una window, due label, due text box (user name, serial) e un bottone per registrarsi. Si parte dal codice del bottone che sarebbe btnRegister_Click. Per verificare che sia effettivamente quella la routine che si occupa del click del bottone, è sufficiente guardarsi il codice nella routine InitializeComponent (routine che vi è per ogni window, infatti lì vengono settati gli attributi della window e delle sue child). Quando arrivate a questa routine non spaventatevi troppo, è molto codice (più child ha la window, più codice vi è), ma dovreste trovare velocemente cosa cercate.
IL_01c9: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
IL_01ce: ldarg.0
IL_01cf: ldfld class [System.Windows.Forms]System.Windows.Forms.Button WinApp.WinAppForm::btnRegister
IL_01d4: ldarg.0
IL_01d5: ldftn instance void WinApp.WinAppForm::btnRegister_Click(object,
class [mscorlib]System.EventArgs)
IL_01db: newobj instance void [mscorlib]System.EventHandler::.ctor(object,
native int)
IL_01e0: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::add_Click(class
[mscorlib]System.EventHandler)
Viene creato un EventHandler dalla funzione btnRegister_Click e settato come event handler per quel determinato bottone. In effetti se avete già programmato con .NET siete molto avvantaggiati perché dovreste sapere come vengono inizializzate le finestre. Andiamo al codice del click:
class [mscorlib]System.EventArgs e) cil managed
{
// Code size 216 (0xd8)
.maxstack 3
.locals init (char[] V_0,
char[] V_1,
char V_2,
int32 V_3,
int32 V_4,
int32 V_5)
IL_0000: ldarg.0
IL_0001: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbUserName
IL_0006: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_000b: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_0010: ldc.i4.6
IL_0011: blt.s IL_0027
IL_0013: ldarg.0
IL_0014: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbSerial
IL_0019: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_001e: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_0023: ldc.i4.s 14
IL_0025: beq.s IL_0028
IL_0027: ret
IL_0028: ldarg.0
IL_0029: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbUserName
IL_002e: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_0033: callvirt instance char[] [mscorlib]System.String::ToCharArray()
IL_0038: stloc.0
IL_0039: ldarg.0
IL_003a: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbSerial
IL_003f: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_0044: callvirt instance char[] [mscorlib]System.String::ToCharArray()
IL_0049: stloc.1
.try
{
IL_004a: ldloc.0
IL_004b: ldc.i4.0
IL_004c: ldelem.u2
IL_004d: stloc.2
IL_004e: ldc.i4.3
IL_004f: ldloc.0
IL_0050: ldc.i4.0
IL_0051: ldelem.u2
IL_0052: ldc.i4.2
IL_0053: mul
IL_0054: ldloc.2
IL_0055: sub
IL_0056: ldloc.2
IL_0057: div
IL_0058: ldc.i4.1
IL_0059: sub
IL_005a: div
IL_005b: pop
IL_005c: ldc.i4.0
IL_005d: stloc.3
IL_005e: ldc.i4.0
IL_005f: stloc.s V_4
IL_0061: br.s IL_0070
IL_0063: ldloc.3
IL_0064: ldloc.0
IL_0065: ldloc.s V_4
IL_0067: ldelem.u2
IL_0068: add
IL_0069: stloc.3
IL_006a: ldloc.s V_4
IL_006c: ldc.i4.1
IL_006d: add
IL_006e: stloc.s V_4
IL_0070: ldloc.s V_4
IL_0072: ldarg.0
IL_0073: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbUserName
IL_0078: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_007d: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_0082: blt.s IL_0063
IL_0084: ldc.i4.0
IL_0085: stloc.s V_5
IL_0087: br.s IL_0096
IL_0089: ldloc.3
IL_008a: ldloc.1
IL_008b: ldloc.s V_5
IL_008d: ldelem.u2
IL_008e: add
IL_008f: stloc.3
IL_0090: ldloc.s V_5
IL_0092: ldc.i4.1
IL_0093: add
IL_0094: stloc.s V_5
IL_0096: ldloc.s V_5
IL_0098: ldarg.0
IL_0099: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbSerial
IL_009e: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_00a3: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_00a8: blt.s IL_0089
IL_00aa: ldloc.3
IL_00ab: ldc.i4 0x1337
IL_00b0: bne.un.s IL_00bf
IL_00b2: ldstr "Thank You For Registering"
IL_00b7: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult
[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
IL_00bc: pop
IL_00bd: br.s IL_00ca
IL_00bf: ldstr "Invalid Name/Serial"
IL_00c4: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult
[System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string)
IL_00c9: pop
IL_00ca: leave.s IL_00d7
} // end .try
catch [mscorlib]System.Exception
{
IL_00cc: pop
IL_00cd: ldarg.0
IL_00ce: ldloc.0
IL_00cf: ldloc.1
IL_00d0: call instance void WinApp.WinAppForm::HandleException(char[],
char[])
IL_00d5: leave.s IL_00d7
} // end handler
IL_00d7: ret
} // end of method WinAppForm::btnRegister_Click
Non svenite, procediamo passo passo.
IL_0001: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbUserName
IL_0006: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_000b: callvirt instance int32 [mscorlib]System.String::get_Length()
Prende la lunghezza dello UserName (ricordate che dopo la get_Length il valore di ritorno sta in cima allo stack).
IL_0011: blt.s IL_0027
Confronta la lunghezza con 6. Ovvero salta a IL_0027 se la lunghezza dello user name è minore di 6. Alla locazione IL_0027 troviamo un ret, quindi il nostro user name non deve essere minore di 6 caratteri altrimenti usciamo dalla funzione.
IL_0014: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbSerial
IL_0019: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_001e: callvirt instance int32 [mscorlib]System.String::get_Length()
Prende la lunghezza del seriale.
IL_0025: beq.s IL_0028
IL_0027: ret
Se uguale a 14d, salta a IL_0028. Altrimenti esce dalla funzione.
IL_0029: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbUserName
IL_002e: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_0033: callvirt instance char[] [mscorlib]System.String::ToCharArray()
IL_0038: stloc.0
Converte la stringa dello user name in un array di char e lo mette nella lista delle variabili locali all'index 0. Se guardiamo le variabili locali:
char[] V_1,
char V_2,
int32 V_3,
int32 V_4,
int32 V_5)
Come vedete le prime due sono array di char.
IL_003a: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox WinApp.WinAppForm::tbSerial
IL_003f: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_0044: callvirt instance char[] [mscorlib]System.String::ToCharArray()
IL_0049: stloc.1
Prende la stringa del seriale, la converte in array di char e la mette nella seconda variabile locale. Quindi riassumendo char[] V_0 contiene lo user name, mentre char[] V_1 il seriale.
{
Si apre un blocco try, ovvero vi è un exception handler. Ovviamente a qualsiasi reverser già qua sorgerebbe un dubbio.
IL_004b: ldc.i4.0
IL_004c: ldelem.u2
Allora, pusha sullo stack l'array di char dello user name, pusha l'index 0, pusha sullo stack l'elemento dell'array al dato index. In pratica, parlando in pseudo-codice. sarebbe un push UserNameArray[Index]. Adesso in cima allo stack abbiamo il primo carattere di questo array.
Ficca il carattere nella terza variabile locale (un char appunto).
Pusha il valore 3.
IL_0050: ldc.i4.0
IL_0051: ldelem.u2
Pusha sullo stack il primo carattere dello user name.
IL_0053: mul
Lo moltiplica per due.
IL_0055: sub
Gli toglie metà (ricordate che la seconda variabile locale contiene il valore originale del carattere).
IL_0057: div
Lo divide per se stesso. Quindi il risultato di div adesso sarà 1, valore che quindi avremo sullo stack.
IL_0059: sub
Sottrae 1 a 1.
Compie la divisione con il risultato della sottrazione sul valore 3 precdentemente pushato. Quindi alla fine dei conti avremo un 3 / 0. Che ovviamente porta a un'eccezione. Andiamo a