Introduction

Hey everyone! Today, we’re going to wrap up our shellcode analysis work. Like last time, instead of writing our own assembly code, we’re instead going to analyze the work of someone else.

Requirements

Take at least 3 shellcode samples created using msfvenom for x86 Linux

Use GDB, ndisasm, and/or libemu to dissect the functionality of the shellcode

Present your analysis

As we said before, first off, this isn’t going to be the complete assignment 5, instead, this is going to discuss the final shellcode sample generated using msfvenom for x86 Linux. With that in mind though, we can definitely meet the other two goals.

Our shellcode

So to get started, I had to choose what shellcode I was going to analyze. After looking through msfvenom’s payloads, I found one which seemed like it’d be interesting — the linux/x86/adduser payload by Skape (of egghunter and shell_find_tag fame), vlad902, and spoonm.

I think this one will be interesting to review as I wanted to focus on topics which we didn’t explicitly cover in the SLAE course, such as reading adding a new user using NASM. I mean, does this task have a direct system call?

If we take a look at what options we have, we see there are three core options USER, PASS and SHELL (representing the default shell to spawn when logging in:

Looking at these, we do have a number of advanced options which we won’t be working with.

In our case, we’ll stick to the defaults which will simplify the setup required to analyze the shellcode.

Let’s dig into our shellcode though!

Libemu

We’ll start off by analyzing the payload in libemu, which provides the sctest binary for analyzing what a payload does. We’re not going to cover the options, as we previously did in the first shellcode analysis document. But the basics of it is that we’re enabling verbose mode, reading the payload to analyze from stdin, and iterating through up to 10000 steps. If we’re lucky, this will give us pseudocode to start our analysis with.

And, no pseudocode. This sucks, three times in a row without pseudocode. So be it. If we take a look at the execution graph, hopefully we’ll get a bit more. Realistically, I expect we’ll need to drop into ndisasm and walk through the assembly at a lower level to really analyze this.

First Function

So this looks pretty straight forward. We clear ECX by XOR’ing it with itself. We move the value 0x0 into EBX so now both EBX and ECX hold this value. We then pop 0x46 into EAX which is the sys_setreuid16 system call. This sets the real and effective user ID to 0 (root).

This is a bit bigger. We first push 0x5 onto the stack and pop it into EAX. This means we’re going to be referencing the SYS_OPEN system call. We then clear out ECX by XOR’ing it with itself, and pushing 0x0 onto the stack. This is going to be our null terminator.

If we work through the dword in reverse, we see 0x6374652f literally translates to cte/ or /etc if we fix the directionality of it. We then have 0x61702f2f which is literally ap//. Thus far we have /etc//pa. Lastly, we have 0x64777373 which reads as dwss. Giving us a full string of /etc/passwd with a null terminator on the stack.

We move a pointer to our string into EBX, increment ECX (which was 0x0, so now it’s 0x1) which is the O_WRONLY open flag. We then move 0x4 into ch giving ECX a value of 0x401 giving us the flag O_WRONLY|O_APPEND, and trigger our interrupt. This gives us the following commented NASM:

Author Kevin Kirsche

Kevin is a Principal Security Architect with Verizon. He holds the OSCP, OSWP, OSCE, and SLAE certifications. He is interested in learning more about building exploits and advanced penetration testing concepts.