Bootstrap

CTF-RE 从0到 N: 高版本 APK 调试 + APK逻辑修改再打包 + os层调试[2024 强网杯青少年专项赛 Flip_over] writeup

非常好的题,很适合新手入门!!!

how tu use JEB

通过百度网盘分享的文件:app-debug.apk
链接:https://pan.baidu.com/s/11oPBq7LTnzasuefGeU6mXA?pwd=1111 
提取码:1111 
--来自百度网盘超级会员V2的分享

step1

反编译查看Manifest

   android:allowBackup="true"
    android:appComponentFactory="androidx.core.app.CoreComponentFactory"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:debuggable="true"
    android:extractNativeLibs="false"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.Flipover">

能发现其中的android:debuggable="true"

此题目在低版本不可用(安卓9)

使用 Android Studio Emulator
注意:
1.虚拟机版本要用(APIS),play无法获得root,
2.HAXM安装之后检测不到直接去目录里点.exe按

step2 JEB反编译定位核心代码

请添加图片描述
随便输入一个
请添加图片描述
请添加图片描述
定位到代码

package com.example.flipover;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private Button loginButton;
    private EditText passwordField;
    private EditText usernameField;

    static {
        System.loadLibrary("native-lib");
    }

    @Override  // androidx.fragment.app.FragmentActivity
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(layout.activity_main);
        this.usernameField = (EditText)this.findViewById(id.editTextUsername);
        this.passwordField = (EditText)this.findViewById(id.editTextPassword);
        this.loginButton = (Button)this.findViewById(id.buttonLogin);
        this.loginButton.setOnClickListener((View v) -> {
            if(this.validateLogin(this.usernameField.getText().toString(), this.passwordField.getText().toString())) {
                this.startActivity(new Intent(this, FlipGameActivity.class));
                this.finish();
                return;
            }

            Toast.makeText(this, "Invalid login credentials", 0).show();
        });
    }

    public native boolean validateLogin(String arg1, String arg2) {
    }
}

这是一个 Android 应用的登录界面代码:

  1. 类声明和变量:
public class MainActivity extends AppCompatActivity {
    private Button loginButton;
    private EditText passwordField; 
    private EditText usernameField;
  • 这是主活动类,继承自 AppCompatActivity
  • 定义了登录按钮和用户名、密码输入框的变量
  1. 加载原生库:
static {
    System.loadLibrary("native-lib");
}
  • 加载包含原生代码的库文件(native-lib)
  1. onCreate 方法:
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(layout.activity_main);
  • 活动创建时调用
  • 设置界面布局
  1. 初始化 UI 元素:
this.usernameField = (EditText)this.findViewById(id.editTextUsername);
this.passwordField = (EditText)this.findViewById(id.editTextPassword);
this.loginButton = (Button)this.findViewById(id.buttonLogin);
  • 获取并关联界面上的输入框和按钮
  1. 按钮点击事件处理:
this.loginButton.setOnClickListener((View v) -> {
    if(this.validateLogin(usernameField.getText().toString(), 
                         passwordField.getText().toString())) {
        this.startActivity(new Intent(this, FlipGameActivity.class));
        this.finish();
        return;
    }
    Toast.makeText(this, "Invalid login credentials", 0).show();
});
  • 设置登录按钮的点击监听器
  • 调用 validateLogin 验证登录信息
  • 如果验证成功,跳转到游戏界面
  • 验证失败则显示错误提示
  1. 原生方法声明:
public native boolean validateLogin(String arg1, String arg2);
  • 声明一个原生方法用于验证登录
  • 具体实现在 C/C++ 代码中

这是一个包含原生代码(JNI)的 Android 登录界面,主要功能是:
7. 提供用户名和密码输入
8. 通过原生代码验证登录信息
9. 验证成功后跳转到游戏界面
10. 验证失败显示错误提示

step3 动态调试使得条件强制不成立

强制更改其寄存器值进入密码正确分支
请添加图片描述
运行后成功进入请添加图片描述
查看game类

package com.example.flipover;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Arrays;

public class FlipGameActivity extends AppCompatActivity {
    private Button buttonSubmit;
    private EditText editTextInput;

    // 加载native库
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(layout.activity_flip_game);

        // 初始化UI组件
        editTextInput = (EditText) findViewById(id.editTextInput);
        buttonSubmit = (Button) findViewById(id.buttonSubmit);
        RecyclerView recyclerView = (RecyclerView) findViewById(id.recyclerViewCards);

        // 设置RecyclerView的布局管理器为3列的网格布局
        recyclerView.setLayoutManager(new GridLayoutManager(this, 3));

        recyclerView.setAdapter(new CardAdapter(this, Arrays.asList(new String[]{
            "Stay positive!", "Dream big!", "Believe in yourself!", 
            "Stay curious!", "Keep learning!", "Chase your dreams!", 
            "Stay strong!", "Be kind!", "You're amazing!"
        })));

        // 设置提交按钮的点击监听器
        buttonSubmit.setOnClickListener((View v) -> {
            // 获取输入文本并验证加密
            if (!validateAndEncrypt(editTextInput.getText().toString().trim())) {
                Toast.makeText(this, "Invalid input", Toast.LENGTH_SHORT).show();
                return;
            }

            Toast.makeText(this, "Congratulations, you have flipped to a hidden secret", Toast.LENGTH_SHORT).show();
        });
    }

    // 声明本地方法,用于验证和加密输入
    public native boolean validateAndEncrypt(String arg1);
}

导出.os在 ida 分析validateAndEncrypt函数

__int64 __fastcall Java_com_example_flipover_FlipGameActivity_validateAndEncrypt(__int64 a1, __int64 a2, __int64 a3)
{
  __int64 input; // x21
  __int64 i; // x8
  unsigned int v7; // w19
  int v8; // w4
  int v10; // w4
  _OWORD v11[2]; // [xsp+10h] [xbp-100h]
  unsigned __int64 v12; // [xsp+30h] [xbp-E0h]
  __int16 v13; // [xsp+38h] [xbp-D8h]
  _BYTE v14[64]; // [xsp+3Eh] [xbp-D2h] BYREF
  _BYTE v15[64]; // [xsp+7Eh] [xbp-92h] BYREF
  char v16[65]; // [xsp+BEh] [xbp-52h] BYREF
  char dest[4]; // [xsp+FFh] [xbp-11h] BYREF
  char v18[5]; // [xsp+103h] [xbp-Dh] BYREF
  __int64 v19; // [xsp+108h] [xbp-8h]

  v19 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  input = (*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0LL);
  if ( strlen((const char *)input) == 42
    && !strncmp((const char *)input, "flag{", 5uLL)
    && *(_BYTE *)(input + 41) == 125 )
  {
    strncpy(dest, (const char *)input, 4uLL);
    strncpy(v18, (const char *)input, 4uLL);
    v18[4] = 0;
    memcpy(v16, "a4c3f8927d9b8e6d6e483fa2cd0193b0a6e2f19c8b47d5a8f3c7a91e8d4b9f67", sizeof(v16));
    sub_AF3F4(dest, v16, 65LL, v14);
    sub_AF47C(dest, v14, 64LL, v15);
    i = 0LL;
    v13 = -28958;
    v12 = 0x89DDAB508133AF93LL;
    v11[1] = xmmword_74CEB;
    v11[0] = xmmword_74CDB;
    while ( (*(_BYTE *)(input + i) ^ 0x21 ^ v15[i]) == *((unsigned __int8 *)v11 + i) )
    {
      if ( ++i == 42 )
      {
        (*(void (__fastcall **)(__int64, __int64, __int64))(*(_QWORD *)a1 + 1360LL))(a1, a3, input);
        v7 = 1;
        __android_log_print(3, "NativeLib", "JNI_TRUE[%d]: %02x", 1, v8);
        return v7;
      }
    }
    (*(void (__fastcall **)(__int64, __int64, __int64))(*(_QWORD *)a1 + 1360LL))(a1, a3, input);
    __android_log_print(3, "NativeLib", "JNI_FALSE[%d]: %02x", 0, v10);
  }
  else
  {
    (*(void (__fastcall **)(__int64, __int64, __int64))(*(_QWORD *)a1 + 1360LL))(a1, a3, input);
  }
  return 0;
}

到此为止可以硬逆,但是本题既然是动态调试那我们就继续调试.os层

step4 修改代码逻辑并重新打包

我们可以使用MT管理器修改代码逻辑让其无论输入任何密码都为正确

00000028  invoke-virtual      MainActivity->validateLogin(String, String)Z, p0, v0, v1
0000002E  move-result         v2
00000030  if-eqz              v2, :50
:34
00000034  new-instance        v2, Intent
00000038  const-class         v3, FlipGameActivity
0000003C  invoke-direct       Intent-><init>(Context, Class)V, v2, p0, v3
00000042  invoke-virtual      MainActivity->startActivity(Intent)V, p0, v2
00000048  invoke-virtual      MainActivity->finish()V, p0
0000004E  goto                :64
:50
00000050  const-string        v2, "Invalid login credentials"
00000054  const/4             v3, 0
00000056  invoke-static       Toast->makeText(Context, CharSequence, I)Toast, p0, v2, v3
0000005C  move-result-object  v2
0000005E  invoke-virtual      Toast->show()V, v2
:64
00000064  return-void

根据包名找到类
请添加图片描述
定位到汇编

请添加图片描述

请添加图片描述
改为if-nez反转if条件,保存后重签名

请添加图片描述
成功!现在无论输入什么都会进入游戏界面!

step4 .os层调试

将ida服务器拖入虚拟机按如下操作运行,挂载ida服务器

C:\Users\A5rZ_admin>adb forward tcp:23946 tcp:23946
 23946
C:\Users\A5rZ_admin>adb shell
emu64xa:/ # mv /sdcard/Download/android_server /data/local/tmp/
emu64xa:/ # chmod 755 /data/local/tmp/android_server
emu64xa:/ # /data/local/tmp/android_server
IDA Android 64-bit remote debug server(ST) v9.0.30. Hex-Rays (c) 2004-2024
2024-11-25 14:25:44 Listening on 0.0.0.0:23946...

启动debug

adb shell am start -D -n com.example.flipover/.MainActivity

附加到程序
请添加图片描述
先睡了

;