Skip to content

Commit 6a1972b

Browse files
gebingoleavr
authored andcommittedAug 10, 2019
Improve registerClass() to support user-defined fields (#130)
(And fix #133 while at it.) Resolves #76.
1 parent 9a35842 commit 6a1972b

File tree

3 files changed

+102
-11
lines changed

3 files changed

+102
-11
lines changed
 

‎lib/class-factory.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -1786,12 +1786,14 @@ function ClassFactory (vm) {
17861786
const interfaces = (spec.implements || []);
17871787
const superClass = (spec.superClass || factory.use('java.lang.Object'));
17881788

1789+
const dexFields = [];
17891790
const dexMethods = [];
17901791
const dexSpec = {
17911792
name: makeJniObjectTypeName(className),
17921793
sourceFileName: makeSourceFileName(className),
17931794
superClass: makeJniObjectTypeName(superClass.$classWrapper.__name__),
17941795
interfaces: interfaces.map(iface => makeJniObjectTypeName(iface.$classWrapper.__name__)),
1796+
fields: dexFields,
17951797
methods: dexMethods
17961798
};
17971799

@@ -1804,6 +1806,12 @@ function ClassFactory (vm) {
18041806
});
18051807
});
18061808

1809+
const fields = spec.fields || {};
1810+
Object.getOwnPropertyNames(fields).forEach(name => {
1811+
const fieldType = getTypeFromJniTypeName(fields[name]);
1812+
dexFields.push([name, fieldType.name]);
1813+
});
1814+
18071815
const baseMethods = {};
18081816
const pendingOverloads = {};
18091817
allInterfaces.forEach(iface => {
@@ -1869,7 +1877,7 @@ function ClassFactory (vm) {
18691877
delete pendingOverloads[overloadIds[0]];
18701878
const overload = baseMethod.overloads[0];
18711879

1872-
method = overload;
1880+
method = Object.assign({}, overload, { holder: placeholder });
18731881
returnType = overload.returnType;
18741882
argumentTypes = overload.argumentTypes;
18751883
impl = methodValue;
@@ -1897,7 +1905,7 @@ function ClassFactory (vm) {
18971905
const [overload, parentTypeHandle] = pendingOverload;
18981906
delete pendingOverloads[id];
18991907

1900-
method = overload;
1908+
method = Object.assign({}, overload, { holder: placeholder });
19011909

19021910
const reflectedMethod = env.toReflectedMethod(parentTypeHandle, overload.handle, 0);
19031911
const thrownTypes = invokeObjectMethodNoArgs(env.handle, reflectedMethod, Method.getGenericExceptionTypes);

‎lib/mkdex.js

+52-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const kEndianTag = 0x12345678;
1111

1212
const kClassDefSize = 32;
1313
const kProtoIdSize = 12;
14+
const kFieldIdSize = 8;
1415
const kMethodIdSize = 8;
1516
const kTypeIdSize = 4;
1617
const kStringIdSize = 4;
@@ -20,6 +21,7 @@ const TYPE_HEADER_ITEM = 0;
2021
const TYPE_STRING_ID_ITEM = 1;
2122
const TYPE_TYPE_ID_ITEM = 2;
2223
const TYPE_PROTO_ID_ITEM = 3;
24+
const TYPE_FIELD_ID_ITEM = 4;
2325
const TYPE_METHOD_ID_ITEM = 5;
2426
const TYPE_CLASS_DEF_ITEM = 6;
2527
const TYPE_MAP_LIST = 0x1000;
@@ -69,6 +71,7 @@ class DexBuilder {
6971
const {
7072
classes,
7173
interfaces,
74+
fields,
7275
methods,
7376
protos,
7477
parameters,
@@ -100,8 +103,9 @@ class DexBuilder {
100103
const protoIdsSize = protos.length * kProtoIdSize;
101104
offset += protoIdsSize;
102105

103-
const fieldIdsOffset = 0;
104-
const fieldIdsCount = 0;
106+
const fieldIdsOffset = offset;
107+
const fieldIdsSize = fields.length * kFieldIdSize;
108+
offset += fieldIdsSize;
105109

106110
const methodIdsOffset = offset;
107111
const methodIdsSize = methods.length * kMethodIdSize;
@@ -203,7 +207,7 @@ class DexBuilder {
203207
offset = align(offset, 4);
204208
const mapOffset = offset;
205209
const typeListLength = interfaces.length + parameters.length;
206-
const mapNumItems = 8 + (3 * classes.length) + ((typeListLength > 0) ? 1 : 0) +
210+
const mapNumItems = 8 + (3 * classes.length) + ((typeListLength > 0) ? 1 : 0) + ((fields.length > 0) ? 1 : 0) +
207211
annotationDirectories.length + annotationSets.length + throwsAnnotations.length;
208212
const mapSize = 4 + (mapNumItems * kMapItemSize);
209213
offset += mapSize;
@@ -228,8 +232,8 @@ class DexBuilder {
228232
dex.writeUInt32LE(typeIdsOffset, 0x44);
229233
dex.writeUInt32LE(protos.length, 0x48);
230234
dex.writeUInt32LE(protoIdsOffset, 0x4c);
231-
dex.writeUInt32LE(fieldIdsCount, 0x50);
232-
dex.writeUInt32LE(fieldIdsOffset, 0x54);
235+
dex.writeUInt32LE(fields.length, 0x50);
236+
dex.writeUInt32LE(fields.length > 0 ? fieldIdsOffset : 0, 0x54);
233237
dex.writeUInt32LE(methods.length, 0x58);
234238
dex.writeUInt32LE(methodIdsOffset, 0x5c);
235239
dex.writeUInt32LE(classes.length, 0x60);
@@ -254,6 +258,15 @@ class DexBuilder {
254258
dex.writeUInt32LE((params !== null) ? params.offset : 0, protoOffset + 8);
255259
});
256260

261+
fields.forEach((field, index) => {
262+
const [classIndex, typeIndex, nameIndex] = field;
263+
264+
const fieldOffset = fieldIdsOffset + (index * kFieldIdSize);
265+
dex.writeUInt16LE(classIndex, fieldOffset);
266+
dex.writeUInt16LE(typeIndex, fieldOffset + 2);
267+
dex.writeUInt32LE(nameIndex, fieldOffset + 4);
268+
});
269+
257270
methods.forEach((method, index) => {
258271
const [classIndex, protoIndex, nameIndex] = method;
259272

@@ -373,9 +386,12 @@ class DexBuilder {
373386
[TYPE_STRING_ID_ITEM, strings.length, stringIdsOffset],
374387
[TYPE_TYPE_ID_ITEM, types.length, typeIdsOffset],
375388
[TYPE_PROTO_ID_ITEM, protos.length, protoIdsOffset],
376-
[TYPE_METHOD_ID_ITEM, methods.length, methodIdsOffset],
377-
[TYPE_CLASS_DEF_ITEM, classes.length, classDefsOffset],
378389
];
390+
if (fields.length > 0) {
391+
mapItems.push([TYPE_FIELD_ID_ITEM, fields.length, fieldIdsOffset]);
392+
}
393+
mapItems.push([TYPE_METHOD_ID_ITEM, methods.length, methodIdsOffset]);
394+
mapItems.push([TYPE_CLASS_DEF_ITEM, classes.length, classDefsOffset]);
379395
annotationSets.forEach((set, index) => {
380396
mapItems.push([TYPE_ANNOTATION_SET_ITEM, set.items.length, annotationSetOffsets[index]]);
381397
});
@@ -419,10 +435,10 @@ class DexBuilder {
419435
}
420436

421437
function makeClassData (klass, constructorCodeOffset) {
422-
const {constructorMethod, virtualMethods} = klass.classData;
438+
const {instanceFields, constructorMethod, virtualMethods} = klass.classData;
423439

424440
const staticFieldsSize = 0;
425-
const instanceFieldsSize = 0;
441+
const instanceFieldsSize = instanceFields.length;
426442
const directMethodsSize = 1;
427443

428444
const [constructorIndex, constructorAccessFlags] = constructorMethod;
@@ -433,6 +449,11 @@ function makeClassData (klass, constructorCodeOffset) {
433449
directMethodsSize,
434450
]
435451
.concat(createUleb128(virtualMethods.length))
452+
.concat(instanceFields.reduce((result, [indexDiff, accessFlags]) => {
453+
return result
454+
.concat(createUleb128(indexDiff))
455+
.concat(createUleb128(accessFlags));
456+
}, []))
436457
.concat(createUleb128(constructorIndex))
437458
.concat(createUleb128(constructorAccessFlags))
438459
.concat(createUleb128(constructorCodeOffset))
@@ -466,6 +487,7 @@ function computeModel (classes) {
466487
const strings = new Set();
467488
const types = new Set();
468489
const protos = {};
490+
const fields = [];
469491
const methods = [];
470492
const throwsAnnotations = {};
471493
const superConstructors = new Set();
@@ -488,6 +510,14 @@ function computeModel (classes) {
488510
types.add(iface);
489511
});
490512

513+
klass.fields.forEach(field => {
514+
const [fieldName, fieldType] = field;
515+
strings.add(fieldName);
516+
strings.add(fieldType);
517+
types.add(fieldType);
518+
fields.push([klass.name, fieldType, fieldName]);
519+
});
520+
491521
klass.methods.forEach(method => {
492522
const [methodName, retType, argTypes, thrownTypes = []] = method;
493523

@@ -605,6 +635,15 @@ function computeModel (classes) {
605635
}, {});
606636
const parameterItems = Object.keys(parameters).map(id => parameters[id]);
607637

638+
const fieldItems = fields.map(field => {
639+
const [klass, fieldType, fieldName] = field;
640+
return [
641+
typeToIndex[klass],
642+
typeToIndex[fieldType],
643+
stringToIndex[fieldName]
644+
];
645+
});
646+
608647
const methodItems = methods.map(method => {
609648
const [klass, protoId, name, annotationsId] = method;
610649
return [
@@ -694,6 +733,8 @@ function computeModel (classes) {
694733
annotationDirectories.push(annotationsDirectory);
695734
}
696735

736+
const instanceFields = fieldItems.map((field, index) => [index, kAccPublic]);
737+
697738
const constructorNameIndex = stringToIndex['<init>'];
698739
const constructorMethod = classMethods
699740
.filter(([, name]) => name === constructorNameIndex)
@@ -715,6 +756,7 @@ function computeModel (classes) {
715756
}));
716757

717758
const classData = {
759+
instanceFields,
718760
constructorMethod,
719761
superConstructorMethod,
720762
virtualMethods,
@@ -736,6 +778,7 @@ function computeModel (classes) {
736778
return {
737779
classes: classItems,
738780
interfaces: interfaceItems,
781+
fields: fieldItems,
739782
methods: methodItems,
740783
protos: protoItems,
741784
parameters: parameterItems,

‎test/re/frida/ClassCreationTest.java

+40
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class ClassCreationTest {
2626
private static Class myOutputClass1 = null;
2727
private static Class myOutputClass2 = null;
2828
private static Class orangeClass = null;
29+
private static Class userDefinedFieldClass = null;
2930

3031
@Test
3132
public void simpleClassCanBeImplemented() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
@@ -310,6 +311,45 @@ public void derivedInterfaceCanBeImplemented() throws ClassNotFoundException, In
310311
assertEquals(Arrays.equals(new String[] { "tasty", "sweet" }, fruit.getTags()), true);
311312
}
312313

314+
// Issue #76/#133
315+
@Test
316+
public void classWithUserDefinedFieldsCanBeImplemented() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
317+
loadScript("var Formatter = Java.use('re.frida.Formatter');" +
318+
"var UserDefinedFields = Java.registerClass({" +
319+
" name: 're.frida.StatefulFormatter'," +
320+
" implements: [Formatter]," +
321+
" fields: {" +
322+
" lastInt: 'int'," +
323+
" lastStr: 'java.lang.String'," +
324+
" }," +
325+
" methods: {" +
326+
" format: [{" +
327+
" returnType: 'java.lang.String'," +
328+
" argumentTypes: ['int']," +
329+
" implementation: function (val) {" +
330+
" const oldVal = this.lastInt.value;" +
331+
" this.lastInt.value = val;" +
332+
" return oldVal + ': ' + val;" +
333+
" }" +
334+
" }, {" +
335+
" returnType: 'java.lang.String'," +
336+
" argumentTypes: ['java.lang.String']," +
337+
" implementation: function (val) {" +
338+
" const oldVal = this.lastStr.value;" +
339+
" this.lastStr.value = val;" +
340+
" return oldVal + ': ' + val;" +
341+
" }" +
342+
" }]" +
343+
" }" +
344+
"});" +
345+
"Java.use('re.frida.ClassCreationTest').userDefinedFieldClass.value = UserDefinedFields.class;");
346+
Formatter formatter = (Formatter) userDefinedFieldClass.newInstance();
347+
assertEquals("0: 1", formatter.format(1));
348+
assertEquals("1: 2", formatter.format(2));
349+
assertEquals("null: First", formatter.format("First"));
350+
assertEquals("First: Second", formatter.format("Second"));
351+
}
352+
313353
private Script script = null;
314354

315355
private void loadScript(String code) {

0 commit comments

Comments
 (0)
Please sign in to comment.