安卓動態除錯七種武器之離別鉤 – Hooking(下)

wyzsk發表於2020-08-19
作者: 蒸米 · 2015/11/03 10:30

0x00 序


隨著移動安全越來越火,各種除錯工具也都層出不窮,但因為環境和需求的不同,並沒有工具是萬能的。另外工具是死的,人是活的,如果能搞懂工具的原理再結合上自身的經驗,你也可以創造出屬於自己的除錯武器。因此,筆者將會在這一系列文章中分享一些自己經常用或原創的除錯工具以及手段,希望能對國內移動安全的研究起到一些催化劑的作用。

目錄如下:

安卓動態除錯七種武器之長生劍 - Smali Instrumentation
安卓動態除錯七種武器之孔雀翎 – Ida Pro
安卓動態除錯七種武器之離別鉤 – Hooking (上)
安卓動態除錯七種武器之離別鉤 – Hooking (下)
安卓動態除錯七種武器之碧玉刀- Customized DVM
安卓動態除錯七種武器之多情環- Customized Kernel
安卓動態除錯七種武器之霸王槍 - Anti Anti-debugging
安卓動態除錯七種武器之拳頭 - Tricks & Summary

文章中所有提到的程式碼和工具都可以在我的github下載到,地址是: https://github.com/zhengmin1989/TheSevenWeapons

0x01 利用函式掛鉤實現native層的hook


我們在離別鉤(上)中已經可以做到動態的載入自定義so檔案並且執行so檔案中的函式了,但還不能做到hook目標函式,這裡我們需要用到函式掛鉤的技術來做到這一點。函式掛鉤的基本原理是先用mprotect()將原始碼段改成可讀可寫可執行,然後修改原函式入口處的程式碼,讓pc指標跳轉到動態載入的so檔案中的hook函式中,執行完hook函式以後再讓pc指標跳轉回原本的函式中。

用來注入的程式hook5邏輯與之前的hook4相比並沒有太大變化,僅僅少了“呼叫 dlclose 解除安裝so檔案”這一個步驟,因為我們想要執行的hook後的函式在so中,所以並不需要呼叫dlclose進行解除安裝。基本步驟如下:

儲存當前暫存器的狀態 -> 獲取目標程式的mmap, dlopen, dlsym, dlclose 地址 -> 呼叫mmap分配一段記憶體空間用來儲存引數資訊 –> 呼叫dlopen載入so檔案 -> 呼叫dlsym找到目標函式地址 -> 使用ptrace_call執行目標函式 -> 恢復暫存器的狀態

hook5的主要程式碼邏輯如下:

#!c
void injectSo(pid_t pid,char* so_path, char* function_name,char* parameter)
{
    struct pt_regs old_regs,regs;
    long mmap_addr, dlopen_addr, dlsym_addr, dlclose_addr;

//save old regs

    ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
    memcpy(&regs, &old_regs, sizeof(regs));

//get remote addres

    printf("getting remote addres:\n");
    mmap_addr = get_remote_addr(pid, libc_path, (void *)mmap);
    dlopen_addr = get_remote_addr( pid, libc_path, (void *)dlopen );
    dlsym_addr = get_remote_addr( pid, libc_path, (void *)dlsym );
    dlclose_addr = get_remote_addr( pid, libc_path, (void *)dlclose );

    printf("mmap_addr=%p dlopen_addr=%p dlsym_addr=%p dlclose_addr=%p\n",
    (void*)mmap_addr,(void*)dlopen_addr,(void*)dlsym_addr,(void*)dlclose_addr);


    long parameters[10];

//mmap

    parameters[0] = 0; //address
    parameters[1] = 0x4000; //size
    parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; //WRX
    parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; //flag
    parameters[4] = 0; //fd
    parameters[5] = 0; //offset

    ptrace_call(pid, mmap_addr, parameters, 6, &regs);
    ptrace(PTRACE_GETREGS, pid, NULL, &regs);

    long map_base = regs.ARM_r0;
    printf("map_base = %p\n", (void*)map_base);

//dlopen

    printf("save so_path = %s to map_base = %p\n", so_path, (void*)map_base);
    putdata(pid, map_base, so_path, strlen(so_path) + 1);

    parameters[0] = map_base;
    parameters[1] = RTLD_NOW| RTLD_GLOBAL;

    ptrace_call(pid, dlopen_addr, parameters, 2, &regs);
    ptrace(PTRACE_GETREGS, pid, NULL, &regs);

    long handle = regs.ARM_r0;

    printf("handle = %p\n",(void*) handle);

//dlsym

    printf("save function_name = %s to map_base = %p\n", function_name, (void*)map_base);
    putdata(pid, map_base, function_name, strlen(function_name) + 1);

    parameters[0] = handle;
    parameters[1] = map_base;

    ptrace_call(pid, dlsym_addr, parameters, 2, &regs);
    ptrace(PTRACE_GETREGS, pid, NULL, &regs);

    long function_ptr = regs.ARM_r0;

    printf("function_ptr = %p\n", (void*)function_ptr);

//function_call

    printf("save parameter = %s to map_base = %p\n", parameter, (void*)map_base);
    putdata(pid, map_base, parameter, strlen(parameter) + 1);

    parameters[0] = map_base;

    ptrace_call(pid, function_ptr, parameters, 1, &regs);

//restore old regs

    ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
}

我們知道arm處理器支援兩種指令集,一種是arm指令集,另一種是thumb指令集。所以要hook的函式可能是被編譯成arm指令集的,也有可能是被編譯成thumb指令集的。Thumb指令集可以看作是arm指令壓縮形式的子集,它是為減小程式碼量而提出,具有16bit的程式碼密度。Thumb指令體系並不完整,只支援通用功能,必要時仍需要使用ARM指令,如進入異常時。需要注意的一點是thumb指令的長度是不固定的,但arm指令是固定的32位長度。

為了讓大家更容易的理解hook的原理,我們先只考慮arm指令集,因為arm相比thumb要簡單一點,不需要考慮指令長度的問題。所以我們需要將target和hook的so編譯成arm指令集的形式。怎麼做呢?很簡單,只要在Android.mk中的檔名後面加上”.arm”即可 (真正的檔案不用加)。

#!c
include $(CLEAR_VARS)
LOCAL_MODULE    := target
LOCAL_SRC_FILES := target.c.arm
include $(BUILD_EXECUTABLE)

include $(CLEAR_VARS)
LOCAL_MODULE    := inject2
LOCAL_SRC_FILES := inject2.c.arm
LOCAL_LDLIBS := -llog 
include $(BUILD_SHARED_LIBRARY)

確定了指令集以後,我們來看實現掛鉤最重要的邏輯,這個邏輯是在注入後的so裡實現的。首先我們需要一個結構體儲存彙編程式碼和hook地址:

#!c
struct hook_t {
    unsigned int jump[3]; //儲存跳轉指令
    unsigned int store[3]; //儲存原指令
    unsigned int orig; //儲存原函式地址
    unsigned int patch; //儲存hook函式地址
};

我們接著來看注入的邏輯,最重要的函式為hook_direct(),他有三個引數,一個引數是我們最開始定義的用來儲存彙編程式碼和hook地址的結構體,第二個是我們要hook的原函式的地址,第三個是我們用來執行hook的函式。函式的原始碼如下:

#!c
int hook_direct(struct hook_t *h, unsigned int addr, void *hookf)
{
    int i;

    printf("addr  = %x\n", addr);
    printf("hookf = %x\n", (unsigned int)hookf);

//mprotect
    mprotect((void*)0x8000, 0xa000-0x8000, PROT_READ|PROT_WRITE|PROT_EXEC);

//modify function entry 
    h->patch = (unsigned int)hookf;
    h->orig = addr;
    h->jump[0] = 0xe59ff000; // LDR pc, [pc, #0]
    h->jump[1] = h->patch;
    h->jump[2] = h->patch;
    for (i = 0; i < 3; i++)
        h->store[i] = ((int*)h->orig)[i];
    for (i = 0; i < 3; i++)
        ((int*)h->orig)[i] = h->jump[i];

//cacheflush    
    hook_cacheflush((unsigned int)h->orig, (unsigned int)h->orig+sizeof(h->jump));
    return 1;
}

雖然android有ASLR,但並沒有PIE,所以program image是固定在0x8000這個地址的,因此我們用mprotect()函式將整個target程式碼段變成RWX,這樣我們就能修改函式入口處的程式碼了。是否修改成功可以透過cat /proc/[pid]/maps檢視:

#!bash
# cat /proc/25298/maps
00008000-0000a000 rwxp 00000000 b3:1c 627105     /data/local/tmp/target
0000a000-0000b000 r--p 00001000 b3:1c 627105     /data/local/tmp/target
0000b000-0000c000 rw-p 00000000 00:00 0 
0017f000-00180000 rw-p 00000000 00:00 0          [heap]
……

隨後我們需要確定目標函式的地址,這個有兩種方法。如果目標程式本身沒有被strip的話,那些symbol都是存在的,因此可以使用dlopen()和dlsym()等方法來獲取目標函式地址。但很多情況,目標程式都會被strip,特別是可以直接執行的二進位制檔案預設都會被直接strip。比如target中的sevenWeapons()這個函式名會在編譯的時候去掉,所以我們使用dlsym()的話是無法找到這個函式的。這時候我們就需要使用ida或者objdump來定位一下目標函式的地址。比如我們用objdump找一下target程式裡面sevenWeapons(int number)這個函式的地址:

#!bash
……
    84d4:       e1a01000        mov     r1, r0
    84d8:       e59f200c        ldr     r2, [pc, #12]   ; 84ec <[email protected]+0xe4>
    84dc:       e59f000c        ldr     r0, [pc, #12]   ; 84f0 <[email protected]+0xe8>
    84e0:       e08f2002        add     r2, pc, r2
    84e4:       e08f0000        add     r0, pc, r0
    84e8:       eaffffb1        b       83b4 <[email protected]>
    84ec:       00002b18        andeq   r2, r0, r8, lsl fp
    84f0:       ffffff58                        ; <UNDEFINED> instruction: 0xffffff58
    84f4:       e1a02000        mov     r2, r0
    84f8:       e59f100c        ldr     r1, [pc, #12]   ; 850c <[email protected]+0x104>
    84fc:       e59f000c        ldr     r0, [pc, #12]   ; 8510 <[email protected]+0x108>
    8500:       e08f1001        add     r1, pc, r1
    8504:       e08f0000        add     r0, pc, r0
    8508:       eaffffac        b       83c0 <[email protected]>
    850c:       00001080        andeq   r1, r0, r0, lsl #1
    8510:       00001074        andeq   r1, r0, r4, ror r0
    8514:       b5006803        strlt   r6, [r0, #-2051]        ; 0xfffff7fd
    8518:       d503005a        strle   r0, [r3, #-90]  ; 0xffffffa6
851c:       06122280        ldreq   r2, [r2], -r0, lsl #5
……

雖然target這個binary被strip了,但還是可以找到sevenWeapons()這個函式的起始地址是在0x84f4。因為”mov r2, r0”就是載入number這個引數的指令,隨後呼叫了<[email protected]>用來輸出結果。 最後一個引數也就是我們要執行的hook函式的地址。得到這個地址非常簡單,因為是so中的函式,呼叫hook_direct()的時候直接寫上函式名即可。

hook_direct(&eph,hookaddr,my_sevenWeapons);

接下來我們看如何修改函式入口(modify function entry),首先我們儲存一下原函式的地址和那個函式的前三條指令。隨後我們把目標函式的第一條指令修改為 LDR pc, [pc, #0],這條指令的意思是跳轉到PC指標所指的地址,由於pc暫存器讀出的值實際上是當前指令地址加8,所以我們把後面兩處指令都儲存為hook函式的地址,這樣的話,我們就能控制PC跳轉到hook函式的地址了。

最後我們再呼叫hook_cacheflush()這個函式來重新整理一下指令的快取。因為雖然前面的操作修改了記憶體中的指令,但有可能被修改的指令已經被快取起來了,再執行的話,CPU可能會優先執行快取中的指令,使得修改的指令得不到執行。所以我們需要使用一個隱藏的系統呼叫來重新整理一下快取。hook_cacheflush()程式碼如下:

#!c
void inline hook_cacheflush(unsigned int begin, unsigned int end)
{   
    const int syscall = 0xf0002;

    __asm __volatile (
        "mov     r0, %0\n"          
        "mov     r1, %1\n"
        "mov     r7, %2\n"
        "mov     r2, #0x0\n"
        "svc     0x00000000\n"
        :
        :   "r" (begin), "r" (end), "r" (syscall)
        :   "r0", "r1", "r7"
        );
}

重新整理完快取後,再執行到原函式的時候,pc指標就會跳轉到我們自定義的hook函式中了,hook函式里的程式碼如下:

#!c
void  __attribute__ ((noinline)) my_sevenWeapons(int number)
{
    printf("sevenWeapons() called, number = %d\n", number);
    number++;

    void (*orig_sevenWeapons)(int number);
    orig_sevenWeapons = (void*)eph.orig;

    hook_precall(&eph);
    orig_sevenWeapons(number);
    hook_postcall(&eph);

}

首先在hook函式中,我們可以獲得原函式的引數,並且我們可以對原函式的引數進行修改,比如說將數字乘2。隨後我們使用hook_precall(&eph);將原本函式的內容進行還原。hook_precall()內容如下:

#!c
void hook_precall(struct hook_t *h)
{
    int i;
    for (i = 0; i < 3; i++)
        ((int*)h->orig)[i] = h->store[i];

    hook_cacheflush((unsigned int)h->orig, (unsigned int)h->orig+sizeof(h->jump)*10);
}

在hook_precall()中,我們先對原本的三條指令進行還原,然後使用hook_cacheflush()對記憶體進行重新整理。經過處理之後,我們就可以執行原來的函式orig_sevenWeapons(number)了。執行完後,如果我們還想再次hook這個函式,就需要呼叫hook_postcall(&eph)將原本的三條指令再進行一次修改。

下面我們來使用hook5和libinject2.so來注入一下target這個程式:

#!bash
# ./target
Hello,LiBieGou! 0
Hello,LiBieGou! 1
Hello,LiBieGou! 2
Hello,LiBieGou! 3
Hello,LiBieGou! 4
Hello,LiBieGou! 5
Hello,LiBieGou! 6
mzheng Hook pid = 18962
Hello sevenWeapons
addr  = 84f4
hookf = b6e73e88
sevenWeapons() called, number = 7
Hello,LiBieGou! 14
sevenWeapons() called, number = 8
Hello,LiBieGou! 16
sevenWeapons() called, number = 9
Hello,LiBieGou! 18
sevenWeapons() called, number = 10
Hello,LiBieGou! 20
sevenWeapons() called, number = 11
Hello,LiBieGou! 22
sevenWeapons() called, number = 12
Hello,LiBieGou! 24
sevenWeapons() called, number = 13

./hook5 28922                                                                                                                                                             
getting remote addres:
mmap_addr=0xb6f84c81 dlopen_addr=0xb6fd4f4d dlsym_addr=0xb6fd4e9d dlclose_addr=0xb6fd4e19
map_base = 0xb6f33000
save so_path = /data/local/tmp/libinject2.so to map_base = 0xb6f33000
handle = 0xb6fd1494
save function_name = mzhengHook to map_base = 0xb6f33000
function_ptr = 0xb6f2e368
save parameter = sevenWeapons to map_base = 0xb6f33000

可以看到經過注入後,我們成功的獲取了引數number的值,並且將”Hello,LiBieGou!”後面的數字變成了原來的兩倍。

0x02 使用adbi實現JNI層的hook


我們在上一節中介紹瞭如何hook native層的函式。下面我們再來講講如何利用adbi來hook JNI層的函式。Adbi是一個android平臺上的一個注入框架,本身是開源的。Hook的原理和我們之前介紹的技術是一樣的,這個框架支援arm和thumb指令集,也支援透過字串定位symbol函式的地址。

首先我們需要一個例子用來講解,所以我寫了程式叫test2。

enter image description here

點選程式中的button後,程式會呼叫so中的Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz,jint a,jint b)函式用來計算a+b的結果。我們預設傳的引數是a=1, b=1。接下來我就來教你如何利用adbi來hook這個JNI函式。

因為adbi是一個注入框架,我們下載好原始碼後,只要對應著原始碼中給的example照貓畫虎即可。Hijack那個資料夾是儲存的用來注入的程式,和我們之前講的hook5.c的原理是一樣的,所以不用做任何修改。我們只需要修改example中的程式碼,也就是將要注入的so檔案的原始碼。

首先,我們在/adbi-master/instruments/example這個資料夾下新建兩個檔案”hookjni.c”和” hookjni_arm.c”。“hookjni_arm.c”其實只是一個殼,用來將hook函式的入口編譯成arm指令集的,內容如下:

#!c
extern jstring my_Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz,jint a,jint b);

jstring my_Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI_arm(JNIEnv* env,jobject thiz,jint a,jint b)
{
    return my_Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI(env, thiz, a, b);
}

這個檔案的目的僅僅是為了用arm指令集進行編譯,可以看到Android.mk中在”hookjni_arm.c”後面多了個”.arm”:

#!c
include $(CLEAR_VARS)
LOCAL_MODULE    := libexample
LOCAL_SRC_FILES := ../hookjni.c  ../hookjni_arm.c.arm
LOCAL_CFLAGS := -g
LOCAL_SHARED_LIBRARIES := dl
LOCAL_STATIC_LIBRARIES := base
include $(BUILD_SHARED_LIBRARY)

下面我們來看”hookjni.c”:

#!c
jstring my_Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz,jint a,jint b)
{
    jstring (*orig_stringFromJNI)(JNIEnv* env,jobject thiz,jint a,jint b);
    orig_stringFromJNI = (void*)eph.orig;

    a = 10;
    b = 10;

    hook_precall(&eph);
    jstring res = orig_stringFromJNI(env, thiz, a, b);
    if (counter) {
        hook_postcall(&eph);
        log("stringFromJNI() called\n");
        counter--;
        if (!counter)
            log("removing hook for stringFromJNI()\n");
    }

    return res;
}

void my_init(void)
{
    counter = 3;
    log("%s started\n", __FILE__)
    set_logfunction(my_log);

    hook(&eph, getpid(), "libhello-jni.", "Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI", my_Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI_arm, my_Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI);
}

這段程式碼和我上一節講的程式碼非常像,my_init()用來進行hook操作,我們需要提供想要hook的so檔名和函式名,然後再提供thumb指令集和arm指令集的hook函式地址。

my_Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI()就是我們提供的hook函式了。我們在這個hook函式中把a和b都改成了10。除此之外,我們還使用counter這個全域性變數來控制hook的次數,這裡我們把counter設定為3,當hook了3次以後,就不再進行hook操作了。

編輯好程式碼後,我們只需要在adbi的根目錄下執行” build.sh”進行編譯:

#!bash
adbi-master$ ./build.sh 
[armeabi] Compile arm    : hijack <= hijack.c
[armeabi] Executable     : hijack
[armeabi] Install        : hijack => libs/armeabi/hijack
[armeabi] Compile arm    : base <= util.c
[armeabi] Compile arm    : base <= hook.c
[armeabi] Compile arm    : base <= base.c
[armeabi] StaticLibrary  : libbase.a
[armeabi] Compile thumb  : example <= hookjni.c
[armeabi] Compile arm    : example <= hookjni_arm.c
[armeabi] SharedLibrary  : libexample.so
[armeabi] Install        : libexample.so => libs/armeabi/libexample.so

編譯好後,我們把hijack和libexample.so複製到/data/local/tmp目錄下。然後使用hijack進行注入:

#!bash
#./hijack -d -p 21734 -l /data/local/tmp/libexample.so                                                                                                                     
mprotect: 0x4011c444
dlopen: 0x400d5f4d
pc=4011d6e0 lr=4018588b sp=bed65308 fp=bed6549c
r0=fffffffc r1=bed65328
r2=10 r3=ffffffff
stack: 0xbed45000-0xbed66000 leng = 135168
executing injection code at 0xbed652b8
calling mprotect
library injection completed!

然後我們再點選button就可以看到我們已經成功的修改了a和b的值為10了,最後顯示1+1=20。

enter image description here

透過cat adbi_example.log我們可以看到hook過程中列印的log:

#!bash
#cat adbi_example.log                                                                                                                                                      
/home/aliray/7weapons/libiegou/adbi-master/instruments/example/jni/../hookjni.c started
hooking:   Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI = 0x7538ecc5 THUMB using 0x763c9581
stringFromJNI() called
stringFromJNI() called
stringFromJNI() called
removing hook for stringFromJNI()

可以看到adbi是透過thumb指令集進行hook的,原因是test2程式是用thumb指令集進行編譯的。其實hook thumb指令集和arm指令集差不多,在”/adbi-master/instruments/base”中可以找到hook thumb指令集的邏輯:

    if ((unsigned long int)hook_thumb % 4 == 0)
        log("warning hook is not thumb 0x%lx\n", (unsigned long)hook_thumb)
    h->thumb = 1;
    log("THUMB using 0x%lx\n", (unsigned long)hook_thumb)
    h->patch = (unsigned int)hook_thumb;
    h->orig = addr; 
    h->jumpt[1] = 0xb4;
    h->jumpt[0] = 0x60; // push {r5,r6}
    h->jumpt[3] = 0xa5;
    h->jumpt[2] = 0x03; // add r5, pc, #12
    h->jumpt[5] = 0x68;
    h->jumpt[4] = 0x2d; // ldr r5, [r5]
    h->jumpt[7] = 0xb0;
    h->jumpt[6] = 0x02; // add sp,sp,#8
    h->jumpt[9] = 0xb4;
    h->jumpt[8] = 0x20; // push {r5}
    h->jumpt[11] = 0xb0;
    h->jumpt[10] = 0x81; // sub sp,sp,#4
    h->jumpt[13] = 0xbd;
    h->jumpt[12] = 0x20; // pop {r5, pc}
    h->jumpt[15] = 0x46;
    h->jumpt[14] = 0xaf; // mov pc, r5 ; just to pad to 4 byte boundary
    memcpy(&h->jumpt[16], (unsigned char*)&h->patch, sizeof(unsigned int));
    unsigned int orig = addr - 1; // sub 1 to get real address
    for (i = 0; i < 20; i++) {
        h->storet[i] = ((unsigned char*)orig)[i];
        //log("%0.2x ", h->storet[i])
    }
    //log("\n")
    for (i = 0; i < 20; i++) {
        ((unsigned char*)orig)[i] = h->jumpt[i];
        //log("%0.2x ", ((unsigned char*)orig)[i])
    }

其實h->jumpt[20]這個字元陣列儲存的就是thumb指令集下控制pc指標跳轉到hook函式地址的程式碼。Hook完之後的流程就和arm指令集的hook一樣了。

0x03 使用Cydia或Xposed實現JAVA層的hook


關於Cydia和Xposed的文章和例子已經很多了,這裡就不再重複的進行介紹了。這裡推薦一下瘦蛟舞和我寫的文章,基本上就知道怎麼使用這兩個框架了:

Android.Hook框架xposed篇(Http流量監控) /papers/?id=7488

Android.Hook框架Cydia篇(脫殼機制作) /tips/?id=8084

手把手教你當微信運動第一名 – 利用Android Hook進行微信運動作弊 /tips/?id=8416

個人感覺Xposed框架要做的更好一些,主要原因是Cydia的作者已經很久沒有更新過Cydia框架了,不光有很多bug還不支援ART。但是有很多不錯的除錯軟體/外掛是基於兩個框架製作的,所以有時候還是需要用到Cyida的。

接下來就推薦幾個很實用的基於Cydia和Xposed的外掛:

  1. ZjDroid: ZjDroid是基於Xposed Framewrok的動態逆向分析模組,逆向分析者可以透過ZjDroid完成以下工作: 1、DEX檔案的記憶體dump 2、基於Dalvik關鍵指標的記憶體BackSmali,有效破解主流加固方案 3、敏感API的動態監控 4、指定記憶體區域資料dump 5、獲取應用載入DEX資訊。 6、獲取指定DEX檔案載入類資訊。 7、dump Dalvik java堆資訊。 8、在目標程式動態執行lua指令碼。 https://github.com/halfkiss/ZjDroid

  2. XPrivacy: XPrivacy是一款基於Xposed框架的模組應用,可以對所有應用可能洩露隱私的許可權進行管理,對禁止可能會導致崩潰的應用採取欺騙策略,提供偽造資訊,比如說可以偽造手機的IMEI號碼等。 https://github.com/M66B/XPrivacy

  3. Introspy: Introspy是一款可以追蹤分析移動應用的黑盒測試工具並且可以發現安全問題。這個工具支援很多密碼庫的hook,還支援自定義hook。 https://github.com/iSECPartners/Introspy-Android

0x04 Introspy實戰


我們使用alictf上的evilapk400作為例子講解如何利用introspy來除錯程式。Evilapk400使用了比較複雜的dex加殼技術,如果不利用基於自定義dalvik的脫殼工具來進行脫殼的話做起來會非常麻煩。但我們如果換一種思路,直接透過動態除錯的方法來獲取加密演算法的字串,key和IV等資訊就可以直接獲取答案了。

首先我們安裝cyida.apk,Introspy-Android Config.apk到手機上,然後用eclipse開啟“Introspy-Android Core”的原始碼增加一個自定義的hook函式。雖然Introspy預設對密碼庫進行了hook,但卻沒有對一些strings的函式進行hook。所以我們手動新增一個對String.equals()的hook:

enter image description here

enter image description here

然後我們編譯,生成並安裝Introspy-Android Core.apk到手機上。然後我們安裝上EvilApk400。然後開啟Introspy-Android Config勾選com.ali.encryption。

enter image description here

接著開啟evilapk400,然後隨便輸入點內容並點選登陸。

enter image description here

然後我們使用adb logcat就能看到Introspy輸出的資訊了:

enter image description here

透過log,很容易就能看出來evilapk400使用了DES加密。透過log我們獲取了密文,Key以及IV,所以我們可以寫一個python程式來計算出最後的答案:

#!python
from M2Crypto.EVP import Cipher
from base64 import b64encode, b64decode

key = b64decode('H5jOqyCXcO+odcJFhT7Odh+Yzqsgl3Dv')
iv = b64decode('AAoKCgoCAqo=')
ciphertext = '5458d715704493d8e6b9bd38f8b6be0e'.decode('hex')
decipher = Cipher(alg='des_ede3_cbc', key=key, op=0, iv=iv)
plaintext = decipher.update(ciphertext)
plaintext += decipher.final()
print plaintext

$ python decrypt.py 
[email protected]

enter image description here

enter image description here

0x05 總結


本篇介紹了native層,JNI層以及JAVA層的hook,基本上可以滿足我們平時對於android上hook的需求了。 另外文章中所有提到的程式碼和工具都可以在我的github下載到,地址是: https://github.com/zhengmin1989/TheSevenWeapons

0x06 參考資料


  1. Android平臺下hook框架adbi的研究(下)http://blog.csdn.net/roland_sun/article/details/36049307
  2. ALICTF Writeups from Dr. Mario

0xFF 版權宣告


本文獨家首發於烏雲知識庫(drops.wooyun.org)。本文並沒有對任何單位和個人授權轉載。如本文被轉載,一定是屬於未經授權轉載,屬於嚴重的侵犯智慧財產權,本單位將追究法律責任。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章