吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 82|回复: 1
上一主题 下一主题
收起左侧

[Android 原创] 【系列文章】阿里 sgmian第二篇:初始化详细分析10102

[复制链接]
跳转到指定楼层
楼主
rsds0duck 发表于 2026-11-4 22:34 回帖奖励

【系列文章】阿里 sgmian 初始化详细分析 10102

前情提要

时间: 2026 年 11 月 04 日 星期三 18:59:50 CST
作者: 人生导师

依旧是慢慢搞的,配合模型进行分析吧,要不是真的难办,随项目写的太烂了不过没办法,有的时候就是当时想到的
推荐有虚拟机分析经验的朋友看,并且跟出来也非常非常恶心

正文

10102 是最简单的一个了,只做准备工作没有多线程调用,把三个参数传递进去然后绑定最终返回 0

命令号进来以后会进行一些取余之类的操作,大概如下,模型也是几秒钟就出来了

// 0x5a310 ~ 0x5a378,简化后
a = cmd / 10000;      // 0x68db8bad, asr #0x2c
r = cmd % 10000;      // MSUB:cmd - a*10000
b = r / 100;          // 先乘 0x147B(5243),到 callee 里再 asr #0x13(>>19)
c = cmd % 100;        // 0x51eb851f, asr #0x25 得 cmd/100,再 MSUB 回余数

cmd = 10102 代进去:

a = 10102 / 10000      = 1
b = (10102 / 100) % 100 = 1
c = 10102 % 100         = 2

然后是 ADR X14, loc_5A380 加一串 EOR/SUB/BR 的混淆跳转。IDA 是看不出来花指令的,但 trace 直接跳吧:

line 44  0x5a3ac  br x14
line 45  0x5a3c0  asr w9, w8, #0x13        <-- 落在 doCommand_decode_and_invoke(我给它的重命名)

这块怎么说呢?花指令这个玩意给我搞的懵逼了,我看 ida 自己分析那个函数边界,我给他当真了,然后我还在想这些东西到底怎么搞的,寄存器咋不对劲啊,然后给 trace 日志和 ida 都拿出来比较,想起来花指令是模糊了函数边界,上下汇编是一体的不能拆开看,然后可以发现把解析后的三个数字传递进去,后面还跟着一个 1,当时只能把它当一个数字,因为不知道是干啥的(后来看 vm_invoke 才知道是选表的 flag)
还有就是直接进来时候 x0 的值给了 x19,还有一个 x4 这个东西是一个结构体

x4 结构体

这个结构体以我的实力肯定是推不出来的,但是 DeepSeek 一眼解决,我只能讲讲为什么
前面那些参数都可以一眼看出来,唯独这个 x4 是ADD X4, SP, #40(也就是 SP+0x28)

vm_invoke(
    a,
    b,
    c,
    /*flag=*/1,
    struct ctx *ctx /*X4=SP+0x28*/,
    int *status /*X5=SP+0x24*/
);

我来带各位从内存布局上开始推吧,先把这个内存布局推出来然后再根据它实际代码执行进行理解

LOAD:000000000005A3C0                 ASR             W9, W8, #19 ; doCommand 解码后组 ctx {a,b,c,env,args},再交给 VM 分发
LOAD:000000000005A3C4                 STR             X3, [SP,#64] ; ctx.args = X3(Java Object[])
LOAD:000000000005A3C8                 ADD             W1, W9, W8,LSR#31
LOAD:000000000005A3CC                 ADD             X4, SP, #40
LOAD:000000000005A3D0                 ADD             X5, SP, #36
LOAD:000000000005A3D4                 MOV             W3, #1
LOAD:000000000005A3D8                 STP             WZR, W0, [SP,#36]
LOAD:000000000005A3DC                 STP             XZR, X19, [SP,#48] ; ctx.env = X19(doCommandNative 保存的 JNIEnv*)
LOAD:000000000005A3E0                 STP             W1, W2, [SP,#44]

以 10 进制来看更好看一些,先说 x5 吧,这个简单一眼看出来了:0x5A3D0 把 x5 指到 SP+36,紧接着 0x5A3D8 的 STP WZR, W0, [SP,#36] 第一个寄存器就是 wzr,写的正是 x5 指的那块,所以 x5 顺手就被清零了

以 x4 地址为基线来看,w0 写入的地址是 sp+40,也就是 x4 的基线,然后 w1、w2 后续写入了 sp+44、sp+48 这两个内存地址也就是 x4+4 和 x4+8 然后是 0x5A3DC 这个地址它存的是 sp+48 到 sp+55 为 0、sp+56 到 sp+63(x 寄存器是 8 字节的) 为 x19(刚刚进入函数时期设置的 JNIEnv) 然后 x3 并没变,直接被赋值给了 sp+64(x4+24)

struct ctx {              // sizeof = 0x20
    int32_t a;           // +0  : W0
    int32_t b;           // +4  : W1
    int32_t c;           // +8  : W2
    uint32_t reserved;   // +12 : XZR 的高半,恒 0,目前没见谁读
    JNIEnv* env;         // +16 : X19
    jobjectArray args;   // +24 : X3 jobjectArray 是一个引用,只有一个64位引用,不是具体数据
};

虽然内存布局是这样的,0x5A3DC 是把 sp+48 到 sp+55 这块清零了,但是后续执行 w2 那条却又 tm 的写进去数据了 W2(覆盖原 XZR 低 4 字节),不知道是设计这样还是编译器优化这样的

把 IDA 里被花指令拆开的那两段函数边界合并成一整段以后,hex-rays 给出的更直白:先 ctx.reserved = 0、ctx.env = env,再 ctx.c = code % 100,正好就是那个 8 字节 STP 被低 4 字节覆盖回来的顺序。+0xC 那半个字节从构造到 handler 入口都没人读过,所以这个名字先挂着 reserved 吧,不排除后面哪条命令会用它

vm_inovke

这个名字不知道准不准确,不过只能先这样叫了,它接手了上面的所有参数,并且在它的入口处有一个小小的反调试,针对于 brk 的检测
也不是很严格,就是看当前执行的这个函数头是不是 brk,不是很理解这个是为什么,为啥会在函数头插入 brk 呢?之前技术栈都没到内核的吧?还希望各位大佬解惑一手

然后把那个引用给清了,不知道为啥,还是没看懂,不过问题不大反正已经存到 x4 了,然后加载了两段数据,DeepSeek 给它们命名为

LOAD:0000000000057014                 STR             XZR, [SP,#0x40]
LOAD:0000000000057018                 ADRL            X8, g_vm_script_table
LOAD:0000000000057020                 LDR             X8, [X8]
LOAD:0000000000057024                 ADRL            X9, g_vm_native_table
LOAD:000000000005702C                 LDR             X9, [X9]

然后对上层传递进来的 w3 进行了一个比较(这层是 w24),看看传递进来的是不是 0,然后根据结果选表:非 0 走 g_vm_native_table,为 0 走 g_vm_script_table。也就是说 doCommand 传进来的那个 1 不是什么数字,就是个 flag,10102 这种 C++ 侧的命令走 native 表(这里 F5 一下就可以非常明确的看见 if 了),然后加了一个读写锁,然后就开始了参数的赋值,随后进入了 0x567a8 函数(解析命令号)

LOAD:0000000000057044                 MOV             X0, X25 // 前文比较后得到的是 g_vm_native_table 的数据
LOAD:0000000000057048                 MOV             W1, W22 // a
LOAD:000000000005704C                 MOV             W2, W21 // b
LOAD:0000000000057050                 MOV             W3, W20 // c
LOAD:0000000000057054                 MOV             W4, WZR // create=0:只查不建
LOAD:0000000000057058                 ADD             X5, SP, #arg_40 // &handler,查到的函数指针写这里
0x567a8:命令表查表

这个函数不是"不重要",它其实是 VM 的核心 —— 命令分发就是在这里查表。0x56830 那段也是被花指令拆出去的,合并边界、再把里面 8 个 BR X8 换成 B 之后,一次就能 F5 出来:

cmd = a * 10000 + b * 100 + c;     // 又把三个数字拼回原命令号
v31 = *(_DWORD *)(table + 8);      // 第一级表的条目数

for (i = 0; **(_DWORD **)(*(_QWORD *)table + 8 * i) != a; ++i)
    if (i + 1 == v31) goto 没找到;
entry_a = *(_QWORD **)(*(_QWORD *)table + 8 * i);

三级表,正好对应命令号拆出来的三个数字:

第一级:entry_a[0] 的 int(+0) == a
第二级:entry_b[0] 的 int(+4) == b
第三级:entry_c[0] 的 int(+8) == c

命中之后:

*handler = (__int64)entry_b[1] ^ entry_c[2];   // qword[1] ^ qword[2]

entry_b 的第二个 qword 和 entry_c 的第三个 qword 异或回来才是 handler 真地址。10102 的 (a,b,c) = (1,1,2) 找到的就是 0x5bc2c。

没命中的时候看第五个参数。之前我以为它是"清零了不知道干啥",其实是 create:

  • create == 0(vm_invoke 传的就是 0):只查不建,直接返回错误码
  • create != 0:calloc 一个条目填进去,还会调表头 +0x18 的插入回调;key 用 srand48(time(0)) + lrand48() 现生成一个随机值

返回码全是带 key 前缀的:

err = cmd * 10000 + 9904;   // a 层没找到
err = cmd * 10000 + 9905;   // b 层没找到
err = cmd * 10000 + 9906;   // c 层没找到
err = cmd * 10000 + 9999;   // create 时条目已经存在

这也正好解释了 vm_invoke 的 hook 分支里那个 key * 10000 + 9907 —— 9904~9907 是同一套带 key 的状态码,拿到码就能反推是哪一层出的问题。

然后回到 vm_invoke:

LOAD:000000000005708C                 CBZ             X8, loc_5713C
LOAD:0000000000057158 loc_57158                               ; CODE XREF: vm_invoke_impl+7C↑j
LOAD:0000000000057158                 LDR             X8, [SP,#arg_40]
LOAD:000000000005715C                 MOV             X0, X23
LOAD:0000000000057160                 MOV             X1, X19
LOAD:0000000000057164                 BLR             X8      ; 查表成功:v13(ctx, status) 调用 handler
LOAD:0000000000057168                 STR             X0, [SP,#arg_10]
LOAD:000000000005716C                 LDR             X8, [SP,#arg_10]
LOAD:0000000000057170                 STR             X8, [SP,#arg_8]

走这个分支走查表成功的函数,也就是我们的目标,这次就是 0x5bc2c 这个地址

handler:0x5bc2c

v13(ctx, status) 进来以后就是 10102 的真身。x0 是 ctx,x1 是 status,函数头先把这俩存了:

0x5bc48   MOV  X19, X1      ; status
0x5bc4c   MOV  X20, X0      ; ctx

后面所有 [X19] 都是写状态码。然后又是一组 ADR/BR 跳板跳到 0x5bc98 —— 这函数也是花指令切开的,尾部还单独被 IDA 认成了一个函数(0x5c8f4 那段)。把边界合并、BR 顺手改成 B 以后,整个 handler 一次就能 F5 出来,这才看得出流程。

第一句是:

*status = 9999;

先丢个哨兵。后面每个失败分支把 status 改成 9901/9902/9903,成功改 0。所以看到 9999 就说明压根没走到底。

两段懒初始化

第一次进来先补字符串表,byte_2F6890 守着。一堆 off_1C5EXx 调用,参数都是 (dst, len, 密文, key, keylen, 常量),只是算法有 xor+add / sub+xor 好几种变体。解出来的就是后面要用的字符串:

全局 内容
g_str_slot slot
g_str_J J
g_str_libsg libsg
g_str_so_dash so-
g_str_main main
g_str_ro_build_version_release ro.build.version.release
g_str_ro_build_version_sdk ro.build.version.sdk
g_str_SGPluginExtras 55 字节,全限定类名

顺手验证了一下 xor+add 这个变体:out[i] = (key[i % keylen] ^ enc[i]) + 加数。拿它把 resolve_jni_onload 里那 11 个字节解出来,正好是 JNI_OnLoad。

第二段是 runtime 初始化,byte_2F4F14 守着:

desc = { 0, &g_tag_plve, 1, 0, 0 };
rt   = get_runtime_singleton();
ret  = (*(rt + 0x10))(1, 32, 12, 0, &desc, &rc);
g_vm_init_flag = rc ? 1 : ret;
if (g_vm_init_flag > 0)
    sg_set_sLoadVersion();

(1, 32, 12) 这组数字先不深究,反正 >0 就是起来了,顺手把 sLoadVersion 置 1。10102 自己不干活,它就是把 SDK 的运行环境拉起来。

参数契约
env = ctx->env;
len = env->functions[171](env, ctx->args);   // GetArrayLength
if (len != 3 && len != 4) { *status = 9901; ... }

0x558 / 8 = 171,JNI 函数表里就是 GetArrayLength。所以 Java 侧必须传 3 个或者 4 个元素的 Object[]:

args[0]  主工程名,正常是 "main"
args[1]  版本号,比如 "6.8.260901"
args[2]  so 的完整路径,可以为空
args[3]  可选,一个带静态 long slot 字段的 Class

前三个用 array_string_element_to_cstr(env, args, i) 转成 cstr(里面就是 GetObjectArrayElement + GetStringUTFChars)。

args[3]:藏在 Java 侧的 slot
if (init_ok && len > 3) {
    cls  = GetObjectArrayElement(env, args, 3);
    fid  = GetStaticFieldID(env, cls, g_str_slot, g_str_J);   // "slot", "J"
    slot = env->functions[151](env, cls, fid);                // GetStaticLongField
}

151 就是 GetStaticLongField。这个 slot 不是数据,是函数指针,而且 XOR 过:

if (slot != 0 && slot != 0x37)
    fn = (void *)(slot ^ 0x37);
else
    fn = 0;   // 没配,走下面正常加载

0 和 0x37 都当没配。这条是给 Java 侧已经拿到过插件句柄的场景准备的,能直接跳过 dlopen。

正常路径:从名字/路径到 JNI_OnLoad

args[2] 非空就直接转成 String;空的话现拼库名去找:

buf  = "libsg" + args[0] + "so-" + args[1];   // libsgmainso-6.8.260901
path = find_module_path_in_maps(getpid(), buf);

拿不到路径 → 9902,到此为止。

然后是插件对象:

if (!g_cached_plugin_obj) {
    rt = get_runtime_singleton();
    if (rt)
        g_cached_plugin_obj = (*(rt + 8))(1, 1, 4, 0, 0);
}

接着先看 args[0]:

if (strcmp(args[0], "main") == 0 && g_cached_plugin_obj)
    (*(g_cached_plugin_obj + 0x40))(g_cached_plugin_obj);   // 绑定 main

if (strcmp(args[0], "main") == 0) {
    *status = 0;
    return Integer(0);                                      // 直接收工
}

所以传 "main" 那次是不会去加载 so 的,插件对象在就顺手绑一下,然后把 Integer(0) 还回去。整条 10102 里最快的一条路。

不是 main 才继续。如果上面 args[3] 没给 slot,就从 SGPluginExtras 类上再取一次静态 long "slot":

cls  = FindClass(env, g_str_SGPluginExtras);
gcls = NewGlobalRef(env, cls);
fid  = GetStaticFieldID(env, gcls, g_str_slot, g_str_J);
slot = GetStaticLongField(env, gcls, fid);

拿到值还是老规矩:不是 0 也不是 0x37,就 fn = slot ^ 0x37 直接开跑。

都没有就加载:

sdk     = atoi_with_default(get_system_property("ro.build.version.sdk", 0), 0);
release = get_system_property("ro.build.version.release", 0);

if (sdk <= 23 && (!release || release[0] != 'N'))
    handle = dlopen(path, 1);
else
    handle = find_module_base_in_maps(getpid(), basename(path), 0);

fn = resolve_jni_onload(handle, release, sdk);

resolve_jni_onload 里才把 JNI_OnLoad 解出来,然后分两条:

if (sdk > 23 || (release && release[0] == 'N'))
    fn = elf_dlsym(handle, "JNI_OnLoad");      // 自己走 ELF 的 .dynsym
else {
    fn = dlsym(handle, "JNI_OnLoad");
    dlclose(handle);
}

也就是说 Android N 以后他们不用 dlopen 的句柄了,改成按 basename 在 maps 里找到模块基址,再自己解析 ELF 的 .dynsym 找符号。这个 elf_dlsym 就是手写的符号查找:校验 ELF magic → 扫 program header 找 PT_DYNAMIC → 遍历 dyn 条目 → 拿字符串表比对名字。

拿到 fn:

fn(NULL, get_runtime_singleton());   // JNI_OnLoad(NULL, runtime)

再给 runtime 排一个 init 回调:

cb = malloc(0x18);
cb->path    = strdup(path);
cb->version = strdup(version);
cb->next    = 0;
init = { sub_5CF38, cb, 30 };
(*(rt + 8))(1, 8, 1, &init, 0);

最后 *status = 0; return Integer(0);(这个 Integer 到底哪来的,见下一节)。

收尾和状态码

所有分支都汇到 0x5caa0:依次 free 掉 String 对象、三个 cstr、两个 property 字符串,再返回 Integer 或者 NULL。

status 含义
9999 哨兵,正常不会带出去
9901 参数不对(len 不是 3/4,或者 args[0..2] 拿不到)
9902 so 路径无效 / 模块加载失败
9903 JNI_OnLoad 解析失败
0 成功,返回 Integer(0)

追一下 Integer:其实 JNI_OnLoad 里就缓存好了

handler 里两次 return Integer(0),当时只看到 jni_NewObject_cached(env, 0),那 Integer 这玩意到底哪来的?往上翻一层就找到了。

Integer 的 class 和构造器在全局缓存里,初始化它的函数是 jni_cache_init(JavaVM *vm),入口 0x14f29c。注意这家伙也是花指令重灾区:IDA 之前只把中间一块(0x59ca8 那段)认成了函数,真正的 JNI_OnLoad 入口在 0x59c40。把边界合并、BR 全换成 B 之后,整条链是这样:

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    if (!(byte_2F4E87 & 1)) {          // 一次性解字符串:"libc.so"、"JNICLibrary"
        decrypt(...);
        byte_2F4E87 = 1;
    }
    if (!vm) return -1;

    qword_2F4E38 = dlopen("libc.so", 2);      // 后面 __system_property_get 用
    jni_cache_init(vm);                       // ← Integer 就在这里
    env = jni_GetEnv();

    if (env) {
        ...
        cls = FindClass(env, "JNICLibrary");
        if (cls) {
            ...
            thunk_register_command_handlers();  // 10102 的 handler 进表就是这步
            ...
        }
    }
    return 0;
}

jni_cache_init 里 Integer 就三句,标准的 JNI 三件套:

cls             = (*env)->FindClass(env, "java/lang/Integer");
g_integer_class = (*env)->NewGlobalRef(env, cls);
g_integer_ctor  = (*env)->GetMethodID(env, g_integer_class, "<init>", "(I)V");

同一个函数里还顺手缓存了 SecException、getBytes 这些,全是 FindClass → NewGlobalRef → GetMethodID。参数就是 JavaVM *:里面先用 vm->[6](GetEnv)拿 env,拿不到再走 vm->[4](AttachCurrentThread)。

真正 new 的地方是 jni_NewObject_cached:

void *jni_NewObject_cached(JNIEnv *env, int value)
{
    if (!env || !*env) return 0;
    obj = (*env)->NewObject(env, g_integer_class, g_integer_ctor, value);
    if ((*env)->ExceptionCheck(env))
        (*env)->ExceptionClear(env);
    return obj;
}

JNI 函数表下标:NewObject = 28(0xE0)、ExceptionCheck = 228(0x720)、ExceptionClear = 17(0x88)。

handler 里两处调用,都是 value = 0:

  • 0x5c4b4:args[0] == "main" 的分支
  • 0x5ca60:so 加载成功、JNI_OnLoad 调完之后

返回的 jobject 走 X0 原样往回传:

jni_NewObject_cached → handler → vm_invoke → doCommand_decode_and_invoke
→ jni_doCommandNative 的 X0 → JVM

所以 Java 侧 doCommandNative 拿到的确实是 Integer 对象,不是 int。这也是为什么错误要单独走 *status:返回值只有一个寄存器,值走 X0,错误走 status,两边互不干扰。

补丁:Integer 缓存来源

jni_cache_init 这个函数缓存了很多很多的类,先对整数类进行了查找,以下代码

v13 = (__int64)(*env)->FindClass(env, g_str_java_lang_Integer); // 查找这个类
g_integer_class = (__int64)(*env)->NewGlobalRef(env, (jobject)v13) // 看这个类在不在,然后对它反射
g_integer_ctor = (__int64)(*env)->GetMethodID(env, (jclass)g_integer_class, g_str_init, g_str_sig_I_V); // 然后查找它实际方法ID并缓存

然后 10102 直接调用

v38 = jni_NewObject_cached((JNIEnv *)*p_env, 0);
// NewObject 包装(用缓存的 class / methodID)
void *jni_NewObject_cached(JNIEnv *env, int value)
{
  jobject v5; // [xsp+8h] [xbp-28h]

  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  if ( !env || !*env )
    return 0LL;
  v5 = (*env)->NewObject(env, (jclass)g_integer_class, (jmethodID)g_integer_ctor, (unsigned int)value);
  if ( (*env)->ExceptionCheck(env) )
    (*env)->ExceptionClear(env);
  return v5;
}

就直接实现返回 Java 整数 0 了

顺手把 env 的类型还原了

jni_cache_init 里拿到 env 之后,后面所有 JNI 调用都是基于它:

if (!(*vm)->GetEnv(vm, (void **)&env, 65540))
    ...
(*env)->FindClass(env, "java/lang/Integer");

不过这是把类型打对之后的样子。原始反编译里它是 v15,调用全长这样:

(*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v15 + 1824LL))(v15);              // ExceptionCheck
(*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)v15 + 168LL))(v15, v13); // NewGlobalRef

*(_QWORD *)v15 取的是 env 指向结构的第一格 —— 也就是函数表指针 const JNINativeInterface *functions,再加偏移就是表里的成员。所以 v15 的类型就是 JNIEnv *(等价于 const struct JNINativeInterface **),不是 void *。

打上类型之后,偏移自动变成成员名,一眼就能读:

(*env)->ExceptionCheck(env)
(*env)->ExceptionClear(env)
(*env)->NewGlobalRef(env, (jobject)v13)
(*env)->GetMethodID(env, (jclass)g_integer_class, "<init>", "(I)V")
(*env)->DeleteLocalRef(env, v2)

这张表顺手记下来,以后看到 *(_QWORD *)x + 偏移 就知道是不是 JNI 调用、是哪个:

偏移 下标 函数
0x30 6 FindClass
0xA8 21 NewGlobalRef
0xB8 23 DeleteLocalRef
0x108 33 GetMethodID
0x88 17 ExceptionClear
0x720 228 ExceptionCheck

一个小尾巴:第一处 FindClass 还是 *(_QWORD *)v19[0] + 48 的原始形态,因为 env = v19[0] 之后 Hex-Rays 没把第一次使用替换过来,逻辑没问题。

10102 小结

看着是"最简单的一个",其实是整个 SDK 的启动开关:

  1. 解字符串、把 runtime 拉起来、置 sLoadVersion
  2. 校验 Object[] 是 3 个还是 4 个
  3. 拿插件对象(runtime 的一次 (1,1,4) 调用,缓存住)
  4. "main" 只绑定就返回;其他名字才找 so → JNI_OnLoad → 调起来 → 挂 init 回调
  5. 所有路径都返回 Integer(0),错误全靠 status 带出去

后面 10101/10103 那些命令能跑,靠的就是这里把 runtime 和插件对象准备好了。所以别被它"返回 0"骗了,它做的准备工作比看起来多。

我感觉就这些东西,搞 10 个小厂也不会有这么多的价值,对我来说,10102 让我对数据类型的组合有了更深入的理解
也算是迈上更高层级的一步,之前都是对独立算法或者虚拟机进行分析的,现在是对体系内容进行分析,下一个就开始巨难的提升了
10101 开始有多线程了,没招了,慢慢分析吧

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
 楼主| rsds0duck 发表于 2026-11-4 22:35 |楼主
作者已经燃尽,下一个阶段不知道怎么分析,希望大佬们多多交流
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-11-5 03:47

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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