查看: 1664|回复: 0

[技术分享] 《安卓逆向这档事》十一、实战系列 利用内嵌模块hook设备信息 完成软件破/解

[复制链接]
累计签到:207 天
连续签到:1 天

218

主题

317

回帖

9908

积分

域主

自出洞来无/敌手,得饶人处且饶人。

名望
145
星币
2837
星辰
38
好评
442

夜猫子勋章实习版主勋章版主勋章星座专属勋章星辰勋章灌水天才奖鼎力支持奖热心助人奖优秀会员奖明星会员奖魅力会员奖欢乐天使奖在线大神动漫大使幸运猪

发表于 2023-7-27 20:09:43 | 显示全部楼层 |阅读模式

注册登录后全站资源免费查看下载

您需要 登录 才可以下载或查看,没有账号?立即注册

×
转载自吾爱破/解精华帖芽衣手下的文章





零、前言
1、Xposed介绍
Xposed框架(Xposed Framework)是一套开源的、在Android高权限模式下运行的框架服务,可以在不修改APK文件的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。
现在主流的框架有LSPosed、Xposed框架、太极等,内嵌框架有TweakMe、Lspatch等,但是我感觉最好用的就不得不提TweakMe了,好多地方都没有人出过用TweakMe破/解软件的相关教程,实际上这个框架还是很不错的,作者确实厉害,可以安利一下。不过在安卓13上偶有过签失效的问题(第一次启动没问题,第二次启动就显示盗版了),不知道什么原因。
2、模块的编写
https://www.52pojie.cn/thread-1740944-1-1.html
https://www.52pojie.cn/thread-1748081-1-1.html
上面的两个帖子简单介绍了xp模块的编写方法,现在网上有个AIDE安卓版,自带xp模板,可一键打包成apk,非常推荐这个。
不过我习惯用AS编写,因为查错和码字都比较方便,这里有个空白模板https://github.com/lz-ang/XposedSample,直接导入AS即可编译成apk,一些设置已经全部帮你弄好了,适合所有阶段的逆向玩家进行模块开发。
3、案例软件
Poweramp。之前改汇编虽然可以卡试用,但毕竟不是完整版。改汇编也能改成完整版,不过我试了一下有些小毛病,就放弃了。
我在贴吧注意到APP正常激活的时候有个提示:授权已永/久存储到设备,不再需要在线检查。于是,破/解的思路就开始浮现出来。
一、破/解目标
原版有15天的使用限制,到期后无法使用。现在要用手头上的工具进行破/解,使之自动激活完整版,并且能够在不同的手机上工作。


二、准备工具
1、Android Studio
2、TweakMe
3、小黄鸟
4、MT、NP等,视情况使用
5、一部root的手机
6、52.88块钱RMB


三、逆向思路
首先,这软件是把授权码保存在本地的,在注册后肯定要去私有目录里面把文件给拷贝出来,我们在破/解的时候需要写个代码,在另一台设备运行的时候自动释放授权文件。
当然不会那么容易,因为唯一授权肯定包含了设备信息,要不然没法知道激活了多少次,官网也说得很清楚。
此激活操作将解锁适用于Android的音乐播放器Poweramp。它将移除试用期限限制。此激活操作仅授权用于一台设备/电子邮件组合。
提供有限的激活次数。
发布/分享您的订单ID和电子邮件以及/或通过多个不同的设备重新激活将导致许可永/久性禁用。
一经售出,概不退换。您将无法获得退款。
所以,逆向步骤如下:
1、明确激活时,向服务器发送了什么设备信息;
2、用xp模块逐个拦截,拦截后正常购买软件;
3、注册成功后前往“data/data/包名”拷贝文件备用;
4、把授权文件装入apk,编写Java文件,运行时自动释放;
5、多设备测试是否完成破/解。



四、用小黄鸟进行抓包
直接安装软件,然后进入到激活界面,随便输入邮箱和订单号,看看它发送了什么信息。







从上图来看,一些机型、品牌、安卓id等都包含在里面了,看来Deviceid是由多个信息拼合而成的,并不是单一的元素。为了更加方便查阅,可以打开“/system/build.prop”查看对应的信息。


build.prop 是Android系统中一个类似于Windows系统注册表的文件,该文件内定义了系统初始(或永/久)的一些参数属性、功能的开放等。并且在 Android中虽然每一版都有自己独有的参数,但绝大部分都是通用的,且可以起到关键性作用的。


ro.build.id=                 #build的标识,一般在编译时产生

ro.build.version.sdk=        #系统编译时,使用的SDK的版本


ro.build.version.codename=   #版本编码名称


ro.build.version.release=    #公布的版本,显示为手机信息的系统版本,


ro.build.date=               #系统编译的时间


ro.build.type=               #系统编译类型


ro.build.user=               #系统用户名


ro.build.host=               #系统主机名


ro.build.tags=test-keys      #系统标记


ro.product.name=             #机器名


ro.product.device=           #设备名


ro.product.board=            #主板名


ro.product.locale.language=  #系统语言


ro.product.locale.region=    #系统所在地区


net.bt.name=                 #蓝牙网络中显示的名称


ro.media.enc.jpeg.quality=100       #相机照片压缩质量,此处为100%高质量


ro.media.dec.jpeg.memcap=8000000    #相机捕捉像素,此处为800万像素


dalvik.vm.heapsize=                 #dalvik的虚拟内存大小


debug.sf.hw=1                       #硬件GPU加速,1为开启,0为关闭


persist.adb.notify=0                #USB插入时的特别通知,1为显示,0为关闭


video.accelerate.hw=1               #视/频硬件加速,1为开启,0为关闭


debug.sf.nobootanimation=1          #不显示开机动画,1为关闭动画,0为开启动画


view.touch_slop=15                  #触摸屏灵敏度,数值越大越灵敏


view.minimum_fling_velocity=25      #滑动速度


view.scroll_friction=0.008          #滑动误差


wifi.interface=eth0                 #WIFI界面


wifi.supplicant_scan_interval=45    #WIFI扫描间隔时间,这里值是45秒,把这个值设置越大越省电




获取的这些信息基本上属于android.os.Build,它是一个获取设备一些信息的类,该类的主要信息都是通过一些static的字段获得。现在明确了软件获取了什么设备信息,等下编写xp模块hook的目标就是这些静态字段。


五、编写hook代码
hook翻译过来就是钩子,也许很多小白还不知道是什么意思,但换成“拦截”应该一目了然。比如这件古玩是假的,你跟小王说是真的,这就是hook。中途拦截不该返回的数据。
根据开发文档,我们需要自己写hook代码,但是不用生成apk,框架自带编译程序,可以一键内嵌到目标软件,这样就省去了生成独立xp模块的麻烦。




正己版主的帖子也有教学,就是不知道有多少人觉得看了很多,但又觉得好像没看过一样undefined
hook设备码示范,两种写法,可根据实际情况使用。
复制代码 隐藏代码
package com.android.guobao.liao.apptweak.plugin;import android.content.ContentResolver;import com.android.guobao.liao.apptweak.xposed.IXposedHookLoadPackage;import com.android.guobao.liao.apptweak.xposed.XC_LoadPackage;import com.android.guobao.liao.apptweak.xposed.XC_MethodHook;import com.android.guobao.liao.apptweak.xposed.XposedHelpers;public class Xposed_hookid implements IXposedHookLoadPackage {    protected void hook_method(String className,ClassLoader classLoader,String methodName,                               Object... parameterTypesAndCallback){        try {            XposedHelpers.findAndHookMethod(className,classLoader,methodName,parameterTypesAndCallback);        }catch (Exception ignored){        }    }    @Override    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpp) throws Throwable{        if ("com.maxmpz.audioplayer".equals(lpp.packageName)) {    //过滤软件包名            hook_method(                    "android.provider.Settings$Secure",  //要hook类名路径,现在是安卓id                    lpp.classLoader,                    "getString",      // 要hook的方法名                    ContentResolver.class,                    String.class,                    new XC_MethodHook() {                        @Override                        protected void afterHookedMethod(MethodHookParam param) throws Throwable{                            String id = "52pojie666666666";    // 设置新的返回值                            param.setResult(id);                        }                    });            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "MODEL", "yayi");     //机型            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "BRAND", "yayi123");       //品牌            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "FINGERPRINT", "yayi321"); //指纹            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "BOARD", "yayi520");       //主板            XposedHelpers.setStaticObjectField(android.os.Build.VERSION.class,                    "RELEASE", "12");     //安卓版本            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "PRODUCT", "yayi23333");    //产品名称            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "DEVICE", "yayi1111");      //开发代号            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "ID", "yayi007");          //编译编号            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "TAGS", "release-keys");    //release-keys            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "MANUFACTURER", "I_love_China");  //生产商            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "SERIAL", "001100");         //序列号            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "HARDWARE", "I_love_China");   //硬件名            XposedHelpers.setStaticObjectField(android.os.Build.VERSION.class,                    "CODENAME", "REL");           //系统开发代号            XposedHelpers.setStaticObjectField(android.os.Build.class,                    "DISPLAY", "yayi007");       //版本包        }    }}



编写好后修改JavaTweak.java,激活JavaTweak_xposed(modules)插件,还有自己的xp模块。
其实还有一个比较重要的是imei,不过可能是因为隐私问题,软件并没有获取这个值。imei的方法是“getDeviceId”,属于android.telephony.TelephonyManager。

用自带的命令打包成apk后,前往官网购买激活码,保存邮箱和购买订单号,然后在已经hook的软件里面激活它。


六、提取授权文件
因为文件比较多,直接把所有的文件夹给提取出来,不过一般在databases文件夹里面。
经过测试,文件databases/folders.db、folders.db-wal保存了授权验证信息,files/fsp/l文件保存了商店信息,就是购买渠道。我随机删了几个文件,发现保留folders.db、folders.db-wal即可实现注册,购买渠道的文件有没有影响不大。不过你如果是破/解共享的话建议还是把这个渠道给加上,免得日后出现反弹等问题。






七、将授权文件放入apk,并自动释放
我选择的存放目录是assets,并在该目录下新建了一个“yayi”的文件夹,用于存放数据文件。
assets 目录是专门用于保存各种外部文件的。常见的有:图像、音视/频、配置文件、字体、自带数据库等。之所以说它适合用来管理这些文件,是因为应用程序在编译时不会去处理这个目录下的文件,但是却会将它们打包进 APK 中。而其它你随便创建的目录在编译时就会被直接忽略掉。同时,你可以在 assets 目录内任意创建目录层级关系,这对于有大量外部文件需要集成的应用来说,就能很方便地分类管理了。




然后写一个复制文件的代码,如:
复制代码 隐藏代码
package com.maxmpz.audioplayer;import android.content.Context;import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;public class yayiUtil {    public static void copy(Context context, String dataPath, String savePath){        try {            String[] fileNames = context.getAssets().list(dataPath);   //获取目录下的所有文件及文件夹            if (fileNames.length > 0) {    //如果是文件夹                File file = new File(savePath);                file.mkdirs();    //如果文件夹不存在,则递归                for (String fileName : fileNames) {                    copy(context, dataPath + "/" + fileName, savePath + "/" + fileName);                }            } else {   // 如果是文件                InputStream is = context.getAssets().open(dataPath);                FileOutputStream fos = new FileOutputStream(savePath);                byte[] buffer = new byte[1024];                int byteCount;                while ((byteCount = is.read(buffer)) != -1)                    fos.write(buffer, 0, byteCount);   //将读取的输入流写入到输出流                fos.flush();                is.close();                fos.close();            }        } catch (Exception e) {            e.printStackTrace();        }    }}


复制代码 隐藏代码
.class public Lcom/maxmpz/audioplayer/yayiUtil;.super Ljava/lang/Object;# direct methods.method public constructor <init>()V    .registers 1    .line 8    invoke-direct {p0}, Ljava/lang/Object;-><init>()V    return-void.end method.method public static copy(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V    .registers 16    const/4 v8, 0x0    .line 12    :try_start_1    invoke-virtual {p0}, Landroid/content/Context;->getAssets()Landroid/content/res/AssetManager;    move-result-object v9    invoke-virtual {v9, p1}, Landroid/content/res/AssetManager;->list(Ljava/lang/String;)[Ljava/lang/String;    move-result-object v5    .line 13    array-length v9, v5    if-lez v9, :cond_4d    .line 14    new-instance v3, Ljava/io/File;    invoke-direct {v3, p2}, Ljava/io/File;-><init>(Ljava/lang/String;)V    .line 15    invoke-virtual {v3}, Ljava/io/File;->mkdirs()Z    .line 16    array-length v9, v5    :goto_15    if-ge v8, v9, :cond_6f    aget-object v4, v5, v8    .line 17    new-instance v10, Ljava/lang/StringBuilder;    invoke-direct {v10}, Ljava/lang/StringBuilder;-><init>()V    invoke-virtual {v10, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    move-result-object v10    const-string v11, "/"    invoke-virtual {v10, v11}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    move-result-object v10    invoke-virtual {v10, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    move-result-object v10    invoke-virtual {v10}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;    move-result-object v10    new-instance v11, Ljava/lang/StringBuilder;    invoke-direct {v11}, Ljava/lang/StringBuilder;-><init>()V    invoke-virtual {v11, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    move-result-object v11    const-string v12, "/"    invoke-virtual {v11, v12}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    move-result-object v11    invoke-virtual {v11, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    move-result-object v11    invoke-virtual {v11}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;    move-result-object v11    invoke-static {p0, v10, v11}, Lcom/maxmpz/audioplayer/yayiUtil;->copy(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V    .line 16    add-int/lit8 v8, v8, 0x1    goto :goto_15    .line 20    :cond_4d    invoke-virtual {p0}, Landroid/content/Context;->getAssets()Landroid/content/res/AssetManager;    move-result-object v8    invoke-virtual {v8, p1}, Landroid/content/res/AssetManager;->open(Ljava/lang/String;)Ljava/io/InputStream;    move-result-object v7    .line 21    new-instance v6, Ljava/io/FileOutputStream;    invoke-direct {v6, p2}, Ljava/io/FileOutputStream;-><init>(Ljava/lang/String;)V    .line 22    const/16 v8, 0x400    new-array v0, v8, [B    .line 23    const/4 v1, 0x0    .line 24    :goto_5f    invoke-virtual {v7, v0}, Ljava/io/InputStream;->read([B)I    move-result v1    const/4 v8, -0x1    if-eq v1, v8, :cond_70    .line 26    const/4 v8, 0x0    invoke-virtual {v6, v0, v8, v1}, Ljava/io/FileOutputStream;->write([BII)V    :try_end_6a    .catch Ljava/lang/Exception; {:try_start_1 .. :try_end_6a} :catch_6b    goto :goto_5f    .line 32    :catch_6b    move-exception v2    .line 33    invoke-virtual {v2}, Ljava/lang/Exception;->printStackTrace()V    .line 35    :cond_6f    :goto_6f    return-void    .line 28    :cond_70    :try_start_70    invoke-virtual {v6}, Ljava/io/FileOutputStream;->flush()V    .line 29    invoke-virtual {v7}, Ljava/io/InputStream;->close()V    .line 30    invoke-virtual {v6}, Ljava/io/FileOutputStream;->close()V    :try_end_79    .catch Ljava/lang/Exception; {:try_start_70 .. :try_end_79} :catch_6b    goto :goto_6f.end method




再新建一个类名,用于运行后调用和删除一些残留文件。
写法仅供参考,具体需求视情况而定。
复制代码 隐藏代码
package com.maxmpz.audioplayer;import android.content.Context;import java.io.File;public class copy {    private static final String a ="yayi";    private static final String b ="data/data/com.maxmpz.audioplayer";    private static final String c ="data/data/com.maxmpz.audioplayer/files/fsp";    public static void yayi(Context context){        deleteDir(c);        //先把原来的文件删了        try {            Thread.sleep(500);     //毫秒        } catch(InterruptedException ex) {            Thread.currentThread().interrupt();        }        yayiUtil.copy(context,a,b);   //开始复制数据    }    public static void deleteDir(String fsp) {        File file = new File(fsp);        if (file.isFile()) {            file.delete(); // 删除文件        } else {            File[] files = file.listFiles();            if (files == null) {                file.delete(); // 删除空文件夹            } else {                for (File f : files) {                    deleteDir(f.getAbsolutePath()); // 迭代删除非空文件夹                }                file.delete();            }        }    }}



复制代码 隐藏代码
.class public Lcom/maxmpz/audioplayer/copy;.super Ljava/lang/Object;# static fields.field private static final a:Ljava/lang/String; = "yayi".field private static final b:Ljava/lang/String; = "data/data/com.maxmpz.audioplayer".field private static final c:Ljava/lang/String; = "data/data/com.maxmpz.audioplayer/files/fsp"# direct methods.method public constructor <init>()V    .registers 1    .line 13    invoke-direct {p0}, Ljava/lang/Object;-><init>()V    return-void.end method.method public static deleteDir(Ljava/lang/String;)V    .registers 7    .line 34    new-instance v1, Ljava/io/File;    invoke-direct {v1, p0}, Ljava/io/File;-><init>(Ljava/lang/String;)V    .line 35    invoke-virtual {v1}, Ljava/io/File;->isFile()Z    move-result v3    if-eqz v3, :cond_f    .line 36    invoke-virtual {v1}, Ljava/io/File;->delete()Z    .line 48    :goto_e    return-void    .line 38    :cond_f    invoke-virtual {v1}, Ljava/io/File;->listFiles()[Ljava/io/File;    move-result-object v2    .line 39    if-nez v2, :cond_19    .line 40    invoke-virtual {v1}, Ljava/io/File;->delete()Z    goto :goto_e    .line 42    :cond_19    array-length v4, v2    const/4 v3, 0x0    :goto_1b    if-ge v3, v4, :cond_29    aget-object v0, v2, v3    .line 43    invoke-virtual {v0}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;    move-result-object v5    invoke-static {v5}, Lcom/maxmpz/audioplayer/copy;->deleteDir(Ljava/lang/String;)V    .line 42    add-int/lit8 v3, v3, 0x1    goto :goto_1b    .line 45    :cond_29    invoke-virtual {v1}, Ljava/io/File;->delete()Z    goto :goto_e.end method.method public static yayi(Landroid/content/Context;)V    .registers 5    .line 19    const-string v1, "data/data/com.maxmpz.audioplayer/files/fsp"    invoke-static {v1}, Lcom/maxmpz/audioplayer/copy;->deleteDir(Ljava/lang/String;)V    .line 21    const-wide/16 v2, 0x1f4    :try_start_7    invoke-static {v2, v3}, Ljava/lang/Thread;->sleep(J)V    :try_end_a    .catch Ljava/lang/InterruptedException; {:try_start_7 .. :try_end_a} :catch_12    .line 25    :goto_a    const-string v1, "yayi"    const-string v2, "data/data/com.maxmpz.audioplayer"    invoke-static {p0, v1, v2}, Lcom/maxmpz/audioplayer/yayiUtil;->copy(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V    .line 26    return-void    .line 22    :catch_12    move-exception v0    .line 23    invoke-static {}, Ljava/lang/Thread;->currentThread()Ljava/lang/Thread;    move-result-object v1    invoke-virtual {v1}, Ljava/lang/Thread;->interrupt()V    goto :goto_a.end method




[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码
[backcolor=rgb(27, 36, 38) !important][color=rgb(255, 255, 255) !important]
[color=#ffffff !important]?

1

invoke-static {p0}, Lcom/maxmpz/audioplayer/copy;->yayi(Landroid/content/Context;)V





然后在合适的地方调用这个代码。
我选择的地方是com.maxmpz.audioplayer.dialogs.APMUnlockDialogActivity,就是上面的那个激活解锁界面,因为这里只调用一次,激活成功后这个按钮就消失了,所以是插入首选。如果选择启动界面,还需要加入判断,判断是否第一次启动或者是否复制过文件,要不然每次打开APP都会删除数据,这样就容易出现问题。


如果需要判断是否初次安装,可以利用SharedPreferences存储来完成,在之前的破/解中已经用过好多次sp了,这里就点到为止。
SharedPreferences是Android中用于实现存储方式的技术。SharedPreferences的使用非常简单,能够轻松的存放数据和读取数据。SharedPreferences只能保存简单类型的数据,例如,String、int等。一般会将复杂类型的数据转换成Base64编码,然后将转换后的数据以字符串的形式保存在XML文件中,再用SharedPreferences保存。
使用SharedPreferences保存key-value对的步骤如下:
  (1)使用Activity类的getSharedPreferences方法获得SharedPreferences对象,其中存储key-value的文件的名称由getSharedPreferences方法的第一个参数指定。
  (2)使用SharedPreferences接口的edit获得SharedPreferences.Editor对象。
  (3)通过SharedPreferences.Editor接口的putXxx方法保存key-value对。其中Xxx表示不同的数据类型。例如:字符串类型的value需要用putString方法。
  (4)通过SharedPreferences.Editor接口的commit方法保存key-value对。commit方法相当于数据库事务中的提交(commit)操作。



八、运行测试
将代码打包进apk,先在另一部真机中测试是否正常运行、是否成功复制文件、是否成功自动注册。如果出现异常需要回头查找原因,属于APP的写入保护就比较头疼,是自身的代码问题还好解决。
因为插入的地方是注册界面,所以初次运行肯定是没有注册的,按照常规步骤进入注册界面,一切正常,而且私有目录已经出现了变化,说明文件已经成功删除和添加。关闭APP后再次打开,已完成注册!






九、总结
个人感觉内置授权码难倒是不难,非要说难点就是愿不愿意自掏腰包了。
当然你如果说逆向算法并写死文件这种就比较逆天了……

好吧我不会。


正己发的教程贴对新手来说是不错的,但是从实操来看逆向中会有很多意想不到的情况,比如这软件如果在初次运行前就已经有文件的话会闪退,所以不能过早复制文件。从整个流程来看,会使用较多的工具进行辅/助分析,所以单单只会编写hook代码还是不足以破/解一款软件的,正己1~10新手教程会让你对逆向有个初步的认识,系统的学习后对于破/解一些简单的软件应该是没有什么阻碍。
本次实战利用了多方面的知识,方法不唯一,仅供参考。本文目的是让部分学员学习后,如何把hook代码打包到安装包里面,而不是写完就完了,怎么用都不知道。

除了文中介绍的框架外,还有Lspatch,比较成熟的一款框架(支持安卓9以上,但适配的机型会比较多),还支持模块内嵌,内嵌后可以脱离本机使用,适合黑客发布破/解包。












不知道主题怎么写,直接模仿正己的好了,发帖格式也是照搬,要是以前我懒得用MD发帖。undefined


成品在poweramp吧,想要自己去拿。(根据github反馈和吧友反馈,安卓10无法运行。安卓10的话我做成xp模块用lsp内嵌正常运行。)


回复

使用道具 举报

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

本版积分规则

Archiver|手机版|偏爱技术社区-偏爱技术吧-源码-科学刀-我爱辅助-娱乐网--教开服-游戏源码

偏爱技术社区-偏爱技术吧-源码-科学刀-我爱辅助-娱乐网-游戏源码

Powered by Discuz! X3.5

GMT+8, 2024-12-28 06:26 , Processed in 0.087872 second(s), 34 queries .

快速回复 返回顶部 返回列表