本篇是一个追随技术的人照着网上为数不多的资料,在探索过程中发现了许多意想不到的问题,搜了好多文章发现对于这方面的记载很少,甚至连一个实现的蓝本都没找到,写下这篇文章希望能帮到像我一样对so壳感兴趣的伙伴在做实践的时候能有一个抓手,只有了解了加壳原理才能更好的脱壳,其实so文件在系统当中都对应了一个soinfo指针如果能找到soinfo指针那么脱掉so壳也不是不可能,如果有机会的话后面会出so脱壳相关的知识,其实最难的点在于如何找到当前so的soinfo指针这个,源自aosp8.1.0_r33.
回想dex可以通过动态加载插件的方式完成加壳,例如一代壳,那么so是否也有这种技术呢,答案是肯定的,只是用c实现的so没有办法使用java中的classloader这种,快速便捷的类加载方式,那么我们现在就遇到了2个问题就是如何将so加载到内存中,如何使用so中的函数,那么其实我们现在就可以从安卓源码入手,看一看so是如何加载到系统中的
从System.loadLibrary入手(ps:我们平常加载so就是用到这两个函数一个是System.loadLibrary传入Classloader所在的相对路径,一个是System.load传入绝对路径,最后发现他们在native层都会汇聚到一点),一直跟下去就会发现最后Runtime下的私有函数doLoad中调用了nativeLoad来进入native层
那么顺利进入so层,进入了libcore/ojluni/src/main/native/Runtime.c
中的Runtime_nativeLoad方法,一直跟下去最终在system/core/libnativeloader/native_loader.cpp
中找到了OpenNativeLibrary方法找到了dlopen和android_dlopen_ext(ps:通过分析发现这两个函数最终也汇聚于linker下的do_dlopen)
那么其实现在就清楚了,其实java层的loadLibrary也是通过linker中的do_dlopen来j将so文件加载到内存中的,那么下面开始分析do_dlopen中的so的加载流程。
那么继续跟进find_library函数,发现如下关键点代码
在find_libraries函数中,通过find_library_internal,来搞定所有需要的so和初始化我们的so等一些列工作
一直跟下去,最终在load_library函数中发现了调用了LoadTask对象的read函数,之后又调用了ElfReader对象的read函数来初始化LoadTask
LoadTask对象的read方法十分简洁这里就不贴代码了。在/bionic/linker/linker_phdr.cpp
目录中找到了ElfReader类中read函数的实现,这个函数很重要所以就全贴上了,可以看到,这里将我们打开so文件的fd以及file_size等一系列信息复制给了ElfReader对象然后做了5件事
1:读取elf头
2:验证elf头
3:读取程序头
4:读取节头
5:读取Dynamic节
但是我们是做一个简单的壳肯定要去掉这些对于加载用不到的地方,所以这里可以只实现读取程序头和elf头,因为Execution View下一定会使用程序头加载段而节信息可有可无
下面就是我模仿安卓源码写的一个ReadProgramHeader的实现,首先要实现一个loader类,模仿源码中的ElfReader类给他填上属性。,然后实现ReadProgramHeader方法.
需要将我们的真正的so加载到内存中(如果是壳的话可以是加密文件或者直接贴在dex文件最后有很多方法这里不再讨论,这里只讨论最基础的动态加载),使用mmap就好
然后read函数就完成了其他部分我们不需要看。结束后load_library函数中第二个关键的部分就是对依赖so的加载,这里重点看for_each_dt_needed函数,逻辑十分的简单,就是从dynamic段中寻找类型为0x1(在elf.h中#define DT_NEEDED 1)的段然后将这个so放入到LoadTask对象的加载队列中,最终通过read函数将每一个未被加载的so加载到内存中,并且返回所有的soinfo指针。
接着继续find_library函数,发现之后又调用了task的load函数。跟踪进去load函数中发现分2块内容,第一块内容就是调用ElfReader类的load方法装载so,第二部分就是对于有soinfo的修正,代码十分简洁这里就不贴了,那么现在的任务就是看看ElfReader类的load方法是如何实现的。load函数就少了一点只做了3件事
1:申请段加载空间
2:装载段
3:找到Phdr在内存中的地址
那么这里我们就要将这三个方法全部都实现了
首先是申请加载空间,期间有一个phdr_table_get_load_size方法我们直接从源码里面抄过来即可
接下来就是装载段信息,这部分也是完全仿照安卓源码的写法,将段头中所有类型为0x1的段加载到内存当中( #define PT_LOAD 1)我这里直接用memcpy了主要是不想从文件中加载so想的是我这种方案直接内存加载so,并且用mprotect申请权限,最后填0占位
最后实现FindPhdr方法,其中引用了CheckPhdr方法,这里FindPHPtr函数主要分2类如果直接就有程序头段那么直接用就好否则就找虚拟地址为0的段从文件头解析它,全部也是直接超过来就好,但是注意这个要抄在load类里面,上面的phdr_table_get_load_size写在类的外面也无关紧要。
至此我们已经写完了装载过程,至于引用的其他so我们写一个循环直接重走一边即可,由于我这里没有引用所以先不展示这一方面的内容了,这里还有一段Load函数结尾需要将我们上面读到的内容存储到link维护的本so的soinfo中,而由于我们是加壳所以要做的就是将刚才得到的段信息替换掉本so的soinfo,类似于dex整体加壳(一代壳)的classloader的修正,接下来进入获得soinfo部分,会写在下篇里面
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
.....
private String doLoad(String name, ClassLoader loader) {
.....
return
nativeLoad(name, loader, librarySearchPath);
}
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
.....
private String doLoad(String name, ClassLoader loader) {
.....
return
nativeLoad(name, loader, librarySearchPath);
}
Runtime_nativeLoad(JNIEnv
*
env, jclass ignored, jstring javaFilename,
jobject javaLoader, jstring javaLibrarySearchPath)
{
return
JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}
......c
void
*
OpenNativeLibrary(...) {
UNUSED(target_sdk_version);
if
(class_loader
=
=
nullptr) {
*
needs_native_bridge
=
false;
return
dlopen(path, RTLD_NOW);
}
.....
void
*
handle
=
android_dlopen_ext(path, RTLD_NOW, &extinfo);
/
/
当classloader不为空的情况下调用android_dlopen_ext继续跟下去其实也可以看到它的实现最终也是do_dlopen
Runtime_nativeLoad(JNIEnv
*
env, jclass ignored, jstring javaFilename,
jobject javaLoader, jstring javaLibrarySearchPath)
{
return
JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}
......c
void
*
OpenNativeLibrary(...) {
UNUSED(target_sdk_version);
if
(class_loader
=
=
nullptr) {
*
needs_native_bridge
=
false;
return
dlopen(path, RTLD_NOW);
}
.....
void
*
handle
=
android_dlopen_ext(path, RTLD_NOW, &extinfo);
/
/
当classloader不为空的情况下调用android_dlopen_ext继续跟下去其实也可以看到它的实现最终也是do_dlopen
void
*
do_dlopen(......) {
...
soinfo
*
si
=
find_library(ns, translated_name, flags, extinfo, caller)
/
/
通过find_library函数拿到当前handle对应的soinfo
...
}
void
*
do_dlopen(......) {
...
soinfo
*
si
=
find_library(ns, translated_name, flags, extinfo, caller)
/
/
通过find_library函数拿到当前handle对应的soinfo
...
}
...
static soinfo
*
find_library(android_namespace_t
*
ns,
const char
*
name,
int
rtld_flags,
const android_dlextinfo
*
extinfo,
soinfo
*
needed_by) {
...
if
(!find_libraries(const_cast<android_namespace_t
*
>(task
-
>get_start_from()),task,&zip_archive_cache,&load_tasks,rtld_flags,search_linked_namespaces || is_dt_needed))
/
/
关键函数详细分析
...
}
...
static soinfo
*
find_library(android_namespace_t
*
ns,
const char
*
name,
int
rtld_flags,
const android_dlextinfo
*
extinfo,
soinfo
*
needed_by) {
...
if
(!find_libraries(const_cast<android_namespace_t
*
>(task
-
>get_start_from()),task,&zip_archive_cache,&load_tasks,rtld_flags,search_linked_namespaces || is_dt_needed))
/
/
关键函数详细分析
...
}
bool
find_libraries(...) {
...
if
(!find_library_internal(....){
return
false;
}
...
}
bool
find_libraries(...) {
...
if
(!find_library_internal(....){
return
false;
}
...
}
static
bool
load_library(...) {
...
if
(!task
-
>read(realpath.c_str(), file_stat.st_size))
/
/
通过ElfReader对象的read函数初始化我们的LoadTask对象
...
for
(const ElfW(Dyn)
*
d
=
elf_reader.dynamic(); d
-
>d_tag !
=
DT_NULL;
+
+
d) {
if
(d
-
>d_tag
=
=
DT_RUNPATH) {
si
-
>set_dt_runpath(elf_reader.get_string(d
-
>d_un.d_val));
}
if
(d
-
>d_tag
=
=
DT_SONAME) {
si
-
>set_soname(elf_reader.get_string(d
-
>d_un.d_val));
}
}
for_each_dt_needed(task
-
>get_elf_reader(), [&](const char
*
name) {
load_tasks
-
>push_back(LoadTask::create(name, si, ns, task
-
>get_readers_map()));
});
}
static
bool
load_library(...) {
...
if
(!task
-
>read(realpath.c_str(), file_stat.st_size))
/
/
通过ElfReader对象的read函数初始化我们的LoadTask对象
...
for
(const ElfW(Dyn)
*
d
=
elf_reader.dynamic(); d
-
>d_tag !
=
DT_NULL;
+
+
d) {
if
(d
-
>d_tag
=
=
DT_RUNPATH) {
si
-
>set_dt_runpath(elf_reader.get_string(d
-
>d_un.d_val));
}
if
(d
-
>d_tag
=
=
DT_SONAME) {
si
-
>set_soname(elf_reader.get_string(d
-
>d_un.d_val));
}
}
for_each_dt_needed(task
-
>get_elf_reader(), [&](const char
*
name) {
load_tasks
-
>push_back(LoadTask::create(name, si, ns, task
-
>get_readers_map()));
});
}
bool
ElfReader::Read(const char
*
name,
int
fd, off64_t file_offset, off64_t file_size) {
if
(did_read_) {
return
true;
}
name_
=
name;
fd_
=
fd;
file_offset_
=
file_offset;
file_size_
=
file_size;
if
(ReadElfHeader() &&
VerifyElfHeader() &&
ReadProgramHeaders() &&
ReadSectionHeaders() &&
ReadDynamicSection()) {
did_read_
=
true;
}
return
did_read_;
}
bool
ElfReader::Read(const char
*
name,
int
fd, off64_t file_offset, off64_t file_size) {
if
(did_read_) {
return
true;
}
name_
=
name;
fd_
=
fd;
file_offset_
=
file_offset;
file_size_
=
file_size;
if
(ReadElfHeader() &&
VerifyElfHeader() &&
ReadProgramHeaders() &&
ReadSectionHeaders() &&
ReadDynamicSection()) {
did_read_
=
true;
}
return
did_read_;
}
class
load {
public:
const char
*
name_;
int
fd_;
ElfW(Ehdr) header_;
size_t phdr_num_;
void
*
phdr_mmap_;
ElfW(Phdr)
*
phdr_table_;
ElfW(Addr) phdr_size_;
void
*
load_start_;
size_t load_size_;
ElfW(Addr) load_bias_;
const ElfW(Phdr)
*
loaded_phdr_;
void
*
st;
public:
load(void
*
sta)
: phdr_num_(
0
), phdr_mmap_(NULL), phdr_table_(NULL), phdr_size_(
0
),
load_start_(NULL), load_size_(
0
), load_bias_(
0
),
loaded_phdr_(NULL), st(sta) {
}
bool
loadhead(){
return
memcpy(&(header_),st,sizeof(header_));
/
/
赋值elf头
};
bool
ReadProgramHeader() {
phdr_num_
=
header_.e_phnum;
/
/
由于我们没有执行ReadElfHeader函数所以一会要手动给这个header赋值
ElfW(Addr) page_min
=
PAGE_START(header_.e_phoff);
/
/
页对齐
ElfW(Addr) page_max
=
PAGE_END(header_.e_phoff
+
(phdr_num_
*
sizeof(ElfW(Phdr))));
ElfW(Addr) page_offset
=
PAGE_OFFSET(header_.e_phoff);
/
/
获得程序头的偏移
void
*
*
c
=
reinterpret_cast<void
*
*
>((char
*
) (st)
+
page_min);
phdr_table_
=
reinterpret_cast<ElfW(Phdr)
*
>(reinterpret_cast<char
*
>(c)
+
page_offset);
/
/
获得程序头实际地址
return
true;
};
}
class
load {
public:
const char
*
name_;
int
fd_;
ElfW(Ehdr) header_;
size_t phdr_num_;
void
*
phdr_mmap_;
ElfW(Phdr)
*
phdr_table_;
ElfW(Addr) phdr_size_;
void
*
load_start_;
size_t load_size_;
ElfW(Addr) load_bias_;
const ElfW(Phdr)
*
loaded_phdr_;
void
*
st;
public:
load(void
*
sta)
: phdr_num_(
0
), phdr_mmap_(NULL), phdr_table_(NULL), phdr_size_(
0
),
load_start_(NULL), load_size_(
0
), load_bias_(
0
),
loaded_phdr_(NULL), st(sta) {
}
bool
loadhead(){
return
memcpy(&(header_),st,sizeof(header_));
/
/
赋值elf头
};
bool
ReadProgramHeader() {
phdr_num_
=
header_.e_phnum;
/
/
由于我们没有执行ReadElfHeader函数所以一会要手动给这个header赋值
ElfW(Addr) page_min
=
PAGE_START(header_.e_phoff);
/
/
页对齐
ElfW(Addr) page_max
=
PAGE_END(header_.e_phoff
+
(phdr_num_
*
sizeof(ElfW(Phdr))));
ElfW(Addr) page_offset
=
PAGE_OFFSET(header_.e_phoff);
/
/
获得程序头的偏移
void
*
*
c
=
reinterpret_cast<void
*
*
>((char
*
) (st)
+
page_min);
phdr_table_
=
reinterpret_cast<ElfW(Phdr)
*
>(reinterpret_cast<char
*
>(c)
+
page_offset);
/
/
获得程序头实际地址
return
true;
};
}
int
fd;
void
*
start;
struct stat sb;
fd
=
open
(
"/data/local/tmp/1.so"
, O_RDONLY);
/
/
打开获得我们插件so的fd
fstat(fd, &sb);
start
=
static_cast<void
*
*
>(mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd,
0
));
/
/
用mmap把文件加载到内存中
load a(start);
/
/
构造load对象
int
fd;
void
*
start;
struct stat sb;
fd
=
open
(
"/data/local/tmp/1.so"
, O_RDONLY);
/
/
打开获得我们插件so的fd
fstat(fd, &sb);
start
=
static_cast<void
*
*
>(mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd,
0
));
/
/
用mmap把文件加载到内存中
load a(start);
/
/
构造load对象
static void for_each_dt_needed(const ElfReader& elf_reader, F action) {
for
(const ElfW(Dyn)
*
d
=
elf_reader.dynamic(); d
-
>d_tag !
=
DT_NULL;
+
+
d) {
if
(d
-
>d_tag
=
=
DT_NEEDED) {
action(fix_dt_needed(elf_reader.get_string(d
-
>d_un.d_val), elf_reader.name()));
}
}
}
static void for_each_dt_needed(const ElfReader& elf_reader, F action) {
for
(const ElfW(Dyn)
*
d
=
elf_reader.dynamic(); d
-
>d_tag !
=
DT_NULL;
+
+
d) {
if
(d
-
>d_tag
=
=
DT_NEEDED) {
action(fix_dt_needed(elf_reader.get_string(d
-
>d_un.d_val), elf_reader.name()));
}
}
}
bool
ElfReader::Load(const android_dlextinfo
*
extinfo) {
CHECK(did_read_);
if
(did_load_) {
return
true;
}
if
(ReserveAddressSpace(extinfo) &&
LoadSegments() &&
FindPhdr()) {
did_load_
=
true;
}
return
did_load_;
}
bool
ElfReader::Load(const android_dlextinfo
*
extinfo) {
CHECK(did_read_);
if
(did_load_) {
return
true;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)