[Fat Tiger's Reverse Road] 04——Principle of shelling (one-generation shell) & detailed explanation of related concepts of shelling
[Fat Tiger's Reverse Road] 01 - Detailed Explanation of Dynamic Loading and Class Loading Mechanism
[Fat Tiger's Reverse Road] 02 - Detailed Explanation & Implementation of Android's Overall Packing Principle
[Fat Tiger's Reverse Road] 03 - Android Generation Shelling method & practical operation
Article directory
- [Fat Tiger's Reverse Road] 04——Principle of shelling (one-generation shell) & detailed explanation of related concepts of shelling
- foreword
- 1. Dex loading process
- 2. Dex2Oat compilation process
- 3. Class loading process
- 4. DexFile Detailed Explanation
- 5. Detailed explanation of ArtMethod
- Summarize
- references
foreword
提示:这里可以添加本文要记录的大概内容:
In the above, we have explained the basic method and practical operation of Android unpacking. Now we will introduce the principle of unpacking (first generation shell) and the basic knowledge related to unpacking. Due to the limited ability of the author, we will try our best to describe the first generation The process and principle of shelling and shelling, if there are any mistakes in this article, please correct me, thank you~
1. Dex loading process
In the process of daily analysis of the shelling point, the basic process of Dex loading should also be understood and familiarized
DexPathList: This class is mainly used to find the paths of Dex and SO libraries, and these paths form an array as a whole. Element
: Convert dexPath into a File list according to the multi-path separator ";", and record all dexFiles.
DexFile: Used to describe Dex Files, Dex loading and Class searching are all done by the class calling its native method
Let's analyze the source code in this process in turn
DexPathList
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
**********************
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
**********************
}
makeDexElements
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader) {
**********************
DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
**********************
}
loadDexFile
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
loadDex
static DexFile loadDex(String sourcePathName, String outputPathName,
int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
}
DexFile
/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
mCookie = openDexFile(fileName, null, 0, loader, elements);
mInternalCookie = mCookie;
mFileName = fileName;
//System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
}
The mCookie that appears here, mCookie is a pointer to DexFile in the C/C++ layer, we will explain it in detail below
openDexFile
private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) throws IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null)
? null
: new File(outputName).getAbsolutePath(),
flags,
loader,
elements);
}
Here we enter the C/C++ layer
openDexFileNative
In order to save space, we quickly analyze and pass some functions in the middle
OpenDexFilesFromOat()
MakeUpToDate()
GenerateOatFileNoChecks()
Dex2Oat()
Finally entered Dex2Oat, which entered the compilation process of Dex2Oat
Conversely, if we cause dex2oat to fail through Hook related methods or execv or execve in the following Dex2Oat process, we will return to OpenDexFilesFromOat
OpenDexFilesFromOat
It will try to load our Dex in HasOriginalDexFiles first , that is, if our shell blocks the compilation process of dex2oat, and then calls the Open function of DexFile.
DexFile::Open
Verify the magic word field of dex, and then call DexFile::OpenFile
DexFile::OpenFile
/art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::OpenFile(int fd,
const std::string& location,
bool verify,
bool verify_checksum,
std::string* error_msg) {
**************************************
std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
map->Size(),
location,
dex_header->checksum_,
kNoOatDexFile,
verify,
verify_checksum,
error_msg);
**************************************
}
OpenCommon
Finally, we return to the DexFile class again , where our analysis of the basic process of loading dex files is completed
2. Dex2Oat compilation process
Dex2oat is a mechanism used by Google to improve compilation efficiency. It has been implemented since Android 8.0. Some packers often disable Dex2oat when extracting shells, and Dex2Oat, which is not disabled for the overall packer, has also become an unpacking point.
Exec
/art/runtime/exec_utils.cc
bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
int status = ExecAndReturnCode(arg_vector, error_msg);
if (status != 0) {
const std::string command_line(android::base::Join(arg_vector, ' '));
*error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",
command_line.c_str());
return false;
}
return true;
}
ExecAndReturnCode
And we can disable Dex2Oat through Hook execv or execve, and if we do not disable dex2oat, the execve function is used to call the binary program of dex2oat to load the dex file. At this time, we find the file dex2oat.cc and find the main function
/art/dex2oat/dex2oat.cc
int main(int argc, char** argv) {
int result = static_cast<int>(art::Dex2oat(argc, argv));
if (!art::kIsDebugBuild && (RUNNING_ON_MEMORY_TOOL == 0)) {
_exit(result);
}
return result;
Here we call Dex2oat
Dex2Oat
/art/dex2oat/dex2oat.cc
static dex2oat::ReturnCode Dex2oat(int argc, char** argv) {
**************************************
dex2oat::ReturnCode setup_code = dex2oat->Setup();
dex2oat::ReturnCode result;
if (dex2oat->IsImage()) {
result = CompileImage(*dex2oat);
} else {
result = CompileApp(*dex2oat);
}
**************************************
}
In Dex2oat, the dex file will be compiled class by class and function by class, and the setup() function will complete the loading of dex
Then execute sequentially, you will enter CompileApp
During the compilation process, it will be compiled function by function, and it will enter CompileMethod
At this point, the basic process of Dex2oat is analyzed
3. Class loading process
To understand why DexFile is so important, first we have to clear the class loading process of Android APP. Android class loading is generally divided into two types: implicit loading and explicit loading
1.隐式加载:
(1)创建类的实例,也就是new一个对象
(2)访问某个类或接口的静态变量,或者对该静态变量赋值
(3)调用类的静态方法
(4)反射Class.forName("android.app.ActivityThread")
(5)初始化一个类的子类(会首先初始化子类的父类)
2.显示加载:
(1)使用LoadClass()加载
(2)使用forName()加载
Let's look at display loading in detail:
Class.forName 和 ClassLoader.loadClass加载有何不同:
(1)ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作)
(2)Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作
Let's take a closer look at the process in the class loading process:
java layer
We can find the key DexFile in class loading, which is used to describe Dex files, so our shelling object is DexFile
Here from DexFile to the Native layer, there is another key field is mCookie
We will introduce the function of mCookie in detail later
Let's further analyze and enter the Native layer
Native layer
/art/runtime/native/[dalvik_system_DexFile.cc
ConvertJavaArrayToDexFiles processed cookies
Through the analysis here, we can know that after mCooike is converted into a C/C++ layer pointer, it is the index of the dexfile
We continue to analyze DefineClass
art/runtime/class_linker.cc
mirror::Class* ClassLinker::DefineClass(Thread* self,
const char* descriptor,
size_t hash,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def) {
***************
LoadClass(self, *new_dex_file, *new_class_def, klass);
***************
}
LoadClass
art/runtime/class_linker.cc
void ClassLinker::LoadClass(Thread* self,
3120 const DexFile& dex_file,
3121 const DexFile::ClassDef& dex_class_def,
3122 Handle<mirror::Class> klass) {
3123 const uint8_t* class_data = dex_file.GetClassData(dex_class_def);
3124 if (class_data == nullptr) {
3125 return; // no fields or methods - for example a marker interface
3126 }
3127 LoadClassMembers(self, dex_file, class_data, klass);
3128}
LoadClassMembers
art/runtime/class_linker.cc
void ClassLinker::LoadClassMembers(Thread* self,
const DexFile& dex_file,
const uint8_t* class_data,
Handle<mirror::Class> klass) {
***************
LoadMethod(dex_file, it, klass, method);
LinkCode(this, method, oat_class_ptr, class_def_method_index);
***************
}
LoadMethod
art/runtime/class_linker.cc
void ClassLinker::LoadMethod(const DexFile& dex_file,
const ClassDataItemIterator& it,
Handle<mirror::Class> klass,
ArtMethod* dst) {
}
link code
We can find that here we entered the interpreter after the linkcode, and judged whether to perform dex2oat, we directly entered the interpreter to continue the analysis
We know that the Art interpreter is divided into two types: in the interpretation mode and in the quick mode, and we know that Android 8.0 starts dex2oat
如果壳没有禁用dex2oat,那类中的初始化函数运行在解释器模式下
如果壳禁用dex2oat,dex文件中的所有函数都运行在解释器模式下
则类的初始化函数运行在解释器模式下
Therefore, the general packer manufacturers will disable dex2oat, so that all functions can run in the interpretation mode, so some unpacking points are selected in the dex2oat process, which may not be used for the case of disabling dex2oat. Here we mainly focus on the overall Packing, so we won’t talk about it, and finally we learned that the interpreter will run under Execute
Execute
art/runtime/interpreter/interpreter.cc
static inline JValue Execute(
Thread* self,
const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame,
JValue result_register,
bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_){
***************
ArtMethod *method = shadow_frame.GetMethod();
***************
}
Here we have roughly analyzed the idea of class loading
4. DexFile Detailed Explanation
We have analyzed a lot before, and have a very detailed understanding of dex loading, class loading, etc. In the end, the core of everything is DexFile. DexFile is the focus of our unpacking. The ice boss is seeing the sun: Android APP The essence of unpacking and how to quickly find the unpacking point under ART mentioned that as long as the DexFile object is obtained under ART, then we can get the starting address and size of the dex file in memory, and then complete the unpacking.
Let's first look at some DexFile structures
As long as we can get the start address begin and size, we can successfully remove the dex file. Here we remember that DexFile contains a virtual function table, so according to the C++ layout, we need to offset a pointer and the DexFile class also provides us
with Convenient API
In this way, as long as we find a DexFile object in the function, we can further dump the dex file by calling the API, so according to the idea of Big Brother Ice, a large number of shelling points are generated.
(1) Direct search method
By directly searching for DexFile in the Android source code, we can obtain a large number of unpacking points
We can also get a lot of unpacking points by searching the DexFile exported by libart.so in IDA
(2) Indirect search method
Here is the first-level indirect method of obtaining the DexFile object to which the ArtMethod belongs through the getDexFile() of the ArtMethod object mentioned in the article by Mr. Ice. First, the ArtMethod is obtained through the getCurrentMethod() function of Thread or through the getMethod of ShadowFrame. Obtain the ArtMethod object, and then obtain the second-level indirect method of the DexFile to which the ArtMethod object belongs through getDexFile
getDexFile()
getMethod()
5. Detailed explanation of ArtMethod
We have analyzed the file structure of DexFile in detail above. We know that DexFile can be obtained through ArtMethod, so why mention ArtMethod separately, because ArtMethod plays an important role in extracting shells and VMP shells.
ArtMethod structure
We can obtain the offset and method index of codeitem through ArtMethod. Friends who are familiar with the dex structure know that codeitem is the actual value of the code, and codeitem plays a crucial address in the subsequent packing technology, and ArtMethod also has very rich methods , can help you achieve many functions, so it is also very important in the shelling work
Summarize
The above is what I’m going to talk about today. I mainly borrowed some ideas and articles from the boss of Walking with the Wind. The purpose of rewriting the article is to deepen the memory. It can be easily viewed in the future learning process~
references
https://security-kitchen.com/2022/12/04/Packer5/
https://bbs.kanxue.com/thread-254555.htm#msg_header_h2_2