Android java层调试之smali插桩 您所在的位置:网站首页 新建java包 Android java层调试之smali插桩

Android java层调试之smali插桩

2023-06-15 20:03| 来源: 网络整理| 查看: 265

smali插桩是一度是android上最流行的 java层逆向调试方法,即使在当下依然能满足大部分java层逆向分析要求,但是其缺点,一是使用起来每次修改都要重打包,异常繁琐;二是重打包在很多时候是无法实现的,比如,应用程序的安全对抗使其重打包困难重重,当前的很多知名应用程序在运行时会校验安装包的签名,若是发现签名错误将会拒绝运行,另外,apk的加壳技术也会使重打包异常棘手,无从下手。

抛开这些不谈,本文只是就smali插桩技术的过程做简要说明。

本文smali插桩重打包测试工程下载地址:点击下载

(一)原理 假设调试目标是程序A,利用dex2jar和jd-gui程序,静态反汇编分析,找到大致可疑的监视点,假设为A类中的D方法是我们要监视的函数。利用apktool,将A程序的apk文件解包(解包后的文件为smali和资源)。创建程序B,并实现一个public static类型的方法F,该方法的主要功能是,将要监视的函数的参数或者其他变量等,输出到日志log或者sd卡文件中。将B程序的apk文件解包。将B中F方法的smali代码拷贝到A类的smali代码文件中。在A类的D方法中寻找插桩位置,使用smali语法调用F方法。利用apktool将A重新打包,使用autosign重新签名并安装到android上并运行,查看相应标志的日志和文件,就可以实现对A类的D函数的监控。 (二)例子

创建一个test程序,包名为com.example.test。该程序为我们要调试的目标程序,我们想要得到MainActivity类testUserPass中的String类型参数值user和pass。 在这里插入图片描述

创建第二个程序,包名为com.example.smaliHook,其中有个方法mytestlog,其主要功能是日志输出和文件记录两个String变量的值。 该功能包含两个函数,函数mytestlog有两个String类型输入参数,在程序中获取Context后,分别使用toast、log、写入"/storage/sdcard0/SmaliLog/SmaliLog.txt"文件等3种方式输出两个参数的值,其中写入sd卡文件的操作由函数WriteLogFile完成。

public static void mytestlog(String str1,String str2){ String TAG = "smaliHook"; try { Class ActivityThread = Class.forName("android.app.ActivityThread"); Method methodcat = ActivityThread.getMethod("currentActivityThread"); Object currentActivityThread = methodcat.invoke(ActivityThread); Method methodga = currentActivityThread.getClass().getMethod("getApplication"); Context context =(Context)methodga.invoke(currentActivityThread); if (context == null) { Log.e(TAG, "context null"); }else{ String str = TAG + "get context ok,package name:" + context.getPackageName()+"/class name:" + context.getClass().getName(); String param = "param 1 is:" + str1 + ",param 2 is:" + str2; Log.e(TAG,param ); WriteLogFile(param); //context = context.getApplicationContext(); //Toast.makeText(context, param, Toast.LENGTH_LONG).show(); return ; } } catch (Exception e) { e.printStackTrace(); } return ; } public static void WriteLogFile(String str) { String LOG_FILE_PATH = "/storage/sdcard0/SmaliLog/"; String LOG_FILE_NAME = "SmaliLog.txt"; String sdStatus = Environment.getExternalStorageState(); if(!sdStatus.equals(Environment.MEDIA_MOUNTED)) { return; } try { String pathName=LOG_FILE_PATH; String fileName=LOG_FILE_NAME; File path = new File(pathName); File file = new File(pathName + fileName); if( !path.exists()) { path.mkdir(); } if( !file.exists() ) { file.createNewFile(); } FileOutputStream stream = new FileOutputStream(file,true); if(str.length() > 0) { stream.write(str.getBytes()); } stream.write("\r\n".getBytes()); stream.close(); } catch(Exception e) { e.printStackTrace(); } } 使用apktool解包。包括第一个和第二个apk包。

apktool解包命令(以test.apk为例):

java -jar apktool.jar d -f test.apk

apktool打包命令(以上文中test.apk为例):

java -jar apktool.jar b test

除了使用第三方的apktool外,还可以使用google sdk自带的工具smali.jar和baksmali.jar来实现。命令执行时记得关闭360,否则一堆提示后,发现刚生成的文件不见了,悲哀:)。

将dex分解为smali文件的命令(以classes.dex为例):

java -jar baksmali-2.0.3.jar classes.dex -o class

将smali文件生成为dex文件的命令(以class为例):

java -jar smali-2.0.3.jar class -o myclass.dex

相对来说,还是apktool.jar比较简单。

反汇编完成后,上述WriteLogFile和mytestlog两个函数的smali代码如下:

.method public static mytestlog(Ljava/lang/String;Ljava/lang/String;)V .locals 12 .param p0, "str1" # Ljava/lang/String; .param p1, "str2" # Ljava/lang/String; .prologue .line 76 const-string v1, "smaliHook" .line 78 .local v1, "TAG":Ljava/lang/String; :try_start_0 const-string v9, "android.app.ActivityThread" invoke-static {v9}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class; move-result-object v0 .line 79 .local v0, "ActivityThread":Ljava/lang/Class;, "Ljava/lang/Class;" const-string v9, "currentActivityThread" const/4 v10, 0x0 new-array v10, v10, [Ljava/lang/Class; invoke-virtual {v0, v9, v10}, Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; move-result-object v5 .line 80 .local v5, "methodcat":Ljava/lang/reflect/Method; const/4 v9, 0x0 new-array v9, v9, [Ljava/lang/Object; invoke-virtual {v5, v0, v9}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; move-result-object v3 .line 81 .local v3, "currentActivityThread":Ljava/lang/Object; invoke-virtual {v3}, Ljava/lang/Object;->getClass()Ljava/lang/Class; move-result-object v9 const-string v10, "getApplication" const/4 v11, 0x0 new-array v11, v11, [Ljava/lang/Class; invoke-virtual {v9, v10, v11}, Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; move-result-object v6 .line 82 .local v6, "methodga":Ljava/lang/reflect/Method; const/4 v9, 0x0 new-array v9, v9, [Ljava/lang/Object; invoke-virtual {v6, v3, v9}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; move-result-object v2 check-cast v2, Landroid/content/Context; .line 83 .local v2, "context":Landroid/content/Context; if-nez v2, :cond_0 .line 84 const-string v9, "context null" invoke-static {v1, v9}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I .line 102 .end local v0 # "ActivityThread":Ljava/lang/Class;, "Ljava/lang/Class;" .end local v2 # "context":Landroid/content/Context; .end local v3 # "currentActivityThread":Ljava/lang/Object; .end local v5 # "methodcat":Ljava/lang/reflect/Method; .end local v6 # "methodga":Ljava/lang/reflect/Method; :goto_0 return-void .line 86 .restart local v0 # "ActivityThread":Ljava/lang/Class;, "Ljava/lang/Class;" .restart local v2 # "context":Landroid/content/Context; .restart local v3 # "currentActivityThread":Ljava/lang/Object; .restart local v5 # "methodcat":Ljava/lang/reflect/Method; .restart local v6 # "methodga":Ljava/lang/reflect/Method; :cond_0 new-instance v9, Ljava/lang/StringBuilder; invoke-static {v1}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String; move-result-object v10 invoke-direct {v9, v10}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V const-string v10, "get context ok,package name:" invoke-virtual {v9, v10}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v9 invoke-virtual {v2}, Landroid/content/Context;->getPackageName()Ljava/lang/String; move-result-object v10 invoke-virtual {v9, v10}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v9 const-string v10, "/class name:" invoke-virtual {v9, v10}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v9 invoke-virtual {v2}, Ljava/lang/Object;->getClass()Ljava/lang/Class; move-result-object v10 invoke-virtual {v10}, Ljava/lang/Class;->getName()Ljava/lang/String; move-result-object v10 invoke-virtual {v9, v10}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v9 invoke-virtual {v9}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v8 .line 88 .local v8, "str":Ljava/lang/String; new-instance v9, Ljava/lang/StringBuilder; const-string v10, "param 1 is:" invoke-direct {v9, v10}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V invoke-virtual {v9, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v9 const-string v10, ",param 2 is:" invoke-virtual {v9, v10}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v9 invoke-virtual {v9, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v9 invoke-virtual {v9}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v7 .line 90 .local v7, "param":Ljava/lang/String; invoke-static {v1, v7}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I .line 92 invoke-static {v7}, Lcom/example/smalihook/MainActivity;->WriteLogFile(Ljava/lang/String;)V .line 94 const/4 v9, 0x1 invoke-static {v2, v7, v9}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; move-result-object v9 invoke-virtual {v9}, Landroid/widget/Toast;->show()V :try_end_0 .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 goto :goto_0 .line 98 .end local v0 # "ActivityThread":Ljava/lang/Class;, "Ljava/lang/Class;" .end local v2 # "context":Landroid/content/Context; .end local v3 # "currentActivityThread":Ljava/lang/Object; .end local v5 # "methodcat":Ljava/lang/reflect/Method; .end local v6 # "methodga":Ljava/lang/reflect/Method; .end local v7 # "param":Ljava/lang/String; .end local v8 # "str":Ljava/lang/String; :catch_0 move-exception v4 .line 99 .local v4, "e":Ljava/lang/Exception; invoke-virtual {v4}, Ljava/lang/Exception;->printStackTrace()V goto :goto_0 .end method 将项目smaliHook中,mytestlog和WriteLogFile的smali代码,拷贝到test项目MainActivity类的smali文件中。将上述两个函数mytestlog和WriteLogFile复制到/com/example/test/MainActivity.smali文件中,位置可以随意。插桩函数mytestlog和WriteLogFile的几点要求:(1) 插桩函数最好是public static 属性,因为java中的非静态方法都要使用类指针来引用,这样一来需要维护整个类的插入工作,大大增加了工作量;(2) 尽量将插桩代码复制到与监视方法的同一个类中;(3) 插桩函数尽量使用局部变量,这样会减少类中变量成员的引用复杂度。上述几点的目的是,使插桩操作尽可能的简单。在目标方法中查找合适的插桩位置,并添加smali调用。因为被插入方法一般是public static属性,因此使用的smali调用格式是invoke-static;后面是调用参数列表,比如{p1,p2},其中p1,p2代表上一级调用本函数时的参数,如果要传入的参数是局部变量,就只能用v前缀而不是p前缀来表示变量;再后面是方法的全路径,此处是 Lcom/example/test/MainActivity;->mytestlog(Ljava/lang/String;Ljava/lang/String;)V ,注意smali中类的表示语法,最后面的大写V代表返回值是void类型。调用时使用如下格式: invoke-static {p1,p2}, Lcom/example/test/MainActivity;->mytestlog(Ljava/lang/String;Ljava/lang/String;)V

插桩后的testUserPass函数smali代码如下: 在这里插入图片描述

对目标apk进行打包。将插桩后的com.example.test工程重新打包,对打包后的test.apk重新签名。安装test.apk并运行、查看插桩日志输出或者sd卡中的文件输出,就可以得到程序运行中两个参数的值。


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有