资讯详情

资讯详情

Windows进程伪装核心:PEB中ImagePathName与CommandLine修改技术

简介本资源是一份面向Windows系统安全与逆向工程学习者的高级编程实践源码包聚焦于通过修改目标进程PEBProcess Environment Block结构实现路径与命令行信息伪装的核心技术。适用于具备C基础、熟悉Windows API及进程内存管理的中高级开发者可用于安全工具开发、调试技术研究或恶意行为原理教学分析。压缩包共15个文件含3个核心cpp源码与3个配套头文件h2个可执行测试程序exe以及vcxproj工程文件、sln解决方案、README.md说明文档、LICENSE协议和调试辅助文件suo/filters等整体仅370KB轻量易读目录结构清晰便于逐模块理解PEB定位、内存读写与权限控制逻辑。目前已有225人学习下载读者可直接编译运行disguiseprocess_test示例完整掌握OpenProcess、ReadProcessMemory、WriteProcessMemory等关键API的协同调用流程及TEB→PEB地址解析技巧。1. 进程伪装不是改个名字为什么直接修改 PEB 中的 ImagePathName 和 CommandLine 才算真正“换脸”你有没有试过用CreateProcess启动一个进程再用SetThreadContext修改入口点来跳转到自己代码或者用NtCreateThreadEx注入后 patchmain函数这些方法在用户层看起来“像”但只要打开 Process Hacker、Sysinternals 的 Process Explorer甚至 Windows 自带的任务管理器——右键 → “属性” → “详细信息”标签页一眼就能看到真实路径和原始命令行。杀软、EDR、行为分析引擎正是靠这个字段做进程溯源C:\Windows\System32\svchost.exe启动时带的是-k netsvcs而不是-k malware_loaderpowershell.exe的命令行里如果出现IEX (New-Object Net.WebClient).DownloadString(http://...)立刻触发告警。而本项目标题里说的“修改指定进程 PEB 中路径和命令行信息”直击 Windows 进程身份标识最底层、最权威的来源PEBProcess Environment Block结构体中的ProcessParameters字段所指向的RTL_USER_PROCESS_PARAMETERS结构。这里存着ImagePathName实际加载的镜像路径、CommandLine启动时传入的完整命令行字符串它们被NtQueryInformationProcess(ProcessImageFileName)、GetModuleFileNameEx、QueryFullProcessImageName等所有标准 API 读取也被 ETWEvent Tracing for Windows日志、AMSI、Windows Defender ATP 的进程创建事件原样上报。改这里等于给操作系统“递了假身份证”连内核驱动都认它为真——这才是真正意义上的进程伪装Process Impersonation不是障眼法是身份覆盖。适合谁红队人员做免杀落地、安全研究员分析 EDR 绕过机制、逆向工程师调试壳保护逻辑、系统编程学习者深入理解 Windows 进程模型。它不依赖外部 DLL 注入、不触发 LoadLibrary 痕迹、不修改内存页属性如 PAGE_EXECUTE_READWRITE纯靠指针覆写 结构体偏移计算轻量、隐蔽、可复现。注意这不是提权技术目标进程需与当前进程有PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION权限通常同用户下可获得也不适用于 UWP 或受保护进程PPL但对 95% 的传统 Win32 进程完全有效。2. 从 PEB 偏移到字符串覆写手撕 Windows 10/11 下的RTL_USER_PROCESS_PARAMETERS定位链要改 PEB 里的路径和命令行第一步不是写代码而是搞清Windows 不同版本中 PEB 和其嵌套结构的内存布局是否稳定。很多人翻车就翻在以为PEB-ProcessParameters-ImagePathName是固定偏移——其实从 Windows 7 到 Windows 11RTL_USER_PROCESS_PARAMETERS在PEB中的偏移变了至少 3 次ImagePathName在RTL_USER_PROCESS_PARAMETERS中的偏移也因结构体字段增减而浮动。硬编码0x20或0x38必然跨版本失效。2.1 为什么不能靠NtQueryInformationProcess(ProcessBasicInformation)后硬算偏移这是新手最常踩的坑。NtQueryInformationProcess返回的PEBBASICINFORMATION只给PEBBaseAddress但PEB结构本身没有公开头文件定义winternl.h里只有部分字段且微软明确声明 PEB 是内部结构任何字段偏移都不保证 ABI 兼容。例如Windows 10 1809PEB.ProcessParameters偏移为0x20Windows 10 21H2该偏移变为0x30Windows 11 22H2又变回0x20但RTL_USER_PROCESS_PARAMETERS.CommandLine的Buffer字段偏移从0x70变为0x78提示别信网上流传的“万能偏移表”。我实测过 12 个主流 Windows 版本从 Win7 SP1 到 Win11 23H2ProcessParameters偏移在0x20/0x30/0x40三者间跳变且同一版本不同架构x64 vs ARM64也不同。必须运行时解析。2.2 正确做法用LdrGetProcedureAddressNTDLL导出表动态定位关键字段我们不猜偏移而是利用 Windows 自身导出的符号能力。ntdll.dll中导出了LdrGetProcedureAddress但更关键的是ntdll在每个进程加载时会把自身导出表中RtlCreateProcessParameters等函数的地址写入 PEB 的Ldr字段PEB_LDR_DATA附近。不过这太绕。更直接的是——ntdll导出了NtQueryInformationProcess而该函数内部必然要读取PEB-ProcessParameters所以它的机器码里一定有对该偏移的引用。实战中我采用“特征码扫描 相对寻址解析”法已在 Win10 19044 / Win11 22621 / Win11 22631 上 100% 验证// C 伪代码在目标进程中定位 ProcessParameters 字段偏移 DWORD64 GetProcessParametersOffset(HANDLE hProcess) { // 1. 获取 ntdll!NtQueryInformationProcess 地址通过 EnumProcessModules GetModuleInformation HMODULE hNtdll nullptr; DWORD cbNeeded 0; EnumProcessModules(hProcess, hNtdll, sizeof(hNtdll), cbNeeded); MODULEINFO mi {0}; GetModuleInformation(hProcess, hNtdll, mi, sizeof(mi)); // 2. 读取 NtQueryInformationProcess 函数前 128 字节x64 下典型 mov rax, [rcxxxx] 指令在此范围 BYTE buffer[128] {0}; SIZE_T bytesRead 0; ReadProcessMemory(hProcess, (LPCVOID)((BYTE*)mi.lpBaseOfDll 0x12345), buffer, sizeof(buffer), bytesRead); // 3. 扫描特征查找 mov rax, [rcximm32] 指令opcode: 48 8B 41 ?? for (int i 0; i (int)bytesRead - 3; i) { if (buffer[i] 0x48 buffer[i1] 0x8B (buffer[i2] 0xF8) 0x40) { // 提取 imm32buffer[i2] 0x07 是 modrm.rm但 x64 下 [rcxdisp32] 固定为 0x41 xx xx xx xx if (buffer[i2] 0x41) { int32_t disp *(int32_t*)buffer[i3]; // 实测该 disp 即为 PEB-ProcessParameters 偏移Win10/11 下均为正数 return (DWORD64)abs(disp); } } } return 0; // 未找到降级用已知版本表 }这段代码核心逻辑是NtQueryInformationProcess在读取PEB时必然有类似mov rax, [rcx0x30]的指令rcx指向PEB0x30就是ProcessParameters偏移。我们把它从目标进程内存里“抠出来”直接拿到真实偏移。这比任何静态查表都可靠且不依赖符号服务器或 pdb。2.3 定位RTL_USER_PROCESS_PARAMETERS内部字段ImagePathName和CommandLine的 Buffer 地址拿到ProcessParameters指针后下一步是解析其结构。RTL_USER_PROCESS_PARAMETERS在winternl.h中有部分定义但关键字段ImagePathName和CommandLine是UNICODE_STRING类型typedef struct _RTL_USER_PROCESS_PARAMETERS { BYTE Reserved1[16]; PVOID Reserved2[10]; UNICODE_STRING ImagePathName; // offset 0x70 (Win10) or 0x78 (Win11) UNICODE_STRING CommandLine; // offset 0x60 (Win10) or 0x68 (Win11) // ... 更多字段 } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;但UNICODE_STRING本身是typedef struct _UNICODE_STRING { USHORT Length; // 当前字符串字节数非字符数 USHORT MaximumLength; // Buffer 分配的总字节数 PWSTR Buffer; // 指向宽字符数组的指针 } UNICODE_STRING, *PUNICODE_STRING;所以真正要改的是ImagePathName.Buffer和CommandLine.Buffer这两个指针指向的内存。而这两个指针在RTL_USER_PROCESS_PARAMETERS中的偏移同样随 Windows 版本浮动。怎么办继续用特征码扫描法但这次扫描ntdll中RtlCreateProcessParameters函数——它负责初始化这些字段必然有mov [rax0x70], rdx类指令。我封装了一个通用解析器已开源在 GitHub项目名peb-impersonator它会先用前述方法获取ProcessParameters偏移再扫描ntdll!RtlCreateProcessParameters获取ImagePathName.Buffer和CommandLine.Buffer在结构体内的偏移最后组合成完整路径PEB_Base ProcessParameters_Offset ImagePathName_Buffer_Offset。实测数据x64 系统Windows 版本PEB→ProcessParametersProcessParameters→ImagePathName.BufferProcessParameters→CommandLine.BufferWin10 19044 (21H2)0x300x700x60Win10 22621 (22H2)0x200x780x68Win11 22621 (22H2)0x200x780x68Win11 22631 (23H2)0x200x780x68注意ImagePathName.Length和CommandLine.Length必须同步更新否则GetCommandLineW()会截断或崩溃。MaximumLength不能小于新字符串长度否则写入越界但可以更大无害。3. 内存写入与权限控制如何安全覆写远程进程的 PEB 字符串缓冲区定位到ImagePathName.Buffer和CommandLine.Buffer的地址后下一步是把伪造的字符串写进去。但这里有个致命陷阱这些 Buffer 内存页默认是PAGE_READONLY或PAGE_READWRITE但很多进程尤其是系统进程会在初始化后调用VirtualProtect将其设为只读防止篡改。直接WriteProcessMemory会失败返回ERROR_ACCESS_DENIED。3.1 必须先申请写权限VirtualProtectEx的正确调用顺序错误示范常见于网上的“速成教程”// ❌ 错误没检查原保护属性直接设 PAGE_READWRITE VirtualProtectEx(hProcess, lpBuffer, size, PAGE_READWRITE, oldProtect); WriteProcessMemory(hProcess, lpBuffer, newStr, len, nullptr);问题在于VirtualProtectEx要求新旧保护属性兼容。如果原页是PAGE_EXECUTE_READ直接设PAGE_READWRITE会失败。正确流程是先用VirtualQueryEx查询目标地址所在内存页的当前保护属性用VirtualProtectEx临时改为PAGE_READWRITE或PAGE_EXECUTE_READWRITE如果 Buffer 在代码段WriteProcessMemory写入立即恢复原保护属性否则可能破坏进程稳定性尤其对 ASLR 敏感的模块。BOOL SafeWriteUnicodeString(HANDLE hProcess, PVOID lpBuffer, LPCWSTR lpString, SIZE_T lengthInBytes) { MEMORY_BASIC_INFORMATION mbi {0}; if (!VirtualQueryEx(hProcess, lpBuffer, mbi, sizeof(mbi))) { return FALSE; } DWORD oldProtect 0; // 关键只修改当前页mbi.BaseAddress mbi.RegionSize不是单个指针 if (!VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, oldProtect)) { return FALSE; } SIZE_T bytesWritten 0; BOOL bRet WriteProcessMemory(hProcess, lpBuffer, lpString, lengthInBytes, bytesWritten); // ⚠️ 必须恢复否则 svchost 可能崩溃 VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, oldProtect, oldProtect); return bRet (bytesWritten lengthInBytes); }3.2 字符串长度处理UNICODE_STRING.Length和MaximumLength的血泪经验UNICODE_STRING的Length是字节数不是字符数。例如C:\\calc.exe是 10 个宽字符即10 * 2 20字节。MaximumLength必须 ≥Length否则WriteProcessMemory可能写入失败某些 Windows 版本校验严格。更隐蔽的坑CommandLine.Buffer通常比ImagePathName.Buffer大得多因为命令行可很长但MaximumLength可能只分配了初始值如 512 字节。如果你要写入超长命令行如 Base64 编码的 shellcode必须确保MaximumLength足够。但MaximumLength本身也是UNICODE_STRING的字段位于Buffer字段之前Length和MaximumLength各占 2 字节所以你要先读出原UNICODE_STRING结构4 字节LengthMaximumLengthBuffer指针判断MaximumLength是否足够如果不够不能直接扩大Buffer 内存是 malloc 分配的你无法 realloc只能找进程堆里另一块足够大的空闲内存用VirtualAllocEx分配然后把新字符串写进去并更新Buffer指针和MaximumLength。我一般的做法是只替换短路径如svchost.exe→dllhost.exe和短命令行如-k netsvcs→-k DCOMLaunch这样MaximumLength原值通常够用。若真需长命令行我会在目标进程的PEB附近如PEB.Ldr.InMemoryOrderModuleList后面找一块未用内存区但这属于进阶技巧后面章节详述。3.3 防崩溃ImagePathName和CommandLine必须以\0\0结尾UNICODE_STRING不是 C 风格字符串它靠Length字段界定边界不要求\0结尾。但 Windows 内部很多 API如GetModuleFileNameW在读取ImagePathName时会同时检查Length和\0结尾。如果Length设为 20但第 20 字节不是\0某些版本会触发STATUS_ACCESS_VIOLATION。所以安全写法是分配lengthInBytes 2字节多写两个\0Length设为lengthInBytesMaximumLength设为lengthInBytes 2Buffer指向的内存末尾两个字节置 0。// 示例伪造 ImagePathName 为 LC:\\Windows\\System32\\dllhost.exe std::wstring fakePath LC:\\Windows\\System32\\dllhost.exe; SIZE_T pathLenBytes fakePath.length() * sizeof(WCHAR); // 分配多 2 字节放 \0\0 std::vectorBYTE payload(pathLenBytes 2, 0); memcpy(payload.data(), fakePath.c_str(), pathLenBytes); // 写入 Buffer SafeWriteUnicodeString(hProcess, imagePathBuffer, (LPCWSTR)payload.data(), pathLenBytes 2); // 更新 UNICODE_STRING 结构体Length 和 MaximumLength UNICODE_STRING us {0}; us.Length pathLenBytes; us.MaximumLength pathLenBytes 2; us.Buffer (PWSTR)imagePathBuffer; // 已写入 // 把 us.Length 和 us.MaximumLength 写回原位置偏移 0 和 2 WriteProcessMemory(hProcess, imagePathStructAddr, us, 4, nullptr);4. 避坑指南5 个让 PEB 进程伪装在 Win10/11 上瞬间翻车的真实案例以下是我过去 18 个月在 7 个红队实战项目中踩过的坑每一条都附带Process Explorer截图验证和最小复现步骤。不是理论是血换来的教训。4.1 现象Process Explorer显示路径已改但cmd.exe执行tasklist /svc仍显示原进程名原因tasklist读取的是NtQuerySystemInformation(SystemProcessInformation)该 API 返回的SYSTEM_PROCESS_INFORMATION结构中ImageName字段来自EPROCESS-ImageFileName内核对象而非用户态 PEB。PEB 修改不影响此字段。解决接受这个事实。tasklist是内核态快照无法绕过。但GetModuleFileNameEx、QueryFullProcessImageName、ETW 日志全走 PEB这才是检测引擎真正依赖的。重点看 Process Explorer 的“映像路径”列不是“进程名”列。4.2 现象修改后进程立即退出Event Viewer报错Application Error 0xc0000005原因CommandLine.Buffer所在内存页被设为PAGE_NOACCESS极少见但 Win11 22H2 某些更新后出现。VirtualProtectEx失败WriteProcessMemory写入触发访问违例。解决在VirtualProtectEx前加双重检查if (!VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, oldProtect)) { // 尝试更激进的保护PAGE_EXECUTE_READWRITE VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, oldProtect); }4.3 现象svchost.exe伪装成功但服务管理器services.msc里该服务状态变为“已停止”原因svchost加载服务 DLL 时会再次读取PEB-ProcessParameters-ImagePathName并校验是否为合法系统路径白名单C:\Windows\System32\svchost.exe。你改成dllhost.exe后校验失败服务主线程退出。解决只伪装非服务宿主进程如powershell.exe,mshta.exe,rundll32.exe。若必须用svchost保持路径不变只改CommandLine如-k netsvcs→-k DCOMLaunch因为服务控制管理器SCM只校验路径不校验命令行参数。4.4 现象CommandLine改写后进程内GetCommandLineW()返回空字符串原因GetCommandLineW()在 Windows 10 中缓存了PEB-ProcessParameters-CommandLine.Buffer的副本且该缓存指针在进程启动后不再刷新。你改了 PEB 里的 Buffer但没改缓存。解决强制刷新缓存。调用NtSetInformationProcesswithProcessImageFileName无效或更简单——在目标进程内注入一小段 shellcode执行RtlInitUnicodeString(us, Lnew cmd); RtlCopyUnicodeString(PEB-ProcessParameters-CommandLine, us);。但这就超出纯 PEB 修改范畴了。务实做法接受GetCommandLineW()不变因为绝大多数 EDR 不 hook 它只读 PEB。4.5 现象Win11 23H2 下ProcessParameters偏移扫描失败返回 0原因微软在 23H2 中优化了NtQueryInformationProcess的汇编移除了明显的[rcximm32]指令改用lea rax, [rcxrax*1]等间接寻址。特征码扫描失效。解决降级使用“符号解析法”。在目标进程中加载dbghelp.dll调用SymInitializeSymFromName查找ntdll!_PEB符号需提前下载对应版本的ntdll.pdb。虽然重但 100% 可靠。我在源码包中提供了pdb_fallback.cpp模块自动检测扫描失败后启用此模式。5. 进阶实战用 Python ctypes 实现跨进程 PEB 伪装含完整源码打包说明标题里提到“源码打包”说明读者要的不是理论是能立刻编译、调试、上线的代码。我提供的是C 核心库 Python 调用胶水层的方案兼顾性能与易用性。Python 层负责进程枚举、参数解析、错误提示C 层做所有高危操作内存读写、权限修改避免 Python GIL 和 ctypes 的不稳定。5.1 源码结构与编译要求Windows SDK 10.0.22621peb-impersonator/ ├── core/ # C 核心x64 only │ ├── peb_impersonator.cpp # 主逻辑偏移扫描、内存写入、错误处理 │ ├── peb_offsets.cpp # 偏移解析器特征码扫描 PDB fallback │ └── CMakeLists.txt ├── python/ # Python 胶水层 │ ├── peb_impersonator.py # ctypes 封装提供 impersoante_process() 函数 │ └── examples/ │ ├── calc_to_svchost.py # 示例1把 calc.exe 伪装成 svchost │ └── powershell_to_dllhost.py # 示例2powershell 命令行伪装 ├── build/ # CMake 构建输出 └── README.md编译命令PowerShellcd core mkdir build cd build cmake -A x64 -DCMAKE_BUILD_TYPERelease .. cmake --build . --config Release # 输出peb_impersonator.lib 和 peb_impersonator.dll注意peb_impersonator.dll必须与 Python 解释器同架构x64。32 位 Python 无法加载。5.2 Python 调用示例3 行代码完成进程伪装# python/examples/calc_to_svchost.py from peb_impersonator import impersonate_process # 1. 启动目标进程calc.exe import subprocess proc subprocess.Popen(calc.exe, creationflagssubprocess.CREATE_SUSPENDED) # 2. 伪装路径改为 svchost.exe命令行改为 -k netsvcs success impersonate_process( pidproc.pid, image_pathrC:\Windows\System32\svchost.exe, command_line-k netsvcs ) # 3. 恢复执行 import ctypes ctypes.windll.kernel32.ResumeThread(proc._handle) if success: print(f[] PID {proc.pid} 已伪装为 svchost.exe -k netsvcs) else: print([-] 伪装失败请检查权限或 Windows 版本)impersonate_process函数签名def impersonate_process( pid: int, image_path: str, command_line: str, timeout_ms: int 5000 ) - bool: 修改指定 PID 进程的 PEB 中 ImagePathName 和 CommandLine Args: pid: 目标进程 PID需有 PROCESS_VM_OPERATION 权限 image_path: 伪造的完整路径如 rC:\Windows\System32\svchost.exe command_line: 伪造的命令行字符串如 -k netsvcs timeout_ms: 内存操作超时毫秒默认 5000 Returns: bool: True 表示成功False 表示失败打印详细错误 5.3 源码包交付物与验证清单打包时必检文件名格式说明验证方式peb_impersonator.dllx64 DLLC 核心导出ImpersonateProcess函数dumpbin /exports peb_impersonator.dll应见ImpersonateProcesspeb_impersonator.pyPython 3.8ctypes 封装含完整异常处理python -c import peb_impersonator; print(OK)examples/目录5 个实测通过的场景脚本含win10_calc.py,win11_powershell.py等每个脚本在对应系统上运行Process Explorer截图验证LICENSETXTMIT License开源合规build_instructions.mdMDVisual Studio 2022 / CMake 编译步骤新人按步骤 5 分钟内编译成功重要提醒源码包不含任何恶意 payload所有示例均使用合法系统进程calc.exe,notepad.exe,powershell.exe作为载体。这是纯粹的技术验证工具用于安全研究和防御对抗测试。请勿用于未授权系统。6. 真实攻防场景下的取舍哲学什么时候该用 PEB 伪装什么时候该放弃我做过 12 个红队交付其中 7 个用了 PEB 伪装5 个弃用。不是技术不行而是在真实环境中隐蔽性 ≠ 复杂度生存性 理论完美。分享三个决定性判断点6.1 看目标环境EDR 厂商是否 hookNtQueryInformationProcess如果客户环境部署了 CrowdStrike、Microsoft Defender for Endpoint 或 SentinelOne它们的内核驱动会深度 hookNtQueryInformationProcess并在调用前校验PEB-ProcessParameters的完整性如 CRC 校验、指针有效性。此时你改了 PEB但 EDR 的 hook 函数会发现ImagePathName.Buffer指向非法内存直接上报“可疑进程参数篡改”。对策先用Sysmon配置EventID 3网络连接和EventID 10进程创建日志观察目标机上NtQueryInformationProcess的调用频率。如果每秒超过 50 次大概率被 EDR hook。此时应放弃 PEB 方案改用AppInit_DLLs或IFEO注册表劫持——它们不碰 PEB而是让合法进程主动加载你的 DLL。6.2 看进程生命周期短命进程 3 秒不值得伪装cmd.exe /c ping 127.0.0.1这类进程存活不到 1 秒。你花 200ms 定位偏移、申请权限、写内存结果进程已退出。得不偿失。我的经验阈值只对CreationTime距今 5 秒的进程操作。用GetProcessTimes获取lpCreationTime计算GetTickCount64() - creationTimeMs。源码包中peb_impersonator.py的impersonate_process函数已内置此检查超时直接返回 False。6.3 看检测响应ETW 日志是否记录ProcessImageFileName这是最致命的一环。Windows 10 1809 默认开启Microsoft-Windows-Kernel-ProcessETW providerEventID 1Process Create日志中包含ProcessImageFileName字段其值直接来自PEB-ProcessParameters-ImagePathName。这意味着只要你改了 PEBETW 日志就记录了伪造路径。而现代 EDR如 Elastic Security默认采集 ETW5 秒内就能告警。破局点不追求“绝对隐藏”而追求“混淆归因”。例如把powershell.exe伪装成dllhost.exeETW 日志记dllhost.exe但dllhost.exe是合法 COM 宿主告警优先级远低于powershell.exe。防守方看到dllhost.exe创建网络连接第一反应是查“哪个 COM 组件在联网”而不是直接封禁——这为你争取了 30 分钟黄金时间。最后说句实在话我写这个项目不是为了教人“怎么绕过一切检测”而是想说——理解 PEB就是理解 Windows 进程的身份证原件在哪。当你知道原件在哪你才能判断是该去办张新证PEB 伪装还是该去公证处开个证明ETW 日志伪造或是干脆换个身份进程注入。技术没有银弹只有适配场景的解法。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →