Реверс-инжиниринг приложений после обфускации (Часть 2) +15


Введение


Данная публикация направлена на изучение некоторых приемов реверс-инжиниринга. Все материалы представлены исключительно в ознакомительных целях и не предназначены в использовании в чьих-либо корыстных целях.

Рекомендуется к прочтению после первой части
Если хирурга учат как устроен человек и дают ему в руки скальпель, не значит что он будет применять эти знания кому-то во вред, а знающий ассемблер не грезит написанием супер вируса.
Так и в этих уроках не стоит искать намеки на кряки и взломы.

Предмет исследования


Продолжаем изучать код плагина к Visual Studio Atomineer Pro Documentation (далее APD). Давайте познакомимся поближе с инструментом и с его возможностями. Итак, предположим, что у нас есть класс на языке С++.

class ClassForReadFile
{
public:
	ClassForReadFile();
};

настроим APD так, чтобы комментарии были в стиле Doxygen. Встаем курсором на class и нажимаем CTRL+SHIFT+D. Получаем следующее:

/** The class for read file. */
class ClassForReadFile
{
public:
	ClassForReadFile();
};

Плагин добавил красивое описание класса. Все отлично! Двигаемся далее. Предположим класс принадлежит библиотеке и мы должны экспортировать его. Добавляем макрос и изменяем определение класса

#ifdef DLL_EXPORTS
#define DATA_READER_DLL_EXPORTS __declspec(dllexport)
#else
#define DATA_READER_DLL_EXPORTS __declspec(dllimport)
#endif

class DATA_READER_DLL_EXPORTS ClassForReadFile
{
public:
	ClassForReadFile();
};

Для языка C++ (ОС Windows) ситуация стандартная. Проверим наш плагин. Нажимаем CTRL+SHIFT+D и получаем совсем не то что ожидали

/** A data reader DLL exports. */
class DATA_READER_DLL_EXPORTS ClassForReadFile
{
};

название дефайна DATA_READER_DLL_EXPORTS определилось как название класса, вместо ClassForReadFile, и по этому названию и сгенерилось описание класса. То есть в коде плагина данная ситуация, а именно экспорт класса, или не обрабатывается или обрабатывается с ошибкой. Это мы и будем пытаться исправлять.

Шаг 1


Будем искать зацепки. Во первых, так как экспорт функций и классов с С/С++ ситуация стандартная, то все же попробуем «заставить» плагин правильно. На место дефайна DATA_READER_DLL_EXPORTS вставим саму инструкцию __declspec и сгенерим документацию

/** The class for read file. */
class __declspec(dllexport) ClassForReadFile
{
};

И, о чудо, получили правильное описание класса! Таким образом делаем вывод, что в APD есть некий код который проверяет наличие строки "__declspec" в описании класса и игнорирует ее дальнейшем алгоритме построении документации.

Декомпилируем библиотеку штатным ildasm.exe из состава Microsoft SDKs. Найдем строку "__declspec". Она встречается в 2х методах CmdDocComment::a и CmdDocComment::b. Класс один. Его мы будем подвергать дальнейшему изучению.

Шаг 2


Сразу скажу, что то что мы ищем находится в методе CmdDocComment::a

Вот то место, где встречается __declspec. Приведены только наиболее интересные строки.

string a(CmdDocComment.GeneratorInfo A_0)
List<string> e = A_0.e;
//.......
List<string> list = A_0.e;

int num3 = 0;
while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":"))
{
  if (list[num3] == A_0.b && num2 < 0)
  {
    num2 = num3;
  }
  if (list[num3] == "__declspec")
  {
    if (num3 + 1 < list.Count)
    {
      num = list[num3 + 1].IndexOf(')');
      if (num >= 0)
      {
        list[num3 + 1] = list[num3 + 1].Substring(num + 1).Trim();
      }
    }
    list.RemoveAt(num3);
    num3--;
  }
  num3++;
}
if (list.Count > 0 && (list[0] == "struct" || list[0] == "union"))
{
  if (list.Count == 1)
  {
//......


Такой вывод мы сделали на основании того, что после проверки
list[num3] == "__declspec"
вызывается метод удаления
list.RemoveAt(num3);
Рассуждения (мысли вслух):

  1. В методе CmdDocComment::a есть локальная переменная содержащая массив строк

    List<string> list = A_0.e;
  2. Первый элемент этого массива хранит начало описания функции, структуры, класса и т.д., Тоесть ключевое слово «class», «struct», «union»
    list[0] == "struct"
  3. Каждый элемент массива содержит отдельное слово. В нашем случае это будут {«class», «DATA_READER_DLL_EXPORTS», «ClassForReadFile»}
  4. Есть цикл который обходит все элементы массива «е», ищет элемент "__declspec", и удаляет его и все что есть в скобках
  5. Есть дополнительное условие выхода из цикла. Это нахождение слов «where» или ":". Служебное слово «where» мне, честно говоря, не знакомо, а ":" используется при наследовании классов

Определим новый алгоритм и цель изменений:

1. изменения не должны повлиять на остальную функциональность

2. удалять элементы массива «list» будем по алгоритму
— пропускаем первый элемент;
— если следующий элемент не ":" и не «where» и не конец массива, тогда удаляем.

Напишем желаемый цикл

// этот цикл оставим без изменений, так как в цикле есть еще присваивание временной переменной num2
while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":"))
{
  // условие, о котором мы ничего не знаем. Оставим его как есть 
  if (list[num3] == A_0.b && num2 < 0)
  {
    num2 = num3;
  }
  // вместо if (list[num3] == "__declspec"), напишем
  if (num3 != 0 && num3 < (list.Count - 1) && list[num3 + 1] != ":" && list[num3 + 1] != "where")
  {
     e.RemoveAt(index);
     --index;
   }
  num3++;
}

Осталось это запрограммировать.

Шаг 3


Запрограммировать громко сказано. Программирование в общем случае это написание исходных кодов, компиляция, линковка. Но обфускатор лишил нас такой возможности. Воспользуемся рекомендованным инструментом dnSpy. Мы будем менять HEX коды команд CIL прямо в библиотеке, что, как оказалось, очень увлекательно и познавательно! Приступим. Откроем dnSpy, загрузим библиотеку.

Найдем наш метод
image

выделим while и изменим вид на IL
Наш цикл
image

Также приведу листинг, хоть он и довольно громоздкий

Наш цикл
/* 0x00016710 07           */ IL_018C: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x00016711 1119         */ IL_018D: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x00016713 6FF900000A   */ IL_018F: callvirt  instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x00016718 72925E0070   */ IL_0194: ldstr     "where"	// Помещает в стек ссылку на новый объект, представляющий строковой литерал, хранящийся в метаданных.
/* 0x0001671D 287000000A   */ IL_0199: call      bool [mscorlib]System.String::op_Equality(string, string)	// Вызывает метод, на который ссылается переданный дескриптор метода.
/* 0x00016722 3AAB000000   */ IL_019E: brtrue    IL_024E	// Передает управление конечной инструкции, если значение value равно true, либо отличается от null и от нуля.

/* 0x00016727 07           */ IL_01A3: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x00016728 1119         */ IL_01A4: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x0001672A 6FF900000A   */ IL_01A6: callvirt  instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x0001672F 72A31D0070   */ IL_01AB: ldstr     ":"	// Помещает в стек ссылку на новый объект, представляющий строковой литерал, хранящийся в метаданных.
/* 0x00016734 287000000A   */ IL_01B0: call      bool [mscorlib]System.String::op_Equality(string, string)	// Вызывает метод, на который ссылается переданный дескриптор метода.
/* 0x00016739 3A94000000   */ IL_01B5: brtrue    IL_024E	// Передает управление конечной инструкции, если значение value равно true, либо отличается от null и от нуля.

/* 0x0001673E 07           */ IL_01BA: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x0001673F 1119         */ IL_01BB: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x00016741 6FF900000A   */ IL_01BD: callvirt  instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x00016746 03           */ IL_01C2: ldarg.1	// Загружает аргумент с индексом 1 в стек вычислений.
/* 0x00016747 7B12010004   */ IL_01C3: ldfld     string Atomineer.Utils.CmdDocComment/GeneratorInfo::b	// Выполняет поиск значения поля в объекте, ссылка на который находится в стеке вычислений.
/* 0x0001674C 287000000A   */ IL_01C8: call      bool [mscorlib]System.String::op_Equality(string, string)	// Вызывает метод, на который ссылается переданный дескриптор метода.
/* 0x00016751 2C07         */ IL_01CD: brfalse.s IL_01D6	// Передает управление конечной инструкции, если значением value является false, пустая ссылка или ноль.

/* 0x00016753 09           */ IL_01CF: ldloc.3	// Загружает в стек вычислений локальную переменную с индексом 3.
/* 0x00016754 16           */ IL_01D0: ldc.i4.0	// Помещает целочисленное значение 0 в стек вычислений как int32.
/* 0x00016755 2F03         */ IL_01D1: bge.s     IL_01D6	// Передает управление конечной инструкции (короткая форма), если первое значение больше второго или равно ему.

/* 0x00016757 1119         */ IL_01D3: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x00016759 0D           */ IL_01D5: stloc.3	// Извлекает верхнее значение в стеке вычислений и сохраняет его в списке локальных переменных с индексом 3.

/* 0x0001675A 07           */ IL_01D6: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x0001675B 1119         */ IL_01D7: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x0001675D 6FF900000A   */ IL_01D9: callvirt  instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x00016762 729E5E0070   */ IL_01DE: ldstr     "__declspec"	// Помещает в стек ссылку на новый объект, представляющий строковой литерал, хранящийся в метаданных.
/* 0x00016767 287000000A   */ IL_01E3: call      bool [mscorlib]System.String::op_Equality(string, string)	// Вызывает метод, на который ссылается переданный дескриптор метода.
/* 0x0001676C 2C51         */ IL_01E8: brfalse.s IL_023B	// Передает управление конечной инструкции, если значением value является false, пустая ссылка или ноль.

/* 0x0001676E 1119         */ IL_01EA: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x00016770 17           */ IL_01EC: ldc.i4.1	// Помещает целочисленное значение 1 в стек вычислений как int32.
/* 0x00016771 58           */ IL_01ED: add	// Складывает два значения и помещает результат в стек вычислений.
/* 0x00016772 07           */ IL_01EE: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x00016773 6FF700000A   */ IL_01EF: callvirt  instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count()	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x00016778 2F37         */ IL_01F4: bge.s     IL_022D	// Передает управление конечной инструкции (короткая форма), если первое значение больше второго или равно ему.

/* 0x0001677A 07           */ IL_01F6: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x0001677B 1119         */ IL_01F7: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x0001677D 17           */ IL_01F9: ldc.i4.1	// Помещает целочисленное значение 1 в стек вычислений как int32.
/* 0x0001677E 58           */ IL_01FA: add	// Складывает два значения и помещает результат в стек вычислений.
/* 0x0001677F 6FF900000A   */ IL_01FB: callvirt  instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x00016784 1F29         */ IL_0200: ldc.i4.s  41	// Помещает переданное значение с типом int8 в стек вычислений как int32 (короткая форма).
/* 0x00016786 6FC800000A   */ IL_0202: callvirt  instance int32 [mscorlib]System.String::IndexOf(char)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x0001678B 0C           */ IL_0207: stloc.2	// Извлекает верхнее значение в стеке вычислений и сохраняет его в списке локальных переменных с индексом 2.
/* 0x0001678C 08           */ IL_0208: ldloc.2	// Загружает в стек вычислений локальную переменную с индексом 2.
/* 0x0001678D 16           */ IL_0209: ldc.i4.0	// Помещает целочисленное значение 0 в стек вычислений как int32.
/* 0x0001678E 3221         */ IL_020A: blt.s     IL_022D	// Передает управление конечной инструкции (короткая форма), если первое значение меньше второго значения.

/* 0x00016790 07           */ IL_020C: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x00016791 1119         */ IL_020D: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x00016793 17           */ IL_020F: ldc.i4.1	// Помещает целочисленное значение 1 в стек вычислений как int32.
/* 0x00016794 58           */ IL_0210: add	// Складывает два значения и помещает результат в стек вычислений.
/* 0x00016795 07           */ IL_0211: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x00016796 1119         */ IL_0212: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x00016798 17           */ IL_0214: ldc.i4.1	// Помещает целочисленное значение 1 в стек вычислений как int32.
/* 0x00016799 58           */ IL_0215: add	// Складывает два значения и помещает результат в стек вычислений.
/* 0x0001679A 6FF900000A   */ IL_0216: callvirt  instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x0001679F 08           */ IL_021B: ldloc.2	// Загружает в стек вычислений локальную переменную с индексом 2.
/* 0x000167A0 17           */ IL_021C: ldc.i4.1	// Помещает целочисленное значение 1 в стек вычислений как int32.
/* 0x000167A1 58           */ IL_021D: add	// Складывает два значения и помещает результат в стек вычислений.
/* 0x000167A2 6FCB00000A   */ IL_021E: callvirt  instance string [mscorlib]System.String::Substring(int32)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x000167A7 6F8600000A   */ IL_0223: callvirt  instance string [mscorlib]System.String::Trim()	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x000167AC 6FFF00000A   */ IL_0228: callvirt  instance void class [mscorlib]System.Collections.Generic.List`1<string>::set_Item(int32, !0)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.

/* 0x000167B1 07           */ IL_022D: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x000167B2 1119         */ IL_022E: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x000167B4 6F6701000A   */ IL_0230: callvirt  instance void class [mscorlib]System.Collections.Generic.List`1<string>::RemoveAt(int32)	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x000167B9 1119         */ IL_0235: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x000167BB 17           */ IL_0237: ldc.i4.1	// Помещает целочисленное значение 1 в стек вычислений как int32.
/* 0x000167BC 59           */ IL_0238: sub	// Вычитает одно значение из другого и помещает результат в стек вычислений.
/* 0x000167BD 1319         */ IL_0239: stloc.s   V_25	// Извлекает верхнее значение в стеке вычислений и сохраняет его в списке локальных переменных с индексом index (короткая форма).

/* 0x000167BF 1119         */ IL_023B: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x000167C1 17           */ IL_023D: ldc.i4.1	// Помещает целочисленное значение 1 в стек вычислений как int32.
/* 0x000167C2 58           */ IL_023E: add	// Складывает два значения и помещает результат в стек вычислений.
/* 0x000167C3 1319         */ IL_023F: stloc.s   V_25	// Извлекает верхнее значение в стеке вычислений и сохраняет его в списке локальных переменных с индексом index (короткая форма).

/* 0x000167C5 1119         */ IL_0241: ldloc.s   V_25	// Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
/* 0x000167C7 07           */ IL_0243: ldloc.1	// Загружает в стек вычислений локальную переменную с индексом 1.
/* 0x000167C8 6FF700000A   */ IL_0244: callvirt  instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count()	// Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений.
/* 0x000167CD 3F3EFFFFFF   */ IL_0249: blt       IL_018C	// Передает управление конечной инструкции, если первое значение меньше второго.


Теперь перед нами окно в CIL командами, их HEX представление, смещение в файле и описание. Все в одном месте. Очень удобно (спасибо CrazyAlex25).
Обратим внимание на блок с упоминанием "__declspec". Смещение блока 0x0001675A. Это будет начало наших правок. Далее найдем метод RemoveAt. Он нам пригодится в неизменном виде. Последний байт блока 0x000167BF. Перейдем в HEX-редактор Ctrl+X и запишем в этот диапазон 0x00. Сохраним и проверим к чему привели изменения.
пустой цикл
while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":"))
{
  if (list[num3] == A_0.b && num2 < 0)
  {
    num2 = num3;
  }
  list.RemoveAt(num3);
  num3--;
  num3++;
}


Теперь будем реализовывать новую логику. Для начала добавим условие

 if (num3 != 0 && num3 < list.Count - 1)

В таблице приведены новые команды и их описание
1119 ldloc.s Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма).
2C61 brfalse.s Передает управление конечной инструкции, если значением value является false, пустая ссылка или ноль. Примечание: Если num3 == 0, то переходим к шагу увеличения итератора цикла. Значение 0x64 это смещение адреса до инструкции 0x000167BF (см. листинг)
1119 ldloc.s Загружает в стек вычислений локальную переменную с указанным индексом (короткая форма)
07 ldloc.1 Загружает в стек вычислений локальную переменную с индексом 1
6FF700000A callvirt get_Count() — Вызывает метод объекта с поздней привязкой и помещает возвращаемое значение в стек вычислений
17 ldc.i4.1 Помещает целочисленное значение 1 в стек вычислений как int32
59 sub Вычитает одно значение из другого и помещает результат в стек вычислений
2F55 bge.s Передает управление конечной инструкции (короткая форма), если первое значение больше второго или равно ему. Примечание: Если num3 > list.Count — 1, то переходим к шагу увеличения итератора цикла. Значение 0x55 это смещение адреса до инструкции 0x000167BF

Эти байты запишем начиная со смещения 0x0001675A. Сохраним и декомпилируем заново

Первое условие

while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":"))
{
  if (list[num3] == A_0.b && num2 < 0)
  {
    num2 = num3;
  }
  // появилось наше первое условие
  if (num3 != 0 && num3 < list.Count - 1)
  {
    list.RemoveAt(num3);
    num3--;
  }
  num3++;
}


Теперь добавим проверку строк «where» и ":". Следующий HEX-код привожу без дополнительных комментариев:

07 11 19 17 58 6F F9 00 00 0A 72 A3 1D 00 70 28 70 00 00 0A 2D 3F
07 11 19 17 58 6F F9 00 00 0A 72 92 5E 00 70 28 70 00 00 0A 2D 29

декомпилируем и получаем то, что и планировали

Новый цикл
while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":"))
{
  if (list[num3] == A_0.b && num2 < 0)
  {
    num2 = num3;
  }
  if (num3 != 0 && num3 < list.Count - 1 && !(list[num3 + 1] == ":") && !(list[num3 + 1] == "where"))
  {
    list.RemoveAt(num3);
    num3--;
  }
  num3++;
}


С такими изменениями плагин сгенерит следующее документирование кода:

/** The class for read file. */
class DATA_READER_DLL_EXPORTS ClassForReadFile
{
};

Заключение


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




К сожалению, не доступен сервер mySQL