ASM en Delphi
ASM en Delphi
ASM en Delphi
Lisa&&Alquimista
( Introduccin copiada de un articulo de Nestor Freire )
La necesidad de cualquier lenguaje de alto nivel, como lo es Delphi, de conseguir rutinas a bajo nivel, es muy importante. Lamentablemente no es suficiente el API. Pues no, no es suficiente el API y me estoy refiriendo a Windows. Hay actuaciones concretas que no son controladas como nosotros quisiramos, y Delphi, deja en manos de las API una cantidad ingente de trabajo, y hacer esto con la interface de un S.O. con numerosas lagunas. Pero hay que ser educado, y antes que ninguna otra cosa dar las gracias nuestro buen amigo, Francisco Charte, cuyo libro publicado hace muy poco, y de ttulo Ensamblador, es una muestra de lo que cualquier programador deberamos conocer y un acto de valenta por su parte, habida cuenta de que la inmensa mayora preferimos que nos lo den todo hecho y no complicarnos. Pero no vamos a escribir un artculo sobre ensamblador, sino el tratamiento de ste con Delphi.
Ensamblador s o no?
A la vez, y lamentablemente, la utilizacin de Ensamblador (o Assembler, para quien est ms acostumbrado a esa terminologa) es un tema vedado. Desconozco lo que puede pensar el lector, y hago constar lo que pienso yo, y es que nos alejan cada vez ms de aquello que est cercano a la mquina y por lo tanto susceptible de entradas no deseadas. Ocurre con este lenguaje, el ms bajo que existe y por lo tanto el ms poderoso, segn se enfoque, y lo vemos en temas diarios como puedan ser los punteros disfrazados en Delphi. Los fabricantes me recuerdan a la letra de un cantoautor espaol, J.M. Serrat: nio, eso no se hace, eso no se dice, eso no se toca. Delphi, al menos hasta la versin 6, ha incluido un ensamblador que se le conoce por BASM (Borland ASseMbler), pero segn vamos avanzando en la versin del Sistema, menos posibilidades tenemos de utilizacin. Teixeira y Pacheco incorporan en su libro sobre Delphi 5, indicaciones con referencia al uso de BASM como: No optimice sus programas con cdigo ensamblador. El cdigo ensamblador optimizado manualmente puede ejecutarse ms rpidamente que el cdigo Object Pascal, pero pagando el precio de la legibilidad y la mantenibilidad del mismo. Igualmente indican Comente siempre su cdigo ensamblador detalladamente [. .] La falta de comentarios puede ser que sea difcil de entender. Pienso que como programadores debemos de comentar todo el cdigo, con independencia del lenguaje. Ensamblador no tiene por qu ser un caso especial. Hasta aqu el articulo ahora explicaremos un poco como utilizar ASM en Delphi, que es lo que realmente importa ,pues para eso escribimos este ensayo. Bueno lo primero de todo vamos a ver el deburgger que incorpora Delphi, para dichos casos, que como veremos es muy parecido a Olly, as pues, no hay ningn problema para manejarlo.
Debugger de Delphi
Para nosotros esta parte ser muy fcil pues estamos acostumbrados a Olly y son parecidos.
Con dicha Herramienta podemos ver el Deburgger de Delphi y lo que es nuestro programa segn lo estamos generando. Tan slo hay que activar un punto de ruptura y pasar a ejecucin. Cuando se detenga se selecciona View Debug Window CPU y nos encontraremos con algo del tipo que aparece en la Figura 1. Figura 1.
Donde el cuadro superior izquierdo muestra en negrita el cdigo fuente y a continuacin las instrucciones en ensamblador que se corresponden con dicho cdigo. Por supuesto, y casi siempre, el cdigo en Delphi tiene muchas menos lneas que su traduccin a ensamblador. ( se parece a Olly as pues no problema ).
Cdigo ASM generado por BASM ( El cdigo original tambin esta en ASM)
En la imagen superior podemos ver que si el cdigo escrito en Delphi esta en ASM el cdigo generado es igual al cdigo escrito( no hace falta traducirlo). Vemos tambin que en negrilla esta la instruccin en Delphi, que se esta ejecutando, ( podemos verlo tambin seleccionando ver cdigo (Crtl+V) ,veremos su comentario si lo hemos puesto) debajo de la instruccin en Delphi esta la instruccin que ejecutara el compilador en ASM con su direccin de memoria. ( podramos trazar el programa desde Olly en dicha direccin y solo veramos las instrucciones que no estn en negrilla). Por ejemplo si el cdigo esta en Delphi como se muestra abajo Var i :Integer; begin for i := 1 to 100 do begin // Poner los 100 numeros en un ListBox ListBox1.Items.Add (IntToStr(i)); end; end; Cdigo ASM generado por BASM ( El cdigo original esta en Delphi)
Vemos las instrucciones en Delphi en negrilla y el cdigo ASM generado por el compilador, como vemos aqu son diferentes ya que el compilador tiene que traducir las instrucciones de Delphi a ASM.
El cuadro inferior izquierdo es un mapa de memoria, al cual podemos visualizar la direccin deseada. (Pero hay que acordarse de que las direcciones por defecto son en Decimal, as pues hay que poner el smbolo del dlar para que sean Hexa $00401000). Desde el tambin podemos buscar Bit o cadenas ASCII.
Tambin podemos ver los registros que por supuesto podemos modificar a voluntad.
Tambin tenemos los Flag los cuales como no podemos modificar, para por ejemplo forzar un salto.
En las imgenes superiores en la primera (la de los Flag) se ve que hemos cambiado
el Flag ZF ( Esta en rojo), y en la segunda la flecha izquierda indica la instruccin actual, y la derecha ( como hemos cambiado el Flag), El salto se va ha ejecutar y ser hacia abajo. Si este no se ejecutara la flecha derecha no estara. Y por ltimo, el inferior derecho nos muestra el contenido de la pila del programa: direccin de memoria, valor que contiene y representacin ASCII. Como curiosidad, se vern ordenados en orden decreciente, debido a la propiedad LIFO de la pila. Podemos ver todas las variables de nuestro programa que queramos, de donde venimos o los hilos en proceso, para ello solo hace falta activar View Debug Window y la opcin deseada. Por cierto, que podemos utilizar el sistema Delphi de las teclas F7 y F8, y sera lo deseable para ir lnea a lnea y estudiar los resultados. Bueno esto es lo bsico sobre el debugger que Delphi trae incorporado, como comentamos anteriormente es parecido a Olly as pues ningn problema con , seguro que trasteando un poco con l se sacan ms funciones que las comentadas aqu, pero el cometido de este ensayo es aprender a programar ASM dentro del entorno de Delphi no Analizar exhaustivamente el debugger que trae incorporado.
Y elegimos los colores que queramos para ASM. En este ensayo el color ser Azul todo lo que este en Azul ser ASM.
Bueno pues sigamos Pues lo nico que tenemos que hacer es meter las intrusiones ASM entre las intrusiones ASM y END. Un ejemplo: . . . asm // aqu empieza XOR EAX,EAX // XOR EBX,EBX // XOR ECX,ECX // XOR EDX,EDX // End; // Fin de ASM . Se ve claramente que el cdigo anterior es ASM Es muy recomendable utilizar sangra de texto ( para su mejor visualizacin) y poner el comentario en el END de final de asm ( no solo en este sino en todos los END ) pues sino tendremos en montn de end y no sabremos donde. Los bucles como ya sabemos en ASM hay que hacer uso de los diferentes saltos.
JMP JA (JNBE) JAE (JNBE) JB (JNAE) JBE (JNA) JE (JZ) JNE (JNZ) JG (JNLE) JGE (JNL) JL (JNGE) JLE (JNG) JC JNC JNO JNP (JPO) JNS JO JP (JPE) JS
NO es propsito de este ensayo explicar todos los saltos, as pues seguimos, Lo primero de todo ser saber como se ponen las etiquetas en ASM de Delphi, es muy fcil.
Lo primero ser saber como definir y utilizar una Etiqueta (Una etiqueta es una direccin de memoria con nombre). Etiqueta: @@Nombre de la etiqueta: As de fcil dos arrobas+Nombre+ dos puntos Veamos la funcin de comparar cadenas de Delphi en ASM:
function CompareStr(const S1, S2: string): Integer; assembler; asm
// Comienza el ASM
PUSH ESI PUSH EDI MOV ESI,EAX MOV EDI,EDX OR JE EAX,EAX @@ Etiqueta_1 EDX,EDX @@ Bucle_2 // Aqu no se ponen los dos puntos
Bueno el ejemplo anterior a quedado claro como definir y utilizar etiquetas, para conseguir hacer bucles. Con nico que hay que tener cuidado son con los dos puntos finales de la etiqueta se ponen solo en la definicin de la etiqueta, pero no son puestos en los saltos que utilizan dicha etiqueta. Definicin de la Etiqueta : Utilizacin de la Etiqueta
@@Bucle_2: JE @@ Bucle_2 // Aqu no se ponen los dos puntos
Begin
Num_1:= 401000; Num_2:=$401000; Nombre:=CracksLatinos; Asm // empieza el ASM Mov Eax, Num_2; // Mov Ebx, Num_1; // Mov Num_3, Edx; // Mov Ecx,Nombre; // //Nombre End; // Fin de asm Pon Pon Pon Pon $401000 en EAX 401000 en EBX en Numero 3 el valor de EDX en Ecx la dir de memoria de
Vemos como los Registros tienen los valores que acabamos de meter: Eax 401000 (hex) Ebx 61E68 (Hex) 401000 (dec) Ecx 0044FC0C // Direccin de la variable Nombre que contiene Cracklatinos, como podemos ver en el cuadro de memoria mostrado abajo.
Pues con esto queda aclarado como introducir datos a los registros:
Veamos un ejemplo de lo dicho anterirmente : var serial: Integer; Contador: Integer; begin Contador := 10; Asm // Rutina para sumar XOR EAX,EAX // Limpiamos todos los registros XOR EBX,EBX // Limpiamos todos los registros XOR EDX,EDX // Limpiamos todos los registros MOV EBX, Contador // Metemos el contador 10 vueltas @@LOOP: TEST BL,BL // Comparamos si es el final JE @@Fin // Si lo es se acabo ADD EDX,EBX // Mete la suma en EDX DEC EBX; // retrocede el contador JMP @@LOOP @@Fin: MOV Serial,EDX // Guardamos el resultado en serial end; // asm // se acabo el ASM Edit1.Text:=IntToStr(Serial); // Aqu genera el error end; El trozo de cdigo nos genera el error (imagen inferior) en la instruccin marcada en rojo, podramos repetir este ejemplo sin el bucle y todo funciona perfectamente, el bucle esta bien, entonces Que pasa ?.
Bueno el proceso fue el siguiente, Utilizamos Olly para lo que realmente fue diseado, para reparar errores y descubrimos que el error esta en la pila esta queda totalmente descuadradra cuando abandonamos el Bucle de ASM y intentamos copiar el resultado en un control de Delphi. Una vez conocido el problema la solucin es de lo ms sencilla, la primera instruccin de ASM ser siempre PUSHAD y la ltima POPAD con esto conseguimos que la pila quede en su sitio. As pues todas las rutinas de ASM escritas desde Delphi sern:
ASM PUSHAD . . . POPAD END; // Fin de asm Bueno aqu acaba la Nota aclaratoria. Una vez entendida dicha Nota, proporcionaremos una manera segura de comunicar nuestro programa Delphi con rutinas ASM.
Veamos esto con ejemplo muy simple una funcin para restar dos nmeros. Nota : En este ejemplo las normas no serian necesarias, pero no pasa nada si las incluimos, si los clculos son mas complejos son normas necesarias. Function resta ( A:Integer; B:Integer):Integer; Var // Declararemos siempre una variable como Integer // donde almacenar el resultado del algoritmo // En este caso resultado Resultado :integer; Begin Asm // Inicio de ASM // Pondremos siempre al principio la instruccin Pushad pushad; mov EAX,a; mov EBX,b; Sub Eax,Ebx; // Moveremos a la variable el resultado del algoritmo Mov Resultado,Eax; // Pondremos siempre al Final la instruccin Popad Popad; end; // asm; // Ya fuera de ASM asignaremos la variable que tiene el // resultado a Result ( es obligatorio) Result:=Resultado; end; // funcion Resta Esta seria la rutina en ASM y La llamada desde un procedimiento sera: procedure TForm1.Button1Click(Sender: TObject); var a :Integer; b :Integer; c :Integer; begin a:=10; b:=8; c:=Resta(a,b); Edit1.Text:=(InttoStr(c)); end;
En el Edit veramos un 2. Como no poda ser de otra forma. Bueno con esto damos pro terminado el paso de parmetros de Delphi a ASM Ahora veremos el caso opuesto es decir de ASM Delphi
function LaFuncion(A, B: Integer): Integer; stdcall; Pero si cambisemos la directiva por stdcall, como hemos hecho ejemplo de arriba, nos dara lo mismo, el compilador de Delphi es as de listo. Todo esto en Delphi, Pero si nuestra rutina fuera en ASM, habra diferencia y mucha diferencia ya que si escribimos una rutina en ASM ( Por ejemplo rutina A ) y esta tiene que llamar a otra rutina ( por ejemplo rutina B ) la rutina B tiene que incluir necesariamente la directiva. Y esto de las directivas en ASM para que sirve ? Mejor vemoslo con un ejemplo cogeremos la funcin resta anterior o escribimos otra en Delphi que es ms fcil. function Resta_Stdcall (a,b:integer):integer; Stdcall; begin Result:=a-b; end; function Resta_Pascal(a,b:Integer):integer;Pascal; begin Result:=a-b; end; Como vemos hemos escrito dos funciones Resta idnticas lo nico que hemos cambiado es la directiva, ahora realicemos una prueba, con un programa en ASM que llamara a ambas funciones y podremos ver cual es la diferencia de este lo de directivas. Crearemos un form con un Botn calcular y dos Edit el primero visualizara el resultado de la funcin Resta_StdCall y el segundo Edit visualizara el resultado de la funcin Resta_Pascal.
Aqu el cdigo del botn generar procedure TForm1.Button1Click(Sender: TObject); Var a :Integer; b :Integer; c :Integer; begin a:=9; b:=5; asm push a; // primer parmetro de la funcin Resta push b; // Segundo parmetro de la funcin Resta call Resta_StdCall; // Resta mov c,eax // Guarda el resultado en C end; // asm Edit1.Text:=IntTostr(c); // Visualiza C a:=9; b:=5; asm push a; // primer parmetro de la funcin Resta push b; // Segundo parmetro de la funcin Resta call Resta_Pascal; // Resta mov c,eax // Guarda el resultado en C end; // asm Edit2.Text:=IntTostr(c); // Visualiza C end;
La Imagen no deja lugar a dudas cuando necesitemos desde una rutina ASM llamar a otra rutina esta para que funcione igual que en Delphi necesita llevar siempre la directiva de Pascal. **** MUY IMPORTANTE ****
Si una rutina tiene que ser llamada desde un cdigo ASM dicha rutina tiene que incluir siempre la directiva, pues sino aunque al compilar no nos de errores, no funciona de forma adecuada, se puede probar con el ejemplo anterior quitando la directiva a una de las funciones. Bueno pues con esto ya somos capaces de conexionar sin Meigas Delphi y ASM.
Bueno pues empecemos con el primer Crackme para construir el KeyGen (pensamos que seria muy instructivo hacer el KeyGen, de un Crackme que el KeyGen estuviera hecho en Delphi as se podran compara ambos KeyGen. Entonces asunto resulto realizaremos el KeyGen del mismo Crame que utilizamos para el primer ensayo de KeyGen en Delphi. Que es el concurso 17 nivel 02: Dicho KeyGen fue resulto por HouDini en ASM, despus lo resolvimos en Delphi y ahora lo realizaremos en Delphi + ASM ( el cdigo se parecer mucho ms al de HouDini ). Bueno pues nos leemos los dos ensayos el de HouDini en ASM y el de Delphi ya sabemos que hace el Crackme, Utilizaremos el mismo diseo que el utilizado en Delphi solo cambiaremos el cdigo asociado al botn Generar. as pues ya solo nos queda copiar el cdigo con el Plugin.
Programando el KeyGen
// Botn Salir
Lo primero que vamos a realizar es el evento del botn salir, para ellos pulsamos dos veces en dicho botn y nos sale la pantalla de cdigo en la cual entre : Begin y End escribiremos nuestro cdigo. begin Close; end; Como vemos solo hemos escrito Close; (Salir) ahora si compilamos el programa y le ejecutamos (F9) el programa ya sabe lo que hacer con el botn salir, si lo ejecutamos vemos que efectivamente se sale del programa.
// Botn Generar
Procedemos igual que antes pero ahora encima de begin vamos a definir las variables que vamos a utilizar, escribimos Var (variables ) y ahora las variables : Primero definiremos las de tipo numrico y las definiremos de tipo Integer (entero), estas son: LongNombre -> Una variable donde almacenar la longitud del nombre Resultado -> Donde se almacena la solucin del algoritmo. Despus definiremos las de tipo String que son: Nombre -> Donde se almacena el nombre introducido.
ADD EAX,EBX MOV Resultado,EAX // Guardamos el numero en Resultado Popad // Restauramos todos los registros end; // se acabo el asm edit2.Text:= IntToHex (Resultado,0); // Pon el edit2 el contenido de solucin end; // if end; // procedure
Como vemos en la imagen hemos copiado todo con el Plugin y solo hemos adaptado el cdigo en lo siguiente. MOV EAX, Nombre; //toma nuestro nombre de usuario y lo mueve a EAX //coge caracter por carcter y lo mueve a EAX MOVSX EAX,BYTE PTR DS:[EAX+ECX]; Metemos la direccin del Nombre en un registro ( el mismo que utiliza el Crame) MOV EAX, Nombre; //toma nuestro nombre de usuario y lo mueve a EAX luego miramos cual es el registro que utiliza de contador para ir pasando letra a letra ese es ECX miramos la Instruccin /*40110C*/ MOVSX EAX,BYTE PTR SS:[EBP+ECX-B8] Vemos cual es el registro pues como nosotros la direccin del nombre la tenemos en EAX. //coge caracter por carcter y lo mueve a EAX MOVSX EAX,BYTE PTR DS:[EAX+ECX]; Despus en: /*401119*/ CMP ECX,DWORD PTR SS:[EBP-28] Compara el Contador con el final del nombre CMP ECX,LongNombre
Y por ltimo ver donde se almacena el serial y meterlo en nuestra variable /*401126*/ MOV DWORD PTR SS:[EBP-38],EAX
MOV Resultado,EAX // Guardamos el numero en Resultado Todo esto unido a las normas comentadas en este ensayo dan como resultado el KeyGen en perfecto funcionamiento. Nota : Para quien le gusten las pruebas y no se crea nada de lo que este ensayo trata, que elimine del cdigo de ASM las instrucciones Pushad y Popad, vera que el programa no funciona. Con esto esta acabado el Keygen esperemos que HouDiNi no se enfade mucho por imitarle en el KeyGen y van dos veces.
Una cosa curiosa de este Crackme es que genera un serial distinto si esta siendo debuggeado que si no lo esta, de hay la casilla de verificacin que por defecto esta desactivada.
Bueno pues aprovecharemos esa circunstancia ( la que el Crackme detecta el deburgger, para realizar dos rutinas en ASM, estas sern llamadas desde Delphi y este recoger el resultado del algoritmo de las rutinas, para Mostrarlo en el Edit correspondiente. function SinDeburgger (Nombre :String ):Integer; function ConDeburgger (Nombre :String ):Integer; Como vemos las funciones se llaman: SinDeburgger la cual ser ejecutada cuando la casilla correspondiente no este marcada. ConDeburgger la cual ser ejecutada cuando la casilla correspondiente este marcada. Dichas funciones en ASM aceptaran como parmetro una String ( el Nombre cogido de un Edit), devolvern un Integer (resultado del algoritmo). Bueno pues aqu pondr las dos funciones si se comparan con el crame literalmente esta copiado con el Plugin con las mismas modificaciones que en el ejemplo anterior. As pues Y para no alargar demasiado este ensayo que ya es muy largo y dado que las modificaciones que hay que hacer en las rutina copiada con el Plugin son las mismas que en el ejemplo anterior. ( las modificaciones siempre sern muy parecidas a las realizadas anteriormente) No se explicaran exhaustivamente. Bueno empecemos con el crame Bueno primero una Imagen del Crackme donde tiene la rutina que genera si el programa esta siendo No Esta siendo deburggeado.
Como veremos solo la tenemos que copiar con el Plugin y hacer las modificaciones de siempre. function SinDeburgger (Nombre :String ):Integer; var Serial :Integer; begin asm pushad // Guardamos todos los registro XOR EAX,EAX // Limpiamos todos los registros XOR EBX,EBX // Limpiamos todos los registros XOR ECX,ECX // Limpiamos todos los registros XOR EDX,EDX // Limpiamos todos los registros @@Segir: MOV EBX, Nombre //toma nuestro nombre y lo mueve a EBX //coge el carcter y lo mueve a EBX MOVZX EBX, BYTE PTR DS:[EBX+ECX] TEST BL,BL // Comparamos si es el final JE @@Fin // Si lo es se acabo ADD EDX,EBX OR EAX,EDX INC EAX ROL EDX,CL NOT EAX INC ECX ADD EAX,EDX JMP @@Segir @@Fin: MOV Serial,EDX // Guardamos el numero en serial popad // Restauramos todos los registros end; // asm // se acabo el asm Result := Serial; end; // Funcin sin deburger
Bueno primero una Imagen del Crackme donde tiene la rutina que genera si el programa esta siendo deburggeado. Como veremos solo la tenemos que copiar con el Plugin y hacer las modificaciones de siempre. function ConDeburgger (Nombre :String ):Integer; var Serial :Integer; begin asm pushad // Guardamos todos los registro XOR EAX,EAX // Limpiamos todos los registros XOR EBX,EBX // Limpiamos todos los registros XOR ECX,ECX // Limpiamos todos los registros MOV EDI,Nombre //toma nuestro nombre y lo mueve a EDI @@Segir: MOV BL,BYTE PTR DS:[ECX+EDI] // Introduce un carcter TEST BL,BL JE @@Fin ADD EAX,EBX ADD EAX,$10 INC ECX JMP @@Segir @@Fin: MOV Serial,EAX // Guardamos el numero en serial popad // Restauramos todos los registros end; // asm // se acabo el asm Result := Serial; end; // Funcin Con deburgger Una vez que tenemos las dos funciones lo dems es muy fcil ya solo es Delphi.
El cdigo del Botn Generar Es este procedure TPrincipal.BitBtn1Click(Sender: TObject); Var Result :Integer; Tamanno:Integer; Serial :String; Name :String; begin Name:= E_Nombre.text; // Copiamos en la variable el nombre introducido en edit1 Tamanno:=length(Name); // Calculamos su longitud if Tamanno = 0 then // Verifica si hemos introducido algo en el edit1 begin E_Serial.Clear; Application.MessageBox('Pon Algo','Upss..!',mb_ok+Mb_IconInformation); // Si no hay nada nos muestra este mensaje E_Nombre.SetFocus; end else // Si hay algo escrito continuamos begin if Check_debug.Checked = True then // Miramos el estado de la casilla de verificacion begin // Si esta activada Result:=ConDeburgger (Name); // Calcula el serial con debugger end else begin Result:=SinDeburgger (Name); // Calcula el serial sin debugger end; // if debug Serial :=inttoHex(Result,0); // Lo formateamos a Hexadecimal E_serial.text:= (Serial); // Muestra el Resultado final en el edit2.text end; // if end;// Procedimiento Generar Bueno pues con esto damos por terminado el ensayo de Programacin ASM en Delphi. Nota: Por supuesto, si alguien quiere las fuentes de dichos ensayos pues solo tiene que pedirlas.
Solo una cosa ms mandar un montn de suerte y animo al verdadero motor de Crackslatinos al maestro RICARDO NAVAJA.