Comprimir binarios

2019/01/21

En varias competiciones de demoscene lo que importa es el tamaño: 1k, 4k, 64k por nombrar algunas.

Suponiendo que ya se tiene el binario, minimizado (ver linux_4k_intro_coding_asm06) es común comprimirlo.

Las dos formas más comunes que suelen encontrarse difieren en quien realiza la descompresión:

  1. La descompresión la realiza el ELF
  2. La descompresión se realiza mediante un script que se encuentra en el header

A continuación se verán las distintas alternativas mediante ejemplos encontrados en distintas demos, centrandose en las del caso 2.

Descompresión realizada por el archivo ELF

Mediante upx - About Time

En el archivo synth_src/bin/strip.sh se encuentran los pasos para preparar el archivo:

strip -s -R .comment -R .gnu.version demo
sstrip demo
upx-nrv --ultra-brute --overlay=strip --all-methods --all-filters --no-mode --no-owner --no-time --lzma demo
sstrip demo

Aparte de los programas strip y sstrip utilizados para reducir el tamaño del binario, se utiliza upx para realizar la descompresión a memoria en tiempo de ejecución.

Notar que el uso de strip y sstrip se realiza siempre, no solamente al utilizar upx.

Descompresión realizada mediante script en el header

Uso de sed - geelimanipulaatio

En la cabecera del archivo geelimanipulaatio_1920x1080 se encuentra:

cp $0 /tmp/M;(sed 1d $0|lzcat)>$_;$_;exit

Tamaño: 42 bytes.

Esta es la cabecera de menor tamaño entre las estudiadas.

Se utiliza sed para borrar la primer linea del archivo y lzcat para descomprimirlo. Aquí $_ hace referencia al último argumento del comando anterior, con lo que se ahorran 4 bytes con cada uso.

Si bien el contenido de la copia (cp $0 /tmp/M) es reemplazado con el siguiente comando ((sed 1d $0|lzcat)>$_), su motivo está relacionado con el permiso de ejecución del archivo, el cual se mantiene del archivo original y evita tener que utilizar el comando chmod +x.

Uso de sed - lo jai rodybo’e nunco’e fai paki’orevo poi’i no’a kaike’a

En el directorio src/src/stub se encuentran alternativas del header a utilizar, por ejemplo, src/src/stub/stub2-outer-xz:

sed 1d $0|xzcat>j;chmod +x j;./j;rm j;exit

Tamaño: 43 bytes.

Similar a la anterior, borra la primera linea del archivo en que se encuentra el header, descomprime el binario, le da permisos de ejecución, lo ejecuta y lo elimina.

Si no se eliminara el archivo sería la cabecera de menor tamaño entre las estudiadas, pero es razonable que se borre el archivo si se descomprime en un directorio que no sea /tmp.

Uso de tail - bioterrorism

En el archivo bioterrorism/src/unpack.header:

a=/tmp/a;tail -n+2 $0|zcat>$a;chmod +x $a;$a;exit

Tamaño: 50 bytes.

Otra alternativa, comparada con el uso de sed, se pierden 2 bytes. La única ventaja de utilizar tail en lugar de sed es la velocidad, pero para estos casos no es apreciable.

Uso de dd - Yellow Rose of Texas

En el archivo src/unpack.header se encuentra:

dd bs=1 skip=83<$0|gunzip>/tmp/T;cd /tmp;chmod +x T;__GL_FSAA_MODE=4 ./T;rm T;exit

En este caso el truco consiste en las opciones al comando dd: bs=1 skip=83 copia el resto del archivo a partir del final del texto exit, ya que la linea ocupa exactamente 83 bytes (contando la nueva linea).

Si se omite la variable de entorno utilizada se obtiene:

dd bs=1 skip=66<$0|gunzip>/tmp/T;cd /tmp;chmod +x T;./T;rm T;exit

Tamaño: 66 bytes.

Conclusión

Como se indicó anteriormente nos centramos en el caso de descompresión basada en script en el header.

Para obtener una conclusión de que script conviene utilizar se normalizarán los scripts anteriores:

Y se tendrán en cuenta las siguientes combinaciones:

  1. El archivo se extrae en el directorio actual y debe borrarse al terminar la ejecución.
  2. El archivo se extrae en el directorio /tmp y no debe borrarse al terminar la ejecución.

A continuación los scritps:

  1. Se extrae en el directorio actual, se borra

    cp $0 P;(sed 1d $0|zcat)>P;./P;rm P;exit
    sed 1d $0|zcat>P;chmod +x P;./P;rm P;exit
    tail -n+2 $0|zcat>P;chmod +x P;./P;rm P;exit
    dd bs=1 skip=46<$0|zcat>P;chmod +x P;./P;exit
    
  2. Se extrae en /tmp, no se borra

    cp $0 /tmp/P;(sed 1d $0|zcat)>$_;$_;exit
    sed 1d $0|zcat>/tmp/P;chmod +x $_;$_;exit
    tail -n+2 $0|zcat>/tmp/P;chmod +x $_;$_;rm $_;exit
    dd bs=1 skip=57<$0|zcat>/tmp/T;chmod +x $_;$_;rm $_;exit
    

En ambos casos el primer script es el ganador (por 1 byte) con un tamaño de 41 bytes totales, contando la nueva linea.

Nota: En los scripts que utilizan dd se saltan la cantidad de carácteres de la linea.

Por último, conviene recordar que no es posible calcular con antelación que método será el más efectivo para obtener un tamaño menor del ejecutable, por lo que conviene utilizar los dos anteriores (ELF, script) y ver con cual se obtiene un archivo de menor tamaño.