Paolo PeregoFollowSpecialista di sicurezza applicativa e certificato OSCE e OSCP, amo spaccare e ricostruire il codice in maniera sicura. Sono cintura nera di taekwon-do, marito e papà. Ranger Caotico Neutrale, scrivo su @codiceinsicuro.
Assignment #1: Create a bind shellcode
4535
parole - Lo leggerai in 25 minuti
The first assignment for SLAE certification is to create a standard shell TCP
bind shellcode in assembler language. The shellcode will bind on a given port,
waiting for clients and spawn a shell on the incoming connection.
The binding port should be easily configurable.
The assignment was written on an Ubuntu Linux 18.04, with a Linux kernel 4.15
version.
The POC
To start, I created a very simple PoC code written in C to solve the
assignment. As you can see it’s a standard TCP mono process server performing
some very basic stuff:
opens a socket
binds the socket on a given port
connects the socket
listens for incoming connections
accepts connections.
When a client connect on the shellcode port, the server redirects its standard
output, standard input and standard error on the socket descriptor and then
spawn the shell using execve() system call.
Please consider that when execve() it has been executed, the code in the
process text segment is substituted with the shell one, so there is no need for
an exit() call at the end of the PoC.
Please consider also that I removed all error checking code, in order to have
the shellcode as small as possible.
PoC analysis
The shellcode has very few system calls to implement in our assembler code.
Before Linux kernel 4.3, every system calls in the network programming domain,
was multiplexed by socketcall() system call.
However, socketcall() was not available on every architecture, there is no such
as call in x86-64 or ARM processor where single system calls are available
instead.
Newer Linux kernel versions, for x86-32 architecture, introduced also separate
system calls to facilitates the creation of seccomp(2) filters that filter
sockets system calls and to have some performance boost.
Additionally, during system call opcode mapping, I’ve discovered that accept()
is not a real call itself, instead, it is a wrapper to accept4() call with the
latest parameter, flag, set to 0.
socket()
The socket() system call it is used to create a communication endpoint, mapped
as a file descriptor accordingly to the Unix definition that everything is a
file.
Since we want to create a reliable TCP socket, we will set the socket type to
SOCK_STREAM and the domain to AF_INET, to support IPv4 protocols.
The last parameter, protocol, let us specify different protocols (if any)
available for the defined double <domain, type>. Setting it to 0, it will be up
to the Operating System to choose the default one.
Most of other system calls will have the socket descriptor as the first argument.
So it can be a wise decision to save the socket return value into EBX register,
so we don’t need to use the stack to retrieve everytime that value.
bind()
The opened socket will be bound to TCP port 4444 as default value, 0x5c11. The
system call bind(), as the second parameter, has a pointer to a (struct sockaddr)
data structure. In case of TCP communications, the structure to be used is
(struct sockaddr_in) that it is equal in size but with some fields useful for
TCP/IP sockets.
As we can see from the following code snippet, we say again the socket family
is an Internet IPv4 socket and that the process must bind itself on every
available IP address, e.g. 0.0.0.0 and the port should be 4444, written in
network byte order.
Intel X86 processors work in little-endian order, so with the least significant
bytes on the right of the registers, meanwhile network byte order stores values
with the least significant bytes on the left. htons() makes this translation
for us in C. In assembler, we must do this on our own.
Please note that, in order to have the port number easily configurable, we
created a python script changing the assembly value, managing endianess on an
higher level.
The assembly code loading bind parameters and invoking system call is the
following.
In this code, for safety reason I initialized EAX back to 0 however, this is
useless. EAX would be always zero in every calls, except the first socket() and
this bind(), if the previous one complete successfully. As a further
optimization, since there is no error check, we can strip down every ‘xor eax,
eax’ from all calls later this bind.
However, to obtain a clear and understandable code, I decided to let the init
to 0 code back.
listen()
After binding a TCP socket on a given port, we must call listen() to say the
operating system, that descriptor will be used to accept incoming connections.
The listen call, along with the socket descript as the first argument, accepts an
integer parameter saying how many connections the socket will handle as its
backlog. In our case, the backlog is zero.
accept()
Handling accept was a little trickier. The original PoC code, was the following:
On the listening socket descriptor, we will call accept() call, having the
process to block itself waiting for incoming connections.
The call prototype is the following, where addr and addrlen are pointers to
data structures storing information about our peer. Since we don’t care about
managing incoming connection IP address or TCP port, we pass accept NULL
pointers.
However, there is no accept() system call defined in Linux Kernel. There is
instead, an accept4() system call, with the same arguments and with a fourth
parameter, an integer that can be used to specify two different options for the
socket (you will have to OR them together if you want to set both):
SOCK_NOBLOCK: saying the process must not blocking itself waiting for
connections;
SOCK_CLOEXEC: saying the socket descriptor must be closed when the process
dies.
Since we do want the socket to be blocking and since the subsequent execve()
call will overwrite our process text segment it will be useless to say we want
to close that descriptor. When execve() call will terminate, the process will
die and the operating system will free all allocated resources.
If flags is set to 0, as in our case and as manual page, the accept4 call is
the same as the accept, so our PoC code will result in:
In the source code preamble, right before including socket.h, we have to define
_GNU_SOURCE macro to say the compiler we are on Linux and we want to enable
accept4() call. Please note that on other operating systems there is no
accept4(), so this assembler code must be changed accordingly if we want to
port on other systems.
In assembly, everything is turned in the following code. EBX is still storing
socket descriptor, so there is no need to touch it.
ECX is set to 0 since the previous bind, so we push it on the stack in order to
have a NULL area and we pass the call the pointer on that area, pushing the ESP
register on ECX and EDX.
ESI register is used to handle the fourth parameter and it is set to 0.
accept() call returns the socket descriptor resulting of the newly accepted
communication. Since the original socket descriptor is not going to be used
anymore, we save on EBX register the new descriptor and we move forward.
dup()
Before giving the control to execve() we must redirect standard input, output,
and error to the newly accepted socket descriptor. This way, we will create a
bi-directional data flow mechanism to the socket and to the execve().
We will use the dup2() system call to achieve this. Its task is to duplicate
the descriptor specified as then second argument to the one passed as the first
argument.
Nothing complicated here. I just use a loop in order not to duplicate a lot of
code and also to practice me with jump instructions.
And the final call: execve()
Ok, if everything was set correctly we have a listening socket and our process
has its standard input, output and error redirected on that socket. It’s now
time to spawn a shell.
The shell we’re going to invoke using execve() system call will inherit the
duplicated descriptors, so any flow will be on the accepted socket.
Our PoC here is very simple. execve() call has three parameters:
The first parameter is the executable to be executed, the second parameter is
the pointer of the parameter list and the third parameter is a pointer to the
list of the environmental variables to be used by our executable.
In our case, we don’t need to pass command line argument or environmental
variables to our shell, therefore, we can easily pass NULL pointers.
In assembler everythins is translated as:
My helper programs
The compile script shell:
The script to dump the shellcode from the compiled binary:
The assembly code
Putting all pieces together, this is the first assignment solution in assembler.
After having compiled the assembler source file with compile.sh script shell, I used scdump.sh script to dump the shellcode.
I added this shellcode into a C program, trying to execute it in order to check the payload is correct.
Please note that compile.sh script uses -fno-stack-protector -z execstack flags
in order to disable stack protection mechanisms in Linux Operating System.
The configurator
Last bits. Our shellcode implements a bind shell code on port 4444 (\xc9\x51 in
hexadecimal and network byte order), which is hardcoded.
The assignment asked the target port to be easily configurable, that’s why I
created a python script to do this.
Using this python script is very easy. Just pass the required port number as
the parameter and it will give the resulting shellcode for us. The script
checks for port number to be in between 1024 and 65535 and it deals with
network byte order and hexadecimal encoding.
Code in action
After completed the assembler, it’s time to run. I compiled the code using the
compile.sh helper and run it. As you can see from the following video the 4444
port is opened and a shell was successfully spawned when connecting using
netcat tool.
Using scdump.sh helper, I extracted the shellcode from the binary tool and
inserted in the shellcode.c helper program.
As you can see from the video, after compiling the C code, disabling stack
protection and run it, the 4444 TCP port it has been opened and a shell
successfully spawned when connecting to it.
Now it’s time to change the PORT. For such a reason, I’m going to use the
python script providing a different port number, 5555 in this case. I pasted
the obtained shellcode into the C launcher, compile it, run it and as you can
see everything works as expected and a shell is spawned when connecting to port 5555.
SLAE Exam Statement
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Se questo post ti è piaciuto, sono abbastanza sicuro che troverai questi contenuti altrettanto interessanti. Buona lettura.
Episodio 32: Quando l'EDR fa crock
Introduzione
Ciao caro lettore. Ero come al solito in ritardo nella creazione di questo
numero della newsletter di cybersecurity più aperiodica dell’universo, quando
Internet si è rotta ancora.
Eh già… in questi mesi ho dato anima e corpo al canale YouTube ed ho trascurato un po’ il mio blog. Questa però è una delle cose che voglio prima raccontare qui, nella mia versione digitale di un Bullet Journal.