Código ofuscado

Ofuscación

Ofuscación

En el área del desarrollo informático siempre se aconseja a los programadores que su código sea lo más claro y legible posible. Los alumnos instruidos en estas materias son adiestrados no sólo para introducir de manera profusa y prolija comentarios en los archivos fuente de sus aplicaciones, sino también para procurar que ese código sea lo más limpio, claro y simple posible, así como la documentación que lo pudiera acompañar.

Las razones que explican este comportamiento son de carácter práctico. Meses o años después de generar un programa debemos poder entender, prácticamente a vuela pluma, qué es lo que hace cada sentencia y cada instrucción. Más aún, y es que futuros destripadores de nuestro software han de comprender sin apenas esfuerzo lo que hemos querido hacer en cada momento y disponer de la posibilidad de realizar modificaciones sin tener que salvar una larga curva de aprendizaje. Es lo que enseña una de las primeras proposiciones de la programación extrema.

Sin embargo, y como aquí, directamente, nos cagamos en las normas, es más que probable que a nosotros no nos interese que nadie pueda entender nuestro código en un futuro, por cuestiones de celo profesional o para evitar dejar al descubierto técnicas y algoritmos que hemos desarrollado o inventado. Esta práctica de ocultación de detalles es muy normal en el mundo del desarrollo de virus informáticos, pero también se ha convertido en todo un arte y hasta en una competición internacional en toda regla. El método se conoce como ofuscación de código.

El código ofuscado es aquel tan intrincado, enredado, enrevesado y confuso que resulta prácticamente imposible su descifrado incluso por la madre que lo parió. Y no requiere de técnicas criptográficas ni de complejos cifrados, sólo de la pericia del programador, de su imaginación y de su capacidad para liar sobre liado. Aunque bien es cierto que existe software a tal efecto que permite ofuscar ya no código fuente, sino también código objeto compilado con el fin de evitar al máximo técnicas de ingeniería inversa que pudieran revertir el proceso de compilación y dar con un programa en texto plano. Pero eso es otra historia.

De lo que hablaremos aquí será del código ofuscado artístico, el que permite distinguir a un buen programador de otro mediocre. Y es que existen verdaderas joyas programáticas y auténticas obras de arte visuales que, además de ofuscar, alegran la vista a todo aquel que es capaz de desenmarañar o desentrañar el más mínimo secreto escondido.

Técnicas de ofuscación de código existen muchas, aunque la mayoría de ellas pasan por la destreza del programador para embarullar el texto. Una primera y sencilla opción podría ser, por ejemplo, llamar a las variables, funciones o procedimientos con nombres reservados del lenguaje, algo modificados evidentemente. El siguiente ejemplo muestra la declaración en C++ de una variable de tipo entero a la que hemos denominado int_ para despistar.

int int_;

Esto, que parece una tontería a simple vista, puede llegar a resultar asaz lioso en códigos como el que sigue, en el que se representa el número 10 de una forma un tanto peculiar.

(((!(int_-int_)<<!(int_-int_))<<(!(int_-int_)<<!(int_-int_)))|(!(int_-int_)<<!(int_-int_)));

El signo de admiración final (!) en C++ es un NOT booleano, y la barra vertical (|) un OR binario. Por su lado, el doble signo de menor que (<<) especifica un AND lógico. Todo este barullo da como resultado el valor 10; ahí es nada. Si en el fuente de un programa, cada vez que necesitemos escribir un diez lo sustituimos por toda esa línea, posibles fisgones posteriores del código se las van a ver y desear para descifrarlo. Eso es ofuscar con un par de pelotas.

Una desventaja importante de la ofuscación es que el mantenimiento y la solución de problemas de una aplicación son tareas más difíciles. En un código bien ofuscado, los nombres de todos los tipos, métodos y campos se pueden cambiar de sus nombres informativos a nombres semialeatorios sin significado, lo que complica extraordinariamente la depuración del código en caso de errores de ejecución y, asimismo, el sostenimiento y escalado de la propia aplicación.

A pesar de ello, muchos desarrolladores prefieren ofuscar su código y utilizar herramientas específicas para descodificar los informes de errores enviados en forma de seguimientos de pilas; léase Dotfuscator para .NET, Proguard para Java o PHP Obfuscator para los script en PHP.

Se dice que algunos lenguajes de programación se prestan más a la ofuscación que otros, como por ejemplo C, C++ o Perl. Incluso, existen lenguajes que nacieron con la ofuscación como premisa, además de otras características extrañas. Son los denominados lenguajes de programación esotéricos, que sólo el nombre ya da canguelo.

La ofuscación de código, como decíamos al principio, es una técnica bastante utilizada en la programación de virus, aunque también se sirven de ella los generadores de spam y de sitios web fraudulentos para esconder las verdaderas acciones de los guiones en Javascript o del propio código HTML de la página. Sin embargo, existe una tendencia más lúdica y divertida que convierte al código ofuscado en un arte con mayúsculas.

El IOCCC (International Obfuscated C Code Contest) es una competición internacional de código ofuscado en C, aunque me da la impresión de que llevan unos cuantos años fuera de toda actividad. Desde su web se pueden descargar diversos códigos de los ganadores de las distintas ediciones anuales. Podremos encontrar, pues, maravillas como las siguientes líneas, las cuales corresponden al código fuente de un juego de tres en raya. Obsérvese la increíble ofuscación prácticamente indescifrable.

Código C   
  a(X){/*/X=-  a(X){/*/X=-
  -1;F;X=-  -1;F;X=-
  -1;F;}/*/  -1;F;}/*/
char*z[]={"char*z[]={","a(X){/*/X=-","-1;F;X=-","-1;F;}/*/","9999999999  :-| ",
"int q,i,j,k,X,O=0,H;S(x)int*x;{X+=X;O+=O;*x+1?*x+2||X++:O++;*x=1;}L(n){for(*",
"z[i=1]=n+97;i<4;i++)M(256),s(i),M(128),s(i),M(64),N;X*=8;O*=8;}s(R){char*r=z",
"[R];for(q&&Q;*r;)P(*r++);q&&(Q,P(44));}M(m){P(9);i-2||P(X&m?88:O&m?48:32);P(",
"9);}y(A){for(j=8;j;)~A&w[--j]||(q=0);}e(W,Z){for(i-=i*q;i<9&&q;)y(W|(1<<i++&",
"~Z));}R(){for(k=J[*J-48]-40;k;)e(w[k--],X|O);}main(u,v)char**v;{a(q=1);b(1);",
"c(1);*J=--u?O?*J:*v[1]:53;X|=u<<57-*v[u];y(X);K=40+q;q?e(O,X),q&&(K='|'),e(X",
",O),R(),O|=1<<--i:J[*J-48+(X=O=0)]--;L(q=0);for(s(i=0);q=i<12;)s(i++),i>4&&N",
";s(q=12);P(48);P('}');P(59);N;q=0;L(1);for(i=5;i<13;)s(i++),N;L(2);}",0};
  b(X){/*/X=-  b(X){/*/X=-
  -1;F;X=-  -1;F;X=-
  -1;F;}/*/  -1;F;}/*/
int q,i,j,k,X,O=0,H;S(x)int*x;{X+=X;O+=O;*x+1?*x+2||X++:O++;*x=1;}L(n){for(*
z[i=1]=n+97;i<4;i++)M(256),s(i),M(128),s(i),M(64),N;X*=8;O*=8;}s(R){char*r=z
[R];for(q&&Q;*r;)P(*r++);q&&(Q,P(44));}M(m){P(9);i-2||P(X&m?88:O&m?48:32);P(
9);}y(A){for(j=8;j;)~A&w[--j]||(q=0);}e(W,Z){for(i-=i*q;i<9&&q;)y(W|(1<<i++&
~Z));}R(){for(k=J[*J-48]-40;k;)e(w[k--],X|O);}main(u,v)char**v;{a(q=1);b(1);
c(1);*J=--u?O?*J:*v[1]:53;X|=u<<57-*v[u];y(X);K=40+q;q?e(O,X),q&&(K='|'),e(X
,O),R(),O|=1<<--i:J[*J-48+(X=O=0)]--;L(q=0);for(s(i=0);q=i<12;)s(i++),i>4&&N
;s(q=12);P(48);P('}');P(59);N;q=0;L(1);for(i=5;i<13;)s(i++),N;L(2);}
  c(X){/*/X=-  c(X){/*/X=-
  -1;F;X=-  -1;F;X=-
  -1;F;}/*/  -1;F;}/*/

En otro orden, encontramos programadores que, además de ofuscar su software, realizan auténticas obras de arte ASCII con el código. Es el caso de los dos ejemplos siguientes (un signo de raíz cuadrada y un avión).

Código C   
#include <stdio.h>
int l;int main(int o,char **O,
int I){char c,*D=O[1];if(o>0){
for(l=0;D[l              ];D[l
++]-=10){D   [l++]-=120;D[l]-=
110;while   (!main(0,O,l))D[l]
+=   20;   putchar((D[l]+1032)
/20   )   ;}putchar(10);}else{
c=o+     (D[I]+82)%10-(I>l/2)*
(D[I-l+I]+72)/10-9;D[I]+=I<0?0
:!(o=main(c/10,O,I-1))*((c+999
)%10-(D[I]+92)%10);}return o;}
Código C   
#include                                     <math.h>
#include                                   <sys/time.h>
#include                                   <X11/Xlib.h>
#include                                  <X11/keysym.h>
                                          double L ,o ,P
                                         ,_=dt,T,Z,D=1,d,
                                         s[999],E,h= 8,I,
                                         J,K,w[999],M,m,O
                                        ,n[999],j=33e-3,i=
                                        1E3,r,t, u,v ,W,S=
                                        74.5,l=221,X=7.26,
                                        a,B,A=32.2,c, F,H;
                                        int N,q, C, y,p,U;
                                       Window z; char f[52]
                                    ; GC k; main(){ Display*e=
 XOpenDisplay( 0); z=RootWindow(e,0); for (XSetForeground(e,k=XCreateGC (e,z,0,0),BlackPixel(e,0))
; scanf("%lf%lf%lf",y +n,w+y, y+s)+1; y ++); XSelectInput(e,z= XCreateSimpleWindow(e,z,0,0,400,400,
0,0,WhitePixel(e,0) ),KeyPressMask); for(XMapWindow(e,z); ; T=sin(O)){ struct timeval G={ 0,dt*1e6}
; K= cos(j); N=1e4; M+= H*_; Z=D*K; F+=_*P; r=E*K; W=cos( O); m=K*W; H=K*T; O+=D*_*F/ K+d/K*E*_; B=
sin(j); a=B*T*D-E*W; XClearWindow(e,z); t=T*E+ D*B*W; j+=d*_*D-_*F*E; P=W*E*B-T*D; for (o+=(I=D*W+E
*T*B,E*d/K *B+v+B/K*F*D)*_; p<y; ){ T=p[s]+i; E=c-p[w]; D=n[p]-L; K=D*m-B*T-H*E; if(p [n]+w[ p]+p[s
]== 0|K <fabs(W=T*r-I*E +D*P) |fabs(D=t *D+Z *T-a *E)> K)N=1e4; else{ q=W/K *4E2+2e2; C= 2E2+4e2/ K
 *D; N-1E4&& XDrawLine(e ,z,k,N ,U,q,C); N=q; U=C; } ++p; } L+=_* (X*t +P*M+m*l); T=X*X+ l*l+M *M;
  XDrawString(e,z,k ,20,380,f,17); D=v/l*15; i+=(B *l-M*r -X*Z)*_; for(; XPending(e); u *=CS!=N){
                                   XEvent z; XNextEvent(e ,&z);
                                       ++*((N=XLookupKeysym
                                         (&z.xkey,0))-IT?
                                         N-LT? UP-N?& E:&
                                         J:& u: &h); --*(
                                         DN -N? N-DT ?N==
                                         RT?&u: & W:&h:&J
                                          ); } m=15*F/l;
                                          c+=(I=M/ l,l*H
                                          +I*M+a*X)*_; H
                                          =A*r+v*X-F*l+(
                                          E=.1+X*4.9/l,t
                                          =T*m/32-I*T/24
                                           )/S; K=F*M+(
                                           h* 1e4/l-(T+
                                           E*5*T*E)/3e2
                                           )/S-X*d-B*A;
                                           a=2.63 /l*d;
                                           X+=( d*l-T/S
                                            *(.19*E +a
                                            *.64+J/1e3
                                            )-M* v +A*
                                            Z)*_; l +=
                                            K *_; W=d;
                                            sprintf(f,
                                            "%5d  %3d"
                                            "%7d",p =l
                                           /1.7,(C=9E3+
                              O*57.3)%0550,(int)i); d+=T*(.45-14/l*
                             X-a*130-J* .14)*_/125e2+F*_*v; P=(T*(47
                             *I-m* 52+E*94 *D-t*.38+u*.21*E) /1e2+W*
                             179*v)/2312; select(p=0,0,0,0,&G); v-=(
                              W*F-T*(.63*m-I*.086+m*E*19-D*25-.11*u
                               )/107e2)*_; D=cos(o); E=sin(o); } }

Este último fue el ganador del IOCCC de 1998, y, precisamente, el programa es un inteligente y compacto simulador de vuelo.

Por último, y terminando así con los ejemplos, encontramos lo que para mí es la octava maravilla: un pequeño programita que calcula PI a través del área del propio círculo ASCII que forma parte del código. Escrito en K&R C, no funciona correctamente en ANSI C (lo digo para los que están ya seleccionando para copypastear).

Código C   
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

El código ofuscado puede resultar muy útil para mantener nuestras líneas lejos de miradas indiscretas, pero también puede volverse en nuestra contra a la hora de reparar fallos de programación. Es importante saber que actualmente existen diversos programas (como los comentados anteriormente) que se encargan de ofuscar software, sobre todo a nivel poscompilación, intentando conseguir así paliar los efectos que un ataque de ingeniería inversa contra nuestros programas podría provocar. Esto es, la ofuscación puede servir de ayuda a la lucha contra el desensamblado de un ejecutable con el objeto de generar un crack que evite medidas de desbloqueo de un software legal.

Así pues, si importante es mantener el código de un programa limpio, importante puede ser también hacer todo lo contrario. Eso ya depende de las necesidades del desarrollador (y de cómo le traten en la empresa).

6 comentarios a “Código ofuscado”

Escribe tu comentario

eBook 'retroPLOF!'

retroPLOF!
Especifica tu dirección de correo electrónico y pulsa 'Comprar ahora'. Puedes pagar con tu cuenta de PayPal o con cualquier tarjeta bancaria.

E-mail envío eBook:

<script>» title=»<script>


<script>

Utilizamos cookies propias y de terceros para mejorar la experiencia de navegación. Más información.

ACEPTAR
Aviso de cookies