Introduction
In the last blogpost, we covered how to use unidbg from scratch to emulate an Android native library. As some might have noticed, the Proof of Concept code is not production ready as it does not allow for a way to call the signing functionality externally. More importantly, the code is too slow for practical use. Let’s add some time measuring code to our previous main method to see this in action:
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Signer signer = new Signer("/tmp/libhellosignjni.so");
String signature = signer.sign("helloworld");
System.out.println("Signature: " + signature);
long end = System.currentTimeMillis();
System.out.println("Total execution time : " + ((end - start) / 1000.0) + " seconds");
}
}
Running the above code takes more than 6 seconds for a single signature (on a decent modern computer)!
Signature: c24e48124f5c69ec647a5147193932f2a7aef0a9362163ce0ca29da259b2047c
Total execution time : 6.483 seconds
In this blogpost, we’ll cover how to make unidbg usable for production by pointing out the bottleneck in execution. In addition, we’ll add a layer around unidbg to expose the signing functionality to other services.
The bottleneck
After debugging the code, it is pretty obvious which part of the code takes the longest to run. See the updated code snippet:
public class Main {
public static void main(String[] args) {
long t1 = System.currentTimeMillis();
Signer signer = new Signer("/tmp/libhellosignjni.so");
long t2 = System.currentTimeMillis();
System.out.println("Execution time for initialization: " + ((t2 - t1) / 1000.0) + " seconds");
String signature = signer.sign("helloworld");
System.out.println("Signature: " + signature);
long t3 = System.currentTimeMillis();
System.out.println("Execution time for signing: " + ((t3 - t2) / 1000.0) + " seconds");
}
}
Running the above code results into the following output:
Execution time for initialization: 6.258 seconds
Signature: c24e48124f5c69ec647a5147193932f2a7aef0a9362163ce0ca29da259b2047c
Execution time for signing: 0.041 seconds
We can conclude that what takes the longest is initializing and setting up the emulator, whereas the signing call only takes a fraction of a second. This is good news, since this means we can create a service where we initialize the emulator object once, and subsequent signing requests will be handled directly without the setup overhead.
Signing as a Service
Emulating the signing procedure is cool but we need to provide a programmatic interface for other applications to integrate with our unidbg signing service. There are many options, but for simplicity’s sake, we’ll create an HTTP API endpoint using Spring boot.
After adding the Spring boot dependency to our pom.xml
file, we create the main Spring boot application:
package me.bhamza.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringUnidbgSignerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringUnidbgSignerApplication.class, args);
}
}
Next, let’s create our Spring Service:
package me.bhamza.example;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class SignerService {
private Signer unidbgSigner;
public SignerService(@Value("${so-file}") String so_file) {
this.unidbgSigner = new Signer(so_file);
}
String sign(String message) {
return this.unidbgSigner.sign(message);
}
}
The Value
annotation is used to automatically inject the so_file
value which represents the path to the .so
file we want to emulate. We can define this value by creating an application.properties
file under the resources
folder:
spring.application.name=unidbgsigner
so-file=/tmp/libhellosignjni.so
Finally, let’s create a controller for our signing endpoint:
package me.bhamza.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
public class SignerController {
@Autowired
public SignerService signerService;
@GetMapping("/")
public String index() {
return "Hello signer!";
}
@RequestMapping(path = "/sign", consumes = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.POST})
public String sign(@RequestBody Map<String, String> payload) {
return signerService.sign(payload.getOrDefault("message", ""));
}
}
@AutoWired
is used to inject our unidbg signing service. Upon starting the Spring boot application, the service is instantiated. This might take a few seconds, but only on startup!
Test it out
Let’s run it and test it out:
A quick curl command to confirm it is working!
curl -H "Content-Type: application/json" --request POST --data '{"message":"hellosign"}' http://localhost:8080/sign
c15991f870f43089493b8750718e0b88e7d020e6018a7faa73b8e21f609859a6
Check out the final source code here.
Do you have questions? Want to see more? DM me.