For the past few years, a number of groups of scammers have been cold-calling thousands if not millions of people in what's been referred to as the "Ammyy Scam" or the "Microsoft Tech Support Scam" among other names. The scammers pretend to be from Microsoft or another official group and claim to have detected errors on the users' computers. They have the victims pull up internal logs that show errors, and convince them to download and run the Ammyy Admin software to allow them to remotely control the system. After that point, they may install backdoors or other malware, or simply ask for hundreds of dollars to "fix" the problem. The phone scammers have prompted numerous responses from Microsoft as well as warnings from Ammyy itself on its website. Even though at least two groups have been prosecuted, many more continue to operate. Ammyy Admin is one of many remote control software programs; it is not inherently malicious. The scammers just use it because it's an entirely self-contained executable that runs without any installation, it's the easiest to use for an ad hoc connection.
The internet is also full of technical users who have trolled the scammers, wasting their time, making fun of them, or forcing them to see disgusting images. Like most of us in the security industry, I was amused, but thought little about it until the scam hit closer to home when I discovered one of these groups had managed to scam my grandparents and leave their computer an infected mess for me to clean up. So I set out to find out if I could counter an attempted scam with a full fledged remote exploit, and turn the tables on the scammers.
This was also a very interesting challenge, because most of the time, exploiting software begins with fuzzing the network protocol or file formats used based on format specifications like HTTP or FTP, modifying samples of legitimate files or network traffic, and/or examining source code for vulnerabilities. Exploiters often take advantage of debug symbols, which Microsoft provides for their binaries, or other public documentation. For example, for some targets, finding an exploitable vulnerability is as easy as throwing a long string in the protocol and watching a stack overflow give you control of the instruction pointer. And if your target is like that, awesome for you! But it gets harder for widely-used software (Ammyy claims that the software is used by over 36 million personal and corporate users) especially if it wasn't selected for being a weak target.
In this case, Ammyy Admin not only did not have publicly described protocols, source code, traffic samples, or debug symbols, as I found out, its traffic was also incomprensible. The first thing I did was to set up an Ammyy Admin connection between two virtual machines and capture the network traffic. I started comparing it against other well-known remote desktop protocols, such as VNC and RDP, but quickly discovered that Ammyy uses a completely proprietary network protocol. Not only is the protocol not well known, but all the network traffic was encrypted, making it impossible to reverse-engineer or even replay and reproduce with network traffic alone:
You can keep staring at that all day, but I'll save you the trouble; it's just encrypted data.
Being a reverse engineer by heart, I first set out to identify the code that parses the first few packets to see if there was any kind of vulnerability I could find in it. Ammyy Admin is a decently large (764 kilobytes) executable that does not include the C runtime library, and it appears to be written in C++, which means the call graph between functions is generally lost in a mass of vtable function pointers. But with a little debugging, it wasn't hard to find. This code snippet parses a number of flags that aren’t really important and then begins initializating the crypto functions, but it was very small and doesn't contain exploitable vulnerabilities.
Ammyy uses the same executable for sending and receiving code, and it won't connect to itself, so I decided to use two VMs. While it would be possible to patch the Ammyy code to allow connections to another instance running on the same box, the effort needed to make that work would probably mean you weren't saving any time doing it.
Instead, I focused on reverse engineering the code the sets up and handles the encryption and decryption so that I could write code in a scripting language to simulate one end of the conversation. Although following and cataloging the thousands of binary arithmetic operations is strangely addictive, I had to stop myself before I wasted any more time. Exploring the advanced settings, you can see that the crypto is based on AES-256 and optionally RSA-1024, and I'm sure someone who is bored can finish that, but I needed to get to the core protocol parsers to look for bugs.
So the next direction I went was to identify the wrapper methods around send and recv that performed the encryption or decryption and sending/receiving of data. Those methods were the ones to send and receive the plaintext of the protocol, and I needed to be able to record the data passing through and be able to modify it. There are a few different options for dynamic instrumentation; some vulnerability researchers like using Intel’s Pin framework, for example. Since I had already put together a flexible hooking library for Ambush with generic function hooking library and process injection code, I just made a sniffer from a local fork of the Ambush codebase to handle hooking the internal send or receive functions and save their output to a file, each traffic chunk broken apart by four X's. The output now looked like this:
Much nicer, clearly plaintext and structured, and best of all, repeatable.
The next step was to turn the injected internal protocol sniffer into a fuzzer. I took a brief look at some of the publicly available fuzzers, but none of them appeared to make the process any easier. Fuzzers like minifuzz are easy for fuzzing file-parsing programs, and Peach can easily generate network traffic starting from a protocol specification, but I'm not aware of how to set up any to perform modifications via injected code, while synchronizing and looking for crashes across multiple VM's. So I accomplished this by using the "hookfuzzer" I had written. I modified it to receive a seed over the network and generate pseudorandom numbers to flip about one out of every two hundred bits. I set up the “controlled” end to send the manipulated data back to the “controller” side. There’s not really any point in trying to crash the controlled side, since the controller already owns it.
After running the fuzzer manually a few times, the Ammyy controller crashed! Ammyy caught the error and displayed an error message with the faulting instruction. I modified the injecting sniffer/fuzzer to replay that transcript and verified that the crash was reproducible.
The instruction was an invalid memory access, which didn’t appear immediately easily exploitable, since it didn’t show control of the instruction pointer and the functions responsible seemed extra-awful to reverse, so I decided to automate the fuzzer and run it until I got a better crash.
I wrote a few helper executables and two PowerShell scripts, one on the controller side and one on the controlled side, to form my ad hoc fuzzing framework. They automated starting a new instance of Ammyy Admin, injecting the fuzzer, clicking the appropriate buttons to connect, waiting a few seconds for a crash, detect whether or not a crash had happened, saving the crash address and transcripts of the plaintext traffic that had caused the crash, and restarting the whole process. Next, I cloned a bunch of Windows Vista VM’s, loaded the fuzzers on there, and let them go.
After running for a few days on 5 pairs of VM's, the fuzzer had collected a few thousand crashes, at 11 unique addresses. The vast majority of them fell on the same address as the first crash, and most of the unique crashing addresses were in different functions in the same executable, but two of the crashes demonstrated control of the instruction pointer.
I pulled up the saved transcript and loaded a debugger into my test VM, and traced back the crashing code. For all the effort I had put into building the fuzzing framework, the flaw was the same root vulnerability in the same awful function as the first crash I found. Oh well, looked like I needed to get back into RE and find out exactly how the protocol worked.
After a few days tracing the code, the protocol became clearer. Upon negotiating a successful connection, the controller spawns a thread to handle rendering the remote screen. The first data sent from the controlled end includes header data and global flags for the connection. It then contains system information such as operating system and system name. Finally it includes screen dimensions and various other fields. The controller then allocates a screen buffer using Windows GDI functions based on the screen dimensions, stored in RGBA format; four bytes to a pixel. After a chunk of data describing the cursor, the data stream sends what I call "stroke sets" which draw or update a rectangle on the screen buffer, which could be the entire screen or could just be a portion of it; each defines a start X and Y and width and height. Each stroke set then contains a list of strokes, each of which describes the pixels in up to a 16x16 pixel area. I won't go into full detail, as there are many forms the stroke can take to, for example, paint the entire block the same color in just a few bytes or draw each pixel individually, but in each case, the stroke defines the red, green, and blue portions of the pixels. The protocol uses a flipped coordinate system from GDI; the low index rows that come first are in the last memory addresses and vice versa.
Whew! All of this is important, because the crashing data in the fuzzed transcript is a stroke set that draws a rectangle just off the end of the image buffer. Since the image buffer is not in a heap or stack or other structure, if it was randomized sufficiently, there might not be any data reliably at a given offset to overwrite. However, because of the peculiarities of Windows memory allocation, even though the stack and heaps are nominally randomized, the stack of the renderer thread is allocated in 1MB of reserved memory directly before the image buffer; the last and highest allocations of the "low" allocations in the process. (the high addresses are where the DLLs are mapped) They are not randomized in relation to each other.
Thankfully for us, the Ammyy Admin executable does not opt-in to ASLR or DEP, which makes exploitation far simpler once you have the initial vulnerability discovered. The simplest plan of attack is to first write shellcode into the image buffer, then write the address of a pair of push esp; ret instructions over the return address of the stroke set parser function in the rendering thread followed by a jmp to the start of the buffer with an out-of-bounds array write. An alternate plan of attack is to write a ROP payload that will return to a LoadLibraryW call that will load a DLL from a remote UNC path, which will bypass Always-On DEP. However, this relies on the Web Client service to be enabled and requires waiting for a DLL to be pulled from a UNC path to load which usually takes 30 seconds to a minute.
Writing the shellcode for the direct attack entails a little bit of difficulty, since every fourth byte will be a 0, we can only control the RG and B bytes of the pixels. The good news is that we have no byte restrictions and we have plenty of room, since we can easily reserve a million pixels (4MB) or more if we want. So I just used the Metasploit Unicode encoder to create shellcode that will work when every other byte is a 0, which will work with basically any shellcode.
As far as the out of bounds write, the stroke set parser return address is at 0325FEBC when pixel data starts at 03360000. That's a 0x144 or 324 byte OOB overwrite from start of image, which is 81 pixels. So, with an 800x600 screen buffer, a stroke set with X offset 719 and Y offset 600 (since rows go down in address) will write to the appropriate offset.
With this work done, I put together a metasploit module that will generate a plaintext transcript to send to the remote end via the injected DLL into a running Ammyy instance that will exploit the remote end trying to take over your computer. In order to run it, you still need to run Ammyy Admin, save the plaintext transcript in its directory, and inject the DLL into the process which will load up the transcript. So I put together an executable package to automate this. I wrote the exploit for Ammyy Admin 3.4, including both the direct and ROP targets, and I updated for 3.5 when it was released. The vulnerability has been present for as long as I checked, at least back to 3.0, and probably before then.
You can download the complete package here, including a fully commented metasploit module and detailed README with more information on running it: https://www.scriptjunkie.us/aaa.html The one remaining caveat is that Ammyy can connect in two main ways; either by ID, which routes a connection through relay servers run by Ammyy (rl.ammyy.com), or directly by IP. I have only written and used the exploit with a direct IP connection to avoid sending it over the internet, so although the vulnerability should be present either way, I recommend blocking rl.ammyy.com in a hosts file and simply using direct IP connections. Or at this point, feel free to look into making it work over the relays, but I have not.
No scammer group has ever called me, and I have never used this except to test it and in demonstrations. I don't normally release zero day exploits, but I made an exception in this case because given the reporting and usage of Ammyy Admin I consider it highly unlikely to be used to compromise innocent victims. The primary users at risk of compromise are the scammer groups. Hopefully, it will be a deterrent to those who would attempt to compromise and take advantage of innocent victims.