XCTF_MOBILE11_ Hacker spirit

First sight

The attachment is an apk. Run it in the simulator first.

The simulator with ARM CPU must be selected, because the native library in the app only provides the version of arm instruction set, not x86 instruction set:

After the simulator starts, drag apk to the simulator for installation. After installation, the icon of app in the list is a year:

Run the app and see another year:

There is nothing but a button. Click this button to pop up a two button dialog box:

Click "stop playing" and the app will exit directly.

Click "register" to enter a new interface. In the new interface, you can enter a string of characters and have a registration button. Enter a string casually, click registration, and the pop-up box displays "your registration code has been saved":

After the pop-up, the process exits immediately. The function of this registration code can't be seen from the behavior. Decompile the code.

MainActivity analysis

Open apk with jadx. First take a look at the code of MainActivity. You can find the response function of "freely define and share" button in onCreate function:

this.btn1.setOnClickListener(new View.OnClickListener() {      
            public void onClick(View v) {
                MyApp myApp2 = (MyApp) MainActivity.this.getApplication();
                if (MyApp.m == 0) {
                    MainActivity.this.doRegister();
                    return;
                }
                ((MyApp) MainActivity.this.getApplication()).work();
                Toast.makeText(MainActivity.this.getApplicationContext(), MainActivity.workString, 0).show();
            }
});

The core is myapp When m is empty, call doRegister(). Then call work().

First look at the doRegister() function:

    public void doRegister() {
        new AlertDialog.Builder(this).setTitle("register").setMessage("Flag Right ahead!").setPositiveButton("register", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                Intent intent = new Intent();
                intent.setComponent(new ComponentName(BuildConfig.APPLICATION_ID, "com.gdufs.xman.RegActivity"));
                MainActivity.this.startActivity(intent);
                MainActivity.this.finish();
            }
        }).setNegativeButton("Stop playing", new DialogInterface.OnClickListener() { 
            public void onClick(DialogInterface dialog, int which) {
                Process.killProcess(Process.myPid());
            }
        }).show();
    }

It can be seen that the doRegister() function is to pop up the dialog box below and click the processing button:

Click "stop playing" and directly kill process.

Click "register" to create a new Activity with the class "com.gdufs.xman.RegActivity" and start it.

To sum up:

If myapp M is empty. Create a RegActivity to register and make myapp M is not empty. Then call work().

If myapp If M is not empty, call work() directly.

This work is a native function:

public native void work();

RegActivity

RegActivity corresponds to the following interface:

Focus on the response function of the "register" button:

public void onClick(View v) {
     String sn = RegActivity.this.edit_sn.getText().toString().trim();
     if (sn == null || sn.length() == 0) {
           Toast.makeText(RegActivity.this, "Your input is empty", 0).show();
           return;
     }
     ((MyApp) RegActivity.this.getApplication()).saveSN(sn);
     new AlertDialog.Builder(RegActivity.this).setTitle("reply").setMessage("Your registration code has been saved").setPositiveButton("ok", new DialogInterface.OnClickListener() { 
            public void onClick(DialogInterface dialog, int which) {
                   Process.killProcess(Process.myPid());
            }
     }).show();
}

It can be seen that after clicking the "register" button:

  1. Call the saveSN() function to register
  2. The pop-up box will prompt "your registration code has been saved", as shown in the figure above
  3. killProcess

Here, the logic that has been analyzed is:

If myapp M is empty, calling saveSN() to register, and then calling work().

If myapp If M is not empty, call work() directly.

This saveSN() function is also a native function:

public native void saveSN(String str);

Let's analyze these two native functions.

native library analysis

Change the apk suffix to zip and unzip it. In the unzipped lib directory, you can get the native library file: libmyjni so.

Load libmyjni.com with IDA so.

However, the work function and saveSN function were not found in the export function. But you can see that JNI is exported_ Onload function, which is reminiscent of the dynamic registration method of JNI function:

  1. The corresponding relationship between java method and JNI function is recorded by using the structure JNI nativemethod array;

  2. Implement JNI_OnLoad method, after loading the dynamic library, execute dynamic registration;

  3. Call FindClass method to get java object;

  4. Call the RegisterNatives method, pass in the java object, the JNI nativemethod array, and the number of registrations to complete the registration;

This is the same as the JNI of the question_ The onload function process is consistent:

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
  if ( !(*vm)->GetEnv(vm, (void **)&g_env, 65542) )
  {
    j___android_log_print(2, "com.gdufs.xman", "JNI_OnLoad()");
    native_class = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)g_env + 24))(g_env, "com/gdufs/xman/MyApp");
    if ( !(*(int (__fastcall **)(int, int, char **, int))(*(_DWORD *)g_env + 860))(g_env, native_class, off_5004, 3) )
    {
      j___android_log_print(2, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() ok");
      return 65542;
    }
    j___android_log_print(6, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() failed");
  }
  return -1;
}

Visible, off_5004 is the corresponding relationship between java function and JNI function, and the corresponding relationship is obtained as follows:

  • InitSN----n1
  • SaveSN----n2
  • Work----n3

Let's analyze the native function.

SaveSN

The core decompile code is:

int __fastcall n2(int a1, int a2, int a3)
{
  v5 = j_fopen("/sdcard/reg.dat", "w+");
  strcpy(v13, "W3_arE_whO_we_ARE");
  v7 = input_string;
  v8 = v7;
  v12 = j_strlen(v7);
  v9 = 2016;
  while ( 1 )
  {
    v10 = v8 - v7;
    if ( v8 - v7 >= v12 )
      break;
    if ( v10 % 3 == 1 )
    {
      v9 = (v9 + 5) % 16;
      v11 = v13[v9 + 1];
    }
    else if ( v10 % 3 == 2 )
    {
      v9 = (v9 + 7) % 15;
      v11 = v13[v9 + 2];
    }
    else
    {
      v9 = (v9 + 3) % 13;
      v11 = v13[v9 + 3];
    }
    *v8++ ^= v11;
  }
  j_fputs(v7, v5);
  return j_fclose(v5);
}

The pseudo code here is simplified and easy to understand. The core logic of this is:

  1. In the loop, the input string is deformed according to "W3_arE_whO_we_ARE"
  2. Save the deformed string into the file "/ sdcard/reg.dat"

InitSN

The core decompile code is:

int __fastcall n1(int a1)
{
  v2 = j_fopen("/sdcard/reg.dat", "r+");
  j_fread(v6, v5, 1, v2);
  if ( !j_strcmp(v6, "EoPAoY62@ElRD") )
  {
    v8 = a1;
    v9 = 1;
  }
  else
  {
    v8 = a1;
    v9 = 0;
  }
  return j_fclose(v3);
}

The pseudo code here is also simplified by me. The core functional logic is:

  1. Read the contents of the "/ sdcard/reg.dat" file
  2. Compare the content, isn't it“ EoPAoY62@ElRD "

It's clear here that the problem is to transform our input string into“ EoPAoY62@ElRD ".

Problem solving

The logic of deforming the input string in SaveSN is also very simple:

  1. Take each character a in the input string
  2. According to the subscript of the character in the input string, a value i is calculated
  3. Take the character b with subscript i in "W3_arE_whO_we_ARE"
  4. Calculate the value of a or b and write it to the file

According to the deformation process, use python to decrypt the code:

seedstr = bytes('W3_arE_whO_we_ARE','utf-8')
seed = 2016;
result = bytes('EoPAoY62@ElRD','utf-8')
for i in range(13):
	if(i % 3 == 1):
		seed = (seed + 5) % 16
		tmp = seedstr[seed + 1]
		tmp = tmp ^ result[i]
		print(chr(tmp), end = '')
	elif(i % 3 == 2):
		seed = (seed + 7) % 15
		tmp = seedstr[seed + 2]
		tmp = tmp ^ result[i]
		print(chr(tmp), end = '')
	else:
		seed = (seed + 3) % 13
		tmp = seedstr[seed + 3]
		tmp = tmp ^ result[i]
		print(chr(tmp), end = '')

Get the string: 201608Am!2333.

Enter the string to register, then open the app again, click "free and just sharing", and the prompt is:

So the flag is: xman{201608Am!2333}

---------------------—

Welcome to my microblog: Daxiong_ RE. Focus on software reverse, share the latest good articles and tools, and track the research results of industry leaders.

Keywords: Android CTF

Added by kelvin on Mon, 21 Feb 2022 15:17:52 +0200