[Yugong Series] May 2023 Attack and Defense World-MOBILE (LoopCrypto)


foreword

1. Tool introduction

Here are two decompilation tools

  • jadx is an open source tool for decompiling Android APK files, static decompilation, powerful search index
  • Jeb is very similar to IDA. It belongs to dynamic debugging. You can watch java assembly and generate pseudo code, and you can also dynamically attach to the target for debugging.

Reverse tool selection for so files

  • The IDA reverse tool is a disassembler, which is widely used in the field of software reverse engineering. It can disassemble binary program codes of various platforms and restore them to readable assembly codes.

Objection is a mobile device runtime exploit tool, powered by Frida, that can help researchers access mobile applications and assess the security of mobile applications without jailbreaking or rooting.

install command

pip3 install objection 

Frida is a portable, free, and platform-wide hook framework that can interact with frida_server by writing JavaScript and Python codes

The installation of frida can refer to: https://www.jianshu.com/p/60cfd3f6afde

2. What is fork

After fork, the operating system will copy a child process that is exactly the same as the parent process. Although it is a father-son relationship, from the perspective of the operating system, they are more like brothers. These two processes share code space, but the data space is independent of each other. , the content in the data space of the child process is a complete copy of the parent process, and the instruction pointer is exactly the same, but there is only one difference. If the fork succeeds, the return value of fork in the child process is 0, and the return value of fork in the parent process is the child process The process ID, if the fork is unsuccessful, the parent process will return an error.
It can be imagined that the two processes have been running at the same time, and they are in the same step. After the fork, they do different jobs respectively, that is, fork. This is why fork is called fork.

1. LoopCrypto

1. Topic

insert image description here

2. Answer questions

2.1 java layer analysis

1. Use jdax to load the apk. First, check the main activity of the APP.

package com.a.sample.loopcrypto;

import android.os.Bundle;
import android.support.v7.app.c;
import android.widget.Button;
import android.widget.EditText;

/* loaded from: classes.dex */
public class MainActivity extends c {
    
    
    /* JADX INFO: Access modifiers changed from: protected */
    @Override // android.support.v7.app.c, android.support.v4.a.l, android.support.v4.a.h, android.app.Activity
    public void onCreate(Bundle bundle) {
    
    
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setText(Decode.a(new byte[]{
    
    78, -65, 73, -45, 103}, 116));
        EditText editText = (EditText) findViewById(R.id.editText);
        editText.setHint(Decode.a(new byte[]{
    
    -72, -55, 35, -43, -108, -108, 93, -1, -91, 92, -39, -30, 44, 110, -127}, 170));
        button.setOnClickListener(new a(editText));
    }
}

insert image description here
MainActivity only initializes the control, and it can be seen that the displayed strings are all decrypted by the Decode.a() method.

2. Enter the onClick method

package com.a.sample.loopcrypto;

import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/* loaded from: classes.dex */
public class a implements View.OnClickListener {
    
    
    private EditText a;

    /* JADX INFO: Access modifiers changed from: package-private */
    public a(EditText editText) {
    
    
        this.a = editText;
    }

    @Override // android.view.View.OnClickListener
    public void onClick(View view) {
    
    
        String str;
        try {
    
    
            Signature[] signatureArr = view.getContext().getPackageManager().getPackageInfo("com.a.sample.loopcrypto", 64).signatures;
            MessageDigest instance = MessageDigest.getInstance("MD5");
            for (Signature signature : signatureArr) {
    
    
                instance.update(signature.toByteArray());
            }
            byte[] digest = instance.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
    
    
                int i = b & 255;
                if (i < 16) {
    
    
                    sb.append("0");
                }
                sb.append(Integer.toHexString(i));
            }
            str = sb.toString();
        } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
    
    
            str = "";
        }
        Toast.makeText(view.getContext(), new Decode().check(this.a.getText().toString(), str), 1).show();
    }
}

insert image description here
Class a first obtains the signature of the APP, and then performs md5 calculation on the signature. After calculation, both the user's input and signature md5 are passed into the new Decode().check() method. Then directly use Toast to prompt the return result of the check() method.

3. Decode class

package com.a.sample.loopcrypto;

import java.io.UnsupportedEncodingException;

/* loaded from: classes.dex */
public class Decode {
    
    
    static {
    
    
        System.loadLibrary(a(new byte[]{
    
    46, 1, -100, -4, -87}, 168));
    }

    public static String a(byte[] bArr, int i) {
    
    
        try {
    
    
            return new String(a(bArr, (long) i), "UTF-8");
        } catch (UnsupportedEncodingException e) {
    
    
            return new String(new byte[0]);
        }
    }

    public static byte[] a(byte[] bArr, long j) {
    
    
        for (int i = 0; ((long) i) < j; i++) {
    
    
            for (int i2 = 0; i2 < bArr.length; i2++) {
    
    
                bArr[i2] = (byte) (((bArr[i2] >> 4) & 15) + ((bArr[i2] & 15) << 4));
            }
            for (int length = bArr.length - 1; length >= 0; length--) {
    
    
                if (length != 0) {
    
    
                    bArr[length] = (byte) (bArr[length] ^ bArr[length - 1]);
                } else {
    
    
                    bArr[length] = (byte) (bArr[length] ^ bArr[bArr.length - 1]);
                }
                bArr[length] = (byte) (bArr[length] ^ 150);
            }
            for (int length2 = bArr.length - 1; length2 >= 0; length2--) {
    
    
                if (length2 != 0) {
    
    
                    bArr[length2] = (byte) (bArr[length2] - bArr[length2 - 1]);
                } else {
    
    
                    bArr[length2] = (byte) (bArr[length2] - bArr[bArr.length - 1]);
                }
                bArr[length2] = (byte) (bArr[length2] - 58);
            }
        }
        return bArr;
    }

    public native String check(String str, String str2);
}

insert image description here
The flow of the method is as follows:

  1. r21-r23: eight bits per byte, the first four bits are swapped with the last four bits
  2. r24-r31: The value of each bit of the array is equal to the value obtained by XORing the previous bit
  3. r32-r39: Each bit of the array is looped to subtract the previous bit, and subtract 58 at the same time

4. Copy java code for encryption operation

public class App 
{
    
    

    public static String a(byte[] bArr, int i) {
    
    
        try {
    
    
            return new String(a(bArr, (long)i), "UTF-8");
        } catch (UnsupportedEncodingException e) {
    
    
            return new String(new byte[0]);
        }
    }

    public static byte[] a(byte[] bArr, long j) {
    
    
        for (int i = 0; i < j; i++) {
    
    
            for (int i2 = 0; i2 < bArr.length; i2++) {
    
    
                bArr[i2] = (byte) (((bArr[i2] >> 4) & 15) + ((bArr[i2] & 15) << 4));
            }
            for (int length = bArr.length - 1; length >= 0; length--) {
    
    
                if (length != 0) {
    
    
                    bArr[length] = (byte) (bArr[length] ^ bArr[length - 1]);
                } else {
    
    
                    bArr[length] = (byte) (bArr[length] ^ bArr[bArr.length - 1]);
                }
                bArr[length] = (byte) (bArr[length] ^ 150);
            }
            for (int length2 = bArr.length - 1; length2 >= 0; length2--) {
    
    
                if (length2 != 0) {
    
    
                    bArr[length2] = (byte) (bArr[length2] - bArr[length2 - 1]);
                } else {
    
    
                    bArr[length2] = (byte) (bArr[length2] - bArr[bArr.length - 1]);
                }
                bArr[length2] = (byte) (bArr[length2] - 58);
            }
        }
        return bArr;
    }

    public static void main( String[] args )
    {
    
    
        System.out.println("button.setText(\""+a(new byte[]{
    
    78, -65, 73, -45, 103}, 116)+"\");");
        System.out.println("editText.setHint(\""+a(new byte[]{
    
    -72, -55, 35, -43, -108, -108, 93, -1, -91, 92, -39, -30, 44, 110, -127}, 170)+"\");");
        System.out.println("System.loadLibrary(\""+a(new byte[]{
    
    46, 1, -100, -4, -87}, 168)+"\");");
    }
}

insert image description here
The result of the operation is as follows:

button.setText("Check");
editText.setHint("Input your flag");
System.loadLibrary("check");

5. Use Frida to display, the relevant hook code is as follows:

function main() {
    
    
    Java.perform(function () {
    
    
        Java.use("com.a.sample.loopcrypto.Decode").a.overload("[B", "long").implementation = function (b, i) {
    
    
            var result = this.a(b, i);
            console.log(Java.use("java.lang.String").$new(result));
            return result;
        };
    });
}

setImmediate(main);

The start command and output are as follows:

$ frida -H 192.168.0.102:8888 com.a.sample.loopcrypto -l loopcrypto.js

[Remote::com.a.sample.loopcrypto]-> Check
Input your flag

Of course, Frida cannot be used for hooking at this time, because the program adds detection at the so layer

2.2 Analysis of so layer

We start the analysis of so, decompress the APK, find its lib/armeabi-v7a/libcheck.so file, and load it with IDA32.

2.2.1 JNI_OnLoad

When you get the so file, you must first look at its function table to see if the native method is statically registered, but this question is obviously not, so look for the JNI_OnLoad function.

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    
    
  jint v2; // r4
  int v4; // [sp+0h] [bp-10h] BYREF

  v2 = 65542;
  if ( (*vm)->GetEnv(vm, (void **)&v4, 65542) )
    return -1;
  if ( !sub_88C4(v4) )
    return -1;
  return v2;
}

insert image description here
This function is relatively simple. After obtaining JNIEnv, pass the env to the sub_88C4 function, and continue to follow the function.

After redefining the variable name and type of the function as follows:

bool __fastcall sub_88C4(JNIEnv *env)
{
    
    
  jclass v2; // r1
  _DWORD method[3]; // [sp+8h] [bp-19Ch] BYREF
  char arg[128]; // [sp+14h] [bp-190h] BYREF
  char methodName[128]; // [sp+94h] [bp-110h] BYREF
  char className[128]; // [sp+114h] [bp-90h] BYREF

  decode_str((int)env, (int)&unk_BD9B, 30, 87, className);
  decode_str((int)env, (int)&unk_BDBA, 5, 122, methodName);
  decode_str((int)env, (int)&unk_BDC0, 56, 49, arg);
  method[0] = methodName;
  method[1] = arg;
  method[2] = sub_87FC;                         // 要注册的check函数地址
  v2 = (*env)->FindClass(env, className);
  return v2 && (*env)->RegisterNatives(env, v2, (const JNINativeMethod *)method, 1) >= 0;
}

The decode_str() function is as follows

char *__fastcall decode_str(JNIEnv *env, char data, int data_len, int arg2, char *result)
{
    
    
  jclass clazz; // r0
  void *clazz_; // r6
  _jmethodID *aID; // r5
  char *v12; // r11
  jbyteArray arg1_; // r8
  jobject v14; // r5
  const char *v15; // r6
  int arg2_; // [sp+8h] [bp-10h]

  clazz = (*env)->FindClass(env, "com/a/sample/loopcrypto/Decode");
  clazz_ = clazz;
  if ( !clazz )
    return 0;
  aID = (*env)->GetStaticMethodID(env, clazz, "a", "([BI)Ljava/lang/String;");
  if ( !aID )
    return 0;
  v12 = result;
  arg2_ = arg2;
  arg1_ = (*env)->NewByteArray(env, data_len);
  (*env)->SetByteArrayRegion(env, arg1_, 0, data_len, (const jbyte *)data);
  v14 = (*env)->CallStaticObjectMethod(env, clazz_, aID, arg1_, arg2_);// 执行Decode.a方法进行解密
  v15 = (*env)->GetStringUTFChars(env, v14, 0); // 从jstring中获取char字符数组
  strcpy(result, v15);
  (*env)->ReleaseStringUTFChars(env, v14, v15);
  return v12;                                   // 解密后字符串数组返回
}

insert image description here
It can be seen that the Decode.a() method is used to reflect and call the Decode.a() method for decryption through the JNI method. After deducting the incoming parameter data, you can use the Java code above to decrypt it. The code is as follows:

System.out.println(a(new byte[]{
    
    
                0x29, (byte) 0xCF, 0x0B, 0x1E, (byte) 0xDC, (byte) 0xD2, 0x35, (byte) 0xA0, 0x0F, (byte) 0xB1, 0x47, (byte) 0x86, (byte) 0xEA, (byte) 0x90, (byte) 0xE7, (byte) 0x90,
                (byte) 0xDC, 0x30, (byte) 0xE8, (byte) 0x8C, 0x4F, 0x5A, 0x3F, 0x21, (byte) 0x9C, (byte) 0xA8, 0x04, 0x1E, 0x2C, 0x4A
        },87));
        System.out.println(a(new byte[]{
    
    
                0x02, (byte) 0xA1, (byte) 0xE7, 0x0B, (byte) 0xEB
        },122));
        System.out.println(a(new byte[]{
    
    
                (byte) 0xC2, (byte) 0x94, 0x1E, 0x6D, (byte) 0xA6, 0x6E, (byte) 0xF3, 0x3B, (byte) 0xCA, 0x54, (byte) 0xFB, (byte) 0xB2, 0x24, (byte) 0x9F, 0x58, (byte) 0xE3,
                (byte) 0xCF, 0x23, 0x5B, 0x13, 0x4A, (byte) 0x96, 0x01, (byte) 0x89, (byte) 0x9A, (byte) 0x87, (byte) 0x91, 0x17, 0x6B, (byte) 0xD1, 0x3A, (byte) 0xD6,
                (byte) 0xE6, 0x3F, (byte) 0xB0, 0x3E, (byte) 0xAD, 0x0C, 0x05, 0x7A, 0x08, (byte) 0xE2, 0x49, (byte) 0xEC, (byte) 0xA8, (byte) 0x90, 0x14, (byte) 0xAB,
                (byte) 0xD2, (byte) 0xE1, 0x25, (byte) 0x8D, (byte) 0xEE, (byte) 0xD4, 0x64, (byte) 0x84
        },49));

The resulting output is as follows:

com/a/sample/loopcrypto/Decode
check
(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

If you encounter complex dynamic registration, we can use https://github.com/lasting-yang/frida_hook_libartthe hook_RegisterNatives.js script in the script to directly obtain the address of the dynamic registration through the hook RegisterNatives function.
insert image description here

2.2.2 Initialization and anti-debugging of init so

If we want to use Frida HOOK or IDA dynamic debugging, there will be errors. When using the System.loadLibrary() method to load a so file, we also need to check whether the so has initialization functions. These initialization functions will be called and executed by Linker. It is used to complete initialization operations such as decryption.

Return to IDA, use the ctrl+s shortcut to view the section of the so file, and you can see that there is a .init_array section.
insert image description here
When Linker loads so, it will execute every function in this section. Double-click to enter, and you can see that there is indeed an initialization function. .
insert image description here
Double-click to enter the function, F5 to view the pseudo-code, as shown in the figure below.
insert image description here
insert image description here
When debugging an Android application, it is necessary to call ptrace(PTRACE_TRACEME) to attach the process. ptrace will fail if the process has already been traced. This can be used for anti-debugging.

The program first decrypts the string, then forks a child process, and then uses ptrace to attach itself to prevent being debugged. After that, read the /proc/%d/status file every 2 seconds, and check the TracerPid field to determine whether it is attached.

The script to decrypt the string is as follows:

void decode_init_str() {
    
    
    unsigned char format[128] = {
    
    
            0xC6, 0x99, 0x9B, 0x86, 0x8A, 0xC6, 0xCC, 0x8D, 0xC6, 0x9A, 0x9D, 0x88, 0x9D, 0x9C, 0x9A, 0xE9,
            0x9B, 0xE9, 0xBD, 0x9B, 0x88, 0x8A, 0x8C, 0x9B, 0xB9, 0x80, 0x8D, 0xE9, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    format[0] = 47;
    int i;
    for ( i = 1; i != 128; ++i )
        format[i] ^= 0xE9u;
    std::cout<<format<<std::endl;
    std::cout<<&format[16]<<std::endl;
    std::cout<<&format[18]<<std::endl;
}

The output is as follows:

/proc/%d/status
r
TracerPid

For the function initialized by Frida Hook, we have briefly analyzed the loading process of so above, so we can hook the call_function function in the linker. When it is detected that the initialization function is to be executed, the function will not be executed.

The Frida script code is as follows:

function hook_init() {
    
    
    // 寻找linker
    var linker = Process.findModuleByName("linker");
    var call_function_addr = null;
    // 遍历linker的符号
    var symbols = linker.enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
    
    
        // 取到名称
        var name = symbols[i].name;
        // 如果名称是call_function,则得到该地址并返回
        if (name.indexOf("call_function") >= 0) {
    
    
            call_function_addr = symbols[i].address;
            break;
        }
    }
    console.log("call_function_addr->", call_function_addr);
    // 来附加call_function函数
    Interceptor.attach(call_function_addr,{
    
    
        onEnter:function(args){
    
    
            // 进入函数的时候判断要执行的函数地址末尾是否是3dd
            if(String(args[1]).indexOf("3dd")>=0){
    
    
                console.log("found");
                // 如果根据函数地址找到了初始化函数,则对其进行hook,置空处理
                Interceptor.replace(
                    args[1],
                    new NativeCallback(
                        function (s, addr, rp) {
    
    
                            console.log("destory")
                        },
                        "int",
                        []
                    )
                );
            }
        },
        onLeave: function(retval){
    
    

        }
    })
    
}

setImmediate(hook_init);

The command and output are as follows:

$ frida -H 192.168.0.102:8888 -f com.a.sample.loopcrypto -l loopcrypto.js


Spawning `com.a.sample.loopcrypto`...                                   
call_function_addr-> 0xe7d1453d
Spawned `com.a.sample.loopcrypto`. Use %resume to let the main thread start executing!
[Remote::com.a.sample.loopcrypto]-> %resume
[Remote::com.a.sample.loopcrypto]-> found
destory

After using the hook to stop the execution of the initialization function, it is now possible to enter the app with Frida attached.

Of course, we can also use the IDA patch method, but it is too troublesome compared to Frida.

2.2.3 check function execution

After bypassing the anti-debugging of the program and using Frida as you like, let's analyze the check function.

jstring __fastcall check(JNIEnv *a1, jobject a2, jstring a3, jstring a4)
{
    
    
  const char *v7; // r5
  const char *v8; // r6
  char v10[256]; // [sp+4h] [bp-110h] BYREF

  v7 = (*a1)->GetStringUTFChars(a1, a3, 0);
  v8 = (*a1)->GetStringUTFChars(a1, a4, 0);
  sub_8690(v7, v8, v10);
  (*a1)->ReleaseStringUTFChars(a1, a3, v7);
  (*a1)->ReleaseStringUTFChars(a1, a4, v8);
  return (*a1)->NewStringUTF(a1, v10);
}

insert image description here
After the function gets the string array from the two parameters, it is passed into the check_ function. Continue to follow up and check.

insert image description here
insert image description here
In this function, a pipeline is defined to transmit data, and then a sub-process is forked, and the md5 signature of the APK is passed in to the sub-process to perform dynamic decryption of the code. It can be seen that the decryption depends on the correct APK signature. If the signature is incorrect, It will prompt us to change the signature, which is a precautionary measure to prevent APK from being repackaged. Of course, we have not repackaged the APK here.

After the code is dynamically decrypted, on line 22, the flag and pipeline input by the user are passed in to perform the corresponding flag verification operation.

The main process reads the result of the subprocess from the pipeline, and returns the prompt result string array.

In the sub_85E0 function, the data and the signature are XORed first, and then the finally decrypted function code is obtained through the sub_84D0 function.

Then enter the some_opt (sub_85E0) function
insert image description here
and follow up with the sub_84D0 function. Although there is a string "1.2.11" as a reminder, unless we are experienced, it is difficult for us to link it with zlib1.2.11 decompression.

int __fastcall sub_84D0(char *a1, int *a2, int a3, int *a4)
{
    
    
  int v7; // r4
  int v8; // r11
  int v9; // r8
  int v10; // r6
  int v11; // r0
  int v13; // [sp+4h] [bp-4Ch] BYREF
  int v14; // [sp+8h] [bp-48h]
  char *v15; // [sp+10h] [bp-40h]
  int i; // [sp+14h] [bp-3Ch]
  int v17; // [sp+18h] [bp-38h]
  int v18; // [sp+24h] [bp-2Ch]
  int v19; // [sp+28h] [bp-28h]
  int v20; // [sp+2Ch] [bp-24h]
  char v21; // [sp+3Fh] [bp-11h] BYREF

  v7 = *a2;
  v8 = *a4;
  if ( *a2 )
  {
    
    
    *a2 = 0;
  }
  else
  {
    
    
    v7 = 1;
    a1 = &v21;
  }
  v13 = a3;
  v9 = 0;
  v14 = 0;
  v18 = 0;
  v19 = 0;
  v20 = 0;
  v10 = sub_6808(&v13, -15, "1.2.11", 56);
  if ( !v10 )
  {
    
    
    v15 = a1;
    for ( i = 0; ; v9 = i )
    {
    
    
      if ( !v9 )
      {
    
    
        i = v7;
        v7 = 0;
      }
      if ( !v14 )
      {
    
    
        v14 = v8;
        v8 = 0;
      }
      v10 = sub_68EC(&v13, 0);
      if ( v10 )
        break;
    }
    *a4 -= v14 + v8;
    if ( a1 == &v21 )
    {
    
    
      v11 = v7;
      if ( v17 )
        v11 = 1;
      if ( v10 == -5 )
        v7 = v11;
    }
    else
    {
    
    
      *a2 = v17;
    }
    sub_7C22(&v13);
    if ( v10 == 1 )
    {
    
    
      return 0;
    }
    else if ( v10 == 2 )
    {
    
    
      return -3;
    }
    else if ( v10 == -5 && v7 + i )
    {
    
    
      return -3;
    }
  }
  return v10;
}

insert image description here
At this point, we can use Frida to hook to obtain the decryption result of this function. It should be noted that this function runs in a subprocess, so we need to attach to the subprocess to hook the result.

On the premise of running the Frida script that removed the anti-debugging before, write the following python script and run it

import frida

device = frida.get_device_manager().add_remote_device("192.168.0.102:8888")
def on_spawned(spawn):
    print('on_spawned:', spawn)
    # 附加子进程的pid
    session1 = device.attach(spawn.pid)
    # 编写脚本hook
    script1 = session1.create_script("""
    var libcheck_addr =  Module.findBaseAddress("libcheck.so");
    console.log("libcheck_addr",libcheck_addr)
    // 对代码解密函数进行hook,thumb模式需要+1
    Interceptor.attach(libcheck_addr.add(0x85e0+1),{
        onEnter:function(args){
            console.log("0x85e0 onEnter")
            // var buffer = Memory.readByteArray(libcheck_addr.add(0xF1B0), 623);
            // console.log(buffer)
        }
        ,
        onLeave:function(retval){
            console.log("0x85e0 onLeave",retval)
            var buffer = Memory.readByteArray(retval, 0x270);
            
            console.log(buffer)
    
        }
    })
""").load()
    # 唤醒子进程
    device.resume(spawn.pid)
    
# 添加创建子进程回调
device.on('child-added', on_spawned)

session = device.attach("com.a.sample.loopcrypto")
# 开启子进程控制模式
session.enable_child_gating()

with open("./loopcrypto.js") as f:
    # 创建一个新脚本
    script = session.create_script(f.read())

# 加载脚本
script.load()

command = ""
while True:
    command = input("Enter `n` for leave: ")
    if command == "n":
        break

After clicking the button to actively execute the check function, the running result is shown in the figure below.

insert image description here
It can be seen that it has successfully printed the decrypted function code data.

Write an idapython script to write this segment of data into the original data segment. Although it will damage the file, it is convenient for us to perform static analysis.

import idc

addr = 0xF1B0
func = [0x2d, 0xe9, 0xf0, 0x4f, 0xad, 0xb0, 0x44, 0xf6, 0xa4, 0x61, 0x47, 0xf2, 0x7b, 0x4b, 0xc2, 0xf2,
0xbb, 0x61, 0x46, 0xf6, 0x66, 0x4e, 0x23, 0x91, 0x4d, 0xf6, 0x43, 0x41, 0xc6, 0xf6, 0xbb, 0x11,
0x46, 0xf6, 0x5f, 0x6a, 0x24, 0x91, 0x42, 0xf6, 0x5c, 0x71, 0xcd, 0xf6, 0x3f, 0x31, 0x03, 0xaa,
0x25, 0x91, 0x42, 0xf6, 0x9f, 0x71, 0xc7, 0xf6, 0xbd, 0x31, 0xc6, 0xf6, 0x48, 0x1b, 0x26, 0x91,
0x4f, 0xf6, 0x31, 0x61, 0xc6, 0xf2, 0xcd, 0x51, 0xc6, 0xf2, 0x61, 0x7e, 0x27, 0x91, 0x4a, 0xf6,
0xc4, 0x61, 0xc3, 0xf6, 0x91, 0x61, 0xc3, 0xf2, 0x25, 0x7a, 0x28, 0x91, 0x4a, 0xf6, 0x94, 0x71,
0xce, 0xf2, 0x03, 0x71, 0x29, 0x91, 0x4e, 0xf2, 0xf4, 0x11, 0xcc, 0xf2, 0xd1, 0x71, 0x2a, 0x91,
0x49, 0xf2, 0x20, 0x21, 0xc8, 0xf2, 0x1a, 0x21, 0x19, 0x91, 0x44, 0xf2, 0xf3, 0x31, 0xc9, 0xf2,
0xcd, 0x61, 0x1a, 0x91, 0x48, 0xf2, 0xba, 0x11, 0xc3, 0xf2, 0xd4, 0x61, 0x1b, 0x91, 0x40, 0xf2,
0x6d, 0x71, 0xcc, 0xf6, 0x2c, 0x31, 0x1c, 0x91, 0x45, 0xf2, 0x68, 0x71, 0xc0, 0xf6, 0xc6, 0x51,
0x1d, 0x91, 0x4d, 0xf6, 0x5d, 0x61, 0xcb, 0xf6, 0xb4, 0x21, 0x1e, 0x91, 0x49, 0xf6, 0x0f, 0x01,
0xc7, 0xf2, 0x71, 0x51, 0x1f, 0x91, 0x4e, 0xf6, 0xc7, 0x41, 0xce, 0xf2, 0x82, 0x31, 0x20, 0x91,
0x4e, 0xf6, 0xe0, 0x01, 0xcc, 0xf2, 0x33, 0x71, 0x21, 0x91, 0x42, 0xf6, 0x26, 0x71, 0xc7, 0xf2,
0x81, 0x41, 0x22, 0x91, 0x45, 0xf6, 0xa0, 0x51, 0xc1, 0xf6, 0xa0, 0x51, 0x0f, 0x91, 0x4e, 0xf2,
0x28, 0x31, 0xc0, 0xf6, 0x07, 0x31, 0x10, 0x91, 0x4d, 0xf2, 0x78, 0x71, 0xcb, 0xf2, 0x72, 0x51,
0x11, 0x91, 0x42, 0xf2, 0xbb, 0x71, 0xcc, 0xf2, 0xee, 0x31, 0x12, 0x91, 0x45, 0xf2, 0x1b, 0x41,
0xcd, 0xf2, 0x00, 0x21, 0x13, 0x91, 0x4c, 0xf6, 0xe0, 0x01, 0xca, 0xf2, 0x95, 0x51, 0x14, 0x91,
0x4a, 0xf2, 0xc8, 0x51, 0xc4, 0xf2, 0xb3, 0x41, 0x15, 0x91, 0x41, 0xf2, 0x04, 0x61, 0xc2, 0xf2,
0xf1, 0x11, 0x16, 0x91, 0x4b, 0xf6, 0x82, 0x21, 0xcd, 0xf6, 0x1e, 0x51, 0x17, 0x91, 0x4f, 0xf2,
0x9c, 0x11, 0xcd, 0xf2, 0xcb, 0x41, 0x18, 0x91, 0x00, 0x21, 0xd0, 0xe9, 0x00, 0x40, 0x00, 0x90,
0x45, 0xf6, 0x53, 0x70, 0xc2, 0xf2, 0x21, 0x40, 0x65, 0x5c, 0x1d, 0xb1, 0x55, 0x54, 0x01, 0x31,
0x27, 0x29, 0xf9, 0xd3, 0x00, 0x24, 0x54, 0x54, 0x01, 0x31, 0x01, 0x91, 0x43, 0xd0, 0x47, 0xf6,
0xb9, 0x16, 0x4f, 0xf0, 0x00, 0x09, 0xc9, 0xf6, 0x37, 0x66, 0x02, 0xeb, 0x09, 0x01, 0x52, 0xf8,
0x09, 0x50, 0x02, 0x91, 0x4f, 0x68, 0x20, 0x24, 0x31, 0x46, 0x01, 0xeb, 0x07, 0x0c, 0x0e, 0xeb,
0x07, 0x18, 0x88, 0xea, 0x0c, 0x02, 0x0b, 0xeb, 0x57, 0x13, 0x5a, 0x40, 0x01, 0x3c, 0x15, 0x44,
0x01, 0xeb, 0x05, 0x02, 0x31, 0x44, 0x00, 0xeb, 0x05, 0x13, 0x82, 0xea, 0x03, 0x02, 0x0a, 0xeb,
0x55, 0x13, 0x82, 0xea, 0x03, 0x02, 0x17, 0x44, 0xe7, 0xd1, 0x02, 0x99, 0x03, 0xaa, 0x42, 0xf8,
0x09, 0x50, 0x09, 0xf1, 0x08, 0x09, 0x4f, 0x60, 0x01, 0x99, 0x89, 0x45, 0xd5, 0xd3, 0x01, 0x99,
0x89, 0xb1, 0x9d, 0xf8, 0x0c, 0x10, 0xa4, 0x29, 0x0a, 0xd1, 0x23, 0xa9, 0x01, 0x24, 0x01, 0x9b,
0x9c, 0x42, 0x08, 0xd2, 0x66, 0x1c, 0x0b, 0x5d, 0x17, 0x5d, 0x34, 0x46, 0x9f, 0x42, 0xf6, 0xd0,
0x0d, 0xf1, 0x3c, 0x0c, 0x06, 0xe0, 0x01, 0x9a, 0x0f, 0xa9, 0x0d, 0xf1, 0x64, 0x0c, 0x00, 0x2a,
0x08, 0xbf, 0x8c, 0x46, 0x48, 0xf2, 0x47, 0x64, 0x4f, 0xf0, 0x00, 0x09, 0xc6, 0xf2, 0xc8, 0x14,
0x0c, 0xeb, 0x09, 0x08, 0x5c, 0xf8, 0x09, 0x70, 0x43, 0xf2, 0x20, 0x75, 0xd8, 0xf8, 0x04, 0x60,
0xcc, 0xf2, 0xef, 0x65, 0x20, 0x21, 0x00, 0xeb, 0x07, 0x12, 0x0a, 0xeb, 0x57, 0x13, 0x5a, 0x40,
0xeb, 0x19, 0x5a, 0x40, 0x01, 0x39, 0xa6, 0xeb, 0x02, 0x06, 0x05, 0xeb, 0x06, 0x02, 0x25, 0x44,
0x0e, 0xeb, 0x06, 0x13, 0x82, 0xea, 0x03, 0x02, 0x0b, 0xeb, 0x56, 0x13, 0x82, 0xea, 0x03, 0x02,
0xa7, 0xeb, 0x02, 0x07, 0xe7, 0xd1, 0x09, 0xf1, 0x08, 0x09, 0xc8, 0xe9, 0x00, 0x76, 0xb9, 0xf1,
0x28, 0x0f, 0xd5, 0xd3, 0x00, 0x98, 0x28, 0x23, 0x46, 0x68, 0x30, 0x46, 0x61, 0x46, 0x1a, 0x46,
0x4f, 0xf0, 0x04, 0x07, 0x00, 0xdf, 0x00, 0x20, 0x2d, 0xb0, 0xbd, 0xe8, 0xf0, 0x8f]
for i in range(len(func)):
    idc.patch_byte(addr+i,func[i])

Then switch this part of alt+G to thumb mode, press C to convert it into code, press P to declare it into a function, and then you can use F5.

In the decrypted function code, the input characters are first encrypted with tea, and then compared at line 103 to line 109. If they are incorrect, the string prompts v26 and v25 will be decrypted respectively and the prompt results will be returned.

We can write a script to decrypt the flag and prompt string respectively. The script code is as follows:

#include <iostream>


void destr();


void tea_decode1(uint32_t *origin, uint32_t *key) {
    
    
    uint32_t v0 = origin[0], v1 = origin[1], i;  /* set up */
    uint32_t delta = 0x61C88647;
    uint32_t sum = 0xC6EF3720;
    uint32_t k0 = key[0], k1 = key[1], k2 = key[2], k3 = key[3];   /* cache key */
    for (i = 0; i < 32; i++) {
    
                             /* basic cycle start */
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        sum += delta;
    }                                              /* end cycle */
    origin[0] = v0;
    origin[1] = v1;
}


int main() {
    
    
    destr();
    uint32_t v27[8];
    v27[0] = 0x26BB4EA4;
    v27[1] = 0x69BBDC43;
    v27[2] = 0xDB3F2F5C;
    v27[3] = 0x7BBD2F9F;
    v27[4] = 0x65CDFE31;
    v27[5] = 0x3E91AEC4;
    v27[6] = 0xE703AF94;
    v27[7] = 0xC7D1E1F4;
    uint32_t key[4];
    key[0] = 0x67616C66;
    key[1] = 0x6948747B;
    key[2] = 0x24215F53;
    key[3] = 0x37256E5F;

    for (int i = 0; i < 8; i += 2) {
    
    
        tea_decode1(&v27[i], key);
    }
    std::cout << (char *) v27 << std::endl;
    return 0;
}

void destr() {
    
    
    uint32_t v26[10], v25[10];
    v26[0] = 0x821A9220;
    v26[1] = 0x96CD43F3;
    v26[2] = 0x36D481BA;
    v26[3] = 0xCB2C076D;
    v26[4] = 0xDC65768;
    v26[5] = 0xBAB4DE5D;
    v26[6] = 0x7571980F;
    v26[7] = 0xE382ECC7;
    v26[8] = 0xC733E8E0;
    v26[9] = 0x74812F26;
    v25[0] = 0x1DA05DA0;
    v25[1] = 0xB07E328;
    v25[2] = 0xB572D778;
    v25[3] = 0xC3EE27BB;
    v25[4] = 0xD200541B;
    v25[5] = 0xA595C8E0;
    v25[6] = 0x44B3A5C8;
    v25[7] = 0x21F11604;
    v25[8] = 0xDD1EBA82;
    v25[9] = 0xD4CBF19C;
    uint32_t key[4];
    key[0] = 0x67616C66;
    key[1] = 0x6948747B;
    key[2] = 0x24215F53;
    key[3] = 0x37256E5F;

    for (int i = 0; i < 10; i += 2) {
    
    
        tea_decode1(&v25[i], key);
    }
    std::cout << (char *) v25 << std::endl;

    for (int i = 0; i < 10; i += 2) {
    
    
        tea_decode1(&v26[i], key);
    }
    std::cout << (char *) v26 << std::endl;
}

insert image description here
Get the flag:flag{LOoK|N9_An_3@&9_s%Lue?!?!}

Guess you like

Origin blog.csdn.net/aa2528877987/article/details/130461450