How do I monitor a specific process file read/writes on android?

259 views Asked by At

I want to discover the reads and writes of a process on android. Is there any tool such as ptrace that can perform this action?

On a rooted android device I tried running the command

# ptrace
/system/bin/sh: ptrace: inaccessible or not found

1

There are 1 answers

1
Howard Jack On

In order to track file system reads and write you would need to track all functions that interact with the file-system. Luckily, @FrenchYeti from codeshare.frida.re did this for us:

https://codeshare.frida.re/@FrenchYeti/android-file-system-access-hook/

In case the link breaks in the future I'm including the script here (credit to @FrenchYeti and codeshare.frida.re)

/**
It should be launch earlier in order to be aware of a maximun 
quantity of file descriptors.
@author @FrenchYeti
*/
Java.perform(function() {

// ============= Config
var CONFIG = {
    // if TRUE enable data dump 
    printEnable: true,
    // if TRUE enable libc.so open/read/write hook
    printLibc: false,
    // if TRUE print the stack trace for each hook
    printStackTrace: false,
    // to filter the file path whose data want to be dumped in ASCII 
    dump_ascii_If_Path_contains: [".log", ".xml", ".prop"],
    // to filter the file path whose data want to be NOT dumped in hexdump (useful for big chunk and excessive reads) 
    dump_hex_If_Path_NOT_contains: [".png", "/proc/self/task", "/system/lib", "base.apk", "cacert"],
    // to filter the file path whose data want to be NOT dumped fron libc read/write (useful for big chunk and excessive reads) 
    dump_raw_If_Path_NOT_contains: [".png", "/proc/self/task", "/system/lib", "base.apk", "cacert"]
}

// =============  Keep a trace of file descriptor, path, and so
var TraceFD = {};
var TraceFS = {};
var TraceFile = {};
var TraceSysFD = {};


// ============= Get classes
var CLS = {
    File: Java.use("java.io.File"),
    FileInputStream: Java.use("java.io.FileInputStream"),
    FileOutputStream: Java.use("java.io.FileOutputStream"),
    String: Java.use("java.lang.String"),
    FileChannel: Java.use("java.nio.channels.FileChannel"),
    FileDescriptor: Java.use("java.io.FileDescriptor"),
    Thread: Java.use("java.lang.Thread"),
    StackTraceElement: Java.use("java.lang.StackTraceElement"),
    AndroidDbSQLite: Java.use("android.database.sqlite.SQLiteDatabase")
};
var File = {
    new: [
        CLS.File.$init.overload("java.io.File", "java.lang.String"),
        CLS.File.$init.overload("java.lang.String"),
        CLS.File.$init.overload("java.lang.String", "java.lang.String"),
        CLS.File.$init.overload("java.net.URI"),
    ]
};
var FileInputStream = {
    new: [
        CLS.FileInputStream.$init.overload("java.io.File"),
        CLS.FileInputStream.$init.overload("java.io.FileDescriptor"),
        CLS.FileInputStream.$init.overload("java.lang.String"),
    ],
    read: [
        CLS.FileInputStream.read.overload(),
        CLS.FileInputStream.read.overload("[B"),
        CLS.FileInputStream.read.overload("[B", "int", "int"),
    ],
};
var FileOuputStream = {
    new: [
        CLS.FileOutputStream.$init.overload("java.io.File"),
        CLS.FileOutputStream.$init.overload("java.io.File", "boolean"),
        CLS.FileOutputStream.$init.overload("java.io.FileDescriptor"),
        CLS.FileOutputStream.$init.overload("java.lang.String"),
        CLS.FileOutputStream.$init.overload("java.lang.String", "boolean")
    ],
    write: [
        CLS.FileOutputStream.write.overload("[B"),
        CLS.FileOutputStream.write.overload("int"),
        CLS.FileOutputStream.write.overload("[B", "int", "int"),
    ],
};



// ============= Hook implementation

File.new[1].implementation = function(a0) {
    prettyLog("[Java::File.new.1] New file : " + a0);

    var ret = File.new[1].call(this, a0);
    var f = Java.cast(this, CLS.File);
    TraceFile["f" + this.hashCode()] = a0;


    return ret;
}
File.new[2].implementation = function(a0, a1) {
    prettyLog("[Java::File.read.2] New file : " + a0 + "/" + a1);

    var ret = File.new[2].call(this, a0, a1);;
    var f = Java.cast(this, CLS.File);
    TraceFile["f" + this.hashCode()] = a0 + "/" + a1;

    return ret;
}


FileInputStream.new[0].implementation = function(a0) {
    var file = Java.cast(a0, CLS.File);
    var fname = TraceFile["f" + file.hashCode()];

    if (fname == null) {
        var p = file.getAbsolutePath();
        if (p !== null)
            fname = TraceFile["f" + file.hashCode()] = p;
    }
    if (fname == null)
        fname = "[unknow]"

    prettyLog("[Java::FileInputStream.new.0] New input stream from file (" + fname + "): ");

    var fis = FileInputStream.new[0].call(this, a0)
    var f = Java.cast(this, CLS.FileInputStream);

    TraceFS["fd" + this.hashCode()] = fname;

    var fd = Java.cast(this.getFD(), CLS.FileDescriptor);

    TraceFD["fd" + fd.hashCode()] = fname;

    return fis;
}



FileInputStream.read[1].implementation = function(a0) {
    var fname = TraceFS["fd" + this.hashCode()];
    var fd = null;
    if (fname == null) {
        fd = Java.cast(this.getFD(), CLS.FileDescriptor);
        fname = TraceFD["fd" + fd.hashCode()]
    }
    if (fname == null)
        fname = "[unknow]";

    var b = Java.array('byte', a0);

    prettyLog("[Java::FileInputStream.read.1] Read from file,offset (" + fname + "," + a0 + "):\n" +
        prettyPrint(fname, b));

    return FileInputStream.read[1].call(this, a0);
}
FileInputStream.read[2].implementation = function(a0, a1, a2) {
    var fname = TraceFS["fd" + this.hashCode()];
    var fd = null;
    if (fname == null) {
        fd = Java.cast(this.getFD(), CLS.FileDescriptor);
        fname = TraceFD["fd" + fd.hashCode()]
    }
    if (fname == null)
        fname = "[unknow]";

    var b = Java.array('byte', a0);

    prettyLog("[Java::FileInputStream.read.2] Read from file,offset,len (" + fname + "," + a1 + "," + a2 + ")\n" +
        prettyPrint(fname, b));

    return FileInputStream.read[2].call(this, a0, a1, a2);
}



// =============== File Output Stream ============



FileOuputStream.new[0].implementation = function(a0) {
    var file = Java.cast(a0, CLS.File);
    var fname = TraceFile["f" + file.hashCode()];

    if (fname == null)
        fname = "[unknow]<File:" + file.hashCode() + ">";


    prettyLog("[Java::FileOuputStream.new.0] New output stream to file (" + fname + "): ");

    var fis = FileOuputStream.new[0].call(this, a0);

    TraceFS["fd" + this.hashCode()] = fname;

    var fd = Java.cast(this.getFD(), CLS.FileDescriptor);
    TraceFD["fd" + fd.hashCode()] = fname;

    return fis;
}

FileOuputStream.new[1].implementation = function(a0) {
    var file = Java.cast(a0, CLS.File);
    var fname = TraceFile["f" + file.hashCode()];

    if (fname == null)
        fname = "[unknow]";


    prettyLog("[Java::FileOuputStream.new.1] New output stream to file (" + fname + "): \n");

    var fis = FileOuputStream.new[1].call(this, a0);

    TraceFS["fd" + this.hashCode()] = fname;

    var fd = Java.cast(this.getFD(), CLS.FileDescriptor);

    TraceFD["fd" + fd.hashCode()] = fname;

    return fis;
}

FileOuputStream.new[2].implementation = function(a0) {
    var fd = Java.cast(a0, CLS.FileDescriptor);
    var fname = TraceFD["fd" + fd.hashCode()];

    if (fname == null)
        fname = "[unknow]";


    prettyLog("[Java::FileOuputStream.new.2] New output stream to FileDescriptor (" + fname + "): \n");
    var fis = FileOuputStream.new[1].call(this, a0)

    TraceFS["fd" + this.hashCode()] = fname;

    return fis;
}
FileOuputStream.new[3].implementation = function(a0) {
    prettyLog("[Java::FileOuputStream.new.3] New output stream to file (str=" + a0 + "): \n");

    var fis = FileOuputStream.new[1].call(this, a0)

    TraceFS["fd" + this.hashCode()] = a0;
    var fd = Java.cast(this.getFD(), CLS.FileDescriptor);
    TraceFD["fd" + fd.hashCode()] = a0;

    return fis;
}
FileOuputStream.new[4].implementation = function(a0) {
    prettyLog("[Java::FileOuputStream.new.4] New output stream to file (str=" + a0 + ",bool): \n");

    var fis = FileOuputStream.new[1].call(this, a0)
    TraceFS["fd" + this.hashCode()] = a0;
    var fd = Java.cast(this.getFD(), CLS.FileDescriptor);
    TraceFD["fd" + fd.hashCode()] = a0;

    return fis;
}



FileOuputStream.write[0].implementation = function(a0) {
    var fname = TraceFS["fd" + this.hashCode()];
    var fd = null;

    if (fname == null) {
        fd = Java.cast(this.getFD(), CLS.FileDescriptor);
        fname = TraceFD["fd" + fd.hashCode()]
    }
    if (fname == null)
        fname = "[unknow]";

    prettyLog("[Java::FileOuputStream.write.0] Write byte array (" + fname + "):\n" +
        prettyPrint(fname, a0));

    return FileOuputStream.write[0].call(this, a0);
}
FileOuputStream.write[1].implementation = function(a0) {

    var fname = TraceFS["fd" + this.hashCode()];
    var fd = null;
    if (fname == null) {
        fd = Java.cast(this.getFD(), CLS.FileDescriptor);
        fname = TraceFD["fd" + fd.hashCode()]
    }
    if (fname == null)
        fname = "[unknow]";

    prettyLog("[Java::FileOuputStream.write.1] Write int  (" + fname + "): " + a0);


    return FileOuputStream.write[1].call(this, a0);
}
FileOuputStream.write[2].implementation = function(a0, a1, a2) {

    var fname = TraceFS["fd" + this.hashCode()];
    var fd = null;
    if (fname == null) {
        fd = Java.cast(this.getFD(), CLS.FileDescriptor);
        fname = TraceFD["fd" + fd.hashCode()]
        if (fname == null)
            fname = "[unknow], fd=" + this.hashCode();
    }

    prettyLog("[Java::FileOuputStream.write.2] Write " + a2 + " bytes from " + a1 + "  (" + fname + "):\n" +
        prettyPrint(fname, a0));

    return FileOuputStream.write[2].call(this, a0, a1, a2);
}

// native hooks    
Interceptor.attach(
    Module.findExportByName("libc.so", "read"), {
        // fd, buff, len
        onEnter: function(args) {
            if (CONFIG.printLibc === true) {
                var bfr = args[1],
                    sz = args[2].toInt32();
                var path = (TraceSysFD["fd-" + args[0].toInt32()] != null) ? TraceSysFD["fd-" + args[0].toInt32()] : "[unknow path]";

                prettyLog("[Libc::read] Read FD (" + path + "," + bfr + "," + sz + ")\n" +
                    rawPrint(path, Memory.readByteArray(bfr, sz)));
            }
        },
        onLeave: function(ret) {

        }
    }
);

Interceptor.attach(
    Module.findExportByName("libc.so", "open"), {
        // path, flags, mode
        onEnter: function(args) {
            this.path = Memory.readCString(args[0]);
        },
        onLeave: function(ret) {
            TraceSysFD["fd-" + ret.toInt32()] = this.path;
            if (CONFIG.printLibc === true)
                prettyLog("[Libc::open] Open file '" + this.path + "' (fd: " + ret.toInt32() + ")");
        }
    }
);


Interceptor.attach(
    Module.findExportByName("libc.so", "write"), {
        // fd, buff, count
        onEnter: function(args) {
            if (CONFIG.printLibc === true) {
                var bfr = args[1],
                    sz = args[2].toInt32();
                var path = (TraceSysFD["fd-" + args[0].toInt32()] != null) ? TraceSysFD["fd-" + args[0].toInt32()] : "[unknow path]";

                prettyLog("[Libc::write] Write FD (" + path + "," + bfr + "," + sz + ")\n" +
                    rawPrint(path, Memory.readByteArray(bfr, sz)));
            }
        },
        onLeave: function(ret) {

        }
    }
);



// helper functions
function prettyLog(str) {
    console.log("---------------------------\n" + str);
    if (CONFIG.printStackTrace === true) {
        printStackTrace();
    }
}

function prettyPrint(path, buffer) {
    if (CONFIG.printEnable === false) return "";

    if (contains(path, CONFIG.dump_ascii_If_Path_contains)) {
        return b2s(buffer);
    } else if (!contains(path, CONFIG.dump_hex_If_Path_NOT_contains)) {
        return hexdump(b2s(buffer));
    }
    return "[dump skipped by config]";
}

function rawPrint(path, buffer) {
    if (CONFIG.printEnable === false) return "";

    if (!contains(path, CONFIG.dump_raw_If_Path_NOT_contains)) {
        return hexdump(buffer);
    }
    return "[dump skipped by config]";
}

function contains(path, patterns) {
    for (var i = 0; i < patterns.length; i++)
        if (path.indexOf(patterns[i]) > -1) return true;
    return false;
}

function printStackTrace() {
    var th = Java.cast(CLS.Thread.currentThread(), CLS.Thread);
    var stack = th.getStackTrace(),
        e = null;

    for (var i = 0; i < stack.length; i++) {
        console.log("\t" + stack[i].getClassName() + "." + stack[i].getMethodName() + "(" + stack[i].getFileName() + ")");
    }
}

function isZero(block) {
    var m = /^[0\s]+$/.exec(block);
    return m != null && m.length > 0 && (m[0] == block);
}

function hexdump(buffer, blockSize) {
    blockSize = blockSize || 16;
    var lines = [];
    var hex = "0123456789ABCDEF";
    var prevZero = false,
        ctrZero = 0;
    for (var b = 0; b < buffer.length; b += blockSize) {
        var block = buffer.slice(b, Math.min(b + blockSize, buffer.length));
        var addr = ("0000" + b.toString(16)).slice(-4);
        var codes = block.split('').map(function(ch) {
            var code = ch.charCodeAt(0);
            return " " + hex[(0xF0 & code) >> 4] + hex[0x0F & code];
        }).join("");
        codes += "   ".repeat(blockSize - block.length);
        var chars = block.replace(/[\\x00-\\x1F\\x20\n]/g, '.');
        chars += " ".repeat(blockSize - block.length);
        if (isZero(codes)) {
            ctrZero += blockSize;
            prevZero = true;
        } else {
            if (prevZero) {
                lines.push("\t [" + ctrZero + "] bytes of zeroes");
            }
            lines.push(addr + " " + codes + "  " + chars);
            prevZero = false;
            ctrZero = 0;
        }
    }
    if (prevZero) {
        lines.push("\t [" + ctrZero + "] bytes of zeroes");
    }
    return lines.join("\\n");
}

function b2s(array) {
    var result = "";
    for (var i = 0; i < array.length; i++) {
        result += String.fromCharCode(modulus(array[i], 256));
    }
    return result;
}

function modulus(x, n) {
    return ((x % n) + n) % n;
}
});