affe-null

ApiDaemon

In the last post I wrote about the #ApiDaemon. In this post, I will show that it is possible to add “add-on” services to the daemon, and also explain what else the daemon does.

Remote services

Remote services are like plugins for the API daemon. Unlike normal services, they don't have rust libraries that are compiled into the api-daemon binary, but instead run as a separate “child” daemon. When needed, the API daemon starts the child daemon with the environment variables LD_LIBRARY_PATH set to the remote service's path and IPC_FD set to a file descriptor number that is used for IPC between the API daemon and the child daemon.

General structure

Remote service daemons are located in subdirectories of /data/local/service/api-daemon. The subdirectories are named after the service. Each subdirectory should contain at least a daemon binary but can also have a valid_build_props.txt file. Remote services also need a JS library in the http_root. For example, the wavoip2 service contains the following files:

/data/local/service/api-daemon/remote/wavoip2/daemon
/data/local/service/api-daemon/remote/wavoip2/libc++_shared.so
/data/local/service/api-daemon/remote/wavoip2/libwa_voip.so
/data/local/service/api-daemon/remote/wavoip2/valid_build_props.txt
/data/local/service/api-daemon/http_root/api/v1/wavoip2/service.js.gz

The valid_build_props.txt file contains a whitelist of values of ro.build.cver. If the file exists, the service is only considered valid if ro.build.cver matches one of the lines and SELinux is in enforcing mode. wavoip2 is even more strict: Even if the daemon does start, it still checks for SELinux enforcing and ro.build.cver values. Because of this, WhatsApp calls don't work on rooted or soft-rootable devices. However, you can spoof SELinux enforcing with the following commands:

echo -n 1 > /data/local/tmp/enforce
mount -o bind /data/local/tmp/enforce /sys/fs/selinux/enforce

And if you then run setprop ro.build.cver QC_8905_3_10_49_SEC_1, wavoip will start successfully.

I haven't figured out the IPC protocol yet, but looking at the API daemon's source code will probably help: https://github.com/kaiostech/api-daemon

KaiAds

The API daemon also serves the #KaiAds SDK for privileged apps. This is probably used because of CSP restrictions that apply only to privileged and certified apps. Newer versions of the SDK provide a “SDK loader” that decides whether to load the real SDK from kaiads.com or http://127.0.0.1:8081/sdk/ads.

Telemetry

Another thing which the daemon does is telemetry. I first noticed this when I tried enabling SELinux enforcing to get wavoip working. After running setenforce 1, my phone suddenly became very slow, and when I looked into logcat, there were a bunch of lines saying that the telemetry process couldn't read from /proc/kmsg!!! Later I found out that this process belongs to the API daemon. This means that the API daemon reads the kernel log messages for telemetry purposes.

Also, I found the following lines in the daemon's configuration file:

[telemetry]
enabled = true
send_app_opened_events = true
token_uri = "https://api.kaiostech.com/v3.0/applications"
metrics_uri = "https://api.kaiostech.com/v3.0/apps/metrics"

so the daemon also sends “app opened” events to KaiOS.

All of this can be disabled by setting enabled to false. But KaiOS also provides development versions of the API daemon that have enabled set to false by default: https://packages.stage.kaiostech.com/base_23/api-daemon-1.4.26.zip. I will write about the packages.*.kaiostech.com domains in the next post.

You might have already heard that #WhatsApp uses some undocumented APIs that are built into the #KaiOS system. These APIs are powered by the api-daemon, a daemon which lies at a lower level than Gecko. In this post, I will explain what I have found out about this daemon.

The api-daemon is written in #Rust, and provides several “services”. Each service has two parts, the JS client and the Rust library. The Rust library is linked into the api-daemon executable. The JS clients are accessible via HTTP at http://127.0.0.1:8081/api/v1/<service>/<script>.js and expose APIs in a lib_<service> variable. A pre-installed version of the daemon is available in /system/kaios, but it is copied to /data/local/service to allow updates without modifying the system partition.

Using the APIs

To demonstrate the usage, let's create a simple app called “Daemon Test”. We will use the telephony service. Although it is meant to be used to get and set the call state, it seems that real calls have no effect on the reported state. However, the state might reflect ongoing WhatsApp and other VoIP calls.

To use the APIs on KaiOS 2.5, we need the external-api permission:

manifest.webapp

{
  "name": "Daemon Test",
  "launch_path": "/index.html",
  "description": "An app to demonstrate api-daemon functionality",
  "type": "privileged",
  "permissions": {
    "external-api": {}
  }
}

index.html

<!DOCTYPE html>
<html>
  <body>
    <div id="callstate">Loading...</div>
    <script src="http://127.0.0.1:8081/api/v1/shared/core.js"></script>
    <script src="http://127.0.0.1:8081/api/v1/shared/session.js"></script>
    <script src="http://127.0.0.1:8081/api/v1/telephony/service.js"></script>
    <script src="daemon-test.js"></script>
  </body>
</html>

We can now use these APIs as described in https://developer.stage.kaiostech.com/docs/sfp-3.0/09.migration-from-2.5/next-new-apis/daemon-api/telephony/daemon-api-telephony/, except that we will need to replace localhost with localhost:8081. Note that the documentation is for KaiOS 3.0, but on 2.5 only the telephony, libsignal (for WhatsApp) and tcpsocket services are available. You can also look at the api-daemon source code, which is also only for KaiOS 3.0.

First, we need to set up a sess variable that stores the api-daemon session:

var sess = new lib_session.Session();

Then, we write some handlers that get called when the session connects/disconnects:

function onsessionconnected(){
  lib_telephony.TelephonyService.get(sess).then(function(service){
    /* handle session connect */
  });
}
function onsessiondisconnected(){
  /* handle session disconnect */
}

And after that we try connecting.

sess.open('websocket', 'localhost:8081', 'secrettoken',
  {onsessionconnected, onsessiondisconnected}, true);

Here is the final code:

daemon-test.js

window.onerror = function(e){
  // Error handling
  alert(e);
}

var sess = new lib_session.Session();
var el_state = document.getElementById('callstate');
var toggleCallState = null;

function callStateChangeCallback(state){
  el_state.textContent = state;
}

// Set up session callbacks
var onsessionconnected = function(){
  alert('Session connected');
  // Get an instance of the telephony manager
  lib_telephony.TelephonyService.get(sess).then(function(manager){
    alert('Got Telephony manager');
    try {
      // Show the call state
      manager.callState.then(function(state){
        el_state.textContent = 'Call state: ' + state;
      });

      // Show it again if it changes
      manager.addEventListener(manager.CALLSTATE_CHANGE_EVENT, function(state){
          el_state.textContent = 'Call state: ' + state;
      });

      toggleCallState = function(){
        // Get the call state
        manager.callState.then(function(state){
          // Set the call state
          manager.callState = state ? 0 : 1;
          alert(state ? 'Idle' : 'Calling');
        });
      }
    } catch(e) {
      alert(e);
    }
  }).catch(function(e){
    alert('Failed to get Telephony manager: ' + JSON.stringify(e));
    toggleCallState = null;
  });
}
var onsessiondisconnected = function(){
  alert('Disconnected');
  toggleCallState = null;
}

// Initialize the api-daemon session
sess.open('websocket', 'localhost:8081', 'secrettoken',
  {onsessionconnected, onsessiondisconnected}, true);

window.addEventListener('keydown', function(e){
  if(e.key == 'Call'){
    // Toggle the state if 'Call' is pressed and the session is ready
    if(typeof toggleCallState === 'function') toggleCallState();
  }
});

There is a lot more to this. I will also make a wiki page, and some more posts about things such as the updater-daemon and remote services.

More tags: #ApiDaemon