发一篇好几年的文章。。因为最近公司要求我研究研究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文件有执行视图和链接视图的区分。以下,我们按照执行视图去寻找表项。
大致流程是:
- 通过ELF文件头找到Program Header
- 通过Program Header找到.dynamic节
- 通过.dynamic节找到重定位表和符号表以及必需的附加信息
- 查找符号表,得出"write"函数在符号表中的索引
- 遍历重定位表,计算每一个重定位项在符号表中的索引
- 比较第四步与第五步得出的索引,若相等,则表明找到了"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。