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(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。

你可能感兴趣的:(Android下hook实现文件读写透明加解密)