zoukankan      html  css  js  c++  java
  • android hook 框架 libinject 如何实现so注入

    前面两篇

    android hook 框架 libinject2 简介、编译、运行

    android hook 框架 libinject2 如何实现so注入  

    实际运行并分析了 Android中的so注入(inject)和挂钩(hook) - For both x86 and arm 这个博客给出了 libinject 改进版的代码。

    今天分析一下古河大神原始的 libinject 的源码,libinject2 与 原始的 libinject 大部分代码是一致的,各种 ptrace 的封装函数基本照抄,但有一点很不一样,古河的 libinject 在执行so注入及执行注入的so内的hook函数时,是将一连串的函数构造成一块统一的 shellcode ,然后整块shellcode 写入目标进程 mmap 出来的一块地址,设置目标进程寄存器,一次性调用。 而 libinject2 是分开多次设置目标进程的内存和寄存器,并多次调用实现同样的效果。从代码可读性看,libinject2 更清晰易懂。这一篇文章只分析两者不同的部分。

    首先,shellcode 代码写在一个单独的汇编文件里, shellcode.s  , 将需要在目标进程执行的 dlopen,dlsym,dlclose, 及hook函数都先用汇编写好,各个函数的真实地址和参数的真实值由 inject.c 里的函数动态指定,这份汇编会与 inject.c 编译后的汇编链接在一起

    .global _dlopen_addr_s
    .global _dlopen_param1_s
    .global _dlopen_param2_s
    
    .global _dlsym_addr_s
    .global _dlsym_param2_s
    
    .global _dlclose_addr_s
    
    .global _inject_start_s
    .global _inject_end_s
    
    .global _inject_function_param_s
    
    .global _saved_cpsr_s
    .global _saved_r0_pc_s
    
    .data
    
    _inject_start_s:
            @ debug loop
    3:
            @sub r1, r1, #0
            @B 3b
    
            @ dlopen
            ldr r1, _dlopen_param2_s
            ldr r0, _dlopen_param1_s
            ldr r3, _dlopen_addr_s
            blx r3
            subs r4, r0, #0
            beq     2f
    
            @dlsym
            ldr r1, _dlsym_param2_s
            ldr r3, _dlsym_addr_s
            blx r3
            subs r3, r0, #0
            beq 1f
    
            @call our function
            ldr r0, _inject_function_param_s
            blx r3
            subs r0, r0, #0
            beq 2f
    
    1:
            @dlclose
            mov r0, r4
            ldr r3, _dlclose_addr_s
            blx r3
    
    2:
            @restore context            
            ldr r1, _saved_cpsr_s
            msr cpsr_cf, r1
            ldr sp, _saved_r0_pc_s
            ldmfd sp, {r0-pc}
    
    _dlopen_addr_s:
    .word 0x11111111
    
    _dlopen_param1_s:
    .word 0x11111111
    
    _dlopen_param2_s:
    .word 0x2
    
    _dlsym_addr_s:
    .word 0x11111111
    
    _dlsym_param2_s:
    .word 0x11111111
    
    _dlclose_addr_s:
    .word 0x11111111
    
    _inject_function_param_s:
    .word 0x11111111
    
    _saved_cpsr_s:
    .word 0x11111111
    
    _saved_r0_pc_s:
    .word 0x11111111
    
    _inject_end_s:
    
    .space 0x400, 0
    
    .end

    这里插入一下,怎么在android里使用这份代码呢,仍然是新建一个module,然后 Android.mk 如下:

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE    := inject
    LOCAL_SRC_FILES := ../inject.c ../shellcode.s
    LOCAL_ARM_MODE := arm
    LOCAL_CFLAGS := -g
    include $(BUILD_EXECUTABLE)

    在Android.mk文件目录下执行 ndk-build 即可

    libinject 的注入过程如下:

    int inject_remote_process( pid_t target_pid, const char *library_path, const char *function_name, void *param, size_t param_size )
    {
    int ret = -1; void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr; void *local_handle, *remote_handle, *dlhandle; uint8_t *map_base; struct pt_regs regs, original_regs; extern uint32_t _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s, _saved_cpsr_s, _saved_r0_pc_s; // shellcode.s 汇编代码定义的变量 long parameters[10]; DEBUG_PRINT( "[+] Injecting process: %d ", target_pid );

             if ( ptrace_attach( target_pid ) == -1 )
                  return EXIT_SUCCESS;

    if ( ptrace_getregs( target_pid, &regs ) == -1 )
                    goto exit;
    
            /* save original registers */
            memcpy( &original_regs, &regs, sizeof(regs) ); // 第一步,attach目标进程,并保留注入前的寄存器状态
    
            mmap_addr = get_remote_addr( target_pid, "/system/lib/libc.so", (void *)mmap );
    
            /* call mmap */
            parameters[0] = 0;      // addr
            parameters[1] = 0x4000; // size
            parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC;  // prot
            parameters[3] =  MAP_ANONYMOUS | MAP_PRIVATE; // flags
            parameters[4] = 0; //fd
            parameters[5] = 0; //offset
    
            DEBUG_PRINT( "[+] Calling mmap in target process.
    " );

             if ( ptrace_call( target_pid, (uint32_t)mmap_addr, parameters, 6, &regs ) == -1 )
                goto exit;

    if ( ptrace_getregs( target_pid, &regs ) == -1 )
                    goto exit;
    
            map_base = (uint8_t *)regs.ARM_r0; // 第二步,在目标进程执行 mmap 分配一块内存
    
            dlopen_addr = get_remote_addr( target_pid, linker_path, (void *)dlopen );
            dlsym_addr = get_remote_addr( target_pid, linker_path, (void *)dlsym );
            dlclose_addr = get_remote_addr( target_pid, linker_path, (void *)dlclose );
    
            remote_code_ptr = map_base + 0x3C00;
            local_code_ptr = (uint8_t *)&_inject_start_s; // 本进程 shellcode 的起始地址
    
            _dlopen_addr_s = (uint32_t)dlopen_addr;
            _dlsym_addr_s = (uint32_t)dlsym_addr;
            _dlclose_addr_s = (uint32_t)dlclose_addr; //第三步,获取目标进程 dlopen,dlsym,dlclose函数地址并赋值给汇编文件定义的变量
    
    
            code_length = (uint32_t)&_inject_end_s - (uint32_t)&_inject_start_s;
            dlopen_param1_ptr = local_code_ptr + code_length + 0x20;
            dlsym_param2_ptr = dlopen_param1_ptr + MAX_PATH;
            saved_r0_pc_ptr = dlsym_param2_ptr + MAX_PATH;
            inject_param_ptr = saved_r0_pc_ptr + MAX_PATH; // 设置dlopen,dlsym等函数的参数相对于汇编代码起始地址的地址

             strcpy( dlopen_param1_ptr, library_path );
            _dlopen_param1_s = REMOTE_ADDR( dlopen_param1_ptr, local_code_ptr, remote_code_ptr );

    /* dlopen parameter 1: library name */
            DEBUG_PRINT( "[+] _dlopen_param1_s: %x
    ", _dlopen_param1_s );
    
            /* dlsym parameter 2: function name */
            strcpy( dlsym_param2_ptr, function_name );
            _dlsym_param2_s = REMOTE_ADDR( dlsym_param2_ptr, local_code_ptr, remote_code_ptr );
            DEBUG_PRINT( "[+] _dlsym_param2_s: %x
    ", _dlsym_param2_s );
    
            /* saved cpsr */
            _saved_cpsr_s = original_regs.ARM_cpsr;
    
            /* saved r0-pc */
            memcpy( saved_r0_pc_ptr, &(original_regs.ARM_r0), 16 * 4 ); // r0 ~ r15
            _saved_r0_pc_s = REMOTE_ADDR( saved_r0_pc_ptr, local_code_ptr, remote_code_ptr );
            DEBUG_PRINT( "[+] _saved_r0_pc_s: %x
    ", _saved_r0_pc_s );
    
            /* Inject function parameter */
            memcpy( inject_param_ptr, param, param_size );
            _inject_function_param_s = REMOTE_ADDR( inject_param_ptr, local_code_ptr, remote_code_ptr );
            DEBUG_PRINT( "[+] _inject_function_param_s: %x
    ", _inject_function_param_s );//第四步,计算要执行的 dlopen,dlsym函数及参数在目标进程内相对于前面mmap出来的地址的地址
    
            DEBUG_PRINT( "[+] Remote shellcode address: %x
    ", remote_code_ptr );
            ptrace_writedata( target_pid, remote_code_ptr, local_code_ptr, 0x400 );//第五步: 将整块shellcode代码写入目标进程的内存
    
            memcpy( &regs, &original_regs, sizeof(regs) );
            regs.ARM_sp = (long)remote_code_ptr;
            regs.ARM_pc = (long)remote_code_ptr; 
            ptrace_setregs( target_pid, &regs ); // 第六步,修改目标进程的栈顶指针 sp 执行shellcode 的初始位置,且设置pc寄存器也指向这个位置
            ptrace_detach( target_pid ); // 第七步,detach目标进程,目标进程接下去会开始执行shellcode
    
            // inject succeeded
            ret = 0;
    
    exit:
            return ret;
    }

    我们看到,libinject 没有将 original_regs 恢复到目标进程的ptrace 操作,其实shellcode里执行完hook函数后,最后会执行  @restore context   的代码,这几句就是恢复寄存器状态的

    到目前为止,一共分析了3份so注入代码,前两份为:

    android hook 框架 ADBI 如何实现so注入

    android hook 框架 libinject2 如何实现so注入

    对比一下,

    首先,3份注入代码原理是一样的,都是利用ptrace系统调用,获取目标进程寄存器,在目标进程调用dlopen/dlsym等动态库函数实现在目标进程加载指定的so, 加载之后执行挂钩函数时,hijack 利用  __attribute__ ((constructor)) 函数属性实现自动执行,libinject/libinject2 还是通过寄存器ptrace操作执行函数。

    其次,古河的libinject和adbi的hijack都是事先构造一片‘shellcode’,再块拷贝到目标进程地址空间(先操作目标进程寄存器调用mmap分配一块),然后再执行这块预先构造好的‘shellcode’,不同是libinject 是用汇编代码构造,hijack是写在一个数组里。libinject2在实现注入时,将上述shellcode分成多次的 ptrace 调用,代码可读性更高,逻辑更清晰

    最后,libinject/libinject2只是提高了注入的实现,没有挂钩的实现。 adbi 项目还提供了挂钩框架的实现

  • 相关阅读:
    layui 动态设置radio选中
    C# ling 查询 in 用法
    sql 去除小数点后面无效的0
    VUE 全局变量申明和取值
    SQL 逗号分隔将一行拆成多行
    devexpress 延长试用期 licenses.licx
    BugkuCTF-WEB4
    一招破解网页复制+网页上如何实现禁止复制粘贴
    JS的函数
    JS的数组
  • 原文地址:https://www.cnblogs.com/jiayy/p/4287847.html
Copyright © 2011-2022 走看看