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

в 21:29, , рубрики: C#, реверс-инжиниринг

Введение

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

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

Продолжаем изучать код плагина к 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) удалять элементы массива «е» будем по алгоритму
— пропускаем первый элемент;
— если следующий элемент не ":" и не «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
{
};

Заключение

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

Автор: DmiZy

Источник

* - обязательные к заполнению поля