-
-
[原创]Unidbg-Linker部分源码分析(上)
-
发表于: 2021-10-29 18:05 26128
-
我们有了Android Linker源码部分的解析,还学习了Unicorn的详细使用。如果没有看过之前文章的同学可以看下之前的文章哦。今天我们就来看下Unidbg是如何将一个So加载且跑起来的
在Unidbg中,我们想要加载一个So一般都是通过
来加载一个So文件,通过这个方法,我们最终会发现Unidbg调用了AndroidElfLoader类的loadInternal方法。AndroidElfLoader类也就充当了Linker的角色,所以我们今天就来分析一下这个类
我们先来分析一下AndroidElfLoader的构造方法
上面的注释也写的很清楚了,再总结一下AndroidElfLoader初始化做了什么
对TLS感兴趣可以阅读以下源码
http://androidxref.com/4.4.4_r1/xref/bionic/libc/bionic/libc_init_common.cpp#111
http://androidxref.com/4.4.4_r1/xref/bionic/libc/bionic/pthread_internals.cpp#66
http://androidxref.com/4.4.4_r1/xref/bionic/libc/private/bionic_tls.h#90
那么当我们调用了loadLibrary方法后,在Android中最终调用了AndroidElfLoader类下的loadInternal方法,我们来分析这个方法,该类的初始化我们已经分析过了
上面的函数接着调用了重载loadInternal方法继续加载,加载完成会返回一个LinuxModule对象,该对象就保存了该So文件加载后的信息,接着处理了符号和调用初始化函数
我们接着来看重载loadInternal方法,代码比较长
我们分为上下两篇文章。我们看到上面这个loadInternal方法就是加载一个So主要的内容,大致内容看到这里其实已经够用了,如果想了解更多的细节就看下篇。其中的很多方法我们没有展开来讲解,接下来我们就来处理剩余的细枝末节。如果文章有错误的地方还请指正,可以加个VX一起交流:roy5ue或直接在文章下方进行评论哦
DalvikModule dalvikModule
=
vm.loadLibrary(new
File
(
"so_path"
), true);
DalvikModule dalvikModule
=
vm.loadLibrary(new
File
(
"so_path"
), true);
public AndroidElfLoader(Emulator<AndroidFileIO> emulator, UnixSyscallHandler<AndroidFileIO> syscallHandler) {
/
/
调用父类构造方法,初始化emulator和syscallHandler字段
super
(emulator, syscallHandler);
/
/
初始化SP
stackSize
=
STACK_SIZE_OF_PAGE
*
emulator.getPageAlign();
/
/
将栈空间mem_map映射,因为在Backend(Unicorn)中,所有需要用的内存都需要先进行映射才能够进行使用,大小就是STACK_SIZE_OF_PAGE
*
emulator.getPageAlign(),可读可写权限
backend.mem_map(STACK_BASE
-
stackSize, stackSize, UnicornConst.UC_PROT_READ | UnicornConst.UC_PROT_WRITE);
/
/
设置SP寄存器
setStackPoint(STACK_BASE);
/
/
初始化TLS(线程局部存储相关),在libc一些系统库中是有线程局部变量的,如errno等。这里就做了相关的协处理器的初始化操作
this.environ
=
initializeTLS(new String[] {
"ANDROID_DATA=/data"
,
"ANDROID_ROOT=/system"
});
this.setErrno(
0
);
}
public AndroidElfLoader(Emulator<AndroidFileIO> emulator, UnixSyscallHandler<AndroidFileIO> syscallHandler) {
/
/
调用父类构造方法,初始化emulator和syscallHandler字段
super
(emulator, syscallHandler);
/
/
初始化SP
stackSize
=
STACK_SIZE_OF_PAGE
*
emulator.getPageAlign();
/
/
将栈空间mem_map映射,因为在Backend(Unicorn)中,所有需要用的内存都需要先进行映射才能够进行使用,大小就是STACK_SIZE_OF_PAGE
*
emulator.getPageAlign(),可读可写权限
backend.mem_map(STACK_BASE
-
stackSize, stackSize, UnicornConst.UC_PROT_READ | UnicornConst.UC_PROT_WRITE);
/
/
设置SP寄存器
setStackPoint(STACK_BASE);
/
/
初始化TLS(线程局部存储相关),在libc一些系统库中是有线程局部变量的,如errno等。这里就做了相关的协处理器的初始化操作
this.environ
=
initializeTLS(new String[] {
"ANDROID_DATA=/data"
,
"ANDROID_ROOT=/system"
});
this.setErrno(
0
);
}
protected final LinuxModule loadInternal(LibraryFile libraryFile, boolean forceCallInit) {
/
/
File
对象被封装为了LibraryFile对象
try
{
/
/
接着调用了loadInternal(重载)方法,继续加载流程
LinuxModule module
=
loadInternal(libraryFile);
/
/
处理符号(关于重定位)
resolveSymbols(!forceCallInit);
/
/
callInitFunction默认true
if
(callInitFunction || forceCallInit) {
/
/
调用初始化函数
for
(LinuxModule m : modules.values().toArray(new LinuxModule[
0
])) {
boolean forceCall
=
(forceCallInit && m
=
=
module) || m.isForceCallInit();
if
(callInitFunction) {
m.callInitFunction(emulator, forceCall);
}
else
if
(forceCall) {
m.callInitFunction(emulator, true);
}
m.initFunctionList.clear();
}
}
/
/
添加引用计数
module.addReferenceCount();
return
module;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
protected final LinuxModule loadInternal(LibraryFile libraryFile, boolean forceCallInit) {
/
/
File
对象被封装为了LibraryFile对象
try
{
/
/
接着调用了loadInternal(重载)方法,继续加载流程
LinuxModule module
=
loadInternal(libraryFile);
/
/
处理符号(关于重定位)
resolveSymbols(!forceCallInit);
/
/
callInitFunction默认true
if
(callInitFunction || forceCallInit) {
/
/
调用初始化函数
for
(LinuxModule m : modules.values().toArray(new LinuxModule[
0
])) {
boolean forceCall
=
(forceCallInit && m
=
=
module) || m.isForceCallInit();
if
(callInitFunction) {
m.callInitFunction(emulator, forceCall);
}
else
if
(forceCall) {
m.callInitFunction(emulator, true);
}
m.initFunctionList.clear();
}
}
/
/
添加引用计数
module.addReferenceCount();
return
module;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private LinuxModule loadInternal(LibraryFile libraryFile) throws IOException {
/
/
将我们的So文件让ElfFile类去解析,这个ElfFile是jelf库经过凯神改装过的,可以帮助解析Elf文件
final ElfFile elfFile
=
ElfFile.fromBytes(libraryFile.mapBuffer());
/
/
... 判断文件是否合法,
32
位模拟器是否加载了
64
位So等待
long
start
=
System.currentTimeMillis();
/
/
获取当前So的最大虚拟地址和页对齐align参数
long
bound_high
=
0
;
long
align
=
0
;
for
(
int
i
=
0
; i < elfFile.num_ph; i
+
+
) {
ElfSegment ph
=
elfFile.getProgramHeader(i);
if
(ph.
type
=
=
ElfSegment.PT_LOAD && ph.mem_size >
0
) {
/
/
遍历所有mem_size>
0
的PT_LOAD段
long
high
=
ph.virtual_address
+
ph.mem_size;
/
/
寻找bound_high最大值
if
(bound_high < high) {
bound_high
=
high;
}
/
/
寻找alignment最大值
if
(ph.alignment > align) {
align
=
ph.alignment;
}
}
}
ElfDynamicStructure dynamicStructure
=
null;
/
/
从获取到的So指定的alignment和默认的PageAlign取一个最大值,一般拿到的就是
4K
大小
final
long
baseAlign
=
Math.
max
(emulator.getPageAlign(), align);
/
/
根据baseAlign来计算该So的加载地址。初始地址
0x40000000L
final
long
load_base
=
((mmapBaseAddress
-
1
)
/
baseAlign
+
1
)
*
baseAlign;
/
/
这个就相当于Linker在计算load_size,但Unidbg中将所有So的最小虚拟地址默认为
0
/
/
这里有改进空间对吧,因为Linker中为了防止内存浪费,出现了一个load_bias_字段
/
/
但是出于目的不用,Unidbg的目的是让二进制文件跑起来
long
size
=
ARM.align(
0
, bound_high, baseAlign).size;
/
/
设置加载下个So的mmapBaseAddress
setMMapBaseAddress(load_base
+
size);
/
/
MemRegion存储了哪块内存对应了哪个So文件
final
List
<MemRegion> regions
=
new ArrayList<>(
5
);
MemoizedObject<ArmExIdx> armExIdx
=
null;
MemoizedObject<GnuEhFrameHeader> ehFrameHeader
=
null;
Alignment lastAlignment
=
null;
/
/
再次遍历所有段
for
(
int
i
=
0
; i < elfFile.num_ph; i
+
+
) {
ElfSegment ph
=
elfFile.getProgramHeader(i);
switch (ph.
type
) {
case ElfSegment.PT_LOAD:
/
/
PT_LOAD段
/
/
获取该段在内存中对应的操作权限,如该段未指定,设置满权限(一般不会出现这种情况)
int
prot
=
get_segment_protection(ph.flags);
if
(prot
=
=
UnicornConst.UC_PROT_NONE) {
prot
=
UnicornConst.UC_PROT_ALL;
}
/
/
该段在内存中的起始地址
final
long
begin
=
load_base
+
ph.virtual_address;
/
/
计算该段在内存中的位置和大小
Alignment check
=
ARM.align(begin, ph.mem_size, Math.
max
(emulator.getPageAlign(), ph.alignment));
/
/
获取上一个内存快
final
int
regionSize
=
regions.size();
MemRegion last
=
regionSize <
=
0
? null : regions.get(regionSize
-
1
);
/
/
处理两个段之间重叠部分?未找到案例
MemRegion overall
=
null;
if
(last !
=
null && check.address >
=
last.begin && check.address < last.end) {
overall
=
last;
}
if
(overall !
=
null) {
/
/
处理重叠段,应该为特殊情况,正常都会走下面
else
分支
long
overallSize
=
overall.end
-
check.address;
backend.mem_protect(check.address, overallSize, overall.perms | prot);
if
(ph.mem_size > overallSize) {
Alignment alignment
=
this.mem_map(begin
+
overallSize, ph.mem_size
-
overallSize, prot, libraryFile.getName(), Math.
max
(emulator.getPageAlign(), ph.alignment));
regions.add(new MemRegion(alignment.address, alignment.address
+
alignment.size, prot, libraryFile, ph.virtual_address));
if
(lastAlignment !
=
null) {
throw new UnsupportedOperationException();
}
lastAlignment
=
alignment;
}
}
else
{
/
/
将该PT_LOAD段指示的内存大小进行映射
Alignment alignment
=
this.mem_map(begin, ph.mem_size, prot, libraryFile.getName(), Math.
max
(emulator.getPageAlign(), ph.alignment));
/
/
添加一块MemRegion
regions.add(new MemRegion(alignment.address, alignment.address
+
alignment.size, prot, libraryFile, ph.virtual_address));
if
(lastAlignment !
=
null) {
/
/
处理该段与上一个段之间的空隙,置
0
long
base
=
lastAlignment.address
+
lastAlignment.size;
long
off
=
alignment.address
-
base;
if
(off <
0
) {
throw new IllegalStateException();
}
if
(off >
0
) {
backend.mem_map(base, off, UnicornConst.UC_PROT_NONE);
if
(memoryMap.put(base, new MemoryMap(base, (
int
) off, UnicornConst.UC_PROT_NONE)) !
=
null) {
log.warn(
"mem_map replace exists memory map base="
+
Long
.toHexString(base));
}
}
}
lastAlignment
=
alignment;
}
/
/
将该段对应的数据写入进已经映射好的内存
ph.getPtLoadData().writeTo(pointer(begin));
break
;
case ElfSegment.PT_DYNAMIC:
/
/
DYNAMIC段
dynamicStructure
=
ph.getDynamicStructure();
break
;
case ElfSegment.PT_INTERP:
/
/
INTERP段指定了解释器位置,在So中没用
if
(log.isDebugEnabled()) {
log.debug(
"["
+
libraryFile.getName()
+
"]interp="
+
ph.getInterpreter());
}
break
;
case ElfSegment.PT_GNU_EH_FRAME:
/
/
没分析过,未知TODO
ehFrameHeader
=
ph.getEhFrameHeader();
break
;
case ElfSegment.PT_ARM_EXIDX:
/
/
异常相关的段
armExIdx
=
ph.getARMExIdxData();
break
;
default:
if
(log.isDebugEnabled()) {
log.debug(
"["
+
libraryFile.getName()
+
"]segment type=0x"
+
Integer.toHexString(ph.
type
)
+
", offset=0x"
+
Long
.toHexString(ph.offset));
}
break
;
}
}
/
/
此时,该So中的有用的段信息已经处理完毕
/
/
该加载到内存的已经加载到内存
/
/
该置空的内存也已置空
/
/
动态段、异常段、PT_GNU_EH_FRAME段的信息已经保存下来,继续看接下来的处理
/
/
动态段是必须有的
if
(dynamicStructure
=
=
null) {
throw new IllegalStateException(
"dynamicStructure is empty."
);
}
/
/
此SoName是动态段中的tag为SO_NAME指定的内容,而且Unidbg中的Log也是基于这个SoName打印的
/
/
如果该内容为空,才会使用文件名。这也就是有的同学会问为什么我加载的是libxxxxx.so,而日志输出libyyyyyy.so呢
final String soName
=
dynamicStructure.getSOName(libraryFile.getName());
/
/
下面处理So中的依赖库
Map
<String, Module> neededLibraries
=
new HashMap<>();
/
/
dynamicStructure.getNeededLibraries(),这个方法是Unidbg改写jelf库加上的方法,会获取到所有的依赖库的名字
for
(String neededLibrary : dynamicStructure.getNeededLibraries()) {
if
(log.isDebugEnabled()) {
log.debug(soName
+
" need dependency "
+
neededLibrary);
}
/
/
modules字段保存了所有已经加载过的库,这里就是在寻找是否该So已经被加载过
LinuxModule loaded
=
modules.get(neededLibrary);
if
(loaded !
=
null) {
/
/
如果加载过了,添加引用计数、放到neededLibraries变量
loaded.addReferenceCount();
neededLibraries.put(FilenameUtils.getBaseName(loaded.name), loaded);
continue
;
}
/
/
如果依赖还没有被加载过,就开始寻找这个依赖文件在哪,先在当前So的路径下找
LibraryFile neededLibraryFile
=
libraryFile.resolveLibrary(emulator, neededLibrary);
/
/
如果当前路径下没有找到,就去找library解析器去找
if
(libraryResolver !
=
null && neededLibraryFile
=
=
null) {
neededLibraryFile
=
libraryResolver.resolveLibrary(emulator, neededLibrary);
}
if
(neededLibraryFile !
=
null) {
/
/
大吉大利,So找到啦,就会在这里加载
LinuxModule needed
=
loadInternal(neededLibraryFile);
needed.addReferenceCount();
neededLibraries.put(FilenameUtils.getBaseName(needed.name), needed);
}
else
{
log.info(soName
+
" load dependency "
+
neededLibrary
+
" failed"
);
}
}
/
/
到这里,该So所依赖的So也被加载进来了
/
/
下面这个循环会处理未解决(符号为
0
特殊情况)的重定位,进行二次重定位,极少数能成功,如果确定没用可以注释掉
for
(LinuxModule module : modules.values()) {
for
(Iterator<ModuleSymbol> iterator
=
module.getUnresolvedSymbol().iterator(); iterator.hasNext(); ) {
ModuleSymbol moduleSymbol
=
iterator.
next
();
ModuleSymbol resolved
=
moduleSymbol.resolve(module.getNeededLibraries(), false, hookListeners, emulator.getSvcMemory());
if
(resolved !
=
null) {
if
(log.isDebugEnabled()) {
log.debug(
"["
+
moduleSymbol.soName
+
"]"
+
moduleSymbol.symbol.getName()
+
" symbol resolved to "
+
resolved.toSoName);
}
resolved.relocation(emulator);
iterator.remove();
}
}
}
/
/
下面开始处理重定位
List
<ModuleSymbol>
list
=
new ArrayList<>();
for
(MemoizedObject<ElfRelocation>
object
: dynamicStructure.getRelocations()) {
/
/
遍历So中所有的重定位信息
ElfRelocation relocation
=
object
.getValue();
/
/
拿到重定位类型
final
int
type
=
relocation.
type
();
if
(
type
=
=
0
) {
log.warn(
"Unhandled relocation type "
+
type
);
continue
;
}
/
/
拿到重定位项指定的符号信息
ElfSymbol symbol
=
relocation.sym()
=
=
0
? null : relocation.symbol();
long
sym_value
=
symbol !
=
null ? symbol.value :
0
;
/
/
计算需要重定位的位置
Pointer relocationAddr
=
UnidbgPointer.pointer(emulator, load_base
+
relocation.offset());
assert
relocationAddr !
=
null;
Log log
=
LogFactory.getLog(
"com.github.unidbg.linux."
+
soName);
if
(log.isDebugEnabled()) {
log.debug(
"symbol="
+
symbol
+
", type="
+
type
+
", relocationAddr="
+
relocationAddr
+
", offset=0x"
+
Long
.toHexString(relocation.offset())
+
", addend="
+
relocation.addend()
+
", sym="
+
relocation.sym()
+
", android="
+
relocation.isAndroid());
}
ModuleSymbol moduleSymbol;
/
/
根据重定位类型进行不同的处理,下面包含了
32
位
/
64
位下的重定位处理
switch (
type
) {
case ARMEmulator.R_ARM_ABS32: {
int
offset
=
relocationAddr.getInt(
0
);
moduleSymbol
=
resolveSymbol(load_base, symbol, relocationAddr, soName, neededLibraries.values(), offset);
if
(moduleSymbol
=
=
null) {
/
/
不能当即处理的,添加到
list
,后面再处理
list
.add(new ModuleSymbol(soName, load_base, symbol, relocationAddr, null, offset));
}
else
{
moduleSymbol.relocation(emulator);
}
break
;
}
case ARMEmulator.R_AARCH64_ABS64: {
long
offset
=
relocationAddr.getLong(
0
)
+
relocation.addend();
moduleSymbol
=
resolveSymbol(load_base, symbol, relocationAddr, soName, neededLibraries.values(), offset);
if
(moduleSymbol
=
=
null) {
list
.add(new ModuleSymbol(soName, load_base, symbol, relocationAddr, null, offset));
}
else
{
moduleSymbol.relocation(emulator);
}
break
;
}
case ARMEmulator.R_ARM_RELATIVE: {
int
offset
=
relocationAddr.getInt(
0
);
if
(sym_value
=
=
0
) {
relocationAddr.setInt(
0
, (
int
) load_base
+
offset);
}
else
{
throw new IllegalStateException(
"sym_value=0x"
+
Long
.toHexString(sym_value));
}
break
;
}
case ARMEmulator.R_AARCH64_RELATIVE:
if
(sym_value
=
=
0
) {
relocationAddr.setLong(
0
, load_base
+
relocation.addend());
}
else
{
throw new IllegalStateException(
"sym_value=0x"
+
Long
.toHexString(sym_value));
}
break
;
case ARMEmulator.R_ARM_GLOB_DAT:
case ARMEmulator.R_ARM_JUMP_SLOT:
moduleSymbol
=
resolveSymbol(load_base, symbol, relocationAddr, soName, neededLibraries.values(),
0
);
if
(moduleSymbol
=
=
null) {
list
.add(new ModuleSymbol(soName, load_base, symbol, relocationAddr, null,
0
));
}
else
{
moduleSymbol.relocation(emulator);
}
break
;
case ARMEmulator.R_AARCH64_GLOB_DAT:
case ARMEmulator.R_AARCH64_JUMP_SLOT:
moduleSymbol
=
resolveSymbol(load_base, symbol, relocationAddr, soName, neededLibraries.values(), relocation.addend());
if
(moduleSymbol
=
=
null) {
list
.add(new ModuleSymbol(soName, load_base, symbol, relocationAddr, null, relocation.addend()));
}
else
{
moduleSymbol.relocation(emulator);
}
break
;
case ARMEmulator.R_ARM_COPY:
throw new IllegalStateException(
"R_ARM_COPY relocations are not supported"
);
case ARMEmulator.R_AARCH64_COPY:
throw new IllegalStateException(
"R_AARCH64_COPY relocations are not supported"
);
case ARMEmulator.R_AARCH64_ABS32:
case ARMEmulator.R_AARCH64_ABS16:
case ARMEmulator.R_AARCH64_PREL64:
case ARMEmulator.R_AARCH64_PREL32:
case ARMEmulator.R_AARCH64_PREL16:
case ARMEmulator.R_AARCH64_IRELATIVE:
case ARMEmulator.R_AARCH64_TLS_TPREL64:
case ARMEmulator.R_AARCH64_TLS_DTPREL32:
case ARMEmulator.R_ARM_IRELATIVE:
case ARMEmulator.R_ARM_REL32:
default:
log.warn(
"["
+
soName
+
"]Unhandled relocation type "
+
type
+
", symbol="
+
symbol
+
", relocationAddr="
+
relocationAddr
+
", offset=0x"
+
Long
.toHexString(relocation.offset())
+
", addend="
+
relocation.addend()
+
", android="
+
relocation.isAndroid());
break
;
}
}
/
/
重定位完成后,开始执行初始化函数
List
<InitFunction> initFunctionList
=
new ArrayList<>();
if
(elfFile.file_type
=
=
ElfFile.FT_EXEC) {
/
/
处理可执行文件相关,我们分析So的,忽略就可以
int
preInitArraySize
=
dynamicStructure.getPreInitArraySize();
int
count
=
preInitArraySize
/
emulator.getPointerSize();
if
(count >
0
) {
Pointer pointer
=
UnidbgPointer.pointer(emulator, load_base
+
dynamicStructure.getPreInitArrayOffset());
if
(pointer
=
=
null) {
throw new IllegalStateException(
"DT_PREINIT_ARRAY is null"
);
}
for
(
int
i
=
0
; i < count; i
+
+
) {
Pointer func
=
pointer.getPointer((
long
) i
*
emulator.getPointerSize());
if
(func !
=
null) {
initFunctionList.add(new AbsoluteInitFunction(load_base, soName, ((UnidbgPointer) func).peer));
}
}
}
}
if
(elfFile.file_type
=
=
ElfFile.FT_DYN) {
/
/
处理So的初始化函数
/
/
下面的处理内容在新版有修复,我们之前Linker的文章也讲过,他们的顺序不应该是平级的,需要Init函数先执行
int
init
=
dynamicStructure.getInit();
if
(init !
=
0
) {
initFunctionList.add(new LinuxInitFunction(load_base, soName, init));
}
/
/
处理 init.array
int
initArraySize
=
dynamicStructure.getInitArraySize();
int
count
=
initArraySize
/
emulator.getPointerSize();
if
(count >
0
) {
Pointer pointer
=
UnidbgPointer.pointer(emulator, load_base
+
dynamicStructure.getInitArrayOffset());
if
(pointer
=
=
null) {
throw new IllegalStateException(
"DT_INIT_ARRAY is null"
);
}
for
(
int
i
=
0
; i < count; i
+
+
) {
/
/
当作数组来处理每一个init函数
Pointer func
=
pointer.getPointer((
long
) i
*
emulator.getPointerSize());
if
(func !
=
null) {
/
/
将他们添加到initFunction列表中
initFunctionList.add(new AbsoluteInitFunction(load_base, soName, ((UnidbgPointer) func).peer));
}
}
}
}
/
/
至此,依赖So加载了,重定位可以处理的也处理了(不能处理的还会有二次处理)
/
/
初始化函数也被添加到列表中了,但是还没有调用(注意)
SymbolLocator dynsym
=
dynamicStructure.getSymbolStructure();
if
(dynsym
=
=
null) {
throw new IllegalStateException(
"dynsym is null"
);
}
ElfSection symbolTableSection
=
null;
try
{
symbolTableSection
=
elfFile.getSymbolTableSection();
} catch(Throwable ignored) {}
/
/
将加载好的So封装位LinuxModule对象
LinuxModule module
=
new LinuxModule(load_base, size, soName, dynsym,
list
, initFunctionList, neededLibraries, regions,
armExIdx, ehFrameHeader, symbolTableSection, elfFile, dynamicStructure);
/
/
...
/
/
放入已加载的So列表中
modules.put(soName, module);
if
(maxSoName
=
=
null || soName.length() > maxSoName.length()) {
maxSoName
=
soName;
}
if
(bound_high > maxSizeOfSo) {
maxSizeOfSo
=
bound_high;
}
/
/
设置可执行Elf的入口点
module.setEntryPoint(elfFile.entry_point);
log.debug(
"Load library "
+
soName
+
" offset="
+
(System.currentTimeMillis()
-
start)
+
"ms"
+
", entry_point=0x"
+
Long
.toHexString(elfFile.entry_point));
/
/
通知监听器,So已加载完毕
notifyModuleLoaded(module);
return
module;
}
private LinuxModule loadInternal(LibraryFile libraryFile) throws IOException {
/
/
将我们的So文件让ElfFile类去解析,这个ElfFile是jelf库经过凯神改装过的,可以帮助解析Elf文件
final ElfFile elfFile
=
ElfFile.fromBytes(libraryFile.mapBuffer());
/
/
... 判断文件是否合法,
32
位模拟器是否加载了
64
位So等待
long
start
=
System.currentTimeMillis();
/
/
获取当前So的最大虚拟地址和页对齐align参数
long
bound_high
=
0
;
long
align
=
0
;
for
(
int
i
=
0
; i < elfFile.num_ph; i
+
+
) {
ElfSegment ph
=
elfFile.getProgramHeader(i);
if
(ph.
type
=
=
ElfSegment.PT_LOAD && ph.mem_size >
0
) {
/
/
遍历所有mem_size>
0
的PT_LOAD段
long
high
=
ph.virtual_address
+
ph.mem_size;
/
/
寻找bound_high最大值
if
(bound_high < high) {
bound_high
=
high;
}
/
/
寻找alignment最大值
if
(ph.alignment > align) {
align
=
ph.alignment;
}
}
}
ElfDynamicStructure dynamicStructure
=
null;
/
/
从获取到的So指定的alignment和默认的PageAlign取一个最大值,一般拿到的就是
4K
大小
final
long
baseAlign
=
Math.
max
(emulator.getPageAlign(), align);
/
/
根据baseAlign来计算该So的加载地址。初始地址
0x40000000L
final
long
load_base
=
((mmapBaseAddress
-
1
)
/
baseAlign
+
1
)
*
baseAlign;
/
/
这个就相当于Linker在计算load_size,但Unidbg中将所有So的最小虚拟地址默认为
0
/
/
这里有改进空间对吧,因为Linker中为了防止内存浪费,出现了一个load_bias_字段
/
/
但是出于目的不用,Unidbg的目的是让二进制文件跑起来
long
size
=
ARM.align(
0
, bound_high, baseAlign).size;
/
/
设置加载下个So的mmapBaseAddress
setMMapBaseAddress(load_base
+
size);
/
/
MemRegion存储了哪块内存对应了哪个So文件
final
List
<MemRegion> regions
=
new ArrayList<>(
5
);
MemoizedObject<ArmExIdx> armExIdx
=
null;
MemoizedObject<GnuEhFrameHeader> ehFrameHeader
=
null;
Alignment lastAlignment
=
null;
/
/
再次遍历所有段
for
(
int
i
=
0
; i < elfFile.num_ph; i
+
+
) {
ElfSegment ph
=
elfFile.getProgramHeader(i);
switch (ph.
type
) {
case ElfSegment.PT_LOAD:
/
/
PT_LOAD段
/
/
获取该段在内存中对应的操作权限,如该段未指定,设置满权限(一般不会出现这种情况)
int
prot
=
get_segment_protection(ph.flags);
if
(prot
=
=
UnicornConst.UC_PROT_NONE) {
prot
=
UnicornConst.UC_PROT_ALL;
}
/
/
该段在内存中的起始地址
final
long
begin
=
load_base
+
ph.virtual_address;
/
/
计算该段在内存中的位置和大小
Alignment check
=
ARM.align(begin, ph.mem_size, Math.
max
(emulator.getPageAlign(), ph.alignment));
/
/
获取上一个内存快
final
int
regionSize
=
regions.size();
MemRegion last
=
regionSize <
=
0
? null : regions.get(regionSize
-
1
);
/
/
处理两个段之间重叠部分?未找到案例
MemRegion overall
=
null;
if
(last !
=
null && check.address >
=
last.begin && check.address < last.end) {
overall
=
last;
}
if
(overall !
=
null) {
/
/
处理重叠段,应该为特殊情况,正常都会走下面
else
分支
long
overallSize
=
overall.end
-
check.address;
backend.mem_protect(check.address, overallSize, overall.perms | prot);
if
(ph.mem_size > overallSize) {
Alignment alignment
=
this.mem_map(begin
+
overallSize, ph.mem_size
-
overallSize, prot, libraryFile.getName(), Math.
max
(emulator.getPageAlign(), ph.alignment));
regions.add(new MemRegion(alignment.address, alignment.address
+
alignment.size, prot, libraryFile, ph.virtual_address));
if
(lastAlignment !
=
null) {
throw new UnsupportedOperationException();
}
lastAlignment
=
alignment;
}
}
else
{
/
/
将该PT_LOAD段指示的内存大小进行映射
Alignment alignment
=
this.mem_map(begin, ph.mem_size, prot, libraryFile.getName(), Math.
max
(emulator.getPageAlign(), ph.alignment));
/
/
添加一块MemRegion
regions.add(new MemRegion(alignment.address, alignment.address
+
alignment.size, prot, libraryFile, ph.virtual_address));
if
(lastAlignment !
=
null) {
/
/
处理该段与上一个段之间的空隙,置
0
long
base
=
lastAlignment.address
+
lastAlignment.size;
long
off
=
alignment.address
-
base;
if
(off <
0
) {
throw new IllegalStateException();
}
if
(off >
0
) {
backend.mem_map(base, off, UnicornConst.UC_PROT_NONE);
if
(memoryMap.put(base, new MemoryMap(base, (
int
) off, UnicornConst.UC_PROT_NONE)) !
=
null) {
log.warn(
"mem_map replace exists memory map base="
+
Long
.toHexString(base));
}
}
}
lastAlignment
=
alignment;
}
/
/
将该段对应的数据写入进已经映射好的内存
ph.getPtLoadData().writeTo(pointer(begin));
break
;
case ElfSegment.PT_DYNAMIC:
/
/
DYNAMIC段
dynamicStructure
=
ph.getDynamicStructure();
break
;
case ElfSegment.PT_INTERP:
/
/
INTERP段指定了解释器位置,在So中没用
if
(log.isDebugEnabled()) {
log.debug(
"["
+
libraryFile.getName()
+
"]interp="
+
ph.getInterpreter());
}
break
;
case ElfSegment.PT_GNU_EH_FRAME:
/
/
没分析过,未知TODO
ehFrameHeader
=
ph.getEhFrameHeader();
break
;
case ElfSegment.PT_ARM_EXIDX:
/
/
异常相关的段
armExIdx
=
ph.getARMExIdxData();
break
;
default:
if
(log.isDebugEnabled()) {
log.debug(
"["
+
libraryFile.getName()
+
"]segment type=0x"
+
Integer.toHexString(ph.
type
)
+
", offset=0x"
+
Long
.toHexString(ph.offset));
}
break
;
}
}
/
/
此时,该So中的有用的段信息已经处理完毕
/
/
该加载到内存的已经加载到内存
/
/
该置空的内存也已置空
/
/
动态段、异常段、PT_GNU_EH_FRAME段的信息已经保存下来,继续看接下来的处理
/
/
动态段是必须有的
if
(dynamicStructure
=
=
null) {
throw new IllegalStateException(
"dynamicStructure is empty."
);
}
/
/
此SoName是动态段中的tag为SO_NAME指定的内容,而且Unidbg中的Log也是基于这个SoName打印的
/
/
如果该内容为空,才会使用文件名。这也就是有的同学会问为什么我加载的是libxxxxx.so,而日志输出libyyyyyy.so呢
final String soName
=
dynamicStructure.getSOName(libraryFile.getName());
/
/
下面处理So中的依赖库
Map
<String, Module> neededLibraries
=
new HashMap<>();
/
/
dynamicStructure.getNeededLibraries(),这个方法是Unidbg改写jelf库加上的方法,会获取到所有的依赖库的名字
for
(String neededLibrary : dynamicStructure.getNeededLibraries()) {
if
(log.isDebugEnabled()) {
log.debug(soName
+
" need dependency "
+
neededLibrary);
}
/
/
modules字段保存了所有已经加载过的库,这里就是在寻找是否该So已经被加载过
LinuxModule loaded
=
modules.get(neededLibrary);
if
(loaded !
=
null) {
/
/
如果加载过了,添加引用计数、放到neededLibraries变量
loaded.addReferenceCount();
neededLibraries.put(FilenameUtils.getBaseName(loaded.name), loaded);
continue
;
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]Base64 编码原理 && 实现 23383
- [原创]Lsposed 技术原理探讨 && 基本安装使用 26302
- [原创]Unidbg-Linker部分源码分析(下) 28645
- [原创]Unidbg-Linker部分源码分析(上) 26129