Primitive types

How to use, intercept and modify primitive types with Frida

**** NOTE: BLOG MOVED TO https://cmrodriguez.me/ ****

In Java we have the following basic:

  • byte: Byte-length integer

  • short: Short integer

  • int: Integer

  • long: Long integer

  • float: Single-precision floating point

  • double: Double-precision floating point

  • char: A single character

  • boolean: A Boolean value (true or false)

Using integer types

The different integer types are casted by frida automatically to the Javascript numeric type, which is capable of holding the the long Java type without loosing information.

As an example the following code calls different functions from the BasicTypes class, and they are printed by Frida with no need to cast the types:

var BasicTypes = Java.use("com.blog.testfrida.examples.BasicTypes"); 

console.log("addTwoInts: " + BasicTypes.addTwoInts(1,2));
console.log("addTwoBytes: " + BasicTypes.addTwoBytes(1+1,3));
console.log("addTwoShorts: " + BasicTypes.addTwoShorts(1-1,4));
console.log("addTwoLongs: " + BasicTypes.addTwoLongs(1*1,5));
console.log("addTwoInts: " + BasicTypes.addTwoInts(1/1,2));

console.log is the function used to print in the frida CLI application output.

BasicTypes is the javascript wrapper for the BasicTypes class, binded by using the Java.use function.

Frida casts automatically values as well in the reimplementation of functions:

BasicTypes.addTwoBytes.implementation = function (var1,var2) {
	console.log("test addTwoBytes");
	return 3+4;
}
BasicTypes.addTwoShorts.implementation = function (var1,var2) {
	console.log("test addTwoShorts");
	return 3+4;
}
BasicTypes.addTwoLongs.implementation = function (var1,var2) {
	console.log("test addTwoLongs");
	return 3+4;
}

In this case to test the function, it needs to be run from the Java environment, because the changes are not being reflected on the method in the Frida environment, that is why I created in a button in the Main Activity of the application. So in order to trigger the modified behavior you need to click the button in the application.

Using boolean types

Frida converts automatically from Java boolean type to javascript boolean with no weird behavior. The following example is used to send boolean values from the Frida bridge to Java, and from Java to the script:

var testBooleanValues = function () {
	var BasicTypes = Java.use("com.blog.testfrida.examples.BasicTypes"); 

	console.log(BasicTypes.negate(false));
	console.log(BasicTypes.negate(true));
	
	BasicTypes.negate.implementation = function (var1) {
		console.log("test negate");
		return false;
	}
}

Using floating point types

In floating points we see some differences. The issue comes from the use of float values

As an example, when the 23/3 is executed, the Java operation returns 7.666666507720947, because of the limitation on the precision. But javascript by default has an extended precision, so the division returns 7.666666666666667, as the double Java operation does. So be careful when you override a method as there could be issues when some value is sent to a function that works on floats. The following code shows multiple scenarios of float manipulation:

var testFloatValues = function () {
	var BasicTypes = Java.use("com.blog.testfrida.examples.BasicTypes"); 

	console.log("native values");
	console.log(23.0/3.0);
	console.log(0.1234567890123/1);
	console.log(2/5);

	console.log("java values");
	console.log(BasicTypes.divideFloat(23,3));
	console.log(BasicTypes.divideFloat(0.1234567890123,1));
	console.log(BasicTypes.divideDouble(2,5));
	console.log(BasicTypes.divideDouble(23,3));
	console.log(BasicTypes.divideDouble(0.1234567890123,1));
	
	BasicTypes.divideFloat.implementation = function (var1, var2) {
		console.log("test divideFloat");
		return var1 / var2;
	}
	BasicTypes.divideDouble.implementation = function (var1, var2) {
		console.log("test divideDouble");
		return var1 / var2;
	}
}

which returns the following output:

javascript values

  • 23/3: 7.666666666666667

  • 0.1234567890123/1: 0.1234567890123

  • 2/5: 0.4

java values

  • 23/3 (float): 7.666666507720947

  • 0.1234567890123/1 (float): 0.12345679104328156

  • 2/5(float): 0.4

  • 23/3 (double): 7.666666666666667

  • 0.1234567890123/1 (double): 0.1234567890123

Using char types

Frida converts automatically from Java char type to javascript char with no weird behavior. The following example is used to send char values from the Frida bridge to Java, and from Java to the script:

var testCharValues = function () {

	var BasicTypes = Java.use("com.blog.testfrida.examples.BasicTypes"); 

	console.log(BasicTypes.getNextChar('a'));
	console.log(BasicTypes.isCChar('f'));
	console.log(BasicTypes.isCChar('c'));
	
	BasicTypes.getNextChar.implementation = function (char1) {
		return 'F';
	}
	BasicTypes.isCChar.implementation = function (char1) {
		return char1 == 'c';
	}	
}

Using String type

In this case the String is managed as a classic Java Object with some particularities regarding the javascript string type. Based on the previous logic used to send basic type parameters , we could call a method that receives two Strings in the following way:

var testStrings = function () {

    var BasicTypes = Java.use("com.blog.testfrida.examples.BasicTypes"); 
    console.log(BasicTypes.concatString("first","second"));

}

When we run this, we get the following error:

TypeError: undefined not callable (property 'concatString' of [object Object]) 
  at [anon] (../../../frida-gum/bindings/gumjs/duktape.c:65012) 
  at /examples.js:112 
  at frida/node_modules/frida-java-bridge/lib/vm.js:11 
  at E (frida/node_modules/frida-java-bridge/index.js:346) 
  at frida/node_modules/frida-java-bridge/index.js:298 
  at frida/node_modules/frida-java-bridge/lib/vm.js:11

This is an error because the parameter sent is a native javascript String, but it should be encapsulated in a java.lang.String object. So we need to create an instance of the String:

var String = Java.use("java.lang.String");

var str1 = String.$new("test ");
var str2 = String.$new("String");

console.log(BasicTypes.concatString(str1,str2));

The second step to test is the override of a method that receives one or more String. We created the following function that overrides the concatString method from the example APK:

BasicTypes.concatString.implementation = function (str1,str2) {
    console.log("First string: " + str1);
    console.log("Second string: " + str2);

    return str1 + str2;
}

This code works perfectly because Frida wraps the String parameters and then returns a Javascript String to the internal implementation. So the concatenation works as in javascript, and Frida is in charge of converting the result to a java.lang.String object. Also the concatenation works with native attributes as it does in Java. As an example the following command works in java and in Javascript in the same way:

str1<"test "> + int1 <2> + str2 <"strings"> = "test 2 strings"

String comparison

The difference between a comparison between a native variable and a String is that the former is an object. So the variable that the developer manipulates holds a reference (pointer or memory value) to the real object. This is the reason a comparison == does not work with Strings, as it will naturally compare the references from the two Strings instead of the content. So to compare in Frida you should use the following script:

    var strHex = String.$new("hex");
    console.log("--> test");
    return strHex.equals(str1);

Accesing String attributes

If the String is in an attribute of a class, and you want to get access from the Frida script, you need to access it through the attribute "value", as in the following example:

var ScopedObject = Java.use("com.blog.testfrida.complexobjects.ScopeObject");  
console.log("Direct access: " + ScopedObject.publicStaticObject.value);

Encapsulation of attributes (optional)

Initially I thought the access to an attribute will be transparent for the frida cli, so the first time I got to the situation of retrieving it, I tested the following FridaSnippet:

var ScopedObject = Java.use("com.blog.testfrida.complexobjects.ScopeObject");  
console.log("Direct access: " + ScopedObject.publicStaticObject);

which printed "Direct access: [object Object]". This is due to the fact that the attributes in a Frida Object holds an encapsulation of a class. So when someone access it directly, it will return this content, and in this case it will cast it to the default Object string. To print the raw content of the variable (to check what it is), we can use the JSON.stringify javascript function:

var ScopedObject = Java.use("com.blog.testfrida.complexobjects.ScopeObject");  
console.log("Direct access: " + JSON.stringify(ScopedObject.publicStaticObject));

which will return:

{"fieldType":1,"fieldReturnType":{"className":"java.lang.String","name":"Ljava/lang/String;","type":"pointer","size":1},"$holder":{}}

which shows that the field is of type java.lang.String. In order to return the actual value, we should call the attribute value from the Frida Object.

Last updated