[Fat Tiger's Reverse Road] 04——Principle of shelling (one-generation shell) & detailed explanation of related concepts of shelling

[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



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
insert image description here

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

insert image description here

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

insert image description here

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

insert image description here

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
insert image description here

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.

insert image description here
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

insert image description here

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

insert image description here

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加载有何不同:
(1ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作)2Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作

Let's take a closer look at the process in the class loading process:

java layer

insert image description here

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

insert image description here
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

insert image description here

ConvertJavaArrayToDexFiles processed cookies

insert image description here
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

insert image description here

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

insert image description here
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
insert image description here
with Convenient API
insert image description here

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

insert image description here
We can also get a lot of unpacking points by searching the DexFile exported by libart.so in IDA

insert image description here

(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
insert image description here

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

Guess you like

Origin blog.csdn.net/a_Chaon/article/details/128807183