Controlando la memoria no inicializada con LD_PRELOAD

3

Estaba tratando de controlar la memoria no inicializada como se indica en esta publicación de blog . Dice que la memoria no inicializada podría inicializarse usando LD_PRELOAD .

ejemplo:

$ export LD_PRELOAD='perl -e 'print "A"x10000''

y luego ejecutando un programa de prueba.

  

LD_PRELOAD se carga realmente en tiempo de ejecución, ld.so copia el nombre de cada biblioteca en la pila antes de ejecutar el programa, y no se limpia después de sí mismo. Al especificar una variable LD_PRELOAD muy larga y ejecutar un binario, una parte de la pila se sobrescribirá con parte de la variable LD_PRELOAD durante la vinculación, y se mantendrá así una vez que comience la ejecución del programa, incluso en binarios de setuid, donde la biblioteca en sí no está cargado.

Puedo obtener los resultados en Ubuntu basado en 3.0.0-12-generic pero no en 4.6.0-kali1-686-pae .

¿Se han introducido recientemente implementaciones de seguridad?

Resultados no observados 4.6.0-kali1-686-pae.

Indique si se necesita más información para esta pregunta. :-)

    
pregunta sourav punoriyar 29.09.2016 - 21:38
fuente

1 respuesta

3

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.

    
respondido por el grochmal 29.09.2016 - 23:06
fuente

Lea otras preguntas en las etiquetas