吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1703|回复: 11
上一主题 下一主题
收起左侧

[易语言 原创] 利用 MinGW-w64 (i686) 移植代码到易语言

  [复制链接]
跳转到指定楼层
楼主
爱飞的猫 发表于 2023-10-5 09:12 回帖奖励
本帖最后由 爱飞的猫 于 2023-10-9 05:44 编辑

之前试验了下移植 AES 代码,感觉效果还行,就是还得穿插 fasm 来计算偏移什么的有点麻烦。

这次试了下行内汇编,可以一次编译得到想要的二进制文件。

这个方案适合的情况:

  • 需要简单粗暴的移植几个在易语言里面写起来比较繁琐的函数/算法;
  • 降低系统资源 - 在不引用未编译函数的情况下不需要执行任何初始化的代码,数据均随二进制代码和易语言代码一同存储至 .text 只读可执行区段内。

这个方案不适合的情况:

  • 需要有大量查表数据、调用外部 API(虽然直接爬 PEB 写起来也不麻烦,脚手架写好后就能用了)的情况
  • 项目过大。太大的项目编译的字节码也不小,也比较难定位,这样还不如直接编译到 DLL 后引用了。
    • 如果只是想文件不落地,可以参考内存加载 DLL 的方案。

准备工作

1. 安装 MinGW-w64 (i686)

如果已经安装,使用你已经安装好的版本亦可 (如 MSYS2 环境)。

如果还没有,你需要安装一份【预编译的 MinGW-w64】。

本文使用的是 WinLibs 预构建 (GCC 13.2.0 + LLVM 16.0.6 + MinGW-w64 11.0.1 UCRT (release 2)),如果你需要更新版本可以查看其发布首页来获取。百度网盘存档

※ Windows 10 开始内置 UCRT 运行时。若系统低于该版本可以安装 UCRT 运行时,要求 Windows Vista SP2 或更高。

本文预设你的 mingw32 环境安装在目录 M:\mingw32。照着教程操作时请手动替换为你自己的安装路径,该目录下应有一个名为「i686-w64-mingw32」的子目录。

推荐带 llvm (clangd) 的完整版本 winlibs-i686-mcf-dwarf-gcc-13.2.0-llvm-16.0.6-mingw-w64ucrt-11.0.1-r2.7z
如果你不需要 clangd 支持也可以使用更小巧的 winlibs-i686-mcf-dwarf-gcc-13.2.0-mingw-w64ucrt-11.0.1-r2.7z

2. 准备 VS Code 并配置开发环境 (可选)

本文推荐使用 VS Code + clangd LSP 服务。

此处的子步骤可以根据自己需要跳过。

中文语言包

官方扩展商店有中文语言包可以安装。

按下 Ctrl + P,键入 ext install MS-CEINTL.vscode-language-pack-zh-hans 并回车,等待片刻后按照提示重启即可。

新建配置文件

强烈推荐建立一个干净的,用于 MinGW-w64 (i686) 的 C++ 环境,避免不同环境的插件干扰。

首先按下左下角的「齿轮」→「配置文件」→「创建配置文件…」。

配置文件名称随意填写,如 M32 - MinGW-w64 (i686),确保复制来源为「无」,然后点击创建。

以后要切换环境时,选择左下角的「齿轮」→「配置文件」→选择该配置环境。

安装/配置 clangd

按下 Ctrl + P,键入 ext install llvm-vs-code-extensions.vscode-clangd 并回车,等待安装完成。

按下「Ctrl + ,」打开设置:

  • 检索 clangd.path,将该值设定为 M:\mingw32\bin\clangd.exe
  • 检索 editor.formatOnSave,确保该项选中(保存时自动格式化代码)。
  • 检索 files.autoSave,推荐选择「onFocusChange」(切换焦点到终端时自动储存)。

测试编译环境

首先准备一个新的目录并使用 VSCode 打开。

点击左下角齿轮,确保「配置文件」菜单显示刚配置的「M32 - MinGW-w64 (i686)」。

左侧的「资源管理器」:

  • 建立文件 src/example_main.cpp
  • 建立文件 build.cmd

其中 build.cmd 的内容如下:

::::::::::::::::::::: [初始化环境] 开始
@pushd "%~dp0"

:: 指定 mingw32 目录
@set "MINGW32=M:\mingw32"
@set "PATH=%MINGW32%\i686-w64-mingw32\bin;%MINGW32%\bin;%PATH%"

:: 编译参数
@set "CC=gcc"
@set "CXX=g++"
@set "CXXFLAGS=-O2 -fPIC -Wall"
@set "LDFLAGS="

:: 确保 out 目录存在
@if not exist out @mkdir out
::::::::::::::::::::: [初始化环境] 结束

::::::::::::::::::::: 编译代码放在下面

:::::::::::: [测试文件] 开始
%CXX% %CXXFLAGS% -o out/example.exe src/example_main.cpp
:: 执行编译后的文件:
::   .\out\example.exe
:::::::::::: [测试文件] 结束

※ 你也可以自己手写一个 Makefile 代替该脚本。本文就不深入了。

其中 src/example_main.cpp 的内容如下:

#include <cstdio>

int main() {
  puts("Hello World!"); // 输出 "Hello World!"
}

此时可以按下「Ctrl + '」呼出终端,键入 .\build.cmd 开始构建。

构建完成后没有消息提示(如果发生错误会输出错误信息),执行 .\out\example.exe 可以看到可执行文件能够正常运行:

M:\demo\e-crc32>.\build.cmd

M:\demo\e-crc32> g++ -O2 -fPIC -Wall -o out/example.exe src/example_main.cpp

M:\demo\e-crc32>.\out\example.exe
Hello World!

魔改/移植代码

本次代码的移植对象是 CRC32

网络上已经有很多实现了,挑个 zlib@v0.9 里的实现

※ 使用这个古老版本因为没有加入太多「微优化」。

理解原始代码

里面的代码看着唬人,其实有用的没多少:

// 预先计算好的常数表
uLong crc_table[] = {
  0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
  // ... 省略 ...
  0x2d02ef8dL
};

// 宏 - 处理一次
#define DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8);

// 函数本体
uLong crc32(crc, buf, len)
    uLong crc;
    Byte *buf;
    uInt len;
{
    if (buf == Z_NULL) return 0L;
    crc = crc ^ 0xffffffffL;
    while (len >= 8)
    {
      DO8(buf); // 等价于连续写 8 次 "DO1(buf)"。
                // 估计是在做什么优化。这一块可以忽略。
      len -= 8;
    }
    if (len) do {
      DO1(buf); // 展开这个宏就行。
    } while (--len);
    return crc ^ 0xffffffffL;
}

再将 crc32 稍微简化一下:

uint32_t crc32(uint32_t crc, const uint8_t* buf, size_t len) {
  if (buf == nullptr) return crc; // 我这里就改成返回传入的 CRC 值了。

  crc = ~crc;
  while (len--) {
    uint8_t idx = crc ^ *buf++;
    crc = crc_table[idx] ^ (crc >> 8);
  }
  crc = ~crc;

  return crc;
}

储存查表数据

通常来说,只读的静态数据会被编译器储存到 .rdata 区段中。但是我们的代码却在 .text

在 EXE 加载时,系统(EXE 装载代码)会根据 .reloc 区段的信息来进行「重定向」修正。

此时有一个问题 - 数据储存在不同的区段,如果需要手动修正这也太麻烦了。如果能直接将数据随代码储存在 .text 区段访问就好了…

※ 虽然可以用 char xxx[] = { ... } 的写法来内嵌,但是每次执行到该处都会初始化一次…

你可能已经发现了 - x86 并没有「取相对当前可执行地址偏移」的指令(x86-64 倒是有),但是却可以利用 CALL 指令来模拟:

  call _other_code ; 入栈下一个指令的地址 (_my_data)
                   ; 然后再跳转到目标函数 (_other_code)

_my_data:
  db "custom data"

_other_code:
  pop eax ; 此时 eax 为 _my_data 的地址,因此出栈就可以得到地址了。

在 gcc 编译器套件中,可以利用该特性将资源“内嵌”到代码中:

因此获取表数据就可以利用内联汇编来获取:

// 裸函数,不要 inline
__attribute__((naked, noinline)) const uint32_t *get_crc32_table() {
  asm("  call __exit_crc_table            \n"
      "  .incbin \"src/crc_table.bin\"    \n" // 引入资源文件
      "__exit_crc_table:                  \n" // "__exit_crc_table" 需要唯一性
      "  pop %eax                         \n"
      "  ret                              \n");
}

src/crc_table.bin 文件内容为 crc32_table 转存至小段序(Little-Endian)后储存的文件。

然后在用到它的函数进行一次初始化即可:

#include <cstdint>
#include <cstdio>

// ★★ 将上面的 get_crc32_table 放在此处

int main() {
  const auto* crc_table = get_crc32_table();
  printf("0x%08x", crc_table[1]); // 0x77073096
}

能够正常输出对应的值 0x77073096

获取代码数据

大多数情况下,函数出现在内存中的顺序和它们的实现在代码的位置是一致的。

因此我们可以通过在需要包含的所有函数前放置一个 __crc32_exp_start 和结尾放置一个 __crc32_exp_end 用来辅助定位:

文件 src/crc32.h

#pragma once
#include <cstdint>

void __crc32_exp_start();
uint32_t crc32(const uint8_t *buf, uint32_t len, uint32_t crc);
void __crc32_exp_end();

文件 src/crc32.cpp

#include "crc32.h"
#include <cstdint>

/////////////////////////////////////////////
void __crc32_exp_start() {};
/////////////////////////////////////////////

// ★★ 将上面的 get_crc32_table 放在此处

uint32_t crc32(const uint8_t *buf, uint32_t len, uint32_t crc) {
  if (!buf)
    return crc;

  const auto *crc_table = get_crc32_table();
  crc = ~crc;
  while (len--) {
    uint8_t idx = crc ^ *buf++;
    crc = crc_table[idx] ^ (crc >> 8);
  }
  crc = ~crc;

  return crc;
}

/////////////////////////////////////////////
void __crc32_exp_end() {}
/////////////////////////////////////////////

然后在 src/crc32_dump.cpp 加入在运行时提取对应机器码的逻辑:

#include "crc32.h"

#include <cassert>
#include <cstdint>
#include <fstream>

int main() {
  // 简单测试一下
  auto crc_52pojie = crc32((const uint8_t *)"52pojie", 7, 0);
  printf("crc32(52pojie) = %08x\n", crc_52pojie);
  assert(crc_52pojie == 0x78b5ff78);

  auto p_start = (char *)(__crc32_exp_start);
  auto p_end = (char *)(__crc32_exp_end);

  std::ofstream dump_bin("out/crc32.bin.wav", std::ios::binary);
  dump_bin.write(p_start, p_end - p_start);
  dump_bin.close();
  printf("dump size = %d bytes\n", p_end - p_start);
}

随后,在构建脚本底部加入新的编译过程:

[Plain Text] 纯文本查看 复制代码
::::::::::::::::::::: [构建 CRC32 与 dumper] 开始
%CXX% %CXXFLAGS% -c -o out/crc32.cpp.o src/crc32.cpp
%CXX% %CXXFLAGS% -c -o out/crc32_dump.cpp.o src/crc32_dump.cpp
%CXX% %LDFLAGS% -o out/crc32_dump.exe out/crc32.cpp.o out/crc32_dump.cpp.o
.\out\crc32_dump.exe
::::::::::::::::::::: [构建 CRC32 与 dumper] 结束

最后执行 .\build.cmd,将会编译并抽取这部分的代码到 out/crc32.bin.wav

添加引导

因为易语言的函数,大概长这样:

[Visual Basic] 纯文本查看 复制代码
' 启动函数调用
函数名称 (1, 2, 3)

' -------------------------------

.子程序 函数名称, 整数型
.参数 参数1, 整数型
.参数 参数2, 整数型
.参数 参数3, 整数型

' 你的代码
返回 (9)

易语言生成的机器码 (经过黑月处理) 大概是这样:

  push 3
  push 2
  push 1
  call 函数名称    ; 标准 stdcall
  ; 省略

函数名称:
  push ebp
  mov ebp, esp     ; 即便这个函数没有储存变量,也会设置 ebp
                   ; <---  此处开始是我们编写的代码
  mov eax, 0x9
  jmp @f           ; "E9 00 00 00 00" 没啥用的长跳转
@@:
  mov esp, ebp
  pop ebp
  ret 0x0C         ; stdcall 调用返回

因此,如果我们需要一个能直接在易语言用的「ShellCode」,就得照着易语言的规则来。

回到 src/crc32.h,更新一下 __crc32_exp_start 的函数签名:

__attribute__((naked, noinline)) void __crc32_exp_start();

然后到 src/crc32.cpp 改写该函数:

/////////////////////////////////////////////
__attribute__((naked, noinline)) void __crc32_exp_start() {
  // 将易语言的参数重新入栈
  asm("mov 0x10(%ebp), %ecx \n");
  asm("mov 0x0C(%ebp), %edx \n");
  asm("mov 0x08(%ebp), %eax \n");
  asm("pushl %ecx");
  asm("pushl %edx");
  asm("pushl %eax");
  // 调用函数
  asm("call %0" ::"m"(crc32));
  asm("add $0xc, %esp"); // 默认是 cdecl 调用约定,调用方平衡堆栈。
  // 还原 ebp 并返回
  asm("leave");
  asm("ret $0x0C");
};
/////////////////////////////////////////////

重新编译,得到一份新的 crc32.bin.wav 文件。

面向 ShellCode 编程

「置入代码」是易语言提供的一个简单粗暴的内嵌「编译后的机器码」的方案,有点像内联汇编,但是你不能使用名称来引用变量等信息。

首先新建一个易语言程序,依次点击菜单「I.插入」→「R.资源」→「S.声音」,建立一个空白的「声音1」资源。

将名称更为「CRC32_代码」,双击该行右侧的「内容」列,选择「导入新声音(I)」,选择上一步编译得到的「crc32.bin.wav」文件。

回到代码区段(程序集1),粘贴下述内容:

[Visual Basic] 纯文本查看 复制代码
.版本 2

开始测试()

.子程序 开始测试
.局部变量 crc, 整数型

' 模拟分段计算
crc = CRC32 (到字节集 (“Hello ”))
crc = CRC32 (到字节集 (“World”), crc)
检查 (crc = 1243066710)  ' 0x4a17b156

' 直接计算
crc = CRC32 (到字节集 (“52pojie”))
检查 (crc = 2025193336)  ' 0x78b5ff78

.子程序 Ab, 整数型, , 取字节集指针
.参数 变量, 字节集, 参考

置入代码 ({ 139, 69, 8, 139, 0, 131, 192, 8, 139, 229, 93, 194, 4, 0 })
返回 (-1)

.子程序 CRC32, 整数型, 公开, 可重复调用 (如文件分段计算)。后续调用需要提供上一次计算得到的值作为[初始值]。
.参数 内容, 字节集, 参考
.参数 初始值, 整数型, 可空, 若不指定或是初次调用,设为 [#CRC32_默认初始值]。

返回 (CRC32_高级 (Ab (内容), 取字节集长度 (内容), 初始值))

.子程序 CRC32_高级, 整数型, 公开, 可重复调用 (如文件分段计算)。后续调用需要提供上一次计算得到的值作为[初始值]。
.参数 缓冲区地址, 整数型
.参数 缓冲区长度, 整数型
.参数 初始值, 整数型, , 如果是第一次运行,初始值应为 [#CRC32_默认初始值]

置入代码 (#CRC32_代码)
返回 (-1)

此时在易语言 IDE 按下 F5 运行,底部的「输出」栏目应当输出下述内容:

* 测试: CRC32
* 完成,没有错误。

这表示我们移植的代码可以正常在易语言内运行。

优化输出

你可能已经发现之前代码提到的「默认是 cdecl 调用约定」。既然是默认的,那自然也可以使用其他的调用约定

其中一个比较特殊的是 regparm(3),即前三个参数通过 eaxedxecx 传递,后续的参数再入栈,在三个或更少参数的情况时会会有一定机率让编译的代码变小。

回到 src/crc32.h 进行更改:

[Diff] 纯文本查看 复制代码
 #include <cstdint>

+#ifndef __g_fastcall
+#define __g_fastcall __attribute__((regparm(3)))
+#endif

 __attribute__((naked, noinline)) void __crc32_exp_start();
-uint32_t crc32(const uint8_t *buf, uint32_t len, uint32_t crc);
+__g_fastcall uint32_t crc32(const uint8_t *buf, uint32_t len, uint32_t crc);
 void __crc32_exp_end();

也对 src/crc32.cpp 进行更改:

[Diff] 纯文本查看 复制代码
 /////////////////////////////////////////////
 __attribute__((naked, noinline)) void __crc32_exp_start() {
-  // 将易语言的参数重新入栈
+  // 将易语言的参数导入过来
+  // params: eax, edx, ecx
   asm("mov 0x10(%ebp), %ecx \n");
   asm("mov 0x0C(%ebp), %edx \n");
   asm("mov 0x08(%ebp), %eax \n");
-  asm("pushl %ecx");
-  asm("pushl %edx");
-  asm("pushl %eax");
   // 调用函数
   asm("call %0" ::"m"(crc32));
-  asm("add $0xc, %esp"); // 默认是 cdecl 调用约定,调用方平衡堆栈。
   // 还原 ebp 并返回
   asm("leave");
   asm("ret $0x0C");
 };
 /////////////////////////////////////////////

@@: 下面一点继续更改~

-uint32_t crc32(const uint8_t *buf, uint32_t len, uint32_t crc) {
+// buf@eax, len@edx, crc@ecx
+__g_fastcall uint32_t crc32(const uint8_t *buf, uint32_t len, uint32_t crc) {
   if (!buf)

重新编译,然后导入易语言,测试一切正常。

其它

胶水代码写起来还是挺麻烦的。

如果要同时导出多个函数,可以考虑将入口的 __crc32_exp_start 添加一个参数(如参数序号),然后根据这个值将参数转发到对应的函数。

最终的代码存档:

e-CRC32 代码存档 2023.10.05-10.08.7z (41.83 KB, 下载次数: 9)

※ 压缩包中的 v2023.10.05 目录为跟着教程走的结果。其他两个目录为后续更新添加的内容,主要为 C++ 方面的性能优化。


2023.10.08 更新:

  • 加入 CRC32c 实现
    • 同时加入了同利用 SSE 4.2 加速指令的优化实现
  • 重构代码
  • 加入 crc32_tool.exe 计算工具和对应的源码

crc32_tool.exe 运算速度特别块,实测正己老师的第一课 第一节.模拟器环境搭建.mp4 能在 259ms 内计算完成,吞吐率约 23.028 Gbps (大概 2.8GBps)。

> (Measure-Command { .\crc32_tool.exe "M:\Test\test.mp4" | Out-Default }).TotalMilliseconds
M:\Test\test.mp4: 401AF35D
259.5613

这应该是全网用易语言实现的 CRC32 算法的最快的一个了

2023.10.07 更新:

  • 加入可选的优化
    • 查表模式可以选择从 1K 表 → 4K 表 的加速运算;
    • 加入 Hagai 的无码表加速模式(默认不使用)
  • 加入编译开关接口,附加 build.default.cmd 构建推荐构建版本,或 build.full.cmd 构建所有支持的模式(查表 4K)

2023.10.06 更新:

找到个基于 SSE 4.2 的 CRC32 计算优化代码:

// SSE4.2 + PCLMUL 实现修改自:
// https://chromium.googlesource.com/chromium/src/+/f9a8b512a90f410bb9302a3137855fb688316d3d/third_party/zlib/crc32_simd.c#24
/*
 * crc32_sse42_simd_(): compute the crc32 of the buffer, where the buffer
 * length must be at least 64, and a multiple of 16. Based on:
 *
 * "Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction"
 *  V. Gopal, E. Ozturk, et al., 2009, http://intel.ly/2ySEwL0
 */

玩具:手动对齐置入代码

本来看编译器这么执着于将代码对齐,于是试了下… 但是发现性能并没有多大变化。

使用 TCC 编译,编译出来的是小巧的单文件。

原理很简单,找到程序里的代码区段然后将两个特征码包围的内容前后挪动(两个特征码合起来刚好 16 字节),使其实际代码为对齐 0x10 处开始。

e-naive-align 手动对齐置入代码 v2023.10.06.7z (4.13 KB, 下载次数: 0)

免费评分

参与人数 7威望 +1 吾爱币 +30 热心值 +7 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
苏紫方璇 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
DEATHTOUCH + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
vqzhanshi + 1 + 1 谢谢@Thanks!
hrh123 + 1 + 1 用心讨论,共获提升!
朱朱你堕落了 + 3 + 1 涉猎真广,技术大拿!
wystudio + 2 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

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

推荐
DEATHTOUCH 发表于 2023-10-9 00:19
爱飞的猫 发表于 2023-10-8 00:39
加上了,不过实测速度不如利用 `pclmulqdq` 和 SSE 4.2 的优化指令,能在更短的 cycle 时间内处理更多数 ...

这样来看你的那个crc32c_sse42_crc32_proc里面的
[C++] 纯文本查看 复制代码
  while (len >= 4)
  {
    crc = _mm_crc32_u32(crc, *(const uint32_t *)buf);
    len -= 4;
    buf += 4;
  }

这部分就可以进一步提高吞吐量了,就是32位的可能写起来稍微麻烦了点吧。
推荐
 楼主| 爱飞的猫 发表于 2023-10-8 00:39 |楼主
DEATHTOUCH 发表于 2023-10-5 13:05
可以考虑顺便再加一个Intel SSE4.2(应该也有15年了)的crc32,虽然多项式不一样,不过用来自校验速度更快 ...

加上了,不过实测速度不如利用 `pclmulqdq` 和 SSE 4.2 的优化指令,能在更短的 cycle 时间内处理更多数据(64 字节一组来计算)。

有个论文但我没读,直接扒的别人代码… CRC32c 部分应该还可以再优化下,如数据不足 64 字节时进行 16 字节块的处理。
4#
wasm2023 发表于 2023-10-5 11:24
5#
DEATHTOUCH 发表于 2023-10-5 13:05
可以考虑顺便再加一个Intel SSE4.2(应该也有15年了)的crc32,虽然多项式不一样,不过用来自校验速度更快,毕竟32位一次4字节。

点评

加上了,不过实测速度不如利用 `pclmulqdq` 和 SSE 4.2 的优化指令,能在更短的 cycle 时间内处理更多数据(64 字节一组来计算)。 有个论文但我没读,直接扒的别人代码… CRC32c 部分应该还可以再优化下,如数据  详情 回复 发表于 2023-10-8 00:39
这个过段时间再试,到时候另外整个函数计算。 我倒是找到个利用 SSE 4.2 和 PCLMULQDQ 指令的优化版,一会更新…  详情 回复 发表于 2023-10-6 12:19
6#
满不懂 发表于 2023-10-5 14:50
谢谢分享!收藏学习。
7#
 楼主| 爱飞的猫 发表于 2023-10-6 12:19 |楼主
本帖最后由 爱飞的猫 于 2023-10-6 12:28 编辑
DEATHTOUCH 发表于 2023-10-5 13:05
可以考虑顺便再加一个Intel SSE4.2(应该也有15年了)的crc32,虽然多项式不一样,不过用来自校验速度更快 ...

这个过段时间再试,到时候另外整个函数计算。

我倒是找到个利用 SSE 4.2 和 PCLMULQDQ 指令的优化版,计算结果是一致的。
8#
龙小 发表于 2023-10-12 17:26
好东西,收藏看看
9#
Nevatu 发表于 2023-10-20 15:44
感谢分享!!!
10#
pythonkylinos 发表于 2023-11-23 18:37
这个MinGW QT 的程序逆向是什么思路?

点评

文章里没有提到 QT?  详情 回复 发表于 2023-11-24 05:27
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-5-12 02:24

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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