VoiceAttack Security Study
- Tutorial
Which eventually threw some pleasant surprises. Caution, under the cut there are a lot of screenshots in hirez and not hirez, which are not removed under the spoiler due to their incredible importance. Oh, and there’s a joke about the genitals of centaurs, but it’s also included for the sake of context only.
VoiceAttack is known among gamers like Star Citizen and Elite: Dangerous, and is a fairly simple tool for associating keystrokes with voice commands. With a great desire, it can also be used to control a PC, but it fully opens after putting VR glasses on the head
a break for a joke about VR
... and launching any of the above games.
My first acquaintance with VoiceAttack was when I watched a remarkably delivered video from Mr. Rimas, during which I involuntarily respected the Windows speech recognizer, somehow understanding such a terrible accent.
Since I didn’t have a VR helmet (as well as money for it), I decided to play with the recently shared Steam through Elite: Dangerous, controlling a standard boat with my voice. Having run down to the VoiceAttack website, I marveled at the appetites of the developers (10 bucks for wrapping the built-in speech recognizer), and immediately decided for myself that I would use the maximum demo version of the program. However, when I tried to download the downloaded profile, I found another gift from developers - in the demo version of the program it is not possible to save more than 20 teams at a time, which, of course, complicates communication with the ship somewhat - the number of controls in E: D is clearly more than twenty.
Search for a pill for greed
Being a proud pirate who did not want to pay 500 rubles for a license, I climbed the Internet for a crack. I was waiting for disappointment in the form of a pile of broken links, ransomware, and other turbidity offered by the Network in the absence of the necessary data. The only piece of information was found on the exetools forum, but described an old version of the program. It was necessary to do something, or to spit and do nothing already.
Research work
So, for starters, just launch VoiceAttack. After the help, as expected, you will be prompted to register the key:
Turning to the built-in help, we find out that the key is sixteen-digit, and consists of both numbers and letters:
After trying to fill in the fields with garbage, it became clear that there are checks for the validity of the key:
Enter all Favorite test@example.com, 16 digits, and enjoy the key check on the server, and the error message:
Actually, intelligence can be completed on this. The registration principle is quite simple - the program explicitly communicates with the remote server, transfers our registration data, and analyzes the response. This method is corrected either by a patch of the program so that it does not go into the network and immediately considers the key to be correct, or, in order to satisfy the interests of monsieur - the emulator of the registration server is written. This is what we will do.
Analysis and deobfuscation
To analyze executives, I like to use exeinfope - cheaply and angrily (and there is a line of information for lamers!)
Let's open VoiceAttack.exe in exeinfope:
Great, we are lucky - .NET + SmartAssembly is akin to a jackpot - be it written in C ++ (and God forbid only under x64), or obfuscated by something more interesting, I would have to pick my crookedly disassembled output in spaghetti. And so we can easily get by with the dnSpy decompiler and the de4dot deobfuscator.
We copy the program files to a separate folder and deobfuscate:
We are ready for debugging and code analysis.
Analysis of network activity registration
First, launch VoiceAttack, and enter our invalid registration data. Click OK, and quickly stop the program in the debugger. We see the following:
As we thought, when registering, the program knocks on the voiceattack.com server and sends the registration data there. We take out the request line:
http://voiceattack.com/Validate.aspx?pv=JcArVyeUmUOZfJVj6utdKw==&em=iEm1cpNSBqMsA06LJExtLntuDo0yvQwPzKuIJhhbLt8=&vk=A1Zkz8zPx2VUdM1oi+mKHHtuDo0yvQwPzKuIJhhbLt8=&sg=OBaQB9KBl8iHF1miBzpp/Q==&nn=gXWP+H1uDbYW+crJfgFNs8gexDihTyvNmjpBzp/I0//f3IvaGDFmFz+ll1WxqPdu6iC0SAGY1eJBMRvl2GIr2A==&pr=0+jAkAfwc3I1ZMHk2zdDz3tuDo0yvQwPzKuIJhhbLt8=
A new problem is immediately visible - clearly more parameters are passed to the server than we specified, and even in base64. Trying to decode base64:
Not bad, the data is also encrypted. Need to understand further.
Analysis and hacking encryption
Find the registration form in dnSpy. After a short search, we find the suitable form frmRegister and climb into the btnOk_Click event code:
It seems that we need it. The texts of the mistakes already familiar to us are visible. We rewind the code at the moment we need to connect to the server: the
HTTP response is written to the response variable. Find where the encrypted response body is written:
Turn the code further, looking for references to the variable text8 and stumble on overwriting it with the help of a certain function:
Go to the function and see that we hit the point:
Obviously, the function implements AES decryption of the input string, and returns the decrypted result. Now all that remains is to catch the key contained in this.byte_0 variable. Set a breakpoint and run the program. At the moment of reaching the breakpoint, we climb into the values of the variables:
Now we have all the attendances and passwords on hand. You can start work on emulating the registration server.
Registration server
To begin with, we will write an encryption and decryption tool for the key found:
using System;
using System.Security.Cryptography;
using System.Text;
public class Program
{
public static void enc(string text)
{
string result;
Rijndael rijndael = Rijndael.Create();
rijndael.Mode = CipherMode.ECB;
byte[] bytes = Encoding.ASCII.GetBytes(text);
byte[] key = {0x74,0x72,0x75,0x65,0x47,0x52,0x49,0x54}; // Ключ
result = Convert.ToBase64String(rijndael.CreateEncryptor(key, null).TransformFinalBlock(bytes, 0, bytes.Length));
Console.WriteLine(result);
}
public static void dec(string text)
{
string result;
Rijndael rijndael = Rijndael.Create();
rijndael.Mode = CipherMode.ECB;
byte[] key = {0x74,0x72,0x75,0x65,0x47,0x52,0x49,0x54};
byte[] bytes = Convert.FromBase64String(text);
result = Encoding.ASCII.GetString(rijndael.CreateDecryptor(key, null).TransformFinalBlock(bytes, 0, bytes.Length));
Console.WriteLine(result);
}
public static void Main(string[] args)
{
if(args[0]=="enc")
{
enc(args[1]);
} else if (args[0]=="dec")
{
dec(args[1]);
}
}
}
Let's try to decrypt the parameters transmitted to the server:
Excellent. Now we can see what the program sends to the server. We will request the result ourselves using the url we already know and decrypt it:
It seems that the server sends a response in the form of% Validation result% _% Information%.
Let’s go back to the code and find out what the answer means success: Quickly run to add www.voiceattack.com to the hosts, encrypt the line “Validation Success_some useless info” with our program, host the page, register the program ...
And we get a whole cookie in butter in response - upon opening the program again offers to register. This behavior means only one thing - a license check is introduced at startup.
Modifying the server
We climb into the decompiled code again. We find the method for loading the main form frmMain_Load and after a short search we see the code to call the form about the need to register:
We pay attention to the condition for switching to the form loading branch - some variable this.bool_89 is checked. We rewind the code a little higher and see how its value is formed:
There is a check of correspondence of one line to another. On the right is a certain ValidationKey, on the left is the application of the method on a list of strings. Which method do you think is applied to all of these lines in turn?
We put a breakpoint on the comparison line to find out the values of the string_ and text strings. We
get : What values are substituted in the registrationEmail and registrationKey lines, I think, and so it is clear.
We are finalizing the server for parsing a GET request and generating a suitable response:
from SimpleHTTPServer import BaseHTTPServer
import SocketServer
import subprocess
from urlparse import urlparse
from urllib import unquote
import ctypes, sys, os
PORT = 80
def aes(action, text):
return subprocess.Popen(["VoiceAttackCipher.exe", action, text], stdout=subprocess.PIPE).communicate()[0][:-2]
def patch_hosts():
with open("C:\WINDOWS\System32\drivers\etc\hosts", "a") as hosts_file:
hosts_file.write("\n127.0.0.1 www.voiceattack.com")
def unpatch_hosts():
with open("C:\WINDOWS\System32\drivers\etc\hosts", "r") as hosts_file:
hosts_lines = []
for line in hosts_file:
if line != "127.0.0.1 www.voiceattack.com":
hosts_lines.append(line)
hosts_lines[-1] = hosts_lines[-1].strip("\n")
with open("C:\WINDOWS\System32\drivers\etc\hosts", "w") as hosts_file:
for line in hosts_lines:
hosts_file.write(line)
class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_HEAD(s):
s.send_response(200)
s.send_header("Content-type", "text/html")
s.end_headers()
def do_GET(s):
"""Respond to a GET request."""
query = urlparse(s.path).query
query_components = dict(qc.split("=") for qc in query.split("&"))
print "Got registration request! Processing..."
key = aes("dec", unquote(query_components["vk"]))
mail = aes("dec", unquote(query_components["em"]))
magic_1 = aes("dec", unquote(query_components["pv"]))
magic_2 = aes("dec", unquote(query_components["sg"]))
print "Key:", key
print "E-Mail:", mail
print "Magic number #1:", magic_1
print "Magic number #2:", magic_2
print "Creating validation key..."
validation_key = aes("enc", "{}{}{}{}".format(magic_1, mail, key, magic_2))
print "Validation key:", validation_key
print "Creating response..."
response = aes("enc", "Validation Success_{}".format(validation_key))
print "Response:", response
s.send_response(200)
s.send_header("Content-type", "text/html")
s.end_headers()
# Отдаём ответ "Validation Success_{}".format(enc(dec(pv)+dec(em)+dec(vk)+dec(sg)))
s.wfile.write(response)
print "Done! Your program should be registered now!"
if __name__ == "__main__":
print "Starting VoiceAttack Activation Server..."
print "Checking privileges..."
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
if not is_admin:
print "Error: please run this program with admin rights."
os.system("pause")
sys.exit()
print "All good."
print "Patching hosts..."
patch_hosts()
print "VoiceAttack Server Started!"
print "You can try to register VoiceAttack."
print "Press [Ctrl+C] after successful registration."
try:
httpd = SocketServer.TCPServer(("", PORT), MyHandler, bind_and_activate=False)
httpd.allow_reuse_address = True
httpd.server_bind()
httpd.server_activate()
httpd.serve_forever()
except KeyboardInterrupt:
httpd.server_close()
except Exception as e:
print e
finally:
print "Voice Attack Server is shutting down..."
#print "Un-patching hosts..."
#unpatch_hosts()
# Хостс придётся оставить пропатченным, так как проверка происходит при каждом запуске
print "Bye-bye."
os.system("pause")
Instead of a conclusion
All of the above was done solely out of sports interest, and more from the happiness that with the next attempt to pick up the program I would not have to spit on Ida's listings. I wanted to be born in my first post for a long time, and here the UFO also invited me - so I urge you to write in the comments if something is wrong or somehow not very.