Permalink
Browse files

Add support for enumerateLoadedClasses() on ART

  • Loading branch information...
oleavr committed Feb 12, 2017
1 parent a8105b2 commit 5977cebdcabecdd9e3594d2941d0ad15c6aa18dd
Showing with 125 additions and 22 deletions.
  1. +70 −20 index.js
  2. +5 −1 lib/android.js
  3. +46 −0 test/re/frida/ClassRegistryTest.java
  4. +4 −1 test/re/frida/TestRunner.java
View
@@ -12,6 +12,8 @@ const {
checkJniResult
} = require('./lib/result');
const pointerSize = Process.pointerSize;
function Runtime () {
let api = null;
let vm = null;
@@ -81,13 +83,65 @@ function Runtime () {
}
};
function _enumerateLoadedClasses (callbacks, onlyDescription) {
assertJavaApiIsAvailable();
class ArtClassVisitor {
constructor (visit) {
const visitor = Memory.alloc(4 * pointerSize);
const vtable = visitor.add(pointerSize);
Memory.writePointer(visitor, vtable);
const onVisit = new NativeCallback((self, klass) => {
return visit(klass) === true ? 1 : 0;
}, 'bool', ['pointer', 'pointer']);
Memory.writePointer(vtable.add(2 * pointerSize), onVisit);
this.handle = visitor;
this._onVisit = onVisit;
}
}
function enumerateLoadedClassesArt (callbacks) {
const env = vm.getEnv();
const classHandles = [];
const addGlobalReference = api['art::JavaVMExt::AddGlobalRef'];
const vmHandle = api.vm;
const threadHandle = Memory.readPointer(env.handle.add(pointerSize));
const collectClassHandles = new ArtClassVisitor(klass => {
classHandles.push(addGlobalReference(vmHandle, threadHandle, klass));
return true;
});
if (api.flavor !== 'dalvik') {
throw new Error('Enumerating loaded classes is only supported on Dalvik for now');
withAllArtThreadsSuspended(() => {
api['art::ClassLinker::VisitClasses'](api.artClassLinker, collectClassHandles);
});
try {
classHandles.forEach(handle => {
const className = env.getClassName(handle);
callbacks.onMatch(className);
});
} finally {
classHandles.forEach(handle => {
env.deleteGlobalRef(handle);
});
}
callbacks.onComplete();
}
function withAllArtThreadsSuspended (fn) {
const scope = Memory.alloc(pointerSize);
const longSuspend = false;
api['art::ScopedSuspendAll::ScopedSuspendAll'](scope, Memory.allocUtf8String('frida'), longSuspend ? 1 : 0);
try {
fn();
} finally {
api['art::ScopedSuspendAll::~ScopedSuspendAll'](scope);
}
}
function enumerateLoadedClassesDalvik (callbacks) {
const HASH_TOMBSTONE = ptr('0xcbcacccd');
const loadedClassesOffset = 172;
const hashEntrySize = 8;
@@ -101,21 +155,10 @@ function Runtime () {
for (let offset = 0; offset < end; offset += hashEntrySize) {
const pEntryPtr = pEntries.add(offset);
const dataPtr = Memory.readPointer(pEntryPtr.add(4));
if (!(HASH_TOMBSTONE.equals(dataPtr) || NULL.equals(dataPtr))) {
if (!(HASH_TOMBSTONE.equals(dataPtr) || dataPtr.isNull())) {
const descriptionPtr = Memory.readPointer(dataPtr.add(24));
const description = Memory.readCString(descriptionPtr);
if (onlyDescription) {
callbacks.onMatch(description);
} else {
const objectSize = Memory.readU32(dataPtr.add(56));
const sourceFile = Memory.readCString(Memory.readPointer(dataPtr.add(152)));
callbacks.onMatch({
pointer: pEntryPtr,
objectSize: objectSize,
sourceFile: sourceFile,
description: description
});
}
callbacks.onMatch(description);
}
}
callbacks.onComplete();
@@ -128,10 +171,11 @@ function Runtime () {
const classes = [];
this.enumerateLoadedClasses({
onMatch: function (c) {
onMatch(c) {
classes.push(c);
},
onComplete: function () {}
onComplete() {
}
});
return classes;
}
@@ -140,7 +184,13 @@ function Runtime () {
Object.defineProperty(this, 'enumerateLoadedClasses', {
enumerable: true,
value: function (callbacks) {
_enumerateLoadedClasses(callbacks, true);
assertJavaApiIsAvailable();
if (api.flavor === 'art') {
enumerateLoadedClassesArt(callbacks);
} else {
enumerateLoadedClassesDalvik(callbacks);
}
}
});
View
@@ -34,7 +34,11 @@ function _getApi () {
'JNI_GetCreatedJavaVMs': ['JNI_GetCreatedJavaVMs', 'int', ['pointer', 'int', 'pointer']],
'artInterpreterToCompiledCodeBridge': function (address) {
this.artInterpreterToCompiledCodeBridge = address;
}
},
'_ZN3art9JavaVMExt12AddGlobalRefEPNS_6ThreadEPNS_6mirror6ObjectE': ['art::JavaVMExt::AddGlobalRef', 'pointer', ['pointer', 'pointer', 'pointer']],
'_ZN3art16ScopedSuspendAllC1EPKcb': ['art::ScopedSuspendAll::ScopedSuspendAll', 'void', ['pointer', 'pointer', 'bool']],
'_ZN3art16ScopedSuspendAllD1Ev': ['art::ScopedSuspendAll::~ScopedSuspendAll', 'void', ['pointer']],
'_ZN3art11ClassLinker12VisitClassesEPNS_12ClassVisitorE': ['art::ClassLinker::VisitClasses', 'void', ['pointer', 'pointer']],
},
optionals: [
'artInterpreterToCompiledCodeBridge'
@@ -0,0 +1,46 @@
package re.frida;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Test;
import java.io.IOException;
public class ClassRegistryTest {
@Test
public void loadedClassesCanBeEnumerated() {
loadScript("var found = false;" +
"Java.enumerateLoadedClasses({" +
" onMatch: function (entry) {" +
" if (entry === 're.frida.ClassRegistryTest') {" +
" found = true;" +
" }" +
" }," +
" onComplete: function () {" +
" send('found=' + found);" +
" }" +
"});");
assertEquals("found=true", script.getNextMessage());
}
private Script script = null;
private void loadScript(String code) {
Script script = new Script(TestRunner.fridaJavaBundle +
";\n(function (Java) {" +
"Java.perform(function () {" +
code +
"});" +
"})(LocalJava);");
this.script = script;
}
@After
public void tearDown() throws IOException {
if (script != null) {
script.close();
script = null;
}
}
}
@@ -16,7 +16,10 @@ public static void main(String[] args, String dataDir) {
TestRunner.fridaJavaBundle = slurp("frida-java.js");
JUnitCore.main("re.frida.MethodTest");
JUnitCore.main(
"re.frida.ClassRegistryTest",
"re.frida.MethodTest"
);
}
public static String slurp(String name) {

0 comments on commit 5977ceb

Please sign in to comment.