SMBExec: Red Side Analysis (Part 2)
Picking Back Up
In the previous post of this series, we looked at smbexec.py from the Impacket library and how it works under the hood. This was done by using Procmon to observe what happens on a system when smbexec.py connects to it and executes commands. We also made some observations about ways a defender could build detections based on the nature of smbexec.py.
We had to disable Microsoft Defender to perform this exercise, as it was detecting and blocking our activities. Today we are turning Defender back on, validating some of those guesses on how Defender detects smbexec.py, and then bypassing those detections. Afterwards, we are going to repeat the process to accomplish the same results on a popular EDR product.
Reviewing What We Know
The first logical place to start is by changing some of the static details about smbexec.py, such as the Service Name, filenames, and paths. You can find most of the static elements of the script on the lines below.
These are the default values in the latest version of Impacket’s smbexec.py (as of today in February 2023). You will notice some of the values we saw during the last blog post -- “BTOBTO” was the service name created via a registry write, and “execute.bat” and “__output” are the temporary files for executing commands and writing their output. The “SMBSERVER_DIR” and “DUMMY_SHARE” are only leveraged during the “server mode” of smbexec.py, which we are not covering in this series.
Kicking the Defender Tires
Starting with changing the “SERVICE_NAME”, we re-ran smbexec.py and were met with the same Defender detection and alert.
Meanwhile, this was how things looked from the attacking box… not a very revealing error.
Making changes to the “BATCH_FILENAME” and “OUTPUT_FILENAME” variables also didn’t have any effect on Defender’s detection and alerting. However, there are some additional static elements throughout the script, as seen below:
Here we can see the full file paths being built for the output and batch files. We changed the batch file path to another which is often world-writeable, \Users\Public\Downloads, and reran smbexec.py:
OK — that worked! We already bypassed detection and alerting from Defender, by only changing four static values. There were still multiple other potential detection vectors that we hadn’t touched yet, so this was surprising.
At this point, we went back and grabbed a fresh copy of smbexec.py and changed only the batch file path, to see if that was the only necessary change.
As we can see, it was! By changing only the path to the batch file, we had completely bypassed Defender. We should mention this is Defender in its default state with only Cloud Submission disabled. Defender was up to date on definitions and associated Windows Updates. With this in mind, let’s pivot over to testing against a certain commercial EDR product. Surely the same approach won’t work there… :)
Kicking the EDR Tires...A Bit Harder
The EDR product was installed on a separate workstation, with Defender in a disabled state. This was to avoid any conflicts or overlaps in detection. The EDR product was fully enabled and in “block” mode, and it was up to date on the latest definitions.
The first thing we tested here was the same smbexec.py with only the batch file path changed. However, this was detected and blocked...bummer.
We then tinkered with the same attributes that were changed during the Defender testing (despite it not being necessary for bypassing Defender), such as paths and filenames, but to no avail— still caught.
In thinking about when the script gets caught, we noticed that it happens immediately, before even typing a first command. This means we need to look at what is happening during initialization, before the first command execution.
In examining the “RemoteShell” class, we see numerous tasks happening within “init”. Most of it seems pretty self-explanatory, and again we can ignore anything related to SERVER mode. Towards the bottom before other functions are defined (line 209 for us) we see this self.do_cd(‘’) take place.
What does this do? Let’s find out.
Ahh, right. If you’ve run smbexec.py before and tried to CD to a directory, you’ve seen this error— “You can’t CD under SMBEXEC”. This is because, as we know from our previous blog posts, smbexec.py isn’t a real interactive shell. Every command that we run results in a new execution of our Windows Service and command shell, with a new batch file, and a new output file. In the bottom half of the above screenshot, we see this “execute_remote(‘cd ‘), where the output is gathered, newline characters stripped, and a “>” added to the end. This is our fake command “prompt” that is written out, imitating Windows Command Prompt’s prompt:
That thing. And it seems smbexec.py generates this prompt text before every single command that we run. You may have already gathered that from the previous blog post. This is the “echo cd” that we observed being executed (and detected) on an endpoint. Here it is again:
If this static one-liner is executed every time we run smbexec.py, regardless of what commands we run… that’s a pretty good IOC, wouldn’t you say?
Let’s get rid of it. We don’t care about our current working path anyway, because we can’t change directory.
Let's rerun our custom.py with this change, and see what happens...
This is progress! We now get to command execution before things are blocked. After the previous change, we now know EDR is looking at our full command line. We’ve changed almost everything except our command shell. Let’s change it.
One of our favorite methods of doing…well anything, is Living-Off-the-Land. What better way to fly under the radar than to abuse the tools that are already installed on a target?
Head over to the LOLBAS project (https://lolbas-project.github.io/) and pick out a binary that allows for execution. Then just pop it into your smbexec.py shell, and try again! NOTE: Pay attention to the command line usage. Each of these binaries is different, with different command line usage. Might need to tweak things to get it to work for your use case.
We chose conhost.exe at random, and plugged it into my smbexec.py. To our delight, some things ran successfully! We are getting ever so close.
You will see above, however, that while running an “echo” worked, running “whoami” did not. We’ve still got a little work to do to get all commands working. Let’s try throwing PowerShell into our command execution chain (line 278 for us):
The eagle has landed! So by changing our full execution chain for running “whoami” from “cmd.exe -> whoami” to “conhost.exe -> cmd.exe -> powershell.exe -> whoami” we have fully bypassed detection by a certain EDR product!
Conclusion
Yes, this was a very “dirty” and unscientific approach to EDR bypassing. Yes, the resulting code is pretty convoluted… it could use some extra comments after what we’ve done. But— it works. And not just the specific LOLBAS binary that we chose at random. Even if everyone who reads this blogpost uses conhost.exe and gets it “burned” / added to EDR static signatures— it’s still just a single LOLBAS. Switch to another, and carry on hacking! We can even completely change up our command line. We still have “cmd.exe /Q /c” in our execution chain, which really isn’t necessary. Maybe conhost -> powershell is all we need to bypass. Maybe we even do some additional obfuscation, such as encoding things, etc. We have lots of options at this point.
This wraps up our series on SMBExec.py from Impacket. Thanks for reading along, and hopefully this has helped you dive deeper into how the tool works, how different endpoint security products detect it, and how we can bypass those detections.