我目前正在学习网络安全课程,并被赋予了一个任务,即实现一种在运行时与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
我怎样才能实现我的目标?我觉得我在这里错过了什么。
除了从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将不知道请求哪个加载器来加载您要加载的类。