首页
社区
课程
招聘
[原创]Android so hook三部曲(一):dlopen和dlsym的自实现
2024-3-29 18:39 3231

[原创]Android so hook三部曲(一):dlopen和dlsym的自实现

2024-3-29 18:39
3231

自实现目的

对so hook有刚需的人应该都知道,hook的第一步就是要拿到被hook方法的地址,而想要获取方法地址就避不开dlopen()和dlsym()这两个好基友函数,但Android为了避免伤及友军便在Android 7.0以上版本对这两个函数的使用做了限制,以至于在获取很多未公开库方法时会获取失败,目的也是为了提高安全性和稳定性。出发点是好的,但我们有刚需,我们就得造它。

浅谈思路

dlopen

目的就是将目标so文件模拟加载到内存中,大致分为以下几个步骤:

  1. 打开文件,读取头表段表
  2. 申请内存,映射LOAD段
  3. 定位、解析动态段,获取有用信息
  4. 重定位、初始化

dlSym

dlsym则模拟源码实现方式

  1. 通过hash判断目标字符是否存在
  2. 遍历hash表获取目标位置

具体实现

dlopen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
//初始化函数
typedef void*(*PFN_INIT)();
 
Elf64_Hash g_hash = {0};
char* pStrTable = NULL;
Elf64_Sym* pSymTable = NULL;
 
char* bufNedded[256] = {0};
size_t nNumOfNeede = 0;
 
Elf64_Rela* pRelaDyn = NULL;
size_t nNumOfRela=0;
Elf64_Rela* pRelaPlt = NULL;
size_t nNumOfRelaPlt = 0;
 
PFN_INIT* bufInis = NULL;
size_t nNumOfInis = 0;
 
//重定位函数
__attribute__((noinline))
void Relocate(uint8_t* pBase, Elf64_Rela* pRel,
size_t nNumOfRels,Elf64_Sym* pSym,void*hSos[],
size_t nNumOfSos,
const char* pStr)
{
    //解析重定位表
    for (size_t i = 0; i < nNumOfRels; i++)
    {
        uint32_t nSym = ELF64_R_SYM(pRel[i].r_info);    //索引
        uint32_t nType = ELF64_R_TYPE(pRel[i].r_info);  //类型
 
        //根据符号获取地址
        void* nAddr = NULL;
        if(pSym[nSym].st_value != 0)
        {
            //导出符号,自己模块内部的符号
            nAddr = pBase+pSym[nSym].st_value;
        }
        else{
            //导入符号,其他模块符号
            for(size_t i=0; i<nNumOfSos;i++)
            {
                nAddr = dlsym(hSos[i],pStr+pSym[nSym].st_name);
                if(nAddr!=NULL)
                {
                    break;   //找到了
                }
            }
        }
 
        switch (nType)
        {
        case R_AARCH64_RELATIVE:    //相对
            *(uint64_t*)(pBase+pRel[i].r_offset) = (uint64_t)(pBase+pRel[i].r_addend);
            break;
        case R_AARCH64_GLOB_DAT:    //全局偏移量
            *(uint64_t*)(pBase+pRel[i].r_offset) = (uint64_t)nAddr;
            break;
        case R_AARCH64_JUMP_SLOT:   //跳转槽
            *(uint64_t*)(pBase+pRel[i].r_offset) = (uint64_t)nAddr;
            break;
        default:
            break;
        }
    }
}
 
__attribute__((noinline))
void* MyDlSym(uint8_t* pBase, const char* szName)
{
    //判断要查找的字符是否存在于此so内
    uint32_t hash =  gnu_hash(szName);    //计算hash
    uint32_t h2 = hash>>g_hash.shift2;
 
    uint32_t bloom_mask_bits = 64;
    uint32_t word_num = (hash / bloom_mask_bits)& g_hash.maskswords;
    uint64_t bloom_word = g_hash.gnu_bloom_filter_[word_num];
 
    if( (1&(bloom_word>>(hash%bloom_mask_bits)) & (bloom_word>>(h2%bloom_mask_bits))) == 0 )
    {
        //不在模块内
        return NULL;
    }
 
    uint32_t n = g_hash.gnu_bucket_[hash % g_hash.nbucket];
 
    do
    {
        Elf64_Sym* s = pSymTable+n;
        if( ((g_hash.gnu_chain_[n] ^ hash) >> 1) == 0 &&
            strcmp(pStrTable+s->st_name, szName)==0 )
            {
                //找到了,返回地址
                return pBase+s->st_value;
            }
 
    } while ((g_hash.gnu_chain_[n++]&1) == 0);
}
 
void* MyDlopen(const char* szName)
{
    //读取文件、文件头和段表
    FILE* file = fopen(szName,"rb");
    if(file == NULL)
    {
        printf("open file error!\n");
        return 0;
    }
 
    Elf64_Ehdr hdr = {0};               //elf hander
    if(fread(&hdr, 1, sizeof(hdr), file) != sizeof(hdr))
    {
        printf("read Ehdr error!\n");
        return 0;
    }
 
    size_t nSizeOfPhdrs = hdr.e_phentsize*hdr.e_phnum;   //段表大小
    Elf64_Phdr* phdrs = (Elf64_Phdr*)malloc(nSizeOfPhdrs);
    if( fread(phdrs, 1, nSizeOfPhdrs,file) != nSizeOfPhdrs)
    {
        printf("read phdr error!\n");
        return 0;
    }
     
    //fclose(file);
 
    //申请内存、映射 
    size_t nLoadSize = 0;
    for(int i=hdr.e_phnum-1;i>=0;i--)
    {
        if(phdrs[i].p_type == PT_LOAD)
        {
            nLoadSize = ((phdrs[i].p_vaddr+phdrs[i].p_memsz+PAGE_SIZE-1)/PAGE_SIZE)*PAGE_SIZE;    //需要申请的内存大小
            break;
        }
    }
 
    uint8_t* pBase = mmap64(NULL, nLoadSize, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);  //申请空间
    if(pBase == MAP_FAILED)
    {
        printf("mmap64 error: %s\n",strerror(errno));
        return 0;
    }
 
 
    for(size_t i=0;i<hdr.e_phnum;i++)
    {
        if(phdrs[i].p_type == PT_LOAD)
        {
            fseek(file, phdrs[i].p_offset,SEEK_SET);
            fread(pBase+phdrs[i].p_vaddr, 1 , phdrs[i].p_filesz,file);    //读取文件到内存
        }
    }
    free(phdrs);
 
    // 定位动态段
    phdrs = (Elf64_Phdr*)(pBase+hdr.e_phoff);
    int nDyncIdx = 0;    //动态段索引
    Elf64_Dyn* pDyns = NULL;
    size_t nNumofDyns = 0;     //动态段元素个数
     
    for(size_t i=0;i<hdr.e_phnum;i++)
    {
        if(phdrs[i].p_type == PT_DYNAMIC)
        {
            nDyncIdx = i;
            pDyns = (Elf64_Dyn*)(pBase+phdrs[i].p_vaddr);
            nNumofDyns = phdrs[i].p_filesz/sizeof(Elf64_Dyn);
            break;
        }
    }
 
    //解析动态段
    while(pDyns->d_tag!= DT_NULL)
    {
        switch(pDyns->d_tag)
        {
        case DT_STRTAB:    //字符串段
            pStrTable = (char*)(pBase+pDyns->d_un.d_ptr);   //模块基址+字符串表偏移
            break;
 
        case DT_SYMTAB:    //符号表
            pSymTable = (Elf64_Sym*)(pBase+pDyns->d_un.d_ptr);
            break;
 
        case DT_NEEDED:    //elf文件依赖项
            bufNedded[nNumOfNeede++] = pDyns->d_un.d_ptr;     //str表还没解析,先拿偏移
            break;
 
        case DT_RELA:      //重定位表
            pRelaDyn = (Elf64_Rela*)(pBase+pDyns->d_un.d_ptr);
            break;
        case DT_RELASZ:
            nNumOfRela = pDyns->d_un.d_val/sizeof(Elf64_Rela);
            break;
        case DT_JMPREL:    //plt表
            pRelaPlt = (Elf64_Rela*)(pBase+pDyns->d_un.d_ptr);
            break;
        case DT_PLTRELSZ:
            nNumOfRelaPlt = pDyns->d_un.d_val/sizeof(Elf64_Rela);
            break;
 
        case DT_GNU_HASH:  //hash
        {
            uint8_t* pHashTable = pBase+pDyns->d_un.d_ptr;
            g_hash.nbucket = ((uint32_t*)pHashTable)[0];
            g_hash.symindex = ((uint32_t*)pHashTable)[1];
            g_hash.maskswords = ((uint32_t*)pHashTable)[2];
            g_hash.shift2 = ((uint32_t*)pHashTable)[3];
            g_hash.gnu_bloom_filter_ = (uint64_t*)(pHashTable+16);
            g_hash.gnu_bucket_ = (uint32_t*)(g_hash.gnu_bloom_filter_+g_hash.maskswords);
            g_hash.gnu_chain_ = g_hash.gnu_bucket_+g_hash.nbucket-g_hash .symindex;
 
            --g_hash.maskswords;
            break;
        }
 
        case DT_INIT_ARRAY:    //初始化
            bufInis = (PFN_INIT*)(pBase+pDyns->d_un.d_ptr);
            break;
        case DT_INIT_ARRAYSZ:
            nNumOfInis = pDyns->d_un.d_val/sizeof(void*);
            break;
 
        default:
            break;
        }
 
        pDyns++;
    }
 
    //加载模块,此so需要依赖的elf,方便查找导入符号
    void** hSos = malloc(sizeof(void*)*nNumOfNeede);
    for(size_t i=0;i<nNumOfNeede;i++)
    {
        bufNedded[i] = (uint64_t)bufNedded[i]+pStrTable;      //字符串表已加载,偏移加字符串表基址为目标字符串地址
        hSos[i] = dlopen(bufNedded[i], RTLD_NOW);
    }
 
 
    //重定位
    Relocate(pBase, pRelaDyn, nNumOfRela,pSymTable,hSos,nNumOfNeede,pStrTable);
    Relocate(pBase, pRelaPlt,nNumOfRelaPlt,pSymTable,hSos,nNumOfNeede,pStrTable);
 
 
    //调用初始化函数
    for(size_t i = 0;i<nNumOfInis;i++)
    {
        bufInis[i]();
    }
 
    return pBase;
}

dlsym

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
typedef struct elf64_hash{
    uint32_t nbucket;
    uint32_t symindex;
    uint32_t maskswords;
    uint32_t shift2;
    uint64_t* gnu_bloom_filter_;
    uint32_t* gnu_bucket_;
    uint32_t* gnu_chain_;
}Elf64_Hash;
 
//hash值计算
__attribute__((noinline))
uint32_t gnu_hash(const char* name_)
{
    uint32_t h=5381;
    const uint8_t* name = (const uint8_t*)(name_);
    while(*name != 0){
        h+=(h<<5)+*name++;
    }
    return h;
}
 
__attribute__((noinline))
void* MyDlSym(uint8_t* pBase, const char* szName)
{
    //判断要查找的字符是否存在于此so内
    uint32_t hash =  gnu_hash(szName);    //计算hash
    uint32_t h2 = hash>>g_hash.shift2;
 
    uint32_t bloom_mask_bits = 64;
    uint32_t word_num = (hash / bloom_mask_bits)& g_hash.maskswords;
    uint64_t bloom_word = g_hash.gnu_bloom_filter_[word_num];
 
    if( (1&(bloom_word>>(hash%bloom_mask_bits)) & (bloom_word>>(h2%bloom_mask_bits))) == 0 )
    {
        //不在模块内
        return NULL;
    }
 
    uint32_t n = g_hash.gnu_bucket_[hash % g_hash.nbucket];   //n为该桶对应的链数组的下标
 
    do
    {
        Elf64_Sym* s = pSymTable+n;
        if( ((g_hash.gnu_chain_[n] ^ hash) >> 1) == 0 &&
            strcmp(pStrTable+s->st_name, szName)==0 )
            {
                //找到了,返回地址
                return pBase+s->st_value;
            }
 
    } while ((g_hash.gnu_chain_[n++]&1) == 0);
}

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-4-8 15:42 被Tom-gogo编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (12)
雪    币: 3797
活跃值: (5570)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huangjw 2024-3-29 21:37
2
0
可以哦,虽然有点基础
雪    币: 361
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Tom-gogo 2024-3-29 22:33
3
0
huangjw 可以哦,虽然有点基础
初学者,而且是第一次发帖,见谅
雪    币: 251
活跃值: (388)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kinglinzi 2024-4-4 09:00
4
0
谢谢,学习了
雪    币: 122
活跃值: (1390)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
koflfy 1 2024-4-6 18:31
5
0
mark,有完整的内存加载模块的源码吗?
雪    币: 19299
活跃值: (28938)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2024-4-7 09:23
6
1
感谢分享
雪    币: 35
活跃值: (999)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dreameriii 2024-4-7 09:59
7
0
 麻烦 标题能不能修正下 dlopen 不是 dlopne,看到时我一直在想open这个单词该怎么拼
雪    币: 361
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Tom-gogo 2024-4-8 15:43
8
0
dreameriii [em_63] 麻烦 标题能不能修正下 dlopen 不是 dlopne,看到时我一直在想open这个单词该怎么拼[em_78]
感谢指正
雪    币: 600
活跃值: (478)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
time.time 2024-4-8 18:20
9
0
期待剩下两部
雪    币: 361
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Tom-gogo 2024-4-8 20:31
10
0
time.time 期待剩下两部[em_86]
最近在找工作,等稳定下来就更新
雪    币: 337
活跃值: (432)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
mb_kbkqyusp 2024-4-13 11:54
11
0
这个代码看着好熟悉,不会是老王的吧
雪    币: 361
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Tom-gogo 2024-4-13 22:41
12
0
mb_kbkqyusp 这个代码看着好熟悉,不会是老王的吧[em_13]

kr?

最后于 2024-4-13 22:52 被Tom-gogo编辑 ,原因:
雪    币: 1431
活跃值: (1116)
能力值: ( LV3,RANK:23 )
在线值:
发帖
回帖
粉丝
Black貓①呺 3天前
13
0

感谢分享,刚好正在学ELF文件格式

最后于 3天前 被Black貓①呺编辑 ,原因:
游客
登录 | 注册 方可回帖
返回