Android下hook实现文件读写透明加解密

发一篇好几年的文章。。因为最近公司要求我研究研究hook,想起来我以前做的这部分工作 >_<

实现原理:

修改write函数所对应的got表项中的地址,修改成自己定义的函数,则每当系统调用write函数时,会执行我们自定义函数,从而只需在自定义函数中添加加密或者解密算法,就能实现对应用开发者透明的加解密。

几个问题:

Question 1:Android如何实现文件读写?

编写Android应用程序时,遇到文件读写一般使用FileInputStream类中的read()方法和FileOutputStream类中的write方法。我们以调用FileOutputStream.write()为例。
http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/java/java/io/FileOutputStream.java

源代码如下所示,系统会继续调用IoBridge中的write方法。

    @Override
    public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
        IoBridge.write(fd, buffer, byteOffset, byteCount);
    }

接着,系统调用final类Libcore中的static field OS的write方法。
static field os转型BlockGuard对象。
http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/java/libcore/io/Libcore.java

    public final class Libcore {
    private Libcore() { }
    public static Os os = new BlockGuardOs(new Posix());
}

http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/java/libcore/io/IoBridge.java

    public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
        Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
        if (byteCount == 0) {
            return;
        }
        try {
            while (byteCount > 0) {
                int bytesWritten = Libcore.os.write(fd, bytes, byteOffset, byteCount);
                byteCount -= bytesWritten;
                byteOffset += bytesWritten;
            }
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsIOException();
        }
    }

最终调用BlackGuardOs中的write方法。
http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/java/libcore/io/BlockGuardOs.java

    public BlockGuardOs(Os os) {
        super(os);
    }
    @Override 
    public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
        BlockGuard.getThreadPolicy().onWriteToDisk();
        return os.write(fd, bytes, byteOffset, byteCount);
    }

系统继续调用Posix类中的write方法。
http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/java/libcore/io/Posix.java

    public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
        // This indirection isn't strictly necessary, but ensures that our public interface is type safe.
        return writeBytes(fd, bytes, byteOffset, byteCount);
    }

    private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;

最终,我们可以发现,系统将会调用native方法writeBytes.继续深入:
http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/native/libcore_io_Posix.cpp

native方法writeBytes的具体实现在/libcore/luni/src/main/native/libcore_io_Posix.cpp
查看本地方法注册

    NATIVE_METHOD(Posix, writeBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;II)I")

    void register_libcore_io_Posix(JNIEnv* env) {
        jniRegisterNativeMethods(env, "libcore/io/Posix", gMethods, NELEM(gMethods));
    }

宏定义为

    #define NATIVE_METHOD(className, functionName, signature, identifier) \
    { #functionName, signature, reinterpret_cast<void*>(className ## _ ## identifier) }

结合上面的代码,可以得知:本地方法writeBytes被注册成了:Posix_writeBytes函数。

    static jint Posix_writeBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) {
        ScopedBytesRO bytes(env, javaBytes);
        if (bytes.get() == NULL) {
        return -1;
        }
    int fd = jniGetFDFromFileDescriptor(env, javaFd);
    return throwIfMinusOne(env, "write", TEMP_FAILURE_RETRY(write(fd, bytes.get() + byteOffset, byteCount)));
    }

可以发现,writeBytes最终还是调用了write函数。这个write函数应该是系统提供的API,在libc.so中。

Question 2 :Posix_writeBytes函数在哪一个共享库中?

如第一部分的分析,系统在Posix_writeBytes函数中调用write函数。如果我们能够修改掉这个write函数在got中的地址,那么当系统运行至Posix_writeBytes函数中,并企图调用write函数时,将进入我们自定义函数。

但是,Android APP在运行时会加载许多的.so库文件,我们需要知道Posix_writeBytes函数在哪一个库中,才能够修改write在该库文件中got表项的地址。
http://androidxref.com/4.4_r1/xref/libcore/Android.mk

Android.mk文件是标示如何编译源代码的说明书,查看Android.mk得知详情在http://androidxref.com/4.4_r1/xref/libcore/NativeCode.mk中。

    include $(CLEAR_VARS)
    LOCAL_CFLAGS += $(core_cflags)
    LOCAL_CPPFLAGS += $(core_cppflags)
    LOCAL_SRC_FILES += $(core_src_files)
    LOCAL_C_INCLUDES += $(core_c_includes)
    LOCAL_SHARED_LIBRARIES += $(core_shared_libraries) libcrypto libexpat   libicuuc libicui18n libnativehelper libz
    LOCAL_STATIC_LIBRARIES += $(core_static_libraries)
    LOCAL_MODULE_TAGS := optional
    LOCAL_MODULE := libjavacore
    LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/NativeCode.mk
    include external/stlport/libstlport.mk
    include $(BUILD_SHARED_LIBRARY)

得知/libcore/luni/src/main/native/libcore_io_Posix.cpp被编译成了libjavacore.so。

所以,我们得出思路:修改libjavacore.so中write的got表项的值。

Question 3 :如何找到got表中存储write函数地址的表项?

ELF文件有执行视图和链接视图的区分。以下,我们按照执行视图去寻找表项。
大致流程是:

  1. 通过ELF文件头找到Program Header
  2. 通过Program Header找到.dynamic节
  3. 通过.dynamic节找到重定位表和符号表以及必需的附加信息
  4. 查找符号表,得出"write"函数在符号表中的索引
  5. 遍历重定位表,计算每一个重定位项在符号表中的索引
  6. 比较第四步与第五步得出的索引,若相等,则表明找到了"write"的重定位项,读出offset即可。

需要注意的是,第4步查找符号表。实际上,ELF文件完成这一步是依靠HASH表来完成的。
HASH表的结构是这样子的:

||nbucket
||nchain
||bucket[0]~ bucket[nbucket - 1]
||chain [0]~ chain[nchain - 1]

bucket 数组包含 nbucket 个项目, chain 数组包含 nchain 个项目, 下标都是从
0 开始。 bucket 和 chain 中都保存符号表索引。 Chain 表项和符号表存在对应。 符号
表项的数目应该和 nchain 相等,所以符号表的索引也可用来选取 chain 表项。哈希
函数能够接受符号名并且返回一个可以用来计算 bucket 的索引。

因此,如果哈希函数针对某个名字返回了数值 X,则 bucket[X%nbucket] 给出了
一个索引 y, 该索引可用于符号表, 也可用于 chain 表。 如果符号表项不是所需要的,
那么 chain[y] 则给出了具有相同哈希值的下一个符号表项。我们可以沿着 chain 链
一直搜索,直到所选中的符号表项包含了所需要的符号,或者 chain 项中 包含值
STN_UNDEF。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容