No es así como LD_PRELOAD
debe funcionar *. Y estoy realmente luchando para encontrar cómo podría funcionar. Cuando realizo lo siguiente en el último kernel de Linux (4.7), el último glibc
(2.27) y el último ld
(binutils 2.27) obtengo lo siguiente:
$ LD_PRELOAD='0000000' less pp.c
ERROR: ld.so: object '0000000' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
[4]+ Stopped LD_PRELOAD='0000000' less pp.c
Entonces, sí, ld
simplemente se queja de que el objeto (aquí 0x00000) no es un ELF. Esto viene de elf/rtld.c
en glibc
, aquí:
static unsigned int
do_preload (const char *fname, struct link_map *main_map, const char *where)
{
const char *objname;
const char *err_str = NULL;
struct map_args args;
bool malloced;
args.str = fname;
args.loader = main_map;
args.mode = __RTLD_SECURE;
unsigned int old_nloaded = GL(dl_ns)[LM_ID_BASE]._ns_nloaded;
(void) _dl_catch_error (&objname, &err_str, &malloced, map_doit, &args);
if (__glibc_unlikely (err_str != NULL))
{
_dl_error_printf ("\
ERROR: ld.so: object '%s' from %s cannot be preloaded (%s): ignored.\n",
fname, where, err_str);
/* No need to call free, this is still before
the libc's malloc is used. */
}
else if (GL(dl_ns)[LM_ID_BASE]._ns_nloaded != old_nloaded)
/* It is no duplicate. */
return 1;
/* Nothing loaded. */
return 0;
}
Se llama a
do_preload
para cada objeto en LD_PRELOAD
(separados por espacio o dos puntos). Ahora se lanzó Ubuntu 3.0.0 alrededor de julio de 2011, en ese momento el último glibc
era 2.15; Observé do_preload
en glibc-2.15
y encontré exactamente el mismo código.
Ahora, tal vez los chicos de ubuntu usaron un antiguo glibc, por ejemplo, glibc-2.11
(noviembre de 2009, y eso lo está empujando mucho). Si observa ese código, verá que do_preload
sigue siendo el mismo. Ese código se agregó en 1998 y nunca se cambió.
Lo único en lo que puedo pensar son las características agregadas por ubuntu en ese momento (pero eso es bastante dudoso). No debería tener nada que ver con el kernel de Linux, ya que es el trabajo del enlazador agregar los objetos compartidos correctos (por si acaso, fui a uno de esos indizadores de código para el código del kernel y verifiqué todos los kernels 3.xy 4.x . Y, como se esperaba, LD_PRELOAD no se usa en ninguna parte).
Lo único que puedo hacer es discutir sobre cómo se debe utilizar LD_PRELOAD
. Y cómo lograr un efecto similar teniendo en cuenta el extracto anterior de glibc
. La primera opción para obtener ese shellcode en la memoria usa LD_PRELOAD
correctamente (pero eso es muy diferente de lo que hacen en esa publicación del blog), la segunda opción usa el entorno (que creo que es lo que están viendo en la publicación del blog y entendiéndolo mal como un objeto compartido cargado).
Opción 1: usar un objeto compartido real en LD_PRELOAD
El ld
actual verificará si el objeto señalado por LD_PRELOAD
es un ELF, es decir, que comienza con \x7fELF
. Una mejor manera de lograr la carga de un código específico en la memoria de un programa es crear un ELF y luego señalarlo con LD_PRELOAD
:
$ perl -e 'print "int yay(void){char *x=\"" . "A"x10000 . "\"; return 0;}\n"' > pp.c
$ gcc -o pp.so -fPIC -shared pp.c
$ LD_PRELOAD=./pp.so less pp.c
# And Ctrl+Z to send SIGTSTP
$ jobs -l
[1]+ 15476 Stopped LD_PRELOAD=./pp.so less pp.c
$ cat /proc/15476/maps
00400000-00422000 r-xp 00000000 08:03 3811056 /usr/bin/less
00621000-00622000 r--p 00021000 08:03 3811056 /usr/bin/less
00622000-00626000 rw-p 00022000 08:03 3811056 /usr/bin/less
00626000-0062a000 rw-p 00000000 00:00 0
01336000-01357000 rw-p 00000000 00:00 0 [heap]
7f0328272000-7f0328525000 r--p 00000000 08:03 3845397 /usr/lib/locale/locale-archive
7f0328525000-7f032853d000 r-xp 00000000 08:03 3804385 /usr/lib/libpthread-2.24.so
7f032853d000-7f032873c000 ---p 00018000 08:03 3804385 /usr/lib/libpthread-2.24.so
7f032873c000-7f032873d000 r--p 00017000 08:03 3804385 /usr/lib/libpthread-2.24.so
7f032873d000-7f032873e000 rw-p 00018000 08:03 3804385 /usr/lib/libpthread-2.24.so
7f032873e000-7f0328742000 rw-p 00000000 00:00 0
7f0328742000-7f03288d7000 r-xp 00000000 08:03 3804421 /usr/lib/libc-2.24.so
7f03288d7000-7f0328ad6000 ---p 00195000 08:03 3804421 /usr/lib/libc-2.24.so
7f0328ad6000-7f0328ada000 r--p 00194000 08:03 3804421 /usr/lib/libc-2.24.so
7f0328ada000-7f0328adc000 rw-p 00198000 08:03 3804421 /usr/lib/libc-2.24.so
7f0328adc000-7f0328ae0000 rw-p 00000000 00:00 0
7f0328ae0000-7f0328b52000 r-xp 00000000 08:03 3811050 /usr/lib/libpcre.so.1.2.7
7f0328b52000-7f0328d51000 ---p 00072000 08:03 3811050 /usr/lib/libpcre.so.1.2.7
7f0328d51000-7f0328d52000 r--p 00071000 08:03 3811050 /usr/lib/libpcre.so.1.2.7
7f0328d52000-7f0328d53000 rw-p 00072000 08:03 3811050 /usr/lib/libpcre.so.1.2.7
7f0328d53000-7f0328dba000 r-xp 00000000 08:03 3804648 /usr/lib/libncursesw.so.6.0
7f0328dba000-7f0328fba000 ---p 00067000 08:03 3804648 /usr/lib/libncursesw.so.6.0
7f0328fba000-7f0328fbe000 r--p 00067000 08:03 3804648 /usr/lib/libncursesw.so.6.0
7f0328fbe000-7f0328fc0000 rw-p 0006b000 08:03 3804648 /usr/lib/libncursesw.so.6.0
7f0328fc0000-7f0328fc3000 r-xp 00000000 08:05 3801272 /home/grochmal/tmp/pp.so
7f0328fc3000-7f03291c2000 ---p 00003000 08:05 3801272 /home/grochmal/tmp/pp.so
7f03291c2000-7f03291c3000 r--p 00002000 08:05 3801272 /home/grochmal/tmp/pp.so
7f03291c3000-7f03291c4000 rw-p 00003000 08:05 3801272 /home/grochmal/tmp/pp.so
7f03291c4000-7f03291e7000 r-xp 00000000 08:03 3804420 /usr/lib/ld-2.24.so
7f03293b0000-7f03293b2000 rw-p 00000000 00:00 0
7f03293e4000-7f03293e6000 rw-p 00000000 00:00 0
7f03293e6000-7f03293e7000 r--p 00022000 08:03 3804420 /usr/lib/ld-2.24.so
7f03293e7000-7f03293e8000 rw-p 00023000 08:03 3804420 /usr/lib/ld-2.24.so
7f03293e8000-7f03293e9000 rw-p 00000000 00:00 0
7fff77a97000-7fff77ab8000 rw-p 00000000 00:00 0 [stack]
7fff77bb5000-7fff77bb7000 r--p 00000000 00:00 0 [vvar]
7fff77bb7000-7fff77bb9000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
El bit importante que /proc/<PID>/maps
nos dice es la ubicación de la memoria del objeto, la cadena "AAAAA ... AAAA" debería estar en algún lugar allí:
7f0328fc0000-7f0328fc3000 r-xp 00000000 08:05 3801272 /home/grochmal/tmp/pp.so
7f0328fc3000-7f03291c2000 ---p 00003000 08:05 3801272 /home/grochmal/tmp/pp.so
7f03291c2000-7f03291c3000 r--p 00002000 08:05 3801272 /home/grochmal/tmp/pp.so
7f03291c3000-7f03291c4000 rw-p 00003000 08:05 3801272 /home/grochmal/tmp/pp.so
Ahora podemos usar GDB para comprobarlo (tenga en cuenta que estoy usando nuevamente el PID que obtuve de jobs -l
):
# gdb -q -p 15476
...
warning: Could not load shared library symbols for ./pp.so
...
Esa línea aparece si GDB se está ejecutando en un directorio de trabajo diferente al que se inició el proceso. Pero está bien, buscamos la memoria, no los símbolos.
(gdb) p 0x7f03291c4000 - 0x7f0328fc0000
$1 = 2113536
(gdb) x/2113536x 0x7f0328fc0000
(Tomé los números de los mapas y usé GDB como calculadora)
Eso imprimirá muchas cosas (toda la memoria del objeto compartido que creamos, incluidos los bits agregados por ELF y PIC), pero mostrará todas las "A" que buscamos:
...
0x7f0328fc2ae0: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2af0: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b00: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b10: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b20: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b30: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b40: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b50: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b60: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b70: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b80: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2b90: 0x41414141 0x41414141 0x41414141 0x41414141
0x7f0328fc2ba0: 0x41414141 0x41414141 0x41414141 0x41414141
...
Opción 2: usar el entorno
Ahora, esto no necesita que la variable de entorno se llame LD_PRELOAD
. La variable puede ser cualquier cosa. Comencemos agregando nuestro código de shell ("AAAA ...") a LD_PRELOAD
como lo hicieron en la publicación del blog:
$ LD_PRELOAD='perl -e 'print "A"x30'' less pp.c
ERROR: ld.so: object 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
[1]+ Stopped LD_PRELOAD='perl -e 'print "A"x30'' less pp.c
$ jobs -l
[1]+ 1566 Stopped LD_PRELOAD='perl -e 'print "A"x30'' less pp.c
Hemos recibido la advertencia, la ignoraremos y adjuntaremos GDB:
# gdb -q -p 1566
(gdb) info variable environ
All variables matching regular expression "environ":
Non-debugging symbols:
0x00007fb7bd5babd8 last_environ
0x00007fb7bd5bbf18 __environ
0x00007fb7bd5bbf18 _environ
0x00007fb7bd5bbf18 environ
0x00007fb7bdcc30c0 __environ
0x00007fb7bdcc30c0 _environ
0x00007fb7bdcc30c0 environ
Y busca la variable environ
( man 3 environ
). Tenemos dos de ellos, eso es una peculiaridad del kernel 4. environ
es un char**
, así que echemos un vistazo a la búsqueda de esas cadenas:
(gdb) x/3gx 0x00007fb7bd5bbf18
0x7fb7bd5bbf18 <environ>: 0x00007ffd1c3884a0 0x0000000000000000
0x7fb7bd5bbf28 <buflen.8581>: 0x0000000000000000
(gdb) x/3gx 0x00007fb7bdcc30c0
0x7fb7bdcc30c0 <environ>: 0x00007ffd1c3884a0 0x0000000000000000
0x7fb7bdcc30d0: 0x0000000000000000
Ambos símbolos environ
apuntan al mismo lugar en la memoria (es decir, 0x00007ffd1c3884a0
). Veamos que hay ahí dentro:
(gdb) x/20gx 0x00007ffd1c3884a0
0x7ffd1c3884a0: 0x00007ffd1c388ba3 0x00007ffd1c388bcd
0x7ffd1c3884b0: 0x00007ffd1c388bd8 0x00007ffd1c388bea
0x7ffd1c3884c0: 0x00007ffd1c388c05 0x00007ffd1c388c15
0x7ffd1c3884d0: 0x00007ffd1c388c27 0x00007ffd1c388c4c
0x7ffd1c3884e0: 0x00007ffd1c388c6c 0x00007ffd1c388c8b
0x7ffd1c3884f0: 0x00007ffd1c388c99 0x00007ffd1c388cd1
0x7ffd1c388500: 0x00007ffd1c388cfa 0x00007ffd1c388d49
0x7ffd1c388510: 0x00007ffd1c388d67 0x00007ffd1c388e01
0x7ffd1c388520: 0x00007ffd1c388e10 0x00007ffd1c388e27
0x7ffd1c388530: 0x00007ffd1c388e38 0x00007ffd1c388ebd
Apuesto a que son cadenas, veamos:
(gdb) x/s 0x00007ffd1c388ba3
0x7ffd1c388ba3: "LD_PRELOAD=", 'A' <repeats 30 times>
(gdb) x/s 0x00007ffd1c388bcd
0x7ffd1c388bcd: "XDG_VTNR=2"
(gdb) x/s 0x00007ffd1c388bd8
0x7ffd1c388bd8: "XDG_SESSION_ID=c1"
Corregir, cadenas. Y también encontramos nuestro shellcode. Solo necesitamos imprimirlo de una manera diferente para obtener los 0x41:
(gdb) x/20wx 0x00007ffd1c388ba3
0x7ffd1c388ba3: 0x505f444c 0x4f4c4552 0x413d4441 0x41414141
0x7ffd1c388bb3: 0x41414141 0x41414141 0x41414141 0x41414141
0x7ffd1c388bc3: 0x41414141 0x41414141 0x44580041 0x54565f47
0x7ffd1c388bd3: 0x323d524e 0x47445800 0x5345535f 0x4e4f4953
0x7ffd1c388be3: 0x3d44495f 0x54003163 0x3d4d5245 0x74767872
Nota importante : ¡Esto no tiene nada que ver con LD_PRELOAD
! El programa comienza como:
$ LD_PRELOAD='perl -e 'print "A"x30'' less pp.c
Se podría hacer bastante con:
$ YAY='perl -e 'print "A"x30'' less pp.c
Y lograríamos el mismo resultado. Pero creo firmemente que esto es lo que realmente están viendo en esa publicación del blog.
* No tengo nada que ejecute Ubuntu 3.0.0-12-generic, pero supongo que su ld
era más permisivo y permitía que se cargaran objetos que no eran ELF. Confío en que mi Arch 4.7 simplemente rechace el objeto en LD_PRELOAD
si no es un ELF adecuado y no tiene indicadores PIC.