發(fā)一篇好幾年的文章。。因為最近公司要求我研究研究hook怜森,想起來我以前做的這部分工作 >_<
實現(xiàn)原理:
修改write函數(shù)所對應的got表項中的地址殿雪,修改成自己定義的函數(shù)暇咆,則每當系統(tǒng)調(diào)用write函數(shù)時,會執(zhí)行我們自定義函數(shù)丙曙,從而只需在自定義函數(shù)中添加加密或者解密算法爸业,就能實現(xiàn)對應用開發(fā)者透明的加解密。
幾個問題:
Question 1:Android如何實現(xiàn)文件讀寫亏镰?
編寫Android應用程序時扯旷,遇到文件讀寫一般使用FileInputStream類中的read()方法和FileOutputStream類中的write方法。我們以調(diào)用FileOutputStream.write()為例索抓。
http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/java/java/io/FileOutputStream.java
源代碼如下所示钧忽,系統(tǒng)會繼續(xù)調(diào)用IoBridge中的write方法。
@Override
public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
IoBridge.write(fd, buffer, byteOffset, byteCount);
}
接著逼肯,系統(tǒng)調(diào)用final類Libcore中的static field OS的write方法耸黑。
static field os轉(zhuǎn)型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();
}
}
最終調(diào)用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);
}
系統(tǒng)繼續(xù)調(diào)用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;
最終,我們可以發(fā)現(xiàn)洲拇,系統(tǒng)將會調(diào)用native方法writeBytes.繼續(xù)深入:
http://androidxref.com/4.4_r1/xref/libcore/luni/src/main/native/libcore_io_Posix.cpp
native方法writeBytes的具體實現(xiàn)在/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函數(shù)。
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)));
}
可以發(fā)現(xiàn)赋续,writeBytes最終還是調(diào)用了write函數(shù)男翰。這個write函數(shù)應該是系統(tǒng)提供的API,在libc.so中纽乱。
Question 2 :Posix_writeBytes函數(shù)在哪一個共享庫中蛾绎?
如第一部分的分析,系統(tǒng)在Posix_writeBytes函數(shù)中調(diào)用write函數(shù)。如果我們能夠修改掉這個write函數(shù)在got中的地址租冠,那么當系統(tǒng)運行至Posix_writeBytes函數(shù)中鹏倘,并企圖調(diào)用write函數(shù)時,將進入我們自定義函數(shù)顽爹。
但是纤泵,Android APP在運行時會加載許多的.so庫文件,我們需要知道Posix_writeBytes函數(shù)在哪一個庫中镜粤,才能夠修改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函數(shù)地址的表項循狰?
ELF文件有執(zhí)行視圖和鏈接視圖的區(qū)分。以下捻浦,我們按照執(zhí)行視圖去尋找表項晤揣。
大致流程是:
- 通過ELF文件頭找到Program Header
- 通過Program Header找到.dynamic節(jié)
- 通過.dynamic節(jié)找到重定位表和符號表以及必需的附加信息
- 查找符號表,得出"write"函數(shù)在符號表中的索引
- 遍歷重定位表朱灿,計算每一個重定位項在符號表中的索引
- 比較第四步與第五步得出的索引昧识,若相等,則表明找到了"write"的重定位項盗扒,讀出offset即可跪楞。
需要注意的是,第4步查找符號表侣灶。實際上甸祭,ELF文件完成這一步是依靠HASH表來完成的。
HASH表的結構是這樣子的:
||nbucket
||nchain
||bucket[0]~ bucket[nbucket - 1]
||chain [0]~ chain[nchain - 1]
bucket 數(shù)組包含 nbucket 個項目褥影, chain 數(shù)組包含 nchain 個項目池户, 下標都是從
0 開始。 bucket 和 chain 中都保存符號表索引凡怎。 Chain 表項和符號表存在對應校焦。 符號
表項的數(shù)目應該和 nchain 相等,所以符號表的索引也可用來選取 chain 表項统倒。哈希
函數(shù)能夠接受符號名并且返回一個可以用來計算 bucket 的索引寨典。
因此,如果哈希函數(shù)針對某個名字返回了數(shù)值 X房匆,則 bucket[X%nbucket] 給出了
一個索引 y耸成, 該索引可用于符號表报亩, 也可用于 chain 表。 如果符號表項不是所需要的井氢,
那么 chain[y] 則給出了具有相同哈希值的下一個符號表項弦追。我們可以沿著 chain 鏈
一直搜索,直到所選中的符號表項包含了所需要的符號毙沾,或者 chain 項中 包含值
STN_UNDEF骗卜。