NSSCTF-安卓逆向工坊WP

发布于 19 天前  28 次阅读


[RE4]P1

先查看Activity的包名

mainactivity源码

package com.example.admodel;
  
  
import android.app.Activity;
  
import android.os.Bundle;

import android.text.TextUtils;
  
import android.view.View;
  
import android.widget.Button;
  
import android.widget.EditText;
  
import android.widget.Toast;
  
import androidx.appcompat.app.AppCompatActivity;
  
  
/* loaded from: classes2.dex */
  
public class MainActivity extends AppCompatActivity {
  
    private static String LOG_TAG = rootTest.class.getName();
  
    private Button btnlogin;
  
    private EditText lgpassword;
  
    private String word = "ajksdkljahfklasjhlkdnalsnflkamskldjlkajhflkbalksndlkanfblasd";
  
  
    /* JADX INFO: Access modifiers changed from: protected */
  
    @Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
  
    public void onCreate(Bundle savedInstanceState) {
  
        super.onCreate(savedInstanceState);
  
        setContentView(R.layout.activity_main);
  
        if (isDeviceRooted()) {
  
            Toast.makeText(this, "root", 1).show();
  
        }
  
        getSupportActionBar().setTitle("Crack Me!");
  
        this.btnlogin = (Button) findViewById(R.id.btnlogin);
  
        this.lgpassword = (EditText) findViewById(R.id.lgusername);
  
        SPDataUtils.saveInfo(this, this.word);
  
        final Userinfo user = SPDataUtils.getInfo(this);
  
        user.getPassword();
  
        final SM4Utils sm4 = new SM4Utils();
  
        sm4.setSecretKey("JeF8U9wHFOMfs2Y8");
  
        System.out.println("CBC模式");
  
        sm4.setIv("UISwD9fW6cFh9SNS");
  
        this.btnlogin.setOnClickListener(new View.OnClickListener() { // from class: com.example.admodel.MainActivity.1
  
            @Override // android.view.View.OnClickListener
  
            public void onClick(View v) {
  
                String password = MainActivity.this.lgpassword.getText().toString();
  
                String cipherText = sm4.encryptData_CBC(password);
  
                if (TextUtils.equals(cipherText, user.getPassword())) {
  
                    Toast.makeText(MainActivity.this, "success", 1).show();
  
                } else {
  
                    Toast.makeText(MainActivity.this, "Fail", 1).show();
  
                }
  
            }
  
        });
  
    }
  
  
    private Activity getActivity() {
  
        return null;
  
    }
  
  
    public static boolean isDeviceRooted() {
  
        return rootTest.checkDeviceDebuggable() || rootTest.checkSuperuserApk() || rootTest.checkBusybox() || rootTest.checkAccessRootData() || rootTest.checkGetRootAuth();
  
    }
  
}

很明显是sm4加密,给了key和iv

sm4.setSecretKey("JeF8U9wHFOMfs2Y8");
  
System.out.println("CBC模式");
  
sm4.setIv("UISwD9fW6cFh9SNS");

代码的主要逻辑是就是实现了一个登录的功能,并且判断密码

public void onClick(View v) {
  
                String password = MainActivity.this.lgpassword.getText().toString();
  
                String cipherText = sm4.encryptData_CBC(password);
  
                if (TextUtils.equals(cipherText, user.getPassword())) {
  
                    Toast.makeText(MainActivity.this, "success", 1).show();
  
                } else {
  
                    Toast.makeText(MainActivity.this, "Fail", 1).show();
  
                }

将用户输入的密码sm4加密过后与正确密码的sm4值比较

双击就可以看到getPassword的内容也是真实的密码

kYEI25N30vqgSURbd5vEmz/yHt1SMH5YtoRKdXvuPtHrbuuaeOYgeyb1p0fgaq4D$
from gmssl.sm4 import CryptSM4, SM4_DECRYPT
import base64

def sm4_decrypt_cbc(key, iv, encrypted_data):
    crypt_sm4 = CryptSM4()
    crypt_sm4.set_key(key.encode('utf-8'), SM4_DECRYPT)
    decrypted_data = crypt_sm4.crypt_cbc(iv.encode('utf-8'), encrypted_data)
    return decrypted_data

# 示例用法
if __name__ == "__main__":
    # 密钥 (16字节)
    key = "JeF8U9wHFOMfs2Y8"
    
    # 初始化向量 (IV) (16字节)
    iv = "UISwD9fW6cFh9SNS"
    
    # Base64解码加密后的数据
    encrypted_data = base64.b64decode("kYEI25N30vqgSURbd5vEmz/yHt1SMH5YtoRKdXvuPtHrbuuaeOYgeyb1p0fgaq4D")

    # 解密
    decrypted_data = sm4_decrypt_cbc(key, iv, encrypted_data)

    # 输出解密后的数据
    print("Decrypted data:", decrypted_data.decode('utf-8'))
flag{9b7fe9357fe94008915ef1e5574bb0a5}

[RE4]P2

先看activity

package com.example.encode1;
  
  
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;
  
  
/* loaded from: classes3.dex */
  
public class MainActivity extends AppCompatActivity {
  
    private Button bt;
  
    private EditText input;
  
  
    /* JADX INFO: Access modifiers changed from: protected */
  
    @Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
  
    public void onCreate(Bundle savedInstanceState) {
  
        super.onCreate(savedInstanceState);
  
        setContentView(R.layout.activity_main);
  
        this.input = (EditText) findViewById(R.id.input);
  
        Button button = (Button) findViewById(R.id.bt);
  
        this.bt = button;
  
        button.setOnClickListener(new View.OnClickListener() { // from class: com.example.encode1.MainActivity.1
  
            @Override // android.view.View.OnClickListener
  
            public void onClick(View view) {
  
                if (new Check().check(MainActivity.this.input.getText().toString())) {
  
                    Toast.makeText(MainActivity.this, "success", 1).show();
  
                } else {
  
                    Toast.makeText(MainActivity.this, "failed", 1).show();
  
                }
  
            }
  
        });
  
    }
  
}

主要实现了一个登录登录的功能,由check类中的check函数来判断正确

package com.example.encode1;
  
  
import android.util.Base64;
  
import javax.crypto.Cipher;
  
import javax.crypto.spec.IvParameterSpec;
  
import javax.crypto.spec.SecretKeySpec;
  
  
/* loaded from: classes3.dex */
  
public class Check {
  
    public static String Encrypt(String sSrc) {
  
        try {
  
            byte[] raw = "OcpP0q9cSBfB6jky".getBytes("utf-8");
  
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
  
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  
            IvParameterSpec iv = new IvParameterSpec("hUgfkF6kl1iaohTf".getBytes());
  
            cipher.init(1, skeySpec, iv);
  
            byte[] encrypted = cipher.doFinal(sSrc.getBytes());
  
            return Base64.encodeToString(encrypted, 2);
  
        } catch (Exception e) {
  
            e.printStackTrace();
  
            return null;
  
        }
  
    }
  
  
    /* JADX INFO: Access modifiers changed from: package-private */
  
    public boolean check(String input) {
  
        if (Encrypt(input).equals("RiOMdgTJh9iZxh+SWt1srcjUVZo0RgZ9pdRTiA0YPjZHIPkrOxaQf10NKsjowDlY")) {
  
            return true;
  
        }
  
        return false;
  
    }
  
}

很明显是个AES加密,给了key和iv

from Crypto.Cipher import AES

import base64

  

def decrypt_aes_cbc(encrypted_data, key, iv):

    # 初始化AES解密器

    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))

    # Base64解码密文

    encrypted_data_bytes = base64.b64decode(encrypted_data)

    # 解密数据

    decrypted_data = cipher.decrypt(encrypted_data_bytes)

    # 去除填充

    decrypted_data = unpad(decrypted_data)

    return decrypted_data.decode('utf-8')

  

def unpad(data):

    # 去除PKCS5Padding填充

    padding_len = data[-1]

    return data[:-padding_len]

  

if __name__ == "__main__":

    # 密钥和IV必须与加密时使用的一致

    key = "OcpP0q9cSBfB6jky"

    iv = "hUgfkF6kl1iaohTf"

    # 加密后的密文(Base64编码后的)

    encrypted_data = "RiOMdgTJh9iZxh+SWt1srcjUVZo0RgZ9pdRTiA0YPjZHIPkrOxaQf10NKsjowDlY"

    # 解密

    decrypted_text = decrypt_aes_cbc(encrypted_data, key, iv)

    print("Decrypted text:", decrypted_text)
flag{2c9c57cb54ce6d2fc5421eb65bf6adbf}

[RE4]P4

查看activity

很明显t是密文,然后t的值传给了n,然后调用了zzsence.a

{127, 41, 32, -23, 53, -113, -59, 154, 5, 16, 52, 188, 91, 150, 43, 163, 140, 170, 158, 36, 145, 140, 211, 17, 18, 79, 200, 177, 122, 78, 219, 247}

很明显是AES加密,但是没用key和 iv

调用了loadlibrary

key和iv是由native层生成的

so代码(不会逆)

使用jeb动态调试 首先要查找入口包名,其实刚才在manifest已经知道了入口 如果包很多的话其实不容器看出来,可以用aapt获取入口包名 用法

aatp dump badging apk的名字

可以看到获取的入口包名和manifest里面是一样的

或者也可以使用

adb shell dumpsys activity top

查看当前位于前台的应用程序的信息(需要启动应用)

开启APK调试模式

 adb shell am start -D -n 包名/类名

这里的包名/类名要用adb的写法来 com.zhuotong.easyctf2/.MainActivity (高版本的安卓系统可能不能直接debug)

进去debug模式后用jeb attach上这个包

应用已经进入主窗口

调用生成ket和iv的地方

下断点

获得key值

[-95, 109, 22, -2, 26, -6, 48, 95, -41, 126, 94, -98, -20, 107, -97, -35]

由于iv不是以赋值的形式生成,需要进入函数内部来查看

步入一个函数看看,iv还没被生成

步入最后一步,可以看到iv已经生成了

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]

解密

key
[-95, 109, 22, -2, 26, -6, 48, 95, -41, 126, 94, -98, -20, 107, -97, -35]

iv
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]

密文
[127, 41, 32, -23, 53, -113, -59, 154, 5, 16, 52, 188, 91, 150, 43, 163, 140, 170, 158, 36, 145, 140, 211, 17, 18, 79, 200, 177, 122, 78, 219, 247]
from Crypto.Cipher import AES
import base64

def decrypt_aes_cbc(ciphertext, key, iv):
    # 将 key, iv, 和 ciphertext 转换为字节数组
    key_bytes = bytes([(i + 256) % 256 for i in key])
    iv_bytes = bytes([(i + 256) % 256 for i in iv])
    ciphertext_bytes = bytes([(i + 256) % 256 for i in ciphertext])
    
    # 创建AES解密器
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
    
    # 解密数据
    decrypted_data = cipher.decrypt(ciphertext_bytes)
    
    # 移除填充
    decrypted_data = unpad(decrypted_data)
    
    return decrypted_data.decode('utf-8')

def unpad(data):
    # 去除PKCS5Padding填充
    padding_len = data[-1]
    return data[:-padding_len]

if __name__ == "__main__":
    key = [-95, 109, 22, -2, 26, -6, 48, 95, -41, 126, 94, -98, -20, 107, -97, -35]
    iv = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
    ciphertext = [127, 41, 32, -23, 53, -113, -59, 154, 5, 16, 52, 188, 91, 150, 43, 163, 140, 170, 158, 36, 145, 140, 211, 17, 18, 79, 200, 177, 122, 78, 219, 247]
    
    # 执行解密
    decrypted_text = decrypt_aes_cbc(ciphertext, key, iv)
    
    print("Decrypted text:", decrypted_text)

[RE4]P5

代码给了很多,直接看flag逻辑部分,代码实现了一个计时器的功能,当时间为66.666s时输出flag

生成flag的代码很长很复杂,但是代码都是java层的,也可以不用frida执行模拟执行

使用frida hook 执行这个函数

setImmediate(function () { //立即执行脚本

    Java.perform(function () {

        Java.scheduleOnMainThread(function () {

                var main = Java.use('com.moible.r15.main');//获取目标类

                var mains = main.$new();//创建一个新的实例

                var result = mains.getit("66.666s");//调用目标函数

                console.log("Result: " + result);//打印结果

        });

    });

});

这里面有个坑,可能是frida版本的原因,代码不是运行在主线程,会抛出RuntimeException

要解决这个问题,需要确保在主线程(UI线程)上执行代码。Frida 提供了 Java.scheduleOnMainThread 方法,可以确保代码在主线程上运行。

[RE4]P6

引入了一个so

主要判断逻辑

调用的是native层的代码

直接可以看到hello函数,很明显是个换表base64

主要逻辑是将输入的内容base64编码后再与加密过的flag比较,我们只需要找到base64所用的表即可解密

[RE4]P7

有三个反调试函数

但都只是在java层上反调试,所以直接用frida hook掉返回值即可

setImmediate(function () {
    Java.perform(function () {
        Java.scheduleOnMainThread(function () {
  
                let c = Java.use("sg.vantagepoint.a.c");
                c["a"].implementation = function () {

                    return false;
                };

                c["b"].implementation = function () {

                    return false;
                };

                c["c"].implementation = function () {

                    return false;
                };

        });
    });
});

可以看到提示已经消失了,而且程序没有退出

flag判断逻辑

很明显是AES加密,第一个是key,第二个是密文

直接解密


Defend with determination