Frida之文件操作 – 作者:无情剑客Burning

前面的文章中介绍了数据库的操作,这篇文章主要介绍文件的操作。

当你在使用程序的时候,可以动态修改程序的文件操作,其实是很恐怖的,比如,本来是往文件中写入100元钱,但是经过动态修改后变成了0元,据此可以脑洞大开一下。 @[toc]

基础概念

Linux下文件描述符

一个Linux进程启动后,会在内核空间创建一个PCB进程控制块,PCB是一个进程的私有财产。这个PCB中有一个已打开文件描述符表,记录着所有该进程打开的文件描述符以及对应的file结构体地址。

默认情况下,启动一个Linux进程后,会打开三个文件,分别是标准输入、标准输出、标准错误分别使用了0、1 、2号文件描述符。

当该进程使用函数open打开一个新的文件时,一般会在内核空间申请一个file结构体,并且把3号文件描述符对应的file指针指向file结构体。在这里插入图片描述v-node table entry是虚拟文件系统对应的文件节点,i-node是磁盘文件系统对应的文件节点。通过这两个节点就能找到最终的磁盘文件。 举个例子:

#include <unistd.h>#include <fcntl.h>#include <stdio.h>int main(int argc,char *argv[]){
   intfd = open("./1.c",O_RDWR);
   printf("fd=%d\n",fd);
}

程序运行结果是: fd=3

每一个进程只有一个process table entry,一般情况下默认使用 fd 0、fd1、fd2,新打开的文件1.c将使用fd 3,后续的文件描述符的值以此类推。 文件描述符 | 含义 ——– | —– 0| 标准输入 1 | 标准输出 2 | 标准错误输出

JavaScript Promise对象

ECMAscript 6 原生提供了 Promise 对象。Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。

varmyFirstPromise = newPromise(function(resolve, reject){
    //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.setTimeout(function(){
        resolve("欢迎关注我的微信公众号:无情剑客!"); //代码正常执行!}, 250);
});

myFirstPromise.then(function(successMessage){
    //successMessage的值是上面调用resolve(...)方法传入的值.//successMessage参数不一定非要是字符串类型,这里只是举个例子document.write("Yay! "+ successMessage);
});

Promise简化了对error的处理,上面的代码我们也可以这样写:

promise.then(onFulfilled).catch(onRejected)

更多Promise的内容,后续会专门介绍。

Android Activity生命周期

为了在 Activity 生命周期的各个阶段之间导航转换,Activity 类提供六个核心回调:onCreate()、onStart()、onResume()、onPause()、onStop() 和 onDestroy()。当 Activity 进入新状态时,系统会调用其中每个回调。在这里插入图片描述

文件和流

文件File

new File(filePath, mode): open or create the file at filePath with the mode string specifying how it should be opened. For example “wb” to open the file for writing in binary mode (this is the same format as fopen() from the C standard library).write(data): synchronously write data to the file, where data is either a string or a buffer as returned by NativePointer#readByteArrayflush(): flush any buffered data to the underlying fileclose(): close the file. You should call this function when you’re done with the file unless you are fine with this happening when the object is garbage-collected or the script is unloaded. 举个例子,hook Activity的onResume函数,当回调onResume函数的时候,先通过File操作向’/data/data/com.lingpao.lpcf622b/files/chat/test.txt’这个文件中写入’hello world’,最后调用系统的onResume回调。这里只是举例,实际情况可能需要保存一些重要的数据或者修改一些重要的数据。

importfrida,sys

def on_message(message, data): if message[‘type’] == ‘send’: print(” {0}”.format(message[‘payload’])) else: print(message) pass

session = frida.get_usb_device().attach(“com.lingpao.lpcf622b”)

jscode = “”” if(Java.available){ Java.perform(function(){ var Activity = Java.use(‘android.app.Activity’); Activity.onResume.implementation = function () { send(‘onResume() got called! ‘); var file = new File(‘/data/data/com.lingpao.lpcf622b/files/chat/test.txt’, ‘wb’); file.write(‘hello world’); file.flush(); file.close(); this.onResume(); }; });

} “””

script = session.create_script(jscode) script.on(“message”, on_message) print(‘ Start attach’) script.load() sys.stdin.read()

####  输入流InputStreamAll methods are fully **asynchronous** and return**Promise** objects.
* close(): close the stream, releasing resources related to it. Once the stream isclosed, all other operations will fail. Closing a stream multiple times isallowed and will not result inan error.

* read(size): read up to size bytes fromthe stream. The returned Promise receives an ArrayBuffer up to size bytes long. End of stream issignalled through an empty buffer.

 * readAll(size): keep reading fromthe stream until exactly size bytes have been consumed. The returned Promise receives an **ArrayBuffer** that isexactly size bytes long. Premature error or end of stream results inthe Promise getting rejected with an error, wherethe Error objecthas a partialData property containing the incomplete data.

在类Unix系统中,获取输入流的方式:
* newUnixInputStream(fd[, options]): create a newInputStream fromthe specified file descriptor fd. You may also supply an options objectwith autoClose setto trueto make the stream close the underlying file descriptor whenthe stream isreleased, either through close() or future garbage-collection.

在Windwos平台下,获取输入流的方式:
* new Win32InputStream(handle[, options]): create a new InputStream from the specified handle, which is a Windows HANDLE value. You may also supply an options object with autoClose set to true to make the stream close the underlying handle when the stream is released, either through close() or future garbage-collection.

Android是基于Linux系统的,所以属于类Unix系统,具体代码示例,在[前面的文章中](https://mp.weixin.qq.com/s?__biz=MzI1NjEyMzgxMw==&mid=2247484367&idx=1&sn=c4fe52a8bde986c92ac86c67757cb603&chksm=ea2a3505dd5dbc137647191d9a72e5a837d133639439cdca934a7deaf372eb598e3fde56889b&token=471719304&lang=zh_CN#rd)对Intercepter进行了介绍,这里对open函数进行替换,对打开的文件读取输入流。

```c
var openPtr = Module.getExportByName(null, 'open');
varopen = newNativeFunction(openPtr, 'int', ['pointer', 'int']);
Interceptor.replace(openPtr, newNativeCallback(function (pathPtr, flags) {
  varpath = pathPtr.readUtf8String();
  console.log('Opening "'+ path + '"');
  varfd = open(pathPtr, flags);
 if(fd > 0){
    varinput = newUnixInputStream(fd);
    varpromise = input.read(1000);
    promise.then(function(result){
      console.log(' burning'+hexdump(result,{lenght:1000}));
    }).catch(function(error){
      console.log(' fail:'+error);
    });

 }
  console.log('Got fd: '+ fd);
  returnfd;
}, 'int', ['pointer', 'int']));

运行结果如下。在这里插入图片描述

输出流OutputStream

All methods are fully asynchronousand return Promiseobjects.

close(): close the stream, releasing resources related to it. Once the stream is closed, all other operations will fail. Closing a stream multiple times is allowed and will not result in an error.

write(data): try to write data to the stream. The data value is either an ArrayBuffer or an array of integers between 0 and 255. The returned Promise receives a Number specifying how many bytes of data were written to the stream.

writeAll(data): keep writing to the stream until all of data has been written. The data value is either an ArrayBuffer or an array of integers between 0 and 255. Premature error or end of stream results in an error, where the Error object has a partialSize property specifying how many bytes of data were written to the stream before the error occurred.

writeMemoryRegion(address, size): try to write size bytes to the stream, reading them from address, which is a NativePointer. The returned Promise receives a Number specifying how many bytes of data were written to the stream.

在类Unix系统中,获取输出流的方式:

new UnixOutputStream(fd[, options]): create a new OutputStream from the specified file descriptor fd. You may also supply an options object with autoClose set to true to make the stream close the underlying file descriptor when the stream is released, either through close() or future garbage-collection.

在Windwos平台下,获取输出流的方式:

new Win32OutputStream(handle[, options]): create a new OutputStream from the specified handle, which is a Windows HANDLE value. You may also supply an options object with autoClose set to true to make the stream close the underlying handle when the stream is released, either through close() or future garbage-collection.

具体使用,可参考InputStream的使用。

输入输出流

输入和输出都是相对的。高中物理讲过火车上的人,以车做参考系,人是静止的,但是如果以树做参考系人是运动的,那末输入输出的参考系是什么?

内存。往内存中写就是输入,从内存中向文件中写就是输出。

写在最后

预告一下,下篇Frida的文章说网络,网络的本质其实还是文件。

公众号

更多Frida相关的内容,欢饮关注我的微信公众号:无情剑客。

在这里插入图片描述

来源:freebuf.com 2020-09-06 23:28:15 by: 无情剑客Burning

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论