Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

Видеокурс программиста и крэкера 5D 2O17
(актуальность: октябрь 2O17)
Свежие инструменты, новые видеоуроки!

  • 400+ видеоуроков
  • 800 инструментов
  • 100+ свежих книг и статей

УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

[.NET] Microinvest Warehouse Pro, заставляем работать без патчей

Обсудить статью на форуме

Хорошая подборка видеоуроков, инструментов крэкера, книг и статей - здесь.

Автор: xa0c <not.valid@e.mail>

Жертва:
Microinvest Warehouse Pro v3.07.041 build 19119 (http://microinvest.su)

Инструменты:
Visual Studio Express 2013 for Windows Desktop (https://www.visualstudio.com/en-us/products/visual-studio-express-vs.aspx)
de4dot (http://de4dot.com/)
Red Gate .NET Reflector (http://www.red-gate.com/products/dotnet-development/reflector)
Reflexil Add-In (http://reflexil.net)
dotPeek (https://www.jetbrains.com/decompiler)
GrayWolf (https://www.digitalbodyguard.com/graywolf.html)
// Как я понял в итоге, не все из перечисленных инструментов оказались нужны, но в процессе поиска решения использовались так или иначе все. GrayWolf хорош тем, что изменения показывает сразу, но тупит иногда и не позволяет создавать переменные, в таком случае использовался Reflector с плагином, неудобно, но работает. Кроме того, рефлектор дичайше тормознуто работает (в плане просто просмотра), но, опять же, в нём есть какой-никакой поиск по константам. dotPeek использовался постоянно, поиск в нём никакой, но есть полезная опция "find usages", которая весьма помогает при исследовании. Кроме того, Дотпик не такой тормозной как Рефлектор.

Тут на форуме один товарищ запостил темку (https://exelab.ru/f/index.php?action=vthread&forum=6&topic=23612), в которой промелькнуло слово "RSA", чем, собсно, и вызвало у меня интерес (см. предыдущую статью).
На дотнете я не писАл ничего уже миллион лет, так что решил "а чего бы не поиграться?".


Попытка первая

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

           FileStream stream2 = new FileStream(str, FileMode.Open);
           BinaryWriter writer2 = new BinaryWriter(stream2);
           str = "1";
           string str3 = "<RSAKeyValue><Modulus>u1T+WGU732ssVi8p9P2218W0pKAYnUPSKyqq1mPHUy/shB5gOwb7rzloj8IEtccbYUdRIDRCwlFvKGTZkFULD3lda/vqN3YIv0q9eFusmSjMv4wf7TWtt+BOnOEF/IF8bSw4KDUo9oi6qFw/ofP2qs4kJ5TM78BHhbe3nvmg5CM=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
           writer2.Seek(0x72001, SeekOrigin.Begin);
           int num10 = str.Length - 1;
           for (num4 = 0; num4 <= num10; num4++)
           {
               buffer3[0] = (byte) Strings.Asc(str.Substring(num4, 1));
               writer2.Write(GClass5.smethod_0(buffer3, str3));
           }

Что мы тут видим? Просто некая строка шифруется и куда-то записывается. Это не проверка серийника.
Проверка серийника здесь:


           string str = string.Empty;
           try
           {
               str = this.method_1(ref str6, expression);
           }
           catch (Exception exception1)
           {
               ProjectData.SetProjectError(exception1);
               ProjectData.ClearProjectError();
           }
           if (str != expression)
           {
               Type type = typeof(Form);
               str = Regex.Replace(Regex.Replace(type.Assembly.CodeBase, "file:///", ""), "/", @"\").ToLowerInvariant();
               str = str.Substring(0, str.ToLowerInvariant().IndexOf("system.windows.forms")) + @"operations\3.0.1.0__14a76cee500f0423\operations.dll";
               byte[] buffer = new byte[] { 0 };
               DateTime time = File.GetLastWriteTime(str);
               FileStream stream = new FileStream(str, FileMode.Open);
               BinaryWriter writer = new BinaryWriter(stream);
               string str2 = "<RSAKeyValue><Modulus>(тут длинный и бесполезный модуль)</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
               writer.Seek(0x73b01, SeekOrigin.Begin);
               Class1.string_7 = Conversion.Val(Conversions.ToDouble(Class1.string_7) + 1.0).ToString("0");
               int num2 = Class1.string_7.Length - 1;
               for (int m = 0; m <= num2; m++)
               {
                   buffer[0] = (byte) Strings.Asc(Class1.string_7.Substring(m, 1));
                   writer.Write(GClass5.smethod_0(buffer, str2));
               }
               writer.Close();
               stream.Close();
               File.SetLastWriteTime(str, time);
               if (Conversion.Val(Class1.string_7) >= 5.0)
               {
                   ProjectData.EndApp();
               }
               this.MIPanel_2.Visible = false;
               this.MIButton_1.Enabled = false;
               this.MIPanel_5.Visible = true;
               break;
           }

Да, вот в этом огромном методе практически незаметная строчка "str = this.method_1(ref str6, expression)" отвечала за то, принимать или не принимать введённый серийник. Если метод возвращает строку отличную от сгенерёного licenseKey, значит serialKey невалидный, записываем в operations.dll какую-то бяку и выходим.
В общем, глянул я на этот метод проверки серийника и сразу закрыл, он просто километровый, разбираться что и как там преобразуется — жизни не хватит.
Ок, открываем GrayWolf, ищем это место (к слову, я тогда ещё думал, что крак близок как никогда, поэтому работал с оригинальной версией, обфусцированой):

class:     Microinvest.WarehousePro.xc838e00a2766f3d9
method:    private void xc2515208eee11d0c(object xe0292b9ed559da7d, EventArgs xfbf34718e704c6bc)
line:      xb55b340ae3a3e4e0 = this.x914f6093b669e9e1(ref xfa55a12162faf367, str2);

В IL это выглядит так:

2539	ldsfld	System.String System.String::Empty
2544	stloc.s	V_12
2546	ldarg.0	
2547	ldloca.s	V_0
2549	ldloc.1	
2550	callvirt	System.String Microinvest.WarehousePro.xc838e00a2766f3d9::x914f6093b669e9e1(System.String&,System.String)
2555	stloc.s	V_12

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

xb55b340ae3a3e4e0 = str2;

Как вы сами догадались, теперь условие "if (xb55b340ae3a3e4e0 != str2)" никогда не выполнится. Другими словами, теперь программа послушно ест любой серийник. Проверяем с вариантом "11111 11111 11111 11111 11111" — работает, в окне "About" какой-то номер лицензии даже светится. Отсылаем товарищу с форума. Радуемся — времени потрачено минут 10.

Рано радуемся

Товарищ отвечает: "серийник хавает, но при попытке изменить используемую программой БД выдаётся сообщение, что на данную БД лицензия не распространяется и настройки БД сбрасываются, поля заблокированы".
Да-с, засада. Всё ещё верю, что решение должно быть простым, продолжаю исследовать оригинальные файлы, обфусцированные, то бишь. Ищу строковые константы похожие на слово "license",
В сборке Setup.dll, классе Microinvest.Setup.x1b76a84c67433622, нахожу следующий код:

int num = (int) MIMessageBox.Show(Dictionary.GetResource("strNoLicence"), Dictionary.GetResource("strWarning"), MIMessageBox.MIMessageBoxButtons.OK, MIMessageBox.MIMessageBoxIcon.Warning);

Ага! Вот то сообщение, которое при выборе "нелицензированой" БД выскакивает.
Смотрю метод внимательней.

private void x5e28a14139a95a85()
{
	checked
	{
		if (this.x1699dc3654222440)
		{
			if (this.xdab8047e7c0a5bd0 <= TDBAccessType.Oracle)
			{
                            // какие-то проверки и вывод сообщения о неподходящей БД.
			}
			IL_A6:
			this.x95cdc0e2f07d1d57.x61a62a6cf415ce3a = (TDBAccessType)this.x25711f8f245f3423.SelectedIndex;
		}
		this.x48e03a76ec8d0fa2.Enabled = (this.x25711f8f245f3423.SelectedIndex == 0);
		this.xc19697ac38b7c1e2.Enabled = (this.x25711f8f245f3423.SelectedIndex != 0);
		this.x0e023a94493c79bc.Enabled = (this.x25711f8f245f3423.SelectedIndex != 0);
		this.x2edc973d3ae6c1ce.Enabled = (this.x25711f8f245f3423.SelectedIndex != 0);
		this.xfd39296ebef973fe.Enabled = (this.x25711f8f245f3423.SelectedIndex != 0);
		this.xb8c9a51ca56d3e78.Enabled = (this.x25711f8f245f3423.SelectedIndex != 0);
	}
}

Ок, думаю, давай-ка просто не будем заходить в это условие. Открываю GrayWolf:

0	ldarg.0	
1	ldfld	System.Boolean Microinvest.Setup.x1b76a84c67433622::x1699dc3654222440
6	brfalse	IL_00bd: ldarg.0

Меняю brfalse на brtrue, получаю обратное условие и возможность выбора любой БД. Т.к. времени особо вникать/проверять не было, отправляю данный вариант челу-с-форума, хотя сомнения в душе, всё-таки, были.

И они подтвердились

Человек ответил "Да, поля теперь разблокированы, да вот только настройки БД всё равно не применяются, и при рестарте сбрасываются.". Беда.
Понимаю, что дальше по обфусцированному коду лазать я не выдержу, спрашиваю у человека чем он всё это деобфусцировал. Получаю ответ: de4dot.
Шикарная утилита, жаль, что раньше про неё не знал. Запускаю:

de4dot -r "C:\Program Files (x86)\Microinvest\Warehouse Pro" -ro "C:\Program Files (x86)\Microinvest\Warehouse Pro\out"

Получаю деобфусцированные и при этом рабочие dll. Не знаю какую магию использует эта утила, но некоторые имена классов/параметров/методов даже обретают человеческие имена: вместо обычной "string_1" получаю "PermissionString", вместо "GClass_5" откуда-то находит хорошее "LicenseForm". Конечно, таких случаев единицы, но сам факт радует.
Итак, после деобфускации снова открываем Setup.dll, класс "x1b76a84c67433622" теперь имеет человечное название "SetupForm", смотрим на его конструктор:

public SetupForm(ref Configuration currentConfig, Database dbAppl, Database dbRepl, string PermissionsString, int UserLevel)
{
...
    string str = PermissionsString;
    switch (str)
    {
        case "0001":
            this.tdbaccessType_0 = TDBAccessType.MSDE;
            goto Label_0155;

        case "0002":
            this.tdbaccessType_0 = TDBAccessType.MySQL;
            goto Label_0155;

        case "0003":
            this.tdbaccessType_0 = TDBAccessType.SQLServer;
            goto Label_0155;

        case "0004":
            this.tdbaccessType_0 = TDBAccessType.Oracle;
            goto Label_0155;

        case "0005":
            this.tdbaccessType_0 = TDBAccessType.All;
            break;
    }
    if (str == "0005")
    {
        this.tdbaccessType_0 = TDBAccessType.All;
    }
    else
    {
        this.tdbaccessType_0 = TDBAccessType.Access;
    }
...
}

Вот оно, то место, где происходит проверка можно ли нам использовать базу, отличную от Access. Достаточно просто в GrayWolf или в Reflexi заменить string "string str = PermissionsString;" на "string str ="0005";". После этого база выбирается любая, без проблем, все настройки сохраняются и работают. В принципе, патч готов, но такой подход меня несколько не устроил. Не сильно люблю патчи, а тут аж в двух местах пришлось. И кто его знает, не появится ли что-то ещё.

Нужен другой подход.

Так как код теперь у нас относительно чистый, читабельный, возвращаемся к месту проверки лицензии Microinvest.WarehousePro.LicenseForm.btnNext_Click(object sender, EventArgs e):
В случае успешной проверки введённого номера мы видим, что происходит несколько записей в файл c:\windows\assembly\gac_msil\operations\3.0.1.0__14a76cee500f0423\operations.dll:
Первая:

            str = "1";
            string str3 = "<RSAKeyValue><Modulus>тут неважнодлинный модуль</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            writer2.Seek(466945, SeekOrigin.Begin);
            int num10 = str.Length - 1;
            for (num4 = 0; num4 <= num10; num4++)
            {
                buffer3[0] = (byte) Strings.Asc(str.Substring(num4, 1));
                writer2.Write(GClass30.smethod_0(buffer3, str3));
            }

Просто запись единицы, типа "программа лицензирована".
Вторая:

            str3 = "<RSAKeyValue><Modulus>тут неважнодлинный модуль</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            int num9 = expression.Length - 1;
            for (num4 = 0; num4 <= num9; num4++)
            {
                buffer3[0] = (byte) Strings.Asc(expression.Substring(num4, 1));
                writer2.Write(GClass30.smethod_0(buffer3, str3));
            }

Сразу за единицей запись серийника (не лицензии, именно ключа, который сама программа генерит в окне лицензирования)
Третья:

            str3 = "<RSAKeyValue><Modulus>тут неважнодлинный модуль</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            int num8 = str6.Length - 1;
            for (num4 = 0; num4 <= num8; num4++)
            {
                buffer3[0] = (byte) Strings.Asc(str6.Substring(num4, 1));
                writer2.Write(GClass30.smethod_0(buffer3, str3));
            }

Введённый лицензионный ключ. В случае патча (самое первое изменение, описАл в начале) запишется без изменений. Без патча — неведомо как модифицированный серийник гигантским методом проверки, в который мне было лень вникать.
Четвёртая:

            writer2.Seek(474881, SeekOrigin.Begin);
            str5 = str5.Substring(3, 1) + str5.Substring(5, 1) + str5.Substring(1, 1) + str5.Substring(2, 1) + str5.Substring(4, 1) + str5.Substring(0, 1);
            str3 = "<RSAKeyValue><Modulus>тут неважнодлинный модуль</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            int num6 = str5.Length - 1;
            for (num4 = 0; num4 <= num6; num4++)
            {
                buffer3[0] = (byte) Strings.Asc(str5.Substring(num4, 1));
                writer2.Write(GClass30.smethod_0(buffer3, str3));
            }

Переход в другое место файла и запись номера лицензии, что отображается в окне about, достаётся из введённого нами лицензионного ключа, но можно и просто написАть "str5 = "351240";" чтобы получить в окне about красивый номер "012345".
Пятая:

            writer2.Seek(473857, SeekOrigin.Begin);
            str3 = "<RSAKeyValue><Modulus>тут неважнодлинный модуль</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            writer2.Seek(0x73b01, SeekOrigin.Begin);
            Class11.string_7 = "1";
            int num5 = Class11.string_7.Length - 1;
            for (num4 = 0; num4 <= num5; num4++)
            {
                buffer3[0] = (byte) Strings.Asc(Class11.string_7.Substring(num4, 1));
                writer2.Write(GClass30.smethod_0(buffer3, str3));
            }

Переход в другое место файла и опять какая-то единица.
Всё, больше ничего не пишется. Ок, теперь нам нужно узнать как и где это всё используется. Мы уже знаем, что где-то берётся "PermissionString", который передаётся в конструктор "SetupForm", ок, с этого и начнём: в dotPeek на конструкторе SetupForm тыкаем правой кнопкой и выбираем "Find Usages", смотрим что нашлось (не забываем, что в dotPeek должны быть при этом открыты и Setup.dll и Warehouse.exe, иначе ничего не найдёт):

Microinvest.WarehousePro.BackUpDatabase.linkSettings_Click(object sender, EventArgs e)
Microinvest.WarehousePro.mdiShell.mnuOtherSetup_Click(object sender, EventArgs e)

Первое нас явно не интересует, нам нужна форма, которая открывается при клике на пункт "настройки", посему открываем второе:

      SetupForm setupForm = new SetupForm(ref Class11.configuration_0, Class11.database_0, Class11.database_1, Class11.string_8, (int) Class11.gclass41_0.TUser_1.UserLevel);
      int num = (int) setupForm.ShowDialog();

Ага! Вот она, PermissionString, это на самом деле "Class11.string_8". Делаем "Find Usages" на этом "string_8", дабы найти то место, где в эту переменную присваивается значение. Поиск показывает штук 6 результатов, из которых присвоение значения происходит только в двух: в LicenseForm проверке серийника и в методе Main() самого этого класса Class11. Метод проверки серийника мы уже смотрели достаточно, посему интересней будет посмотреть что происходит в самом классе Class11, который по всем параметрам выглядит классом, в котором содержится информация о лицензии и разрешениях.

            do
            {
              byte_0[index1] = numArray[checked (466945 + index1 + 6912)];
              checked { ++index1; }
            }
            while (index1 <= (int) sbyte.MaxValue);
            Class11.string_7 += Conversions.ToString(Strings.Chr((int) GClass30.smethod_1(byte_0, "<RSAKeyValue>тут неважнодлинные параметры RSA</RSAKeyValue>")[0]));
            int index2 = 0;
            do
            {
              byte_0[index2] = numArray[checked (466945 + index2)];
              checked { ++index2; }
            }
            while (index2 <= (int) sbyte.MaxValue);
            Class11.bool_1 = Conversions.ToInteger(Strings.Chr((int) GClass30.smethod_1(byte_0, "<RSAKeyValue>тут неважнодлинные параметры RSA</RSAKeyValue>")[0]).ToString()) == 1;
            if (!Class11.bool_1)
            {
               // мы не лицензированы, переписываем лицензию мусором
            } else {
              // продолжаем проверять лицензию
            }

Итак, мы видим два чтения, каждый по одному байту. string_7 пока непонятно что, bool_1 что-то навроде "isLicensed", чтобы оно было true, в файле на этом месте должна быть единица. Ок, продолжим проверку.
Первое чтение:

              do
              {
                int index3 = 0;
                do
                {
                  byte_0[index3] = numArray[checked (466945 + index3 + 7936 + num2 * 128)];
                  checked { ++index3; }
                }
                while (index3 <= (int) sbyte.MaxValue);
                string_11 += Conversions.ToString(Strings.Chr((int) GClass30.smethod_1(byte_0, "<RSAKeyValue>тут неважнодлинные параметры RSA</RSAKeyValue>")[0]));
                checked { ++num2; }
              }
              while (num2 <= 5);
              Class11.string_9 = string_11.Substring(5, 1) + string_11.Substring(2, 1) + string_11.Substring(3, 1) + string_11.Substring(0, 1) + string_11.Substring(4, 1) + string_11.Substring(1, 1);

Сразу понятно, что это чтение шестизначного номера лицензии для отображения в окне about, непонятно зачем им потребовалось перемешивать символы в строке, но оставим это на их совести.
Второе чтение:

              do
              {
                int index3 = 0;
                do
                {
                  byte_0[index3] = numArray[checked (466945 + index3 + 8704 + num3 * 128)];
                  checked { ++index3; }
                }
                while (index3 <= (int) sbyte.MaxValue);
                string_12 += Conversions.ToString(Strings.Chr((int) GClass30.smethod_1(byte_0, "<RSAKeyValue>тут неважнодлинные параметры RSA</RSAKeyValue>")[0]));
                checked { ++num3; }
              }

Пока неясно что это за string_12.
Третье чтение:

              do
              {
                int index3 = 0;
                do
                {
                  byte_0[index3] = numArray[checked (466945 + index3 + 128 + num4 * 128)];
                  checked { ++index3; }
                }
                while (index3 <= (int) sbyte.MaxValue);
                string_13 += Conversions.ToString(Strings.Chr((int) GClass30.smethod_1(byte_0, "<RSAKeyValue>тут неважнодлинные параметры RSA</RSAKeyValue>")[0]));
                checked { ++num4; }
              }
              while (num4 <= 24);

Что-то длинной 25 символов в string_13, серийник или лицензионный ключ, пока неясно.
Четвёртое чтение:

              do
              {
                int index3 = 0;
                do
                {
                  byte_0[index3] = numArray[checked (466945 + index3 + 3328 + num5 * 128)];
                  checked { ++index3; }
                }
                while (index3 <= (int) sbyte.MaxValue);
                Class11.string_8 += Conversions.ToString(Strings.Chr((int) GClass30.smethod_1(byte_0, "<RSAKeyValue>тут неважнодлинные параметры RSA</RSAKeyValue>")[0]));
                checked { ++num5; }
              }
              while (num5 <= 24);

Ещё 25 символов в string_8. Как мы помним из кода записи лицензионного ключа, там писАлись последовательно "серийник, лицензионный ключ". Соответственно, делаем предположение: string_13 — серийник, string_8 — лицензионный ключ.
На этом чтение заканчивается и вызывается какая-то проверка:

              if (!Class11.smethod_19(string_11, string_12, string_13))
              {
                Class11.string_4 = "150";
                Class11.string_6 = "30";
              }

Вот это уже похоже на проверку валидности лицензионного ключа, смотрим в этот метод:

    private static bool smethod_19(string string_11, string string_12, string string_13)
    {
      double a = 0.0;
      if (string_13.Length != 25)
        return false;
      int num1 = 0;
      int num2 = checked (string_13.Length - 2);
      int startIndex1 = num1;
      while (startIndex1 <= num2)
      {
        a += Conversions.ToDouble(string_13.Substring(startIndex1, 1));
        checked { ++startIndex1; }
      }
      if (
              Microsoft.VisualBasic.CompilerServices.Operators.CompareString(
                                       string_13.Substring(checked (string_13.Length - 1), 1), 
                                       checked (unchecked (checked ((long) Math.Round(a)) / 3L) + 6L).ToString().Substring(checked ((unchecked (checked ((long) Math.Round(a)) / 3L) + 6L).ToString().Length - 1), 1), false) != 0)
        return false;

В самом начале мы вернём ошибку в двух случаях: длина string_13 не равна 25. Второй случай — проверка некой контрольной суммы серийника: "последний символ серийника должен быть равен последнему символу суммы первых 23х символов этого серийника". Чуть попроще будет выглядеть так:

            int num2 = checked(string_13.Length - 2);
            int startIndex1 = num1;
            while (startIndex1 <= num2)
            {
                a += Conversions.ToDouble(string_13.Substring(startIndex1, 1));
                checked { ++startIndex1; }
            }
            string firstString = string_13.Substring(string_13.Length - 1, 1);
            string innerString = ((long)Math.Round(a) / 3L + 6L).ToString();
            string secondString = innerString.Substring(innerString.Length - 1, 1);
            if (Microsoft.VisualBasic.CompilerServices.Operators.CompareString(
                firstString, 
                secondString
                , false) != 0)
                return false;

Идём дальше.
Если чексумма у серийника сошлась, то начинаем декодировать серийник (только первые 20 символов):

      string str1 = "";
      int num3 = 0;
      int num4 = checked (string_13.Length - 5);
      int startIndex2 = num3;
      while (startIndex2 <= num4)
      {
        int startIndex3;
        str1 += Conversions.ToString(Math.Abs((100.0 + (Conversion.Val(string_13.Substring(startIndex2, 1)) - Conversion.Val(string_13.Substring(21, 3).Substring(startIndex3, 1)) * 
                                              (double) checked (-(unchecked ((uint) (startIndex2 % 3 == 0) > 0U) ? 1 : 0) * 2 + 1) - (double) startIndex2)) % 10.0));
        checked { ++startIndex3; }
        if (startIndex3 == 3)
          startIndex3 = 0;
        checked { ++startIndex2; }
      }

В качестве "ключа" используются 21, 22 и 23 символы этого же самого серийника.
После декода происходит проверка на версию операционной системы, размер жёсткого диска, и ID процессора:

      string_13 = str1 + string_13.Substring(str1.Length);
      if (
          (CommonModule.OSVersion) checked ((byte) Math.Round(Conversion.Val(string_13.Substring(12, 2)))) != CommonModule.GetOSVersion() || 
          Conversions.ToDouble(string_13.Substring(8, 4)) != Class11.smethod_21() || 
          Microsoft.VisualBasic.CompilerServices.Operators.CompareString(Conversions.ToString(Conversions.ToInteger(string_13.Substring(0, 1)) % 2) + string_13.Substring(1, 2), Class11.smethod_20(), false) != 0)
        return false;

Напомню, что серийник программа генерит сама в окне регистрации, так что по-честному нас все эти проверки волновать не должны.
А вот то, ради чего мы, в принципе, всё это смотрели:
while (startIndex4 <= num8)
{
str3 = !flag ? str3 + Conversions.ToString(Math.Abs((100.0 + Conversion.Val(Class11.string_8.Substring(startIndex4, 1)) - (double) checked (Conversions.ToInteger(Class11.string_8.Substring(0, 1)) *
-(unchecked ((uint) (startIndex4 % 2 == 0) > 0U) ? 1 : 0) * 2 + 1) - (double) startIndex4) % 10.0)) :
str3 + Conversions.ToString(Math.Abs((100.0 + Conversion.Val(Class11.string_8.Substring(startIndex4, 1)) -
(double) checked (Conversions.ToInteger(string_13.Substring(23, 1)) * -(unchecked ((uint) (startIndex4 % 2 == 0) > 0U) ? 1 : 0) * 2 + 1) - (double) startIndex4) % 10.0));
checked { ++startIndex4; }
}
Class11.string_8 = str3.Substring(15, 4);
return true;
}
Вслучае, если всё проходит гладко, то в string_8(а это, как вы помните, искомый нами PermissionString) записывается 4 символа (для нас желательно, чтобы они были "0005") из нашего введённого лицензионного ключа.
Декод похож на декод лицензии, за исключением того, что в качестве ключа используется только 24-ый символ из лицензии.
Что мы можем сделать? Например, написАть вот такой вот код:

        private static string getSerial(int number)
        {
            string enteredSerial = "9999999999999990005999999";
            string result = "";
            for (int i = 0; i < enteredSerial.Length; i++)
            {
                result += Conversions.ToString(Math.Abs(
                                                        (10.0 +
                                                        Conversion.Val(enteredSerial.Substring(i, 1)) +
                                                        number * (double)checked(-(unchecked(i % 2 == 0) ? 1 : 0) * 2 + 1) +
                                                        (double)i)
                                                  % 10.0));
                if ((i+1) % 5 == 0) {
                    result += " ";
                }
            }
            return number + ":" + result;
        }

        for (int i = 0; i < 10; i++)
        {
          Console.WriteLine(getSerial(i));
        }

Так мы получим все (10 штук) рабочие серийники, которые будут валидны в случае самого первого патча:

0: 90123 45678 90123 56738 90123
1: 81032 54769 81032 65829 81032
2: 72941 63850 72941 74910 72941
3: 63850 72941 63850 83001 63850
4: 54769 81032 54769 92192 54769
5: 45678 90123 45678 01283 45678
6: 36587 09214 36587 10374 36587
7: 27496 18305 27496 29465 27496
8: 18305 27496 18305 38556 18305
9: 09214 36587 09214 47647 09214

Выбор серийника зависит лишь от предпоследней цифры лицензионного ключа, который вам показала сама программа.

Я думал написАть подробно ещё и про то, как не патчить программу вообще, но чот энтузиазм затих. Вкраце: мы рассмотрели где, как и что записывается в файл, где как и что из этого файла читается. Всё, что нам нужно — повторить процесс записи так, чтобы записАть нужные значения.

        private void write()
        {
            Type type = typeof(Form);
            string path = @"c:\windows\assembly\gac_msil\operations\3.0.1.0__14a76cee500f0423\operations.dll";
            FileStream fileStream = new FileStream(path, FileMode.Open);
            BinaryWriter writer = new BinaryWriter((Stream)fileStream);

            byte[] buff = new byte[1]
                {
                  (byte) 0
                };

            writer.Seek(474881, SeekOrigin.Begin);
            string reversedLicenseKey = "<RSAKeyValue><Modulus>подставьте сюда правильный модуль из декомпилированого кода</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            string reversedLicense = "352242";
            for (int i = 0; i < reversedLicense.Length; i++)
            {
                buff[0] = checked((byte)Strings.Asc(reversedLicense.Substring(i, 1)));
                writer.Write(GClass30.smethod_0(buff, reversedLicenseKey));
            }

            writer.Seek(466945, SeekOrigin.Begin);
            string oneKey = "<RSAKeyValue><Modulus>подставьте сюда правильный модуль из декомпилированого кода</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            string one = "1";
            for (int i = 0; i < one.Length; i++)
            {
                buff[0] = checked((byte)Strings.Asc(one.Substring(i, 1)));
                writer.Write(GClass30.smethod_0(buff, oneKey));
            }

            string mainNumberKey = "<RSAKeyValue><Modulus>подставьте сюда правильный модуль из декомпилированого кода</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            string mainNumber = getMain(); // я написАл метод, который генерит лицензионный ключ, но вы можете просто забить сюда тот ключ, который вам покажет сама программа
                                                       // если сильно интересно, то можете сделать то же самое, это несложно, в самой программе есть методы для получения размера диска
                                                       // ID процессора и версии OS, вам нужно просто их скопировать к себе, вызвать и правильно поставить на места в лицензионном номере
            double a = 0;
            for (int i = 0; i < mainNumber.Length; i++)
            {
                a += Conversions.ToDouble(mainNumber.Substring(i, 1));
            }
            string innerString = ((long)Math.Round(a) / 3L + 6L).ToString();
            string secondString = innerString.Substring(innerString.Length - 1, 1);
            mainNumber += secondString;
            for (int i = 0; i < mainNumber.Length; i++)
            {
                buff[0] = checked((byte)Strings.Asc(mainNumber.Substring(i, 1)));
                writer.Write(GClass30.smethod_0(buff, mainNumberKey));
            }
            string enteredSerialKey = "<RSAKeyValue><Modulus>подставьте сюда правильный модуль из декомпилированого кода</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            string enteredSerial = getSerial(); // в зависимости от предпоследнего символа в лицензии (mainNumber) выберите правильный серийник из десяти приведённых выше (без пробелов, естестно)
            for (int i = 0; i < enteredSerial.Length; i++)
            {
                buff[0] = checked((byte)Strings.Asc(enteredSerial.Substring(i, 1)));
                writer.Write(GClass30.smethod_0(buff, enteredSerialKey));
            }
            writer.Seek(473857, SeekOrigin.Begin);
            string oneKey2 = "<RSAKeyValue><Modulus>подставьте сюда правильный модуль из декомпилированого кода</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
            string one2 = "1";
            for (int i = 0; i < one2.Length; i++)
            {
                buff[0] = checked((byte)Strings.Asc(one2.Substring(i, 1)));
                writer.Write(GClass30.smethod_0(buff, oneKey2));
            }

            writer.Close();
        }

Вот что получилось у меня. Этот код прописывает нужные символы в файл с лицензией и программа при старте всё правильно читает. Никакие патчи не нужны.
the end.

P.S. email невалидный, логин на форуме этот же.



Обсуждение статьи: [.NET] Microinvest Warehouse Pro, заставляем работать без патчей >>>


Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS