Controllo della memoria non inizializzata con LD_PRELOAD

3

Stavo cercando di controllare la memoria non inizializzata come fornita in questo post del blog . Dice che la memoria non inizializzata può essere inizializzata usando LD_PRELOAD .

Esempio:

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

e quindi eseguendo un programma di test.

LD_PRELOAD are actually loaded at runtime, ld.so copies the name of each library onto the stack prior to executing the program, and doesn’t clean up after itself. By specifying a very long LD_PRELOAD variable and executing a binary, a portion of the stack will be overwritten with part of the LD_PRELOAD variable during linking, and it will stay that way once execution of the program begins, even on setuid binaries, where the library itself is not loaded.

Posso ottenere i risultati in% Ubuntu basato su 3.0.0-12-generic ma non in 4.6.0-kali1-686-pae .

Sono state introdotte nuove implementazioni di sicurezza?

Risultati non osservati 4.6.0-kali1-686-pae.

Indica se sono necessarie ulteriori informazioni per questa domanda? : -)

    
posta sourav punoriyar 29.09.2016 - 21:38
fonte

1 risposta

3

Questo non è il modo in cui LD_PRELOAD è pensato per funzionare *. E sto davvero lottando per scoprire come potrebbe mai funzionare. Quando eseguo quanto segue sull'ultimo kernel di linux (4.7), ultimo glibc (2.27) e ultimo ld (binutils 2.27) ottengo il seguente:

$ 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

Quindi sì, ld si lamenta semplicemente che l'oggetto (qui 0x00000) non è un ELF. Questo deriva da elf/rtld.c in glibc , qui:

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;
}

do_preload viene chiamato per ogni oggetto in LD_PRELOAD (separato da spazio o due punti). Ora Ubuntu 3.0.0 è stato rilasciato intorno a luglio 2011, in quel momento l'ultimo glibc era 2.15; Ho esaminato do_preload in glibc-2.15 e ho trovato lo stesso codice esatto.

Ora, forse i ragazzi ubuntu hanno usato un vecchio glibc, diciamo glibc-2.11 (novembre 2009, e questo lo spinge lontano). Se guardi quel codice, vedi che do_preload è sempre lo stesso. Quel codice è stato aggiunto nel 1998 e non è mai cambiato.

L'unica cosa che mi viene in mente sono le funzionalità aggiunte da Ubuntu al momento (ma questo è piuttosto discutibile). Non dovrebbe avere nulla a che fare con il kernel di Linux poiché è il lavoro del linker per aggiungere gli oggetti condivisi corretti (nel caso in cui, sono andato a uno di quegli indicizzatori di codice per il codice del kernel e controllato tutti i kernel 3.xe 4.x E, come previsto, LD_PRELOAD non è utilizzato da nessuna parte).

L'unica cosa che posso fare è discutere su come utilizzare LD_PRELOAD dovrebbe . E come ottenere un effetto simile prendendo in considerazione il precedente estratto da glibc . La prima opzione per ottenere che lo shellcode in memoria usi correttamente LD_PRELOAD (ma questo è molto diverso da quello che fanno in quel post del blog), la seconda opzione usa l'ambiente (che credo sia quello che vedono nel post del blog e fraintendendolo come oggetto condiviso caricato).

Opzione 1: utilizza un oggetto condiviso reale in LD_PRELOAD

L'attuale ld controllerà se l'oggetto puntato da LD_PRELOAD è un ELF, cioè che inizia con \x7fELF . Un modo migliore per ottenere il caricamento di un codice specifico nella memoria di un programma è creare un ELF da solo e quindi puntarlo 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]

Il bit importante che /proc/<PID>/maps ci dice è la posizione di memoria dell'oggetto, la stringa "AAAAA ... AAAA" dovrebbe essere da qualche parte lì dentro:

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

Ora possiamo usare GDB per controllarlo (nota che sto ancora usando il PID che ho ricevuto da jobs -l ):

# gdb -q -p 15476
...
warning: Could not load shared library symbols for ./pp.so
...

Tale riga appare se GDB è in esecuzione in una directory di lavoro diversa da quella in cui è stato avviato il processo. Ma va bene, stiamo cercando la memoria, non i simboli.

(gdb) p 0x7f03291c4000 - 0x7f0328fc0000
$1 = 2113536
(gdb) x/2113536x 0x7f0328fc0000

(Ho preso i numeri dalle mappe e ho usato GDB come calcolatrice)

Questo stamperà molte cose (tutta la memoria dell'oggetto condiviso che abbiamo creato, inclusi i bit aggiunti da ELF e PIC), ma mostrerà tutte le "A" che stiamo cercando:

...
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
...

Opzione 2: usa l'ambiente

Ora, questo non ha bisogno che la variabile di ambiente sia chiamata LD_PRELOAD . La variabile può essere qualsiasi cosa. Iniziamo aggiungendo il nostro shellcode ("AAAA ...") a LD_PRELOAD come hanno fatto nel post 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

Abbiamo ricevuto l'avviso, lo ignoreremo e collegheremo 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

E cerca la variabile environ ( man 3 environ ). Ne abbiamo due, questa è una particolarità del kernel 4. environ è un char** quindi guardiamo alla ricerca di quelle stringhe:

(gdb) x/3gx 0x00007fb7bd5bbf18
0x7fb7bd5bbf18 <environ>:   0x00007ffd1c3884a0  0x0000000000000000
0x7fb7bd5bbf28 <buflen.8581>:   0x0000000000000000
(gdb) x/3gx 0x00007fb7bdcc30c0
0x7fb7bdcc30c0 <environ>:   0x00007ffd1c3884a0  0x0000000000000000
0x7fb7bdcc30d0: 0x0000000000000000

Entrambi i simboli environ puntano allo stesso posto in memoria (ovvero 0x00007ffd1c3884a0 ). Vediamo cosa c'è 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

Scommetto che quelle sono stringhe, vediamo:

(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"

Corretti, archi. E abbiamo anche trovato il nostro codice shell. Abbiamo solo bisogno di stamparlo in un modo diverso per ottenere il 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 : questo non ha nulla a che fare con LD_PRELOAD ! Il programma inizia come:

$ LD_PRELOAD='perl -e 'print "A"x30'' less pp.c

Potrebbe essere fatto praticamente con:

$ YAY='perl -e 'print "A"x30'' less pp.c

E otterremmo lo stesso risultato. Ma credo fermamente che questo sia quello che vedono effettivamente in quel post del blog.

* Non ho nulla che usi Ubuntu 3.0.0-12-generic, ma suppongo che il suo ld sia più permissivo e permetta il caricamento di oggetti non ELF. Sono certo che il mio Arch 4.7 rifiuta semplicemente l'oggetto in LD_PRELOAD se non è un ELF appropriato e non ha indicatori PIC.

    
risposta data 29.09.2016 - 23:06
fonte

Leggi altre domande sui tag