m0bilesecurity / RMS-Runtime-Mobile-Security

Runtime Mobile Security (RMS) 📱🔥 - is a powerful web interface that helps you to manipulate Android and iOS Apps at Runtime
https://twitter.com/mobilesecurity_
GNU General Public License v3.0
2.62k stars 376 forks source link

Bug - Hooking lab | java.lang.Object #18

Closed 4val0v closed 4 years ago

4val0v commented 4 years ago

Describe the bug: Incorrect parameters generation for method overloading !


Steps to reproduce: Custom Logging Implementation: (See methods) Dump TAB

Go to "hook lab", select a Logging class: (highlighted) Hook lab Generated:

hookclass.d.overload("java.lang.Class","java.lang.String","java.lang.Object[]").implementation = function (v0,v1,v2)
Current Must be
java.lang.Object[] [Ljava.lang.Object;

Desktop:

Smartphone:

4val0v commented 4 years ago

1) Because of this problem, hooking does not work (Only methods with Object) ! 2) For the void methods you do not need to generate out !

m0bilesecurity commented 4 years ago

Hey @4val0v can you kindly share the APK? Many thanks

4val0v commented 4 years ago

Hi @m0bilesecurity! No difference what will it be application

Here look:

Here is the worst code to reproduce the error: (Click) ```java public class MainActivity extends AppCompatActivity { public void test(char[] chars, Object[] objects, Object obj, String[][] strArr, Long[][] arrLong, String[][][][] lol) { System.out.println("Hello"); } } ```
Hook lab -> generated code: Hook lab wrong fixed
char[] [C
java.lang.Object[] [Ljava.lang.Object;
java.lang.String[][] [[Ljava.lang.String;
java.lang.Long[][] [[Ljava.lang.Long;
java.lang.String[][][][] [[[[Ljava.lang.String;

app-debug.apk.zip

4val0v commented 4 years ago

I'll leave it here, suddenly someone will be interested: https://stackoverflow.com/a/19098229/3469132

4val0v commented 4 years ago

@m0bilesecurity Run the generated code using frida to see the error Frida

frida -U --no-pause -f com.wtf.myapplication -l test.js
m0bilesecurity commented 4 years ago

Hey @4val0v, many thanks for all the precious info provided. The issue is clear, notation for overloading methods with an array as arg is in smali. So according to this table:

Element Type         Encoding
boolean              Z
byte                 B
char                 C
class or interface   Lclassname;
double               D
float                F
int                  I
long                 J
short                S

we need to adjust the laodmethod in default.js in order to convert arrays in the right notation. Does exist something ready to use?

4val0v commented 4 years ago
@m0bilesecurity Java - Frida methods: https://neo-geo2.gitbook.io/adventures-on-security/frida-scripting-guide/methods Java Frida
int int
byte byte
short short
long long
float float
double double
char char
<Object> (eg. String) <package>.<Object> (eg. java.lang.String)
int[] [I
byte[] [B
short[] [S
long[] [J
float[] [F
double[] [D
char[] [C
<Object>[] [L<package>.<Object> (eg. [Ljava.lang.String)

https://android.googlesource.com/platform/dalvik/+/gingerbread/docs/dex-format.html

4val0v commented 4 years ago

@m0bilesecurity If you have time, you can continue...

Java.perform(function() {

    var targetClass = Java.use("com.wtf.myapplication.MainActivity");
    var currentMethods = targetClass.class.getDeclaredMethods(); // all methods declared in a Java Class

    console.log("Count methods for class: " + currentMethods.length);

    for (var i = 0; i < currentMethods.length; i++) {
        var args = currentMethods[i].toString().split('(')[1].split(')')[0].split(',');

        console.log("\nCurrent method: " + currentMethods[i].getName());
        console.log("args: " + args);

        // TODO for i

     };

});

Take a look here: https://juejin.im/post/5e967cb5f265da47a74134a6#heading-9 (It didn't work for me)

m0bilesecurity commented 4 years ago

Please check this quick and dirty solution.

// check if the current arg is an array
var arg = args_array[i]
if(arg.includes("[]")){
  // arg is an array --> smali notation conversion
      if (arg.includes(".")) arg="L"+arg+";"
      else if((/boolean/i).test(arg)) arg="Z"+arg.replace(/boolean/i, ""); 
      else if((/byte/i).test(arg)) arg="B"+arg.replace(/byte/i, ""); 
      else if((/char/i).test(arg)) arg="C"+arg.replace(/char/i, ""); 
      else if((/double/i).test(arg)) arg="D"+arg.replace(/double/i, ""); 
      else if((/float/i).test(arg)) arg="F"+arg.replace(/float/i, ""); 
      else if((/int/i).test(arg)) arg="I"+arg.replace(/int/i, ""); 
      else if((/long/i).test(arg)) arg="J"+arg.replace(/long/i, ""); 
      else if((/short/i).test(arg)) arg="S"+arg.replace(/short/i, ""); 
      else arg="L"+arg+";"
  }
  while(arg.includes("[]")){
      arg=arg.replace("[]", "")
      arg="["+arg
  }

Some tests are needed. If you want to help me, just replace the loadmethods function inside default.js with the code below:

loadmethods: function (loaded_classes) {
    var loaded_methods = {};
    Java.perform(function () {
      loaded_classes.forEach(function (className, index) {
        var jClass;
        var classMethods_dirty;

        //catch possible issues
        try{
          jClass = Java.use(className);
          classMethods_dirty = jClass.class.getDeclaredMethods();
        }catch(err){
          send("Exception while loading methods for "+className);
          //skip current loop
          loaded_methods[className] = []
          return;
        }
        var classMethods = []

        classMethods_dirty.forEach(function (m) {
          var method_and_args = {};
          //Cleaning up
          m = m.toString();
          //add info for the UI
          method_and_args["ui_name"] = m.replace(className + ".", "")
          // Remove generics from the method
          while (m.includes("<")) {
            m = m.replace(/<.*?>/g, "");
          }
          // remove "Throws" 
          if (m.indexOf(" throws ") !== -1) {
            m = m.substring(0, m.indexOf(" throws "));
          }
          // remove scope and return type declarations 
          m = m.slice(m.lastIndexOf(" "));
          // remove the class name
          m = m.replace(className + ".", "");

          // remove the signature (args) 
          method_and_args["name"] = m.split("(")[0].trim()

          // get the args 
          var args_dirty = ((/\((.*?)\)/.exec(m)[1]).trim())

          // add quotes between every arg
          var args_array = args_dirty.split(",")
          var args_srt = ""
          for (var i = 0; i < args_array.length; i++) {

            // check if the current arg is an array
            var arg = args_array[i]
            if(arg.includes("[]")){
              // arg is an array --> smali notation conversion
                  if (arg.includes(".")) arg="L"+arg+";"
                  else if((/boolean/i).test(arg)) arg="Z"+arg.replace(/boolean/i, ""); 
                  else if((/byte/i).test(arg)) arg="B"+arg.replace(/byte/i, ""); 
                  else if((/char/i).test(arg)) arg="C"+arg.replace(/char/i, ""); 
                  else if((/double/i).test(arg)) arg="D"+arg.replace(/double/i, ""); 
                  else if((/float/i).test(arg)) arg="F"+arg.replace(/float/i, ""); 
                  else if((/int/i).test(arg)) arg="I"+arg.replace(/int/i, ""); 
                  else if((/long/i).test(arg)) arg="J"+arg.replace(/long/i, ""); 
                  else if((/short/i).test(arg)) arg="S"+arg.replace(/short/i, ""); 
                  else arg="L"+arg+";"
              }
              while(arg.includes("[]")){
                  arg=arg.replace("[]", "")
                  arg="["+arg
              }

            args_srt = args_srt + ("\"" + arg + "\"")
            //add a comma if the current item is not the last one
            if (i + 1 < args_array.length) args_srt = args_srt + ",";
          }

          method_and_args["args"] = args_srt
          classMethods.push(method_and_args);

        });

        loaded_methods[className] = classMethods;
      });

    })
    //DEBUG console.log("loaded_classes.length: " + loaded_classes.length)
    //DEBUG console.log("loaded_methods.length: " + Object.keys(loaded_methods).length)
    return loaded_methods;
  },
m0bilesecurity commented 4 years ago

@4val0v can you kindly add a button to your test app in order to call the "test" method?

4val0v commented 4 years ago

@m0bilesecurity Now everything is OK !

Still need fixed a functional on page "Dump TAB" -> "Hook all methods"

m0bilesecurity commented 4 years ago

FIXED 🚀! Check the last commit. I prefer to keep the signature of the method without the smali notation because it's more easy to read.

Under the hood, the hook is managed correctly also in the dump page ;) I tested your sample app and now I'm able to hook the "test" method. Many thanks for the detailed info provided! Best Paolo