在开始之前先说下什么是crc检测,通俗点讲就是,把本地文件中的数据和内存中的数据进行crc计算得到的结果进行比较,来校验结果是否一致,不一致则判定数据被篡改。
举个例子:以libc.so为目标so,当我们第一次用frida以spwan的方式注入hook 时,未对libc.so的函数进行hook的话,app未退出,一旦我们对libc.so中的函数或指令进行了修改注入(不考虑inline hoook的因素影响),app便直接崩溃退出,这种情况基本就是检测到了数据被篡改,也就是crc检测。
基本的介绍到这里,下面开始对crc相关途径的检测进行分析,以及如何去绕过。
本次分析以libc.so为目标,以下的绕过都是用frida去处理。
app是我总结的一部分crc检测,会把链接放置在结尾。 frida版本: 16.5.9 目标app: LinkerDemo 分析so: libc.so ELF工具:010Editor arm平台: arm64
目前crc的检测大的方向分两种: 1.本地文件与所属app的/proc/{id}/maps文件中so的内存范围作比较 2.本地文件与linker中获取到的so的内存范围作比较
先说一下此校验方法的相关逻辑 描述:提取本地文件/apex/com.android.runtime/lib64/bionic/libc.so的可执行段数据和app在/proc/{id}/maps下映射的libc.so可执行段内存进行crc校验
获取可执行段表中的 p_offset(在文件中的偏移)和 p_filesz(在文件中的大小)。后续都是以这个为参照物与内存进行crc校验
(正常来说只有一行r-xp段,因为我使用了frida,所以会出现这种内存布局) 提取出里面带有x的内存段数据。
最后通过相关算法计算出两种途径获取到的内存结果进行比较。 算法一般都是crc32,当然个例可能会使用其他的算法,比如md5,aes等等,很少见的。
打开app,点击libc maps crc,可以在控制台看到如下输出: 此时我们的环境是正常的。接着我们用frida注入,对libc.so中的pthread_create方法进行hook,得到以下输出: 可以很明显的看出内存中可执行段的crc值与文件中可执行的不一致,并且检测出了环境是hook的。
针对上述的检测,我们可以在maps中模拟一段可执行段数据,并把libc.so原本的可执行段名称给抹去,变为匿名内存。最后app获取到的maps内存范围就是我们模拟的一段数据
注入上述代码后再次点击按钮看控制台输出: 可以看到两种方式获取到的值一致了,环境也是安全的了。
这里面主要使用到了mmap在maps中映射一段名称为libc.so的数据,用mremap把原本的可执行段数据给设置为匿名内存 这里就可以看到maps中libc.so的可执行段已设置为匿名内存,并且map中也有我们模拟的可执行段内存。
本地文件获取的方式不再赘述了,直接看如何从Linker中获取内存。
linker作为so加载器,里面存放了所有已经加载的so,并把这些已经加载的so会依次存放进solist变量中,solist存储了所有so的soinfo结构体,它是一个soinfo结构体数组,我们可以从solist中获取到自己想要的so。
那么如何获取到libc.so的结构体呢? 带着这个疑问我们先了解下soinfo的相关结构组成。
这里我已经标注了相关变量在内存中的指针索引。里面描述了so的基址和一些节表和大量的方法。当然这些不是我们这里的关注重点,我们只需要多关注以下的变量:
检测逻辑: lib base mem crc:获取本地文件的可执行段的偏移地址,计算soinfo结构体中的base与可执行段偏移地址的和,得到内存中的可执行段地址,再取内存中可执行段数据和本地文件可执行段数据作比较。 lib func mem crc: 通过dl_iterate_phdr方法获取到linker map,取linker map中的dlpi_addr获取到so的基址,获取本地文件的可执行段的偏移地址,计算基址和偏移的和,通过再取内存中可执行段数据和本地文件可执行段数据作比较。
分别点击libc base mem crc按钮和libc func mem crc按钮,控制台输出如下
针对上述的检测,我们可以把soinfo结构体中的base,和link_map_head指针指向我们在maps中映射的地址,达到绕过检测的目的。
注入上述代码后,再次点击这两个按钮查看控制台输出: 此时环境也正常了 注:对base和load_bias进行修改,会有app崩溃的风险
4.libc section mem crc的绕过 这个我就简单说下检测及绕过思路:
获取到soinfo结构体中的节表地址(这里以strtab_变量作检测),再与本地文件或取到的节表偏移相见得到so的基地址,计算基地址与可执行段的偏移得到可执行段的地址,最后提取内存中可执行段数据和本地文件可执行段数据作比较。
把soinfo结构体中的节表指针指向我们在maps中映射的地址,达到绕过检测的目的。
注入代码后再次点击按钮:
此次分享主要是提供个思路,仅供参考,包括libart.so也可以这样去弄,总之crc的大方向就是上述的两个,其次小方向的检测手段就是有很多细节去相互嵌套了。
function hiddenSoExecSegmentInMaps(so_path) {
/
/
/
apex
/
com.android.runtime
/
lib64
/
bionic
/
libc.so
let mmap_addr
=
Module.findExportByName(
"libc.so"
,
"mmap"
);
let mmapFunc
=
new NativeFunction(mmap_addr,
'pointer'
, [
'pointer'
,
'int'
,
'int'
,
'int'
,
'int'
,
'int'
])
let munmap_addr
=
Module.findExportByName(
"libc.so"
,
"munmap"
);
let munmapFunc
=
new NativeFunction(munmap_addr,
'int'
, [
'pointer'
,
'int'
])
let mremap_addr
=
Module.findExportByName(
"libc.so"
,
"mremap"
);
let mremapFunc
=
new NativeFunction(mremap_addr,
'pointer'
, [
'pointer'
,
'int64'
,
'int64'
,
'int64'
,
'pointer'
])
let open_addr
=
Module.findExportByName(
"libc.so"
,
"open"
);
var openFunc
=
new NativeFunction(open_addr,
'int'
, [
'pointer'
,
'int'
]);
let memset_addr
=
Module.findExportByName(
"libc.so"
,
"memset"
);
var memsetFunc
=
new NativeFunction(memset_addr,
'pointer'
, [
'pointer'
,
'int'
,
'int'
])
let close_addr
=
Module.findExportByName(
"libc.so"
,
"close"
);
var closeFunc
=
new NativeFunction(close_addr,
'int'
, [
'int'
])
const parts
=
so_path.split(
'/'
);
const so_name
=
parts.pop();
let soExecSegmentRangeFromMaps
=
findSoExecSegmentRangeFromMaps(so_name);
let startAddress
=
soExecSegmentRangeFromMaps.base;
let size
=
soExecSegmentRangeFromMaps.size;
if
(startAddress
=
=
=
0
|| size
=
=
=
0
) {
console.log(
"可执行段未找到:"
, startAddress, size)
return
;
}
let soExecSegmentFromFile
=
findSoExecSegmentFromFile(so_path);
/
/
创建匿名内存,临时存储so可执行段内存
let new_addr
=
mmapFunc(ptr(
-
1
), size,
7
,
0x20
|
2
,
-
1
,
0
);
/
/
0x20
:匿名内存标识符(MAP_ANONYMOUS),
2
:私有(MAP_PRIVATE)
console.log(
"创建的可执行段匿名内存起始地址:"
+
new_addr);
/
/
把so可执行段内存复制到创建的匿名内存中去
Memory.copy(new_addr, startAddress, size);
console.log(
"复制完毕"
)
/
/
调整so,使传入的so可执行段内存变成匿名内存
let ret
=
mremapFunc(new_addr, size, size,
1
|
2
, startAddress);
if
(ret
=
=
=
-
1
) {
console.log(
"mremap 调整失败"
)
return
;
}
console.log(
"匿名目标so可执行段完成 ret:"
+
ret)
/
/
打开需要模拟的文件路径,用于后续在maps中生成指定名称的内存区域
let moniter_path
=
so_path;
let moniter_path_addr
=
Memory.allocUtf8String(moniter_path);
var fd
=
openFunc(moniter_path_addr,
0
);
if
(fd
=
=
=
-
1
) {
console.log(
"open "
+
moniter_path
+
" is error"
)
return
-
1
;
}
/
/
在maps中创建传入so路径名称的内存区域
let target_addr
=
mmapFunc(ptr(
-
1
), size,
7
,
2
, fd,
0
);
console.log(
"模拟的可执行段内存起始地址:"
+
target_addr);
closeFunc(fd)
/
/
给创建的so内存区域全部置
0
memsetFunc(target_addr,
0
, size);
/
/
把so文件中获取的可执行段内存复制到创建的so名称的内存区域中
Memory.copy(target_addr, soExecSegmentFromFile.start, soExecSegmentFromFile.size)
Memory.protect(target_addr, size,
"r-x"
);
/
/
卸载映射的匿名内存
/
/
munmapFunc(new_addr, size);
console.log(
"maps中隐藏可执行段完成"
)
}
function hiddenSoExecSegmentInMaps(so_path) {
/
/
/
apex
/
com.android.runtime
/
lib64
/
bionic
/
libc.so
let mmap_addr
=
Module.findExportByName(
"libc.so"
,
"mmap"
);
let mmapFunc
=
new NativeFunction(mmap_addr,
'pointer'
, [
'pointer'
,
'int'
,
'int'
,
'int'
,
'int'
,
'int'
])
let munmap_addr
=
Module.findExportByName(
"libc.so"
,
"munmap"
);
let munmapFunc
=
new NativeFunction(munmap_addr,
'int'
, [
'pointer'
,
'int'
])
let mremap_addr
=
Module.findExportByName(
"libc.so"
,
"mremap"
);
let mremapFunc
=
new NativeFunction(mremap_addr,
'pointer'
, [
'pointer'
,
'int64'
,
'int64'
,
'int64'
,
'pointer'
])
let open_addr
=
Module.findExportByName(
"libc.so"
,
"open"
);
var openFunc
=
new NativeFunction(open_addr,
'int'
, [
'pointer'
,
'int'
]);
let memset_addr
=
Module.findExportByName(
"libc.so"
,
"memset"
);
var memsetFunc
=
new NativeFunction(memset_addr,
'pointer'
, [
'pointer'
,
'int'
,
'int'
])
let close_addr
=
Module.findExportByName(
"libc.so"
,
"close"
);
var closeFunc
=
new NativeFunction(close_addr,
'int'
, [
'int'
])
const parts
=
so_path.split(
'/'
);
const so_name
=
parts.pop();
let soExecSegmentRangeFromMaps
=
findSoExecSegmentRangeFromMaps(so_name);
let startAddress
=
soExecSegmentRangeFromMaps.base;
let size
=
soExecSegmentRangeFromMaps.size;
if
(startAddress
=
=
=
0
|| size
=
=
=
0
) {
console.log(
"可执行段未找到:"
, startAddress, size)
return
;
}
let soExecSegmentFromFile
=
findSoExecSegmentFromFile(so_path);
/
/
创建匿名内存,临时存储so可执行段内存
let new_addr
=
mmapFunc(ptr(
-
1
), size,
7
,
0x20
|
2
,
-
1
,
0
);
/
/
0x20
:匿名内存标识符(MAP_ANONYMOUS),
2
:私有(MAP_PRIVATE)
console.log(
"创建的可执行段匿名内存起始地址:"
+
new_addr);
/
/
把so可执行段内存复制到创建的匿名内存中去
Memory.copy(new_addr, startAddress, size);
console.log(
"复制完毕"
)
/
/
调整so,使传入的so可执行段内存变成匿名内存
let ret
=
mremapFunc(new_addr, size, size,
1
|
2
, startAddress);
if
(ret
=
=
=
-
1
) {
console.log(
"mremap 调整失败"
)
return
;
}
console.log(
"匿名目标so可执行段完成 ret:"
+
ret)
/
/
打开需要模拟的文件路径,用于后续在maps中生成指定名称的内存区域
let moniter_path
=
so_path;
let moniter_path_addr
=
Memory.allocUtf8String(moniter_path);
var fd
=
openFunc(moniter_path_addr,
0
);
if
(fd
=
=
=
-
1
) {
console.log(
"open "
+
moniter_path
+
" is error"
)
return
-
1
;
}
/
/
在maps中创建传入so路径名称的内存区域
let target_addr
=
mmapFunc(ptr(
-
1
), size,
7
,
2
, fd,
0
);
console.log(
"模拟的可执行段内存起始地址:"
+
target_addr);
closeFunc(fd)
/
/
给创建的so内存区域全部置
0
memsetFunc(target_addr,
0
, size);
/
/
把so文件中获取的可执行段内存复制到创建的so名称的内存区域中
Memory.copy(target_addr, soExecSegmentFromFile.start, soExecSegmentFromFile.size)
Memory.protect(target_addr, size,
"r-x"
);
/
/
卸载映射的匿名内存
/
/
munmapFunc(new_addr, size);
console.log(
"maps中隐藏可执行段完成"
)
}
struct soinfo {
private:
char old_name_[SOINFO_NAME_LEN];
public:
const ElfW(Phdr)
*
phdr;
/
/
0
size_t phnum;
/
/
1
ElfW(Addr) unused0;
/
/
DO NOT USE, maintained
for
compatibility.
ElfW(Addr) base;
/
/
2
size_t size;
/
/
3
uint32_t unused1;
/
/
DO NOT USE, maintained
for
compatibility.
ElfW(Dyn)
*
dynamic;
/
/
4
uint32_t unused2;
/
/
DO NOT USE, maintained
for
compatibility
uint32_t unused3;
/
/
DO NOT USE, maintained
for
compatibility
soinfo
*
next
;
/
/
5
private:
uint32_t flags_;
/
/
6
const char
*
strtab_;
/
/
7
.dynstr
ElfW(Sym)
*
symtab_;
/
/
8
.dynsym
size_t nbucket_;
size_t nchain_;
uint32_t
*
bucket_;
/
/
11
uint32_t
*
chain_;
/
/
12
/
/
This
is
only used by mips
and
mips64, but needs to be here
for
/
/
all
32
-
bit architectures to preserve binary compatibility.
ElfW(Addr)
*
*
plt_got_;
/
/
arm64未被使用
ElfW(Rela)
*
plt_rela_;
/
/
.real.plt
13
size_t plt_rela_count_;
ElfW(Rela)
*
rela_;
/
/
15
/
/
real.dyn
size_t rela_count_;
/
/
16
ElfW(Rel)
*
plt_rel_;
size_t plt_rel_count_;
ElfW(Rel)
*
rel_;
size_t rel_count_;
linker_ctor_function_t
*
preinit_array_;
/
/
空
17
size_t preinit_array_count_;
/
/
空
18
linker_ctor_function_t
*
init_array_;
size_t init_array_count_;
linker_dtor_function_t
*
fini_array_;
size_t fini_array_count_;
linker_ctor_function_t init_func_;
/
/
空
23
linker_dtor_function_t fini_func_;
/
/
空
24
public:
/
/
ARM EABI section used
for
stack unwinding.
uint32_t
*
ARM_exidx;
size_t ARM_exidx_count;
private:
uint32_t mips_symtabno_;
uint32_t mips_local_gotno_;
uint32_t mips_gotsym_;
bool
mips_relocate_got(const VersionTracker& version_tracker,
const soinfo_list_t& global_group,
const soinfo_list_t& local_group);
bool
mips_check_and_adjust_fp_modes();
size_t ref_count_;
/
/
25
public:
link_map link_map_head;
/
/
26
27
28
29
30
/
/
struct link_map {
/
/
ElfW(Addr) l_addr;
/
/
char
*
l_name;
/
/
ElfW(Dyn)
*
l_ld;
/
/
struct link_map
*
l_next;
/
/
struct link_map
*
l_prev;
/
/
};
bool
constructors_called;
/
/
When you read a virtual address
from
the ELF
file
, add this
/
/
value to get the corresponding address
in
the process' address space.
ElfW(Addr) load_bias;
/
/
32
bool
has_text_relocations;
bool
has_DT_SYMBOLIC;
public:
soinfo(android_namespace_t
*
ns, const char
*
name, const struct stat
*
file_stat,
off64_t file_offset,
int
rtld_flags);
~soinfo();
void call_constructors();
void call_destructors();
void call_pre_init_constructors();
bool
prelink_image();
bool
link_image(const soinfo_list_t& global_group, const soinfo_list_t& local_group,
const android_dlextinfo
*
extinfo, size_t
*
relro_fd_offset);
bool
protect_relro();
void add_child(soinfo
*
child);
void remove_all_links();
ino_t get_st_ino() const;
dev_t get_st_dev() const;
off64_t get_file_offset() const;
uint32_t get_rtld_flags() const;
uint32_t get_dt_flags_1() const;
void set_dt_flags_1(uint32_t dt_flags_1);
soinfo_list_t& get_children();
const soinfo_list_t& get_children() const;
soinfo_list_t& get_parents();
bool
find_symbol_by_name(SymbolName& symbol_name,
const version_info
*
vi,
const ElfW(Sym)
*
*
symbol) const;
ElfW(Sym)
*
find_symbol_by_address(const void
*
addr);
ElfW(Addr) resolve_symbol_address(const ElfW(Sym)
*
s) const;
const char
*
get_string(ElfW(Word) index) const;
bool
can_unload() const;
bool
is_gnu_hash() const;
bool
inline has_min_version(uint32_t min_version __unused) const {
return
(flags_ & FLAG_NEW_SOINFO) !
=
0
&& version_ >
=
min_version;
return
true;
}
bool
is_linked() const;
bool
is_linker() const;
bool
is_main_executable() const;
void set_linked();
void set_linker_flag();
void set_main_executable();
void set_nodelete();
size_t increment_ref_count();
size_t decrement_ref_count();
size_t get_ref_count() const;
soinfo
*
get_local_group_root() const;
void set_soname(const char
*
soname);
const char
*
get_soname() const;
const char
*
get_realpath() const;
const ElfW(Versym)
*
get_versym(size_t n) const;
ElfW(Addr) get_verneed_ptr() const;
size_t get_verneed_cnt() const;
ElfW(Addr) get_verdef_ptr() const;
size_t get_verdef_cnt() const;
int
get_target_sdk_version() const;
void set_dt_runpath(const char
*
);
const std::vector<std::string>& get_dt_runpath() const;
android_namespace_t
*
get_primary_namespace();
void add_secondary_namespace(android_namespace_t
*
secondary_ns);
android_namespace_list_t& get_secondary_namespaces();
soinfo_tls
*
get_tls() const;
void set_mapped_by_caller(
bool
reserved_map);
bool
is_mapped_by_caller() const;
uintptr_t get_handle() const;
void generate_handle();
void
*
to_handle();
private:
bool
is_image_linked() const;
void set_image_linked();
bool
elf_lookup(SymbolName& symbol_name, const version_info
*
vi, uint32_t
*
symbol_index) const;
ElfW(Sym)
*
elf_addr_lookup(const void
*
addr);
bool
gnu_lookup(SymbolName& symbol_name, const version_info
*
vi, uint32_t
*
symbol_index) const;
ElfW(Sym)
*
gnu_addr_lookup(const void
*
addr);
bool
lookup_version_info(const VersionTracker& version_tracker, ElfW(Word) sym,
const char
*
sym_name, const version_info
*
*
vi);
template<typename ElfRelIteratorT>
bool
relocate(const VersionTracker& version_tracker, ElfRelIteratorT&& rel_iterator,
const soinfo_list_t& global_group, const soinfo_list_t& local_group);
bool
relocate_relr();
void apply_relr_reloc(ElfW(Addr) offset);
private:
/
/
This part of the structure
is
only available
/
/
when FLAG_NEW_SOINFO
is
set
in
this
-
>flags.
uint32_t version_;
/
/
version >
=
0
dev_t st_dev_;
ino_t st_ino_;
/
/
dependency graph
soinfo_list_t children_;
soinfo_list_t parents_;
/
/
version >
=
1
off64_t file_offset_;
uint32_t rtld_flags_;
uint32_t dt_flags_1_;
size_t strtab_size_;
/
/
version >
=
2
size_t gnu_nbucket_;
uint32_t
*
gnu_bucket_;
uint32_t
*
gnu_chain_;
uint32_t gnu_maskwords_;
uint32_t gnu_shift2_;
ElfW(Addr)
*
gnu_bloom_filter_;
soinfo
*
local_group_root_;
uint8_t
*
android_relocs_;
size_t android_relocs_size_;
const char
*
soname_;
std::string realpath_;
const ElfW(Versym)
*
versym_;
ElfW(Addr) verdef_ptr_;
size_t verdef_cnt_;
ElfW(Addr) verneed_ptr_;
size_t verneed_cnt_;
int
target_sdk_version_;
/
/
version >
=
3
std::vector<std::string> dt_runpath_;
android_namespace_t
*
primary_namespace_;
android_namespace_list_t secondary_namespaces_;
uintptr_t handle_;
friend soinfo
*
get_libdl_info(const char
*
linker_path, const soinfo& linker_si);
/
/
version >
=
4
ElfW(Relr)
*
relr_;
size_t relr_count_;
/
/
version >
=
5
std::unique_ptr<soinfo_tls> tls_;
std::vector<TlsDynamicResolverArg> tlsdesc_args_;
}
struct soinfo {
private:
char old_name_[SOINFO_NAME_LEN];
public:
const ElfW(Phdr)
*
phdr;
/
/
0
size_t phnum;
/
/
1
ElfW(Addr) unused0;
/
/
DO NOT USE, maintained
for
compatibility.
ElfW(Addr) base;
/
/
2
size_t size;
/
/
3
uint32_t unused1;
/
/
DO NOT USE, maintained
for
compatibility.
ElfW(Dyn)
*
dynamic;
/
/
4
uint32_t unused2;
/
/
DO NOT USE, maintained
for
compatibility
uint32_t unused3;
/
/
DO NOT USE, maintained
for
compatibility
soinfo
*
next
;
/
/
5
private:
uint32_t flags_;
/
/
6
const char
*
strtab_;
/
/
7
.dynstr
ElfW(Sym)
*
symtab_;
/
/
8
.dynsym
size_t nbucket_;
size_t nchain_;
uint32_t
*
bucket_;
/
/
11
uint32_t
*
chain_;
/
/
12
/
/
This
is
only used by mips
and
mips64, but needs to be here
for
/
/
all
32
-
bit architectures to preserve binary compatibility.
ElfW(Addr)
*
*
plt_got_;
/
/
arm64未被使用
ElfW(Rela)
*
plt_rela_;
/
/
.real.plt
13
size_t plt_rela_count_;
ElfW(Rela)
*
rela_;
/
/
15
/
/
real.dyn
size_t rela_count_;
/
/
16
ElfW(Rel)
*
plt_rel_;
size_t plt_rel_count_;
ElfW(Rel)
*
rel_;
size_t rel_count_;
linker_ctor_function_t
*
preinit_array_;
/
/
空
17
size_t preinit_array_count_;
/
/
空
18
linker_ctor_function_t
*
init_array_;
size_t init_array_count_;
linker_dtor_function_t
*
fini_array_;
size_t fini_array_count_;
linker_ctor_function_t init_func_;
/
/
空
23
linker_dtor_function_t fini_func_;
/
/
空
24
public:
/
/
ARM EABI section used
for
stack unwinding.
uint32_t
*
ARM_exidx;
size_t ARM_exidx_count;
private:
uint32_t mips_symtabno_;
uint32_t mips_local_gotno_;
uint32_t mips_gotsym_;
bool
mips_relocate_got(const VersionTracker& version_tracker,
const soinfo_list_t& global_group,
const soinfo_list_t& local_group);
bool
mips_check_and_adjust_fp_modes();
size_t ref_count_;
/
/
25
public:
link_map link_map_head;
/
/
26
27
28
29
30
/
/
struct link_map {
/
/
ElfW(Addr) l_addr;
/
/
char
*
l_name;
/
/
ElfW(Dyn)
*
l_ld;
/
/
struct link_map
*
l_next;
/
/
struct link_map
*
l_prev;
/
/
};
bool
constructors_called;
/
/
When you read a virtual address
from
the ELF
file
, add this
/
/
value to get the corresponding address
in
the process' address space.
ElfW(Addr) load_bias;
/
/
32
bool
has_text_relocations;
bool
has_DT_SYMBOLIC;
public:
soinfo(android_namespace_t
*
ns, const char
*
name, const struct stat
*
file_stat,
off64_t file_offset,
int
rtld_flags);
~soinfo();
void call_constructors();
void call_destructors();
void call_pre_init_constructors();
bool
prelink_image();
bool
link_image(const soinfo_list_t& global_group, const soinfo_list_t& local_group,
const android_dlextinfo
*
extinfo, size_t
*
relro_fd_offset);
bool
protect_relro();
void add_child(soinfo
*
child);
void remove_all_links();
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: