Skip to content

Commit 271a47a

Browse files
gebingoleavr
authored andcommittedJul 22, 2019
Improve Java.registerClass() and fix generic array handling (#120)
1 parent 707c442 commit 271a47a

File tree

4 files changed

+226
-6
lines changed

4 files changed

+226
-6
lines changed
 

‎lib/class-factory.js

+60-6
Original file line numberDiff line numberDiff line change
@@ -1785,12 +1785,13 @@ function ClassFactory (vm) {
17851785

17861786
const className = spec.name;
17871787
const interfaces = (spec.implements || []);
1788+
const superClass = (spec.superClass || factory.use('java.lang.Object'));
17881789

17891790
const dexMethods = [];
17901791
const dexSpec = {
17911792
name: makeJniObjectTypeName(className),
17921793
sourceFileName: makeSourceFileName(className),
1793-
superClass: 'Ljava/lang/Object;',
1794+
superClass: makeJniObjectTypeName(superClass.$classWrapper.__name__),
17941795
interfaces: interfaces.map(iface => makeJniObjectTypeName(iface.$classWrapper.__name__)),
17951796
methods: dexMethods
17961797
};
@@ -1804,7 +1805,7 @@ function ClassFactory (vm) {
18041805
const ifaceProto = Object.getPrototypeOf(iface);
18051806
Object.getOwnPropertyNames(ifaceProto)
18061807
.filter(name => {
1807-
return name[0] !== '$' && name !== 'constructor' && name !== 'class';
1808+
return name[0] !== '$' && name !== 'constructor' && name !== 'class' && iface[name].overloads !== undefined;
18081809
})
18091810
.forEach(name => {
18101811
const method = iface[name];
@@ -1851,7 +1852,7 @@ function ClassFactory (vm) {
18511852

18521853
if (typeof methodValue === 'function') {
18531854
const m = baseMethods[name];
1854-
if (m !== undefined) {
1855+
if (m !== undefined && Array.isArray(m)) {
18551856
const [baseMethod, overloadIds, parentTypeHandle] = m;
18561857

18571858
if (overloadIds.length > 1) {
@@ -1950,6 +1951,59 @@ function ClassFactory (vm) {
19501951
env.checkForExceptionAndThrowIt();
19511952
}
19521953

1954+
if (spec.superClass) {
1955+
Object.defineProperty(Klass.$classWrapper.prototype, '$super', {
1956+
enumerable: true,
1957+
get: function () {
1958+
const superInstance = factory.cast(this, superClass);
1959+
return new Proxy(superInstance, {
1960+
get: function (target, property, receiver) {
1961+
const prop = superInstance[property];
1962+
if (prop === undefined || prop.overloads === undefined) {
1963+
return prop;
1964+
}
1965+
function makeProxy (method) {
1966+
return new Proxy(method, {
1967+
apply: function (target, thisArg, args) {
1968+
const tid = Process.getCurrentThreadId();
1969+
try {
1970+
method[PENDING_CALLS].add(tid);
1971+
return method.apply(superInstance, args);
1972+
} finally {
1973+
method[PENDING_CALLS].delete(tid);
1974+
}
1975+
}
1976+
});
1977+
}
1978+
return new Proxy(prop, {
1979+
apply: function (target, thisArg, args) {
1980+
for (let i = 0; i !== prop.overloads.length; i++) {
1981+
const method = prop.overloads[i];
1982+
if (method.canInvokeWith(args)) {
1983+
return makeProxy(method).apply(target, args);
1984+
}
1985+
}
1986+
throwOverloadError(property, prop.overloads, 'argument types do not match any of:');
1987+
},
1988+
get: function (target, property, receiver) {
1989+
switch (property) {
1990+
case 'overloads':
1991+
return prop.overloads.map(makeProxy);
1992+
case 'overload':
1993+
return function (...args) {
1994+
return makeProxy(prop.overload.apply(superInstance, args))
1995+
};
1996+
default:
1997+
return prop[property];
1998+
}
1999+
},
2000+
});
2001+
},
2002+
});
2003+
}
2004+
});
2005+
}
2006+
19532007
const C = classes[spec.name];
19542008

19552009
function placeholder (...args) {
@@ -2466,11 +2520,11 @@ const primitiveArrayTypes = [
24662520
['S', 'short'],
24672521
]
24682522
.reduce((result, [shorty, name]) => {
2469-
result['[' + shorty] = makePrimitiveArrayType(name);
2523+
result['[' + shorty] = makePrimitiveArrayType('[' + shorty, name);
24702524
return result;
24712525
}, {});
24722526

2473-
function makePrimitiveArrayType (name) {
2527+
function makePrimitiveArrayType (shorty, name) {
24742528
const envProto = Env.prototype;
24752529

24762530
const nameTitled = toTitleCase(name);
@@ -2483,7 +2537,7 @@ function makePrimitiveArrayType (name) {
24832537
};
24842538

24852539
return {
2486-
name: name,
2540+
name: shorty,
24872541
type: 'pointer',
24882542
size: 1,
24892543
isCompatible: function (v) {

‎lib/env.js

+2
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,8 @@ Env.prototype.getTypeName = function (type, getGenericsInformation) {
873873

874874
if (this.isInstanceOf(type, this.javaLangClass().handle)) {
875875
return this.getClassName(type);
876+
} else if (this.isInstanceOf(type, this.javaLangReflectGenericArrayType().handle)) {
877+
return this.getArrayTypeName(type);
876878
} else if (this.isInstanceOf(type, this.javaLangReflectParameterizedType().handle)) {
877879
const rawType = invokeObjectMethodNoArgs(this.handle, type, this.javaLangReflectParameterizedType().getRawType);
878880
this.checkForExceptionAndThrowIt();

‎test/re/frida/ClassCreationTest.java

+148
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,26 @@
55
import org.junit.After;
66
import org.junit.Test;
77

8+
import java.io.OutputStream;
9+
import java.io.ByteArrayOutputStream;
810
import java.io.IOException;
911
import java.lang.reflect.InvocationTargetException;
1012
import java.lang.reflect.Method;
1113
import java.security.cert.CertificateException;
1214
import java.security.cert.X509Certificate;
15+
import java.util.Arrays;
1316
import javax.net.ssl.X509TrustManager;
1417

1518
public class ClassCreationTest {
1619
private static Object badgerObject = null;
1720
private static Class bananaClass = null;
21+
private static Class hasFieldClass = null;
22+
private static Class primitiveArrayClass = null;
23+
private static Class myOutputClass = null;
1824
private static Class trustManagerClass = null;
1925
private static Class formatterClass = null;
2026
private static Class weirdTrustManagerClass = null;
27+
private HasField hasField;
2128

2229
@Test
2330
public void simpleClassCanBeImplemented() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
@@ -161,6 +168,147 @@ public void interfaceMethodCanHaveUnrelatedOverload() throws ClassNotFoundExcept
161168
assertEquals("checkServerTrusted B", script.getNextMessage());
162169
}
163170

171+
// Issue #119
172+
@Test
173+
public void interfaceHasFieldCanBeImplemented() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
174+
loadScript("var HasField = Java.use('re.frida.HasField');" +
175+
"var SimpleHasField = Java.registerClass({" +
176+
" name: 're.frida.SimpleHasField'," +
177+
" implements: [HasField]," +
178+
" methods: {" +
179+
" getName: function () {" +
180+
" return 'hasField';" +
181+
" }," +
182+
" getCalories: function (grams) {" +
183+
" return grams * 2;" +
184+
" }," +
185+
" }" +
186+
"});" +
187+
"Java.use('re.frida.ClassCreationTest').hasFieldClass.value = SimpleHasField.class;");
188+
HasField hasField = (HasField) hasFieldClass.newInstance();
189+
assertEquals("hasField", hasField.getName());
190+
assertEquals(100, hasField.getCalories(50));
191+
assertEquals("Field", hasField.field);
192+
}
193+
194+
// Issue #121
195+
@Test
196+
public void primitiveArrayInterfaceMethodsCanBeImplemented() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
197+
loadScript("var PrimitiveArray = Java.use('re.frida.PrimitiveArray');" +
198+
"var SimplePrimitiveArray = Java.registerClass({" +
199+
" name: 're.frida.SimplePrimitiveArray'," +
200+
" implements: [PrimitiveArray]," +
201+
" methods: {" +
202+
" getByteArray: [{" +
203+
" returnType: '[B'," +
204+
" argumentTypes: []," +
205+
" implementation: function () {" +
206+
" return [1, 2, 3, 4, 5];" +
207+
" }" +
208+
" }]," +
209+
" setIntArray: function (array, off) {" +
210+
" var s = '';" +
211+
" for (var i = off; i < array.length; i++) s += array[i];" +
212+
" return s;" +
213+
" }," +
214+
" }" +
215+
"});" +
216+
"Java.use('re.frida.ClassCreationTest').primitiveArrayClass.value = SimplePrimitiveArray.class;");
217+
PrimitiveArray primitiveArray = (PrimitiveArray) primitiveArrayClass.newInstance();
218+
assertEquals(Arrays.equals(new byte[] { 1, 2, 3, 4, 5 }, primitiveArray.getByteArray()), true);
219+
assertEquals("345", primitiveArray.setIntArray(new int[] { 1, 2, 3, 4, 5 }, 2));
220+
}
221+
222+
// Issue #122
223+
@Test
224+
public void extendingClassCanBeImplemented() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
225+
loadScript("const bytes = [];" +
226+
"var MyOutputStream = Java.registerClass({" +
227+
" name: 're.frida.MyOutputSteam'," +
228+
" superClass: Java.use('java.io.OutputStream')," +
229+
" methods: {" +
230+
" write: [{" +
231+
" returnType: 'void'," +
232+
" argumentTypes: ['int']," +
233+
" implementation: function (b) {" +
234+
" bytes.push(b);" +
235+
" }" +
236+
" }]," +
237+
" toString: {" +
238+
" returnType: 'java.lang.String'," +
239+
" argumentTypes: []," +
240+
" implementation: function () {" +
241+
" return bytes.join(',');" +
242+
" }" +
243+
" }," +
244+
" }" +
245+
"});" +
246+
"Java.use('re.frida.ClassCreationTest').myOutputClass.value = MyOutputStream.class;");
247+
OutputStream myOutput = (OutputStream) myOutputClass.newInstance();
248+
myOutput.write(new byte[] { 1, 2, 3, 4, 5 });
249+
assertEquals("1,2,3,4,5", myOutput.toString());
250+
myOutput.write(new byte[] { 1, 2, 3, 4, 5 });
251+
assertEquals("1,2,3,4,5,1,2,3,4,5", myOutput.toString());
252+
}
253+
254+
// Issue #122
255+
@Test
256+
public void extendingClassCanInvoekSuperMethod() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
257+
loadScript("var SuperClass = Java.use('java.io.ByteArrayOutputStream');" +
258+
"var MyOutputStream = Java.registerClass({" +
259+
" name: 're.frida.MyOutputSteamEx'," +
260+
" superClass: SuperClass," +
261+
" methods: {" +
262+
" write: [{" +
263+
" returnType: 'void'," +
264+
" argumentTypes: ['[B', 'int', 'int']," +
265+
" implementation: function (b, off, len) {" +
266+
" this.$super.write(b, off, len);" +
267+
" }" +
268+
" }, {" +
269+
" returnType: 'void'," +
270+
" argumentTypes: ['int']," +
271+
" implementation: function (b) {" +
272+
" this.$super.write(b);" +
273+
" }" +
274+
" }]," +
275+
" }" +
276+
"});" +
277+
"Java.use('re.frida.ClassCreationTest').myOutputClass.value = MyOutputStream.class;");
278+
OutputStream myOutput = (OutputStream) myOutputClass.newInstance();
279+
myOutput.write(new byte[] { '1', '2', '3', '4', '5' });
280+
assertEquals("12345", myOutput.toString());
281+
myOutput.write(new byte[] { '1', '2', '3', '4', '5' });
282+
assertEquals("1234512345", myOutput.toString());
283+
}
284+
285+
// Issue #124
286+
@Test
287+
public void extendInterfaceCanBeImplemented() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
288+
loadScript("var Eatable = Java.use('re.frida.Eatable');" +
289+
"var EatableEx = Java.use('re.frida.EatableEx');" +
290+
"var BananaEx = Java.registerClass({" +
291+
" name: 're.frida.BananaEx'," +
292+
" implements: [EatableEx, Eatable]," +
293+
" methods: {" +
294+
" getName: function () {" +
295+
" return 'Banana';" +
296+
" }," +
297+
" getNameEx: function () {" +
298+
" return 'BananaEx';" +
299+
" }," +
300+
" getCalories: function (grams) {" +
301+
" return grams * 2;" +
302+
" }," +
303+
" }" +
304+
"});" +
305+
"Java.use('re.frida.ClassCreationTest').bananaClass.value = BananaEx.class;");
306+
EatableEx eatable = (EatableEx) bananaClass.newInstance();
307+
assertEquals("Banana", eatable.getName());
308+
assertEquals("BananaEx", eatable.getNameEx());
309+
assertEquals(100, eatable.getCalories(50));
310+
}
311+
164312
private Script script = null;
165313

166314
private void loadScript(String code) {

‎test/re/frida/MethodTest.java

+16
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ public void passingJSStringToReplacementThatThrowsShouldNotCrash() {
173173
assertEquals("java.lang.IllegalStateException: Already dead: w00t", script.getNextMessage());
174174
}
175175

176+
@Test
176177
public void genericsCanBeUsed() {
177178
loadScript("var ArrayList = Java.use('java.util.ArrayList');" +
178179
"var items = ArrayList.$new();" +
@@ -245,6 +246,15 @@ public void replacementCanBeDoneOnReflection() {
245246
}
246247
}
247248

249+
// Issue #125
250+
@Test
251+
public void genericArrayTypeShouldConvertToArray() {
252+
loadScript("var GenericArray = Java.use('re.frida.GenericArray');" +
253+
"var genericArray = GenericArray.getArray();" +
254+
"send(Array.isArray(genericArray) + ',' + genericArray.length);");
255+
assertEquals("true,2", script.getNextMessage());
256+
}
257+
248258
private Script script = null;
249259

250260
private void loadScript(String code) {
@@ -346,3 +356,9 @@ class Reflector {
346356
public static void reflected() {
347357
}
348358
}
359+
360+
class GenericArray {
361+
public static Class<?>[] getArray() {
362+
return new Class<?>[] { Collider.class, Reflector.class };
363+
}
364+
}

0 commit comments

Comments
 (0)
Please sign in to comment.