android应用加固2

非虫的加固总结:http://www.mottoin.com/89035.html

非root hook:
hook需要更改目标内存空间,应用进程本身当然可以,该进程是属于一个用户的。别的用户想动其它用户的进程就需要权限了,同一个用户动自己的其它进程当然也可以。因此原理上免root hook可实现。

动态符号表dynsym hook

hook掉关键函数(jni_Onload)从而阻止一般断点。基本思路:

  1. 获取动态库基址(文件头中无,在加载时系统生成的信息中)
  2. 计算加载头表program header table实际地址
  3. 取动态信息表dynameic地址
  4. 取dynsym地址(动态导出导入符号表,有so中定义的各种符号地址)
  5. 遍历符号表,找到地址对应后更改

ps:代码段一般都只会设置为可读可执行的,因此需要使用mprotect改变内存页为可读可写可执行。
GOT是将代码段所使用的其它模块的函数地址、全局变量地址提出,用别的模块中的函数填入,从而间接使用外部函数。
外部引用查的是什么?如果是提前加载好的全局表应该改节无用,应该是用时查找节。因为该指针函数可以被使用说明全局表已经加载过了。但这个全局表没有给jni用?linker、jni的具体查找函数并调用过程
将指向后的地址加跳转也行。

相关结构体:

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
程序表头:
struct Elf32_Phdr {
Elf32_Word p_type; // Type of segment
Elf32_Off p_offset; // File offset where segment is located, in bytes
Elf32_Addr p_vaddr; // Virtual address of beginning of segment
Elf32_Addr p_paddr; // Physical address of beginning of segment (OS-specific)
Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
Elf32_Word p_memsz; // Num. of bytes in mem image of segment (may be zero)
Elf32_Word p_flags; // Segment flags
Elf32_Word p_align; // Segment alignment constraint
};
dynmiac每项:
typedef struct dynamic {
Elf32_Sword d_tag;
union {
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
符号表每项:
typedef struct elf32_sym {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;

通过符号表hook got

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
#include <jni.h>
#include <dlfcn.h>
#include <elf.h>
#include <android/log.h>
#include <sys/mman.h>
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "ror_got_hook", __VA_ARGS__))
#define MEM_PAGE_SIZE 4096
#define MEM_PAGE_MASK (MEM_PAGE_SIZE-1)
#define MEM_PAGE_START(x) ((x) & ~MEM_PAGE_MASK)
#define MEM_PAGE_END(x) MEM_PAGE_START((x) + (MEM_PAGE_SIZE-1))
#define ElfW(type) Elf32_ ## type
JNIEXPORT jint JNICALL ReturnGiveValue(int val)
{
return val;
}
JNIEXPORT jint JNICALL ReturnFakeValue(int val)
{
return val * 100;
}
int __attribute__((constructor)) gothook()
{
//获取动态库基值
void* testlib = dlopen("/data/data/com.test.test/libtest.so",RTLD_LAZY);
int nBase = *(int*)(testlib+0x8C);
LOGD("nBase : %x",nBase);
//计算program header table实际地址
ElfW(Ehdr) *header = (ElfW(Ehdr)*)(nBase);
if (memcmp(header->e_ident, "\177ELF", 4) != 0) {
return 0;
}
int phOffset = header->e_phoff;
int phNumber = header->e_phnum;
int phPhyAddr = phOffset + nBase;
LOGD("phOffset : %x",phOffset);
LOGD("phNumber : %x",phNumber);
LOGD("phPhyAddr : %x",phPhyAddr);
int i = 0;
ElfW(Phdr)* phdr_table = (ElfW(Phdr)*)(nBase + phOffset);
if (phdr_table == 0)
{
LOGD("phdr_table address : 0");
return 0;
}
/*
// Program header for ELF32.
struct Elf32_Phdr {
Elf32_Word p_type; // Type of segment
Elf32_Off p_offset; // File offset where segment is located, in bytes
Elf32_Addr p_vaddr; // Virtual address of beginning of segment
Elf32_Addr p_paddr; // Physical address of beginning of segment (OS-specific)
Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
Elf32_Word p_memsz; // Num. of bytes in mem image of segment (may be zero)
Elf32_Word p_flags; // Segment flags
Elf32_Word p_align; // Segment alignment constraint
};
*/
//遍历program header table,ptype等于2即为dynameic,获取到p_offset
unsigned long dynamicAddr = 0;
unsigned int dynamicSize = 0;
for (int i = 0; i < phNumber; i++)
{
if (phdr_table[i].p_type == PT_DYNAMIC)
{
dynamicAddr = phdr_table[i].p_vaddr + nBase;
dynamicSize = phdr_table[i].p_memsz;
break;
}
}
LOGD("Dynamic Addr : %x",dynamicAddr);
LOGD("Dynamic Size : %x",dynamicSize);
/*
typedef struct dynamic {
Elf32_Sword d_tag;
union {
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
*/
//开始遍历dynamic段结构,d_tag为6即为符号表地址
int systemTableAddr = 0;
ElfW(Dyn)* dynamic_table = (ElfW(Dyn)*)(dynamicAddr);
for(i=0;i < dynamicSize / 8;i ++)
{
int val = dynamic_table[i].d_un.d_val;
if (dynamic_table[i].d_tag == 6)
{
systemTableAddr = val + nBase;
break;
}
}
LOGD("System Table Addr : %x",systemTableAddr);
/*
typedef struct elf32_sym {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
*/
//遍历符号表,查找符号表中标记的ReturnGiveValue函数地址,替换为ReturnFakeValue的地址
int giveValuePtr = 0;
int fakeValuePtr = 0;
int fakeFunc = (int)ReturnFakeValue - nBase;
int giveFunc = (int)ReturnGiveValue - nBase;
i = 0;
LOGD("fakeFunc Addr : %x",fakeFunc);
LOGD("giveFunc Addr : %x",giveFunc);
void* pstart = (void*)MEM_PAGE_START(((ElfW(Addr))systemTableAddr));
mprotect(pstart,MEM_PAGE_SIZE,PROT_READ | PROT_WRITE | PROT_EXEC);
ElfW(Sym)* symTab = (ElfW(Sym)*)(systemTableAddr);
while(1)
{
LOGD("func Addr : %x",symTab[i].st_value);
if(symTab[i].st_value == giveFunc)
{
symTab[i].st_value = fakeFunc;
LOGD("New Give func Addr : %x",symTab[i].st_value);
break;
}
i ++;
}
mprotect(pstart,MEM_PAGE_SIZE,PROT_READ | PROT_EXEC);
return 0;
}

通过重定位表hook got

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
#include <jni.h>
#include <dlfcn.h>
#include <elf.h>
#include <android/log.h>
#include <sys/mman.h>
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "ror_got_hook", __VA_ARGS__))
#define MEM_PAGE_SIZE 4096
#define MEM_PAGE_MASK (MEM_PAGE_SIZE-1)
#define MEM_PAGE_START(x) ((x) & ~MEM_PAGE_MASK)
#define MEM_PAGE_END(x) MEM_PAGE_START((x) + (MEM_PAGE_SIZE-1))
#define ElfW(type) Elf32_ ## type
char str[] = "return give value";
JNIEXPORT void JNICALL ReturnGiveValue()
{
char logStr[255] = {0};
strncpy(logStr,str,strlen(str));
LOGD("%x",(unsigned int)strncpy);
LOGD("show : %s",logStr);
}
int my_strncpy(char* s1, char* s2, int n)
{
memcpy(s1,"return fake value\0",18);
LOGD("%x",(unsigned int)strncpy);
return 1;
}
int __attribute__((constructor)) gothook()
{
//获取动态库基值
void* testlib = dlopen("/data/local/tmp/libtest.so",RTLD_LAZY);
int nBase = *(int*)(testlib+0x8C);
LOGD("nBase : %x",nBase);
//计算program header table实际地址
ElfW(Ehdr) *header = (ElfW(Ehdr)*)(nBase);
if (memcmp(header->e_ident, "\177ELF", 4) != 0) {
return 0;
}
ElfW(Phdr)* phdr_table = (ElfW(Phdr)*)(nBase + header->e_phoff);
if (phdr_table == 0)
{
LOGD("phdr_table address : 0");
return 0;
}
size_t phdr_count = header->e_phnum;
LOGD("phdr_count : %d", phdr_count);
//遍历program header table,ptype等于PT_DYNAMIC即为dynameic,获取到p_offset
unsigned long dynamicAddr = 0;
unsigned int dynamicSize = 0;
for (int i = 0; i < phdr_count; i++)
{
if (phdr_table[i].p_type == PT_DYNAMIC)
{
dynamicAddr = phdr_table[i].p_vaddr + nBase;
dynamicSize = phdr_table[i].p_memsz;
break;
}
}
LOGD("Dynamic Addr : %x",dynamicAddr);
LOGD("Dynamic Size : %x",dynamicSize);
/*
typedef struct dynamic {
Elf32_Sword d_tag;
union {
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
*/
ElfW(Dyn)* dynamic_table = (ElfW(Dyn)*)(dynamicAddr);
unsigned long jmpRelOff = 0;
unsigned long strTabOff = 0;
unsigned long pltRelSz = 0;
unsigned long symTabOff = 0;
int i;
for(i=0;i < dynamicSize / 8;i ++)
{
int val = dynamic_table[i].d_un.d_val;
if (dynamic_table[i].d_tag == DT_JMPREL)
{
jmpRelOff = val;
}
if (dynamic_table[i].d_tag == DT_STRTAB)
{
strTabOff = val;
}
if (dynamic_table[i].d_tag == DT_PLTRELSZ)
{
pltRelSz = val;
}
if (dynamic_table[i].d_tag == DT_SYMTAB)
{
symTabOff = val;
}
}
//重定位表
ElfW(Rel)* rel_table = (ElfW(Rel)*)(jmpRelOff+nBase);
LOGD("jmpRelOff : %x",jmpRelOff);
LOGD("strTabOff : %x",strTabOff);
LOGD("symTabOff : %x",symTabOff);
//遍历查找要hook的导入函数,这里以strncpy做示例
for(i=0;i < pltRelSz / 8;i++)
{
//取重定位表每一项的符号表下标24 8 位分开功能
int number = (rel_table[i].r_info >> 8) & 0xffffff;
//符号表下标找到对应项
ElfW(Sym)* symTableIndex = (ElfW(Sym)*)(number*16 + symTabOff + nBase);
//找到对应名字
char* funcName = (char*)(symTableIndex->st_name + strTabOff + nBase);
// LOGD("Func Name : %s",funcName);
if(memcmp(funcName,"strncpy",7) == 0)
{
void* pstart = (void*)MEM_PAGE_START(((ElfW(Addr))rel_table[i].r_offset + nBase));
mprotect(pstart,MEM_PAGE_SIZE,PROT_READ | PROT_WRITE | PROT_EXEC);
LOGD("r_off : %x",rel_table[i].r_offset + nBase);
LOGD("my_strncpy : %x",my_strncpy);
//要重定位的位置位于got处,修改其地址。
*(unsigned int*)(rel_table[i].r_offset + nBase) = my_strncpy;
}
}
return 0;
}

资源混淆

resources.arsc这个文件是存放在APK包中的,他是由AAPT工具在打包过程中生成的,他本身是一个资源的索引表,里面维护者资源ID、Name、Path或者Value的对应关系。https://www.jianshu.com/p/3cc131db2002
通过resources.arsc这个桥梁,使R.类+名(这个组合名可以锁定资源,给人看的,二进制中只是id)的int值还原到type+名,从而可以用android框架寻找加载资源。
例如value/string.xml添加名-值生成的R.string.名使用该值,编译为id。使用时resources.arsc中通过id找到对应的分类与名,value下的几个分类都被打包在resources.arsc里了,所以string资源可以直接在resources.arsc中通过种类加名找到。还有些xml被加密放在原处(layout),还有些没加密放原处(mipmap):这些按类就是文件夹,名就是文件名路径就能查找到。在Android系统中,Resources类可以根据ID来查找资源,而AssetManager类根据文件名来查找资源
混淆只是将还原时的type+名改变,同时将res中文件名改变。没有做到资源加密。
针对图片加密:当图片资源放在drawable中的时候,能有相应的Id去解析: BitmapFactory.decodeResource(res, id)
如果放置在assets下,就需要根据文件的名字去解析(Android提供AssetManager)。
若使用id解析,其实是框架中的Resources类先根据ID来找到资源文件名称,然后再将该文件名称交给AssetManager类来打开对应的文件的。但是这个过程由框架实现无法更改,所以若自己写应用时加密方式可采用手动从assets中提取自定义加密文件。
若针对任意apk如何加密资源?(所有资源-layout、string、图片特别关注)其实这些都可以手动加载,但是如何在不大量改代码情况下实现自动化。