以前对Android so的注入只是通过现有的框架,并没有去研究so注入原理,趁现在有时间正好拿出来研究一下。

  首先来看注入流程。Android so的注入流程如下:

attach到远程进程 -> 保存寄存器环境 -> 获取目标程序的mmap, dlopen, dlsym, dlclose 地址 -> 远程调用mmap函数申请内存空间用来保存参数信息 -> 向远程进程内存空间写入加载模块名和调用函数->远程调用dlopen函数加载so文件 -> 远程调用dlsym函数获取目标函数地址->使用ptrace_call远程调用被注入模块的函数 -> 调用 dlclose 卸载so文件 -> 恢复寄存器环境 -> 从远程进程detach(进程暂停->ptrace函数调用,其他函数远程调用->进程恢复)

  下面我们通过代码来实现这个流程。首先创建目录及文件:

  jni
      inject.c
      Android.mk
      Application.mk

  在编写代码之前,我们先熟悉一下pt_regs结构体:  

pt_regs结构的定义:
struct pt_regs{
long uregs[];
};
#define ARM_cpsr uregs[16] 存储状态寄存器的值
#define ARM_pc uregs[15] 存储当前的执行地址
#define ARM_lr uregs[14] 存储返回地址
#define ARM_sp uregs[13] 存储当前的栈顶地址
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_10 uregs[10]
#define ARM_9 uregs[9]
#define ARM_8 uregs[8]
#define ARM_7 uregs[7]
#define ARM_6 uregs[6]
#define ARM_5 uregs[5]
#define ARM_4 uregs[4]
#define ARM_3 uregs[3]
#define ARM_2 uregs[2]
#define ARM_1 uregs[1]
#define ARM_0 uregs[0] 存储R0寄存器的值,函数调用后的返回值会存储在R0寄存器中

在通过ptrace改变远程进程的执行流程之前,需要先读取和保存远程进程的所有寄存器的值,在ARM处理器下,ptrace函数中data参数的regs为pt_regs结构的指针,从远程进程获取的寄存器值将存储到该结构中。在远程进程执行detach操作之前,需要将远程进程的原寄存器的环境恢复,保证远程进程原有的执行流程不被破坏。如果不恢复寄存器的值,则执行detach操作之后会导致远程进程崩溃。

inject.c的代码如下:  

 #include <stdio.h>
#include <stdlib.h>
#include <sys/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <android/log.h> #if defined(__i386__)
#define pt_regs user_regs_struct
#endif #define LOG_TAG "INJECT"
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define CPSR_T_MASK (1u << 5) const char* libc_path = "/system/lib/libc.so";
const char* linker_path = "/system/bin/linker"; /*--------------------------------------------------
* 功能: 向目标进程指定的地址中读取数据
*
* 参数:
* pid 需要注入的进程pid
* src 需要读取的目标进程地址
* buf 需要读取的数据缓冲区
* size 需要读取的数据长度
*
* 返回值: -1
*--------------------------------------------------*/
int ptrace_readdata(pid_t pid, uint8_t *src, uint8_t *buf, size_t size){
uint32_t i, j, remain;
uint8_t *laddr; union u{
long val;
char chars[sizeof(long)];
}d; j = size/;
remain = size%;
laddr = buf; for(i = ; i<j; i++){
//从内存地址src中读取四个字节
d.val = ptrace(PTRACE_PEEKTEXT, pid, src, );
memcpy(laddr, d.chars, );
src += ;
laddr += ;
} if(remain > ){
d.val = ptrace(PTRACE_PEEKTEXT, pid, src, );
memcpy(laddr, d.chars, remain);
}
return ;
} /*--------------------------------------------------
* 功能: 向目标进程指定的地址中写入数据
*
* 参数:
* pid 需要注入的进程pid
* dest 需要写入的目标进程地址
* data 需要写入的数据缓冲区
* size 需要写入的数据长度
*
* 返回值: -1
*--------------------------------------------------*/
int ptrace_writedata(pid_t pid, uint8_t *dest, uint8_t *data, size_t size){
uint32_t i, j, remain;
uint8_t *laddr; union u{
long val;
char u_data[sizeof(long)];
}d; j = size/;
remain = size%; laddr = data; //先4字节拷贝
for(i = ; i<j; i++){
memcpy(d.u_data, laddr, );
//往内存地址中写入四个字节,内存地址由dest给出
ptrace(PTRACE_POKETEXT, pid, dest, d.val); dest += ;
laddr += ;
} //最后不足4字节的,单字节拷贝
//为了最大程度的保持原栈的数据,需要先把原程序最后四字节读出来
//然后把多余的数据remain覆盖掉四字节中前面的数据
if(remain > ){
d.val = ptrace(PTRACE_PEEKTEXT, pid, dest, ); //从内存地址中读取四个字节,内存地址由dest给出
for(i = ; i<remain; i++){
d.u_data[i] = *laddr++;
}
ptrace(PTRACE_POKETEXT, pid, dest, d.val);
}
return ;
} /*--------------------------------------------------
* 功能: 获取指定进程的寄存器信息
*
* 返回值: 失败返回-1
*--------------------------------------------------*/
int ptrace_getregs(pid_t pid, struct pt_regs *regs){
if(ptrace(PTRACE_GETREGS, pid, NULL, regs) < ){
perror("ptrace_getregs: Can not get register values.");
return -;
}
return ;
} /*--------------------------------------------------
* 功能: 修改目标进程寄存器的值
*
* 参数:
* pid 需要注入的进程pid
* pt_regs 需要修改的新寄存器信息
*
* 返回值: -1
*--------------------------------------------------*/
int ptrace_setregs(pid_t pid, struct pt_regs *regs){
if(ptrace(PTRACE_SETREGS, pid, NULL, regs) < ){
perror("ptrace_setregs:Can not set regsiter values.");
return -;
}
return ;
} /*--------------------------------------------------
* 功能: 恢复程序运行
*
* 参数:
* pid 需要注入的进程pid
*
* 返回值: -1
*--------------------------------------------------*/
int ptrace_continue(pid_t pid){
if(ptrace(PTRACE_CONT, pid, NULL, ) < ){
perror("ptrace_cont");
return -;
}
return ;
} /*--------------------------------------------------
* 功能: 附加进程
*
* 返回值: 失败返回-1
*--------------------------------------------------*/
int ptrace_attach(pid_t pid){
if(ptrace(PTRACE_ATTACH, pid, NULL, ) < ){
perror("ptrace_attach");
return -;
}
return ;
} // 释放对目标进程的附加调试
int ptrace_detach(pid_t pid)
{
if (ptrace(PTRACE_DETACH, pid, NULL, ) < ) {
perror("ptrace_detach");
return -;
} return ;
}
/*--------------------------------------------------
* 功能: 获取进程中指定模块的首地址
* 原理: 通过遍历/proc/pid/maps文件,来找到目的module_name的内存映射起始地址。
* 由于内存地址的表达方式是startAddrxxxxxxx-endAddrxxxxxxx的,所以通过使用strtok(line,"-")来分割字符串获取地址
* 如果pid = -1,表示获取本地进程的某个模块的地址,否则就是pid进程的某个模块的地址
* 参数:
* pid 需要注入的进程pid, 如果为0则获取自身进程
* module_name 需要获取模块路径
*
* 返回值: 失败返回NULL, 成功返回addr
*--------------------------------------------------*/
void *get_module_base(pid_t pid, const char* module_name)
{
FILE* fp;
long addr = ;
char* pch;
char filename[];
char line[]; if(pid < ){
snprintf(filename, sizeof(filename), "/proc/self/maps");
}else{
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
} fp = fopen(filename, "r"); if(fp != NULL){
while(fgets(line, sizeof(line), fp)){
if(strstr(line, module_name)){
pch = strtok(line, "-");
//将参数pch字符串根据参数base(表示进制)来转换成无符号的长整型数
addr = strtoul(pch, NULL, );
if(addr == 0x8000)
addr = ;
break;
}
}
fclose(fp);
}
return (void*)addr;
} /*--------------------------------------------------
* 功能: 获取目标进程中函数指针
*
* 参数:
* target_pid 需要注入的进程pid
* module_name 需要获取的函数所在的lib库路径
* local_addr 需要获取的函数所在当前进程内存中的地址
*
* 目标进程中函数指针 = 目标进程模块基址 - 自身进程模块基址 + 内存中的地址
*
* 返回值: 失败返回NULL, 成功返回ret_addr
*--------------------------------------------------*/
void* get_remote_addr(pid_t target_pid, const char* module_name, void* local_addr){
void* local_handle, *remote_handle;
//获取本地某个模块的起始地址
local_handle = get_module_base(-, module_name);
//获取远程pid的某个模块的起始地址
remote_handle = get_module_base(target_pid, module_name); LOGD("[+]get remote address: local[%x], remote[%x]\n", local_handle, remote_handle); //local_addr - local_handle的值为指定函数(如mmap)在该模块中的偏移量,然后再加上remote_handle,结果就为指定函数在目标进程的虚拟地址
void* ret_addr = (void*)((uint32_t)local_addr - (uint32_t)local_handle) + (uint32_t)remote_handle;
return ret_addr;
} /*--------------------------------------------------
* 功能: 通过进程的名称获取对应的进程pid
* 原理: 通过遍历/proc目录下的所有子目录,获取这些子目录的目录名(一般就是进程的进程号pid)。
* 获取子目录名后,就组合成/proc/pid/cmdline文件名,然后依次打开这些文件,cmdline文件
* 里面存放的就是进程名,通过这样就可以获取进程的pid了
* 返回值: 未找到返回-1
*--------------------------------------------------*/
int find_pid_of(const char* process_name){
int id;
pid_t pid = -;
DIR* dir;
FILE* fp;
char filename[];
char cmdline[]; struct dirent* entry; if(process_name == NULL){
return -;
} dir = opendir("/proc");
if(dir == NULL){
return -;
} while((entry = readdir(dir)) != NULL){
id = atoi(entry->d_name);
if(id != ){
sprintf(filename, "/proc/%d/cmdline", id);
fp = fopen(filename, "r");
if(fp){
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);// 释放对目标进程的附加调试 if(strcmp(process_name, cmdline) == ){
pid = id;
break;
}
}
}
}
closedir(dir);
return pid;
} long ptrace_retval(struct pt_regs* regs){
return regs->ARM_r0;
} long ptrace_ip(struct pt_regs* regs){
return regs->ARM_pc;
} /*--------------------------------------------------
* 功能: 调用远程函数指针
* 原理: 1,将要执行的指令写入寄存器中,指令长度大于4个long的话,需要将剩余的指令通过ptrace_writedata函数写入栈中;
* 2,使用ptrace_continue函数运行目的进程,直到目的进程返回状态值0xb7f(对该值的分析见后面红字);
* 3,函数执行完之后,目标进程挂起,使用ptrace_getregs函数获取当前的所有寄存器值,方便后面使用ptrace_retval函数获取函数的返回值。
* 参数:
* pid 需要注入的进程pid
* addr 调用的函数指针地址
* params 调用的参数
* num_params 调用的参数个数
* regs 远程进程寄存器信息(ARM前4个参数由r0 ~ r3传递)
*
* 返回值: 失败返回-1
*--------------------------------------------------*/
int ptrace_call(pid_t pid, uint32_t addr, long* params, uint32_t num_params, struct pt_regs* regs){
uint32_t i;
for(i = ; i<num_params && i < ; i++){
regs->uregs[i] = params[i];
} if(i < num_params){
regs->ARM_sp -= (num_params - i) * sizeof(long);
ptrace_writedata(pid, (void*)regs->ARM_sp, (uint8_t*)&params[i], (num_params - i)*sizeof(long));
}
//将PC寄存器值设为目标函数的地址
regs->ARM_pc = addr;
////指令集判断
if(regs->ARM_pc & ){
/* thumb */
regs->ARM_pc &= (~1u);
regs->ARM_cpsr |= CPSR_T_MASK;
}else{
/* arm */
regs->ARM_cpsr &= ~CPSR_T_MASK;
}
///设置子程序的返回地址为空,以便函数执行完后,返回到null地址,产生SIGSEGV错误
regs->ARM_lr = ; //将修改后的regs写入寄存器中,然后调用ptrace_continue来执行我们指定的代码
if(ptrace_setregs(pid, regs) == - || ptrace_continue(pid) == -){
printf("error.\n");
return -;
} int stat = ;
/* WUNTRACED告诉waitpid,如果子进程进入暂停状态,那么就立即返回。如果是被ptrace的子进程,那么即使不提供WUNTRACED参数,也会在子进程进入暂停状态的时候立即返回。
对于使用ptrace_cont运行的子进程,它会在3种情况下进入暂停状态:①下一次系统调用;②子进程退出;③子进程的执行发生错误。这里的0xb7f就表示子进程进入了暂停状态,
且发送的错误信号为11(SIGSEGV),它表示试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。那么什么时候会发生这种错误呢?显然,当子进程执行完注入的
函数后,由于我们在前面设置了regs->ARM_lr = 0,它就会返回到0地址处继续执行,这样就会产生SIGSEGV了!
*/
waitpid(pid, &stat, WUNTRACED);
/*stat的值:高2字节用于表示导致子进程的退出或暂停状态信号值,低2字节表示子进程是退出(0x0)还是暂停(0x7f)状态。
0xb7f就表示子进程为暂停状态,导致它暂停的信号量为11即sigsegv错误。*/
while(stat != 0xb7f){
if(ptrace_continue(pid) == -){
printf("error.\n");
return -;
}
waitpid(pid, &stat, WUNTRACED);
}
return ;
} /*--------------------------------------------------
* 功能: 调用远程函数指针
*
* 参数:
* pid 需要注入的进程pid
* func_name 调用的函数名称, 此参数仅作Debug输出用
* func_addr 调用的函数指针地址
* param 调用的参数
* param_num 调用的参数个数
* regs 远程进程寄存器信息(ARM前4个参数由r0 ~ r3传递)
*
* 返回值: 失败返回-1
*--------------------------------------------------*/
int ptrace_call_wrapper(pid_t target_pid, const char* func_name, void* func_addr, long* param, int param_num, struct pt_regs* regs){
LOGD("[+]Calling %s in target process.\n", func_name);
if(ptrace_call(target_pid, (uint32_t)func_addr, param, param_num, regs) == -)
return -;
if(ptrace_getregs(target_pid, regs) == -){
return -;
}
LOGD("[+] Target process returned from %s, return value = %x, pc = %x \n", func_name, ptrace_retval(regs), ptrace_ip(regs));
return ;
} /*--------------------------------------------------
* 功能: 远程注入
*
* 参数:
* target_pid 需要注入的进程Pid
* library_path 需要注入的.so路径
* function_name .so中导出的函数名
* param 函数的参数
* param_size 参数大小,以字节为单位
*
* 返回值: 注入失败返回-1
*--------------------------------------------------*/
int inject_remote_process(pid_t target_pid, const char* library_path, const char* function_name, const char* param, size_t param_size){
int ret = -;
void* mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;
void *local_handle, *remote_handle, *dlhandle;
uint8_t *map_base = ;
uint8_t *dlopen_param1_ptr, *dlsym_param2_ptr, *saved_r0_pc_ptr, *inject_param_ptr, *remote_code_ptr, *local_code_ptr; struct pt_regs regs, original_regs;
extern uint32_t _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s, _dlsym_param2_s, _dlclose_addr_s, _inject_start_s, _inject_end_s, _inject_function_param_s, _saved_cpsr_s, _saved_r0_pc_s; uint32_t code_length;
long parameters[]; LOGD("[+] Injecting process: %d\n", target_pid); //①ATTATCH,指定目标进程,开始调试
if(ptrace_attach(target_pid) == -){
goto exit;
} //②GETREGS,获取目标进程的寄存器,保存现场
if(ptrace_getregs(target_pid, &regs) == -)
goto exit; //保存原始寄存器
memcpy(&original_regs, &regs, sizeof(regs)); //③通过get_remote_addr函数获取目标进程的mmap函数的地址,以便为libxxx.so分配内存
//由于mmap函数在libc.so库中,为了将libxxx.so加载到目标进程中,就需要使用目标进程的mmap函数,所以需要查找到libc.so库在目标进程的起始地址。
mmap_addr = get_remote_addr(target_pid, libc_path, (void*)mmap); //libc_path = "/system/lib/libc.so"
LOGD("[+] Remote mmap address: %x\n", mmap_addr); parameters[] = ;  // 设置为NULL表示让系统自动选择分配内存的地址
parameters[] = 0x4000;  // 映射内存的大小
parameters[] = PROT_READ | PROT_WRITE |PROT_EXEC;  // 表示映射内存区域可读可写可执行
parameters[] = MAP_ANONYMOUS | MAP_PRIVATE;  // 建立匿名映射
parameters[] = ;  //若需要映射文件到内存中,则为文件的fd
parameters[] = ;  //文件映射偏移量 //④通过ptrace_call_wrapper调用mmap函数,在目标进程中为libxxx.so分配内存
if(ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, , &regs) == -)
goto exit;
//⑤从寄存器中获取mmap函数的返回值,即申请的内存首地址:
map_base = ptrace_retval(&regs); //⑥依次获取linker中dlopen、dlsym、dlclose、dlerror函数的地址
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);
dlerror_addr = get_remote_addr(target_pid, linker_path, (void*)dlerror); LOGD("[+] Get imports: dlopen: %x, dlsym: %x, dlclose: %x, dlerror: %x\n", dlopen_addr, dlsym_addr, dlclose_addr, dlerror_addr); printf("library path = %s\n", library_path);
//⑦调用dlopen函数
//(1)将要注入的so名写入前面mmap出来的内存
ptrace_writedata(target_pid, map_base, library_path, strlen(library_path) + ); parameters[] = map_base;
parameters[] = RTLD_NOW | RTLD_GLOBAL; //(2)执行dlopen
if(ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, , &regs) == -){
goto exit;
}
//(3)取得dlopen的返回值,存放在sohandle变量中
void* sohandle = ptrace_retval(&regs); //⑧调用dlsym函数
//为functionname另找一块区域
#define FUNCTION_NAME_ADDR_OFFSET 0X100
ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + );
parameters[] = sohandle;
parameters[] = map_base + FUNCTION_NAME_ADDR_OFFSET; //调用dlsym
if(ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, , &regs) == -)
goto exit;
void* hook_entry_addr = ptrace_retval(&regs);
LOGD("hooke_entry_addr = %p\n", hook_entry_addr); //⑨调用被注入函数hook_entry
#define FUNCTION_PARAM_ADDR_OFFSET 0X200
ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, parameters, strlen(parameters) + );
parameters[] = map_base + FUNCTION_PARAM_ADDR_OFFSET; if(ptrace_call_wrapper(target_pid, "hook_entry", hook_entry_addr, parameters, , &regs) == -)
goto exit;
//⑩调用dlclose关闭lib
printf("Press enter to dlclose and detach.\n");
getchar();
parameters[] = sohandle; if(ptrace_call_wrapper(target_pid, "dlclose", dlclose, parameters, , &regs) == -)
goto exit; //⑪恢复现场并退出ptrace
ptrace_setregs(target_pid, &original_regs);
ptrace_detach(target_pid);
ret = ; exit:
return ret;
} int main(int argc, char** argv) {
pid_t target_pid;
target_pid = find_pid_of("com.bbk.appstore");
if (- == target_pid) {
printf("Can't find the process\n");
return -;
}
//target_pid = find_pid_of("/data/test");
inject_remote_process(target_pid, "/data/local/tmp/libentry.so", "hook_entry", "Fuck you!", strlen("Fuck you!"));
return ;
}

上述代码中,我们要hook的进程名为"com.bbk.appstore",我们要将“libentry.so”注入到该进程中去。

Android.mk内容为:

LOCAL_PATH := $(call my-dir)  

include $(CLEAR_VARS)
LOCAL_MODULE := inject
LOCAL_SRC_FILES := inject.c #shellcode.s LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog #LOCAL_FORCE_STATIC_EXECUTABLE := true include $(BUILD_EXECUTABLE)

Application.mk内容为:

# 编译生成的模块文件运行支持的平台
APP_ABI := armeabi-v7a
# 编译生成模块运行支持的Andorid版本
APP_PLATFORM := android-

在jni目录下运行nkd-build编译成生arm平台下的可执行文件:

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk

再来生成要注入的so,创建目录及文件:

jni
    entry.c
    Android.mk
    Application.mk

entry.c的代码为:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <elf.h>
#include <fcntl.h> #define LOG_TAG "DEBUG"
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args) int hook_entry(char * a){
LOGD("Hook success, pid = %d\n", getpid());
LOGD("Hello %s\n", a);
return ;
}

Android.mk文件:

LOCAL_PATH := $(call my-dir)  

include $(CLEAR_VARS)  

LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
#LOCAL_ARM_MODE := arm
LOCAL_MODULE := entry
LOCAL_SRC_FILES := entry.c
include $(BUILD_SHARED_LIBRARY)

Application.mk文件内容跟上面一样。同样将entry.c进行编译。然后将得到inject和libentry.so push到/data/local/tmp目录下,执行:

通过“/proc/pid/maps”查看被注入进程("com.bbk.appstore")的mmap,可以看到我们的so已经被加载了:

通过“adb logcat -s INJECT”命令打印出log:

这就说明我们的注入成功了。

参考资料:

https://blog.csdn.net/qq1084283172/article/details/53942648
https://melonwxd.github.io/2017/12/01/inject-3-hook/
https://www.cnblogs.com/wanyuanchun/p/4020756.html

Android so注入(inject)和Hook技术学习(一)的更多相关文章

  1. Android so注入(inject)和Hook技术学习(三)——Got表hook之导出表hook

    前文介绍了导入表hook,现在来说下导出表的hook.导出表的hook的流程如下.1.获取动态库基值 void* get_module_base(pid_t pid, const char* modu ...

  2. Android so注入(inject)和Hook技术学习(二)——Got表hook之导入表hook

    全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数. GOT表其实包含了 ...

  3. Android so注入( inject)和Hook(挂钩)的实现思路讨论

    本文博客:http://blog.csdn.net/qq1084283172/article/details/54095995 前面的博客中分析一些Android的so注入和Hook目标函数的代码,它 ...

  4. Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...

  5. Android Native Hook技术(一)

    原理分析 ADBI是一个著名的安卓平台hook框架,基于 动态库注入 与 inline hook 技术实现.该框架主要由2个模块构成:1)hijack负责将so注入到目标进程空间,2)libbase是 ...

  6. shellcode 注入执行技术学习

    shellcode 注入执行技术学习 注入执行方式 CreateThread CreateRemoteThread QueueUserAPC CreateThread是一种用于执行Shellcode的 ...

  7. Android Native Hook技术(二)

    Hook技术应用 已经介绍了安卓 Native hook 原理,这里介绍 hook 技术的应用,及 Cyida Substrate 框架. 分析某APP,发现其POST请求数据经过加密,我们希望还原其 ...

  8. Hook技术

    hook钩子: 使用技术手段在运行时动态的将额外代码依附现进程,从而实现替换现有处理逻辑或插入额外功能的目的. 它的技术实现要点有两个: 1)如何注入代码(如何将额外代码依附于现有代码中). 2)如何 ...

  9. API HOOK技术

    API HOOK技术是一种用于改变API执行结果的技术,Microsoft 自身也在Windows操作系统里面使用了这个技术,如Windows兼容模式等. API HOOK 技术并不是计算机病毒专有技 ...

随机推荐

  1. neutron 多租户隔离的实现以及子网间路由的实现

    1.一个network相当于一个二层网络,使用vxlan 隧道连通所有的CNA节点. 2.一个VPC下有多个network,也就是会分配多个vxlan隧道,这些子网间的路由是通过DVR实现的.DVR就 ...

  2. MyEclipse TestNG插件安装与配置

    MyEclipse TestNG插件安装与配置   by:授客 QQ:1033553122 测试环境 jdk1.8.0_121 myeclipse-10.0-offline-installer-win ...

  3. Android为TV端助力 deep link(深度链接)与自定义协议!

    此自定义仅供参考! 首先打开androidManifest.xml 在MainActivity中添加如下内容: <activity android:name=".MainActivit ...

  4. Visualization of Detail Point Set by Local Algebraic Sphere Fitting

    Refers to Dynamic Sampling and Rendering of Algebraic Point Set Surfaces Growing Least Squares for t ...

  5. 直接通过Binder的onTransact完成跨进程通信

    1.具体代码: 服务端实现: public class IPCService extends Service { private static final String DESCRIPTOR = &q ...

  6. 第一篇-Html标签中head标签,body标签中input系列,textarea和select标签

    第十四周课程(1-12章节) HTML 裸体 CSS   穿华丽衣服 Javascript 动起来 一 HTML (20个标签) 1.我们的浏览器是socket客户端 2.一套规则,浏览器认识的规则 ...

  7. ERP承接新后台优惠规则问题

    一.后台在哪配置优惠规则? 1.设置优惠时间段: 2.添加优惠活动: 关于自动和手动: 自动:创建后,ERP同步数据后即生效.     点餐,活动会自动生效,自动计算金额. 手动:创建后,ERP需要手 ...

  8. 如何下载 Google Play 应用的apk

    Google Play 不能直接下载apk安装包,解决办法,安装插件下载 第一步 FQ就不说了 第二步 安装google浏览器 APK Downloader插件 第三步 打开Google play网站 ...

  9. spyder 快捷键

    本文主要介绍了spyder的快捷键. 常用快捷键   快捷键 中文名称 Ctrl+R 替换文本 Ctrl+1 单行注释,单次注释,双次取消注释 Ctrl+4 块注释,单次注释,双次取消注释 F5 运行 ...

  10. python数据类型分类以及运算类型

    一.python数据类型 目录: 1.数字(整数.小数) 2.字符串(单引号.双引号.三引号) 3.元组 #元素确定之后不能修改 4.列表 #元素可以修改 5.集合  #不讲顺序,得到的结果没有重复元 ...