需求:給一個(gè)目標(biāo)apk变屁,要求hook它的native層代碼眼俊,但是不能修改它原本的so文件。
實(shí)現(xiàn)方法:通過(guò)/proc/pid/maps查看目標(biāo)so文件加載到內(nèi)存的基址粟关,然后利用ida查看目標(biāo)函數(shù)在so文件的內(nèi)存偏移疮胖,兩個(gè)數(shù)字相加得到目標(biāo)函數(shù)的內(nèi)存地址,然后利用Android-Inline-Hook框架編寫(xiě)c文件闷板,編譯生成so文件澎灸,再修改apk中的smali文件,加載我們的so文件遮晚,從而達(dá)到hook的效果性昭。
實(shí)例演示:
1、首先看一下我們要hook的apk县遣,這里的apk是我自己寫(xiě)的一個(gè)小demo而已糜颠,它的MainActivity包含一個(gè)native的int方法,同時(shí)在setText()方法中調(diào)用它萧求。
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText("calc() -- "+calc());
}
public native int calc();
}
native方法的實(shí)現(xiàn)很簡(jiǎn)單其兴,只是返回666而已。
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jint JNICALL
Java_com_example_shang_nativehookdemo_MainActivity_calc(
JNIEnv *env,
jobject /* this */) {
return 666;
}
2夸政、這里我們需要hook的就是這個(gè)calc()方法元旬,我想讓它的值返回的是777而不是666。我選擇使用的框架是Android-Inline-Hook (https://github.com/ele7enxxh/Android-Inline-Hook)。這里框架的使用相對(duì)比較簡(jiǎn)單匀归,我們可以看一下GitHub上提供的例子坑资。
#include <stdio.h>
#include "inlineHook.h"
int (*old_puts)(const char *) = NULL;
int new_puts(const char *string)
{
old_puts("inlineHook success");
}
int hook()
{
if (registerInlineHook((uint32_t) puts, (uint32_t) new_puts, (uint32_t **) &old_puts) != ELE7EN_OK) {
return -1;
}
if (inlineHook((uint32_t) puts) != ELE7EN_OK) {
return -1;
}
return 0;
}
int unHook()
{
if (inlineUnHook((uint32_t) puts) != ELE7EN_OK) {
return -1;
}
return 0;
}
int main()
{
puts("test");
hook();
puts("test");
unHook();
puts("test");
}
可以看到在hook()方法中,registerInlineHook就是注冊(cè)hook函數(shù)相關(guān)的了穆端,參入的參數(shù)第一個(gè)就是你要hook的目標(biāo)函數(shù)地址盐茎,第二個(gè)是自己的替換函數(shù)指針,第三個(gè)是保留函數(shù)原來(lái)的指針徙赢。
然后通過(guò)inlineHook正式hook到目標(biāo)函數(shù)。如果想要解除hook的話就通過(guò)inlineUnHook方法來(lái)實(shí)現(xiàn)探越。
回到我們這個(gè)例子狡赐,這里主要的就是拿到calc()這個(gè)函數(shù)在內(nèi)存中的地址,因?yàn)閍pk運(yùn)行時(shí)會(huì)把so文件的內(nèi)容加載到內(nèi)存中去钦幔,這個(gè)內(nèi)存基址我們可以通過(guò)/proc/pid/maps來(lái)得到枕屉,再通過(guò)ida查看calc()函數(shù)在內(nèi)存中的偏移,兩個(gè)數(shù)值相加即為這里的第一個(gè)參數(shù)(注意:有時(shí)需要加1鲤氢,因?yàn)樵趯ふ覍?duì)應(yīng)的方法地址時(shí)搀擂,要注意是否時(shí)Thumb指令,是的話要地址+1卷玉,還有的情況是函數(shù)地址(二進(jìn)制)最后一位為0,即arm指令時(shí)哨颂,需要 +1 );
第二個(gè)參數(shù)是替換的函數(shù)指針相种,我們需要定義一個(gè)跟目標(biāo)函數(shù)類型一致的方法威恼,然后在里面實(shí)現(xiàn)我們替換的邏輯。
第三個(gè)是目標(biāo)函數(shù)的類型寝并。
3箫措、首先下載GitHub上代碼,https://github.com/ele7enxxh/Android-Inline-Hook衬潦,適當(dāng)做一些修改斤蔓。
把example的hooktest.c拿出來(lái),修改Android.mk和Application.mk文件镀岛。
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hook
LOCAL_SRC_FILES := inlineHook.c relocate.c hooktest.c
LOCAL_LDLIBS += -lz
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := armeabi-v7a
APP_PIE:= true
這里我只生成armeabi-v7a的so文件弦牡。
修改hooktest.c文件,首先編寫(xiě)一個(gè)得到指定so文件內(nèi)存基址的函數(shù)
static unsigned long find_database_of(char* soName)//獲取目標(biāo)so內(nèi)存基址
{
char filename[32];
char cmdline[256];
sprintf(filename, "/proc/%d/maps", getpid());
LOGD("filename = %s", filename);
FILE *fp = fopen(filename, "r");
unsigned long revalue = 0;
if (fp)
{
while(fgets(cmdline, 256, fp)) //逐行讀取
{
if(strstr(cmdline, soName) && strstr(cmdline, "r-xp"))//篩選
{
LOGD("cmdline = %s",cmdline);
char *str = strstr(cmdline,"-");
if(str)
{
*str='\0';
char num[32];
sprintf(num, "0x%s", cmdline);
revalue = strtoul( num, NULL, 0 );
LOGD("revalue = %lu", revalue);
return revalue;
}
}
memset(cmdline,0,256); //清零
}
fclose(fp);
}
return 0L;
}
然后用apktool反編譯apk哎媚,用ida打開(kāi)lib下的so文件喇伯,查找calc()函數(shù)在so文件的偏移地址0x590。
完整的testhook.c文件如下
#include <stdio.h>
#include <jni.h>
#include "include/inlineHook.h"
#include <android/log.h>
#include <sys/types.h>
#include <unistd.h>
#define LOG_TAG "xyz"
#define LOGD(fmt,args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,fmt, ##args)
static unsigned long find_database_of(char* soName)//獲取libcocos2dlua.so內(nèi)存基址
{
char filename[32];
char cmdline[256];
sprintf(filename, "/proc/%d/maps", getpid());
LOGD("filename = %s", filename);
FILE *fp = fopen(filename, "r");
unsigned long revalue = 0;
if (fp)
{
while(fgets(cmdline, 256, fp)) //逐行讀取
{
if(strstr(cmdline, soName) && strstr(cmdline, "r-xp"))//篩選
{
LOGD("cmdline = %s",cmdline);
char *str = strstr(cmdline,"-");
if(str)
{
*str='\0';
char num[32];
sprintf(num, "0x%s", cmdline);
revalue = strtoul( num, NULL, 0 );
LOGD("revalue = %lu", revalue);
return revalue;
}
}
memset(cmdline,0,256); //清零
}
fclose(fp);
}
return 0L;
}
unsigned long func = NULL;
int (*old_calc)(void* env,void* jobject) = NULL;
int new_CalcFunc(void* env,void* jobject)
{
int ret = old_calc(env,jobject);
LOGD("修改前的ret = %d", ret);
return 777;
}
int hookCalcFunc()
{
LOGD("func = %x", func);
if (registerInlineHook((uint32_t) func, (uint32_t) new_CalcFunc, (uint32_t **) &old_calc) != ELE7EN_OK) {
return -1;
}
if (inlineHook((uint32_t) func) != ELE7EN_OK) {
return -1;
}
LOGD("hookCalcFunc-------");
return 0;
}
int unHookCalcFunc()
{
if (inlineUnHook((uint32_t) func) != ELE7EN_OK) {
return -1;
}
return 0;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM*vm, void* reserved){
LOGD("enter JNI_OnLoad");
unsigned long base = find_database_of("libnative-lib.so");
LOGD("base = %x ",base);
if (base > 0L) {
func = base + 0x590 + 1;
void* func1 = (void*)(base + 0x590 + 1);
LOGD("FUNC = %x", func);
hookCalcFunc();
}
return JNI_VERSION_1_6;
}
這里要注意的就是jni方法的編寫(xiě)拨与,默認(rèn)都是帶兩個(gè)參數(shù)的稻据,所以new_CalcFunc(void* env,void* jobject)才會(huì)有兩個(gè)參數(shù),因?yàn)槠鋵?shí)這兩個(gè)都沒(méi)有用到,所以用void* 代替即可捻悯。
4匆赃、通過(guò)ndk-build命令生成so文件
注意生成的包是在當(dāng)前目錄的上一個(gè)目錄的libs目錄下
將生成的so文件放到反編譯后的lib目錄下, 同時(shí)修改MainActivity.smali文件今缚,加載我們的so文件算柳。
5、重新打包姓言,簽名瞬项,運(yùn)行即可看到calc()被hook后并且修改了。
附上下載地址:
https://github.com/carrys17/Study-Notes/tree/master/NativeHookDemo%E2%80%94%E2%80%94resource