首页
社区
课程
招聘
[原创]实现简易ARK工具(1) GDT表
发表于: 2025-7-10 21:17 4421

[原创]实现简易ARK工具(1) GDT表

2025-7-10 21:17
4421

前言

​ 大家好,在下最近打算开个坑写一下ARK工具的笔记。第一次接触windows内核,文中一定有许多错误,恳请指正,谢谢你的时间!

​ 鉴于文末贴了代码,所以设置为点赞或回复可观看,嘿嘿。

GDT (Global Descriptor Table)是告诉CPU"内存怎么分段、每段有什么权限"的一张表,ARK工具通过读取这张表来检查系统有没有被篡改。

GDT数组在内存中,每个元素是8字节的段描述符。每个段描述符用于表述三个关键字段:

所以这是命名角度的问题

从CPU角度: 线性地址 (Linear Address)

从程序角度: 虚拟地址 (Virtual Address)

举例说明有分页的情况

每个CPU核心都有自己的**GDTR**寄存器,指向各自的GDT表,进程在哪个核心上跑,指向的就是哪个核心的GDT表,可所有核心的GDT表内容是一样的。

测试:给win7 sp1 32位虚拟机设置为2处理器2内核数量,跑一下遍历的程序,发现GDT的四个基址不同,大小相同。

·· 疑问:如果在64位系统上跑,给出的也是32位大小的GDT表地址,不合理,难道给的是假的?

那么我们拿表的时候就要考虑多核问题,精确的拿到每个核心的GDT表。

模仿一下PCHunter拿的信息

思路

秉持着在R3能干的活不要在R0干的理念。

R3遍历核心读GDT地址->发送每个核心的GDTR给R0->R0读表数据发给R3->R3解析数据。

原始的段描述符转换成UI数据的解析方法

上面这张图片出自221K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6k6h3N6E0k6h3&6@1k6X3q4#2L8s2c8Q4x3X3g2U0L8$3#2Q4x3V1k6S2i4K6u0r3x3e0p5&6x3o6l9H3x3o6l9@1x3o6p5^5y4K6x3H3y4l9`.`.

在下常常被自己的函数解耦能力感动(破防,真是献丑了

Imgui的UI代码就不贴了,贴点功能代码

增加了对系统段的解析,涉及系统调用和各种门。

原来GDT(全局描述符表)中既可以存放段描述符(代码段、数据段),也可以存放门描述符(调用门、陷阱门、中断门)。

在GDT表中,段描述符的s字段决定了段的类型:

系统段的type字段含义:

代码段权限

数据段权限

UI显示的过滤条件自定
图片描述

struct SegmentDescriptor {
    // ========== 历史字段 (来自80286) ==========
    unsigned Limit1:16;    // 界限低16位 - 从80286继承
    unsigned Base1:16;     // 基址低16位 - 从80286继承 
    unsigned Base2:8;      // 基址中8位 - 从80286继承
    unsigned type:4;       // 段类型 - 从80286继承,但扩展了
    unsigned s:1;          // 系统段标志 - 从80286继承
    unsigned dpl:2;        // 特权级 - 从80286继承
    unsigned p:1;          // 存在位 - 从80286继承
     
    // ========== 80386新增字段 ==========
    unsigned Limit2:4;     // 界限高4位 - 新增,支持32位界限
    unsigned avl:1;        // 软件可用位 - 新增
    unsigned res:1;        // 保留位 - 新增,为未来扩展
    unsigned db:1;         // 操作数大小 - 新增,支持16/32位切换
    unsigned g:1;          // 粒度位 - 新增,突破界限限制
    unsigned Base3:8;      // 基址高8位 - 新增,支持32位基址
};
struct SegmentDescriptor {
    // ========== 历史字段 (来自80286) ==========
    unsigned Limit1:16;    // 界限低16位 - 从80286继承
    unsigned Base1:16;     // 基址低16位 - 从80286继承 
    unsigned Base2:8;      // 基址中8位 - 从80286继承
    unsigned type:4;       // 段类型 - 从80286继承,但扩展了
    unsigned s:1;          // 系统段标志 - 从80286继承
    unsigned dpl:2;        // 特权级 - 从80286继承
    unsigned p:1;          // 存在位 - 从80286继承
     
    // ========== 80386新增字段 ==========
    unsigned Limit2:4;     // 界限高4位 - 新增,支持32位界限
    unsigned avl:1;        // 软件可用位 - 新增
    unsigned res:1;        // 保留位 - 新增,为未来扩展
    unsigned db:1;         // 操作数大小 - 新增,支持16/32位切换
    unsigned g:1;          // 粒度位 - 新增,突破界限限制
    unsigned Base3:8;      // 基址高8位 - 新增,支持32位基址
};
15                    3   2   1   0
┌──────────────────────┬───┬───────┐
│      索引 (13位)     │TI │RPL(2位)│
│    (Index 13 bits)   │   │       │
└──────────────────────┴───┴───────┘
- 位 15-3: 索引字段 (13位) = 8192个可能的描述符
  索引0特殊: 指向NULL描述符(必须为0)
  有效索引: 1-8191
   
- 位 2:    TI位 (1位)      = 表指示器
选择使用哪个段描述符表
TI = 0: 使用GDT (Global Descriptor Table)
TI = 1: 使用LDT (Local Descriptor Table)
 
- 位 1-0:  RPL字段 (2位)   = 请求特权级
RPL = 0: Ring 0 (内核级)
RPL = 1: Ring 1 (系统服务级,很少用) 
RPL = 2: Ring 2 (设备驱动级,很少用)
RPL = 3: Ring 3 (用户级)
15                    3   2   1   0
┌──────────────────────┬───┬───────┐
│      索引 (13位)     │TI │RPL(2位)│
│    (Index 13 bits)   │   │       │
└──────────────────────┴───┴───────┘
- 位 15-3: 索引字段 (13位) = 8192个可能的描述符
  索引0特殊: 指向NULL描述符(必须为0)
  有效索引: 1-8191
   
- 位 2:    TI位 (1位)      = 表指示器
选择使用哪个段描述符表
TI = 0: 使用GDT (Global Descriptor Table)
TI = 1: 使用LDT (Local Descriptor Table)
 
- 位 1-0:  RPL字段 (2位)   = 请求特权级
RPL = 0: Ring 0 (内核级)
RPL = 1: Ring 1 (系统服务级,很少用) 
RPL = 2: Ring 2 (设备驱动级,很少用)
RPL = 3: Ring 3 (用户级)
步骤1: 解析段选择子CS
CS = 0x0020 (假设)
实际不是直接取第20项,而是:
1. 索引 = 0x20 >> 3 = 4 (指向GDT第4项)
2. TI = (0x20 >> 2) & 1 = 0 (使用GDT)
3. RPL = 0x20 & 3 = 0 (请求特权级0)
 
步骤2: 从GDT获取段描述符
1. 根据索引4,从GDT[4]读取8字节段描述符
2. 解析出基址、界限、权限等信息
     
步骤3: 地址计算
1. 存在位检查: if (P位 == 0) → 段不存在 异常
2. 特权级检查: if (max(CPL,RPL) > DPL) → 特权级违例
3. 类型检查: 执行/读取/写入权限是否匹配
4. 界限检查: if (偏移 > 段界限) → 段界限违例
 
步骤4: 页表转换
1. 线性地址 = 段基址 + 偏移地址
2. 如果基址=0 (平坦模式),则线性地址=偏移地址
步骤1: 解析段选择子CS
CS = 0x0020 (假设)
实际不是直接取第20项,而是:
1. 索引 = 0x20 >> 3 = 4 (指向GDT第4项)
2. TI = (0x20 >> 2) & 1 = 0 (使用GDT)
3. RPL = 0x20 & 3 = 0 (请求特权级0)
 
步骤2: 从GDT获取段描述符
1. 根据索引4,从GDT[4]读取8字节段描述符
2. 解析出基址、界限、权限等信息
     
步骤3: 地址计算
1. 存在位检查: if (P位 == 0) → 段不存在 异常
2. 特权级检查: if (max(CPL,RPL) > DPL) → 特权级违例
3. 类型检查: 执行/读取/写入权限是否匹配
4. 界限检查: if (偏移 > 段界限) → 段界限违例
 
步骤4: 页表转换
1. 线性地址 = 段基址 + 偏移地址
2. 如果基址=0 (平坦模式),则线性地址=偏移地址
int main()
{
  GDTR gdtr;
  SYSTEM_INFO SystemInfo;
  GetSystemInfo(&SystemInfo);
  int Mask = 1;
  printf("GDT dwNumberOfProcessors:%d\n", SystemInfo.dwNumberOfProcessors);
  for (int i = 0; i < SystemInfo.dwNumberOfProcessors; i++) {
    SetProcessAffinityMask(GetCurrentProcess(), Mask);
    __asm {
      sgdt gdtr
      //lgdt GDT
    }
 
    printf("GDT Base:%08x Limit:%08x\n", gdtr.Base, gdtr.Limit);
    Mask <<= 1;
  }
   
  system("pause");
  return 0;
}
int main()
{
  GDTR gdtr;
  SYSTEM_INFO SystemInfo;
  GetSystemInfo(&SystemInfo);
  int Mask = 1;
  printf("GDT dwNumberOfProcessors:%d\n", SystemInfo.dwNumberOfProcessors);
  for (int i = 0; i < SystemInfo.dwNumberOfProcessors; i++) {
    SetProcessAffinityMask(GetCurrentProcess(), Mask);
    __asm {
      sgdt gdtr
      //lgdt GDT
    }
 
    printf("GDT Base:%08x Limit:%08x\n", gdtr.Base, gdtr.Limit);
    Mask <<= 1;
  }
   
  system("pause");
  return 0;
}
R3(用户态)                    R0(内核态驱动)
│                           │
├─1. sgdt获取GDTR──────────┤
├─2. 计算GDT大小(Limit+1)─┤
├─3. malloc申请缓冲区─────┤
├─4. 发送[CPU索引+GDTR+缓冲区]──►│
│                           ├─5. KeSetSystemAffinityThread切换到对应核心
│                           ├─6. 直接从GDT基址复制数据
│◄─7. 返回[完整GDT数据]─────┤
├─8. 解析段描述符数据───────┤
├─9. 转换为GDT_INFO格式───┤
├─10. 显示在UI界面────────┤
├─11. free释放缓冲区──────┤
R3(用户态)                    R0(内核态驱动)
│                           │
├─1. sgdt获取GDTR──────────┤
├─2. 计算GDT大小(Limit+1)─┤
├─3. malloc申请缓冲区─────┤
├─4. 发送[CPU索引+GDTR+缓冲区]──►│
│                           ├─5. KeSetSystemAffinityThread切换到对应核心
│                           ├─6. 直接从GDT基址复制数据
│◄─7. 返回[完整GDT数据]─────┤
├─8. 解析段描述符数据───────┤
├─9. 转换为GDT_INFO格式───┤
├─10. 显示在UI界面────────┤
├─11. free释放缓冲区──────┤
Base字段重组:
Base3(8位)     Base2(8位)     Base1(16位)
字节7         字节4          字节3-2
┌──────────┬──────────┬────────────────┐
│63......56│39......32│31.............16│  ← 段描述符中的原始位置
└──────────┴──────────┴────────────────┘
     ↓           ↓            ↓
  << 24       << 16         不移位
     ↓           ↓            ↓
┌──────────┬──────────┬────────────────┐
│31......24│23......16│15.............0│  ← 32位完整基址
└──────────┴──────────┴────────────────┘
 
Limit字段重组:
Limit2(4位)        Limit1(16位)
字节6低4位         字节1-0 
┌──────────────┬────────────────────┐
│51..........48│15................0│  ← 段描述符中的原始位置
└──────────────┴────────────────────┘
       ↓                  ↓
    << 16              不移位
       ↓                  ↓
┌──────────────┬────────────────────┐
│19..........16│15................0│  ← 20位完整界限
└──────────────┴────────────────────┘
 
段粒度转换:
G=0 (字节粒度):
  界限值直接使用: 假设limit = 0x12345 → 段大小 = 0x12345 字节
 
G=1 (页粒度): 
  界限值需要转换: 假设limit = 0x123 → 段大小 = (0x123 << 12) | 0xFFF
   
  转换过程:
  原始limit: 0x00000123 (20位)
            ↓ << 12 (左移12位,乘以4KB)
  扩展后:   0x00123000 (32位)
            ↓ | 0xFFF (低12位置1)
  最终:     0x00123FFF (段可用到页尾)
Base字段重组:
Base3(8位)     Base2(8位)     Base1(16位)
字节7         字节4          字节3-2
┌──────────┬──────────┬────────────────┐
│63......56│39......32│31.............16│  ← 段描述符中的原始位置
└──────────┴──────────┴────────────────┘
     ↓           ↓            ↓
  << 24       << 16         不移位
     ↓           ↓            ↓
┌──────────┬──────────┬────────────────┐
│31......24│23......16│15.............0│  ← 32位完整基址
└──────────┴──────────┴────────────────┘
 
Limit字段重组:
Limit2(4位)        Limit1(16位)
字节6低4位         字节1-0 
┌──────────────┬────────────────────┐
│51..........48│15................0│  ← 段描述符中的原始位置
└──────────────┴────────────────────┘
       ↓                  ↓
    << 16              不移位
       ↓                  ↓
┌──────────────┬────────────────────┐
│19..........16│15................0│  ← 20位完整界限
└──────────────┴────────────────────┘
 
段粒度转换:
G=0 (字节粒度):
  界限值直接使用: 假设limit = 0x12345 → 段大小 = 0x12345 字节
 
G=1 (页粒度): 
  界限值需要转换: 假设limit = 0x123 → 段大小 = (0x123 << 12) | 0xFFF
   
  转换过程:
  原始limit: 0x00000123 (20位)
            ↓ << 12 (左移12位,乘以4KB)
  扩展后:   0x00123000 (32位)
            ↓ | 0xFFF (低12位置1)
  最终:     0x00123FFF (段可用到页尾)
//R3 
//单个段描述符信息在UI上展示的信息
typedef struct _GDT_INFO {
    UINT    cpuIndex;       // CPU序号
    USHORT  selector;       // 段选择子(在GDT中的索引 * 8)
    ULONG64 base;           // 基址(Base1+Base2+Base3拼接)
    ULONG   limit;          // 界限(Limit1+Limit2拼接)
    UCHAR   g;              // 段粒度(0=字节,1=4KB)
    UCHAR   dpl;            // 段特权级(0=内核,3=用户)
    UCHAR   type;           // 段类型
    UCHAR   system;         // 系统段标志
    BOOL    p;              // 段存在位
} GDT_INFO, * PGDT_INFO;
 
typedef struct SegmentDescriptor {
    // ========== 历史字段 (来自80286) ==========
    unsigned Limit1 : 16;    // 界限低16位 - 从80286继承
    unsigned Base1 : 16;     // 基址低16位 - 从80286继承 
    unsigned Base2 : 8;      // 基址中8位 - 从80286继承
    unsigned type : 4;       // 段类型 - 从80286继承,但扩展了
    unsigned s : 1;          // 系统段标志 - 从80286继承
    unsigned dpl : 2;        // 特权级 - 从80286继承
    unsigned p : 1;          // 存在位 - 从80286继承
 
    // ========== 80386新增字段 ==========
    unsigned Limit2 : 4;     // 界限高4位 - 新增,支持32位界限
    unsigned avl : 1;        // 软件可用位 - 新增
    unsigned res : 1;        // 保留位 - 新增,为未来扩展
    unsigned db : 1;         // 操作数大小 - 新增,支持16/32位切换
    unsigned g : 1;          // 粒度位 - 新增,突破界限限制
    unsigned Base3 : 8;      // 基址高8位 - 新增,支持32位基址
}*PSEGDESC;
 
#pragma pack(push, 1)
typedef struct GDTR {
  unsigned short Limit;
  unsigned int   Base;
}*PGDTR;
#pragma pack(pop)
 
typedef struct GDT_DATA_REQ {
    unsigned CpuIndex;
    GDTR Gdtr;
}*PGDT_DATA_REQ;
 
 
std::vector<GDT_INFO> _gdtVec;
PSEGDESC ArkR3::GetSingeGDT(UINT cpuIndex, PGDTR pGdtr);          //获得单核GDT表数据指针
std::vector<GDT_INFO> GetGDTVec();                                //返回所有核心GDT数组_gdtVec
//R3 
//单个段描述符信息在UI上展示的信息
typedef struct _GDT_INFO {
    UINT    cpuIndex;       // CPU序号
    USHORT  selector;       // 段选择子(在GDT中的索引 * 8)
    ULONG64 base;           // 基址(Base1+Base2+Base3拼接)
    ULONG   limit;          // 界限(Limit1+Limit2拼接)
    UCHAR   g;              // 段粒度(0=字节,1=4KB)
    UCHAR   dpl;            // 段特权级(0=内核,3=用户)
    UCHAR   type;           // 段类型
    UCHAR   system;         // 系统段标志
    BOOL    p;              // 段存在位
} GDT_INFO, * PGDT_INFO;
 
typedef struct SegmentDescriptor {
    // ========== 历史字段 (来自80286) ==========
    unsigned Limit1 : 16;    // 界限低16位 - 从80286继承
    unsigned Base1 : 16;     // 基址低16位 - 从80286继承 
    unsigned Base2 : 8;      // 基址中8位 - 从80286继承
    unsigned type : 4;       // 段类型 - 从80286继承,但扩展了
    unsigned s : 1;          // 系统段标志 - 从80286继承
    unsigned dpl : 2;        // 特权级 - 从80286继承
    unsigned p : 1;          // 存在位 - 从80286继承
 
    // ========== 80386新增字段 ==========
    unsigned Limit2 : 4;     // 界限高4位 - 新增,支持32位界限
    unsigned avl : 1;        // 软件可用位 - 新增
    unsigned res : 1;        // 保留位 - 新增,为未来扩展
    unsigned db : 1;         // 操作数大小 - 新增,支持16/32位切换
    unsigned g : 1;          // 粒度位 - 新增,突破界限限制
    unsigned Base3 : 8;      // 基址高8位 - 新增,支持32位基址
}*PSEGDESC;
 
#pragma pack(push, 1)
typedef struct GDTR {
  unsigned short Limit;
  unsigned int   Base;
}*PGDTR;
#pragma pack(pop)
 
typedef struct GDT_DATA_REQ {
    unsigned CpuIndex;
    GDTR Gdtr;
}*PGDT_DATA_REQ;
 
 
std::vector<GDT_INFO> _gdtVec;
PSEGDESC ArkR3::GetSingeGDT(UINT cpuIndex, PGDTR pGdtr);          //获得单核GDT表数据指针
std::vector<GDT_INFO> GetGDTVec();                                //返回所有核心GDT数组_gdtVec
//R3
PSEGDESC ArkR3::GetSingeGDT(UINT cpuIndex, PGDTR pGdtr)
{
    DWORD gdtSize = pGdtr->Limit + 1;
    PSEGDESC pBuffer = (PSEGDESC)malloc(gdtSize);
    if (!pBuffer) {
        LogErr("GetSingeGDT malloc err");
        return nullptr;
    }
 
    GDT_DATA_REQ req = { 0 };
    req.CpuIndex = cpuIndex;
    req.Gdtr = *pGdtr;
 
    DWORD dwRetBytes = 0;
    DeviceIoControl(m_hDriver, CTL_GET_GDT_DATA, &req, sizeof(GDT_DATA_REQ),
        pBuffer, gdtSize, &dwRetBytes, NULL);
 
    return pBuffer;
}
 
std::vector<GDT_INFO> ArkR3::GetGDTVec()
{
    _gdtVec.clear();
 
    GDTR gdtr = { 0 };
    SYSTEM_INFO SystemInfo;
    GetSystemInfo(&SystemInfo);
    Log("GetGDTVec GDT dwNumberOfProcessors:%d\n", SystemInfo.dwNumberOfProcessors);
 
    for (UINT i = 0; i < SystemInfo.dwNumberOfProcessors; i++) {
        DWORD_PTR mask = 1UL << i;  //Mask按位 和 i 一致
        SetProcessAffinityMask(GetCurrentProcess(), mask);
        __asm {
            sgdt gdtr
        }
 
        Log("GetGDTVec CPU %d: GDTR Base=%p, Limit=%X\n", i, (void*)gdtr.Base, gdtr.Limit);
        PSEGDESC pGdtData = GetSingeGDT(i, &gdtr);
        if (pGdtData) {
            DWORD descCount = (gdtr.Limit + 1) / 8;  // 段描述符数量
            Log("GetGDTVec CPU %d: 解析 %d 个段描述符\n", i, descCount);
 
            for (UINT index = 0; index < descCount; index++) {
                //pDesc是段描述符指针 下面将原始数据转换成UI上显示的数据格式
                PSEGDESC pDesc = (PSEGDESC)((PUCHAR)pGdtData + index * sizeof(SegmentDescriptor));
 
                // 解析成GDT_INFO
                GDT_INFO gdtInfo = { 0 };
                gdtInfo.cpuIndex = i;
                gdtInfo.selector = index * sizeof(SegmentDescriptor);
 
                // 基址重组 32bit = Base1(16) + Base2(8) + Base3(8)
                gdtInfo.base = pDesc->Base1 |(pDesc->Base2 << 16) |(pDesc->Base3 << 24);
 
                // 界限重组:Limit1(16) + Limit2(4)
                gdtInfo.limit = pDesc->Limit1 | (pDesc->Limit2 << 16);
 
                // 段粒度
                gdtInfo.g = pDesc->g;
                if (gdtInfo.g) {
                    gdtInfo.limit = (gdtInfo.limit << 12) | 0xFFF;  // 低12bit置1 = 4K
                }
 
                gdtInfo.dpl = pDesc->dpl;           // 段特权级
                gdtInfo.type = pDesc->type;         // 段类型
                gdtInfo.system = pDesc->s;          // 系统段标志
                gdtInfo.p = pDesc->p;               // 存在位
 
                _gdtVec.emplace_back(gdtInfo);
                Log("GetGDTVec  [%02d] Sel:0x%04X Base:0x%08X Limit:0x%08X DPL:%d Type:0x%X %s\n",
                    index, gdtInfo.selector, (DWORD)gdtInfo.base, gdtInfo.limit,
                    gdtInfo.dpl, gdtInfo.type, gdtInfo.p ? "P" : "NP");
            }
 
            free(pGdtData);
 
        }
        else {
            Log("GetGDTVec CPU %d: pGdtData nullptr\n", i);
        }
 
    }
 
    Log("GetGDTVec成功获取 %zu 个段描述符信息\n", _gdtVec.size());
    return _gdtVec;
}
//R3
PSEGDESC ArkR3::GetSingeGDT(UINT cpuIndex, PGDTR pGdtr)

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2025-7-18 00:56 被X66iaM编辑 ,原因: 勘误
收藏
免费 31
支持
分享
最新回复 (13)
雪    币: 8441
活跃值: (4942)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
为你点赞!
2025-7-12 08:59
0
雪    币: 238
活跃值: (1510)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
1
2025-7-12 20:59
0
雪    币: 113
活跃值: (1540)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
如果在64位系统上跑,给出的也是32位大小的GDT表地址,不合理,难道给的是假的?
2025-7-13 16:38
0
雪    币: 725
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
支持,感谢分享
2025-7-14 09:54
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
支持,感谢分享
2025-7-14 10:40
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
6666
2025-7-14 15:44
0
雪    币: 3550
活跃值: (3236)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
支持,感谢分享
2025-7-14 20:36
0
雪    币: 0
活跃值: (728)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
感谢分享
2025-7-14 21:39
0
雪    币: 398
活跃值: (1302)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
感谢分享
2025-7-15 06:32
0
雪    币: 239
活跃值: (228)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
感谢分享
2025-8-28 18:54
0
雪    币: 201
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
111111111111111
2025-9-4 21:37
0
雪    币: 2643
活跃值: (7230)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
感谢分享
2025-9-19 07:49
0
雪    币: 205
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
谢谢分享
18小时前
0
游客
登录 | 注册 方可回帖
返回