Начинающий
- Статус
- Оффлайн
- Регистрация
- 14 Мар 2025
- Сообщения
- 76
- Реакции
- 3
В данном гайде мы разберем, что такое JNI и как же его использовать.
JNI (Java Native Interface) — это интерфейс для связи Java с C++, так называемый мост, который позволяет переносить критичную логику, например авторизацию или проверку лицензий, в нативный код (.dll). Это усложняет реверс-инжиниринг, так как байткод Java легко декомпилируется, в отличие бинарника c++.
Зачем нужен JNI?
- Защита кода: Логика в .dll сложнее для анализа, чем в .jar. Например, проверка токена на Java легко выдирается декомпилятором, а в C++ — это бинарник, который требует серьёзных усилий для реверса, а так же имеет больше потенциала в сфере защиты и при этом, защитить dll намного проще в отличие от jar.
- Производительность: Нативный код быстрее для задач вроде криптографии, где JVM добавляет оверхед.
1. Загрузка библиотеки
System.loadLibrary загружает .dll в JVM. Для
loadLibrary("mybridge")
JVM ищет mybridge.dll в случае windows, mybridge.so - линукс. Если файл не найден или зависимости отсутствуют, вылетает UnsatisfiedLinkError.Приведу простой пример использования JNI:
MyBridge.java example:
public class MyBridge {
static {
System.loadLibrary("mybridge");
}
public native int add(int a, int b);
public static void main(String[] args) {
System.out.println(new MyBridge().add(2, 3)); // Вызов C++
}
}
MyBridge.cpp:
#include
#include "MyBridge.h"
JNIEXPORT jint JNICALL Java_MyBridge_add(JNIEnv* env, jobject, jint a, jint b) {
return a + b;
}
В этом примере, мы вызываем функцию для сложения
Java_MyBridge_add
написанную на c++ и использованную в java коде
вызов:
public native int add(int a, int b);
Перейдем к ошибкам:
- UnsatisfiedLinkError: .dll не найдена. Для устранения, нужно положить её рядом с .jar или настроить
java.library.path
- Зависимости: Если .dll требует внешние библиотеки (например, OpenSSL), нужно убедиться, что все они подключены.
System.loadLibrary
просит JVM найти .dll по имени, добавляя суффикс .dll в Windows, so в Linux.- JVM использует ClassLoader для поиска файла в
java.library.path
или рядом с .jar. - ОС мапит .dll в память процесса, разрешает символы и запускает инициализацию.
2. Сигнатуры и заголовки
JNI требует точного соответствия имён функций. Будем использовать javac -h для генерации заголовков.
Пример:
-
Java:
public native String greet(String name);
-
c++ выходной .h:
JNIEXPORT jstring JNICALL Java_com_example_MyClass_greet(JNIEnv* env, jobject, jstring);
javac -h . MyClass.java
создаёт MyClass.h с правильными сигнатурами.JNIEXPORT
делает функцию видимой для JVM, JNICALL задаёт соглашение вызова.- Статические методы используют jclass, методы экземпляра — jobject.
jint
вместо jlong
) приведёт к UnsatisfiedLinkError или некорректному доступу к памяти.3. JNIEnv и взаимодействие с JVM
JNIEnv — это указатель на таблицу функций, через который C++ взаимодействует с JVM: создаёт объекты, вызывает методы, бросает исключения.
Пример :
NativeDemo.cpp:
#include
#include "NativeDemo.h"
JNIEXPORT jstring JNICALL Java_NativeDemo_greet(JNIEnv* env, jobject, jstring name) {
const char* s = env->GetStringUTFChars(name, nullptr);
std::string out = "Hello, " + std::string(s);
env->ReleaseStringUTFChars(name, s);
return env->NewStringUTF(out.c_str());
}
- JNIEnv локален для потока. Нативный поток должен вызвать AttachCurrentThread для получения JNIEnv и DetachCurrentThread после.
- Неправильное использование JNIEnv (например, кэширование между потоками) вызывает сбои.
4. Типы данных
JNI-типы обеспечивают согласованность между Java и C++.
Java | JNI | C++ эквивалент |
int | jint | int32_t |
boolean | jboolean | uint8_t |
long | jlong | int64_t |
float | jfloat | float |
double | jdouble | double |
char | jchar | uint16_t |
-
Строки:
const char* s = env->GetStringUTFChars(jstr, nullptr); → env->ReleaseStringUTFChars(jstr, s);
-
Массивы:
jint* arr = env->GetIntArrayElements(jarr, nullptr); → env->ReleaseIntArrayElements(jarr, arr, 0);
- Объекты: Создавайте через
env->NewObject
сFindClass
иGetMethodID
.
5. Управление памятью
- Память: JVM не управляет malloc/new в C++. Освобождайте вручную.
- Локальные
(
jobject
,
jstring
)
очищаются при выходе из функции или через DeleteLocalRef. - Глобальные
(
NewGlobalRef)
сохраняются до DeleteGlobalRef.
- Локальные
- Пример: Кэширование класса в JNI_OnLoad:
OnLoad:
jclass g_cls = env->NewGlobalRef(env->FindClass("com/example/MyClass"));
- Пропуск освобождения ссылок приводит к утечкам, блокирующим GC (Garbage Collection, сборщик мусора) — это автоматический процесс Java Virtual Machine, освобождающий память, удаляя объекты на которых, нету больше ссылок.
6. Исключения
-
бросайте через:
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), "msg");
- Проверяйте
env->ExceptionCheck()
после вызовов JNI, чтобы избежать ошибок.
Пример:
JNIEXPORT void JNICALL Java_Example_check(JNIEnv* env, jobject, jstring input) {
if (!input) {
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), "Null input");
return;
}
}
7. Перенос логики для скрытия критичных участков кода:
Пример: проверка лицензии.
SecureCheck.java:
public class SecureCheck {
static {
System.loadLibrary("secure");
}
public native boolean verifyLicense(String key);
}
SecureCheck.cpp:
#include
#include
#include "SecureCheck.h"
JNIEXPORT jboolean JNICALL Java_SecureCheck_verifyLicense(JNIEnv* env, jobject, jstring key) {
const char* k = env->GetStringUTFChars(key, nullptr);
if (!k) {
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), "Invalid key");
return false;
}
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)k, strlen(k), hash);
env->ReleaseStringUTFChars(key, k);
return hash[0] == 0xFF; // Упростим для демо
}
Защита:
- Логика в .dll, а не в байткоде.
- Для усиления обфусцируйте .dll (например, VMProtect).
8. Автоматизация с native-obfuscator
Пожалуйста, авторизуйтесь для просмотра ссылки.
(noad) Автоматически конвертит .class в .cpp для JNI, сохраняя логику. Доступен исходный код.
На этом гайд подходит к концу, для его написания я очень постарался и надеюсь, что кто-то откроет для себя, что-то новое.
Если многим зайдет данный гайд, то в следующем расскажу, про сам JVM, classFileParser и создадим собственную custom jvm с отключением jvm-ti и правильной оптимизированной сборкой, а так же просто вызов JVM_CreateJava_VM из jvm.dllгайд написал
Пожалуйста, авторизуйтесь для просмотра ссылки.
Последнее редактирование: