Skip to content

unpack reverse

Az404 edited this page Mar 11, 2018 · 1 revision

Новая технология защиты ПО уже доступна для тестирования

  • Категория: Reverse
  • Стоимость: 600
  • Автор: Алексей Захаров
  • Репозиторий

Условие

Компания QuasiSecurity опубликовала свою новую разработку — инструмент для защиты программ от взлома. На хакерских форумах активное обсуждение этого продукта началось ещё несколько дней назад.

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

Решение

При запуске exe-файла из условия пользователю предлагается ввести некоторый PIN-код:

unpack-reverse> protected.exe
Please, complete security check to run this application.
Enter PIN: 1234
Security check failed. Exiting...

Откроем бинарник в IDA. Функция main состоит из нескольких блоков, в каждом из которых в случае ошибки выводится некоторое сообщение:

.text:00401763                 lea     eax, [ebp+Buffer]
.text:00401769                 mov     [esp+4], eax    ; lpBuffer
.text:0040176D                 mov     dword ptr [esp], 105h ; nBufferLength
.text:00401774                 call    _GetTempPathA@8
.text:00401779                 sub     esp, 8
.text:0040177C                 test    eax, eax
.text:0040177E                 jnz     short loc_40178C
.text:00401780                 mov     dword ptr [esp], offset aCannotGetTempD ; "Cannot get temp directory"
.text:00401787                 call    _fail
.text:0040178C ; ---------------------------------------------------------------------------
.text:0040178C
.text:0040178C loc_40178C:                             ; CODE XREF: _main+34↑j
.text:0040178C                 lea     eax, [ebp+TempFileName]
.text:00401792                 mov     [esp+0Ch], eax  ; lpTempFileName
.text:00401796                 mov     dword ptr [esp+8], 0 ; uUnique
.text:0040179E                 mov     dword ptr [esp+4], 0 ; lpPrefixString
.text:004017A6                 lea     eax, [ebp+Buffer]
.text:004017AC                 mov     [esp], eax      ; lpPathName
.text:004017AF                 call    _GetTempFileNameA@16
.text:004017B4                 sub     esp, 10h
.text:004017B7                 test    eax, eax
.text:004017B9                 jnz     short loc_4017C7
.text:004017BB                 mov     dword ptr [esp], offset aCannotGetTempF ; "Cannot get temp filename"
.text:004017C2                 call    _fail
.text:004017C7 ; ---------------------------------------------------------------------------
.text:004017C7
.text:004017C7 loc_4017C7:                             ; CODE XREF: _main+6F↑j
.text:004017C7                 mov     dword ptr [esp+4], offset aWb ; "wb"
.text:004017CF                 lea     eax, [ebp+TempFileName]
.text:004017D5                 mov     [esp], eax      ; char *
.text:004017D8                 call    _fopen
.text:004017DD                 mov     [ebp+var_C], eax
.text:004017E0                 cmp     [ebp+var_C], 0
.text:004017E4                 jnz     short loc_4017F2
.text:004017E6                 mov     dword ptr [esp], offset aCannotOpenTemp ; "Cannot open temp file"
.text:004017ED                 call    _fail
.text:004017F2 ; ---------------------------------------------------------------------------

По содержанию сообщений и вызываемым функциям (GetTempPathA, GetTempFileNameA, fopen в режиме wb) можно понять, что этот код открывает на запись новый временный файл с именем, которое находится в переменной TempFileName.

Последний блок в main содержит вызов ещё нескольких функций:

.text:004017F2                 call    _deobfuscate
.text:004017F7                 mov     eax, [ebp+var_C]
.text:004017FA                 mov     [esp+0Ch], eax  ; FILE *
.text:004017FE                 mov     dword ptr [esp+8], 4000h ; size_t
.text:00401806                 mov     dword ptr [esp+4], 1 ; size_t
.text:0040180E                 mov     dword ptr [esp], offset _level2 ; void *
.text:00401815                 call    _fwrite
.text:0040181A                 mov     eax, [ebp+var_C]
.text:0040181D                 mov     [esp], eax      ; FILE *
.text:00401820                 call    _fclose
.text:00401825                 lea     eax, [ebp+TempFileName]
.text:0040182B                 mov     [esp], eax      ; char *
.text:0040182E                 call    _system
.text:00401833                 lea     eax, [ebp+TempFileName]
.text:00401839                 mov     [esp], eax      ; lpFileName
.text:0040183C                 call    _DeleteFileA@4

fwrite записывает в открытый временный файл некоторый набор байт, файл закрывается, а затем запускается на исполнение с помощью функции system. После этого он удаляется. Но вызов system - блокирующий, поэтому удаление распакованной программы происходит только после её завершения.

Можно предположить, что все сообщения на консоль выводит именно распакованная программа. При этом она не завершается, пока не введён PIN-код. Тогда для того, чтобы её извлечь, достаточно запустить protected.exe и найти только что созданный временный файл в папке %TEMP%.

При открытии нового файла в IDA нам предлагается загрузить его как Microsoft.Net assembly, из чего мы делаем вывод о том, что это программа, написанная с использованием .Net Framework. Получим её код на C# с помощью одного из декомпиляторов, например dotPeek.

Функция Main в полученном коде начинается с вызова функции Authorize, в которой введённый PIN-код сравнивается с константой.

private static bool Authorize()
{
  Console.WriteLine("Please, complete security check to run this application.");
  Console.Write("Enter PIN: ");
  long result;
  if (long.TryParse(Console.ReadLine(), out result))
    return result == 89543436219193903L;
  return false;
}

Запустив бинарник повторно и введя нужный PIN, получаем новое сообщение:

unpack-reverse> protected.exe
Please, complete security check to run this application.
Enter PIN: 89543436219193903
Security check complete. Starting application...
Enter your flag:

Продолжив анализ C#-кода, снова видим запись во временный файл и его удаление перед выходом. Однако на этот раз файл не запускается, а загружается как dll-библиотека, из которой затем вызывается функция check:

IntPtr hModule = Win32.LoadLibrary(tempFileName);
string procedureName = "check";
((Action) Marshal.GetDelegateForFunctionPointer(Win32.GetProcAddress(hModule, procedureName), typeof (Action)))();
Win32.FreeLibrary(hModule);

Скопируем библиотеку из папки %TEMP% тем же способом, найдя файл, созданный после ввода PIN-кода.

Вновь запустим IDA, откроем dll и перейдём на check в списке функций. Её код устроен следующим образом:

  • с помощью функции puts выводится "Enter your flag:",
  • далее происходит ввод строки с консоли с помощью fgets и её анализ,
  • а в конце печатается одно из двух сообщений: "You are right!" или "You are wrong".

Выбор сообщения происходит в зависимости от значения локальной переменной var_C:

.text:62D8130B                 cmp     [ebp+var_C], 0
.text:62D8130F                 jz      short loc_62D8131F
.text:62D81311                 mov     [esp+58h+Str], offset aYouAreRight ; "You are right!"
.text:62D81318                 call    puts
.text:62D8131D                 jmp     short locret_62D8132B
.text:62D8131F ; ---------------------------------------------------------------------------
.text:62D8131F
.text:62D8131F loc_62D8131F:                           ; CODE XREF: check+8F↑j
.text:62D8131F                 mov     [esp+58h+Str], offset aYouAreWrong ; "You are wrong"
.text:62D81326                 call    puts

Для того, чтобы получить "You are right!" нужно, чтобы var_C была не равна 0. Посмотрим на обращения к этой переменной:

  • сразу после ввода пользовательской строки var_C выставляется в 1
  • strlen для неё должна вернуть 39, иначе var_C станет нулём:
.text:62D812B9                 lea     eax, [ebp+Buf]
.text:62D812BC                 mov     [esp+58h+Str], eax ; Str
.text:62D812BF                 call    strlen
.text:62D812C4                 cmp     eax, 39
.text:62D812C7                 jz      short loc_62D812D0
.text:62D812C9                 mov     [ebp+var_C], 0

Нужно заметить, что функция fgets сохраняет символ \n. Это означает, что длина самой строки должна быть равна 38.

  • кроме того, var_C будет выставлена в 0, если на одной из итераций цикла по символам строки результат некоторых вычислений не совпадёт с ожидаемым:
.text:62D812D9                 mov     eax, [ebp+var_10] ; var_10 - счётчик цикла
.text:62D812DC                 add     eax, offset unk_62D82000
.text:62D812E1                 movzx   eax, byte ptr [eax]
.text:62D812E4                 xor     al, [ebp+var_11] ; var_11 = 87h
.text:62D812E7                 mov     edx, eax
.text:62D812E9                 lea     ecx, [ebp+Buf] ; Buf - введённая строка
.text:62D812EC                 mov     eax, [ebp+var_10]
.text:62D812EF                 add     eax, ecx
.text:62D812F1                 movzx   eax, byte ptr [eax]
.text:62D812F4                 cmp     dl, al
.text:62D812F6                 jz      short loc_62D812FF
.text:62D812F8                 mov     [ebp+var_C], 0

Проанализировав этот код, можно понять, что ключевая операция здесь - xor очередного элемента массива unk_62D82000 с ключом, который находится в переменной var_11 (в начале программы этой переменной присваивается значение 87h). Полученное значение затем сравнивается с соответствующим символом ввода.

Таким образом, чтобы получить флаг, достаточно выполнить xor 38 байтов массива unk_62D82000 с ключом 87h.

Clone this wiki locally