JVMTI Agent.

Writing a JVMTI agent is really easy.

I’ll describe the process for darkfall.

1. You need to get a dll injected into darkfall.exe prior to when it loads sfmiddleware.dll and before it calls JNI_CreateJavaVM. In order to accomplish this, I injected a dll into Lobby.exe that hooks CreateProcess and I modify the creation flags so that darkfall.exe is started in a suspended state, from there I inject my dll into darkfall.exe.

2. Once my dll is injected into darkfall.exe, I hook 2 calls. The call to JNI_CreateJavaVM that is exported from jvm.dll and _Java_com_aventurine_loader_NativeClassLoader_loadClassBytes@12 from sfmiddleware.dll just so I can have darkfall decrypt the class bytes for me. I wrote that hook before the JVMTI agent and could probably do away with it now as you will see later.

3. My JNI_CreateJavaVM hook merely adds 4 options to the JVM startup paramenters. My code looks like so:

	jint JNICALL my_jni_create_java_vm(JavaVM **pvm, void **penv, void *args)
		JavaVMInitArgs *vm_args = reinterpret_cast(args);
		std::vector vm_options;
		for(jint i = 0; i nOptions; ++i)

		JavaVMOption jvmti_agent_option_agent_path;
		JavaVMOption jvmti_agent_option_abort;
		JavaVMOption jvmti_agent_option_exit;
		JavaVMOption jvmti_agent_option_vfprintf;
		std::string option_string("-agentpath:" + get_path_to_dll(dll_module_handle));
		jvmti_agent_option_agent_path.optionString = const_cast(option_string.c_str());

		jvmti_agent_option_abort.optionString = "abort";
		jvmti_agent_option_exit.optionString = "exit";
		jvmti_agent_option_vfprintf.optionString = "vfprintf";

		jvmti_agent_option_abort.extraInfo = reinterpret_cast(my_jvm_abort);
		jvmti_agent_option_exit.extraInfo = reinterpret_cast(my_jvm_exit);
		jvmti_agent_option_vfprintf.extraInfo = reinterpret_cast(my_jvm_vfprintf);

		vm_args->nOptions += 4;
		vm_args->options = &vm_options[0];

		return jni_create_java_vm_hook.OriginalFunc(3, pvm, penv, vm_args);


Basically all it does is give the JVM the path to my JVMTI agent and also some callbacks the JVM can use for certain events like exit and abort, these are not needed but I added them in case I needed them for some debugging.

4. Your JVMTI agent needs an exported function named Agent_OnLoad. Here is where you will initialize your JVMTI agent and fill out some bookkeeping to tell the JVM exactly which events you are interested in, and for those events, which handlers you want called.

This is what mine looks like. Note that I have borrowed some initialization code from some examples on the web.

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
	jvmtiError error;
	jint res;
	jvmtiEnv *jvmti = NULL;

	/* Setup initial global agent data area
	*   Use of static/extern data should be handled carefully here.
	*   We need to make sure that we are able to cleanup after ourselves
	*     so anything allocated in this library needs to be freed in
	*     the Agent_OnUnload() function.

	/*  We need to first get the jvmtiEnv* or JVMTI environment */

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

	if (res != JNI_OK || jvmti == NULL)
		/* This means that the VM was unable to obtain this version of the
		*   JVMTI interface, this is a fatal error.
		printf("ERROR: Unable to access JVMTI Version 1 (0x%x),"
			" is your J2SE a 1.5 or newer version?"
			" JNIEnv's GetEnv() returned %d\n",
			JVMTI_VERSION_1, res);
		return JNI_OK;


	/* Here we save the jvmtiEnv* for Agent_OnUnload(). */
	jvmti_agent = boost::shared_ptr(new Darkfall::JVM::JVMTIAgent(jvm, jvmti));

	jvmtiCapabilities jvmti_supported_interpreter_capabilities =
		0, // can_tag_objects
		1, // can_generate_field_modification_events
		1, // can_generate_field_access_events
		1, // can_get_bytecodes
		0, // can_get_synthetic_attribute
		0, // can_get_owned_monitor_info
		0, // can_get_current_contended_monitor
		0, // can_get_monitor_info
		0, // can_pop_frame
		1, // can_redefine_classes
		0, // can_signal_thread
		1, // can_get_source_file_name
		1, // can_get_line_numbers
		1, // can_get_source_debug_extension
		1, // can_access_local_variables
		0, // can_maintain_original_method_order
		1, // can_generate_single_step_events
		1, // can_generate_exception_events
		1, // can_generate_frame_pop_events
		1, // can_generate_breakpoint_events
		1, // can_suspend
		1, // can_redefine_any_class
		0, // can_get_current_thread_cpu_time
		0, // can_get_thread_cpu_time
		1, // can_generate_method_entry_events
		1, // can_generate_method_exit_events
		1, // can_generate_all_class_hook_events
		1, // can_generate_compiled_method_load_events
		0, // can_generate_monitor_events
		0, // can_generate_vm_object_alloc_events
		1, // can_generate_native_method_bind_events
		0, // can_generate_garbage_collection_events
		0  // can_generate_object_free_events

	error = jvmti->AddCapabilities(&jvmti_supported_interpreter_capabilities);
	check_jvmti_error(jvmti, error, "Unable to get necessary JVMTI capabilities.");

	jvmtiEventCallbacks callbacks = {NULL};
	memset(&callbacks, 0, sizeof(callbacks));
	callbacks.ClassLoad               = reinterpret_cast(jvmti_callback_class_load);
	callbacks.ClassPrepare            = reinterpret_cast(jvmti_callback_class_prepare);
	callbacks.ClassFileLoadHook       = reinterpret_cast(jvmti_callback_class_file_load_hook);
	callbacks.FieldAccess             = reinterpret_cast(jvmti_callback_field_access);
	callbacks.FieldModification       = reinterpret_cast(jvmti_callback_field_modification);
	callbacks.MethodEntry             = reinterpret_cast(jvmti_callback_method_entry);
	callbacks.MethodExit              = reinterpret_cast(jvmti_callback_method_exit);
	callbacks.NativeMethodBind        = reinterpret_cast(jvmti_callback_native_method_bind);
	callbacks.VMDeath                 = reinterpret_cast(jvmti_callback_vm_death);
	callbacks.VMInit                  = reinterpret_cast(jvmti_callback_vm_init);
	callbacks.VMStart                 = reinterpret_cast(jvmti_callback_vm_start);

	//cb.DynamicCodeGenerated    = reinterpret_cast(jvmti_callback_dynamic_code_generated);
	//cb.SingleStep              = jvmti_callback_single_step;
	//cb.Breakpoint              = jvmti_callback_breakpoint;
	//cb.FramePop                = jvmti_callback_frame_pop;
	//cb.Exception               = jvmti_callback_exception;
	//cb.ExceptionCatch          = jvmti_callback_exception_catch;
	//cb.ThreadStart             = jvmti_callback_thread_start;
	//cb.ThreadEnd               = jvmti_callback_thread_end;
	//cb.CompiledMethodLoad      = jvmti_callback_compiled_method_load;
	//cb.CompiledMethodUnload    = jvmti_callback_compiled_method_unload;
	//cb.DataDumpRequest         = jvmti_callback_data_dump_request;
	//cb.MonitorContendedEnter   = jvmti_callback_monitor_contended_enter;
	//cb.MonitorContendedEntered = jvmti_callback_monitor_contended_entered;
	//cb.MonitorWait             = jvmti_callback_monitor_wait;
	//cb.MonitorWaited           = jvmti_callback_monitor_waited;
	//cb.VMObjectAlloc           = jvmti_callback_vm_object_alloc;
	//cb.ObjectFree              = jvmti_callback_object_free;
	//cb.GarbageCollectionStart  = jvmti_callback_garbage_collection_start;
	//cb.GarbageCollectionFinish = jvmti_callback_garbage_collection_finish;

	error = jvmti->SetEventCallbacks(&callbacks, (jint)sizeof(callbacks));
	check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks");

	/* At first the only initial events we are interested in are VM
	*   initialization, VM death, and Class File Loads.
	*   Once the VM is initialized we will request more events.

	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FIELD_ACCESS, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FIELD_MODIFICATION, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, reinterpret_cast(NULL));
	error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_START, reinterpret_cast(NULL));

	//error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DYNAMIC_CODE_GENERATED, reinterpret_cast(NULL));
	//error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, reinterpret_cast(NULL));
	//error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, reinterpret_cast(NULL));
	//error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_BREAKPOINT, reinterpret_cast(NULL));

	check_jvmti_error(jvmti, error, "Cannot set event notification");

	/* Here we create a raw monitor for our use in this agent to
	*   protect critical sections of code.
	//error = jvmti->CreateRawMonitor(jvmti, "agent data", &(gdata->lock));
	//check_jvmti_error(jvmti, error, "Cannot create raw monitor");

	/* We return JNI_OK to signify success */
	return JNI_OK;


You should notice that I have setup callbacks for OnMethodEntry/Exit, NativeMethodBind and some others. Also notice the JVMTI_EVENT_CLASS_FILE_LOAD_HOOK event, this is what can be used to dump out the decrypted byte code for the darkfall java code. The event handler has the following signature:

void JNICALL jvmti_callback_class_file_load_hook( jvmtiEnv *jvmti_env
, JNIEnv* jni_env
, jclass class_being_redefined
, jobject loader
, const char* name
, jobject protection_domain
, jint class_data_len
, const unsigned char* class_data
, jint* new_class_data_len
, unsigned char** new_class_data

Notice that it even has a parameter that can be used to redefine the class. This can be handy if you want to add some custom code to a java class file, say, to call some native code of yours or even to call some java code you have written. This is how for example AspectJ is able to add advice. (They use a java implementation though).

That’s about it for this installation. I think you’ll find that this stuff is pretty easy to do. Next time I will go over how your code can get via breakpoints, and why these might come in handy. I also might give some tips on how one might go about successfully getting information for 4700 obfuscated .java files.


~ by ra1ndog on July 11, 2009.

5 Responses to “JVMTI Agent.”

  1. I’m curious to know if any of the disassembled classes are recognizable/usable.
    Seem’s like a high number of of classes. I could think of some nasty obfuscation tricks to lead to that high number.

    I’m a glutton for punishment and went back in trying to work off a static offset to some basic vars. The Java virtual mem layer makes it all but impossible. When things like X/Z/Y are updares, the stacked is insanely large, and at some point the JVM is parsing strings to determine how mem is laid out.

    I’m going to simply hook the UpdateLocation function I found and be done with it for now. If they end up CRC checking mem oh well.

  2. why do you do it such a complex way? Why not just have callback for ClassPrepare when it is already loaded and insert agent via JAVA_TOOL_OPTIONS environment variable?

    • Ok, hooking into ClassFileLoadHook and simply saving class_data gave me 4472 class files. Many of those really interesting šŸ™‚ I am now going to play with method instrumentation a bit, to see if I’ll be able to change behavior.

      • I have some questions regarding this work. if you could shoot me an email @ replic8tor at gmail dot com that would be excellent.

  3. so… darkfall is written in java?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: