查看: 78|回复: 0

[转载图文] Sandboxie循序渐进

[复制链接]
发表于 2021-1-5 21:01:37 | 显示全部楼层 |阅读模式

Sandboxie循序渐进

Sandboxie循序渐进

本文为看雪论优秀文章
看雪论坛作者ID:一半人生
Sandboxie注入方式回调处理数据发送至SbieSvc,Svc初始化Ldr,监控篇上描述了流程,下面分享一下处理细节。
  
  Sandboxie并未直接再回调中处理,而是发送LPC注入信号至SbieSvC来完成,等待事件。
  

Sandboxie循序渐进

Sandboxie循序渐进
  
  1. LowLevel是DLL项目,DriverAssist::InjectLow_InitHelper(ULONG *errlvl)初始化Helper将会获取资源DLL,保存节表,获取LdrInitializeThunk函数地址。
HRSRC hrsrc = FindResource(NULL, L"LOWLEVEL", RT_RCDATA);
    if (! hrsrc)
        return false;
  
    ULONG binsize = SizeofResource(NULL, hrsrc);
    if (! binsize)
        return false;
  
HGLOBAL hglob = LoadResource(NULL, hrsrc);
..................
section = IMAGE_FIRST_SECTION(nt_hdrs);
..................
    m_LdrInitializeThunk = (ULONG_PTR) GetProcAddress(_Ntdll, "LdrInitializeThunk");
  
    if (! m_LdrInitializeThunk)
        return false;
x64 add
#ifdef _WIN64
    if (Dll_Windows >= 10) {
        unsigned char * code;
        code = (unsigned char *)m_LdrInitializeThunk;
       if (*(ULONG *)&code[0] == 0x24048b48 && code[0xa] == 0x48) {
            m_LdrInitializeThunk += 0xa;
        }
}

  2. InjectLow_InitHelper负责加载Lowlevel.dll数据初始化ldr函数地址。
DriverAssist::InjectLow_InitSyscalls()负责初始化sys_data
void DriverAssist::InjectLow(void *_msg)是SvcInjectEntry入口点,填充lowdata结构体,Lowdata结构体包括下述
   SBIELOW_DATA lowdata;
    memzero(&lowdata, sizeof(lowdata));
  
    lowdata.ntdll_base = (ULONG64)(ULONG_PTR)_Ntdll;
  
    lowdata.is_wow64 = msg->is_wow64;
    lowdata.bHostInject = msg->bHostInject;
  
lowdata.RealNtDeviceIoControlFile = (ULONG64) GetProcAddress((HMODULE) lowdata.ntdll_base,"NtDeviceIoControlFile");
    void *remote_addr = InjectLow_CopyCode(hProcess, lowdata.is_wow64, lowdata.LdrInitializeThunk_tramp, sizeof(lowdata.LdrInitializeThunk_tramp));
    if (!remote_addr) {
        errlvl = 0x33;
        goto finish;
    }

  3. InjectLow_CopyCode负责填充lowdata.IdrInitalizeThunK_tramp。
lowdata.long_diff = TRUE;
    if (Has32BitJumpHorizon((void *)m_LdrInitializeThunk, remote_addr)) {
        lowdata.long_diff = FALSE;
    }
#else
    lowdata.long_diff = FALSE;
#endif
1.InjectLow_SendHandle将SbieDrv句柄拷贝到目标进程
    lowdata.api_device_handle = (ULONG64)(ULONG_PTR)
                                        InjectLow_SendHandle(hProcess);
    if (! lowdata.api_device_handle) {
  
        errlvl = 0x22;
        goto finish;
    }
    lowdata.api_sbiedrv_ctlcode = API_SBIEDRV_CTLCODE;
    lowdata.api_invoke_syscall = API_INVOKE_SYSCALL;
    memcpy(lowdata.NtDelayExecution_code, &m_syscall_data[2], (32 * 4));

  4. tramp_remote_addr构造
#ifdef _WIN64
    lowdata.Sbie64bitJumpTable = (SBIELOW_J_TABLE *) ((ULONG_PTR) remote_addr +m_sbielow_len+0x400); //(0x400 - (m_sbielow_len & 0x3ff))+ m_sbielow_len;
#endif
  
    ULONG_PTR tramp_remote_addr =   // calculate address in remote process
            (ULONG_PTR)remote_addr
        +   m_sbielow_data_offset     // offset of args area
        +   FIELD_OFFSET(SBIELOW_DATA, LdrInitializeThunk_tramp);

  5. LowDLl充当执行的shellcode,data_offset是初始化时候代码如下,这是获取的’zzzz’节表的入口点和数据大小:
   targets = (MY_TARGETS *)& bindata[section[1].PointerToRawData];
    m_sbielow_start_offset = (ULONG)targets->entry - section[0].VirtualAddress;
    m_sbielow_data_offset = (ULONG)targets->data - section[0].VirtualAddress;

  6. 绑定
 if (! InjectLow_BuildTramp(_msg, lowdata.long_diff,
                lowdata.LdrInitializeThunk_tramp, tramp_remote_addr)) {
  
        errlvl = 0x44;
        goto finish;
    }
  
    //
    // copy the syscall data buffer (m_syscall_data) to target process

  7. 拷贝系统数据缓冲到目标进程
void *remote_syscall_data = InjectLow_CopySyscalls(hProcess);
    if (! remote_syscall_data) {
  
        errlvl = 0x55;
        goto finish;
    }
  
    lowdata.syscall_data = (ULONG64)(ULONG_PTR)remote_syscall_data;

  8. 将低数据参数区域(包括转换后的蹦床代码)写入目标进程,并使其执行读取。
    if (! InjectLow_CopyData(hProcess, remote_addr, &lowdata)) {
  
        errlvl = 0x66;
        goto finish;
    }

  9. 覆盖LdrInitializeThunk的顶部以跳转到注入的代码,请注意,我们必须跳过8字节签名(.HEAD.00)删除了对(.HEAD.00)的硬编码依赖性。不再需要在remote_addr中添加8。
if (!InjectLow_WriteJump(hProcess, (UCHAR *)remote_addr + m_sbielow_start_offset, lowdata.long_diff, &lowdata)) {
        errlvl = 0x77;
        goto finish;
    }
  
    //
    // put process into a job for win32 restrictions
    //
2.
    if (!msg->bHostInject)
    {
        if(! GuiServer::GetInstance()->InitProcess(
                hProcess, msg->process_id, msg->session_id,
                msg->add_to_job)) {
  
            errlvl = 0x88;
            goto finish;
        }
}
    //
    // notify driver that we successfully injected the lowlevel code
    //
if (SbieApi_CallOne(API_INJECT_COMPLETE, msg->process_id) == 0)

  10. 上述过程描述细节狠清楚,利用这种方式实现了注入:
  Sandboxie_HOOK_r3模板:
#define ANTSDLL_HOOK(pfx,proc)                  \
    *(ULONG_PTR *)&__sys_##proc = (ULONG_PTR)   \
        ANTSDLL_Hook(#proc, proc, pfx##proc);   \
if (! __sys_##proc) return FALSE;
  
#define GETPROCADDR_DEF(name) \  获取函数地址
    P_##name name = (P_##name) GetProcAddress(module, #name)

  通用HOOK宏定义,入口SBIEDLL_HOOK函数实现,三要素:函数名/函数地址/HOOK函数地址。
_FX void *ANTSDLL_Hook(
const char *SourceFuncName, void *SourceFunc, void *DetourFunc
用法:
GETPROCADDR_DEF(CoGetClassObject);
ANTSDLL_HOOK(Com_, CoGetClassObject);

  r3的就不多说了,基于三要素简答方便管理一些,ANTSDLL_Hook返回的是原函数地址。
_FX void *ANTSDLL_Hook(
const char *SourceFuncName, void *SourceFunc, void *DetourFunc
)
{
UCHAR *tramp, *func;
ULONG_PTR diff;
ULONG_PTR target;
ULONG prot, dummy_prot;
void *orig_addr;
#ifdef _WIN64
long long delta;
BOOLEAN CallInstruction64 = FALSE;
#endif _WIN64
  
// validate parameter - 验证参数
if (!SourceFunc) {
return NULL;
}
 
// jmp xx 共两个字节
if (*(UCHAR *)SourceFunc == 0xEB) {
signed char offset = *((signed char *)SourceFunc + 1);
SourceFunc = (UCHAR *)SourceFunc + offset + 2;
}
  
// jmp xx xx xx xx 后面 jmp current + diff + 5
while (*(UCHAR *)SourceFunc == 0xE9) {
  
diff = *(LONG *)((ULONG_PTR)SourceFunc + 1);
target = (ULONG_PTR)SourceFunc + diff + 5;
if (target == (ULONG_PTR)DetourFunc) {
return NULL;
}
  
#ifdef _WIN64
  
SourceFunc = (void *)target;
  
#else ! WIN_64
  
func = SbieDll_Hook_CheckChromeHook((void *)target);
if (func != (void *)target) {
SourceFunc = func;
goto skip_e9_rewrite;
}
  
func = (UCHAR *)SourceFunc;
diff = (UCHAR *)DetourFunc - (func + 5);
++func;
if (!VirtualProtect(func, 4, PAGE_EXECUTE_READWRITE, &prot)) {
ULONG err = GetLastError();
return NULL;
}
*(ULONG *)func = (ULONG)diff;
VirtualProtect(func, 4, prot, &dummy_prot);
  
return (void *)target;
  
skip_e9_rewrite:;
  
#endif _WIN64
}
  
#ifdef _WIN64
// avast.snxhk64.dll compatibility nop+jmp (90,E9)
if (*(USHORT *)SourceFunc == 0xE990) {
diff = *(LONG *)((ULONG_PTR)SourceFunc + 2);
target = (ULONG_PTR)SourceFunc + diff + 6;
if (*(USHORT *)target == 0x25FF)
SourceFunc = (void *)target;
}
  
// 如果  [x] 只替换地址
// 0xff25 四个字节地址
if(*(USHORT *)SourceFunc == 0x48 && 
*(USHORT *)((UCHAR *)SourceFunc) == 0x25FF) {
// 4825ff and 25ff 一样 所以和ff25一起处理
SourceFunc = (UCHAR *)SourceFunc + 1;
}
  
if (*(USHORT *)SourceFunc == 0x25FF) {
  
orig_addr = NULL;
  
// 首先取地址 +2是0xff25
diff = *(LONG *)((ULONG_PTR)SourceFunc + 2);
  
// jmp qword ptr + 地址 jmp到目标地址判断是否等于我们的挂钩函数
target = (ULONG_PTR)SourceFunc + 6 + diff;
orig_addr = (void *)*(ULONG_PTR *)target;
if (orig_addr == DetourFunc) {
return NULL;
}
  
func = (UCHAR *)target;
if (!VirtualProtect(func, 8, PAGE_EXECUTE_READWRITE, &prot)) {
ULONG err = GetLastError();
return NULL;
}
// 进行挂钩HOOK
*(ULONG_PTR *)target = (ULONG_PTR)DetourFunc;
VirtualProtect(func, 8, prot, &dummy_prot);
  
return orig_addr;
}
  
#endif _WIN64
  
#ifdef _WIN64
// call qword ptr [0xaddress]
if (*(USHORT *)SourceFunc == 0x15FF) {
  
//
// the call instruction pushes a qword into the stack, we need
// to remove this qword before calling our detour function
//
  
UCHAR *NewDetour = (UCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 128);
  
NewDetour[0] = 0x58;        // pop rax
NewDetour[1] = 0x48;        // mov rax, DetourFunc
NewDetour[2] = 0xB8;
*(ULONG_PTR *)(&NewDetour[3]) = (ULONG_PTR)DetourFunc;
NewDetour[11] = 0xFF;       // jmp rax
NewDetour[12] = 0xE0;
  
DetourFunc = NewDetour;
  
//
// when our detour function calls the trampoline to invoke the
// original code, we have to push the qword back into the stack,
// because this is what the original code expects
//
  
NewDetour[16] = 0x48;       // mov rax, SourceFunc+6
NewDetour[17] = 0xB8;
*(ULONG_PTR *)(&NewDetour[18]) = (ULONG_PTR)SourceFunc + 6;
NewDetour[26] = 0x50;       // push rax
NewDetour[27] = 0x48;       // mov rax, trampoline code
NewDetour[28] = 0xB8;
*(ULONG_PTR *)(&NewDetour[29]) = 0;
NewDetour[37] = 0xFF;       // jmp rax
NewDetour[38] = 0xE0;
  
CallInstruction64 = TRUE;
  
//
// overwrite the code at the target of the call instruction
//
  
diff = *(LONG *)((ULONG_PTR)SourceFunc + 2);
target = (ULONG_PTR)SourceFunc + 6 + diff;
SourceFunc = (void *)*(ULONG_PTR *)target;
// if is 0xff15, SourceFunc = jmp [current + offset + 6]
}
  
#endif _WIN64
/*
1. 0xFF25 jmp qword ptr xx xx xx xx R3进行inlinehook直接返回
2. EB/E9/FF15/驱动挂钩处理
3. 貌似没对E8做处理?
*/
// 调用驱动创建tramp 
  
__declspec(align(8)) ULONG64 parms[8];
//tramp = (UCHAR*)VirtualAlloc(NULL, 128, MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
tramp = (UCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 128);
//memset(tramp, 0, 128);
 
parms[0] = 0;
parms[1] = (ULONG64)(ULONG_PTR)SourceFunc;
parms[2] = (ULONG64)(ULONG_PTR)tramp;
Hook_Api_Tramp(parms);
  
// NtDeviceIoControlFile --> API_HOOK_TRAMP --> \\Device\\SandboxieDriverApi 处理tramp
//
// drv.ApiInit() --> 初始化API_DEVICE_NAME端口 Api_SetFunction(API_HOOK_TRAMP,         Hook_Api_Tramp);
//
// Hook_Api_Tramp() --> Hook_BuildTramp() 进行HOOK挂钩
//
// 先利用原dll导出函数
// if (SbieApi_HookTramp(SourceFunc, tramp) != 0) {
//  return NULL;
//}
  
func = (UCHAR *)SourceFunc;
  
if (!VirtualProtect(&func[-8], 20, PAGE_EXECUTE_READWRITE, &prot)) {
  
ULONG err = GetLastError();
return NULL;
}

  代码中有注释,主要对不同的机器码-call/jmp分别做了挂钩处理,对于跳转过去直接是汇编代码Hook,先申请一段内存,拷贝前几行指令到内存,Systemcall从申请的内存地址执行,跳转到挂钩的下一行即可。
  DriverEntry:
  

Sandboxie循序渐进

Sandboxie循序渐进
  
  1) Driver_CheckOsVersion
  
  版本兼容,代码中对不同型号版本结构偏移纪录。
g_TrapFrameOffset = 0x90 and g_TrapFrameOffset = 0x1d8

  2) Pool_Create
  
  Pool_Alloc_Page封装了ExAllocatePoolWithTag申请内存池,最初申请了大小4096的内存池。
struct PAGE {
  
    LIST_ELEM list_elem;
    PAGE *next;
    POOL *pool;
    ULONG eyecatcher;
    USHORT num_free;                    // estimated, not accurate
};

  链表操作请看List.c文件,自实现的链表增删改查功能,这里主要是为页面bitmap初始化内存池。
  
  3) Driver_InitPublicSecurity
  
  代码初始化了Acl,申请128大小内存,初始化ACL,创建安全描述符,创建不同属性的安全描述符
Driver_PublicAcl = Mem_AllocEx(Driver_Pool, 128, TRUE);
    if (! Driver_PublicAcl)
        return FALSE;
  
RtlCreateAcl(Driver_PublicAcl, 128, ACL_REVISION);
    static UCHAR AuthSid[12] = {
        1,                                      // Revision
        1,                                      // SubAuthorityCount
        0,0,0,0,0,5, // SECURITY_NT_AUTHORITY   // IdentifierAuthority
        SECURITY_AUTHENTICATED_USER_RID         // SubAuthority
    };
    static UCHAR WorldSid[12] = {
        1,                                      // Revision
        1,                                      // SubAuthorityCount
        0,0,0,0,0,1, // SECURITY_WORLD_SID_AUTHORITY // IdentifierAuthority
        SECURITY_WORLD_RID                      // SubAuthority
    };

  保存路径注册键值:
    if (ok) {
        Driver_RegistryPath =
            Mem_AllocStringEx(Driver_Pool, RegistryPath->Buffer, TRUE);
        if (! Driver_RegistryPath)
            ok = FALSE;
    }

  注册表获取路径,ObReferenceObjectByHandle获取FILE_OBJECT对象,查询标准路径,DOS/NT之间转换(obj.c里面实现了具体转换路径过程,包含了不同版本)。
  4) Obj_Init()
  
  初始化ObQueryNameInfo/ObGetObjectType函数地址。
  

Sandboxie循序渐进

Sandboxie循序渐进
  
  5) Conf_Init()
  
  Mem_GetLockResource申请内存,初始化资源变量。Conf_Init_User初始化事件EvEnt,设置API函数如下:
Api_SetFunction(API_SET_USER_NAME,  Conf_Api_SetUserName);
Api_SetFunction(API_IS_BOX_ENABLED, Conf_Api_IsBoxEnabled);

  驱动初始化过程,还未安装SbieSvc服务,只是插入到对应的List中,等待SBieSvc启动调用。
  
  DriverCoDe:API_SET_USER_NAME Api必须由sandboxie调用,做判断是否sandboxiepid进程调用:
if (proc || (PsGetCurrentProcessId() != Api_ServiceProcessId)) {
  
        return STATUS_ACCESS_DENIED;
}
DriverCode:API_IS_BOX_ENABLED
Conf_Read()

  IO系列函数创建Sandboxie.ini,读取Templates.ini,Conf_Read_Sections()检测参数调用Conf_Read_Settings()读取文件填充结构。
DLL_Init()
    List_Init(&Dll_List);
    Dll_List_Initialized = TRUE;
  
    if (! Dll_Load(Dll_NTDLL))
        return FALSE;
    if (! Dll_Load(Dll_USER))
        return FALSE;
  
return TRUE;

  1. 初始化NtDll和User32,定义了全局的static LIST Dll_List,初始化的DLL将会被链表全局记录。Dll_Load判断是否已初始化返回指针,反之Map映射。
  
  ZwCreateFile(......,FILE_GENERIC_READ, \\SystemRoot\\System32\\xxx.dll,...);
  

Sandboxie循序渐进

Sandboxie循序渐进
  ZwQueryInformationFile --> ZwCreateSection --> ZwMapViewOfSection
  2. 获取DOS头和NT头,判断x64还是x32分别填充IMAGE_NT_HEADERS(32|64)结构体,保存DLL的Nt,入口点(IMageBase),大小(SizeOfImage),DataDirectory地址。Dll_RvaToAddr转换导出表地址,保存至dll->exports,插入DLL链表。
  

Sandboxie循序渐进

Sandboxie循序渐进
  
  6) Syscall_Init();
  
  全局链表
static LIST Syscall_List;
static SYSCALL_ENTRY **Syscall_Table = NULL;
static ULONG Syscall_MaxIndex = 0;
static UCHAR *Syscall_NtdllSavedCode = NULL;
static SYSCALL_ENTRY *Syscall_SetInformationThread = NULL;

Sandboxie循序渐进

Sandboxie循序渐进
  
  Syscall_Init_List()
  
  初始化全局List,Syscall_List.从全局DLL_List链表中获取NTDll.dll(Zw系列函数),通过Dll_GetNextProc来获取导出表的 IAT-IOD-INT,保存序号表索引和函数名。
ULONG *names = Dll_RvaToAddr(dll, dll->exports->AddressOfNames);
ULONG *addrs = Dll_RvaToAddr(dll, dll->exports->AddressOfFunctions);
USHORT *ordis = Dll_RvaToAddr(dll, dll->exports->AddressOfNameOrdinals);

Sandboxie循序渐进

Sandboxie循序渐进
  
  过滤了特殊的函数如下:
  

Sandboxie循序渐进

Sandboxie循序渐进
  
  分析每一个Zw导出找到索引,SySCall_GetKernelAddr(),关注点如何获取SSDT基地址,Sandboxie定位SSDT代码再Syscall_GetServiceTable()函数中:
            if (syscall_index != -1) {
                Syscall_GetKernelAddr(
                            syscall_index, &ntos_addr, ¶m_count);
            }

  如果有导出函数可以使用,最为方便导出KeServiceDscriptorTable即可。
  

Sandboxie循序渐进

Sandboxie循序渐进
  
  Sandboxie中Windows10寻找SSDT方式如下:
  
  1. 查询获取MODULE_INFO对象,获取kernel_base:
  

Sandboxie循序渐进

Sandboxie循序渐进
  2. 获取KeAddSystemServiceTable地址
  

Sandboxie循序渐进

Sandboxie循序渐进

Sandboxie循序渐进

Sandboxie循序渐进
  3. 最后插入Syscall_List链表
  
  Syscall_Init_Table()
  
  7) Driver_FindMissingServices()
  初始化session相关回调:
    Api_SetFunction(API_SESSION_LEADER,         Session_Api_Leader);
    Api_SetFunction(API_DISABLE_FORCE_PROCESS,  Session_Api_DisableForce);
    Api_SetFunction(API_MONITOR_CONTROL,        Session_Api_MonitorControl);
    Api_SetFunction(API_MONITOR_PUT,            Session_Api_MonitorPut);
    Api_SetFunction(API_MONITOR_PUT2,           Session_Api_MonitorPut2);
    Api_SetFunction(API_MONITOR_GET,            Session_Api_MonitorGet);

  8) Driver_FindMissingServices()
  
  获取ZwSetInformationTOken,FIND_SEVICE进行Hook,后面在对Hook进行分析:
  

Sandboxie循序渐进

Sandboxie循序渐进
  
  9) Process_Init()
  PsSetCreateProcessNotifyRoutine&PsSetLoadImageNotifyRoutine
  1. Process_NotifyProcess:
  
  PspCreateThread中处理Process,Sandboxi回调初始化LdrInitializeThunk注入。具体代码可以看:
  
  Process_NotifyImage:是在DbgKCreateThreaD中处理,负责Sandboxie空间初始化,有一个概念,它属于某一个沙盘内的进程。
if (!proc->bHostInject)    {
        if (ok)
            ok = File_CreateBoxPath(proc);
  
        if (ok)
            ok = Ipc_CreateBoxPath(proc);
  
        if (ok)
            ok = Key_MountHive(proc);
  
        //
        // initialize the filtering components
        //
  
        if (ok)
            ok = File_InitProcess(proc);
  
        if (ok)
            ok = Key_InitProcess(proc);
  
        if (ok)
            ok = Ipc_InitProcess(proc);
  
        if (ok)
            ok = Gui_InitProcess(proc);
  
        if (ok)
            ok = Process_Low_InitConsole(proc);
  
        if (ok)
            ok = Token_ReplacePrimary(proc);
  
        if (ok)
            ok = Thread_InitProcess(proc);
}

  File_CreateBoxPath()
  
  初始化路径:
        RtlInitUnicodeString(&objname, proc->box->file_path);
  
        status = ZwCreateFile(
            &handle,
            FILE_GENERIC_READ | FILE_WRITE_ATTRIBUTES,
            &objattrs,
            &IoStatusBlock,
            NULL,                   // AllocationSize
            0,                      // FileAttributes
            FILE_SHARE_VALID_FLAGS, // ShareAccess
            FILE_OPEN_IF,           // CreateDisposition
            FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
            NULL, 0);               // EaBuffer, EaLength
Ipc_CreateBoxPath(proc)

  循环创建对象:
    while (retries < 64) {
  
        ++retries;
        RtlInitUnicodeString(&objname, proc->box->ipc_path);
  
        status = ZwCreateDirectoryObject(
            &handle, DIRECTORY_ALL_ACCESS, &objattrs);
  
        if (status == STATUS_OBJECT_PATH_NOT_FOUND) {
Key_Init()
initialize the filtering components初始化过滤接口
File_InitProcess()
10) Thread_Init
11) File_Init
12) Key_Init
13) Ipc_Init
14) Gui_Init
15) Api_Init
16) Dll_Unload

Sandboxie循序渐进

Sandboxie循序渐进



温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
论坛交流群:672619046
微信扫一扫即可使用手机论坛
快速回复 返回顶部 返回列表