unidbg实现淘宝请求参数算法,实现脱离模拟器/手机请求淘宝、闲鱼

这篇具有很好参考价值的文章主要介绍了unidbg实现淘宝请求参数算法,实现脱离模拟器/手机请求淘宝、闲鱼。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本篇文章仅适用于学习。

最近一直在研究阿里系的请求问题,原来一直都是hook请求端口,虽然和很多人的hook参数生成x-sign不一样,可以说更稳定一些。但总归是脱离不了安卓环境——或者用模拟器,或者用真机。

对于逆向来说,非常的没有档次,没有逼格。

相对于直接逆向x-sign的算法而言,用unidbg调用so文件的难度要小很多。由易入难,unidbg这时候势在必行了。

但是,随着研究的深入,相关的学习资料越来越少,unidbg和直接逆向学习的资料,虽然有一些,但对于问题的深入,原理的解析,以及具体API方法的使用,可以说是少之又少。鱼和渔的问题,越来越尖锐了。

已经研究了差不多几个月,总感觉临门一脚了,程序调试也没有完全通过。虽然越挫越勇,会继续研究,但还是感觉得慢慢来,研究、学习,本身也是一场永远止境的修行。

下面的代码面向的是mtop6.5.27, 代码还存在不少问题,并不能直接使用。文章来源地址https://www.toymoban.com/news/detail-507254.html

import android.content.Context;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.spi.SyscallHandler;
import com.taobao.tao.TaobaoApplication;
import org.json.JSONObject;
 
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
 
public class TaoBao extends AbstractJni implements IOResolver<AndroidFileIO> {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Context context;
    private final JSONObject data;
    private long slot;
 
//    private int num = 0;
 
    private TaoBao(File apk) throws  Exception{
        emulator = AndroidEmulatorBuilder.for32Bit()
                .setRootDir(new File("D:\\DesktopTemp\\tb\\rootfs"))
                .build();
 
        Map<String, Integer> iNode = new LinkedHashMap<>();
        iNode.put("/data/system", 671745);
        iNode.put("/data/app", 327681);
        iNode.put("/sdcard/android", 294915);
        iNode.put("/data/user/0/com.taobao.taobao", 655781);
        iNode.put("/data/user/0/com.taobao.taobao/files", 655864);
        emulator.set("inode", iNode);
        emulator.set("uid", 10074);
 
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        SyscallHandler<AndroidFileIO> handler = emulator.getSyscallHandler();
        handler.setVerbose(false);
        handler.addIOResolver(this);
        vm = emulator.createDalvikVM(apk);
        vm.setJni(this);
        vm.setVerbose(true);
        context = new TaobaoApplication(vm);
        data = new JSONObject("{\"Soft_SGTMAGIC\":\"4I1q9PXiORQGtBivoqf4hSwMk9pwm1D8o4NitR+kvgA=\",\"dynamicreid_dynamicreid\":\"d0666b5b6022eb0\",\"dynamicrsid_dynamicrsid\":\"e1a2607877e260b\",\"SgDyUpdate_ac7123c301ca455b\":\"1621600637\",\"LOCAL_DEVICE_INFO_982c1b269b8e023e5aede2421cbf9c48\":\"YKepcS4SY+ADAIS37Xj5c7s+\",\"DynamicData_accs_ssl_key2_https:\\/\\/ossgw.alicdn.com_21646297%[B\":\"nRWwrMQ\\/jz+oOTWkAZ5FOjhnS1k48SqJdb3w3u\\/ImZJMSXQnlxpD8g0Lyi4kEfgHy5Me33VQ8fyLfqHjPk5PXZ3SwQDtSG4Km7fj9RhEav6NeP85kaWorOA8KTx9u9MHnXdbQa4GVOpBTln\\/GKsPje5gRpmCtWUb71auNwVEO\\/s9LUhH\\/HOcH\\/fwdPixaJAi\\/wNKYYlijdORJgVTOwrtSls1DeUr61NyCDUQa0SkVhw6\\/8PI8gdM1JNt8QEcBIemgI0sM4zA3yyRxFTb0wwcu8CpLsBmIqxqZbvHA+2081dfYDIKuKguH9vYy4s\\/q++odPRvTB25RuEfvXWW\\/+IPtScYQXMx9\\/MG4RW7t80WR0+DOWZXHtkpVlPhTDcU9P2fI4bcQdRSTOIcaI6uFmnOdmb5b9QdtwU3qXgSOuBTh2Bdd6yTeyydRLChBzlWRtcZm6+tYgHOTJIWRNoDg8CxEw==\",\"llc-local_2c3c7f544c159842\":\"1621600921\",\"llc-local_abv2\":\"his:0\",\"llc-local_tcv2\":\"source:0,0,0\"}");
     }
 
    public static void main(String[] args) throws Exception {
        TaoBao taobao = new TaoBao(new File("D:\\DesktopTemp\\tb\\tb.apk"));
 
        AndroidEmulator emulator = taobao.emulator;
        String methodSign = "doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;";
 
        DvmClass targetClass = taobao.vm.resolveClass("com/taobao/wireless/security/adapter/JNICLibrary");
        DalvikModule main = taobao.vm.loadLibrary("sgmainso-6.5.25", true);
 
        main.callJNI_OnLoad(emulator);
 
        targetClass.callStaticJniMethodObject(emulator, methodSign,
                10101, ProxyDvmObject.createObject(taobao.vm, new Object[]{
                        taobao.context, 3, "", "/data/user/0/com.taobao.taobao/app_SGLib", ""
                }));
 
 
        targetClass.callStaticJniMethodObject(emulator, methodSign,
                10102, ProxyDvmObject.createObject(taobao.vm, new Object[]{
                        "main", "6.5.25", "/data/app/com.taobao.taobao-1/lib/arm/libsgmainso-6.5.25.so"
                }));
 
 
        DalvikModule security = taobao.vm.loadLibrary("sgsecuritybodyso-6.5.33", true);
        security.callJNI_OnLoad(emulator);
 
 
        targetClass.callStaticJniMethodObject(emulator, methodSign,
                10102, ProxyDvmObject.createObject(taobao.vm, new Object[]{
                        "securitybody", "6.5.33", "/data/app/com.taobao.taobao-1/lib/arm/libsgsecuritybodyso-6.5.33.so"
                }));
 
        DalvikModule middletier = taobao.vm.loadLibrary("sgmiddletierso-6.5.27", true);
        middletier.callJNI_OnLoad(emulator);
 
        targetClass.callStaticJniMethodObject(emulator, methodSign,
                10102, ProxyDvmObject.createObject(taobao.vm, new Object[]{
                        "middletier", "6.5.27", "/data/app/com.taobao.taobao-1/lib/arm/libsgmiddletierso-6.5.27.so"
                }));
 
        taobao.loadTest3Hook(middletier.getModule());
 
        DvmObject<?> dvmObject1 = targetClass.callStaticJniMethodObject(emulator, methodSign,
                70102, ProxyDvmObject.createObject(taobao.vm, new Object[]{
                        "丢失", "丢失",
                        false, 0, "mtop.alibaba.cro.umid.networksdk.savewb", "pageName=com.taobao.tao.welcome.Welcome&pageId=", null, null, null, "r_6"
                }));
 
        System.out.println(dvmObject1.getValue().toString());
        try {
            emulator.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    @Override
    public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "java/lang/Integer-><init>(I)V":
                return ProxyDvmObject.createObject(vm, varArg.getIntArg(0));
            case "java/lang/Long-><init>(J)V":
                return ProxyDvmObject.createObject(vm, varArg.getLongArg(0));
            case "java/util/HashMap-><init>(I)V":
                return ProxyDvmObject.createObject(vm, new HashMap<>());
        }
        return super.newObject(vm, dvmClass, signature, varArg);
    }
 
    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        if ("android/os/Build$VERSION->SDK_INT:I".equals(signature)) {
            return 23;
        }
        return super.getStaticIntField(vm, dvmClass, signature);
    }
 
    @Override
    public long getStaticLongField(BaseVM vm, DvmClass dvmClass, String signature) {
        if ("com/alibaba/wireless/security/framework/SGPluginExtras->slot:J".equals(signature)) {
            return slot;
        }
        return super.getStaticLongField(vm, dvmClass, signature);
    }
 
    @Override
    public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        if ("android/content/pm/ApplicationInfo->nativeLibraryDir:Ljava/lang/String;".equals(signature)) {
            return new StringObject(vm, "/data/app/com.taobao.taobao-1/lib/arm");
        } else if ("android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;".equals(signature)) {
            return new StringObject(vm, this.context.getPackageCodePath());
        }
        return super.getObjectField(vm, dvmObject, signature);
    }
 
    @Override
    public void setStaticLongField(BaseVM vm, DvmClass dvmClass, String signature, long value) {
        if ("com/alibaba/wireless/security/framework/SGPluginExtras->slot:J".equals(signature)) {
            slot = value;
            return;
        }
        super.setStaticLongField(vm, dvmClass, signature, value);
    }
 
    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "com/alibaba/wireless/security/securitybody/SecurityGuardSecurityBodyPlugin->getPluginClassLoader()Ljava/lang/ClassLoader;":
                return vm.resolveClass("dalvik/system/PathClassLoader").newObject(this.getClass().getClassLoader());
            case "java/net/NetworkInterface->getNetworkInterfaces()Ljava/util/Enumeration;":
                try {
                    return Context.getNetworkInterfaces(vm);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }
 
    @Override
    public void callStaticVoidMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "com/alibaba/wireless/security/open/edgecomputing/ECMiscInfo->registerAppLifeCyCleCallBack()V":
            case "com/alibaba/wireless/security/securitybody/LifeCycle->setAccessibilityDelegateToView()V":
                return;
        }
        super.callStaticVoidMethod(vm, dvmClass, signature, varArg);
    }
 
    @Override
    public int callStaticIntMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "com/taobao/wireless/security/adapter/common/SPUtility2->saveToFileUnifiedForNative(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)I":
                return 2;
            case "com/alibaba/wireless/security/framework/utils/UserTrackMethodJniBridge->utAvaiable()I":
            case "com/uc/crashsdk/JNIBridge->registerInfoCallback(Ljava/lang/String;IJI)I":
                return 1;
            case "android/provider/Settings$Secure->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I":
                return context.getInt(varArg.getObjectArg(1).getValue().toString(), varArg.getIntArg(2));
        }
        return super.callStaticIntMethod(vm, dvmClass, signature, varArg);
    }
 
    @Override
    public boolean callBooleanMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "java/lang/Boolean->booleanValue()Z":
                return (Boolean) dvmObject.getValue();
            case "android/view/accessibility/AccessibilityManager->isEnabled()Z":
            case "android/view/accessibility/AccessibilityManager->isTouchExplorationEnabled()Z":
                return false;
            case "java/util/Enumeration->hasMoreElements()Z":
                return ((Enumeration) dvmObject).hasMoreElements();
            case "java/net/NetworkInterface->isUp()Z":
                return ((Context.mNetworkInterface) dvmObject.getValue()).isUp();
        }
        return super.callBooleanMethod(vm, dvmObject, signature, varArg);
    }
 
    @Override
    public void callVoidMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        if ("com/taobao/dp/util/CallbackHelper->onUpdated(IILjava/lang/String;)V".equals(signature)) {
            return;
        }
        super.callVoidMethod(vm, dvmObject, signature, varArg);
    }
 
    @Override
    public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        if ("java/lang/Integer->intValue()I".equals(signature)) {
            return (Integer) dvmObject.getValue();
        } else if ("android/telephony/TelephonyManager->getSimState()I".equals(signature)) {
            return ((Context) dvmObject.getValue()).getSimState();
        }
        return super.callIntMethod(vm, dvmObject, signature, varArg);
    }
 
    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "java/lang/String->getBytes()[B":
                return new ByteArray(vm, ((String) dvmObject.getValue()).getBytes());
            case "com/taobao/tao/TaobaoApplication->getPackageCodePath()Ljava/lang/String;":
                return new StringObject(vm, ((Context) dvmObject.getValue()).getPackageCodePath());
            case "com/taobao/tao/TaobaoApplication->getFilesDir()Ljava/io/File;":
            case "android/content/Context->getFilesDir()Ljava/io/File;":
                return ProxyDvmObject.createObject(vm, ((Context) dvmObject.getValue()).getFilesDir());
            case "java/io/File->getAbsolutePath()Ljava/lang/String;":
                return new StringObject(vm, ((File) dvmObject.getValue()).getPath().replace('\\', '/'));
            case "com/taobao/tao/TaobaoApplication->getApplicationInfo()Landroid/content/pm/ApplicationInfo;":
                return super.callObjectMethod(vm, dvmObject,
                        "android/content/Context->getApplicationInfo()Landroid/content/pm/ApplicationInfo;", varArg);
            case "android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":
                return ((TaobaoApplication) dvmObject.getValue()).getSystemService(varArg.getObjectArg(0).getValue().toString());
            case "dalvik/system/PathClassLoader->findClass(Ljava/lang/String;)Ljava/lang/Class;":
                return vm.resolveClass(varArg.getObjectArg(0).getValue().toString());
            case "java/util/Enumeration->nextElement()Ljava/lang/Object;":
                return ((Enumeration) dvmObject).nextElement();
            case "android/content/Context->getContentResolver()Landroid/content/ContentResolver;":
                return ((Context) dvmObject.getValue()).getContentResolver();
            case "java/net/NetworkInterface->getName()Ljava/lang/String;":
                return new StringObject(vm, ((Context.mNetworkInterface) dvmObject.getValue()).getName());
            case "java/lang/Thread->getStackTrace()[Ljava/lang/StackTraceElement;":
                return ProxyDvmObject.createObject(vm, ((Thread) dvmObject.getValue()).getStackTrace());
            case "java/lang/StackTraceElement->toString()Ljava/lang/String;":
                return new StringObject(vm, ((StackTraceElement) dvmObject.getValue()).toString());
            case "java/util/HashMap->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;":
                return ProxyDvmObject.createObject(vm, ((HashMap<Object, Object>) dvmObject.getValue())
                        .put(varArg.getObjectArg(0).getValue(), varArg.getObjectArg(1).getValue()));
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }
 
    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "android/content/Context->getClassLoader()Ljava/lang/ClassLoader;":
                return ProxyDvmObject.createObject(vm, this.getClass().getClassLoader());
            case "android/content/Context->getPackageResourcePath()Ljava/lang/String;":
                return ProxyDvmObject.createObject(vm, ((Context) dvmObject.getValue()).getPackageResourcePath());
            case "android/content/Context->getFilesDir()Ljava/io/File;":
                return ProxyDvmObject.createObject(vm, ((Context) dvmObject.getValue()).getFilesDir());
            case "java/io/File->getPath()Ljava/lang/String;":
                return ProxyDvmObject.createObject(vm, ((File) dvmObject.getValue()).getPath().replace('\\', '/'));
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
 
    @Override
    public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
        switch (pathname) {
            case "/data/app/com.taobao.taobao-1/base.apk":
            case "/data/user/0/com.taobao.taobao/files/sg_oc.lock":
            case "/data/user/0/com.taobao.taobao/files/ab914f43b8296c2c.lock":
            case "/data/user/0/com.taobao.taobao/files/0a231bd8575dcf72.txt":
            case "/data/user/0/com.taobao.taobao/files/.ba2f9c85.lock":
            case "/data/user/0/com.taobao.taobao/files/JX0WDG83P1ZN.txt":
            case "/data/user/0/com.taobao.taobao/files/sgFile.lock":
            case "/data/user/0/com.taobao.taobao/app_SGLib/SG_INNER_DATA":
                return FileResult.success(emulator.getFileSystem().createSimpleFileIO(
                        new File("D:\\DesktopTemp\\tb\\rootfs", pathname), oflags, pathname));
            case "/data/data/com.taobao.taobao/app_SGLib/sec":
            case "/data/user/0/com.taobao.taobao/app_SGLib/sec":
            case "/data/user/0/com.taobao.taobao/app_SGLib/lvmreport":
                return FileResult.success(emulator.getFileSystem().createDirectoryFileIO(
                        new File("D:\\DesktopTemp\\tb\\rootfs", pathname), oflags, pathname));
            default:
                return null;
        }
    }
 
}

到了这里,关于unidbg实现淘宝请求参数算法,实现脱离模拟器/手机请求淘宝、闲鱼的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Fiddler修改https请求与响应 bug修复变灰了选不了等 Fiddle对夜神模拟器抓包设置

    只用于自己的网站,自己安全调试。 1、打到要改的请求 2、替换请求内容 3、开启捕获。操作产生请求。 4、fiddler里查看请求或响应数据 ,确认成功。 具体修改: Rule—打开并编辑Customize Rule文件,在方法 static function OnBeforeRequest(oSession: Session) 的最后插入代码,保存。捕捉,

    2024年02月19日
    浏览(47)
  • 人生重开模拟器(Python实现)

    人生重开模拟器是由VickScarlet上传至GitHub的一款简单的文字网页游戏。 玩家点击“立即重开”并设置角色的初始属性后,程序就会随机为玩家生成对应的人生经历。 下面我们实现一个简化版的人生重开模拟器,主要目的在于熟悉Python的语法。 初始界面可以自由发挥,这里博

    2024年02月02日
    浏览(51)
  • 通过模拟器实现APP抓包

    本教程将跳过工具安装部分,请正确食用 😉 我的环境: 操作系统:win11 模拟器版本:雷电安卓模拟器稳定版-安卓7.1(32位) V5.0.46 抓包工具:Wireshark-Version 4.0.1 抓包工具:charles-Version 4.6.3 工具安装地址: 雷电安卓模拟器👈点击跳转官网地址下载 Wireshark👈点击跳转官网地址

    2024年02月02日
    浏览(62)
  • C#实现键盘鼠标模拟器

    下面程序可指定一连串重复动作,按顺序执行   using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Text.RegularExpressions; using System.Windows.Forms; namespace Simulator {     public partial class Form1 : Form     {         [System.Runtime.InteropServices.DllImp

    2024年02月16日
    浏览(46)
  • 高校数据中心网络规划设计及思科模拟器CISCO模拟实现(网络安全、数据冗余)

            数据中心在现代社会中的地位愈加重要,这得益于信息技术的迅速发展。信息处理的能力、安全性等方面的要求也在不断攀升。因此,在服务器的计算能力、稳定性、可靠性、安全性、未来扩展性以及方便管理等多个方面,都应对其要求更高水平。         高校没有

    2024年02月01日
    浏览(62)
  • M芯片Mac实现安卓模拟器多开

    写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成,愿将昔日所获与大家交流一二

    2024年02月12日
    浏览(42)
  • 华为模拟器实现wlan 三层旁挂式组网

    网络拓扑 说明:本实验采用三层旁挂组网的方式搭建,ap和终端用户的网关都在三层交换机上。 vlan 划分说明: vlan10 用于ap的网关,创建ap的地址池,并配置option 43字段,使ap获取IP地址以及和AC建立隧道的地址。 vlan 20用于员工终端设备的网关,此类终端设备都采用隧道转发

    2024年02月09日
    浏览(42)
  • 咸鱼之王小游戏PC版鼠标模拟器实现

    最近朋友在玩咸鱼之王小游戏,用电脑挂机,由于火把缺乏,我看他经常疯狂的点鼠标攻击敌人。 我:\\\"你为什么不用鼠标模拟器去点?\\\" 朋友:\\\"鼠标模拟器点也不能干其他的事情,鼠标必须放到游戏窗口才行。\\\", 我:\\\"我帮你简单实现个鼠标模拟器吧\\\"  总结 其实原理很简单

    2024年02月12日
    浏览(111)
  • 思科模拟器 | 访问控制列表ACL实现网段精准隔绝

    ACL (Access Control List)是一种网络安全技术,用于控制网络通信和访问权限。它使用规则列表以限制哪些计算机或网络服务可以与另一个计算机或网络服务进行通信,从而为网络提供了一个基本安全机制。 下面是一组有关TCP的会话,很好地体现了ACL的面对外来请求访问的严谨

    2024年02月04日
    浏览(40)
  • 基于EmulatorJs的Docker实现Web端游玩模拟器

    或许大家都见过一些在线游玩fc、gbc等老游戏的网页了,这些网页使我们随时随地都能体验并回味一波老游戏的滋味, 也能在上班的时候摸鱼 ,曾经我思考过这些网站的实现原理,想要模仿一下,自己搭建一个在线的模拟器网站。在参考了网络上各路大神的方案后我选择基于

    2024年01月19日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包