Apprentice's Analysis of Android Development-Confusion of SO Files

Keywords: Attribute Windows SDK Java

SharedObject serves the same function as. dll (dynamic link library) files in windows environment. When we refer to third-party SDK, we will also encounter the need to call the corresponding. so files. So files are more capable of integrating common processing methods, of course, they will also be used to save important data information.

Local obfuscation has always been an effective method for compiling and decompiling applications. For. so files, it is also applicable to confusion. Although the. so files are assembled data when they are opened by using the cracking tool IDA, there are still rules to follow, which do not hinder the reading of professionals. Therefore, it is necessary to master the confusion of. so files.

The confusion of so files starts from two aspects:

1: Method name confusion

2: Data confusion

Method name confusion

When we create the. c and. h files needed to build. so files, NDK helps us automatically generate the native method name, which is formatted and is also the index name in the index table. Because of its particularity, IDA browsing can be quickly viewed through the index table. For example, we create a TestSimple in which we declare a native method showDsc(), and the final method name is:
      Java_com_example_xx_TestSimple_showDsc

The generated. so file is decompiled by IDA and can be clearly seen in the function index table:



Fortunately, NDK provides a way to modify and help us hide such prominent names. First of all, there are two basic methods:
1. Simplify the method name as follows:

Continue to use TestSimple as an example to declare

#defineJNIFUNCTION_NATIVE(sig)  Java_com_example_xx_TestSimple_##sig

Remove the path in front of the function name and display only the function name to avoid appearing direct.

  

 JNIEXPORT void JNICALL JNIFUCTION_NATIVE (showMath(JNIEnv *env, jobject obj));

This method can only be used to simplify the method name and is useful in editing, but it has no effect on hiding in cracking. After modification, it can still be seen in IDA:


 

2. Replace the section index name with the following method:

__attribute__((section(".XXXX")))

Where XXXX represents a custom index name, the method needs to be declared before the specified function name, for example:

__attribute__((section (".xxxsimple")))  JNICALL jstring show(JNIEnv *env, jclass obj){
         return realShow(env,obj);
}


The actual results are as follows:

 

By changing the attribute type, this method affects the parsing order of relevant code segments after cracking.

 

The method name confusion of NDK is to mix 1 and 2 methods by cooperating with RegisterNatives() registration. RegisterNatives() can implement local methods associated with classes. This operation must be completed before invocation, that is, in the process of Library loading. It is usually done with the function JNI_Onload. RegisterNatives() is used to correlate the actual execution function from the declared class function to the cost function to achieve the hiding effect. The calling steps are as follows:

 

Step one:

Declaring the method set to be replaced, JNINativeMethod, is a method type set.

//Pointer operation, pointing the java layer showDsc function to the Native layer show() function
static JNINativeMethodgetMethods[] = {
        {"showDsc","()Ljava/lang/String;",(void*)show},
};

The structure of JNINativeMethod is defined as follows:

typedef struct{
   char *name;// Name of function to be pointed to
   char *signature;// Types of Pointing Functions and Formal Parameters
   void *fnPtr//Function Pointer, Actual Execution Function
}

 

Declare the class name to be registered

#define JNIREG_CLASS"com/example/xx/Test"

Step two:

Mark the custom attribute name before actually executing the function

__attribute__((section(".xxxfirst"))) JNICALL jstring show(JNIEnv *env, jclass obj){
        return realShow(env,obj);
}

Step three:

Actually perform the function to complete the operation.

jstringrealShow(JNIEnv *env, jclass obj){
     return (*env)-> NewStringUTF(env,"10086");
}

Step four:

Execute RegisterNatives(), registering the methods associated with the specified class

static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, getMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

Step 5: Register the specified class with the specified method name. If the registration is successful, it returns 0, but fails 1.

static intregisterNatives(JNIEnv* env)
{
if(!registerNativeMethods(env,JNIREG_CLASS,
getMethods, sizeof(getMethods) /sizeof(getMethods[0])))
{
           return JNI_FALSE;
}
    return JNI_TRUE;
}



 

Step six:

Perform registration in JNI_OnLoad

JNIEXPORT jintJNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
jint result = -1;
    if ((*vm)->GetEnv(vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) {//GetEnv() to get a JNIEnv global object
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {
        return -1;
    }
    result = JNI_VERSION_1_4;
    return result;
}



Once the build is successful, we can see that the name of the called function cannot be found in the method name index table. Only when you find a custom section area can you do the next parsing.

 

data obfuscation

Usually, the method set is placed in the. so library, but if there is text content, data confusion needs to be considered. Once the text data is set in the way of declarative constants, the data will be preserved completely by the basic type in the use process. After the data is cracked, it will appear in front of the cracker directly. This setting allows the assembler to point to a data source, not a pointer address, when directing an address. If we provide textual data to the outside world, we use the method of array splicing to connect the data, pointing to the array to get a pointer address, so that the cracker can see the address tag rather than the direct data source. The following is a comparison between the two groups.

Direct text output:

JNIEXPORT jstring JNICALLJava_com_example_xx_Test_showC
(JNIEnv *env, jobject obj){
      charsha[100];
              nstrcpy(sha,H, A, C, E, J, F, NULL);
      return(*env)-> NewStringUTF(env, sha);
}



In the form of array combinations:

JNIEXPORT jstring JNICALLJava_com_example_xx_Test_showB
(JNIEnv *env, jobject obj){
          char str[15];
          str[0]= O[1];
          str[1]= N[4];
          str[2]= P[0];
          str[3]= N[2];
          str[4]= D[0];
          str[5]= Q[0];
          str[6]= Q[2];
          str[7]= O[1];
          str[8]= D[0];
          str[9]= P[3];
          str[10]= '\0';
      return(*env)-> NewStringUTF(env, str);
}

 

Finally, let's say that there is no protection that can't be cracked, only the people who can't be cracked. No matter how the security software organizations publicize it, they can't be understood when they meet the interested people. We are only increasing the difficulty of cracking when we develop it.

Posted by cwiddowson on Thu, 21 Mar 2019 14:57:52 -0700