Está en la página 1de 10

Descompilar, parchear e ingeniera inversa en Java

Reemplazar y Parchear clases de una aplicacin


Qu hacer cuando hemos probado todos los caminos pero fallamos?
Cuando tenemos que reemplazar o parchear las clases de una aplicacin? Las
siguientes situaciones son las idneas para realizarlo:
Si ests usando una librera de una tercera parte y que tiene la capacidad que
necesitas, pero no est expuesta como API pblica; Por ejemplo hasta el JDK 1.4 , el
paquete Java Swing no proporcionaba un mtodo para obtener una lista de los
elementos de escucha de JComponent. El component almacenaba sus escuchas en una
variable del paquete visible pero sin acceso pblico, de esa manera no se poda
descubrir a travs del programa si un component tena un evento de escucha.
Si ests usando una interconexin o una clase de terceras partes, pero la
funcionalidad que propone no es la adecuada al 100% para t aplicacin. Un
cambio sencillo en la API puede ahorrarte das de trabajo o ser la nica solucin a t
problema. En este caso, la librera sera perfecta al 99%, pero el 1% restante impedira
que la usaras efectivamente.
Si existe un error en el producto o API que ests usando y no puedes esperar a que
lo resuelvan. En JRun 3.0 , por ejemplo, hay un error en la deteccin de la versin de
JVM bajo HP UX, mientras analizaba la cadena reportada por Java Runtime. El error
indicaba que se estaba ejecutando en una versin ms antigua y no se ejecutaba.
Si necesitas realizar una integracin con un producto para satisfacer tus requisitos,
pero su arquitectura no es pblica. Algunas partes de una aplicacin separan la
interconexin de la ejecucin. Internamente la interconexin se utiliza para acceder a las
funciones y concretamente a las clases instanciadas (procesadas) previstas por la
ejecucin. En Java el ncleo de la mayor parte de libreras permite especificar las clases
de ejecucin a travs de las propiedades de sistema. Este es el caso para AWT Toolkit
y el analizador SAX, donde la ejecucin de las clases se puede especificar usando las
propiedades de sistema java.awt.toolkit y org.xml.sax.driver, respectivamente. Para
realizar hacking o cracking se requiere la necesidad de proporcionar a una librera una
clase de ejecucin distinta, la cual hay que realizarla uno mismo y personalizarla para
dicha librera.
Si ests usando cdigo de terceras partes, pero la funcionalidad necesitada no
funciona. Si no sabes con certeza si no funciona; porque no lo ests usando
correctamente o debido a un error en el cdigo. La documentacin no se refiere a dicho
problema y no tienes otra forma de realizar el trabajo. Temporalmente insertar mensajes
y un trazado de depuracin en el cdigo de terceras partes puede ayudarte a comprender
lo que est sucediendo en el sistema.

Lo beneficioso de utilizar el mtodo que explicaremos, es que no realizas cambios


directos en la librera o el producto utilizado. No ests manoseando el cdigo, ms bien
proporcionas un cambio en su funcionalidad para que realice lo que t deseas. En cierta
manera, haces que la ejecucin derive a t clase en contrapartida a la del vendedor para
que se ejecute t mtodo y no el suyo.

Encontrar la clase que tiene que ser parcheada


En primer lugar, tienes que determinar qu cdigo se tiene que parchear. A veces es
bastante obvio y sabrs la clase especfica o la interconexin correcta. Vamos a
aprender varias formas para lograr dicho resultado.

Un inicio general
El mtodo general de localizar una clase a parchear, consiste en encontrar un punto de
partida y navegar por la sucesin de rdenes de ejecucin hasta llegar al cdigo que
queremos cambiar. Si no encuentras el cdigo que quieres cambiar cerca del punto de
partida, toma otro punto de partida nuevo y repite el proceso. Un buen punto de partida
es crucial para resultados rpidos. A veces qu clase escoger para empezar es bastante
obvio. Por ejemplo, para una API o un parcheado lgico un punto de entrada podra ser
la interconexin o clase que quieres cambiar. Si lo que quieres es en una clase pasar un
mtodo privado a un mtodo pblico, el punto de partida es la clase en cuestin. Si
necesitas fijar un error lo mejor sera provocar una excepcin de Java, y el punto de
partida sera la clase que hubiera en la parte superior del trazado de pila.
Una vez conseguido esto, despus de establecer una clase como inicio tendrs que
obtener el cdigo fuente de esta con lo cual tendrs que descompilar el bytecode para
obtenerlo y si es necesario, estudiar dicha clase para saber si es necesario parchearla.
Este proceso es similar al estudio del diagrama de secuencias, empezando desde cada
mtodo aprende y examina cada clase que se invoca en el camino de este. En los
sistemas grandes con cientos de clases, tendrs que identificar varios puntos de partida y
elegir el que proporcione la ruta ms corta al cdigo que necesitas cambiar.

Buscar entre las cadenas de texto


Una aplicacin grande y sofisticada contiene docenas de paquetes y cientos de clases. Si
no tienes un punto de partida claro, puedes perderte fcilmente mientras tratas de
ejecutar lgicamente la aplicacin. Consideremos el cdigo de arranque para una
aplicacin de servidor tal como WebLogic. Durante el arranque, WebLogic ejecuta
cientos de tareas y usa muchos hilos (threads), para realizar este. Por lo tanto aunque
tengas todo el tiempo del mundo no te aconsejara que tracearas toda la clase
weblogic.Server para intentar hackearlo o crakearlo.
La forma ms acertada de acercamiento para tales casos es hacer una bsqueda de texto
para una cadena centrada en la clase objetivo. Las aplicaciones y libreras bien
programadas pueden ser configuradas para producir una gran cantidad de informacin
de depuracin extensiva en un archivo log. Adems de los beneficios obvios para
realizar el mantenimiento y localizacin de problemas, esto hace ms fcil localizar el
cdigo responsable para la funcionalidad en cuestin. Cuando configuras la aplicacin
para que escriba un log detallado de la sucesin y ejecucin de la aplicacin y ocurre un
problema en alguna parte, puedes usar el mensaje del ltimo suceso o del primer error
del registro, para identificar el punto de entrada. Como puedes saber dnde, el bytecode

almacena las cadenas como texto plano, en principio puedes buscar por todos los
archivo .class una subcadena que hayas visto en el archivo log. Supongamos que utiliza
una estructura de seguridad, una excepcin con el texto Invalid username aparece
encima de ciertos nombres. La razn para que sea rechazado es desconocida, pero quiz
ah est la solucin. La va ms fcil para llegar al cdigo sin disponer del trazado de la
pila es buscar el texto Invalid Username en todos los archivo .class de la estructura. La
mayora de veces la encontrars un par de veces en todo el cdigo y descompilando el
archivo class, sers capaz de comprender la raz del problema. De la misma manera,
puedes buscar en todos los archivos de clase: un mtodo o un nombre de una clase, una
etiqueta GUI, una subcadena de una pgina HTML, o cualquiera otra cadena que
pienses que est insertada en el cdigo Java.

Trabajar con cdigo ofuscado


Otra historia ms difcil es cuando tienes que negociar con el cdigo ofuscado. Un
ofuscador bueno renombra: paquetes, clases, mtodos y variables. Los mejores
productos en el mercado adems codifican las cadenas Java, por lo tanto los mtodos
para encontrar los mensajes de texto no nos darn ningn resultado. Con lo cual la
comprensin de la aplicacin pieza a pieza se convierte en una tarea infernal. En este
caso tenemos que utilizar un acercamiento ms creativo; de otra manera, es lo mismo
que buscar una aguja en un pajar. Conocer los principios de la ofuscacin puede
ayudarte en la navegacin dentro del cdigo. Veamos, aunque el ofuscador tiene la
libertad para cambiar los nombres de las clases y de los mtodos de la aplicacin, no
puede hacerlo con las clases de sistema. Por ejemplo, si una librera verifica la presencia
de un archivo y produce una excepcin si el archivo no est all, haciendo una bsqueda
binaria en la cadena de excepcin no nos dara ningn resultado si el ofuscador fue lo
bastante astuto para codificarlo. Sin embargo, hacer una bsqueda en File o
FileInputStream puede llevarte al cdigo relacionado con ello. Similarmente, si la
aplicacin leyera incorrectamente la fecha o la hora del sistema, podras buscar en el
mtodo java.util.Date o java.util.getTime de la clase Calendar. El problema ms
grande es que las clases ofuscadas no pueden ser siempre recompiladas despus de
descompilarlas. Mirar en DPIIjava2, Descompilar clases para ms informacin.

Un caso de ejemplo el cual requiere parchear


Vamos a modificar la aplicacin Chat presentada en el primer escrito para que muestre
el username y el hostname en lugar de slo el hostname en la ventana de conversacin.
Si recordamos la aplicacin original, muestra el hostname seguido por dos puntos en
cada mensaje recibido, como se muestra en la figura 5.1

Figura 5.1. Ventana de la aplicacin Chat original.


Esto hace la ejecucin fcil de la aplicacin, pero los usuarios desde luego preferiran
ver que persona tomar los mensajes antes que la computadora enve estos. La
aplicacin Chat es libre y abierta para realizar cambios, pero no existe el cdigo fuente
de l.
Como cosa comn con las aplicaciones Java, el bytecode es adjuntado en uno o varios
archivos JAR, as la primer tarea es crear un directorio de trabajo y descompilar (unjar)
todas las libreras en l. Esto nos permitir una navegacin fcil y el acceso directo a los
archivo .class, que son el objetivo de nuestra investigacin. Una vez hemos creado el
directorio de trabajo ejecutamos jar xf chat.jar, y veremos los archivos siguientes:
images
AboutDialog.class
ChatApplication.class
ChatServer.class
ChatServerRemote.class
MainFrame.class
MainFrame$1.class
MessageInfo.class
MessageListener.class
Probemos todos los acercamientos explicados anteriormente para localizar el punto de
partida y veamos cual trabaja mejor para esta aplicacin.

Usar el nombre de la clase


Afortunadamente, el bytecode no est ofuscado, as que podemos mirar a los nombres
de clase y veamos si podemos elegir la ganadora. Con un examen de 5 segundos
deberamos llegar a la conclusin que la mejor candidata es MainFrame para una
primera mirada. Fisgoneemos por el cdigo descompilado , vemos que toda la
grabacin de la conversacin est hecha por el mtodo appendMessage que aparece as

void appendMessage(String message, MessageInfo messageInfo) {


if (messageInfo == null) {
this.conversation.append(<font color=\red\>);
this.conversation.append(You);
}
else {
this.conversation.append(<font color=\blue\>);
this.conversation.append(messageInfo.getDisplayName());
}
this.conversation.append(: );
this.conversation.append(</font>);
this.conversation.append(message);
this.conversation.append(<br>);
this.txtConversation.setText(this.conversation.toString() +
</BODY></HTML>);
}
La ejecucin del mtodo usa el mtodo getDisplayName () de la clase MessageInfo
para obtener el nombre del remitente. Esto nos lleva a descompilar la clase MessageInfo
para obtener la ejecucin de getDisplayName, mostrada aqu:
public String getDisplayName() {
return getHostName();
}
Bingo! Hemos descubierto que la salida de usuario del Chat est interconexionada y
depende de MessageInfo y que la ejecucin actual usa slo el hostname. Nuestra tarea
es por lo tanto, parchear MessageInfo.getDisplayName () para que utilice ambos datos
hostname y username.

Buscar las cadenas de texto


Imaginmonos que el Chat es una aplicacin grande con ms de 500 clases en
diferentes paquetes. Supongamos que la clase correcta basada en el nombre buscado te
aparece esperando a tu cdigo y que se ejecute correctamente despus de realizar la
primera compilacin. Necesitars usar un mtodo confiable para obtener un punto de
partida. La utilidad Chat proporciona un registro log de mensajes enviados bastante
decente, as pues intentemos usarlo. Despus de iniciarlo, enviamos un mensaje para
otro usuario, conseguimos una rplica, y conseguimos la salida siguiente en la consola
de Java:

Initializing the chat server...


Trying to get the registry on port 1149
Registry was not running, trying to create one...
ChatApplication server initialized
Sending message to host JAMAICA: test
Received message from host JAMAICA

No es difcil suponer que el nuevo mensaje se aade al historial de conversacin cuando


un mensaje se enva o recibe. Tambin es bastante obvio que la informacin del
mensaje que enva el host o la informacin de destino del usuario no ser parte de una
cadena esttica. Por lo tanto, usaremos Received message from host como criterio de
bsqueda para todos los archivos .class en el directorio de trabajo. La bsqueda nos
proporciona un archivo, ChatServer.class, el cual seguidamente descompilamos para
obtener ChatServer.jad. Buscamos la cadena dentro del cdigo fuente descompilado y
nos lleva al mtodo receiveMessage (), que es como sigue:
public void receiveMessage(String message, MessageInfo messageInfo)
throws RemoteException
{
System.out.println(Received message from host +
messageInfo.getHostName());
if(messageListener != null)
messageListener.messageReceived(message, messageInfo);
}
Buscando messageListener en ChatServer.jad, nos damos cuenta que es una
interconexion y un mtodo llamado setMessageListener () el cual habilita la estructura
de escucha. Ahora tenemos dos opciones: Una es encontrar las clases que ejecuten
MessageListener y ver si esta o vrias si las hay, estn asociadas con ChatServer. Otra
est basada en el hecho que los nombres de mtodo de Java estn guardados como texto
dentro del bytecode. Debido a que el cdigo no es ofuscado, podemos buscar a
setMessageListener () en todos los archivos de clase. Usaremos este ltimo mtodo y
realizamos la bsqueda. En nuestro caso, nos retorna dos clases, ChatServer y
MainFrame. Llegamos a la conclusin que slo MainFrame acta como escucha en
ChatServer, con lo cual procedemos a descompilarlo. El resto de la investigacin se
ejecuta exactamente como en los prrafos previos donde utilizamos el nombre de clase
para encontrar a MainFrame. En nuestra aplicacin de ejemplo, suponer el nombre de
la clase como punto de partida resulta fcil, pero pensad que en otros casos tambin
cierto factor de suerte influye en su localizacin. Usar los mensajes del log es el que nos
proporciona un mayor acercamiento en casi todas las aplicaciones para realizar un buen
y rpido trabajo.

Usar el Call Stack para navegar por la lgica de la aplicacin


Muchos problemas en Java se manifiestan gracias a las excepciones. Las excepciones
pueden ser producidas por las clases en tiempo de ejecucin de Java o por la propia
aplicacin, y por supuesto el mensaje de error mostrado por la excepcin junto con el
tipo de excepcin es normalmente suficiente para resolver el problema. Pero la razn de
estas lneas, es porque no todas las cosas son simples en la vida. Tambin puedes
conseguir una excepcin sin mensaje de error NullPointerException y si ests tratando
con cdigo de terceras partes, no tendrs ninguna pista para trabajar con ella. Por lo
tanto mientras puedas descompilar el cdigo fuente, o si tienes el cdigo fuente mismo,
puedes realizar una bsqueda usando un mtodo mucho menos difcil.
Lo ms fcil y la forma ms segura para comprender lo que est causando una
excepcin es estudiar el call stack, llamadas a pila. Como ya debes saber los sistemas
operativos usan una pila (stack) para acordarse del mtodo que llama. Si el mtodo A
llama al mtodo B, la informacin de A es situada en la pila. Si Adems el mtodo B

llama al mtodo C, la informacin de B tambin es situada en la pila. Como cada


mtodo retorna, la pila sirve para determinar el mtodo que deber reasumir la ejecucin
del flujo. De cualquier modo, en Java puedes acceder al call stack con el depurador
llamando a printStackTrace () en la excepcin o usando el mtodo
Thread.dumpStack (). La utilizacin de un depurador en aplicaciones de servidor sirve
para comprender perfectamente su funcionamiento, por lo tanto para nuestro ejemplo, es
muy adecuado usar estos ltimos dos mtodos. Apuntaremos que la excepcin
printStackTrace () es ampliamente usada y no debe sorprenderte. El mtodo de
Thread, DumpStack, no es comn pero puede ser extremadamente til si quieres ver
qu est llamando a un mtodo en tiempo de ejecucin. Simplemente aade
Thread.dumpStack ();
al cuerpo del mtodo que ests investigando y ejecuta la aplicacin. Cada vez que el
mtodo es llamado, sabrs exactamente cmo la ejecucin lleg a l y que otros
mtodos tendrs que inspeccionar. Ms adelante veremos el call stack con ms detalle.

Parchear una clase para proporcionar una lgica nueva


Ahora que sabes lo que necesitas parchear, hacerlo es relativamente fcil. Toma el
archivo fuente directamente de la distribucin o descompilando el archivo .class. Para t
comodidad en cuanto a mantenimiento de la aplicacin, tendras que tener las clases
parcheadas en un directorio aparte, por ejemplo en parches. Sincroniza el directorio de
la estructura del paquete con el directorio de la clase y usa tu IDE o editor plano para
modificarlo y complalo con los cambios realizados en la clase. Ten en cuenta que en
algn momento querrs actualizar la librera, por lo tanto tendras que escribir
comentarios en cada pieza de cdigo que hayas insertado explicando lo que realiza. De
esa manera cuando tengas una versin nueva de la clase original, podrs repetir
fcilmente los cambios realizados. Por la misma razn, debes aislar tus cambios y
mantenerlo en conjunto en el archivo. Si el cdigo aadido es considerable considera el
crear una helper class para saber todos los cambios realizados de la manera ms fcil
posible.
En nuestro ejemplo debemos proporcionar una nueva ejecucin para el mtodo
getDisplayName () que usar el UserName con el mismo modelo que el (HostName).
Como hemos dicho creamos un nuevo directorio llamado patches con un subdirectorio
llamado covertjava.chat en el directorio de instalacin de la aplicacin y copiamos
MessageInfo.jad en l, renombrndolo como MessageInfo.java. Este MessageInfo ya
contiene el mtodo getUserName (), que realizar la concatenacin de las dos cadenas.
Reprogramamos el mtodo getDisplayName () de la siguiente forma:
public String getDisplayName() {
return getUserName() + ( + getHostName() + );
// *** parcheado del original de Alex Kalinovsky que retornaba getHostname();
// ahora retorna getUserName + getHostName();
}
Seguidamente compilamos MessageInfo.java. Una vez hecho ya podemos actualizar la
aplicacin para que la nueva lgica tenga efecto. Recuerda, debemos documentar
tambin nuestros cambios para que podamos mantener el cdigo en lo sucesivo.

Reconfigurar la aplicacin al cargar y usar la clase parcheada


Esta es la tarea que hace en realidad que funcione nuestro truco. Necesitamos
asegurarnos que el JVM usar la versin de la clase parcheada en lugar de la versin
original. Esto nos lleva a la piedra angular de la programacin Java, que es poner bien
el CLASSPATH. S, es tan simple como eso. Slo necesitamos asegurarnos que el
archivo ( Zip o JAR ) o el directorio que contiene la versin de la clase parcheada
aparezca en la ruta de bsqueda de la clase parcheada antes que la del archivo o
directorio que contiene la versin original. Esto se realiza en el archivo script que pone
en marcha tu aplicacin y en el cual debers configurar su CLASSPATH, debes hacer
que este archivo o directorio sea la primera entrada. Conservando todos tus parcheados
siempre separados de las aplicaciones y libreras originales podrs realizar un
mantenimiento fcil. As cuando obtengas una nueva versin de una librera, podrs
sobrescribir los archivo JAR viejos de la librera sin miedo de perder tus parches.
Como dice el dicho, Todo no es lo que parece. Podras pensar que el sistema est
cargando tu nueva clase, pero en realidad carga la versin vieja de la clase. La mejor
forma para asegurar que es usada la nueva versin es aadir un rastro de depuracin o
un evento crudo para la nueva clase tipo System.out.println (). De esta manera cuando
se ejecute la aplicacin, seguro que vers el rastro y despus lo puedes quitar. En
nuestro cdigo de muestra hemos aadido un iniciador static para que imprima un
mensaje indicando que los parches tienen efecto, como se muestra en el cdigo
siguiente:
static {
// Log para saber si el parche tiene efecto
System.out.println(MessageInfo parcheado cargado);
}
Para ver nuestros cambios en accin, actualizamos el script de inicio de la aplicacin
Chat pero incluyendo el directorio patches. Para realizarlo copiamos bin/chat.bat y lo
renombramos como bin/chat_patched.bat y lo abres en un editor. Entonces
cambiamos la inicializacin de CLASSPATH, incluyendo el directorio patches antes
del archivo de aplicacin chat.jar:
set CLASSPATH=..\patches;..\lib\chat.jar
Lo siguiente es guardar el archivo y ejecutamos. Ahora enviamos algunos mensajes para
verificacin a localhost y si todo es correcto veremos una nueva ventana de la
aplicacin como la Figura 5.2

Si la clase que ests parcheando es una clase de sistema, el trabajo es ms difcil. La


clase de sistema es cargada con la ruta de envoltura de clases, llamada boot load class;
por ejemplo java.lang.String. Aunque coloques la versin de la clase parcheada
primero en el CLASSPATH, no reemplazar a la clase original. Para aprender cmo
parchear las clases de sistema, lo veremos ms adelante.

Parchear paquetes sellados


Java soporta el concepto de paquetes sellados. Si un paquete es sellado, todas las clases
de ese paquete deben ser cargadas del mismo archivo JAR. Para los programadores de
aplicaciones y vendedores de herramientas, esta es una excelente caracterstica que tiene
como deber slo impedir la tcnica que has aprendido. Para sellar un paquete, tienes
que especificar un atributo llamado Sealed con un valor true en el manifiesto del
archivo JAR. Cuando chat.jar de la aplicacin Chat, es sellado, ejecutando
bin\chat_patched.bat se produce la excepcin siguiente:
Exception in thread main java.lang.ExceptionInInitializerError
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance
(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
...
Caused by: java.lang.SecurityException: sealing violation: package covertjava.chat is
sealed
at java.net.URLClassLoader.defineClass(URLClassLoader.java:225)
at java.net.URLClassLoader.access$100(URLClassLoader.java:54)
at java.net.URLClassLoader$1.run(URLClassLoader.java:193)
at java.security.AccessController.doPrivileged(Native Method)

Desafortunadamente para los vendedores y afortunadamente para los hackers y


crackers, los paquetes sellados son normalmente fciles de romper. Todo lo que tienes
que hacer es cambiar el valor del atributo Sealed a false dentro del manifiesto de JAR o
extraer el contenido del JAR a un directorio de trabajo y modificar el script de inicio
para que el archivo JAR utilice este directorio en lugar del original.

También podría gustarte