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

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

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

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

[JAVA] Jindent. RSA+Base64, кейген без дебага.

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

Очень удобно, когда все крэкерские инструменты, книги и статьи в одном месте. Используйте сборник от EXELAB - вот тут.

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

Жертва:
Jindent 4.2.4 (http://plugins.netbeans.org/plugin/44468) есть ещё оффсайт (jindent.com), но там дитрибутив в 10 раз толще, палевно качать.

Инструменты:
Netbeans (https://netbeans.org/)
Procyon Decompiler (https://bitbucket.org/mstrobel/procyon)
YAFU (http://sourceforge.net/projects/yafu/)

Задумался я как-то, что надоело мне красиво код форматировать ручками, особенно переформатировать, когда новое поле классу добавляешь и всё разъезжается. Подумал, что наверняка для такого дела есть плагин (да и сам не раз задумывался что-то такое написАть). В общем, секунд 20 гугления выдали Jindent. Неглядя (благо этот плагин можно установить прямо из самого NetBeans'a) установил, проверил на первом попавшемся файле — красота! Однако, при попытке переформатировать свой класс получил ошибочку "триальная версия, не больше 500 строк кода". Это меня опечалило. Безрезультатные пол часа в гугле в поисках готового лекарства опечалили ещё сильнее, шутка ли — к инструменту для программистов ни один программист крак не сделал. В общем, поехали.
// к слову, первый (и единственный) кейген я делал лет 7 назад :)

Расаковка и декомпиляция

Первым делом, по накатанной, был опробован JD GUI. С горем пополам он jar плагина переварил, но некоторые методы декомпилировать не мог, либо декомпилировал в неадекватный код. Так в моём инструментарии появился Procyon:

java -jar "C:\Freedom\procyon-decompiler-0.5.28.jar" -jar c:\Users\obama\AppData\Roaming\NetBeans\8.0.2\modules\ext\Jindent.jar -o c:\Freedom\out

Знакомым с java всё в этой строчке должно быть понятно. Тем, кто не в курсе, аргументы по порядку: "что запускать, что распаковывать/декомпилировать, куда результат".
Полученную папку с исходниками плагина сразу же открыл в самом NetBeans'e.

Поиск стартовой точки

Следующим делом нужно было найти то место, где, собсно, лицензия проверяется. Ну или хотя бы какие-то намётки на это место, ибо код был обфускирован: методы имели весьма информативные имена ("a", "b", "c"), а строки были заXORены. Причём, в каждом классе свой метод дешифровки срок, искать несколько напряжно.
Ок, открываем настройки плагина, там самым первым пунктом идёт закладка с регистрацией. Что ж, удобно.
Смотрим на имеющиеся в jar'e пакеты, видим "jindent.customizer.gui", и он не обфусцирован, быстро глазами смотрим на классы в этих пакетах, натыкаемся на весьма соблазнительное имя "RegistrationGUIObject" в пакете "jindent.customizer.gui.elements", а в этом классе ещё и метод "installLicenseKey0" имеется. Всё. Стартовая точка найдена.

О расшифровке строковых констант

Как я уже сказал, строки заXORены, что делать, если нужно узнать что же там за константа?
Допустим, класс jindent.util.c, строка такая:

String blabla = e("]0w\u0018^Z5|\u0015QW>y\u0012TL#f\u000fOI$c\u0004BF\u0013V?\u007fy\u0014S4rv\u0019X1us\u0002E.hh\u0007B+ce\b\u0004m)/F\u0001j,$K\u001fs");

Находим в этом же классе метод "e(String)". Он используется для декодирования всех строковых констант в этом классе:

    private static String e(final String s) {
        final char[] charArray = s.toCharArray();
        for (int length = charArray.length, i = 0; i < length; ++i) {
            final char[] array = charArray;
            final int n = i;
            final char c = array[n];
            char c2 = '\0';
            switch (i % 5) {
                case 0: {
                    c2 = '\u001c';
                    break;
                }
                case 1: {
                    c2 = 'r';
                    break;
                }
                case 2: {
                    c2 = '4';
                    break;
                }
                case 3: {
                    c2 = '\\';
                    break;
                }
                default: {
                    c2 = '\u001b';
                    break;
                }
            }
            array[n] = (char)(c ^ c2);
        }
        return new String(charArray);
    }

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

class HelloWorld {
  public static void main(String[] args) {
    String blabla = e("]0w\u0018^Z5|\u0015QW>y\u0012TL#f\u000fOI$c\u0004BF\u0013V?\u007fy\u0014S4rv\u0019X1us\u0002E.hh\u0007B+ce\b\u0004m)/F\u0001j,$K\u001fs");
    System.out.println(blabla);
  }
  // Сюда вставить метод "e" целиком
}

Запускаем, получаем:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/


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

В методе инсталла лицензии вызывается следующий код:

    public static a a(final String s) throws JindentException {
        try {
            final byte[] a = c.a(s);
            int n = -1;
            int n2 = -1;
            for (int length = a.length, i = 0; i < length; ++i) {
                if (n2 == -1 && a[i] == 124) {
                    n2 = i;
                }
                if (a[i] == 0) {
                    n = i;
                    break;
                }
            }
            if (n == -1 || n2 == -1) {
                throw new JindentException();
            }
            final StringTokenizer stringTokenizer = new StringTokenizer(new String(a, 0, n, "ISO-8859-1"), "|");
            final long long1 = Long.parseLong(stringTokenizer.nextToken());
            final String nextToken = stringTokenizer.nextToken();
            final String nextToken2 = stringTokenizer.nextToken();
            final int int1 = Integer.parseInt(stringTokenizer.nextToken());
            final String nextToken3 = stringTokenizer.nextToken();
            final String nextToken4 = stringTokenizer.nextToken();
            final CRC32 crc32 = new CRC32();
            crc32.update(a, n2 + 1, a.length - (n2 + 1));
            if (long1 != crc32.getValue()) {
                throw new JindentException();
            }
            return new a(nextToken, nextToken2, int1, nextToken3, nextToken4);
        }
        catch (Exception ex) {
            throw new JindentException("blablabla, incorrect license");
        }
    }

Отсюда становится понятно, что лицензия — это закодированная строка из шести элементов с |-разделителями и нулём в конце ("CRC|1|2|3|4|5" + '\0'), причём первый элемент этой строки это CRC32 чек-сумма всей оставшейся строки.
"blablabla, incorrect license" — это сообщение нам показывают при попытке ввода мусора вместо корректной лицензии в настройках плагина.

final byte[] a = c.a(s);

В этой строчке происходит магия расшифровки, нужно смотреть метод jindent.util.c.a(String).
Дабы не захламлять лишними листингами статью, скажу, что в методе этом происходит вызов декодирующего метода, а затем unZIP декодированных байт. В анзипе ничего интересного нет, а вот декод нам важен. Происходит он в методе "b(byte[], int, int)" этого же класса:

    public static byte[] b(final byte[] array, final int n, final int n2) {
        final byte[] array2 = new byte[n2 * 3 / 4];
        int n3 = 0;
        final byte[] array3 = new byte[4];
        int n4 = 0;
        for (int i = n; i < n + n2; ++i) {
            final byte b = (byte)(array[i] & 0x7F);
            final byte b2 = jindent.util.c.c.g[b];
            if (b2 < -5) {
                jindent.debugger.a.b(e("^\u0013P|Y}\u0001Qj/<\u001bZ,nhRW4zn\u0013W(~nRU(;") + i + e("&R") + array[i] + e("4\u0016Q?rq\u0013Xu"));
                return null;
            }
            if (b2 >= -1) {
                array3[n4++] = b;
                if (n4 > 3) {
                    n3 += a(array3, 0, array2, n3);
                    n4 = 0;
                    if (b == 61) {
                        break;
                    }
                }
            }
        }
        final byte[] array4 = new byte[n3];
        System.arraycopy(array2, 0, array4, 0, n3);
        return array4;
    }

Признаюсь честно, что в нём происходит я не сразу въехал. Видно, что используется какой-то ключ, что байты собираются по 4 штуки и декодируются в другом методе. Ладно, пробуем получить из этого метода хоть какую-нибудь информацию, расшифровываем (см. выше) что же они в лог пишут при ошибке в данных, получаем: "Bad Base64 input character at i: array[i](decimal)".
Ага! Это ж Base64! Правда, с каким-то дополнительным ключом, походу, но не суть важно. Теперь, к слову, становится понятно такое частое использование константы "61" рядом с break'ами. Это код символа "=", которым заканчивается Base64 строка.
Хорошо, у нас есть алгоритм декода, нужно написАть алгоритм энкода. Идём на читать про Base64 и примеры реализации оного на Java (http://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#Java), видим некий код:

private final static String base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
...
// we add newlines after every 76 output characters, according to
// the MIME specs
if (c > 0 && (c / 3 * 4) % 76 == 0)
  r += "\r\n";

К слову, константу "base64chars" я получил одной из первых (в попытках найти полезную информацию я всегда декодировал сначала самые длинные строки), а в моменты поиска смысла алгоритма декода, в попытке придумать как закодировать строку я не раз пробегал глазами весь класс "jindent.util.c". В общем, магические "(3 * 4) % 76" прям-таки цеплялись за глаз:

    public static String a(final byte[] array, final int n, final int n2, final int n3) {
        final int n4 = n3 & 0x8;
        // проскипано, тут был ZIP
        final boolean b2 = n4 == 0;
        final int n5 = n2 * 4 / 3;
        final byte[] array2 = new byte[n5 + ((n2 % 3 > 0) ? 4 : 0) + (b2 ? (n5 / 76) : 0)];
        int i = 0;
        int n6 = 0;
        final int n7 = n2 - 2;
        int n8 = 0;
        while (i < n7) {
            a(array, i + n, 3, array2, n6);
            n8 += 4;
            if (b2 && n8 == 76) {
                array2[n6 + 4] = 10;
                ++n6;
                n8 = 0;
            }
            i += 3;
            n6 += 4;
        }
        if (i < n2) {
            a(array, i + n, n2 - i, array2, n6);
            n6 += 4;
        }
        try {
            return new String(array2, 0, n6, e("UTF-8"));
        }
        catch (UnsupportedEncodingException ex6) {
            return new String(array2, 0, n6);
        }
    }

Вау. Спасибо авторам плагина, они оставили готовый метод для кодирования строки с лицензией!
Быстренько пишем первую версию генератора лицензий:

    public static String getLicenseKey() throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();

        sb.append("licenseId").append('|');
        sb.append("boundLogins").append('|');
        sb.append("1").append('|');
        sb.append("licensed formatters").append('|');
        sb.append("some additional info");
        sb.append('\0');

        CRC32  crc32 = new CRC32();
        byte[] bytes = sb.toString().getBytes("ISO-8859-1");

        crc32.update(bytes, 0, bytes.length);

        long         crc    = crc32.getValue();
        final String result = crc + "|" + sb.toString();
        byte[]       key    = result.getBytes("ISO-8859-1");
        String license = jindent.util.c.a(key, 0, key.length, 0);
        return license;
    }

Результат:

MTQ1MTIyNzY4MnxsaWNlbnNlSWR8Ym91bmRMb2dpbnN8MXxsaWNlbnNlZCBmb3JtYXR0ZXJzfHNv
bWUgYWRkaXRpb25hbCBpbmZvAA==

Проверяем тут же, в настройках плагина... Ура, наша лицензия отобразилась в списке с нашими данными и без ошибок.
Я думал, что кейген найден. Хе-хе.
При попытке отформатировать файл с 500+ строками плагин выдал ошибку "Error 10, Incorrect license format". Я понял, что до кейгена ещё далеко.

Попытка вторая
Как же искать то место, где мне пишут "Error Code 10", если все методы, классы, переменные, строковые константы обфусцированы? Ответ лежал на поверхности, но я его не сразу заметил.
Если в GUI при проверке лицензии вызывают метод "jindent.util.c.a(String)", возможно ли, что этот же метод вызывается где-то ещё, и лицензия на самом деле не из шести токенов состоит, а чуть подлиннее?
Поиск по использованию метода jindent.util.c.a(String) показал, что так оно и есть. В классе "jindent.formatter.f" обнаружилась такая строка:

final byte[] a3 = jindent.util.c.c.a(a2);

По обзору этого гигантского метода стало понятно, что валидная лицензия имеет гораздо больше частей, чем те 6, которые я обнаружил сначала. Начало лицензии распаковывается, как описано выше, а всё остальное, что идёт после символа '\0' декодируется методом jindent.formatter.o.d():

System.arraycopy(a3, n2 + 1, array3, 0, length - (n2 + 1));
final byte[] d = new o(array3, jindent.formatter.f.plainClusterSize, jindent.formatter.f.cipherClusterSize, jindent.formatter.f.rsaE, jindent.formatter.f.rsaN, jindent.formatter.f.rsaStartVector).d();

Опять же, не стану копировать метод целиком, суть его в декодировании переданных байт кусками, размером равным cipherClusterSize:

        final BigInteger modPow = new BigInteger(array).xor(this.rsaStartVector).modPow(this.rsaE, this.rsaN);
        this.rsaStartVector = modPow;
        final byte[] byteArray = modPow.toByteArray(); // искомый декодированный кусок

Итак, перед нами RSA шифрование. Открытый/закрытый ключ, все дела... Идём читать, что есть что: http://en.wikipedia.org/wiki/RSA_(cryptosystem)#Key_generation
Итак, есть модуль n (в коде rsaN), публичная экспонента e (в коде отсутствует) и приватная экспонента d (в коде она rsaE). По описанию RSA вроде бы появляется слабая надежда: смысл не в сложности зашифровки, а в сложности дешифровки, поэтому приватную экспоненту нужно хранить в секрете. У нас же алгоритм дешифровки есть, вот он, перед нами. Нам нужно только закодировать. По тому же описанию публичная экспонента должна вроде как быть небольшой. Включаю брутфорс на ночь (кодирую и декодирую текст "blabla", проверяю на то, что раскодированный совпадает с начальным). За ночь перебрал числа от 1 до 500 000 000, безуспешно.
Думаю: "может они во время выполнения подменяют какие-то из переменных rsaN/rsaE?". Делаю патч этого метода:

    byte[] d() throws Exception {
        throw new JindentException("d: " + this.rsaE + ", n: " + this.rsaN);
    }

Компилирую:

"c:\Program Files\Java\jdk1.7.0_75\bin\javac" -cp c:\Freedom\out c:\Freedom\out\jindent\formatter\o.java

Полученный файл "c:\Freedom\out\jindent\formatter\o.class" нужно запихать обратно в c:\Users\obama\AppData\Roaming\NetBeans\8.0.2\modules\ext\Jindent.jar/jindent/formatter/o.class (если кто вдруг не знал, jar — это просто архив).
Перезапускаю NetBeans, пытаюсь отформатировать большой файл, получаю в лог эксепшен (самый простой способ вывода информации мне показался) с неизменёнными значениями. Приуныл. Запилил другой патч:

    byte[] d() throws Exception {
        return this.f;
    }

Возвращает байты в неизменном виде. Запилил себе новый ключик:

    public static String getLicenseKey() throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();

        sb.append("licenseId").append('|');
        sb.append("boundLogins").append('|');
        sb.append("1").append('|');
        sb.append("licensed formatters").append('|');
        sb.append("some additional info");
        sb.append('\0');
        sb.append("1|2|3|4|5|6|7|8|9|boundLogins|blacklisted licenses|1|500|15|0|20200101|f0R3s7");

        CRC32  crc32 = new CRC32();
        byte[] bytes = sb.toString().getBytes("ISO-8859-1");

        crc32.update(bytes, 0, bytes.length);

        long         crc    = crc32.getValue();
        final String result = crc + "|" + sb.toString();
        byte[]       key    = result.getBytes("ISO-8859-1");
        String license = jindent.util.c.a(key, 0, key.length, 0);
        return license;
    }

Скормил плагину — работает. Всё форматирует без ограничений.

Попытка третья
Однако ж, половина кейгена + патч это ещё не кейген, нужно-таки доделать.
Гуглю абсолютно странные запросы "взломать RSA", "RSA public key from private", etc. По какому-то из этих запросов наткнулся на упоминание программы YAFU для факторизации чисел. Ну, думаю, чем чёрт не шутит?
Алгоритм вычисления ключей (выше ссылку на википедию давал) таков:

p = bigNumber
q = bigNumber
n = p * q
m = (p-1) * (q-1)
d*e = 1 mod m
e = (1 mod m) / d

Я знал d и n. Мне нужен был e. Для этого нужен m, а для m нужны p и q, которые есть множители n. Итак, мне нужно было факторизовать число n. Знаете сколько YAFU потребовалось на это времени? :)

fac: factoring 65373341396800220194582007527488007406013409404705239251227585675373099259
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
rho: x^2 + 3, starting 1000 iterations on C74
rho: x^2 + 2, starting 1000 iterations on C74
rho: x^2 + 1, starting 1000 iterations on C74
pm1: starting B1 = 150K, B2 = gmp-ecm default on C74
ecm: 30/30 curves on C74, B1=2K, B2=gmp-ecm default
ecm: 74/74 curves on C74, B1=11K, B2=gmp-ecm default
ecm: 110/110 curves on C74, B1=50K, B2=gmp-ecm default, ETA: 0 sec

starting SIQS on c74: 65373341396800220194582007527488007406013409404705239251227585675373099259

==== sieving in progress (1 thread):   24672 relations needed ====
====           Press ctrl-c to abort and save state           ====
24687 rels found: 12951 full + 11736 from 120386 partial, (2030.45 rels/sec)

SIQS elapsed time = 68.0129 seconds.
Total factoring time = 89.4321 seconds


***factors found***

P37 = 1329227995784915872903807060280345027
P38 = 49181435844041887297440861230372749417

p и q найдены, e, соответственно будет

660336781785860810046282904318060680358613591564428647281645630148686917


Всё. Все данные для генерации кейгена присутствуют, финальный вариант:

    public static String getLicenseKey() throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();

        // license header
        sb.append("USSR").append('|');
        sb.append("boundLogins").append('|');
        sb.append("1").append('|');
        sb.append("All, bithces!").append('|');
        sb.append("by xa0c");
        sb.append('\0');

        // rsa encoding
        String     rsaEncodedString = "1|2|3|4|5|6|7|8|9|boundLogins|blacklisted licenses|1|500|15|0|20200101|f0R3s7";
        byte[]     stringBytes      = rsaEncodedString.getBytes();
        byte[]     encodedPart      = new byte[2048];
        int        bytesCount       = 0;
        int        encodedCount     = 0;
        BigInteger vector           = rsaStartVector;

        while (bytesCount < stringBytes.length) {
            int          size = (bytesCount + plainClusterSize < stringBytes.length)
                                ? plainClusterSize
                                : stringBytes.length - bytesCount;
            final byte[] part = new byte[size];

            System.arraycopy(stringBytes, bytesCount, part, 0, size);

            String     p       = new String(part);
            BigInteger encoded = new BigInteger(part).modPow(rsaE, rsaN).xor(vector);

            vector = new BigInteger(part);

            final byte[] encodedBytes = encoded.toByteArray();

            System.arraycopy(encodedBytes, 0, encodedPart, encodedCount, encodedBytes.length);
            encodedCount += encodedBytes.length;
            bytesCount   += size;
        }

        byte[] encodedBytes = new byte[encodedCount];

        System.arraycopy(encodedPart, 0, encodedBytes, 0, encodedCount);
        sb.append(new String(encodedBytes, "ISO-8859-1"));

        CRC32  crc32 = new CRC32();
        byte[] bytes = sb.toString().getBytes("ISO-8859-1");

        crc32.update(bytes, 0, bytes.length);

        long         crc     = crc32.getValue();
        final String result  = crc + "|" + sb.toString();
        byte[]       key     = result.getBytes("ISO-8859-1");
        String       license = encodeKey(key, 0, key.length, 0);

        return license;
    }

Расписывать какой из п|а|р|а|м|е|т|р|о|в что значит не стану, это по коду вычисляется очень быстро.

the end.

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



Обсуждение статьи: [JAVA] Jindent. RSA+Base64, кейген без дебага. >>>


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



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


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