安全风险 - 检测Android设备系统是否已Root

在很多app中都禁止 root 后的手机使用相关app功能,这种场景在金融app、银行app更为常见一些;当然针对 root 后的手机,我们也可以做出风险提示,告知用户当前设备已 root ,谨防风险!

最近在安全检测中提出了一项 风险bug:当设备已处于 root 状态时,未提示用户风险!

那么我们首先要做的就是 Android 设备的 Root 检测,然后根据业务方的述求,给出风险提示或者禁用app

    • 基础认知
    • 细节分析
      • 判断系统内是否包含 su
      • 判断系统内是否包含 busybox
      • 检测系统内是否安装了Superuser.apk之类的App
      • 判断 ro.debuggable 属性和 ro.secure 属性
      • 检测系统是否为测试版
    • 合并实践
      • 封装 RootTool
      • EasyProtector Root检测剥离
      • 调用实践
      • 封装建议(可忽略)

基础认知

Android 设备被 root 后,会多出 su 文件,同时也可能获取超级用户权限,就有可能存在 Superuser.apk 文件 ,所以我们主要从以下几个方面去判断设备被 root

  • 检查系统中是否存在 su 文件
  • 检查系统是否可执行 su 文件
  • 检查系统中是否 /system/app/Superuser.apk 文件(当 root 后会将 Superuser.apk 文件放于 /system/app/中)

细节分析

看了几篇 Blog 后,主要还是借鉴了 Android如何判断系统是否已经被Root + EasyProtector框架 ,希望可以更好的兼容处理方案

判断系统内是否包含 su

 /**
  * 是否存在su命令,并且有执行权限
  *
  * @return 存在su命令,并且有执行权限返回true
  */
 public static boolean isSuEnable() {
     File file = null;
     String[] paths = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/", "/su/bin/"};
     try {
         for (String path : paths) {
             file = new File(path + "su");
             if (file.exists() && file.canExecute()) {
                 Log.i(TAG, "find su in : " + path);
                 return true;
             }
         }
     } catch (Exception x) {
         x.printStackTrace();
     }
     return false;
 }

判断系统内是否包含 busybox

BusyBox 是一个集成了多个常用 Linux 命令和工具的软件,它的主要用途是提供一个基础但全面的 Linux 操作系统环境,适用于各种嵌入式系统和资源受限的环境

 /**
  * 是否存在busybox命令,并且有执行权限
  *
  * @return 存在busybox命令,并且有执行权限返回true
  */
 public static boolean isSuEnable() {
     File file = null;
     String[] paths = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/", "/su/bin/"};
     try {
         for (String path : paths) {
             file = new File(path + "busybox");
             if (file.exists() && file.canExecute()) {
                 Log.i(TAG, "find su in : " + path);
                 return true;
             }
         }
     } catch (Exception x) {
         x.printStackTrace();
     }
     return false;
 }

检测系统内是否安装了Superuser.apk之类的App

 public static boolean checkSuperuserApk(){
     try {
         File file = new File("/system/app/Superuser.apk");
         if (file.exists()) {
             Log.i(LOG_TAG,"/system/app/Superuser.apk exist");
             return true;
         }
     } catch (Exception e) { }
     return false;
 }

判断 ro.debuggable 属性和 ro.secure 属性

默认手机出厂后 ro.debuggable 属性应该为0,ro.secure应该为1;意思就是系统版本要为 user 版本

  private int getroDebugProp() {
     int debugProp;
     String roDebugObj = CommandUtil.getSingleInstance().getProperty("ro.debuggable");
     if (roDebugObj == null) debugProp = 1;
     else {
         if ("0".equals(roDebugObj)) debugProp = 0;
         else debugProp = 1;
     }
     return debugProp;
 }
 
 private int getroSecureProp() {
     int secureProp;
     String roSecureObj = CommandUtil.getSingleInstance().getProperty("ro.secure");
     if (roSecureObj == null) secureProp = 1;
     else {
         if ("0".equals(roSecureObj)) secureProp = 0;
         else secureProp = 1;
     }
     return secureProp;
 }

检测系统是否为测试版

Tips

  • 这种验证方式比较依赖在设备中通过命令进行验证,并不是很适合在软件中直接判断root场景
  • 若是非官方发布版,很可能是完全root的版本,存在使用风险

在系统 adb shell 中执行

# cat /system/build.prop | grep ro.build.tags
ro.build.tags=release-keys

还有一种检测方式是检测系统挂载目录权限,主要是检测 Android 沙盒目录文件或文件夹读取权限(在 Android 系统中,有些目录是普通用户不能访问的,例如 /data/system/etc 等;比如微信沙盒目录下的文件或文件夹权限是否正常)


合并实践

有兴趣的话也可以把 CommandUtilgetProperty方法SecurityCheckUtilroot 相关方法 合并到 RootTool 中,因为我还用到了 EasyProtector框架 的模拟器检测功能,故此处就先不进行二次封装了

封装 RootTool

import android.util.Log;

import java.io.File;

public class RootTool {
    private static final String TAG = "root";

    /**
     * 是否存在su命令,并且有执行权限
     *
     * @return 存在su命令,并且有执行权限返回true
     */
    public static boolean isSuEnable() {
        File file = null;
        String[] paths = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/", "/su/bin/"};
        try {
            for (String path : paths) {
                file = new File(path + "su");
                if (file.exists() && file.canExecute()) {
                    Log.i(TAG, "find su in : " + path);
                    return true;
                }
            }
        } catch (Exception x) {
            x.printStackTrace();
        }
        return false;
    }

    /**
     * 是否存在busybox命令,并且有执行权限
     *
     * @return 存在busybox命令,并且有执行权限返回true
     */
    public static boolean isSuBusyEnable() {
        File file = null;
        String[] paths = {"/system/bin/", "/system/xbin/", "/system/sbin/", "/sbin/", "/vendor/bin/", "/su/bin/"};
        try {
            for (String path : paths) {
                file = new File(path + "busybox");
                if (file.exists() && file.canExecute()) {
                    Log.i(TAG, "find su in : " + path);
                    return true;
                }
            }
        } catch (Exception x) {
            x.printStackTrace();
        }
        return false;
    }

    /**
     * 检测系统内是否安装了Superuser.apk之类的App
     */
    public static boolean checkSuperuserApk() {
        try {
            File file = new File("/system/app/Superuser.apk");
            if (file.exists()) {
                Log.i(TAG, "/system/app/Superuser.apk exist");
                return true;
            }
        } catch (Exception e) {
        }
        return false;
    }

    /**
    *  检测系统是否为测试版:若是非官方发布版,很可能是完全root的版本,存在使用风险
    * */
    public static boolean checkDeviceDebuggable(){
        String buildTags = android.os.Build.TAGS;
        if (buildTags != null && buildTags.contains("test-keys")) {
            Log.i(TAG,"buildTags="+buildTags);
            return true;
        }
        return false;
    }

}

EasyProtector Root检测剥离

为了方便朋友们进行二次封装,在后面我会将核心方法进行图示标明

CommandUtil

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;

/**
 * Project Name:EasyProtector
 * Package Name:com.lahm.library
 * Created by lahm on 2018/6/8 16:23 .
 */
public class CommandUtil {
    private CommandUtil() {
    }

    private static class SingletonHolder {
        private static final CommandUtil INSTANCE = new CommandUtil();
    }

    public static final CommandUtil getSingleInstance() {
        return SingletonHolder.INSTANCE;
    }

    public String getProperty(String propName) {
        String value = null;
        Object roSecureObj;
        try {
            roSecureObj = Class.forName("android.os.SystemProperties")
                    .getMethod("get", String.class)
                    .invoke(null, propName);
            if (roSecureObj != null) value = (String) roSecureObj;
        } catch (Exception e) {
            value = null;
        } finally {
            return value;
        }
    }

    public String exec(String command) {
        BufferedOutputStream bufferedOutputStream = null;
        BufferedInputStream bufferedInputStream = null;
        Process process = null;
        try {
            process = Runtime.getRuntime().exec("sh");
            bufferedOutputStream = new BufferedOutputStream(process.getOutputStream());

            bufferedInputStream = new BufferedInputStream(process.getInputStream());
            bufferedOutputStream.write(command.getBytes());
            bufferedOutputStream.write('\n');
            bufferedOutputStream.flush();
            bufferedOutputStream.close();

            process.waitFor();

            String outputStr = getStrFromBufferInputSteam(bufferedInputStream);
            return outputStr;
        } catch (Exception e) {
            return null;
        } finally {
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (process != null) {
                process.destroy();
            }
        }
    }

    private static String getStrFromBufferInputSteam(BufferedInputStream bufferedInputStream) {
        if (null == bufferedInputStream) {
            return "";
        }
        int BUFFER_SIZE = 512;
        byte[] buffer = new byte[BUFFER_SIZE];
        StringBuilder result = new StringBuilder();
        try {
            while (true) {
                int read = bufferedInputStream.read(buffer);
                if (read > 0) {
                    result.append(new String(buffer, 0, read));
                }
                if (read < BUFFER_SIZE) {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result.toString();
    }
}

SecurityCheckUtil

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.BatteryManager;
import android.os.Process;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * Project Name:EasyProtector
 * Package Name:com.lahm.library
 * Created by lahm on 2018/5/14 下午10:31 .
 */
public class SecurityCheckUtil {

    private static class SingletonHolder {
        private static final SecurityCheckUtil singleInstance = new SecurityCheckUtil();
    }

    private SecurityCheckUtil() {
    }

    public static final SecurityCheckUtil getSingleInstance() {
        return SingletonHolder.singleInstance;
    }

    /**
     * 获取签名信息
     *
     * @param context
     * @return
     */
    public String getSignature(Context context) {
        try {
            PackageInfo packageInfo = context.
                    getPackageManager()
                    .getPackageInfo(context.getPackageName(),
                            PackageManager.GET_SIGNATURES);
            // 通过返回的包信息获得签名数组
            Signature[] signatures = packageInfo.signatures;
            // 循环遍历签名数组拼接应用签名
            StringBuilder builder = new StringBuilder();
            for (Signature signature : signatures) {
                builder.append(signature.toCharsString());
            }
            // 得到应用签名
            return builder.toString();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 检测app是否为debug版本
     *
     * @param context
     * @return
     */
    public boolean checkIsDebugVersion(Context context) {
        return (context.getApplicationInfo().flags
                & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
    }

    /**
     * java法检测是否连上调试器
     *
     * @return
     */
    public boolean checkIsDebuggerConnected() {
        return android.os.Debug.isDebuggerConnected();
    }

    /**
     * usb充电辅助判断
     *
     * @param context
     * @return
     */
    public boolean checkIsUsbCharging(Context context) {
        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = context.registerReceiver(null, filter);
        if (batteryStatus == null) return false;
        int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
        return chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
    }

    /**
     * 拿清单值
     *
     * @param context
     * @param name
     * @return
     */
    public String getApplicationMetaValue(Context context, String name) {
        ApplicationInfo appInfo = context.getApplicationInfo();
        return appInfo.metaData.getString(name);
    }

    /**
     * 检测本地端口是否被占用
     *
     * @param port
     * @return
     */
    public boolean isLocalPortUsing(int port) {
        boolean flag = true;
        try {
            flag = isPortUsing("127.0.0.1", port);
        } catch (Exception e) {
        }
        return flag;
    }

    /**
     * 检测任一端口是否被占用
     *
     * @param host
     * @param port
     * @return
     * @throws UnknownHostException
     */
    public boolean isPortUsing(String host, int port) throws UnknownHostException {
        boolean flag = false;
        InetAddress theAddress = InetAddress.getByName(host);
        try {
            Socket socket = new Socket(theAddress, port);
            flag = true;
        } catch (IOException e) {
        }
        return flag;
    }

    /**
     * 检查root权限
     *
     * @return
     */
    public boolean isRoot() {
        int secureProp = getroSecureProp();
        if (secureProp == 0)//eng/userdebug版本,自带root权限
            return true;
        else return isSUExist();//user版本,继续查su文件
    }

    private int getroSecureProp() {
        int secureProp;
        String roSecureObj = CommandUtil.getSingleInstance().getProperty("ro.secure");
        if (roSecureObj == null) secureProp = 1;
        else {
            if ("0".equals(roSecureObj)) secureProp = 0;
            else secureProp = 1;
        }
        return secureProp;
    }

    private int getroDebugProp() {
        int debugProp;
        String roDebugObj = CommandUtil.getSingleInstance().getProperty("ro.debuggable");
        if (roDebugObj == null) debugProp = 1;
        else {
            if ("0".equals(roDebugObj)) debugProp = 0;
            else debugProp = 1;
        }
        return debugProp;
    }

    private boolean isSUExist() {
        File file = null;
        String[] paths = {"/sbin/su",
                "/system/bin/su",
                "/system/xbin/su",
                "/data/local/xbin/su",
                "/data/local/bin/su",
                "/system/sd/xbin/su",
                "/system/bin/failsafe/su",
                "/data/local/su"};
        for (String path : paths) {
            file = new File(path);
            if (file.exists()) return true;
        }
        return false;
    }

    private static final String XPOSED_HELPERS = "de.robv.android.xposed.XposedHelpers";
    private static final String XPOSED_BRIDGE = "de.robv.android.xposed.XposedBridge";

    /**
     * 通过检查是否已经加载了XP类来检测
     *
     * @return
     */
    @Deprecated
    public boolean isXposedExists() {
        try {
            Object xpHelperObj = ClassLoader
                    .getSystemClassLoader()
                    .loadClass(XPOSED_HELPERS)
                    .newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
            return true;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return true;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return false;
        }

        try {
            Object xpBridgeObj = ClassLoader
                    .getSystemClassLoader()
                    .loadClass(XPOSED_BRIDGE)
                    .newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
            return true;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return true;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 通过主动抛出异常,检查堆栈信息来判断是否存在XP框架
     *
     * @return
     */
    public boolean isXposedExistByThrow() {
        try {
            throw new Exception("gg");
        } catch (Exception e) {
            for (StackTraceElement stackTraceElement : e.getStackTrace()) {
                if (stackTraceElement.getClassName().contains(XPOSED_BRIDGE)) return true;
            }
            return false;
        }
    }

    /**
     * 尝试关闭XP框架
     * 先通过isXposedExistByThrow判断有没有XP框架
     * 有的话先hookXP框架的全局变量disableHooks
     * <p>
     * 漏洞在,如果XP框架先hook了isXposedExistByThrow的返回值,那么后续就没法走了
     * 现在直接先hookXP框架的全局变量disableHooks
     *
     * @return 是否关闭成功的结果
     */
    public boolean tryShutdownXposed() {
        Field xpdisabledHooks = null;
        try {
            xpdisabledHooks = ClassLoader.getSystemClassLoader()
                    .loadClass(XPOSED_BRIDGE)
                    .getDeclaredField("disableHooks");
            xpdisabledHooks.setAccessible(true);
            xpdisabledHooks.set(null, Boolean.TRUE);
            return true;
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            return false;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return false;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 检测有么有加载so库
     *
     * @param paramString
     * @return
     */
    public boolean hasReadProcMaps(String paramString) {
        try {
            Object localObject = new HashSet();
            BufferedReader localBufferedReader =
                    new BufferedReader(new FileReader("/proc/" + Process.myPid() + "/maps"));
            for (; ; ) {
                String str = localBufferedReader.readLine();
                if (str == null) {
                    break;
                }
                if ((str.endsWith(".so")) || (str.endsWith(".jar"))) {
                    ((Set) localObject).add(str.substring(str.lastIndexOf(" ") + 1));
                }
            }
            localBufferedReader.close();
            localObject = ((Set) localObject).iterator();
            while (((Iterator) localObject).hasNext()) {
                boolean bool = ((String) ((Iterator) localObject).next()).contains(paramString);
                if (bool) {
                    return true;
                }
            }
        } catch (Exception fuck) {
        }
        return false;
    }

    /**
     * java读取/proc/uid/status文件里TracerPid的方式来检测是否被调试
     *
     * @return
     */
    public boolean readProcStatus() {
        try {
            BufferedReader localBufferedReader =
                    new BufferedReader(new FileReader("/proc/" + Process.myPid() + "/status"));
            String tracerPid = "";
            for (; ; ) {
                String str = localBufferedReader.readLine();
                if (str.contains("TracerPid")) {
                    tracerPid = str.substring(str.indexOf(":") + 1, str.length()).trim();
                    break;
                }
                if (str == null) {
                    break;
                }
            }
            localBufferedReader.close();
            if ("0".equals(tracerPid)) return false;
            else return true;
        } catch (Exception fuck) {
            return false;
        }
    }

    /**
     * 获取当前进程名
     *
     * @return
     */
    public String getCurrentProcessName() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("/proc/self/cmdline");
            byte[] buffer = new byte[256];// 修改长度为256,在做中大精简版时发现包名长度大于32读取到的包名会少字符,导致常驻进程下的初始化操作有问题
            int len = 0;
            int b;
            while ((b = fis.read()) > 0 && len < buffer.length) {
                buffer[len++] = (byte) b;
            }
            if (len > 0) {
                String s = new String(buffer, 0, len, "UTF-8");
                return s;
            }
        } catch (Exception e) {

        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (Exception e) {

                }
            }
        }
        return null;
    }
}

调用实践

 if (RootTool.checkDeviceDebuggable() || RootTool.checkSuperuserApk() || RootTool.isSuBusyEnable() || RootTool.isSuEnable()||SecurityCheckUtil.getSingleInstance().isRoot) {
     //根据需要进行风险提示等相关业务
     ToastUtils.showToast("您当前设备可能已root,请谨防安全风险!")
 }

封装建议(可忽略)

有兴趣的话,可以将下方这些图示方法 copyRootTool ,这样调用时仅使用 RootTool 即可

CommandUtilgetProperty 反射方法

在这里插入图片描述

SecurityCheckUtilroot 核心方法

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/639084.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【博主推荐】HTML5实现520表白、情人节表白模板源码

文章目录 1.设计来源1.1 表白首页1.2 甜蜜瞬间11.3 甜蜜瞬间21.4 甜蜜瞬间31.5 甜蜜瞬间41.6 甜蜜瞬间51.7 甜蜜瞬间61.8 永久珍藏 2.效果和源码2.1 页面动态效果2.2 页面源代码2.3 源码目录2.4 更多为爱表白源码 3.源码下载地址 作者&#xff1a;xcLeigh 文章地址&#xff1a;…

行业首发 | MS08067-SecGPT(送邀请码)

一、简介 MS08067-SecGPT基于LLM大模型技术专门为网络安全领域设计的智能助手&#xff0c;集问答、分析、工具为一体的对话式安全专家&#xff0c;支持可以创建多会话问答。目的是辅助用户完成网络安全相关的工作&#xff0c;学员通过问答方式体验到SecGPT所具备的威胁情报分…

mySql从入门到入土

基础篇 在cmd中使用MYSQL的相关指令&#xff1a; net start mysql // 启动mysql服务 net stop mysql // 停止mysql服务 mysql -uroot -p1234//登录MYSQL&#xff08;-u为用户名-p为密码&#xff09; //登录参数 mysql -u用户名 -p密码 -h要连接的mysql服务器的ip地址(默认1…

开源博客项目Blog .NET Core源码学习(25:App.Hosting项目结构分析-13)

本文学习并分析App.Hosting项目中后台管理页面的文章管理页面。   文章管理页面用于显示、检索、新建、编辑、删除文章数据&#xff0c;以便在前台页面的首页、文章专栏、文章详情页面显示文章数据。文章管理页面附带一新建及编辑页面&#xff0c;以支撑新建和编辑文章数据。…

智慧平安小区建设方案:EasyCVR视频+AI能力全面提升小区安全管理水平

随着城市化进程的加快和科技的不断发展&#xff0c;智慧平安小区建设成为了提升社区治理水平和居民安全感的重要手段。TSINGSEE青犀EasyCVR智慧平安小区平台采集汇总智慧小区各类视频资源基础数据&#xff0c;进行分级分类管理&#xff0c;并为公安、政法、大数据局、街道社区等…

从零开始:Spring Boot项目中如何集成并使用Infinispan

一、介绍 Infinispan 其实就是一个分布式缓存和数据网格平台&#xff0c;提供了高度可扩展和高性能数据缓存解决方案。Infinispan可以作为本地缓存或分布式缓存使用&#xff0c;支持事务、查询、处理大数据等功能。简单地说&#xff0c;Infinispan 可以理解为是 MySQL 的内存版…

docker容器安装nexus3以及nexus3备份迁移仓库数据

一、安装步骤 1.搜索nexus3镜像 docker search nexus3 2.拉取镜像 docker pull sonatype/nexus3或者指定版本 docker pull sonatype/nexus3:3.68.0 3.查看拉取的镜像 docker images | grep "nexus3" 4.启动nexus服务 直接启动 docker run -d --name nexus3 -…

优于InstantID!中山大学提出ConsistentID:可以仅使用单个图像根据文本提示生成不同的个性化ID图像

给定一些输入ID的图像&#xff0c;ConsistentID可以仅使用单个图像根据文本提示生成不同的个性化ID图像。效果看起来也是非常不错。 相关链接 Code:https://github.com/JackAILab/ConsistentID Paper&#xff1a;https://ssugarwh.github.io/consistentid.github.io/arXiv.pd…

Sentinel重要的前置知识

文章目录 1、雪崩问题及解决方案1.1、雪崩问题1.2、超时处理1.3、仓壁模式1.4、断路器1.5、限流1.6、总结 2、服务保护技术对比3、Sentinel介绍和安装3.1、初识Sentinel3.2、安装Sentinel 4、微服务整合Sentinel ​&#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在…

二叉树实战演练

目录 1.二叉树前序遍历---leetcode 思路 画图解析&#xff1a; 2.相同的树的判断 思路&#xff1a; 3.对称二叉树 思路分析&#xff1a; 4.另一棵树的子树 思路&#xff1a; 5.二叉树的便利---牛客网 建立二叉树的逻辑图&#xff1a; 总结&#xff1a; 1.…

Python3 笔记:部分专有名词解释

1、python 英 /ˈpaɪθən/ 这个词在英文中的意思是蟒蛇。但据说Python的创始人Guido van Rossum&#xff08;吉多范罗苏姆&#xff09;选择Python这个名字的原因与蟒蛇毫无关系&#xff0c;只是因为他是“蒙提派森飞行马戏团&#xff08;Monty Python&#xff07;s Flying Ci…

Softing工业将亮相2024年阿赫玛展会——提供过程自动化的连接解决方案

您可于2024年6月10日至14日前往美因河畔法兰克福11.0号馆&#xff0c;Softing将在C25展位展出&#xff0c;欢迎莅临&#xff01; 作为工业应用中数据交换领域公认的专家&#xff0c;Softing工业致力于帮助各行各业的客户部署网络自动化和优化生产流程。 使用Softing产品&…

什么是DNS缓存投毒攻击,有什么防护措施

随着企业组织数字化步伐的加快&#xff0c;域名系统&#xff08;DNS&#xff09;作为互联网基础设施的关键组成部分&#xff0c;其安全性愈发受到重视。然而&#xff0c;近年来频繁发生的针对DNS的攻击事件&#xff0c;已经成为企业组织数字化发展中的一个严重问题。而在目前各…

基于Go实现的分布式主键系统

基于Go实现的分布式主键系统 摘要 随着互联网的发展&#xff0c;微服务得到了快速的发展&#xff0c;在微服务架构下&#xff0c;分布式主键开始变得越来越重要。目前分布式主键的实现方式颇多&#xff0c;有基于数据库自增的、基于UUID的、基于Redis自增的、基于数据库号段的…

Day3: LeedCode 203. 移除链表元素 707. 设计链表 206. 反转链表

详细讲解移步:Day3: LeedCode 203. 移除链表元素 707. 设计链表 206. 反转链表-CSDN博客 203. 移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&a…

Mybatis源码剖析---第一讲

Mybatis源码剖析 基础环境搭建 JDK8 Maven3.6.3&#xff08;别的版本也可以…&#xff09; MySQL 8.0.28 --> MySQL 8 Mybatis 3.4.6 准备jar&#xff0c;准备数据库数据 把依赖导入pom.xml中 <properties><project.build.sourceEncoding>UTF-8</p…

关于阳光雨露外派联想的面试感想

最近在找工作&#xff0c;接到了一个阳光雨露外派联想的面试邀请。说实在的一开始就有不对劲的感觉。想必这就是大厂的自信吧&#xff0c;上就问能不能现场面试&#xff0c;然后直接发面试邀请。这时候我倒是没觉得有啥问题。 然后今天就去面试去了&#xff0c;住的比较偏&…

CSS demo演示 20240524

说明&#xff1a;不修改父盒子的样式&#xff0c;只在子盒子上设置定位&#xff0c;让子盒子定位在父盒子右侧。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Document</title><style>…

Docker快速部署Seata的TC服务以及微服务引入Seata教程

目录 一、使用docker部署Seata的TC服务 1、拉取TC服务镜像 2、创建并运行容器 ​3、修改配置文件 4、在Nacos中添加TC服务的配置 5、重启TC服务 二、微服务集成Seata 1、引入依赖 2、修改配置文件 Seata是阿里的一个开源的分布式事务解决方案&#xff0c;能够为分布…

2024 电工杯高校数学建模竞赛(B题)数学建模完整思路+完整代码全解全析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024电工杯数学建模竞赛&#xff08;B题&#xff09;的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有详尽的建模过程和解…