提问者:小点点

如何在运行时访问Java类?


我目前正在学习网络安全课程,并被赋予了一个任务,即实现一种在运行时与Java应用程序交互的方法。

目标是访问应用程序的Java虚拟机(JVM)并调用应用程序使用的特定类的方法,以便我们可以在应用程序运行时通过代码与应用程序通信。

我的导师给了我们一个可执行文件

应用程序作为可执行文件(. exe)运行,使用OpenJDK,但它本质上是一个JAR文件。

从我对程序工作原理的调查中,我可以看到它在类路径中包含其他几个JAR文件,其中一个JAR包含我所追求的类。

我已经安装了JDK20,并在Visual Studio中编写了一个C程序,将DLL文件注入给定进程(应用程序的PID):


#include <Windows.h>
#include <iostream>

using namespace std;

HANDLE GetProcessHandle(DWORD procId)
{
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, procId);

    if (hProc == NULL)
    {
        cout << "OpenProcess failed. Make sure you have the correct permissions.\n";
        return NULL;
    }

    return hProc;
}

int main()
{
    DWORD procId;
    cout << "Enter the PID of the target process: ";
    cin >> procId;

    const char* dllPath = "C:\\Users\\Student\\source\\repos\\my_dll_creation\\x64\\Debug\\my_dll_creation.dll";

    HANDLE hProc = GetProcessHandle(procId);

    if (hProc && hProc != INVALID_HANDLE_VALUE)
    {
        void* loc = VirtualAllocEx(hProc, 0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

        if (loc)
        {
            if (!WriteProcessMemory(hProc, loc, dllPath, strlen(dllPath) + 1, 0)) {
                cout << "Failed to write process memory. Please check permissions.\n";
                CloseHandle(hProc);
                return 1;
            }
        }
        else {
            cout << "Failed to allocate virtual memory.\n";
            CloseHandle(hProc);
            return 1;
        }

        HANDLE hThread = CreateRemoteThread(hProc, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, loc, 0, 0);

        if (hThread)
        {
            CloseHandle(hThread);
        }
        else
        {
            cout << "Failed to create remote thread.\n";
            VirtualFreeEx(hProc, loc, 0, MEM_RELEASE);
            CloseHandle(hProc);
            return 1;
        }

        // Cleanup
        VirtualFreeEx(hProc, loc, 0, MEM_RELEASE);

        // Call the ListClasses() function in the DLL.
        HMODULE hModule = GetModuleHandle(NULL);
        if (hModule)
        {
            FARPROC ptrListClasses = GetProcAddress(hModule, "ListClasses");
            if (ptrListClasses)
            {
                (void)ptrListClasses();
            }
        }
    }

    if (hProc)
    {
        CloseHandle(hProc);
    }

    return 0;
}

我的实际DLL代码如下所示:


#include <iostream>
#include <jni.h>
#include <wtypes.h>
#include <jvmti.h>

using namespace std;

JavaVM* jvm = nullptr;
JNIEnv* env = nullptr;
HANDLE hThread = NULL;

DWORD WINAPI ThreadFunc(void* data) {
    AllocConsole();
    freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);

    jsize jvmCount;
    JNI_GetCreatedJavaVMs(&jvm, 1, &jvmCount);

    if (jvmCount > 0) {
        jint attachThreadResult = jvm->AttachCurrentThread((void**)&env, NULL);

        if (attachThreadResult == JNI_OK) {
            cout << "Successfully attached to current thread" << endl;;

            jvmtiEnv* jvmti;
            jvm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_0);

            jint classCount;
            jclass* classes;
            jvmti->GetLoadedClasses(&classCount, &classes);

            cout << "Found: " << classCount << " classes" << endl;

            for (int i = 0; i < classCount; i++) {
                jclass cls = classes[i];

                // Get java.lang.Class class
                jclass classClass = env->FindClass("java/lang/Class");
                if (classClass == nullptr) {
                    cout << "Could not find the java.lang.Class" << endl;
                }
                else {
                    // Get getClassLoader method ID
                    jmethodID getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
                    jmethodID getNameMethod = env->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
                    if (getClassLoaderMethod == nullptr || getNameMethod == nullptr) {
                        cout << "Could not find the getClassLoader or getName method" << endl;
                    }
                    else {
                        // Call getClassLoader
                        jobject classLoader = env->CallObjectMethod(cls, getClassLoaderMethod);
                        jstring nameJString = (jstring)env->CallObjectMethod(cls, getNameMethod);
                        const char* nameCStr = env->GetStringUTFChars(nameJString, NULL);

                        if (classLoader == nullptr) {
                            continue;
                        }
                        else {
                            if (strcmp(nameCStr, "net.tma.api.Client") == 0) {
                                // Do something with the class...
                                cout << "Found net.tma.api.Client!" << endl;
                            }
                        }
                        env->ReleaseStringUTFChars(nameJString, nameCStr);
                    }
                }
            }

            // Detach the thread from the Java VM
            jvm->DetachCurrentThread();
        }
        else {
            cout << "Failed to attach to current thread" << endl;
        }
    }
    else {
        cout << "No JVM found" << endl;
    }

    FreeConsole();
    return 0;
}


BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        FreeConsole();
        break;
    }
    return TRUE;
}

上面的DLL产生了“找到net. tma.api.Client!”但它不是我可以使用的东西。据我所知,它只是类对象。

当我注入我的DLL时,我遇到的另一个问题是我不能立即访问正在运行的类的任何实例。我所说的运行类是指为应用程序本身创建的自定义类,而不是标准Java库类。

当我尝试时,这是显而易见的:

jclass myDesiredClass = env->FindClass("net/tma/api/Client");
if (myDesiredClass != NULL) {
    cout << "Access to net.tma.api.Client class\n";
} else {
    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
    cout << "No access to net.tma.api.Client class\n";
}

并收到

无法访问net. tma.api.Client类

据我所知,当我附加到我新创建的线程时,我卡在引导类加载器级别。

我需要从中遍历(或转义)并进入系统类加载器。所以我使用上面的方法来处理它,但这只给我类,而不是我所追求的类的实例。换句话说,我不能从这里做任何有用的事情。

我的目标是获取这个net. tma.api.Client类的一个实例并调用它的getIndex方法(它返回一个int)。我甚至不确定我是否以正确的方式处理它。

该类是JAR文件(名为client-api. jar)的一部分,该文件包含在主JAR文件的类空间中。client-api.jar包含net.tma.api.Client

我怎样才能实现我的目标?我觉得我在这里错过了什么。


共1个答案

匿名用户

除了从java调用的JNI代码之外,FindClass是不可用的(即java中添加了public native void foo();和实现它的本机代码-可以使用FindClass)。

这是因为FindClass需要知道使用哪个类加载器;为此,它扫描调用堆栈——特别是,调用堆栈中最顶层的java类被获取,然后要求该类的类加载器找到该类。

在您的上下文中,您没有这样的东西/它是java. lang.东西,因此您获得了引导加载程序。

解决方案相当简单:不要调用该方法——它不是为此而生的。你在一个JVMTI上下文中。它工作的事实有点令人震惊。

Java使用即时类加载:它不会在启动时加载每个类,相反,它只加载需要的内容,并缓存结果。换句话说,当您的JVMTI代码附加时,您要查找的类可能还没有加载。因此,您只有两个选择:要么在C代码要求时强制加载它,要么注册一个回调:如果我感兴趣的某个类被加载,请执行此本机代码。

有一个专门为此设计的ClassLoad事件。这里有一个指向JVMTI文档的事件部分的链接来解释这一点。“我想从我的C代码中加载类”的问题是JVMTI从来都不是为此而设计的。它是一个插桩库,旨在修改运行中的事物并监控正在发生的事情。它不是放置业务逻辑的地方,“加载类”听起来像业务逻辑。因此,我不确定你是否可以在没有hackery的情况下强制加载一个类(你可以在现在运行的任何强制加载该类的东西中注入一些java代码,然后你就可以通过注册ClassLoad事件在JVMTI代码中对此做出反应)。

jvmti具有完成循环的C函数“GetLoadedClass”。因此,为了完整性:

  • 调用*SetEventCallBacks(jvmtiEnv*, jniNativeInterface**)将您自己的代理代码注册为事件“ClassLoad”的处理程序。此事件处理程序应检查您感兴趣的类是否正在加载。如果是这样,您现在拥有jclass指针,因此您现在可以运行“用它做某事”代码。
  • 然后使用GetLoadedClass(jvmtiEnv*, jint*,jclass**)列出所有加载的类。如果你感兴趣的类在这里,运行“用它做些什么”代码,你现在有一个jclass指针来运行它。

现在只需等待。无论哪种方式,您的“用它做些什么”代码都会运行,除非JVM永远不会运行任何需要加载它的代码。

这种背景应该有助于解释为什么“但我只想加载所有类”的工作根本不是你能做的。

您最多可以要求JVM加载一个类。这需要一个类加载器,否则JVM将不知道请求哪个加载器来加载您要加载的类。