Tech Blog

Hunting for rogue PowerShell profiles

During the earliest phases of an intrusion, attackers typically move to establish persistence on at least a subset of compromised systems. This might be to ensure that they can easily regain access to the victim environment, such as via a backdoor, or to keep other forms of malicious code running, such as a keystroke logger. At Tanium, we spend a lot of time trying to make it easier to detect rogue persistence mechanisms at enterprise scale. In fact, several of our prior blog posts have focused on this topic, including analysis of Windows Services, DLL Load Order Hijacking, and WMI Event Consumers.

Many Tanium users begin hunting for persistence mechanisms using the simple question, “Get Autorun Program Details”. This sensor collects and stacks metadata for dozens of persistence locations across an enterprise in seconds, allowing for easy filtering, search, and outlier analysis. The screenshot below shows an example of such output from more than 800 systems within a Tanium demo environment.

Hull1One persistence mechanism that is commonly overlooked by incident responders and their tools – including Microsoft’s popular “Autoruns” utility – are shell profile scripts. “Shell profiles” usually spur thought of Linux and Unix operating systems. However, for this post, we’re going to focus on the profile features provided by Windows PowerShell.

PowerShell Profiles 101

PowerShell profiles let users tailor their environments with their favorite modules, cmdlets, aliases, prompts, etc. in order to make their working environments more efficient. Given PowerShell’s broad capabilities, and its frequent use by privileged power-users, this combination presents many opportunities to hide and execute malicious code. This is not a theoretical technique; I have worked several incidents over the years where attackers abused PowerShell profiles.

Where are PowerShell profiles stored on a typical Windows system? One way to find out is to open a PowerShell prompt as an administrator on a system and type in “$profile”, as shown in the screen capture below. However, this only provides a single profile location for the current user.


To find additional profile locations, pipe the $profile command to Format-List as shown below:Hull3

I’ve used “Format-List * -Force” to show a more complete list of PowerShell profile locations, displayed in load order. The AllUsersAllHosts profile applies to all users and all shells (e.g. the PowerShell shell and the PowerShell_ISE); the AllUsersCurrentHost profile applies to all users, but only to the PowerShell shell; the CurrentUserAllHosts applies to all shells for the given user; and finally, CurrentUserCurrentHost applies to the given user’s PowerShell shell only. For additional detail on these default profiles, refer to:

Hunting for PowerShell Profiles with Tanium

An attacker could plant code in any profile script – so how can we quickly find malicious profiles at-scale? First, it’s important to note that these profiles do not exist by default – users have to explicitly create them (if not included in an organization’s build images or created through some other automation or systems management tools). You therefore, should not expect to see a large number of PowerShell profiles in your enterprise. For those that are present, Tanium can help you easily identify, compare, and analyze rogue profiles.

We’ll begin by using Tanium Index™, included as part of the platform’s Incident Response solution, to search across a Windows environment for profile scripts on disk. The screen shot below shows Tanium’s Question Builder interface to search for file names matching the pattern “*profile.ps1”.

Hull4Tanium Index permits searches of file metadata on disk in seconds, without incurring time or I/O intensive crawls through drives for each ad-hoc query. The screen capture below shows the results of running this search across a small lab environment consisting of four machines. I was surprised at the number of results:Hull5

There were more scripts ending in “profile.ps1” than what I knew about. Some of these appear to be example scripts rather than active user profiles. We can determine which hosts contain these files by asking a merged question that includes the Computer Name.


We can see from the MD5 hashes that we have six unique scripts. If we review Directory Paths and File Sizes, we can see we likely have some small variations among some of the profile scripts. During an investigation, I would first focus on profiles belonging to privileged accounts or to the system-wide profile scripts that run when any user opens a PowerShell prompt. I would deprioritize the profile scripts in the Examples paths and those in the Windows side-by-side directories because they should not execute during normal usage scenarios.

I’ve placed a red arrow next to three potential scripts of interest in the screen shot above: one on host “Win7”, one on “ad” and two on “muinat”.

How to proceed from here may be a matter of personal preference, but let’s assume an attacker would add code to an existing profile, rather than replace existing code which may break functionality. In such a scenario, an analyst might begin by acquiring and analyzing profiles that are larger than what’s common. Then, as necessary, one could expand the scope of analysis to include other outlier profile files – potentially using the timestamps recorded by Tanium index, or other metadata, as filtering criteria.

Our largest target profile script is on hostname “muinat” within the user directory for the “dhadmin” account. We’ll begin our triage analysis with Tanium Trace™. I used Trace to make a live connection to “muinat”. Before looking at activity by “dhadmin”, I ran filtered search for all processes executed by user account “Dave Hull”, in order to compare the activity generated by “PowerShell.exe” executing under each account’s context.


Double-clicking the highlighted instance of “PowerShell.exe” navigates to the Process Details screen, where we can review a timeline of all file system, registry, network, and process activity initiated by the selected parent process. Below is a screen shot of that process detail timeline:Hull8

The recorded evidence only showed the creation of Jump List artifacts – nothing unexpected or unusual. Let’s compare that timeline to what we see when “dhadmin” launched PowerShell:


The highlighted row showed an interesting artifact: when user “dhadmin” opened a PowerShell prompt, the process initiated a network connection to within seconds. At this point, we could use Trace’s file copy feature to retrieve the PowerShell profile for this user and review its contents.

The first time I encountered a PowerShell profile used for persistence, we initially missed the malicious code. The attacker made use of a simple, clever trick: they inserted a large number of tabs prior to their code, hiding it past the initial bounds of normal viewable window. To make matters worse, the version of PowerShell ISE included with PowerShell 2.0 does not consistently display horizontal scroll bars at the bottom of the screen when a file contains a long line. The screen shot below illustrates this behavior in ISE version 6.1.7600. (The version of ISE included with later versions of PowerShell no longer exhibit this bug.)


Would you guess that the code on line 261 begins in column 133? It does. Where’s the bottom scroll bar? If you scroll down one more click, it appears. Here’s what the line contains:


Our sample code calls Invoke-Expression to execute a script hosted on GitHub. In this example, we used a harmless script – but an attacker could hide and run any code.

Alternative Approaches to Persistence

Profile scripts are atypical persistence mechanisms because triggering the malicious code requires someone to logon and open a PowerShell prompt. This can make their execution unpredictable for an attacker, but likewise more difficult for an analyst to discover than traditional recurring autoruns.

If an attacker wants to use PowerShell profiles for persistence in a more predictable and reliable manner, they could create an autorun entry that simply calls “PowerShell.exe” with a hidden window in non-interactive mode, bypassing the local execution policy. This could be performed via any persistence mechanism, such as a recurring Scheduled Task or run key in the registry. In such a scenario, an analyst focusing on discovery of malicious autorun binaries might overlook the presence of an entry that runs the legitimate PowerShell interpreter without any overtly suspicious arguments.

As shown in the beginning of this blog post, Tanium’s ability to collect and compare autorun entries in real time is an effective tool to quickly identify outliers across the enterprise. Analysts can filter this data to only include references to PowerShell, and compare results from the resulting set of systems. In another example shown below, we’ve created a recurring scheduled task that executes PowerShell to ensure a malicious profile is loaded. Tanium’s “Get Scheduled Tasks” sensor readily identifies the anomalous task, shown in the last row of output.


As incident responders and their tools continue to mature and improve, attackers will migrate to less traditional tactics and techniques. Modifying shell startup scripts to do nefarious things is certainly not new, but it is less common than many other persistence mechanisms. Good analysts with good tools can find them easily and quickly.

Like what you see? Click here and sign up to receive the latest Tanium news and learn about our upcoming events.

About the author: Dave Hull splits his time between the Product Engineering and Endpoint Detection and Response teams at Tanium. Prior to joining Tanium, Dave was the senior technical lead for security incident response in Microsoft’s Office 365. He has authored a number of open source tools for digital forensics and incident response investigations and has more than 10 years of experience in the DFIR field.

Featured Webinars

Upcoming Events

Contact Sales

Press Inquiries

Contact Us

Thank you for contacting us

Back to the Tanium Home Page