diff options
Diffstat (limited to 'src/main/native/com/code_intelligence/jazzer/android')
5 files changed, 657 insertions, 0 deletions
diff --git a/src/main/native/com/code_intelligence/jazzer/android/BUILD.bazel b/src/main/native/com/code_intelligence/jazzer/android/BUILD.bazel new file mode 100644 index 00000000..74f98cda --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/BUILD.bazel @@ -0,0 +1,47 @@ +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") +load("@bazel_skylib//rules:copy_file.bzl", "copy_file") + +copy_file( + name = "jvmti_h_encoded", + src = "@android_jvmti//file", + out = "jvmti.encoded", + is_executable = False, + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, +) + +genrule( + name = "jvmti_h", + srcs = [ + "jvmti.encoded", + ], + outs = ["jvmti.h"], + cmd = "base64 --decode $< > $(OUTS)", + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, +) + +cc_jni_library( + name = "android_native_agent", + srcs = [ + "dex_file_manager.cpp", + "dex_file_manager.h", + "jazzer_jvmti_allocator.h", + "native_agent.cpp", + ":jvmti_h", + ], + includes = [ + ".", + ], + linkopts = [ + "-lz", + ], + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, + visibility = ["//visibility:public"], + deps = [ + "@com_google_absl//absl/strings", + "@jazzer_slicer", + ], +) diff --git a/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.cpp b/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.cpp new file mode 100644 index 00000000..b409e82b --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.cpp @@ -0,0 +1,208 @@ +// Copyright 2023 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dex_file_manager.h" + +#include <algorithm> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +#include "jazzer_jvmti_allocator.h" +#include "jvmti.h" +#include "slicer/dex_ir.h" +#include "slicer/reader.h" +#include "slicer/writer.h" + +std::string GetName(const char* name) { + std::stringstream ss; + // Class name needs to be in the format "L<class_name>;" as it is stored in + // the types table in the DEX file for slicer to find it + ss << "L" << name << ";"; + return ss.str(); +} + +bool IsValidIndex(dex::u4 index) { return index != (unsigned)-1; } + +void DexFileManager::addDexFile(const unsigned char* bytes, int length) { + unsigned char* newArr = new unsigned char[length]; + std::copy(bytes, bytes + length, newArr); + + dexFiles.push_back(newArr); + dexFilesSize.push_back(length); +} + +unsigned char* DexFileManager::getClassBytes(const char* className, + int dexFileIndex, jvmtiEnv* jvmti, + size_t* newSize) { + dex::Reader dexReader(dexFiles[dexFileIndex], dexFilesSize[dexFileIndex]); + auto descName = GetName(className); + + auto classIndex = dexReader.FindClassIndex(descName.c_str()); + if (!IsValidIndex(classIndex)) { + *newSize = *newSize; + return nullptr; + } + + dexReader.CreateClassIr(classIndex); + auto oldIr = dexReader.GetIr(); + + dex::Writer writer(oldIr); + JazzerJvmtiAllocator allocator(jvmti); + return writer.CreateImage(&allocator, newSize); +} + +uint32_t DexFileManager::findDexFileForClass(const char* className) { + for (int i = 0; i < dexFiles.size(); i++) { + dex::Reader dexReader(dexFiles[i], dexFilesSize[i]); + + std::string descName = GetName(className); + dex::u4 classIndex = dexReader.FindClassIndex(descName.c_str()); + + if (IsValidIndex(classIndex)) { + return i; + } + } + + return -1; +} + +std::vector<std::string> getMethodDescriptions( + std::vector<ir::EncodedMethod*>* encMethodList) { + std::vector<std::string> methodDescs; + + for (int i = 0; i < encMethodList->size(); i++) { + std::stringstream ss; + ss << (*encMethodList)[i]->access_flags; + ss << (*encMethodList)[i]->decl->name->c_str(); + ss << (*encMethodList)[i]->decl->prototype->Signature().c_str(); + + methodDescs.push_back(ss.str()); + } + + sort(methodDescs.begin(), methodDescs.end()); + return methodDescs; +} + +std::vector<std::string> getFieldDescriptions( + std::vector<ir::EncodedField*>* encFieldList) { + std::vector<std::string> fieldDescs; + + for (int i = 0; i < encFieldList->size(); i++) { + std::stringstream ss; + ss << (*encFieldList)[i]->access_flags; + ss << (*encFieldList)[i]->decl->type->descriptor->c_str(); + ss << (*encFieldList)[i]->decl->name->c_str(); + fieldDescs.push_back(ss.str()); + } + + sort(fieldDescs.begin(), fieldDescs.end()); + return fieldDescs; +} + +bool matchFields(std::vector<ir::EncodedField*>* encodedFieldListOne, + std::vector<ir::EncodedField*>* encodedFieldListTwo) { + std::vector<std::string> fDescListOne = + getFieldDescriptions(encodedFieldListOne); + std::vector<std::string> fDescListTwo = + getFieldDescriptions(encodedFieldListTwo); + + if (fDescListOne.size() != fDescListTwo.size()) { + return false; + } + + for (int i = 0; i < fDescListOne.size(); i++) { + if (fDescListOne[i] != fDescListTwo[i]) { + return false; + } + } + + return true; +} + +bool matchMethods(std::vector<ir::EncodedMethod*>* encodedMethodListOne, + std::vector<ir::EncodedMethod*>* encodedMethodListTwo) { + std::vector<std::string> mDescListOne = + getMethodDescriptions(encodedMethodListOne); + std::vector<std::string> mDescListTwo = + getMethodDescriptions(encodedMethodListTwo); + + if (mDescListOne.size() != mDescListTwo.size()) { + return false; + } + + for (int i = 0; i < mDescListOne.size(); i++) { + if (mDescListOne[i] != mDescListTwo[i]) { + return false; + } + } + + return true; +} + +bool classStructureMatches(ir::Class* classOne, ir::Class* classTwo) { + return matchMethods(&(classOne->direct_methods), + &(classTwo->direct_methods)) && + matchMethods(&(classOne->virtual_methods), + &(classTwo->virtual_methods)) && + matchFields(&(classOne->static_fields), &(classTwo->static_fields)) && + matchFields(&(classOne->instance_fields), + &(classTwo->instance_fields)) && + classOne->access_flags == classTwo->access_flags; +} + +bool DexFileManager::structureMatches(dex::Reader* oldReader, + dex::Reader* newReader, + const char* className) { + std::string descName = GetName(className); + + dex::u4 oldReaderIndex = oldReader->FindClassIndex(descName.c_str()); + dex::u4 newReaderIndex = newReader->FindClassIndex(descName.c_str()); + + if (!IsValidIndex(oldReaderIndex) || !IsValidIndex(newReaderIndex)) { + return false; + } + + oldReader->CreateClassIr(oldReaderIndex); + newReader->CreateClassIr(newReaderIndex); + + std::shared_ptr<ir::DexFile> oldDexFile = oldReader->GetIr(); + std::shared_ptr<ir::DexFile> newDexFile = newReader->GetIr(); + + for (int i = 0; i < oldDexFile->classes.size(); i++) { + const char* oldClassDescriptor = + oldDexFile->classes[i]->type->descriptor->c_str(); + if (strcmp(oldClassDescriptor, descName.c_str()) != 0) { + continue; + } + + bool match = false; + for (int j = 0; j < newDexFile->classes.size(); j++) { + const char* newClassDescriptor = + newDexFile->classes[j]->type->descriptor->c_str(); + if (strcmp(oldClassDescriptor, newClassDescriptor) == 0) { + match = classStructureMatches(oldDexFile->classes[i].get(), + newDexFile->classes[j].get()); + break; + } + } + + if (!match) { + return false; + } + } + + return true; +} diff --git a/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.h b/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.h new file mode 100644 index 00000000..2b7dd67a --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.h @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vector> + +#include "jvmti.h" +#include "slicer/reader.h" + +// DexFileManager will contain the contents to multiple DEX files +class DexFileManager { + public: + DexFileManager() {} + + void addDexFile(const unsigned char* bytes, int length); + unsigned char* getClassBytes(const char* className, int dexFileIndex, + jvmtiEnv* jvmti, size_t* newSize); + uint32_t findDexFileForClass(const char* className); + bool structureMatches(dex::Reader* oldReader, dex::Reader* newReader, + const char* className); + + private: + std::vector<unsigned char*> dexFiles; + std::vector<int> dexFilesSize; +}; diff --git a/src/main/native/com/code_intelligence/jazzer/android/jazzer_jvmti_allocator.h b/src/main/native/com/code_intelligence/jazzer/android/jazzer_jvmti_allocator.h new file mode 100644 index 00000000..0748c177 --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/jazzer_jvmti_allocator.h @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> + +#include "slicer/writer.h" + +class JazzerJvmtiAllocator : public dex::Writer::Allocator { + public: + JazzerJvmtiAllocator(jvmtiEnv* jvmti_env) : jvmti_env_(jvmti_env) {} + + virtual void* Allocate(size_t size) { + unsigned char* alloc = nullptr; + jvmtiError error_num = jvmti_env_->Allocate(size, &alloc); + + if (error_num != JVMTI_ERROR_NONE) { + std::cerr << "JazzerJvmtiAllocator Allocation error. JVMTI error: " + << error_num << std::endl; + } + + return (void*)alloc; + } + + virtual void Free(void* ptr) { + if (ptr == nullptr) { + return; + } + + jvmtiError error_num = jvmti_env_->Deallocate((unsigned char*)ptr); + + if (error_num != JVMTI_ERROR_NONE) { + std::cout << "JazzerJvmtiAllocator Free error. JVMTI error: " << error_num + << std::endl; + } + } + + private: + jvmtiEnv* jvmti_env_; +}; diff --git a/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp b/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp new file mode 100644 index 00000000..9f0b2ad8 --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp @@ -0,0 +1,313 @@ +// Copyright 2023 Code Intelligence GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <dlfcn.h> +#include <jni.h> + +#include <fstream> +#include <iostream> +#include <map> +#include <memory> +#include <sstream> +#include <string> +#include <unordered_set> +#include <vector> + +#include "absl/strings/str_split.h" +#include "dex_file_manager.h" +#include "jazzer_jvmti_allocator.h" +#include "jvmti.h" +#include "slicer/arrayview.h" +#include "slicer/dex_format.h" +#include "slicer/reader.h" +#include "slicer/writer.h" + +static std::string agentOptions; +static DexFileManager dfm; + +const std::string kAndroidAgentClass = + "com/code_intelligence/jazzer/android/DexFileManager"; + +void retransformLoadedClasses(jvmtiEnv* jvmti, JNIEnv* env) { + jint classCount = 0; + jclass* classes; + + jvmti->GetLoadedClasses(&classCount, &classes); + + std::vector<jclass> classesToRetransform; + for (int i = 0; i < classCount; i++) { + jboolean isModifiable = false; + jvmti->IsModifiableClass(classes[i], &isModifiable); + + if ((bool)isModifiable) { + classesToRetransform.push_back(classes[i]); + } + } + + jvmtiError errorNum = jvmti->RetransformClasses(classesToRetransform.size(), + &classesToRetransform[0]); + if (errorNum != JVMTI_ERROR_NONE) { + std::cerr << "Could not retransform classes. JVMTI error: " << errorNum + << std::endl; + exit(1); + } +} + +std::vector<std::string> getDexFiles(std::string jarPath, JNIEnv* env) { + jclass jazzerClass = env->FindClass(kAndroidAgentClass.c_str()); + if (jazzerClass == nullptr) { + std::cerr << kAndroidAgentClass << " could not be found" << std::endl; + exit(1); + } + + const char* getDexFilesFunction = "getDexFilesForJar"; + jmethodID getDexFilesForJar = + env->GetStaticMethodID(jazzerClass, getDexFilesFunction, + "(Ljava/lang/String;)[Ljava/lang/String;"); + if (getDexFilesForJar == nullptr) { + std::cerr << getDexFilesFunction << " could not be found\n"; + exit(1); + } + + jstring jJarFile = env->NewStringUTF(jarPath.data()); + jobjectArray dexFilesArray = (jobjectArray)env->CallStaticObjectMethod( + jazzerClass, getDexFilesForJar, jJarFile); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + exit(1); + } + + int length = env->GetArrayLength(dexFilesArray); + + std::vector<std::string> dexFilesResult; + for (int i = 0; i < length; i++) { + jstring dexFileJstring = + (jstring)env->GetObjectArrayElement(dexFilesArray, i); + const char* dexFileChars = env->GetStringUTFChars(dexFileJstring, NULL); + std::string dexFileString(dexFileChars); + + env->ReleaseStringUTFChars(dexFileJstring, dexFileChars); + dexFilesResult.push_back(dexFileString); + } + + return dexFilesResult; +} + +void initializeBootclassOverrideJar(std::string jarPath, JNIEnv* env) { + std::vector<std::string> dexFiles = getDexFiles(jarPath, env); + + std::cerr << "Adding DEX files for: " << jarPath << std::endl; + for (int i = 0; i < dexFiles.size(); i++) { + std::cerr << "DEX FILE: " << dexFiles[i] << std::endl; + } + + for (int i = 0; i < dexFiles.size(); i++) { + jclass bootHelperClass = env->FindClass(kAndroidAgentClass.c_str()); + if (bootHelperClass == nullptr) { + std::cerr << kAndroidAgentClass << " could not be found" << std::endl; + exit(1); + } + + jmethodID getBytecodeFromDex = + env->GetStaticMethodID(bootHelperClass, "getBytecodeFromDex", + "(Ljava/lang/String;Ljava/lang/String;)[B"); + if (getBytecodeFromDex == nullptr) { + std::cerr << "'getBytecodeFromDex' not found\n"; + exit(1); + } + + jstring jjarPath = env->NewStringUTF(jarPath.data()); + jstring jdexFile = env->NewStringUTF(dexFiles[i].data()); + + int length = 1; + std::vector<unsigned char> dexFileBytes; + + jbyteArray dexBytes = (jbyteArray)env->CallStaticObjectMethod( + bootHelperClass, getBytecodeFromDex, jjarPath, jdexFile); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + exit(1); + } + + jbyte* data = new jbyte; + data = env->GetByteArrayElements(dexBytes, 0); + length = env->GetArrayLength(dexBytes); + + for (int j = 0; j < length; j++) { + dexFileBytes.push_back(data[j]); + } + + env->DeleteLocalRef(dexBytes); + env->DeleteLocalRef(jjarPath); + env->DeleteLocalRef(jdexFile); + env->DeleteLocalRef(bootHelperClass); + + unsigned char* usData = reinterpret_cast<unsigned char*>(&dexFileBytes[0]); + dfm.addDexFile(usData, length); + } +} + +void JNICALL jazzerClassFileLoadHook( + jvmtiEnv* jvmti, 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) { + // check if Jazzer class + const char* prefix = "com/code_intelligence/jazzer/"; + if (strncmp(name, prefix, 29) == 0) { + return; + } + + int indx = dfm.findDexFileForClass(name); + if (indx < 0) { + return; + } + + size_t newSize; + unsigned char* newClassDataResult = + dfm.getClassBytes(name, indx, jvmti, &newSize); + + dex::Reader oldReader(const_cast<unsigned char*>(class_data), + (size_t)class_data_len); + dex::Reader newReader(newClassDataResult, newSize); + if (dfm.structureMatches(&oldReader, &newReader, name)) { + std::cout << "REDEFINING WITH INSTRUMENTATION: " << name << std::endl; + *new_class_data = newClassDataResult; + *new_class_data_len = static_cast<jint>(newSize); + } +} + +bool fileExists(std::string filePath) { return std::ifstream(filePath).good(); } + +void JNICALL jazzerVMInit(jvmtiEnv* jvmti_env, JNIEnv* jni_env, + jthread thread) { + // Parse agentOptions + + std::stringstream ss(agentOptions); + std::string token; + + std::string jazzerClassesJar; + std::vector<std::string> bootpathClassesOverrides; + while (std::getline(ss, token, ',')) { + std::vector<std::string> split = + absl::StrSplit(token, absl::MaxSplits('=', 1)); + if (split.size() < 2) { + std::cerr << "ERROR: no option given for: " << token; + exit(1); + } + + if (split[0] == "injectJars") { + jazzerClassesJar = split[1]; + } else if (split[0] == "bootstrapClassOverrides") { + bootpathClassesOverrides = + absl::StrSplit(split[1], absl::MaxSplits(':', 10)); + } + } + + if (!fileExists(jazzerClassesJar)) { + std::cerr << "ERROR: Jazzer bootstrap class file not found at: " + << jazzerClassesJar << std::endl; + exit(1); + } + + jvmti_env->AddToBootstrapClassLoaderSearch(jazzerClassesJar.c_str()); + + jvmtiCapabilities jazzerJvmtiCapabilities = { + .can_tag_objects = 0, + .can_generate_field_modification_events = 0, + .can_generate_field_access_events = 0, + .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 = 0, + .can_redefine_classes = 1, + .can_signal_thread = 0, + .can_get_source_file_name = 1, + .can_get_line_numbers = 0, + .can_get_source_debug_extension = 0, + .can_access_local_variables = 0, + .can_maintain_original_method_order = 0, + .can_generate_single_step_events = 0, + .can_generate_exception_events = 0, + .can_generate_frame_pop_events = 0, + .can_generate_breakpoint_events = 0, + .can_suspend = 0, + .can_redefine_any_class = 0, + .can_get_current_thread_cpu_time = 0, + .can_get_thread_cpu_time = 0, + .can_generate_method_entry_events = 0, + .can_generate_method_exit_events = 0, + .can_generate_all_class_hook_events = 0, + .can_generate_compiled_method_load_events = 0, + .can_generate_monitor_events = 0, + .can_generate_vm_object_alloc_events = 0, + .can_generate_native_method_bind_events = 0, + .can_generate_garbage_collection_events = 0, + .can_generate_object_free_events = 0, + .can_force_early_return = 0, + .can_get_owned_monitor_stack_depth_info = 0, + .can_get_constant_pool = 0, + .can_set_native_method_prefix = 0, + .can_retransform_classes = 1, + .can_retransform_any_class = 0, + .can_generate_resource_exhaustion_heap_events = 0, + .can_generate_resource_exhaustion_threads_events = 0, + }; + + jvmtiError je = jvmti_env->AddCapabilities(&jazzerJvmtiCapabilities); + if (je != JVMTI_ERROR_NONE) { + std::cerr << "JVMTI ERROR: " << je << std::endl; + exit(1); + } + + for (int i = 0; i < bootpathClassesOverrides.size(); i++) { + if (!fileExists(bootpathClassesOverrides[i])) { + std::cerr << "ERROR: Bootpath Class override jar not found at: " + << bootpathClassesOverrides[i] << std::endl; + exit(1); + } + + initializeBootclassOverrideJar(bootpathClassesOverrides[i], jni_env); + } + + retransformLoadedClasses(jvmti_env, jni_env); +} + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { + jvmtiEnv* jvmti = nullptr; + if (vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_2) != JNI_OK) { + return 1; + } + + jvmtiEventCallbacks callbacks; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.ClassFileLoadHook = jazzerClassFileLoadHook; + callbacks.VMInit = jazzerVMInit; + + jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks)); + jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL); + jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL); + + // Save the options string here, this is the only time it will be available + // however, we wont be able to use this to initialize until VMInit callback is + // called + agentOptions = std::string(options); + return 0; +} |