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.