首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
3
23
[原创] Rust如何实现跨进程的内存浏览器
发表于: 2025-5-17 10:28
6230
[原创] Rust如何实现跨进程的内存浏览器
Hrlies
2025-5-17 10:28
6230
# Rust红队开发从浅到深的学习和实现(1),完整教程请进入个人公众号查看 # 个人安全公众号:从生活到技术上的安全 ### 一、架构设计核心思想 ``` // 使用 trait 进行平台抽象 pub trait ProcessHandle { fn read_memory(&self, address: usize, buffer: &mut [u8]) -> Result<()>; // 其他方法... } // 运行时动态分派 pub struct Process { handle: Box<dyn ProcessHandle>, // 动态分发实现 } ``` **关键点**: 1. 使用 `cfg`条件编译区分平台 2. 通过 trait object 实现运行时多态 3. 统一接口层封装平台差异 - - - ### 二、Windows 实现细节剖析 #### 1\. 进程打开机制 ``` // windows.rs use windows::Win32::System::Threading::OpenProcess; impl WindowsProcess { pub fn open(pid: u32) -> Result<Self> { unsafe { let handle = OpenProcess( PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, false, // 不继承句柄 pid, )?; Ok(Self { handle }) } } } ``` **技术细节**: * `PROCESS_VM_READ`:允许读取目标进程内存 * `PROCESS_QUERY_INFORMATION`:获取进程信息权限 * 必须使用 `unsafe`块调用 Win32 API #### 2\. 内存读取实现 ``` unsafe fn read_memory(&self, address: usize, buffer: &mut [u8]) -> Result<()> { let mut bytes_read = 0; let success = ReadProcessMemory( self.handle, address as *const _, buffer.as_mut_ptr() as *mut _, buffer.len(), &mut bytes_read, ); if success.as_bool() && bytes_read == buffer.len() { Ok(()) } else { Err(anyhow!("Failed to read memory. Error: {}", GetLastError())) } } ``` **注意事项**: * 处理 `ERROR_PARTIAL_COPY`错误(可能读取到部分数据) * 地址需要对齐(通常 4KB 边界) * 需检查返回的已读字节数 - - - ### 三、Linux/macOS 实现关键点 #### 1\. 通过 procfs 访问内存 ``` // unix.rs impl UnixProcess { fn read_memory(&self, address: usize, buffer: &mut [u8]) -> Result<()> { let path = format!("/proc/{}/mem", self.pid); let file = File::open(path)?; // 使用 pread 原子操作 file.read_at(buffer, address as u64)?; Ok(()) } } ``` **技术细节**: * `/proc/[pid]/mem`需要 `ptrace`权限 * 需要 root 权限或设置 `ptrace_scope` * `read_at`使用 `pread`系统调用实现原子操作 #### 2\. 解析内存映射信息 ``` fn parse_maps(pid: i32) -> Result<Vec<MemoryRegion>> { let maps = fs::read_to_string(format!("/proc/{}/maps", pid))?; maps.lines().filter_map(|line| { let parts: Vec<&str> = line.splitn(6, ' ').collect(); let (addr, perms) = (parts[0], parts[1]); let addrs: Vec<&str> = addr.split('-').collect(); let start = usize::from_str_radix(addrs[0], 16).ok()?; let end = usize::from_str_radix(addrs[1], 16).ok()?; Some(MemoryRegion { start, end, permissions: perms.to_string(), path: parts.get(5).unwrap_or(&"").to_string(), }) }).collect() } ``` **关键字段**: * 地址范围:`00400000-00401000` * 权限标志:`r-xp`(读/执行/私有) * 映射文件路径:`/usr/lib/libc.so.6` - - - ### 四、跨平台差异处理策略 #### 1\. 内存对齐问题 ``` // 通用对齐检查 fn is_page_aligned(addr: usize) -> bool { let page_size = page_size::get(); addr % page_size == 0 } // 统一处理函数 pub fn safe_read(process: &dyn ProcessHandle, addr: usize, buf: &mut [u8]) -> Result<()> { if !is_page_aligned(addr) || !is_page_aligned(addr + buf.len()) { return Err(anyhow!("Unaligned memory access")); } process.read_memory(addr, buf) } ``` #### 2\. 权限管理抽象 ``` // 统一权限表示 #[derive(Debug, Clone, Copy)] pub enum MemoryProtection { Read, Write, Execute, // ... } // 转换为平台特定值 impl Into<windows::Win32::System::Memory::PAGE_PROTECTION_FLAGS> for MemoryProtection { fn into(self) -> PAGE_PROTECTION_FLAGS { match self { Self::Read => PAGE_READONLY, // ... } } } ``` - - - ### 五、高级功能实现示例 #### 1\. 内存模式搜索 ``` impl Process { pub fn pattern_scan(&self, pattern: &[Option<u8>]) -> Result<Vec<usize>> { let regions = self.get_memory_regions()?; let mut results = Vec::new(); for region in regions { if !region.is_readable() { continue; } let mut buffer = vec![0; region.size()]; self.read_memory(region.start, &mut buffer)?; for (offset, window) in buffer.windows(pattern.len()).enumerate() { if pattern.iter().enumerate().all(|(i, &expected)| { expected.map(|b| b == window[i]).unwrap_or(true) }) { results.push(region.start + offset); } } } Ok(results) } } ``` #### 2\. 内存修改监控 ``` // Windows 实现 fn monitor_memory_writes(process: &WindowsProcess) -> Result<()> { unsafe { let old_protect = std::mem::zeroed(); VirtualProtectEx( process.handle, address as _, size, PAGE_GUARD, // 设置内存保护 &mut old_protect, )?; } // 通过异常处理捕获写入事件 } // Linux 实现 fn monitor_memory_writes(pid: Pid) -> Result<()> { let regs = ptrace::getregs(pid)?; ptrace::setregs(pid, regs)?; // 设置断点 // 使用 SIGTRAP 处理写入事件 } ``` - - - ### 六、调试技巧与常见问题 #### 1\. 权限问题诊断 ``` // 统一错误处理 match process.read_memory(addr, &mut buf) { Err(e) if e.kind() == ErrorKind::PermissionDenied => { eprintln!("需要管理员权限运行!"); } // ... } ``` #### 2\. 地址空间差异处理 ``` // 处理 32/64 位差异 #[cfg(target_pointer_width = "32")] type AddressType = u32; #[cfg(target_pointer_width = "64")] type AddressType = u64; ``` #### 3\. 性能优化策略 ``` // 批量读取优化 const CHUNK_SIZE: usize = 4096 * 1024; // 4MB fn read_large_memory(process: &dyn ProcessHandle, addr: usize, size: usize) -> Result<Vec<u8>> { let mut buffer = Vec::with_capacity(size); unsafe { buffer.set_len(size); } for chunk in buffer.chunks_mut(CHUNK_SIZE) { process.read_memory(addr + offset, chunk)?; } Ok(buffer) } ``` - - - ### 七、跨平台测试策略 #### 1\. 模拟测试框架 ``` #[cfg(test)] mod tests { #[test] #[cfg(target_os = "linux")] fn test_linux_memory_read() { // 测试当前进程的内存读取 } #[test] #[cfg(windows)] fn test_windows_handle_acquisition() { // 测试进程打开权限 } } ``` #### 2\. 集成测试方案 ``` // 启动测试进程 fn spawn_test_process() -> (u32, JoinHandle<()>) { let handle = thread::spawn(|| { let data = vec![0xAAu8; 1024]; loop { thread::park(); } // 保持进程存活 }); (thread::current().id(), handle) } ``` - - - ### 总结:关键设计模式 1. **平台抽象层**:通过 trait 隔离平台实现 2. **统一错误处理**:使用 `anyhow`整合系统错误 3. **条件编译**:`#[cfg(target_os = "windows")]`处理平台差异 4. **内存安全边界**:严格校验地址合法性 5. **性能分级**:小数据直接读取 vs 大内存分块处理 实际开发中需要特别注意: * 不同平台的内存对齐要求 * 权限模型的差异(Windows ACL vs Linux capabilities) * 地址空间布局差异(32/64 位系统) * 信号/异常处理机制的不同实现 可以通过添加更多中间抽象层(如统一的内存保护标志、跨平台的地址类型封装)来增强代码的可维护性。同时建议结合 `libc`crate 处理底层系统调用,使用 `bitflags`处理复杂的标志位组合。 #### 当然,之前只是简单的讲述,想要完整实现还需要关注不同平台之间的问题,以及更好的性能 # 完整实现补充 以下针对实际开发中需要特别注意的问题和解决方案,通过代码案例进行补充说明: --- ### 一、内存区域缓存优化 **问题**:频繁解析 `/proc/pid/maps` 或调用 `VirtualQueryEx` 会产生性能瓶颈 ```rust // process.rs use std::time::{Duration, Instant}; pub struct Process { pid: u32, handle: Box<dyn ProcessHandle>, regions_cache: Option<(Vec<MemoryRegion>, Instant)>, } impl Process { const CACHE_TTL: Duration = Duration::from_secs(5); pub fn get_memory_regions_cached(&mut self) -> Result<&Vec<MemoryRegion>> { if let Some((regions, timestamp)) = &self.regions_cache { if timestamp.elapsed() < Self::CACHE_TTL { return Ok(regions); } } let new_regions = self.handle.get_memory_regions()?; self.regions_cache = Some((new_regions, Instant::now())); Ok(&self.regions_cache.as_ref().unwrap().0) } } ``` **优化点**: - 使用 `Instant` 记录缓存时间戳 - 可配置的 TTL(5秒过期) - 避免重复解析内存映射信息 --- ### 二、跨平台错误统一处理 **问题**:不同平台返回的错误类型需要统一处理 ```rust // error.rs #[derive(thiserror::Error, Debug)] pub enum MemoryError { #[error("Permission denied: {0}")] PermissionDenied(String), #[error("Invalid address: 0x{0:x}")] InvalidAddress(usize), #[error("Partial read: {0} bytes")] PartialRead(usize), #[error("Platform error: {0}")] Platform(#[from] PlatformError), } // windows.rs impl From<windows::core::Error> for MemoryError { fn from(e: windows::core::Error) -> Self { match e.code().0 as u32 { windows::Win32::Foundation::ERROR_PARTIAL_COPY => Self::PartialRead(0), windows::Win32::Foundation::ERROR_ACCESS_DENIED => Self::PermissionDenied("PROCESS_VM_READ required".into()), _ => Self::Platform(PlatformError::Windows(e)) } } } // unix.rs impl From<nix::Error> for MemoryError { fn from(e: nix::Error) -> Self { match e { nix::Error::EPERM => Self::PermissionDenied("ptrace access required".into()), nix::Error::EIO => Self::InvalidAddress(0), _ => Self::Platform(PlatformError::Unix(e)) } } } ``` **优势**: - 统一错误类型方便上层处理 - 保留原始错误信息同时提供友好提示 - 自动转换平台特定错误码 --- ### 三、ASLR 地址重定位处理 **问题**:需要动态计算模块基址偏移 ```rust // module.rs pub struct ModuleInfo { pub base_address: usize, pub size: usize, pub name: String, } impl Process { pub fn find_module(&self, name: &str) -> Result<ModuleInfo> { let regions = self.get_memory_regions()?; regions.iter() .filter(|r| !r.path.is_empty()) .find(|r| r.path.ends_with(name)) .map(|r| ModuleInfo { base_address: r.start, size: r.end - r.start, name: r.path.clone(), }) .ok_or_else(|| anyhow!("Module not found")) } pub fn rebase_address(&self, offset: usize, module_name: &str) -> Result<usize> { let module = self.find_module(module_name)?; Ok(module.base_address + offset) } } // 使用示例: let process = Process::open(pid)?; let real_address = process.rebase_address(0x1234, "target.dll")?; ``` **关键点**: - 通过内存区域路径识别模块 - 动态计算运行时实际地址 - 支持偏移量+模块名的寻址方式 --- ### 四、跨平台内存保护管理 **问题**:统一不同平台的内存权限操作 ```rust // protection.rs use bitflags::bitflags; bitflags! { pub struct MemoryProtection: u32 { const READ = 0b0001; const WRITE = 0b0010; const EXECUTE = 0b0100; const COPY = 0b1000; // 写时复制 } } #[cfg(windows)] impl Into<windows::Win32::System::Memory::PAGE_PROTECTION_FLAGS> for MemoryProtection { fn into(self) -> PAGE_PROTECTION_FLAGS { let mut flags = PAGE_PROTECTION_FLAGS(0); if self.contains(Self::EXECUTE) { flags |= PAGE_EXECUTE_READWRITE; } else if self.contains(Self::WRITE) { flags |= PAGE_READWRITE; } else if self.contains(Self::READ) { flags |= PAGE_READONLY; } if self.contains(Self::COPY) { flags |= PAGE_WRITECOPY; } flags } } #[cfg(unix)] impl Into<nix::sys::mman::ProtFlags> for MemoryProtection { fn into(self) -> ProtFlags { let mut flags = ProtFlags::empty(); if self.contains(Self::READ) { flags |= ProtFlags::PROT_READ; } if self.contains(Self::WRITE) { flags |= ProtFlags::PROT_WRITE; } if self.contains(Self::EXECUTE) { flags |= ProtFlags::PROT_EXEC; } flags } } ``` **使用示例**: ```rust fn set_memory_protection( process: &dyn ProcessHandle, address: usize, size: usize, prot: MemoryProtection ) -> Result<()> { #[cfg(windows)] { let mut old_protect = 0; unsafe { VirtualProtectEx( process.handle(), address as _, size, prot.into(), &mut old_protect, )?; } } #[cfg(unix)] { let ret = unsafe { libc::mprotect( address as *mut _, size, prot.into().bits() as i32 ) }; if ret != 0 { return Err(MemoryError::from(std::io::Error::last_os_error())); } } Ok(()) } ``` --- ### 五、处理32/64位混合环境 **问题**:在64位系统中调试32位进程 ```rust // arch.rs #[derive(Debug, Clone, Copy)] pub enum ProcessArch { X86, X86_64, ARM, ARM64, Unknown, } impl Process { pub fn get_architecture(&self) -> Result<ProcessArch> { #[cfg(windows)] { let mut is_wow64 = 0; unsafe { IsWow64Process( self.handle, &mut is_wow64 )?; } Ok(if is_wow64 != 0 { ProcessArch::X86 } else { ProcessArch::X86_64 }) } #[cfg(unix)] { let auxv = fs::read(format!("/proc/{}/auxv", self.pid))?; // 解析 ELF 头获取架构信息 // 实际实现需要完整解析逻辑 Ok(ProcessArch::X86_64) } } } // 地址转换示例 fn read_pointer(&self, address: usize) -> Result<usize> { match self.get_architecture()? { ProcessArch::X86 => { let mut buf = [0u8; 4]; self.read_memory(address, &mut buf)?; Ok(u32::from_le_bytes(buf) as usize) } ProcessArch::X86_64 => { let mut buf = [0u8; 8]; self.read_memory(address, &mut buf)?; Ok(u64::from_le_bytes(buf) as usize) } _ => Err(anyhow!("Unsupported architecture")) } } ``` --- ### 六、高级内存扫描优化 **问题**:暴力扫描内存效率低下 **基于 Boyer-Moore 算法优化**: ```rust pub fn boyer_moore_search(haystack: &[u8], needle: &[u8]) -> Vec<usize> { let mut results = Vec::new(); let bad_char = build_bad_char_table(needle); let good_suffix = build_good_suffix_table(needle); let mut i = 0; while i <= haystack.len() - needle.len() { let mut j = needle.len() - 1; while haystack[i + j] == needle[j] { if j == 0 { results.push(i); break; } j -= 1; } let shift = std::cmp::max( bad_char.get(&haystack[i + j]).copied().unwrap_or(needle.len()), good_suffix[j] ); i += shift; } results } // 内存分块扫描 impl Process { pub fn efficient_scan(&self, pattern: &[u8], chunk_size: usize) -> Result<Vec<usize>> { let regions = self.get_memory_regions()?; let mut results = Vec::new(); for region in regions.filter(|r| r.is_readable()) { let mut offset = 0; while offset < region.size() { let read_size = std::cmp::min(chunk_size, region.size() - offset); let mut buffer = vec![0; read_size]; self.read_memory(region.start + offset, &mut buffer)?; let matches = boyer_moore_search(&buffer, pattern); results.extend(matches.into_iter().map(|m| region.start + offset + m)); offset += read_size - pattern.len() + 1; } } Ok(results) } } ``` --- ### 七、安全增强措施 **问题**:防止非法内存访问导致进程崩溃 ```rust // address validation impl Process { fn validate_address(&self, addr: usize, size: usize) -> Result<()> { let regions = self.get_memory_regions()?; if regions.iter().any(|r| addr >= r.start && addr + size <= r.end && r.permissions.contains('r') ) { Ok(()) } else { Err(MemoryError::InvalidAddress(addr)) } } pub fn safe_read(&self, addr: usize, buf: &mut [u8]) -> Result<()> { self.validate_address(addr, buf.len())?; self.read_memory(addr, buf) } } ``` ### 总结补充要点 1. **生命周期管理**: ```rust // Windows 需要显式关闭句柄 impl Drop for WindowsProcess { fn drop(&mut self) { unsafe { CloseHandle(self.handle); } } } ``` 2. **信号处理(Unix)**: ```rust // 处理 SIGSEGV 等信号 nix::sys::signal::signal( Signal::SIGSEGV, SigHandler::Handler(handle_segv) ).unwrap(); ``` 3. **性能监控**: ```rust // 使用 Instant 进行性能分析 let start = Instant::now(); process.efficient_scan(pattern, 1_000_000)?; println!("扫描耗时:{:?}", start.elapsed()); ``` 4. **结构化输出**: ```rust // 使用 serde 导出内存数据 #[derive(Serialize)] struct MemoryDump { address: String, data: Vec<u8>, } fn export_json(process: &Process) -> Result<String> { let data = process.read_memory_range(0x400000, 0x1000)?; let dump = MemoryDump { address: format!("{:x}", 0x400000), data, }; Ok(serde_json::to_string(&dump)?) } ``` 这些补充内容覆盖了实际开发中需要处理的深层次问题,包括性能优化、错误处理、安全增强和测试策略等关键方面。实际开发时建议: 1. 使用 `cfg_if!` 宏简化平台条件判断 2. 集成日志系统(如 `tracing`)记录调试信息 3. 为关键操作添加性能监控指标
回复或点赞可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2025-5-18 11:38 被Hrlies编辑 ,原因:
#基础知识
#系统内核
#开源分享
#开发技巧
收藏
・
3
点赞
・
23
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
git_51951meggadf3df
非常支持你的观点!
2天前
mb_lthgjpwj
为你点赞!
2026-8-21 09:09
晨曦。
为你点赞!
2026-8-17 06:02
wx_晨梦
为你点赞!
2026-7-16 10:39
mb_lbvebwpg
+1
感谢你分享这么好的资源!
2026-7-6 20:00
npc0vo
感谢你的积极参与,期待更多精彩内容!
2026-4-26 01:49
ldljlzw
感谢你的贡献,论坛因你而更加精彩!
2026-3-6 09:04
我不是深渊
感谢你分享这么好的资源!
2025-8-27 18:54
道友请留步
谢谢你的细致分析,受益匪浅!
2025-7-9 15:15
岁月。
谢谢你的细致分析,受益匪浅!
2025-7-4 15:51
kagayaki
谢谢你的细致分析,受益匪浅!
2025-6-18 23:04
叮叮当_468844
这个讨论对我很有帮助,谢谢!
2025-6-17 10:47
coreofthesun
感谢你的积极参与,期待更多精彩内容!
2025-6-6 14:11
axhack1
谢谢你的细致分析,受益匪浅!
2025-5-28 21:56
吻天的狼
期待更多优质内容的分享,论坛有你更精彩!
2025-5-28 16:27
Editor
感谢你的积极参与,期待更多精彩内容!
2025-5-26 15:12
Jtian
你的帖子非常有用,感谢分享!
2025-5-25 00:14
Hell02W0rld
为你点赞!
2025-5-21 17:39
dx苹果的心愿
谢谢你的细致分析,受益匪浅!
2025-5-20 15:00
pexillove
期待更多优质内容的分享,论坛有你更精彩!
2025-5-20 14:12
午十二
+1
感谢你的贡献,论坛因你而更加精彩!
2025-5-18 23:58
顽劣
这个讨论对我很有帮助,谢谢!
2025-5-17 21:55
huangyalei
为你点赞!
2025-5-17 10:49
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
12
)
mb_ldbucrik
雪 币:
6
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
691
粉丝
7
关注
私信
mb_ldbucrik
2
楼
感谢分享
2025-5-17 12:39
0
绿林科技
雪 币:
19
活跃值:
(438)
能力值:
( LV2,RANK:10 )
在线值:
发帖
8
回帖
80
粉丝
5
关注
私信
绿林科技
3
楼
谢谢分享~
2025-5-17 13:45
0
xxyiyi
雪 币:
408
活跃值:
(1162)
能力值:
( LV2,RANK:10 )
在线值:
发帖
11
回帖
62
粉丝
4
关注
私信
xxyiyi
4
楼
感谢分享
2025-5-20 11:33
0
吻天的狼
雪 币:
2
活跃值:
(143)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
13
粉丝
0
关注
私信
吻天的狼
5
楼
感谢分享
2025-5-28 16:27
0
achenyuan
雪 币:
237
活跃值:
(1865)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
22
粉丝
0
关注
私信
achenyuan
6
楼
谢谢分析昂!
2025-5-30 09:40
0
Zero*/
雪 币:
2310
活跃值:
(1755)
能力值:
( LV8,RANK:144 )
在线值:
发帖
3
回帖
11
粉丝
1
关注
私信
Zero*/
2
7
楼
感谢分享
2025-6-6 15:36
0
xiao小bai白
雪 币:
4397
活跃值:
(4072)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
9
粉丝
0
关注
私信
xiao小bai白
8
楼
原来真正的干货在这,谢谢大佬
2025-6-15 09:26
0
mb_pxdkpjwh
雪 币:
193
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
14
粉丝
0
关注
私信
mb_pxdkpjwh
9
楼
666
2025-6-18 10:59
0
vuu
雪 币:
181
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
5
粉丝
0
关注
私信
vuu
10
楼
感谢分享!
2025-7-4 15:21
0
mb_llakvlta
雪 币:
25
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
45
粉丝
0
关注
私信
mb_llakvlta
11
楼
6666
2026-5-21 22:08
0
Fzzz
雪 币:
3584
活跃值:
(265)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
62
粉丝
0
关注
私信
Fzzz
12
楼
跨平台抽象和性能优化讲得很实用
2026-8-26 14:56
0
mb_rqlksylg
雪 币:
96
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
74
粉丝
0
关注
私信
mb_rqlksylg
13
楼
666
2026-8-27 05:11
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Hrlies
17
发帖
18
回帖
20
RANK
关注
私信
他的文章
[原创] 如何使用Rust实现ELF动态加载器
4990
[原创] Rust如何实现跨进程的内存浏览器
6230
[原创]Rust核心特性所有权劫持注入
9513
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部