C贸mo escribir c贸digo de ensamblador con instrucciones superpuestas (otra t茅cnica para ofuscar el c贸digo de bytes)

Presentamos a su atenci贸n la t茅cnica de crear programas ensambladores con instrucciones superpuestas, para proteger el bytecode compilado del desmontaje. Esta t茅cnica es capaz de soportar an谩lisis de bytecode est谩ticos y din谩micos. La idea es seleccionar una secuencia de bytes que, cuando se desmonta de dos desplazamientos diferentes, da como resultado dos cadenas de instrucciones diferentes, es decir, dos formas diferentes de ejecutar el programa. Para hacer esto, tomamos instrucciones de ensamblador multibyte y ocultamos el c贸digo protegido en las partes variables del bytecode de estas instrucciones. Para enga帽ar al desensamblador coloc谩ndolo en un camino falso (de acuerdo con una cadena de instrucciones de enmascaramiento) y para proteger de sus ojos una cadena oculta de instrucciones.



Tres requisitos previos para crear una "superposici贸n" efectiva


Para enga帽ar al desensamblador, el c贸digo superpuesto debe cumplir las siguientes tres condiciones: 1) Las instrucciones de la cadena de enmascaramiento y la cadena oculta siempre deben cruzarse entre s铆, es decir. no deben alinearse entre s铆 (sus primeros y 煤ltimos bytes no deben coincidir). De lo contrario, parte del c贸digo oculto ser谩 visible en la cadena de enmascaramiento. 2) Ambas cadenas deben consistir en instrucciones de montaje plausibles. De lo contrario, el enmascaramiento ya se detectar谩 en la etapa de an谩lisis est谩tico (habiendo tropezado con un c贸digo inadecuado para la ejecuci贸n, el desensamblador corregir谩 el puntero del comando y expondr谩 el enmascaramiento). 3) Todas las instrucciones de ambas cadenas deben ser no solo plausibles, sino tambi茅n ejecutadas correctamente (para evitar que esto suceda, el programa se bloque贸 cuando intenta ejecutarlas). De lo contrario, durante el an谩lisis din谩mico, las fallas atraer谩n la atenci贸n del reverso y se revelar谩 la m谩scara.


Descripci贸n de la t茅cnica de instrucciones de ensamblador "superpuestas"


Para que el proceso de creaci贸n de c贸digo superpuesto sea lo m谩s flexible posible, es necesario seleccionar solo las instrucciones multibyte, para las cuales la mayor cantidad posible de bytes puede tomar cualquier valor. Estas instrucciones multibyte constituir谩n una cadena de instrucciones de enmascaramiento.


En la b煤squeda del objetivo de crear c贸digo superpuesto que satisfaga las tres condiciones anteriores, consideramos cada instrucci贸n de enmascaramiento como una secuencia de bytes de la forma: XX YY ZZ.


Aqu铆 XX es el prefijo de instrucci贸n (c贸digo de instrucci贸n y otros bytes est谩ticos, que no se pueden cambiar).


YY son bytes que pueden cambiarse arbitrariamente (como regla, estos bytes almacenan el valor num茅rico directo pasado a la instrucci贸n; o la direcci贸n del operando almacenado en la memoria). Debe haber tantos bytes YY como sea posible para que entren m谩s instrucciones ocultas en ellos.


ZZ: estos tambi茅n son bytes que se pueden cambiar arbitrariamente, con la 煤nica diferencia de que la combinaci贸n de bytes ZZ con los bytes XX posteriores (ZZ XX) debe formar una instrucci贸n v谩lida que satisfaga las tres condiciones formuladas al comienzo del art铆culo. Idealmente, ZZ deber铆a ocupar solo un byte, de modo que en YY (esta es esencialmente la parte m谩s importante, nuestro c贸digo oculto se coloca aqu铆) deber铆a haber tantos bytes como sea posible. La 煤ltima instrucci贸n oculta debe terminar en ZZ, creando un punto de convergencia para las dos cadenas de ejecuci贸n.


Instrucciones de encolado


La combinaci贸n ZZ XX: llamaremos a las instrucciones de pegado. Se necesita una instrucci贸n de pegado, en primer lugar, para unir las instrucciones ocultas que se encuentran en las instrucciones de enmascaramiento adyacentes y, en segundo lugar, para cumplir la primera condici贸n necesaria indicada al principio del art铆culo: las instrucciones de ambas cadenas siempre deben cruzarse entre s铆 (por lo tanto, la instrucci贸n de pegado siempre ubicado en la intersecci贸n de dos instrucciones de enmascaramiento).


La instrucci贸n de pegado se ejecuta en una cadena oculta de comandos y, por lo tanto, debe seleccionarse de manera que imponga la menor cantidad posible de restricciones sobre el c贸digo oculto. Suponga que cuando se ejecuta, se cambian los registros de prop贸sito general y el registro EFLAGS, entonces el c贸digo oculto no podr谩 usar efectivamente los registros correspondientes y los comandos condicionales (por ejemplo, si la instrucci贸n de pegado est谩 precedida por el operador de comparaci贸n, y la instrucci贸n de pegado misma cambia el valor del registro EFLAGS, luego la transici贸n condicional, que se encuentra despu茅s de las instrucciones de pegado no funcionar谩 correctamente).


La descripci贸n anterior de la t茅cnica de superposici贸n se ilustra en la siguiente figura. Si la ejecuci贸n comienza con los bytes de inicio (XX), se activa una cadena de instrucciones de enmascaramiento. Y si desde bytes YY, se activa una cadena de instrucciones ocultas.



Instrucciones de ensamblador adecuadas para el papel de "instrucciones de enmascaramiento"


La m谩s larga de las instrucciones, que a primera vista nos conviene m谩s, es una versi贸n de 10 bytes de MOV, donde el desplazamiento especificado por el registro y la direcci贸n de 32 bits se transfiere como el primer operando, y el n煤mero de 32 bits como el segundo operando. Esta instrucci贸n contiene la mayor铆a de los bytes que se pueden cambiar arbitrariamente (hasta 8 piezas).



Sin embargo, aunque esta instrucci贸n parece plausible (en teor铆a, se puede ejecutar correctamente), todav铆a no nos conviene, porque su primer operando, como regla, indicar谩 una direcci贸n inaccesible y, por lo tanto, al intentar ejecutar dicho MOV, el programa colapsar谩 T.O. este MOV de 10 bytes no cumple la tercera condici贸n necesaria: todas las instrucciones de ambas cadenas deben ejecutarse correctamente.


Por lo tanto, elegiremos para el papel de enmascarar instrucciones solo aquellos solicitantes que no presenten un riesgo de colapso del programa. Esta condici贸n reduce significativamente el rango de instrucciones adecuadas para crear c贸digo superpuesto, pero todav铆a hay otras adecuadas. A continuaci贸n hay cuatro de ellos. Cada una de estas cuatro instrucciones contiene cinco bytes, que pueden cambiarse arbitrariamente, sin el riesgo de un bloqueo del programa.


  • LEA Esta instrucci贸n calcula la direcci贸n de memoria especificada por la expresi贸n en el segundo operando y almacena el resultado en el primer operando. Como podemos referirnos a la memoria sin acceso real a ella (y, en consecuencia, sin el riesgo de un bloqueo del programa), los 煤ltimos cinco bytes de esta instrucci贸n pueden tomar valores arbitrarios.


  • CMOVcc. Esta instrucci贸n realiza la operaci贸n MOV si se cumple la condici贸n "cc". Para que esta instrucci贸n satisfaga el tercer requisito, la condici贸n debe seleccionarse de modo que, bajo ninguna circunstancia, tenga el valor FALSO. De lo contrario, esta instrucci贸n puede intentar acceder a una direcci贸n de memoria inaccesible, etc. derribar el programa.


  • SETcc Funciona seg煤n el mismo principio que CMOVcc: establece el byte en uno si se cumple la condici贸n "cc". Esta instrucci贸n tiene el mismo problema que CMOVcc: acceder a una direcci贸n no v谩lida har谩 que el programa se bloquee. Por lo tanto, la elecci贸n de la condici贸n "cc" debe abordarse con mucho cuidado.


  • NOP. Los NOP pueden tener diferentes longitudes (de 2 a 15 bytes), seg煤n los operandos que se indiquen en ellos. En este caso, no habr谩 riesgo de bloquear el programa (debido al acceso a una direcci贸n de memoria no v谩lida). Debido a que lo 煤nico que hacen los NOP es aumentar el contador de instrucciones (no realizan ninguna operaci贸n en los operandos). Por lo tanto, los bytes NOP en los que se especifican los operandos pueden tomar un valor arbitrario. Para nuestros prop贸sitos, un NOP de 9 bytes es el m谩s adecuado.


Como referencia, aqu铆 hay algunas otras opciones de NOP.



Instrucciones del ensamblador adecuadas para el papel de "instrucciones de pegado"


La lista de instrucciones adecuadas para el papel de una instrucci贸n de pegado es 煤nica para cada instrucci贸n de enmascaramiento espec铆fica. A continuaci贸n se muestra una lista (generada por el algoritmo que se muestra en la siguiente figura) utilizando NOP de 9 bytes como ejemplo.



Al formar esta lista, tomamos en cuenta solo aquellas opciones en las que ZZ toma 1 byte (de lo contrario, quedar谩 poco espacio para el c贸digo oculto). Aqu铆 hay una lista de instrucciones adhesivas adecuadas para un NOP de 9 bytes.



Entre esta lista de instrucciones, no hay una que est茅 libre de efectos secundarios. Cada uno de ellos cambia EFLAGS, o registros de prop贸sito general, o ambos a la vez. Esta lista se divide en 4 categor铆as, seg煤n el efecto secundario que tenga la instrucci贸n.


La primera categor铆a incluye instrucciones que cambian el registro EFLAGS, pero no cambian los registros de prop贸sito general. Las instrucciones de esta categor铆a se pueden usar cuando no hay saltos condicionales o cualquier instrucci贸n en la cadena de instrucciones ocultas basadas en la evaluaci贸n de la informaci贸n del registro EFLAGS. En este caso, en este caso (para un NOP de 9 bytes) solo hay dos instrucciones: TEST y CMP.



El siguiente es un ejemplo simple de c贸digo oculto que usa TEST como una instrucci贸n de pegado. Este ejemplo realiza una llamada al sistema de salida, que devuelve un valor de 1 para cualquier versi贸n de Linux. Para formar correctamente la instrucci贸n TEST para nuestras necesidades, necesitaremos establecer el 煤ltimo byte del primer NOP en 0xA9. Este byte, cuando se combina con los primeros cuatro bytes del pr贸ximo NOP (66 0F 1F 84), se convertir谩 en una instrucci贸n TEST EAX, 0x841F0F66. Las siguientes dos figuras muestran el c贸digo de ensamblador correspondiente (para la cadena de enmascaramiento y la cadena oculta). La cadena oculta se activa cuando el control se transfiere al cuarto byte del primer NOP.




La segunda categor铆a incluye instrucciones que cambian los valores de los registros generales o la memoria disponible (pila, por ejemplo), pero no cambian el registro EFLAGS. Al ejecutar una instrucci贸n PUSH o cualquier variante MOV, donde se especifica un valor inmediato como el segundo operando, el registro EFLAGS permanece sin cambios. T.O. las instrucciones de pegado de la segunda categor铆a pueden incluso ubicarse entre la instrucci贸n de comparaci贸n (TEST, por ejemplo) y la instrucci贸n que eval煤a el registro EFLAGS. Sin embargo, las instrucciones en esta categor铆a limitan el uso del registro que aparece en las instrucciones de pegado correspondientes. Por ejemplo, si MOV EBP, 0x841F0F66 se usa como una instrucci贸n de pegado, entonces las posibilidades de usar el registro EBP (del resto del c贸digo oculto) son significativamente limitadas.


La tercera categor铆a incluye instrucciones que cambian el registro EFLAGS, y los registros de prop贸sito general (o memoria) cambian. Estas instrucciones no tienen ventajas obvias sobre las instrucciones de las dos primeras categor铆as. Sin embargo, tambi茅n se pueden usar, ya que no contradicen las tres condiciones formuladas al principio del art铆culo. La cuarta categor铆a incluye instrucciones, cuya implementaci贸n no garantiza que el programa no se bloquee; existe el riesgo de acceso ilegal a la memoria. Es extremadamente indeseable usarlos, porque No satisfacen la tercera condici贸n.


Instrucciones de ensamblador que se pueden usar en una cadena oculta


En nuestro caso (cuando los NOP de 9 bytes se usan como instrucciones de enmascaramiento), la longitud de cada instrucci贸n de la cadena oculta no debe exceder los cuatro bytes (esta restricci贸n no se aplica a las instrucciones fijas que ocupan 5 bytes). Sin embargo, esta no es una limitaci贸n muy cr铆tica, porque la mayor铆a de las instrucciones que tienen m谩s de cuatro bytes se pueden descomponer en varias instrucciones m谩s cortas. El siguiente es un ejemplo de un MOV de 5 bytes que es demasiado grande para caber en una cadena oculta.



Sin embargo, este MOV de cinco bytes se puede descomponer en tres instrucciones, cuya longitud no supera los cuatro bytes.



Mejora del enmascaramiento mediante la dispersi贸n de los NOP de enmascaramiento en todo el programa


Un gran n煤mero de NOP consecutivos parece, desde el punto de vista inverso, muy sospechoso. Enfocando su inter茅s en estos NOP sospechosos, un inversor experimentado puede llegar al fondo del c贸digo oculto en ellos. Para evitar esta exposici贸n, los NOP enmascarados se pueden dispersar por todo el programa.


La cadena correcta de ejecuci贸n del c贸digo oculto en este caso puede ser apoyada por instrucciones de doble byte de salto incondicional. En este caso, los dos 煤ltimos bytes de cada NOP ocupar谩n un JMP de 2 bytes.


Este truco le permite dividir una secuencia larga de NOP en varias cortas (o incluso usar un NOP cada una). En el 煤ltimo NOP de una secuencia tan corta, solo se pueden asignar 3 bytes de la carga 煤til (la instrucci贸n de salto incondicional tomar谩 el cuarto byte). T.O. Aqu铆 hay una restricci贸n adicional sobre el tama帽o de las instrucciones v谩lidas. Sin embargo, como se mencion贸 anteriormente, se pueden establecer instrucciones largas en una cadena de instrucciones m谩s cortas. A continuaci贸n se muestra un ejemplo del mismo MOV de 5 bytes, que ya presentamos para que se ajuste al l铆mite de 4 bytes. Sin embargo, ahora descomponemos este MOV de tal manera que se ajuste al l铆mite de 3 bytes.



Despu茅s de descomponer todas las instrucciones largas en otras m谩s cortas de acuerdo con el mismo principio, podemos, para enmascarar m谩s, generalmente usar solo NOPs dispersos en todo el programa. Las instrucciones JMP de dos bytes pueden avanzar y retroceder 127 bytes, lo que significa que dos NOP consecutivos (consecutivos, en t茅rminos de una cadena de instrucciones ocultas) deben estar dentro de 127 bytes.


Este truco tiene otra ventaja significativa (adem谩s del enmascaramiento mejorado): con su ayuda, puede colocar c贸digo oculto en los NOP existentes del archivo binario compilado (es decir, insertar una carga 煤til en el binario despu茅s de compilarlo). En este caso, no es necesario que estos NOP hu茅rfanos sean de 9 bytes. Por ejemplo, si hay varios NOP de un solo byte en una fila en el binario, entonces se pueden convertir en NOP de varios bytes, sin interrumpir la funcionalidad del programa. A continuaci贸n se muestra un ejemplo de una t茅cnica para dispersar NOP (este c贸digo es funcionalmente equivalente al ejemplo discutido anteriormente).



Dicho c贸digo oculto, oculto en NOP dispersos por todo el programa, ya es mucho m谩s dif铆cil de detectar.


Un lector atento debe haber notado que el primer NOP no tiene el 煤ltimo byte. Sin embargo, no hay nada de qu茅 preocuparse. Porque este byte no reclamado est谩 precedido por un salto incondicional. T.O. el control nunca ser谩 transferido a 茅l. Entonces todo est谩 en orden.


Aqu铆 hay una t茅cnica para crear c贸digo superpuesto. Uso en salud. Esconde tu precioso c贸digo de miradas indiscretas. Pero simplemente adopte alguna otra instrucci贸n, no un NOP de 9 bytes. Porque los inversores probablemente tambi茅n leer谩n este art铆culo.

Source: https://habr.com/ru/post/es425905/


All Articles