Fun with Frida


August 22, 2016

During a previous engagement Securus Global was asked to review a desktop application that used a local SQLite3 database to store a list of blacklisted URLs. As expected the database file was encrypted and not much that could be done with the database.

If the consultant tried to open the database using any SQLite3 client an error message would pop up in our face. At Securus Global we have extensive experience with Frida, a framework that allows you to inject JavaScript to explore native apps on Windows, Mac, Linux, iOS and Android (more on Frida here http://frida.re). It is used heavily in our Mobile App Penetration Tests so the team decided to take a further look at the application and managed to trace the “requests” to libsqlite3.dylib.

Keep in mind that the same approach will work for libsqlite3.so. Also note that this has not been tested in a Windows environment.

Our goal at the time was to discover the SQL queries performed by the application and try to acquire some useful information, we started to look into two specific functions in libsqlite3.dylib:

The open function is defined as:

image_1

To know the database filename the consultant had to hook the function open and read the first argument from memory, args[0]. Thankfully, Frida provides an excellent wrapper to read from memory with Memory.readUtf8String. For more detailed information please refer to this link: http://www.frida.re/docs/javascript-api/#memory.

Below is a sample code snippet to read the first argument from open’s function. Based on the sqlite3_open definition we know that the first argument is a pointer to filename, so if we read that argument we’ll figure out the database filename.

#open.js

'use strict';

//libsqlite3.dylib

var sqlite3_open = Module.findExportByName('libsqlite3.dylib ', 'sqlite3_open');

Interceptor.attach(sqlite3_open, {
  onEnter: function(args) {
     console.log('Database filename: ' + Memory.readUtf8String(args[0]));
  }

});

The team found the file, but as mentioned before, unfortunately the file was encrypted.

The team decided to change the approach and find the SQL statements before they were saved in the encrypted file.

The prepare function is defined as:

image_2

In order to not be “jailed” by the file encryption, we need to follow the same steps as previously applied in the open function, but now we have to read an argument in a different function, sqlite3_prepare_v2, and check above the sqlite3_prepare_v2 structure.

Sample code to read the second argument from sqlite3_prepare_v2 function.

#prepare.js

'use strict';

// libsqlite3.dylib

var sqlite3_prepare_v2 = Module.findExportByName('libsqlite3.dylib', 'sqlite3_prepare_v2');

Interceptor.attach(sqlite3_prepare_v2, {
  onEnter: function(args) {
     console.log('SQL: ' + Memory.readUtf8String(args[1]));
  }
});

Our Proof of Concept (PoC) is quite simple, basically we use the sample code provided in Frida’s documentation to inject one of our JavaScript files, open.js or prepare.js, into the application process.

#poc.py

import frida
import sys
import codecs


def on_message(message, data):
    if message['type'] == 'send':
        print(message['payload'])
    elif message['type'] == 'error':
        print(message['stack'])


pid = raw_input("app pid: ")
try:
    session = frida.attach(int(pid))s
    print "[+] Process Attached"
except Exception as e:
    print "Error => {0}".format(e)
    sys.exit(0)


with codecs.open('./prepare.js', 'r', 'utf-8') as f:
    source = f.read()
script = session.create_script(source)
script.on('message', on_message)
script.load()

try:
    while True:
      pass
except KeyboardInterrupt:
    session.detach()
    sys.exit(0)

Screenshots

Running our PoC to attach it to the application process and inject our JavaScript. The output is the SQL Statements.

image_3image_4

The same PoC code could be used against others applications that use a SQLite3 database, such as: Skype.

image_5

As we have been using Frida for some time now we highly recommend it for doing mobile application tests. Along with our own scripts, highly dependent on the test, we recommend the following Frida extensions:

How to prevent It

The simplest approach to fix this is, instead of encrypt the database file the application should encrypt the information in memory before saving it to the database.

References

Leave a Reply

Your email address will not be published. Required fields are marked *