以前对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. (后端)mybatis中使用Java8的日期LocalDate、LocalDateTime

    原文地址:https://blog.csdn.net/weixin_38553453/article/details/75050632 MyBatis的型处理器是属性“createdtime参数映射为 ...

  2. JAVA基础库的使用点滴

    Idea中双击SHIFT可以搜索[Eclipse中也有],这个很重要,可以找到当前的项目中可以引擎的已有的类,不要再次自己发明轮子 各种Util先在基础库和开源库中找 Base64编码 guava:c ...

  3. bootstrap-paginator分页示例 源码 MVC

    准备 1.数据:bootstrap包(含分页插件bootstrap-paginator.js) 2.技术方案:ajax动态加载分页.部分视图.BLL取数 代码 模板页 @{ Layout = null ...

  4. C#-封装(七)

    封装概念 C#是面向对象的一门语言,面向对象的语言有三大特性:封装.继承.多态.而封装可以实现一个自定义的类,从而定义新的对象 封装是将一个或多个项目集合在一个单元中,这个单元称之为类.这样可以防止对 ...

  5. javascript中(function($){...})(jQuery)写法是什么意思

    这里实际上是匿名函数function(arg){...}这就定义了一个匿名函数,参数为arg 而调用函数 时,是在函数后面写上括号和实参的,由于操作符的优先级,函数本身也需要用括号,即:(functi ...

  6. WebAPI接口设计:SwaggerUI文档 / 统一响应格式 / 统一异常处理 / 统一权限验证

    为什么还要写这类文章?因为我看过网上很多讲解的都不够全面,而本文结合实际工作讲解了swaggerui文档,统一响应格式,异常处理,权限验证等常用模块,并提供一套完善的案例源代码,在实际工作中可直接参考 ...

  7. web前端(11)—— 页面布局1

    要说页面布局的话,那就必须说说margin,padding,和background.这三个属性其实都是前面讲过的,这里还是再次讲解以下,为什么呢?因为是这样的,光靠前面的css样式来设置,你很可能会遇 ...

  8. Gson解析泛型

    1.简单对象我们传入对象Class来将JSON字符串转为对象 private static <T> T fromJson(String result, Class<T> cla ...

  9. Win10改AHCI无需重装系统(无需改注册表)的方法

    下面就开始:1.开机后按下WIN键 加 R键2.输入 msconfig3.如图中所示进行点击.1 引导界面 2安全引导打钩 .最小打钩 3 下面的确定.4.点击重新启动5.在重启时连续按 F2 进入B ...

  10. 关于激活Windows10专业版2018长期服务版

    之前重装了一次系统,偷懒用了小白一键重装,装好之后显示的是Windows10专业版2018长期服务版,当时也没想太多就放着用了. 然后 ,这几天一直提示  “你的windows许可证即将过期” ,就按 ...