Created
April 9, 2019 08:55
-
-
Save romainthomas/f5bdcb846897c9f7a47df71384666dff to your computer and use it in GitHub Desktop.
QBDI API example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <iomanip> | |
#include <cstdlib> | |
#include <cstdint> | |
#include <cstring> | |
#include <jni.h> | |
#include <set> | |
#include "LIEF/ELF.hpp" | |
#include <android/log.h> | |
#include <dlfcn.h> | |
#include "QBDI.h" | |
#include "callback.hpp" | |
#define FAKE_RET_ADDR 42 | |
static JavaVM* jvm = nullptr; | |
static JNIEnv* env = nullptr; | |
static const char* TARGET_LIB = "libnative-lib.so"; | |
static constexpr size_t STACK_SIZE = 0x100000; // 1MB | |
static uintptr_t base_address = 0; | |
static auto console = spdlog::stdout_color_mt("console"); | |
using namespace QBDI; | |
using namespace LIEF::ELF; | |
extern "C" { | |
JNIEXPORT void InitializeSignalChain() {} | |
JNIEXPORT void ClaimSignalChain() {} | |
JNIEXPORT void UnclaimSignalChain() {} | |
JNIEXPORT void InvokeUserSignalHandler() {} | |
JNIEXPORT void EnsureFrontOfChain() {} | |
JNIEXPORT void AddSpecialSignalHandlerFn() {} | |
JNIEXPORT void RemoveSpecialSignalHandlerFn() {} | |
} | |
int main(int argc, char** argv) { | |
// Create JVM | |
// =========== | |
JavaVMOption opt[2]; | |
opt[0].optionString = "-Djava.library.path=/data/local/tmp"; | |
opt[1].optionString = "-verbose:jni"; // may want to remove this, it's noisy | |
JavaVMInitArgs args; | |
args.version = JNI_VERSION_1_6; | |
args.options = opt; | |
args.nOptions = 2; | |
args.ignoreUnrecognized = JNI_FALSE; | |
void* handler = dlopen("/system/lib/libart.so", RTLD_NOW); | |
// ... | |
console->info("JVM created!"); | |
void* hdl = dlopen(TARGET_LIB, RTLD_NOW); | |
if (hdl == nullptr) { | |
std::cerr << dlerror() << std::endl; | |
return EXIT_FAILURE; | |
} | |
using jni_function_t = jstring(*)(JNIEnv*, jobject, jstring); | |
auto&& jni_function = reinterpret_cast<jni_function_t>(dlsym(hdl, "Java_kr_repo_h2spice_crypto500_MainActivity_a")); | |
if (jni_function == nullptr) { | |
console->error("{}", dlerror()); | |
return EXIT_FAILURE; | |
} | |
context_t ctx; | |
std::vector<MemoryMap> maps = getCurrentProcessMaps(); | |
auto&& it_range = std::find_if(std::begin(maps), std::end(maps), | |
[] (MemoryMap mm) { | |
return mm.name.find(TARGET_LIB) != std::string::npos; | |
}); | |
if (it_range != std::end(maps)) { | |
ctx.range = &(it_range->range); | |
base_address = ctx.range->start; | |
} | |
std::unique_ptr<Binary> bin = Parser::parse(TARGET_LIB); | |
if (not bin) { | |
console->error("Error while parsing {}", TARGET_LIB); | |
return EXIT_FAILURE; | |
} | |
ctx.bin = bin.get(); | |
uint8_t *fakestack = nullptr; | |
console->info("Initializing VM ..."); | |
std::unique_ptr<VM> vm{new VM()}; | |
const char* INPUT = "g1UlZafiuGdCgpTkWYjaZg3kE6qCd7kF3kV+nMKcGHc="; | |
jstring jINPUT = env->NewStringUTF(INPUT); | |
jstring real_output = jni_function(env, nullptr, jINPUT); | |
console->info("Input: {}", INPUT); | |
console->info("Real Output: {}", env->GetStringUTFChars(real_output, nullptr)); | |
console->info("Setup callback"); | |
vm->addCodeCB(PREINST, inst_cbk, nullptr); | |
vm->recordMemoryAccess(MEMORY_READ_WRITE); | |
vm->addCodeCB(POSTINST, inst_cbk, nullptr); | |
// Get a pointer to the GPR state of the vm | |
GPRState* state = vm->getGPRState(); | |
// Setup initial GPR state, this fakestack will produce a ret NULL at the end of the execution | |
allocateVirtualStack(state, STACK_SIZE, &fakestack); | |
console->info("Simulate call in QBDI"); | |
simulateCall(state, FAKE_RET_ADDR, { | |
reinterpret_cast<rword>(env), | |
/* jobject */ reinterpret_cast<rword>(nullptr), | |
/* Base64 Input */ reinterpret_cast<rword>(jINPUT), | |
}); | |
console->info("Instument module: {}", TARGET_LIB); | |
vm->addInstrumentedModule(TARGET_LIB); | |
// Run the DBI execution | |
console->info("Run '{}' through QBDI", "Java_kr_repo_h2spice_crypto500_MainActivity_a"); | |
vm->run(reinterpret_cast<rword>(jni_function), static_cast<rword>(FAKE_RET_ADDR)); | |
rword ret = QBDI_GPR_GET(state, REG_RETURN); | |
const char* dbi_output = env->GetStringUTFChars((jstring)ret, nullptr); | |
console->info("DBI output {}", dbi_output); | |
alignedFree(fakestack); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment