> For the complete documentation index, see [llms.txt](https://www.adroxz.foo/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.adroxz.foo/building-a-practical-homelab-adaptixc2-malware-development-and-elastic-edr.md).

# Building a Practical Homelab: AdaptixC2, Malware Development, and Elastic EDR

Build a homelab with 16GB RAM to practice Malware development, Initial access, phishing, persistence, and purple teaming

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FPV8ZJW7BFxW50xa2nU8Z%2Fimage.png?alt=media&amp;token=9da070a3-4def-46cd-95ab-bd4d54f753aa" alt=""><figcaption></figcaption></figure>

### Building a 2-VM Purple Team Homelab: C2, MalDev & Elastic EDR

In this guide, I will walk you through setting up a fully functional, isolated 2-VM Purple Team homelab using **AdaptixC2** and **Elastic Security**. I’ll also cover the exact issues I faced during the build and how to fix them, so you don’t have to spend days troubleshooting like I did.

### A Visual Summary

* Firstly this is what our homelab is going to look like, I made this for testing and learning maldev, initial access and phishing techniques. But, you may use it for whatever you want... but to use it for AD attacks you would also need another VM (windows server) to make a DC, and then connect the Windows 10 VM to its domain.

```
[ Windows 10 VM (TARGET)] (10.10.10.20)
       │
       │ (1) Beacons to 10.10.10.1:10443 
       │ (2) Checks in to 10.10.10.1:8220 
       │ (3) Ships logs to 10.10.10.1:9200 
       ▼
[ Windows Host (Dev+attacker machine) ] (10.10.10.1)
       │
       │ Netsh Portproxy intercepts and forwards 
       ▼
[ WSL2 (Server machine) ] (WSL IP)
       ├── Adaptix Server (port 8443)
       ├── Fleet Server   (port 8220)
       └── Elasticsearch  (port 9200)
```

### The Architecture

This lab is designed to run on a 16GB machine, with the target machine isolated and cut off from the internet. but still connected to our main machine and WSL environment, for simulating C2 callbacks and let elastic watch and log every move

* **Host Machine:** Windows 11 (16GB RAM)
* **Hypervisor:** VMware Workstation (using an isolated VMnet2 Host-Only network).
* **Target VM:** Windows 10 (4GB RAM) `10.10.10.20`
* **Attacker Infrastructure:** WSL2 Debian/Kali `WSL IP` (IP changes on every reboot).

### NETWORK BRIDGE

* **Adaptix C2 Management:** Port `4321`
* **Adaptix Payload Callback:** External `10443` -> Internal `8443`
* **Elastic Fleet Server:** Port `8220`
* **Elasticsearch Telemetry:** Port `9200`

#### REASON FOR EACH PORT (if you're confused)

1. **PORT 4321** - 10.10.10.1:4321 -> `WSL IP's:4321`
   * Adaptix C2 Management: Port `4321`,**\*\*\*\*** Though in the host machine we can connect it directly with the WSL's IP, but if we use another VM to launch our C2 client then, we can forward it to our host machine and make it forward it to WSL
2. **PORT 10443** - `10.10.10.1:10443` -> `WSL IP's:8443`
   * Portproxy catches the beacon callback and forwards it to the Adaptix server listening on WSL. (we're not using 8443, cos it was taken by WSL for some reason, idk you can check it on your side, cos basically the main thing is destination port so it doesnt matter which port we use to get it there
3. **PORT 8220**: `10.10.10.1:8220` -> `WSL'sIP:8220`
   * Elastic agent uses this port to enroll, get its assigned policies and get status updates, in the Fleet Docker container in WSL2 (I'll explain later what this is)
4. **PORT 9200** - `10.10.10.1.9200` -> `<WSL'sIP:9200`
   * 8220 port is used by fleet to only register the agent and setup it's policies, but the EDR uses 9200 port to actually transport the telemetry data

```powershell
netsh interface portproxy add v4tov4 listenport=4321 listenaddress=10.10.10.1 connectport=4321 connectaddress=<wsl address>
netsh interface portproxy add v4tov4 listenport=10443 listenaddress=10.10.10.1 connectport=8443 connectaddress=<wsl address>
netsh interface portproxy add v4tov4 listenport=8220 listenaddress=10.10.10.1 connectport=8220 connectaddress=<wsl address>
netsh interface portproxy add v4tov4 listenport=9200 listenaddress=10.10.10.1 connectport=9200 connectaddress=<wsl address>
```

***

### INSTALLING TARGET MACHINE (WIN10)

* First install the VM watching this video\
  <https://youtu.be/-85D8WIKaCc?si=vE7G5BUed_1t2fKb>
* then create a host only network in VMware named `VMnet2` with the subnet `10.10.10.0./24` using the following steps
* **Open VMware as Administrator:** Open VMware Workstation (or Player Pro) on your Windows Host. If you are using Player, you might need to run it as Administrator to edit network settings.
* **Open Virtual Network Editor:**
  * Go to the top menu and click **Edit** → **Virtual Network Editor**.
  * *(If you are on Windows and don't see "Change Settings", click the **Change Settings** button to grant Admin privileges).*
* **Add the Network:**
  * Click the **Add Network...** button at the bottom.
  * Select **VMnet2** from the dropdown list and click **OK**.
* **Configure VMnet2 (The Critical Settings):**
  * Select **VMnet2** in the list.
  * Set **Type** to **Host-only** (This is crucial—it means it will not bridge to your home router).
  * Look at the **Subnet IP** section. Change the IP to **`10.10.10.0`** and the mask to **`255.255.255.0`**.
  * Check the box: **"Connect a host virtual adapter to this network"**. (This is what assigns the `10.10.10.1` IP to your Windows Host).
  * **Uncheck** the box: **"Use local DHCP service to distribute IP addresses to VMs"**. (You want static IPs).
  * Make sure **"Bridged to an external network"** is **NOT** checked.
* **Apply and Save:**
  * Click **Apply** and then **OK**.

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2Fe6IMrtnEuS0zcmDcVS0H%2FPasted%20image%2020261030191617.png?alt=media&amp;token=c6937af5-ea7a-417f-8065-49d0c49e8cca" alt="" width="527"><figcaption></figcaption></figure>

this should automatically assign 10.10.10.1 to our main host machine, but still if it didn't, do it using

* win+r -> `ncpa.cpl` then open the VMNet2 and make it like this
* Right-click **VMware Network Adapter VMnet2** → **Properties**.
* Double-click **Internet Protocol Version 4 (TCP/IPv4)**.
* Select **Use the following IP address** and type in:
  * IP: `10.10.10.1`
  * Subnet mask: `255.255.255.0`
  * Gateway: *Leave blank* (it doesn't need one because it's an isolated network).

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FNUxRt4i2qwqDSWVKiy3r%2FPasted%20image%2020261030201604.png?alt=media&amp;token=5696cff9-3751-42a8-9554-d2895b82106d" alt="" width="379"><figcaption></figcaption></figure>

#### SETTING UP THE NETWORK IN THE TARGET MACHINE

#### 1. VMware Settings

Before even turning on the VM, you had to tell VMware to plug the VM into the correct virtual switch.

* go to **VM Settings** → **Network Adapter**.
* Change it from **NAT** (or Bridged) to **Custom**.
* Select **VMnet2** from the dropdown.
* Check the box for **"Connect at power on"**.

#### 2. INSIDE Windows 10

Once the VM was plugged into the VMnet2 switch, boot it up and configure the IP address manually:

1. Press `Win + R`, typed `ncpa.cpl`, and hit Enter.
2. Right-click the **Ethernet0** adapter and click **Properties**.
3. Double-click **Internet Protocol Version 4 (TCP/IPv4)**.
4. Select **Use the following IP address** and enter:
   * **IP Address:** `10.10.10.20`
   * **Subnet Mask:** `255.255.255.0`
   * **Default Gateway:** `10.10.10.1`
5. Select **Use the following DNS server addresses** and enter:
   * **Preferred DNS server:** `10.10.10.1` (Setting this prevents Windows from trying to reach the internet to resolve DNS).\
     Like this

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2F1mjm4cIsj3GfIvLyMeOh%2FPasted%20image%2020261031002543.png?alt=media&amp;token=ea6fb701-3078-479a-ae1e-b47f733e8862" alt="" width="302"><figcaption></figcaption></figure>

### SETTING UP ADAPTIX C2

* I wont explain how to install Adaptix, they have that info in the docs.
* but yea, after building go to adaptix server, run `make server-ext` to build the server and extenders

**Why i used 10443 instead of 8443, so if you face the same problem yk why**\
**\*The Problem I Faced:** So I set up the port proxy for `8443`, but the VM got `TcpTestSucceeded : False`.\*\
**\*The Cause:** WSL2 has a nasty habit of "stealing" ports. When you start a listener in WSL2, it binds to `[::1]:8443` on the Windows host (localhost). If you try to bind your `netsh` rule to `10.10.10.1:8443` at the same time, the `netsh` rule silently fails due to a port collision.\*\
**\*The Fix:** Never use the same external port as the internal WSL port. Forward external port `10443` to internal port `8443`.\*

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FoebY2DY9AIR2tpJ2hfd1%2FPasted%20image%2020261030200917.png?alt=media&amp;token=28014d26-c86c-4edf-a77e-d4ceefff8375" alt="" width="563"><figcaption></figcaption></figure>

Now Open the adaptix client, and connect on to the server using WSL's server.\
Generate an implant and test it on the target machine (with defender off) just to check connectivity, and you should get a session like this\
If not, *you probably made a mistake in networking part*

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FxfWpvOhU9SqobSOk4fuP%2FPasted%20image%2020261031004020.png?alt=media&amp;token=67ad13b0-e62a-43e0-bd20-026c9107b7a8" alt=""><figcaption></figcaption></figure>

### THE EDR

*Firstly because I had only 16GB RAM, i faced this issue where Kibana was taking forever to boot, and kept throwing `429 Too Many Requests` error (it wont show this while booting, if the boot time takes more than 400seconds, check the logs), so the problem was that the machine learning model `ELSER` takes too much RAM, and space. SO MAKE SURE YOU HAVE SETUP WSL IN A DRIVE WITH ENOUGH SPACE AND INCREASED DOCKER'S MEMORY LIMIT TO 7GB*\
To set memory limit to 7 GB on the docker, do this

* edit `elastic-stack-fleet-docker-compose/.env`, change `MEM_LIMIT`'s value to be `7516192768`, and put this line under `ES_PORT` `ES_JAVA_OPTS=-Xms3g -Xmx3g`

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FfH8pkNqsP9qiuRvFFkvo%2FPasted%20image%2020261030202441.png?alt=media&amp;token=a12813d3-eef2-41f5-aeb5-3c68aefe6c5d" alt="" width="563"><figcaption></figcaption></figure>

* (ALSO CHANGE FLEET URL AND ES\_URL TO BE THE SAME AS YOUR MAIN HOST MACHINE, because this is where you elastic agent will try to contact the server, and since we have setup the port forwarding, it'll forward the data to our elastic server)\
  *also adding this line in docker-compose.yml, right under es01 environement to stop the ML model from crashing*

```
- xpack.ml.use_auto_machine_memory_percent=true
```

because ElasticSearch put 30% cap on the memory to use from the allocated RAM, and this specific line removes that cap

**ANOTHER BIG PROBLEM I FACED WAS**

* Kibana would boot fine, but right after booting there was health checkup loading, and during that it kept crashing by throwing the error `Error dependency kib01 failed to start`, which also made fleet stop, so to solve this we'll bypass the healthcheck, and forcing the fleet container to start manually `docker compose up -d --no-deps fleet`

This command starts all the containers while forcefully ignoring the dependencies

```powershell
docker compose up -d --no-deps
```

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2F5e68HbD4sKs0QscThCKO%2FPasted%20image%2020261031001050.png?alt=media&amp;token=60743f33-58db-4875-9b68-014a6314d011" alt=""><figcaption></figcaption></figure>

if this waiting time keeps increasing (and eventually crash), dont worry, it will still work just fine. the dashboard is ready to use now at\
<https://localhost:5601\\>
(the default username:password are elastic:changeme)

### ENROLLING THE AGENT

Now comes one of my favorite part, enrolling our windows machine in elastic EDR

**Step 1) Install the Elastic EDR integration**

Go to Security > manage > endpoint

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2Fgz0xNLY4VAfn2pR151qV%2FPasted%20image%2020261031001717.png?alt=media&amp;token=a82bdfdf-da82-4e77-befc-993647bad936" alt="" width="520"><figcaption></figcaption></figure>

Click on Add Elastic defend, this will add the intergration, it will ask for some basic information, fill it out. (choose `Complete EDR (Endpoint Detection & Response)`, and the default for the policy). Then it will give you steps start the fleet server, and enroll your agent. just follow through (also copy the enrollment token it gives, that will be used to enroll)

**STEP 2) Come on the Windows 10 VM (target) now**

Download the latest elastic agent (same as server), in its folder run this command

```
.\elastic-agent.exe install --url=https://10.10.10.1:8220 --enrollment-token=<YOUR_NEW_ENROLLMENT_TOKEN> --insecure
```

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FrnjetU72teOqUnQCvxJT%2FPasted%20image%2020261031005051.png?alt=media&amp;token=79d37fbd-ea73-43a5-9dd7-d2ff4d7f83d2" alt="" width="527"><figcaption></figcaption></figure>

Now your Lab is done, to test we'll first use elastic in observation only mode. (useful to get telemetry and improve the payload)\
I'll demonstrate a PoC for getting session at Adaptix using Mapped injetion+Thread hijacking+Dynamic API resolution+AES encryption, also this bypassed Defender but not elastic EDR, thats why I've put elastic at observation mode to demonstrate how it monitors ETW and other telemetry.\
**To put it in observation mode click the three dots next to our endpoint name, and then "View agent policy**

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FRkhhpMz1wgfuHtlMTKbt%2FPasted%20image%2020261031005637.png?alt=media&amp;token=23f8c218-ab73-4c9e-baaf-11ee56075cf0" alt="" width="563"><figcaption></figcaption></figure>

then choose whatever your agent name was, and in every security settings change prevent to detect.\
This helps you threat model your malwares and payloads, to see what telemetry it is generating and how to improve them

* Now after excuting our malware PoC, we can see the session it got us in adaptix (in notepad, since i chose it to hijack)

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FwmI2eaPsiuw4YzWVqYEQ%2FPasted%20image%2020261031010009.png?alt=media&amp;token=3dab2ce8-d01a-4835-9a05-8fd655bfd472" alt=""><figcaption></figcaption></figure>

* **NOW FOR THE MOST INTERESTING PART, CHECKING OUR TELEMETRY IN ELASTIC DASHBOARD**\
  We'll go to `Security > Detections > Alert`\
  and as we can see our malware was actually very loud

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2F70TWBFsPc5v1a7uSOWYE%2FPasted%20image%2020261031010305.png?alt=media&amp;token=98b4d40c-88d7-4f44-90d9-d823fa483463" alt=""><figcaption></figcaption></figure>

* Bonus: As shown in the Kibana alerts table above, even though our custom loader (AES + Dynamic API Resolution + Mapped Injection + Thread Hijacking) bypassed Windows Defender and landed a session inside `notepad.exe`, Elastic immediately flagged the file event, remote thread manipulation, and unbacked memory execution.

With the Purple Team lab fully operational, **Part 2** of this series will dive straight into the C loader source code, dissect each of these Elastic detection rules and process trees in detail, and walk through how to engineer around them.

But still, for a teaser, looking at the alert timeline from bottom to top shows the exact stages Elastic flagged:

1. **Malware Detection Alert (File Event):** Fired as soon as the loader binary hit the endpoint.
2. **Malicious Behavior Detection Alert (Process Event on `notepad.exe`):** Triggered when the loader mapped the section and hijacked the remote thread inside `notepad.exe`.
3. **Memory Threat Detection Alert (`notepad.exe`):** Flagged the unbacked executable memory region (`MEM_MAPPED`) where the Adaptix beacon was executing.

### REFERENCES

* <https://github.com/Adroxz1122/MappedInj> - PoC i used of (mapped injection loader)
* <https://www.adroxz.foo/mapping-injection-abusing-section-objects-for-remote-code-execution> - Resource to study mapped injection
* <https://www.adroxz.foo/thread-hijacking> - Resource to study Thread hijacking
