10 Minute Read
Student ID: SLAE-1250

Assignment Five: Analysis of Shellcode Part Three - linux/x86/exec

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

All code can be found in: https://github.com/securitychops/security-tube-slae32

All work was tested on a 32bit version of Ubuntu 12.10

TLDR; - JMP short Final_Breakdown

Part three of assignment five of the SLAE has us performing the final analysis of the Linux/x86 shellcode payload from msfvenom: linux/x86/exec

Just like in part one and part two we must first generate our shellcode to perform our analysis on!

As with the previous blog entires in the SLAE series, we start off by generating our shellcode and immediately converting it to base64 for easy transport between computers.

The msfvenom command used was as follows:

msfvenom -p linux/x86/exec CMD=id | base64

Which produced the following output:


Generating the payload

after moving the base64 encoded contents into a file on the x86 Ubuntu VM I converted it back to raw with:

cat linux-x86-exec.b64 | base64 -d > linux-x86-exec

followed by the following command to start to analysis via ndisasm:

cat linux-x86-exec | ndisasm -u -
“-u” specifies x86 and “-“ specifies to use the contents piped into it from the cat operation

which produced the following disassembled output:

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  E803000000        call dword 0x20
0000001D  696400575389E1CD  imul esp,[eax+eax+0x57],dword 0xcde18953
00000025  80                db 0x80

Given how small this block of code is we are going to try something different this time and just work through each line at a time to see if we can’t get a handle on what this shellcode is doing!

ASM Code Block One

00000000  6A0B              push byte +0xb
00000002  58                pop eax

These two lines should look pretty familiar at this point as this is the value of the function to call being popped into the eax register, which is 0xB (decimal 11).

If we investigate what this value represents by referencing /usr/include/i386-linux-gnu/asm/unistd_32.h we will see that it is making a call to the execve function


which has a C function signature of:

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

There are several things we need to revisit about making a call to execve. The first is that the first parameter, filename, just needs to point to a memory address containing the value for the command to execute (/bin/sh). The second parameter is where the real magic is happening as we will need to point to a memory address that contains the following values: [/bin/sh, -c, id]. The third parameter in our case can simply be null since we won’t have any environment specific information to pass into the function.

Lets start by diving into the code and figure out how to make all of this happen!

The very next instruction we encounter is:

00000003  99                cdq

This instruction is a fairly interesting one in that it takes advantage of the sign bit that is already present in the eax register (0000 0000 0000 0000). cdq will take whatever the value of the sign bit is in the eax register and copy that bit into every single bit in the edx register, so really this is just a different way of performing a xor operation on edx!

The value stored in the edx register will end up being incredibly useful to us throughout the code block since this parameter will end up being null as we have no environmental settings to pass to the function, or the equivalent of an ‘\0’ that we can use over and over to push to the end of any string (char array) we need to null terminate…

00000004  52                push edx

Speak of the devil, the very next instruction has us pushing edx (‘\0’) onto the stack in order to null terminate the instruction that comes after it (since this is x86 / little endian)!

00000005  66682D63          push word 0x632d

This will push c- to the stack (or logically it will be -c) and since we have already pushed a null terminator what we really end up having is -c\0

00000009  89E7              mov edi,esp

The previous instruction may seem a little weird at first, but as we will eventually need to store the values currently in esp in ecx we are going to need to offload it to another register that we can later move to ecx since we are about to need to use the stack again for a bit…

0000000B  682F736800        push dword 0x68732f
00000010  682F62696E        push dword 0x6e69622f

Now that we have offloaded the previous value of the stack to edi we can now freely push “h/sh “ and “nib/” to the stack which will actually end up logically translating to: /bin/sh since we are dealing with x86 / little endian.

After this we see the following commands:

00000015  89E3              mov ebx,esp
00000017  52                push edx

This instruction will move esp (or the address pointing to /bin/sh) to the register ebx, which in our C code is parameter const char *filename!

After which it immediately pushes a null onto the stack in order to null terminate the string!

At this point in our code we have now accounted for both ebx and edx and our function call looks like the following:

 int execve("/bin/bash\0", char *const argv[], "\0");

Another thing to keep in mind as we keep moving forward is that the value of the register edi is also currently (logically) set to: -c\0”

Having said all of that it is at this point we again face several instructions that look a little suspect:

00000018  E803000000        call dword 0x20
0000001D  696400575389E1CD  imul esp,[eax+eax+0x57],dword 0xcde18953
00000025  80                db 0x80

If you were thinking these just didn’t look like they could be the correct instructions then you would be spot on, as we once again have some mangled instructions due to a string being in the middle of these commands. Our original command to execute was id which just so happens to translate to the hexadecimal characters 0x69, 0x64 and 0x00!

If we run the following command:

echo 696400 | xxd -r -p

it will indeed output:


This is particularly useful since the value of id was accessed via a call to the address of id which will cause the address of id to end up being stored on the stack!

After accounting for the hex values of 0x69, 0x64 and 0x00 we are left with the following hexadecimal values to re-translate into assembly code for analysis: 575389E1CD80

Feeding those values back into ndisasm with the following command:

echo -ne "\x57\x53\x89\xe1\xcd\x80" | ndisasm -u -

we get the following output:

00000000  57                push edi
00000001  53                push ebx
00000002  89E1              mov ecx,esp
00000004  CD80              int 0x80

These set of codes definitely look much better!

At this point we push edi, but why, what is in edi? Well, edi currently contains -c and by pushing it we are logically causing the stack to currently look like [-c, id] which is just one element away from being done!

After pushing edi we unsurprisingly push ebx which has the value /bin/bash.

Next up we move the address of the stack (esp) into ecx which means we now have [/bin/bash, -c, id] stored in ecx!

After the mov ecx,esp our function call now looks like:

 int execve("/bin/bash\0", "{/bin/bash, -c, id}", "\0");

At this point we are ready to make the call to execve, which is rather convenient since the very next instruction is int 0x80, which is the interrupt command which will indeed fire execve and execute the id command!


The linux/x86/exec shellcode from msfvenom performs the following steps:

  • Executes execve with the following function signature:
 int execve("/bin/bash\0", "{/bin/bash, -c, id}", "\0");

Just for completeness I have also included the final disassembled assembly code after accounting for id throwing off the disassembly:

push byte +0xb
pop eax
push edx
push word 0x632d
mov edi,esp
push dword 0x68732f
push dword 0x6e69622f
mov ebx,esp
push edx
call dword 0x20
push edi
push ebx
mov ecx,esp
int 0x80

Jonathan Crosby

growing my chops in cybersecurity
(all opinions are my own and not the views of my employer)