Skip to content

Instantly share code, notes, and snippets.

@jreyesr
Created March 15, 2023 02:08
Show Gist options
  • Select an option

  • Save jreyesr/9a5c69956fdf1273956aedaeb81935e3 to your computer and use it in GitHub Desktop.

Select an option

Save jreyesr/9a5c69956fdf1273956aedaeb81935e3 to your computer and use it in GitHub Desktop.
Frida + TimeDoctor

Frida + TimeDoctor

This gist contains a Python script that uses Frida to hook onto a TimeDoctor process and passively monitor all its SQL queries. This enables the process to export logs about the SQL statements written, which incidentally reveals a lot of information about the computer's user's activity.

This script can be used as a data source to recreate the reports sent by TimeDoctor about the user, especially in so-called "silent" installations where TimeDoctor has no UI and thus users have no way of knowing the data that is being reported about them.

This gist is the main companion to this article.

Applicability

This script has been tested on a silent installation of TimeDoctor 3.6.43 on an Ubuntu 22.04 machine. It should work on close(ish) versions of TimeDoctor and on multiple versions of Ubuntu. It has not been tested on Windows or MacOS, nor on interactive (i.e., non-silent) versions of TimeDoctor. Those have a GUI and thus it's less important to extract this information.

Usage

  1. Ensure that you have Frida available: sudo pip install frida-tools
  2. Run the script: sudo python spy.py
  3. In less than three minutes (since TimeDoctor saves data to its DB , it should spit a series of log lines that contain SQL statements.
  4. The same log lines will be saved to the file /var/log/spy.log, from where they can be processed by a log management tool.
import json
import sys
import logging
import time
from logging.handlers import RotatingFileHandler
import frida
from pythonjsonlogger import jsonlogger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
stdout_handler = logging.StreamHandler()
file_handler = RotatingFileHandler('/var/log/spy.log', maxBytes=1*1024*1024, backupCount=3)
formatter = jsonlogger.JsonFormatter(timestamp=True)
stdout_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
logger.addHandler(stdout_handler)
logger.addHandler(file_handler)
def on_message(message, _data):
payload = json.loads(message["payload"])
# print("[on_message] message:", payload)
logger.info("on_message", extra=payload)
session = frida.attach("sfproc") # Hook into the program
logger.debug("starting")
script = session.create_script("""
const mod = Process.findModuleByName("/opt/sfproc/SF/sfproc/3.6.43/plugins/sqldrivers/libqsqlcipher.so");
const msg = (data) => send(JSON.stringify(data));
Interceptor.attach(mod.getExportByName("sqlite3_prepare_v2"), {
onEnter(args) { msg({func: "sqlite3_prepare16_v2", stmt: args[1].readUtf8String()}); }
});
Interceptor.attach(mod.getExportByName("sqlite3_prepare16_v2"), {
onEnter(args) { msg({func: "sqlite3_prepare16_v2", stmt: args[1].readUtf16String()}); }
});
Interceptor.attach(mod.getExportByName("sqlite3_bind_int"), {
onEnter(args) { msg({func: "sqlite3_bind_int", type: "bind_stmt", "index": args[1].toInt32(), value: args[2].toInt32()}); }
});
Interceptor.attach(mod.getExportByName("sqlite3_bind_text"), {
onEnter(args) { msg({func: "sqlite3_bind_text", type: "bind_stmt", "index": args[1].toInt32(), value: args[2].readUtf8String()}); }
});
Interceptor.attach(mod.getExportByName("sqlite3_bind_text16"), {
onEnter(args) { msg({func: "sqlite3_bind_text16", type: "bind_stmt", "index": args[1].toInt32(), value: args[2].readUtf16String()}); }
});
Interceptor.attach(mod.getExportByName("sqlite3_step"), {
onEnter(args) { msg({func: "sqlite3_step"}); }
});
""")
script.on("message", on_message)
script.load()
logger.debug("script_loaded")
# IMPORTANT: Otherwise the script terminates before the messages are received
while True:
time.sleep(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment