Post

逆向利器:Xposed

玩了8年的安卓,终于学会了一些软件破解,写了自己的第一个Xposed模块。中午看见老师讲课的手机里有Magisk,就问老师是不是玩酷安,结果发现居然都是16年入坑酷安的,直接变成酷友😎说起来挺有意思的,也不知道为什么就对安卓这么感兴趣,初中换recovery刷rom,高中开始接触Magisk和Xposed,大学自学了安卓开发,现在又开始学安卓逆向和Xposed开发,可能以后也会继续玩下去吧,只是在这个系统越发封闭的趋势下不知道还能玩多久就是了,希望小米跟一加撑住(

signed.apk 逆向

环境配置

  1. 编译 ApkShelling

    image-20240526171903267

程序脱壳

  1. 加固分析

    打开 mt 管理器,点击 sign.apk 查看相关信息,可看到程序是用梆梆加固进行了加壳。可以看到里面的代码是完全不可分析的状态。

    image-20240526210311810 image-20240526210402817
  2. 脱壳

    将编译好的 ApkSehlling 安装在手机上并使用它对 signed.apk 进行脱壳,得到如下文件:

    image-20240526210528164

    我们打开最大的文件进行查看,找到其中的 MainActivity,即可获得 flag:

    image-20240526210648071

jstz.apk 逆向

静态分析

  1. MainActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
     package an.droid.j;
        
     import android.os.Bundle;
     import android.os.Handler;
     import android.support.v7.app.AppCompatActivity;
     import android.widget.Button;
     import android.widget.TextView;
        
     public class MainActivity extends AppCompatActivity {
         long df_all;
         long t;
         long t1;
         long t2;
         int zygote = 0x50e28da8;
        
         public native String j(int i);
        
         public native int p(int i);
        
         protected void onCreate(Bundle bundle) {
             super.onCreate(bundle);
             setContentView(0x7f09001c);
             Button button = (Button) findViewById(0x7f070022);
             Button button2 = (Button) findViewById(0x7f070023);
             TextView textView = (TextView) findViewById(0x7f07008f);
             Handler handler = new Handler();
             MainActivity$1 r3 = new MainActivity$1(this, textView, handler);
             button.setOnClickListener(
                 new MainActivity$2(this, handler, r3)
             );
             button2.setOnClickListener(
                 new MainActivity$3(this, handler, r3, textView)
             );
         }
        
         static {
             System.loadLibrary("j");
         }
     }
    

    主要功能如下:

    • 设置按钮监听
    • 实例化其余 Activity
    • 加载 libj.so
    • 声明 libj.so 中的两个 native 方法

    在安卓的架构里,分为 java 层和 native 层。java 层负责大部分代码逻辑的处理,native 层通过 C/C++ 提供更加快速的处理并让 ART 虚拟机支持更多扩展。从 java 层对 native 层进行调用的时候需要用到 JNI 调用,也就是这里声明的 native 关键字。

  2. MainActivity$1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
     package an.droid.j;
        
     import android.os.Handler;
     import android.widget.TextView;
        
     class MainActivity$1 implements Runnable {
         final MainActivity this$0;
         final Handler val$hd;
         final TextView val$tv1;
        
         MainActivity$1(MainActivity mainActivity, TextView textView, Handler handler) {
             this.this$0 = mainActivity;
             this.val$tv1 = textView;
             this.val$hd = handler;
         }
        
         @Override
         public void run() {
             this.this$0.t2 = System.currentTimeMillis();
             long j = this.this$0.t2 - this.this$0.t1;
             MainActivity mainActivity = this.this$0;
             mainActivity.df_all = mainActivity.t2 - this.this$0.t;
             if (j > 100) {
                 this.this$0.t1 += 100;
                 MainActivity mainActivity2 = this.this$0;
                 mainActivity2.zygote = mainActivity2.p(mainActivity2.zygote);
                 this.val$tv1.setText(String.format(
                     "Time:%.1f", Double.valueOf(
                         Math.floor(this.this$0.df_all / 100) / 10.0d)));
             }
             this.val$hd.postDelayed(this, 30L);
         }
     }
    

    主要功能如下:

    • 每隔 100 ms 进行调用 libj.so 中的 p() 方法,对 zygote 进行迭代
    • 迭代后将其显示在 TextView 上
  3. MainActivity$2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
     package an.droid.j;
        
     import android.os.Handler;
     import android.view.View;
        
     class MainActivity$2 implements View.OnClickListener {
         final MainActivity this$0;
         final Handler val$hd;
         final Runnable val$rn;
        
         MainActivity$2(MainActivity mainActivity, Handler handler, Runnable runnable) {
             this.this$0 = mainActivity;
             this.val$hd = handler;
             this.val$rn = runnable;
         }
        
         @Override
         public void onClick(View view) {
             this.this$0.t = System.currentTimeMillis();
             MainActivity mainActivity = this.this$0;
             mainActivity.t1 = mainActivity.t;
             this.val$hd.postDelayed(this.val$rn, 0L);
         }
     }
    

    主要功能如下:

    • 重写 onClick(), 当 start 按钮被按下时计算与开始时间的差值
  4. MainActivity$3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    
     package an.droid.j;
        
     import android.os.Handler;
     import android.view.View;
     import android.widget.TextView;
        
     class MainActivity$3 implements View.OnClickListener {
         final MainActivity this$0;
         final Handler val$hd;
         final Runnable val$rn;
         final TextView val$tv1;
        
         MainActivity$3(MainActivity mainActivity, Handler handler, Runnable runnable, TextView textView) {
             this.this$0 = mainActivity;
             this.val$hd = handler;
             this.val$rn = runnable;
             this.val$tv1 = textView;
         }
        
         @Override
         public void onClick(View view) {
             this.val$hd.removeCallbacks(this.val$rn);
             if (this.this$0.df_all / 100 != 99999) {
                 if (this.this$0.df_all / 100 < 9999) {
                     this.val$tv1.setText("too soon");
                     return;
                 } else {
                     this.val$tv1.setText("too late");
                     return;
                 }
             }
             TextView textView = this.val$tv1;
             StringBuilder sb = new StringBuilder();
             sb.append("flag{");
             MainActivity mainActivity = this.this$0;
             sb.append(mainActivity.j(mainActivity.zygote));
             sb.append("}");
             textView.setText(sb.toString());
         }
     }
        
    

    主要功能如下:

    • 判断累计时间是否在 99999009999999 之间
    • 符合条件则调用 libj.so 中的 j() 方法,传入 zygote 更新获取 flag
    • 不符合则输出相应提示
  5. 分析

    根据上述函数的静态分析,可以知道我们应该让程序正好停在 9999900 毫秒,并且要获得这个时刻的 zygote 值。手动停止显然是不可能的,一是时间太长,二是不可能做到这么精确。因此需要对程序进行 hook。

Java 层 Hook

课堂上老师讲解的方法是通过 frida 爆破时间,获得需要的 zygote,再用 unidbg 模拟出来一个安卓环境,在模拟器中从 libj.so 中 dump 出 flag。但是这个方法需要用到不止一个工具,配环境很麻烦,而且只要用到模拟器,效率一定不高。正巧我从初中就开始接触 Xposed 框架,去年我在可信计算课上也完整讲了一遍 Xposed 的原理,何不通过这个机会学习一下如何开发一个 Xposed 模块呢?

以下的步骤将逐步构建一个 Xposed 模块,用一个更加优雅的方式在手机上实现实时对程序进行 hook。

  1. 创建项目

    在 Android Studio 中新建一个不含任何 Activity 的空项目,在 settings.gradle 中引入 Xposed 依赖库:

    1
    2
    3
    4
    5
    6
    7
    8
    
     dependencyResolutionManagement {
         repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
         repositories {
             google()
             mavenCentral()
             maven { url 'https://api.xposed.info/' }  // 添加这一行即可
         }
     }
    

    进入 build.gradle (Module :app) 添加如下依赖:

    1
    2
    3
    
     dependencies {
         compileOnly 'de.robv.android.xposed:api:82' 
     }
    

    由于 Xposed 模块不包含 Activity,因此不含界面,也就无需主题配置,移除相关配置:

    • 移除 src/res/values/themes.xml (night) 中的所有 <item>
    • 移除 AndroidManifest.xml 中的 android:theme="xxx"
  2. 声明 Xposed 模块

    新建 src/res/values/arrays.xml,在其中配置模块作用域:

    1
    2
    3
    4
    5
    6
    
     <resources>
         <string-array name="xposedscope" >
             <!-- 模块作用域应用的包名 -->
             <item>an.droid.j</item> 
         </string-array>
     </resources>
    

    修改 AndroidManifest.xml,加入模块声明以让框架识别出应用类别:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
     <application 
         OtherSettings="xxx" >
            
         <meta-data
             android:name="xposedmodule"
             android:value="true"/>
        
         <!-- 模块的简介(在框架中显示) -->
         <meta-data
             android:name="xposeddescription"
             android:value="一个简单的Xposed框架" />
        
         <!-- 模块最低支持的Api版本 一般填54即可 -->
         <meta-data
             android:name="xposedminversion"
             android:value="54"/>
        
         <!-- 模块作用域 -->
         <meta-data
             android:name="xposedscope"
             android:resource="@array/xposedscope"/>
     </application>
    

    最后修改启动配置,勾选Always install with package manager并且将Launch Options改成Nothing

  3. 模块编写

    新建 src/main/assets/xposed_init,在其中填写入口类名,我的是 com.aaroncomo.simplexposed.MainHook

    根据上述填写的入口类名,创建相应 java 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
     package com.aaroncomo.simplexposed;
        
     import de.robv.android.xposed.IXposedHookLoadPackage;
     import de.robv.android.xposed.callbacks.XC_LoadPackage;
        
     public class MainHook implements IXposedHookLoadPackage {
            
         // 具体的hook逻辑
         private void hookJ(XC_LoadPackage.LoadPackageParam lpparam) {}
            
         @Override
         public void handleLoadPackage(
             XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
             // 过滤应用
             if (lpparam.packageName.equals("me.kyuubiran.xposedapp")) {
                 hookJ(lpparam);
             }
         }
     }
    
    • 实现 IXposedHookLoadPackage 接口
    • 重写父类方法 handleLoadPackage() 在其中找到我们的目标包名并调用 hook() 来完成相关代码逻辑

    OK,编译并推送到手机上,打开 LSPosed,可以看到模块界面已经出现了刚刚写的模块 SimpleXposed!第一个 Xposed 模块初步完成。选择我们的 jstz.apk 为作用域:

    image-20240526215557959image-20240526215450446
  4. hook 逻辑编写

    通过上一节的分析,我们需要获取 zygote 并将时间设为 9999900

    首先对 MainActivity 中的 protected void onCreate(Bundle bundle) 进行 hook,如果成功的话,只要我们打开应用就会弹出一个窗口通知模块已经挂载。这里因为我们 hook 的是 MainActivity,它本身就是一个上下文所以可以直接强转为 Context,作为 Toast.makeText() 的参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
     // Hook主函数,弹出提示
     XposedHelpers.findAndHookMethod(
         "an.droid.j.MainActivity",
         lpparam.classLoader,
         "onCreate",
         Bundle.class,
         new XC_MethodHook() {
             @Override
             protected void beforeHookedMethod(MethodHookParam param) 
                 throws Throwable {
                 super.beforeHookedMethod(param);
                 Toast.makeText(
                     (Activity) param.thisObject, 
                     "LSPosed: 模块加载成功!", 
                     Toast.LENGTH_SHORT
                 ).show();
             }
         }
     );
    

    编译一下,推送到手机,打开 jstz.apk,模块成功挂载:

    image-20240526221143895

    接下来,对 MainActivity$3 中的 onClick() 进行 hook,劫持结束按钮的行为。每次点击时我们之间调用 libj.so 中的 p() 函数对 zygote 进行迭代,最后修改累计时间val$tv1 绕过 if 函数的判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    
     // Hook结束按钮,直接输出flag
     XposedHelpers.findAndHookMethod(
         "an.droid.j.MainActivity$3",
         lpparam.classLoader,
         "onClick",
         View.class,
         new XC_MethodHook() {
             @Override
         	protected void beforeHookedMethod(MethodHookParam param) 
                 throws Throwable {
                 super.beforeHookedMethod(param);
                 XposedBridge.log("Before onClick");
        
                 // 获取 MainActivity 实例
                 Object mainActivity$3 = param.thisObject;
                 Object mainActivity 
                     = XposedHelpers.getObjectField(mainActivity$3, "this$0");
        
                 // 修改 df_all 字段的值,使条件总是为 true
                 XposedHelpers.setIntField(mainActivity, "df_all", 9999900);
        
                 // 计算df_all=99999时,zygote的值
                 zygote = XposedHelpers.getIntField(mainActivity, "zygote");
                 for (int i = 0; i < 99999; i++) {
                     zygote = (int) XposedHelpers.callMethod(
                         mainActivity, 
                         "p", 
                         zygote
                     );
                 }
             }
        
             @Override
         	protected void afterHookedMethod(MethodHookParam param) 
                 throws Throwable {
                 super.afterHookedMethod(param);
                 Object mainActivity$3 = param.thisObject;
                 Object mainActivity = 
                     XposedHelpers.getObjectField(mainActivity$3, "this$0");
                 TextView tv1 = (TextView) 
                     XposedHelpers.getObjectField(mainActivity$3, "val$tv1");
        
                 // 修改 TextView 内容
                 tv1.append("\nLSPosed: Hook成功!");
                 tv1.append("\nzygote: " + zygote);
             }
         }
     );
    

    编译运行,直接点击 END 按钮:

    image-20240526221245402

    在不到 1s 后就可以看到显示出了 zygote 的值,这与老师用 frida 的输出一致但是效率是 frida 的近 50 倍。但是让人奇怪的是 flag,它里面是一句话,告诉我们这不是真的 flag,它还藏在其他地方。

Native 层 Hook

又回去看了一眼代码,发现 java 层已经没有任何有用的信息了,结合 MainActivity 中一直在调用 libj.so,猜测真正的 flag 应该在 native 层里。

  1. 静态分析

    使用 Binary Ninja 对 libj.so 进行反编译,能够看到如下三个函数:

    image-20240526222212095

    j()p() 都在应用中调用了。首先看一下 j() 的逻辑。

    image-20240526222520476

    找到了我之前输出的假 flag:FlagLostHelpMeGetItBack,大概看了一下逻辑,没有其他的输出类代码或者 flag 构建的了。

    接着换到 p() 中,截取了其中一部分,看到这一堆 if 嵌套就知道静态分析已经没戏了

    image-20240526222538484

    image-20240526222555362

    切到控制流看一眼:

    image-20240526222748101

    好家伙,说是电路板我都信,这个应该是经过了代码混淆的效果。不过我们已经知道了它的功能是根据当前的 zygote 状态计算下一个状态,不分析也没什么关系。

    这两个函数都跟 flag 没啥关系,继续对最后一个 init() 函数进行分析。最关键的代码是这里:

    image-20240526223545486

    高亮处在进行移位等操作,很快就能联想到解密操作。另外程序其实并没有调用这个函数,所以猜测这个就是真正输出 flag 的函数。

  2. hook 逻辑编写

    其实代码并不难写,就一句话:

    1
    2
    3
    4
    5
    6
    
     // 获取真正flag
     String flag = (String) XposedHelpers.callMethod(
         mainActivity, 
         "init", 
         zygote
     );
    

    MainActivity$3onClick() 执行的最后把 zygote 送进 init() 函数,看看返回的是不是个 flag。以防万一,还是加个异常处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     try {
         String flag = (String) XposedHelpers.callMethod(
             mainActivity, 
             "init", 
             zygote
         );
     } catch (Exception e) {
         XposedBridge.log(e.getMessage());
     }
    

    编译运行,啥也没出来:

    image-20240526224000106

    看一下后台日志:

    image-20240526224454715

    抛出一个 java.lang.NoSuchMethodError 提示找不到 init() 方法,因此调用失败了。想想也是,一个 native 方法如果没有被声明肯定是调用不到的,毕竟 JNI 只有在明确声明的情况下才会被激活。

  3. 解决调用问题

    1. 最初的想法很简单,直接在模块里加载我放在 /sdcard 下的 lib.so 库,然后调用 init()。尝试之后提示找不到 init() 方法
    2. 第二次尝试把 libj.so 编译进模块里作为依赖,依旧是找不到 init() 方法
    3. 想起反编译的时候,init() 全名叫 Java_an_droid_j_MainActivity_init() 这个名字里是有包名的,所以猜测找不到函数体的原因就是因为我是在模块里调用的。这次开始尝试直接在原始软件里声明这个 native 方法。
  4. 声明 native 方法

    最初尝试了通过 Xposed 向目标程序注入声明,但是发现没法对一个类注入函数声明(应该是有办法的,但是因为第一次写 Xposed 所以没找到),那么改个思路,反正有源码了,直接从 smali 代码入手,通过中间层代码进行注入。

    image-20240526225631435

    可以看到 java 的语句 public native int p(int); 对应的 smali 代码是上图的形式,模仿这个形式手写一个 public native String init(int); 的声明:

    image-20240526225833407 image-20240526225858746

    添加完之后反编译到 java,可以看到已经成功加上了一条 native 方法声明。将应用程序重新打包并签名,覆盖之前到应用。

    重启新的程序,Xposed 模块会自动挂载。点击 END 按钮,终于输出了真正的 flag:

    image-20240526230105526

getflag.apk 逆向

静态分析

  1. 应用分析

    打开应用程序,可以看到这是个登陆界面,推测登陆成功即可输出 flag:

    image-20240526230410308

  2. 源码分析

    代码太长了,这里就只说关键的地方了。

    首先是 MainActivity.onCreate()。这个通过 AES 算法对某个字符串进行了解密操作,并且将其存储在 str 变量中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    
     public class MainActivity extends AppCompatActivity {
         private String str;
        
         public void onCreate(Bundle bundle) {
             super.onCreate(bundle);
             setContentView(0x7f0b001c);
             TextView textView = (TextView) findViewById(0x7f08019f);
             TextView textView2 = (TextView) findViewById(0x7f08011c);
             Button materialButton = (Button) findViewById(0x7f0800cf);
             byte[] bArr = {
                 11, 22, 13, 23, 121, 32, 15, 27,
                 44, 35, 43, 32, 13, 24, 13, 111
             };
             try {
                 this.str = decrypt(
                     "AES/CBC/PKCS5Padding",
                     "HyKsaPpqT4l436tHiSEXtIlLgVV4GE7mGc"
                     + "2WoI0KlP2YhKFco7OPcJYtS58BFwDq",
                     new SecretKeySpec(
                         new byte[]{
                             12, 32, 13, 14, 23, 108, 31, 108, 
                             44, 121, 42, 121, 42, 113, 41, 124
                         }, 0, 16, "AES"
                     ),
                     new IvParameterSpec(
                         new byte[]{
                             12, 32, 13, 14, 23, 108, 31, 108, 
                             44, 121, 42, 121, 42, 113, 41, 124
                         }
                     )
                 );
             } catch (Exception e) {
                 e.printStackTrace();
             }
             materialButton.setOnClickListener(
                 new MainActivity$1(this, textView2, bArr, textView)
             );
         }
            
         ...
     }
    

    然后就是 MainActivity$1.onClick()。这个函数定义了在登陆界面的用户名和密码提交时对密码与用户名进行匹配,如果成功则通过 Toast 弹出一个数值进行显示,推测就是 flag。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
     class MainActivity$1 implements View.OnClickListener {
        	
         ...
                
         @Override
         public void onClick(View view) {
             String securePassword = MainActivity.access$000(
                 this.this$0,
                 this.val$textView2.getText().toString(),
                 this.val$bArr
             );
             if (!this.val$textView.getText().toString().equals("hillstone") 
                 || !securePassword.equals(
                     "4368354b57abefdb3da930aa1f7db42c" 
                     + "3a1d318401d7474c25f5a14bbaf8fb34"
                 )) {
                 Toast.makeText(
                     (Context) this.this$0,
                     (CharSequence) "Login Failure",
                     0
                 ).show();
                 System.out.println(securePassword);
             } else {
                 MainActivity mainActivity = this.this$0;
                 Toast.makeText(
                     (Context) mainActivity,
                     (CharSequence) MainActivity.access$100(mainActivity), 
                     0
                 ).show();
             }
         }
     }
    

Hook

这道题解法很多,老师讲的是用 frida 去对登陆界面进行动态改动。但其实如果用 Xposed 的话会变得极其简单,因为它能在代码的任何位置进行劫持。既然这样就没有必要去动登陆逻辑了,因为 flag 在 onCreate() 就算出来了,因此我们直接在程序启动的时候 hook 就大功告成了,后面根本就不用管。

  1. 新增目标作用域

    在 src/res/values/arrays.xml 中加入新的作用域

    1
    
     <item>com.ayy.hsapk1</item>
    
  2. 重写接口

    基于上一题的代码,加入对新的包名的劫持:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     @Override
     public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) 
         throws Throwable {
        
         if (lpparam.packageName.equals("com.ayy.hsapk1")) 
             hookGetFlag(lpparam);
         if (lpparam.packageName.equals("an.droid.j")) 
             hookJ(lpparam);
     }
    
  3. hook 逻辑编写

    onCreate() 进行劫持,在执行前调用 Toast.makeText() 弹出挂载提示,在执行后获取已经解密的 flag,用一个 Toast 弹出。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
     private void hookGetFlag(XC_LoadPackage.LoadPackageParam lpparam) {
         XposedHelpers.findAndHookMethod(
             "com.ayy.hsapk1.MainActivity",
             lpparam.classLoader,
             "onCreate",
             Bundle.class,
             new XC_MethodHook() {
                 @Override
                 protected void beforeHookedMethod(MethodHookParam param) 
                     throws Throwable {
                     super.beforeHookedMethod(param);
                     Toast.makeText(
                         (Activity) param.thisObject, 
                         "LSPosed: 模块加载成功!", 
                         Toast.LENGTH_SHORT).show();
                 }
                    
                 @Override
                 protected void afterHookedMethod(MethodHookParam param) 
                     throws Throwable {
                     super.afterHookedMethod(param);
                     String str = (String) XposedHelpers.getObjectField(
                         param.thisObject, "str"
                     );
                     Toast.makeText(
                         (Activity) param.thisObject, 
                         str, 
                         Toast.LENGTH_SHORT
                     ).show();
                 }
             }
         );
     }
    
  4. 测试

    编译运行,打开 getflag.apk,首先弹出了挂载提示:

    image-20240526232829190

    等这条提示消失后,紧接着弹出了第二条提示,输出了 flag:

    image-20240526232921679

    这样我们就绕过了登陆界面的 hook,用最简单的方式得到了 flag。

1
2
3
4
5
6
7
8
9
10
11
12
int vuln() {
    int var_10 = 10, var_4 = 0, var_C = 0, var_8;

    while (var_C < var_10) {
        var_4++;
        if (var_4 > 5) 
            var_8 = 44;
    	var_C++;
    }

    return var_8;
}
This post is licensed under CC BY 4.0 by the author.