» » Исследование защиты программы VoiceAttack

 

Исследование защиты программы VoiceAttack

Автор: admin от 29-01-2018, 19:20, посмотрело: 195

Исследование защиты программы VoiceAttack


Которая в итоге подкинула несколько приятных неожиданностей. Осторожно, под катом много скринов в хайрезе и не хайрезе, которые не убраны под спойлер ввиду их невероятной важности. А, ну и ещё там есть шутка про половые органы кентавров, но она тоже включена исключительно ради контекста.

замечательно поставленного видео от г-на Rimas, в процессе которого я невольно зауважал распознаватель речи Windows, как-то понимающий такой ужасный акцент.



Так как VR-шлема у меня не было (как, впрочем, и денег на него), я решил поиграться с недавно расшаренным мне через Стим Elite: Dangerous, поуправляв стандартным корабликом с помощью голоса. Сбегав на сайт VoiceAttack, я подивился аппетитам разработчиков (10 баксов за обёртку встроенного распознавателя речи), и сразу решил для себя, что использовать буду максимум демо-версию программы. Однако, при попытке загрузить скачанный профиль, я обнаружил очередной подарок от разрабов — в демо-версии программы не удаётся сохранить больше 20 команд одновременно, что, разумеется, несколько усложняет общение с кораблём — количество контролей в E:D явно больше двадцати.



Поиски таблетки от жадности



Исследование защиты программы VoiceAttack


Будучи гордым пиратом, не желающим платить 500 рублей за лицензию, я полез в Интернет за кряком. Меня ждало разочарование в виде кучи битых ссылок, ransomware, и прочей мути, предлагаемой Сетью в случае отсутствия необходимых данных. Единственный кусочек информации был найден на форуме exetools, но описывал старую версию программы. Надо было что-то делать, либо плюнуть и не делать уже ничего.



Исследование работы



Итак, для начала просто запустим VoiceAttack. После справки, как и ожидалось, будет предложено зарегистрировать ключ:



Исследование защиты программы VoiceAttack


Обратившись к встроенной справке, узнаём что ключ шестнадцатизначный, и состоит как из цифр, так и из букв:



Исследование защиты программы VoiceAttack


После попытки заполнить поля мусором, стало понятно что присутствуют проверки на валидность ключа:



Исследование защиты программы VoiceAttack


Исследование защиты программы VoiceAttack


Вводим всеми любимый [email protected], 16 цифр, и любуемся на проверку ключа на сервере, и сообщение об ошибке:



Исследование защиты программы VoiceAttack


Исследование защиты программы VoiceAttack


Собственно, разведку на этом можно и закончить. Принцип регистрации довольно прост — программа явно связывается с удалённым сервером, передаёт наши регистрационные данные, и анализирует ответ. Такой метод правится либо патчем программы, чтобы не лезла в сеть и сразу считала ключ верным, либо, ради удовлетворения интересов месье — пишется эмулятор сервера регистрации. Этим и займёмся.



Анализ и деобфускация



Для анализа экзешников я люблю использовать exeinfope — дёшево и сердито (и есть строчка информации для ламеров!)



Откроем VoiceAttack.exe в exeinfope:



Исследование защиты программы VoiceAttack


Отлично, нам повезло — .NET + SmartAssembly это сродни джекпоту — будь программа написана на C++ (и не дай Бог только под x64), или обфусцирована чем-нибудь поинтереснее, пришлось бы ковыряться в спагетти криво дизассемблированного вывода. А так мы легко обойдёмся декомпилятором dnSpy и деобфускатором de4dot.



Копируем файлы программы в отдельную папку и деобфусцируем:



Исследование защиты программы VoiceAttack


Мы готовы к дебагу и анализу кода.



Анализ сетевой активности регистрации



Для начала запустим VoiceAttack, и введём наши невалидные регистрационные данные. Нажмём OK, и быстро остановим программу в дебаггере. Видим следующее:



Исследование защиты программы VoiceAttack


Как мы и думали, при регистрации программа стучится на сервер voiceattack.com и отправляет туда данные регистрации. Вынимаем строку реквеста:



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=


Сразу видна новая проблема — на сервер передаётся явно больше параметров чем задали мы, да ещё и в base64. Пробуем декодировать base64:



Исследование защиты программы VoiceAttack


Неплохо, данные ещё и зашифрованы. Нужно разбираться дальше.



Анализ и взлом шифрования



Найдём в dnSpy форму регистрации. После недолгих поисков, находим подходящую форму frmRegister и лезем в код события btnOk_Click:



Исследование защиты программы VoiceAttack


Кажется, то что надо. Видны тексты уже знакомых нам ошибок. Отмотаем код на нужный нам момент соединения с сервером:



Исследование защиты программы VoiceAttack


HTTP-ответ пишется в переменную response. Найдём, куда записывается зашифрованное тело ответа:



Исследование защиты программы VoiceAttack


Крутим код дальше, ища упоминания переменной text8 и натыкаемся на её перезапись с помощью некой функции:



Исследование защиты программы VoiceAttack


Переходим в функцию и видим что попали в точку:



Исследование защиты программы VoiceAttack


Очевидно, что функция реализует AES-дешифрование входной строки, и возвращает дешифрованный результат. Теперь всё что там остаётся — отловить ключ, содержащийся в переменной this.byte_0. Установим брейкпоинт и запустим программу. На моменте достижения брейкпоинта лезем в значения переменных:



Исследование защиты программы VoiceAttack


Теперь у нас на руках все явки и пароли. Можно начинать работу над эмуляцией сервера регистрации.



Сервер регистрации



Для начала напишем инструмент шифрования и дешифрования по найденному ключу:



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]);
        }
    }
}


Попробуем дешифровать передаваемые на сервер параметры:



Исследование защиты программы VoiceAttack


Отлично. Теперь мы можем видеть что же всё-таки отправляет программа на сервер. Запросим результат сами по уже знакомому нам url и дешифруем его:



Исследование защиты программы VoiceAttack


Похоже что сервер передаёт ответ в виде %Результат валидации%_%Информация%. Снова обратимся к коду и узнаем какой ответ означает успех:



Исследование защиты программы VoiceAttack


Быстро бежим вносить www.voiceattack.com в hosts, шифруем строку «Validation Success_some useless info» нашей программой, хостим страничку, регистрируем программу…



И получаем в ответ целый кукиш в маслом — при открытии программа снова предлагает зарегистрироваться. Означает такое поведение только одно — при запуске внедрена проверка лицензии.



Дорабатываем сервер



Снова лезем в декомпилированный код. Находим метод загрузки главной формы frmMain_Load и после недолгих поисков видим код вызова формы о необходимости регистрации:



Исследование защиты программы VoiceAttack


Обращаем внимание на условие перехода в ветку загрузки формы — проверяется некая переменная this.bool_89. Отмотаем код чуть выше и посмотрим как формируется её значение:



Исследование защиты программы VoiceAttack


Налицо проверка соответствия одной строки другой. Справа некий ValidationKey, слева применение метода на списке строк. Как вы думаете, какой метод поочерёдно применяется ко всем этим строкам?



Ставим брейкпоинт на строку сравнения, чтобы узнать значения строк string_ и text. Получаем:



Исследование защиты программы VoiceAttack


Какие значения подставляются в строки registrationEmail и registrationKey я думаю и так понятно.



Дорабатываем сервер на парсинг GET-запроса и формирование подходящего ответа:



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:WINDOWSSystem32driversetchosts", "a") as hosts_file:
        hosts_file.write("n127.0.0.1 www.voiceattack.com")

def unpatch_hosts():
    with open("C:WINDOWSSystem32driversetchosts", "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:WINDOWSSystem32driversetchosts", "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")


Вместо заключения



Всё вышеописанное производилось исключительно из спортивного интереса, да и больше от счастья что при очередной попытке расковырять программу мне не придётся плеваться в листинги Иды. Разродиться на свой первый пост я хотел давно, а тут и НЛО ещё пригласило — так что призываю писать в комментарии если что-то не так или как-то не очень.

Источник: Хабрахабр

Категория: Программирование

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *