Introduction

Hey everyone! Today, we’re going to shift gears a little bit. Instead of writing our own assembly language, we’re instead going to analyze the work of someone else. This is pretty interesting to me because it actually highlighted a payload that I didn’t know about, which we’ll be discussing today.

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

Awesome. So first off, this isn’t going to be the complete assignment 5, instead, this is going to first discuss the first of three shellcode samples generated using msfvenom for x86 Linux. I’ll be creating more articles to discuss the other two payloads. Otherwise, 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 the one which seemed like it’d be interesting — the linux/x86/shell_find_tag payload by Skape (same one that we encountered during our egg hunter work!).

If we take a look at what options we have, we see there are a fair number of them:

Wow! That’s a fair number of them. Interestingly though, all of them are advanced options, so they’re going to be outside of scope for this discussion.

As a result, we’ll dive right into analyzing the payload.

Libemu

First, we’ll take a look at the payload with libemu. Libemu, if you aren’t familiar with it, is a library used to emulate things like raw shellcode and provide you with information about what it’s doing. In our case, we’re using it’s sctest binary with a few options. Specifically, we want to use it’s verbose output (-vvv), read the payload from stdin (-S) and perform 10000 steps (-s 10000) worth of iteration, if it’s available. This makes sure that we’ve fully processed the shellcode. We can do this with:

Oddly, unlike many times, while we got a large amount of output from sctest, it doesn’t seem to output any pseudocode for this payload. So let’s see if we can get a graphical representation of what’s happening instead.

This will generate our payload, pass it to sctest which will process it an generate a dot file. The dotfile is then converted using graphviz’s dot binary into a PNG which we can view. And when we do this, we see:

So it seems that libemu / sctest was able to read it, just not decide what the pseudocode should have been. Somewhat confusingly, we also end up with different shellcode (though similar to the graph above) when we use ndisasm -u – (which is shorthand to state this is 32-bit code:

So the first thing that jumps out here are lines 00000013 and 00000015, since we can quickly see that this is a socketcall operation. We’ll want to look this up so that we can see what the function signature we’re looking at actually looks like. In this case, at 00000007, we see mov bh, 0xa. If we look this up, 0xa is the SYS_RECV socketcall. This means we’re building the following function signature:

recv(int sockfd, void *buf, size_t len, int flags)

First, we see xor ebx, ebx which is clearing out the EBX register. This gives us a starting register structure of:

Address

Instruction

EAX

EBX

ECX

EDX

ESI

00000000

xor ebx, ebx

Unknown

0x0000

Unknown

Unknown

Unknown

We then push EBX onto the stack which is the flags argument to SYS_RECV, and move a pointer to the dword zero into ESI, storing the pointer for us. Our registers now look like:

Address

Instruction

EAX

EBX

ECX

EDX

ESI

00000000

xor ebx, ebx

Unknown

0x0000

Unknown

Unknown

Unknown

00000002

push ebx

Unknown

0x0000

Unknown

Unknown

Unknown

00000003

mov esi, esp

Unknown

0x0000

Unknown

Unknown

0xffffd16c

We then push 0x40 (64 decimal) onto the stack as our size argument, meaning that we’re going to recv 64 bytes of data, and then move 0xa (10 decimal) into bh (the high portion of EBX, we’ll switch where this value is later).

Our registers now look like:

Address

Instruction

EAX

EBX

ECX

EDX

ESI

00000000

xor ebx, ebx

Unknown

0x0000

Unknown

Unknown

Unknown

00000002

push ebx

Unknown

0x0000

Unknown

Unknown

Unknown

00000003

mov esi, esp

Unknown

0x0000

Unknown

Unknown

0xffffd16c

00000005

push byte +0x40

Unknown

0x0000

Unknown

Unknown

0xffffd16c

00000007

mov bh,0xa

Unknown

0x0a00

Unknown

Unknown

0xffffd16c

Cool, we have 0xa in bh now. We then push EBX onto the stack (0x0a00 hex / 2560 decimal) as our length argument, push ESI which is a pointer to the buffer we’re going to write to. Then we push EBX again, which pushes 0x0a00 onto the stack. At this point, our stack looks like this:

With our function arguments on the stack, we move a pointer to our arguments into ECX, exchange BH and BL (moving 0x0a into bl) and increment the value of the socket file descriptor value (remember that [ecx] gets the value of the pointer stored in ECX). This changes 0x0a00 to 0x0a01. Note that this is our socket file descriptor. We then push 0x66 (socketcall system call) onto the stack and pop it into EAX. With that complete, we trigger our system call. Throughout this, our registers progress like so:

So first, we compare the value in ESI with a set of bytes. These bytes translate from 0x4f4f3547 to G5OO (switching the byte order from little endian to human readable). This is the TAG option from the advanced options section which is used to signify the connection. If we didn’t receive it, we jump back to 00000010 which is our inc [ebx] command (incrementing our socket file descriptor), and repeating the socketcall to recv. This is a jump not zero because 0 is true, 1 is false. Essentially we’re looping through the socket file descriptors until we find the connection that we want to execute. This repeats 00000010 through 0000001E until we find the socket.

When we do find the socket, we pop the socketfd into EDI (remember that we’ve been incrementing the file descriptor on the stack) we begin the process we used for our bind shell and reverse shells via dup2 to connect stdin, stdout and stderr to the socket. With this knowledge, we can comment the code like so:

Onto our last function call! We first push 0xb onto the stack and pop it into EAX. If you remember, 0xb is the execve system call. We then clear EDX using CDQ. 00000032–00000038 we push /bin//sh with a null terminator onto the stack (the command we want to execute). We put a pointer to the command string into EBX, push a null value, onto the stack via push edx, push a pointer to the byte array onto the stack, and move the pointer into ECX. We then call the function and execute our function giving us a shell on the existing socket connection.

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.