DEV Community

Lavínia Rodriguês
Lavínia Rodriguês

Posted on

Deciphering MalwareTech's Static Analysis Challenges [shellcode1]

Up until recently, I'd never tried the "Beginner Reverse Engineering Challenges" from MalwareTech as part of my studies, even after knowing their existence for quite some time. I don't have a exact reason, I just hadn't tried them before.

But today I began solving those challenges for pure fun, here is my solution to the shellcode1 challenge.

What we already know?

The challenge description reads...

Position independent code (AKA Shellcode) is assembly code which can simply be copied to a memory location and run. Due to the lack of need for complex loading & initialization, it is popular for many tasks such as code injection. These challenges are designed to test your ability to reverse engineer malware shellcode.

What we can deduce from this is that the provided binary will, obviously, run a shellcode. For a shellcode to be run, it needs to be mapped to some area in memory where it can be executed from.

What this means is that the challenge provided binary needs to first allocate some space somewhere in memory, copy the position independent code to such space, and then pass execution to it.

As the challenge is windows focused, I can infer it probably uses VirtualAlloc for memory allocation.

Well, we shall look into the code.

Finding the shellcode

For this challenge, I used radare2.

I began by disassembling the binary's entrypoint, which doesn't seem to be a stub to a main function. So I looked out for a call to VirtualAlloc followed by a call to memcpy, and sure here it is!

Piece from the disassembly where a call to VirtualAlloc follows a call to memcpy

We know that memcpy should copy the shellcode from somewhere in the binary to the allocated memory. We can see in the code that it copies 13 (0xd) bytes from address 0x404068 to the new memory space.

Piece from the disassembly showing VirtualAlloc's returned address being passed as one of the three arguments to a call to memcpy

0x404068 should be where our shellcode is stored in the binary, and it is 13 bytes long!

Let's dump it...

Disassembly of the shellcode

Analyzing the shellcode

It begins by loading an address from esi into edi.

0x00404068      8b3e           mov edi, dword [esi]
Enter fullscreen mode Exit fullscreen mode

Next, it loads some value into ecx.

0x0040406a      8b4e04         mov ecx, dword [esi + 4]
Enter fullscreen mode Exit fullscreen mode

The ecx register is commonly used as a loop counter, which the next two instructions effectively do.

0x0040406d      c0440fff05     rol byte [edi + ecx - 1], 5
0x00404072      e2f9           loop 0x40406d
0x00404074      c3             ret
Enter fullscreen mode Exit fullscreen mode

The loop 0x40406d instruction loops the execution to 0x40406d every time it is hit, decrementing the ecx register. When ecx becomes 0, it stops looping back and passes execution to the next instruction (ret) which exits off the shellcode.

rol is the bitwise left-rotation operation. Here, it left-rotates the byte at address pointed by edi + ecx - 1, 5 times.

As rol instruction loops and the ecx register decrements, the given string is iterated backwards, left-rotating every byte on it 5 times in a roll.

We can infer from this is that the encoded flag string is stored in the address pointed by edi, which is set before the shellcode being called, and all (or some) bytes from it needs to be left-rotated for decoding.

Decoding the flag

By looking back at the entrypoint function, we see that edi is set to 0x404040 right before the execution being passed to the shellcode.

Piece of disassembly showing the execution being passed to the shellcode

Piece of disassembly showing var_4h being set to 0x404040, which later is used as a value to edi

So, the encoded flag should be stored at 0x404040! Dumping it...

Dumped encoded flag

Here it is! I copied the bytes from 0x404040 to 0x404066 (where a 0x00 byte appears, which indicates a string's end).

Finally, here's a python script I made for decoding it.

#!/usr/bin/env python3

def rotate_left(byte, n):
    return ((byte << n) | (byte >> (8 - n))) & 0xFF

ENCODED_FLAG = bytearray(b"\x32\x62\x0a\x3a\xdb\x9a\x42\x2a\x62\x62\x1a\x7a\x22\x2a\x69\x4a\x9a\x72\xa2\x69\x52\xaa\x9a\xa2\x69\x32\x7a\x92\x69\x2a\xc2\x82\x62\x7a\x4a\xa2\x9a\xeb\x00")

decoded_flag = bytearray(b"X" * len(ENCODED_FLAG)) # the letter "X" is a placeholder

for i in range(0, len(ENCODED_FLAG)):
    decoded_flag[i] = rotate_left(ENCODED_FLAG[i], 5)

print(str(decoded_flag, "ascii"))
Enter fullscreen mode Exit fullscreen mode

And then we get our flag!

Decoded flag

Yaaayyyyy!!

Top comments (2)

Collapse
 
cherryramatis profile image
Cherry Ramatis

Quite fun to follow the debugging processes within the assembly, i'm a noob to everything security so it was really useful for learning some concepts! Thanks for sharing

Collapse
 
laviniasec profile image
Lavínia Rodriguês

I'm happy you liked it! :)