个人曾经ARM64_汇编角度_PLTHOOK的研究
ARM64基础HOOK研究_2024
之前为了实现一个修改器变速器的小功能,结果研究了很多关于ELF的内容,特别是so文件(ARM64的)
还研究了Hook,以及注入进程等操作,以及实现类似IDA那样的断点,汇编转换,以及软硬断点等(实现了CE那种谁写入/访问/读取的检测),这里就不作记录了,只记录一下简单的hook研究经历
我个人是比较喜欢研究底层原理的,所以搞了这些> _ <,之前目标是研究IDA调试App的so的原理,想着实现,边调试边Hook,然后一发不可收拾了…今天整理电脑文件,所以就想发点记录
如今,告别曾经的自己,再也不搞了,太浪费时间了> _ <
2024年研究的HOOK,现在翻出来,做个记录吧(恭送之前的自己)
inlineHOOK
// 原理
// 替换函数指令实现跳转
[小范围跳转]
// 最大跳转范围 前后64MB大小
uint_fast64_t mask = 0x03ffffffu;
//跳转指令 B BL BLR 指令
/*
B 指令:跳转到标签处,不保存返回地址,不设置链接寄存器。
BR 指令:跳转到寄存器中的地址,不保存返回地址,不设置链接寄存器。
BLR 指令:跳转到寄存器中的地址,保存返回地址到链接寄存器(例如 x30),用于函数调用和返回。
*/
// B #pc 一般是无参数 无条件分支指令 [目标:函数内部_标签] (循环,分支...)
0x0: 00 00 00 14 B 0
// BL 一般是有参数 函数调用指令 [目标:导出函数_标签]
0x4: FF FF FF 97 BL 0
// BLR 函数调用和返回指令 [目标:指定寄存器里存的地址],并且可以在调用后,返回调用的位置继续执行
0x8: 00 02 3F D6 BLR x16
// BLR 无条件地跳转 [目标:指定寄存器里存的地址],但是不能回到原来调用位置
0xc: 00 00 1F D6 BR x0
// ============= [HOOK 寻址计算] =============
// 1.B和BL 指令 (A里面调用B函数)
size_t pc_offset = (B地址 - A里当前指令位置地址)/4; (地址间相差多少条指令)
// 计算指令
uint_fast64_t mask = 0x03ffffffu;
long b_inst =0x14000000 | ((pc_offset) & mask);
// 使用
B pc_offset
BL pc_offset
// 2.BR指令,original是要HOOK的函数的指针,symbol是要HOOK的函数
//uint32_t *original = static_cast<uint32_t *>(symbol);
//地址写入对齐校验
//奇数:地址写入内存时需要对齐内存,需要补充一个NOP指令,完成对齐
//偶数:不需要处理,直接写入即可
int32_t count = ((uint64_t)((uint32_t *)original + 2) & 7u) != 0u ? 5 : 4;
//把地址写到相对当前位置偏移处
/**
* LDR X17, #0x8
* [分析]
* LDR X17,#距离当前位置偏移的数值
* 0x4 LDR X17, #0x8
* 从LDR下一行开始数,每一行是一个0x4
* 从LDR下一行开始,第二行写入要跳转的函数的地址
*
*/
original[0] = 0x58000051u; // LDR X17, #0x8 (addr:0x0)
//br x17
original[1] = 0xd61f0220u; // BR X17 20 02 1F D6 (addr:0x4)
//写入要跳转的新函数地址
original[2] = replace; // 这里对应0x8的位置 (addr:0x8)
// ============= [优化处理] =============
int edit_count = 修改指令的个数;
// 刷新代码缓存
__flush_cache(symbol, edit_count* sizeof(uint32_t));
PLT HOOK
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
// 解锁内存
int a = mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
printf("[状态码]:%d\n",a);
// 擦除缓存
__builtin___clear_cache((char *)PAGE_START(addr), (char *)PAGE_END(addr));
处理libc.so里malloc…这种函数
#include <iostream>
__attribute__((constructor))
void my_init() {
printf("[A64 HOOK 框架加载成功]\n");
// 可以在这里执行其他初始化操作
}
__attribute__((destructor()))
void my_cleanup() {
printf("[A64 HOOK 框架加载成功]\n");
// 可以在这里执行其他初始化操作
}
一些测试
我的ElfHOOK传说记录
#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <dlfcn.h>
#include "test.h"
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
static void* (*real_malloc)(size_t) = NULL;
void *my_malloc(size_t size)
{
if (!real_malloc) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
if (!real_malloc) {
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
exit(EXIT_FAILURE);
}
}
printf("%zu bytes memory are allocated by libtest.so\n", size);
return real_malloc(size);
}
void hook()
{
char line[512];
FILE *fp;
uintptr_t base_addr = 0;
uintptr_t addr;
//find base address of libtest.so
if(NULL == (fp = fopen("/proc/self/maps", "r"))) return;
while(fgets(line, sizeof(line), fp))
{
if(NULL != strstr(line, "executable") &&
sscanf(line, "%" PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1)
break;
}
fclose(fp);
if(0 == base_addr) return;
//the absolute address
addr = base_addr + 0x11FA8;
//add write permission
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
//replace the function address
*(void **)addr =(void *)(my_malloc);
//clear instruction cache
// __builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
}
int main()
{
hook();
say_hello();
say_hello();
say_hello();
say_hello();
return 0;
}
参考
https://github.com/iqiyi/xHook/blob/master/docs/overview/android_plt_hook_overview.zh-CN.md
test.h
#ifndef TEST_H
#define TEST_H 1
#ifdef __cplusplus
extern "C" {
#endif
void say_hello();
#ifdef __cplusplus
}
#endif
#endif
tese.c
#include <stdlib.h>
#include <stdio.h>
void say_hello()
{
char *buf = malloc(1024);
if(NULL != buf)
{
snprintf(buf, 1024, "%s", "hello\n");
printf("%s", buf);
}
}
基础HOOK
#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include "test.h"
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
void *my_malloc(size_t size)
{
printf("%zu bytes memory are allocated by libtest.so\n", size);
return malloc(size);
}
void hook()
{
char line[512];
FILE *fp;
uintptr_t base_addr = 0;
uintptr_t addr;
//find base address of libtest.so
if(NULL == (fp = fopen("/proc/self/maps", "r"))) return;
while(fgets(line, sizeof(line), fp))
{
if(NULL != strstr(line, "executable") &&
sscanf(line, "%" PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1)
break;
}
fclose(fp);
if(0 == base_addr) return;
//the absolute address
addr = base_addr + 0x11FB0;
//add write permission
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
//replace the function address
*(void **)addr =(void *)(my_malloc);
//clear instruction cache
__builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
}
int main()
{
hook();
say_hello();
return 0;
}
1.基于LD_LIBRARY_PATH的加载引导HOOK
原理探索
# 这是最基础的【库函数】hook方法
# 编译上面的libtest.so库 和 main
# 然后
adb push ./libtest.so ./main /data/local/tmp
adb shell "chmod +x /data/local/tmp/main"
# 关键是这里的 export LD_LIBRARY_PATH=/data/local/tmp;
# 这里是延时加载的,在main里调用的函数,会率先从/data/local/tmp/libtest.so里找,找不到的才会去libc.so这样的库里找
adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
2.ELF的结构分析
为啥要分析这个,因为基于PLT的HOOK需要这个分析
1.查看基础头信息
# 命令 arm-linux-androideabi-readelf -h 你的.so
# 或者NDK下的 D:\NDK\android-ndk-r19c\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\aarch64-linux-android\bin\readelf -h 你的.so
ELF Header:
Magic: 7f 45 4c 46 | 02 01 01 00 00 00 00 00 00 00 00 00 #魔术变量ELF|架构(02:64bit 01:32bit) ....
Class: ELF64 #类型
Data: 2's complement, little endian
# 二进制补码(规范处理正负数),大小端(HEX阅读顺序)信息
Version: 1 (current) # (ELF版本号)
OS/ABI: UNIX - System V # (架构)
ABI Version: 0 # (架构版本)
Type: DYN (Shared object file) # (共享对象文件,需要其他库支撑运行)
Machine: AArch64 #(AArch64 架构)
Version: 0x1 #(ELF 文件格式的版本号)
Entry point address: 0xb10
#(程序开始执行的入口地址,_start_main的偏移地址,注意并非是C代码里的main函数地址)
Start of program headers: 64 (bytes into file) #程序头表偏移地址0x40,可以说都是这个位置开始的
Start of section headers: 14864 (bytes into file) #节头表偏移地址
Flags: 0x0 # 文件头的标志字段,这里没有设置任何特定的标志,因此为0x0。
Size of this header: 64 (bytes) # ELF 文件头的大小为64 (0x40)字节
Size of program headers: 56 (bytes) # 每个程序头的大小为56字节
Number of program headers: 9 # 包含9个程序头表
Size of section headers: 64 (bytes) # 每个节头的大小为64字节
Number of section headers: 34# 包含34个节头表
Section header string table index: 31 # 指向节头表字符串表的索引为31。节头表字符串表包含节名称的字符串
D:\NDK\android-ndk-r19c\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\aarch64-linux-android\bin>
# Section header string table index 节表名字在第几个节表内存着
# sh_offse + sh_size+sh_num ->读取所有的节表 其实这个就是个动态分配的数组
# Elf64_Shdr *shdrs = (Elf64_Shdr*)malloc(elf_header->e_shnum*sizeof(Elf64_Shdr));
# 取出 节表名字对应的节表 -> Elf64_Shdr shstrtab = shdrs[elf_header->e_shstrndx];
# 读取节表的所有内容 (其实只是个Tab标签) char *shstrtab_data =(char*) malloc(shstrtab.sh_size);
# pread64(fd, shstrtab_data,shstrtab.sh_size,shstrtab.sh_offset);
# 从字节表名字偏移处读取一个,读取到shstrtab_data里面
3.基于PLT的HOOK
原理探索
程序头
节表列表 Elf64_Shdr elf_header->e_shentsize * elf_header->e_shnum
节表名单 Elf64_Shdr shdrs[elf_header->e_shstrndx]
所有[函数]符号表
.symtab 符号列表 [函数...结构体] --- int index = symtab->st_name
.strtab 符号名字列表 [函数所有名字] char* fuc_name = strtab[index]
很多不同的内容,导入函数 addr: 0000000 Type:FUN GLOBAL (可能有@@LIB)
[过滤所有导入符号]
if(sym->st_value ==0 &&(ELF64_ST_TYPE(sym->st_info) == STT_FUNC) && ELF64_ST_BIND(sym->st_info) == STB_GLOBAL)
// 判断函数名字
记录当前序号i (是addr: 0000000 Type:FUN GLOBAL这个类型里的序号!!!!!)
.rela.plt 关联表 [libc.so等导入函数的结构体] ------ Elf64_Rel ----- plt_base_offset = rela_plt_tab[0].r_offset
计算导入函数
target = plt_base_offset + 8*i
HOOK
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
// 计算PLT地址
addr = lib_base+target
// 给予可修改权限
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE)
// 替换
*(void **)addr =(void *)(replace);
// 擦除缓存
__builtin___clear_cache((char *)PAGE_START(addr), (char *)PAGE_END(addr));
Elf64_Ehdr *elf_header = (Elf64_Ehdr *) malloc(sizeof(Elf64_Ehdr));
拿到所有节表
Elf64_Shdr *shdrs = new Elf64_Shdr[elf_header->e_shnum];
节表所有名字保存在 index = elf_header->e_shstrndx 这个节表里
Elf64_Shdr shstrtab = shdrs[elf_header->e_shstrndx];
拿到所有表的名字数组
char *shstrtab_data =(char*) malloc(shstrtab.sh_size);
遍历所有节,获取节真实名字
shstrtab_data + shdrs[i].sh_name
记录 .symtab 序号 ----------- 所有函数详细信息,偏移,类型(名字不在这里)
记录 .strtab 序号 ----------- 所有函数名字
char *dynstr = (char *)malloc(strtab.sh_size);
记录 .rela.plt 序号
从strtab表获取到(系统函数的名字)
并从symtab表记录这个类型的索引++
[sym->st_value] 偏移量:0
[ELF64_ST_TYPE(sym->st_info)] 类型: STT_FUNC
[ELF64_ST_BIND(sym->st_info)] 绑定类型:STB_GLOBAL
记录目标fucname 对应的0&STB_GLOBAL&STT_FUNC的 [目标GLOBAL索引序号]
获取 .plt 表 包含的是Elf64_Rel重定向信息
Elf64_Shdr rela_plt_section = shdrs[rela_plt_index];
拿到第一个 .plt 表元素的偏移量
frist_global_offset = rela_plt_tab[0].r_offset;
根据 [目标GLOBAL索引序号] 计算目标函数偏移
目标函数偏移 = rela_plt_tab[0].r_offset + [目标GLOBAL索引序号]*0x8
--------
为什么这样计算??? 直接获取对应的 [目标GLOBAL索引序号] 取出 rela_plt_tab[index].r_offset不就行了???
不行,因为可以直接遍历出来的rela_plt_tab[index]是有限个的,不一定会真的获取到所需要的函数
也就是 [系统函数] 用了10个,但是 .plt 里看到的,可能只有5个或者更少,所以要计算
--------
获取后
uintptr_t addr = info.lib_base + info.target_offset;
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
---------
#include <inttypes.h>
#define PAGE_SIZE 4096
#define PAGE_MASK (~(PAGE_SIZE - 1))
---------
#define PAGE_START(addr) ((addr) & PAGE_MASK) // 内存对齐
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
*(void **)addr =(void *)(replace); // 替换函数
__builtin___clear_cache((char *)PAGE_START(addr), (char *)PAGE_END(addr)); //擦除原函数的缓存
处理libc.so里malloc…这种函数
汇编HOOK的研究
#include <iostream>
#include <cstdio>
#include <cstring>
#include <inttypes.h>
#include <asm-generic/fcntl.h>
#include <fcntl.h>
#include <linux/elf.h>
#include <unistd.h>
#include <elf.h>
#include <asm-generic/mman.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include "Hook/PltHook.h"
#include "Hook/IHOOK.hpp"
#include "Hook/IASM64.h"
#include "Hook/And64InlineHook.hpp"
/**
* inline HOOK 2种
* 1.当前so内直接调用 ----- 1
* 前后 64 MB
* B #pc
* 2.调用其他so内函数 ----- 6
* STP x8,x0 ,[sp-20]
* LDR X0,8
* BR X0
* [ADDR]
* LDR X8,[sp]
* @return
*/
int inline_A() {
printf("[本so] 函数A\n");
return 666;
}
void inline_B() {
printf("[本so] HOOK 到了 函数B\n");
}
// 跳板子
int (*inline_A_tamp)() = NULL;
static __attribute((aligned(PAGE_SIZE))) uint32_t __insns_pool[256][5 * 10];
// 跳板集合
void log(char* tag,ADDR_TYPE value){
printf("[%s] %llx\n",tag,value);
}
#define __flush_cache(c, n) __builtin___clear_cache(reinterpret_cast<char *>(c), reinterpret_cast<char *>(c) + n)
void HOOK_inline(const void *symbol, void *replace, void **result) {
// 创建一个跳板 池子,每次 HOOK都会 在池子里选择一个格子 256次HOOK 每次HOOK里可以存40条指令
// 最好这样写
static __attribute((aligned(PAGE_SIZE))) uint32_t __insns_pool[256][4 * 10];
// static uint32_t __insns_pool[256][5 * 10];
// HOOK 次数计数器
static __attribute((aligned(PAGE_SIZE))) uint32_t tams_index = 0;
// 计算pc偏移
INST_IMPORT pc_offset = getPC_ADDR(ADDR_TYPE(symbol), ADDR_TYPE(replace));
printf("[pc] %llx\n",pc_offset );
// 如果在+-64MB范围之内,直接使用B跳转
if (llabs(pc_offset) <= (B_PC_MASK >> 1)) {
// 一般是在自己程序调试时使用
printf("hook small\n");
// 回调原函数
if (result != NULL) {
// 开启当前备份 rwxp -> 可读可写可执行 权限
mwrxp(FUN_CALL(__insns_pool[tams_index]), 1);
// 获取原函数第一条指令,用作备份
INST_TYPE raw_frist = getasm32(FUN_CALL(symbol), 0);
INST_IMPORT backup_pc_offset = getPC_ADDR(ADDR_TYPE(__insns_pool[tams_index]), ADDR_TYPE(symbol));
__insns_pool[tams_index][0] = raw_frist; // 第一条是原指令
__insns_pool[tams_index][1] = backup_pc_offset; // B symbol+4 # 调到原函数的第二条指令继续执行
*result = __insns_pool[tams_index]; // 把当前指令配置给回调函数
tams_index++; // 更新池子索引
}
// 开启原函数的 rwxp ->可写权限 权限
mwrxp(FUN_CALL(symbol), 1);
// 替换原函数第一条指令为 B #replace函数
editasm32(FUN_CALL(symbol), 0, pc_offset);
} else {
// 一般是在自己程序---加载,引用库时,启用
printf("hook big\n");
int32_t count = ((uint64_t)((uint32_t *)symbol + 2) & 7u) != 0u ? 5 : 4;
// 开启原函数的 rwxp ->可写权限 权限
mwrxp(FUN_CALL(symbol), 1);
if (count == 5){
uint32_t *original = (uint32_t *)symbol; // symbol要HOOK的函数
editasm32(FUN_CALL(symbol), 1*0x4, a64_ldr_x_num(17,0x8)); // LDR X17, #0x8
editasm32(FUN_CALL(symbol), 2*0x4, a64_br_x(17)); // BR X17
editasm64(FUN_CALL(symbol), 3*0x4, replace); // BR X17
__flush_cache((char*)symbol, 5 * sizeof(uint32_t));
}
// __flush_cache(symbol, 7* sizeof(uint32_t));
}
}
void BB() {
printf("[本so] BB\n");
}
int (*AA)() = NULL;
int (*CC)() = NULL;
int test_inline_hook_local() {
// 总共6条指令
printf("----------[调用原A]-----------\n");
inline_A();
// A64HookFunction(FUN_CALL(inline_A), FUN_CALL(inline_B), (void **) &AA);
// A64HookFunction(FUN_CALL(AA), FUN_CALL(BB), (void **) &CC);
// lookInst(ADDR_TYPE(AA),30);
HOOK_inline(FUN_CALL(inline_A), FUN_CALL(inline_B), (void **) &AA);
HOOK_inline(FUN_CALL(AA), FUN_CALL(BB), (void **) &CC);
printf("----------[A被HOOK后 调用A]-----------\n");
inline_A();
if (AA) {
printf("----------[调用AA]-----------\n");
AA();
}
printf("----------[AA被HOOK后]-----------\n");
AA();
if (CC) {
CC();
printf("[调用CC]\n");
}
return 0;
}
#define 程序入口 main
void (*function_in_file2)() = NULL;
int 程序入口() {
char* path = "/data/local/tmp/libts_arm64-v8a.so";
void *lib_handle = dlopen(path,RTLD_LAZY);
if (!lib_handle) {
fprintf(stderr, "Failed to open library: %s\n", dlerror());
return -1; // 处理打开失败的情况
}
printf("\033[37;3m 基础 HOOK 框架 \033[0m\n");
function_in_file2 = (void (*)()) dlsym(lib_handle,"_Z21Dysm_shared_hook_Testv");
printf(" 【库】0x%llx\n", function_in_file2);
printf("【自己】0x%llx\n", inline_A);
printf("----------[调用原hook_Test]-----------\n");
function_in_file2();
void* symbol = (void*)function_in_file2;
void* replace = (void*)inline_A;
void** results = (void**)AA;
// A64HookFunction(FUN_CALL(symbol), FUN_CALL(replace), FUN_CALL_RAW(&results));
int32_t count = ((uint64_t)((uint32_t *)symbol + 2) & 7u) != 0u ? 5 : 4;
printf("[PC] 0x%llx\n",count);
mwrxp(symbol,1);
uint32_t *original = (uint32_t *)symbol; // symbol要HOOK的函数
HOOK_inline(FUN_CALL(symbol), FUN_CALL(replace), FUN_CALL_RAW(&results));
printf("----------[HOOK后 调用原hook_Test]-----------\n");
function_in_file2();
printf("----------[结束]-----------\n");
return 0;
}
void iii(){
uint64_t obb = 0b11110001101010010001101111111010001100011110100;
size_t s = 30;
for (int i = 0; i < s; ++i){
printf("[%d] %llx\n",i, obb&i);
}
}
结束
可能比较乱,没办法,这几年Hook的个人研究的一部分,本来准备早点发的,但是由于CSDN上传图片总是失败,一张一张上传太麻烦,所以直接不上传图了,其它文件太多,也不想发出来
不要提问,因为好久不整了,也不打算整了,不做回复请见谅 > _ <