返回列表 发新帖

《安卓逆向这档事》十七、你的RPCvs佬的RPC

  [复制链接]

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-2-22 12:45:42 | 显示全部楼层 | 阅读模式  来自 福建
本帖最后由 正己 于 2024-2-27 18:07 编辑




一、课程目标

1.了解常见系统库的hook
2.了解frida_rpc
二、工具

1.教程Demo(更新)
2.jadx-gui
3.VS Code
4.jeb
5.IDLE

三、课程内容


1.Hook_Libart

`libart.so`: 在 Android 5.0(Lollipop)及更高版本中,`libart.so` 是 Android 运行时(ART,Android Runtime)的核心组件,它取代了之前的 Dalvik 虚拟机。可以在 `libart.so` 里找到 JNI 相关的实现。
PS:在高于安卓10的系统里,so的路径是/apex/com.android.runtime/lib64/libart.so,低于10的则在system/lib64/libart.so




frida_hook_libart
yang神的hook三件套
简单介绍:
hook_art.js:hook art中的jni函数并且有打印参数和返回值,使用之前记得先加上过滤的so名称,另外高版本的系统也需要在脚本68行的过滤修改成`_ZN3art3JNI`(最好是加载libart.so查看一下),这个脚本包含了hook_RegisterNatives.js的内容(但不太稳定,做个了解即可)
hook_RegisterNatives.js:hook打印动态注册的函数

hook_artmethod.js:打印所有java函数的调用

  1. frida -U -f com.zj.wuaipojie -l hook_RegisterNatives.js --no
复制代码

`Hook_RegisterNatives`
  1. function find_RegisterNatives(params) {
  2.     // 在 libart.so 库中枚举所有符号(函数、变量等)
  3.     let symbols = Module.enumerateSymbolsSync("libart.so");  
  4.     let addrRegisterNatives = null; // 用于存储 RegisterNatives 方法的地址

  5.     // 遍历所有符号来查找 RegisterNatives 方法
  6.     for (let i = 0; i < symbols.length; i++) {
  7.         let symbol = symbols[i]; // 当前遍历到的符号

  8.         // 检查符号名称是否符合 RegisterNatives 方法的特征
  9.         if (symbol.name.indexOf("art") >= 0 && //RegisterNatives 是 ART(Android Runtime)环境的一部分
  10.                 symbol.name.indexOf("JNI") >= 0 &&  //RegisterNatives 是 JNI(Java Native Interface)的一部分
  11.                 symbol.name.indexOf("RegisterNatives") >= 0 && //检查符号名称中是否包含 "RegisterNatives" 字样。
  12.                 symbol.name.indexOf("CheckJNI") < 0) { //CheckJNI 是用于调试和验证 JNI 调用的工具,如果不过滤,会有两个RegisterNatives,而带有CheckJNI的系统一般是关闭的,所有要过滤掉
  13.             addrRegisterNatives = symbol.address; // 保存方法地址
  14.             console.log("RegisterNatives is at ", symbol.address, symbol.name); // 输出地址和名称
  15.             hook_RegisterNatives(addrRegisterNatives); // 调用hook函数
  16.         }
  17.     }
  18. }

  19. function hook_RegisterNatives(addrRegisterNatives) {
  20.     // 确保提供的地址不为空
  21.     if (addrRegisterNatives != null) {
  22.         // 使用 Frida 的 Interceptor hook指定地址的函数
  23.         Interceptor.attach(addrRegisterNatives, {
  24.             // 当函数被调用时执行的代码
  25.             onEnter: function (args) {
  26.                 // 打印调用方法的数量
  27.                 console.log("[RegisterNatives] method_count:", args[3]);

  28.                 // 获取 Java 类并打印类名
  29.                 let java_class = args[1];
  30.                 let class_name = Java.vm.tryGetEnv().getClassName(java_class);
  31.                
  32.                 let methods_ptr = ptr(args[2]); // 获取方法数组的指针
  33.                 let method_count = parseInt(args[3]); // 获取方法数量

  34.                 // 遍历所有方法
  35.                                 //jni方法里包含三个部分:方法名指针、方法签名指针和方法函数指针。每个指针在内存中占用 Process.pointerSize 的空间(这是因为在 32 位系统中指针大小是 4 字节,在 64 位系统中是 8 字节)。为了提高兼容性,统一用Process.pointerSize,系统会自动根据架构来适配
  36.                 for (let i = 0; i < method_count; i++) {
  37.                     // 读取方法的名称、签名和函数指针
  38.                     let name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));//读取方法名的指针。这是每个方法结构体的第一部分,所以直接从起始地址读取。
  39.                     let sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));//读取方法签名的指针。这是结构体的第二部分,所以在起始地址的基础上增加了一个指针的大小
  40.                     let fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));//读取方法函数的指针。这是结构体的第三部分,所以在起始地址的基础上增加了两个指针的大小(Process.pointerSize * 2)。

  41.                     // 将指针内容转换为字符串
  42.                     let name = Memory.readCString(name_ptr);
  43.                     let sig = Memory.readCString(sig_ptr);

  44.                     // 获取方法的调试符号
  45.                     let symbol = DebugSymbol.fromAddress(fnPtr_ptr);

  46.                     // 打印每个注册的方法的相关信息
  47.                     console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr,  " fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress));
  48.                 }
  49.             }
  50.         });
  51.     }
  52. }

  53. setImmediate(find_RegisterNatives); // 立即执行 find_RegisterNatives 函数
复制代码


`hook_GetStringUTFChars`
!图片
  1. function hook_GetStringUTFChars() {
  2.     var GetStringUTFChars_addr = null;
  3.     // jni 系统函数都在 libart.so 中
  4.     var module_libart = Process.findModuleByName("libart.so");
  5.     var symbols = module_libart.enumerateSymbols();
  6.     for (var i = 0; i < symbols.length; i++) {
  7.         var name = symbols[i].name;
  8.         if ((name.indexOf("JNI") >= 0)
  9.             && (name.indexOf("CheckJNI") == -1)
  10.             && (name.indexOf("art") >= 0)) {
  11.             if (name.indexOf("GetStringUTFChars") >= 0) {
  12.                 // 获取到指定 jni 方法地址
  13.                 GetStringUTFChars_addr = symbols[i].address;
  14.             }
  15.         }
  16.     }

  17.     Java.perform(function(){
  18.         Interceptor.attach(GetStringUTFChars_addr, {
  19.             onEnter: function(args){

  20.             }, onLeave: function(retval){
  21.                 // retval const char*
  22.                                 console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString());
  23.                                 if(ptr(retval).readCString().indexOf("普通") >=0){
  24.                                         console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString());
  25.                                         console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
  26.                                 }

  27.             }
  28.         })
  29.     })
  30. }
  31. function main(){
  32.     Java.perform(function(){
  33.         hook_GetStringUTFChars();
  34.     });
  35. }
  36. setImmediate(main);
复制代码


2.Hook_Libc

`libc.so`: 这是一个标准的 C 语言库,用于提供基本的系统调用和功能,如文件操作、字符串处理、内存分配等。在Android系统中,`libc` 是最基础的库之一。



`hook_kill`

  1. js
  2. function replaceKILL() {
  3.     // 查找libc.so库中kill函数的地址
  4.     var kill_addr = Module.findExportByName("libc.so", "kill");
  5.     // 使用Interceptor.replace来替换kill函数
  6.     Interceptor.replace(kill_addr, new NativeCallback(function (arg0, arg1) {
  7.         // 当kill函数被调用时,打印第一个参数(通常是进程ID)
  8.         console.log("arg0=> ", arg0);
  9.         // 打印第二个参数(通常是发送的信号)
  10.         console.log("arg1=> ", arg1);
  11.         // 打印调用kill函数的堆栈跟踪信息
  12.         console.log('libc.so!kill called from:\n' +
  13.             Thread.backtrace(this.context, Backtracer.ACCURATE)
  14.             .map(DebugSymbol.fromAddress).join('\n') + '\n');
  15.     }, "int", ["int", "int"]))
  16. }

复制代码

`hook_pthread_create`6
  1. js
  2. function hook_pthread_create(){
  3.     //hook反调试
  4.     var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create");
  5.     console.log("pthread_create_addr: ", pthread_create_addr);
  6.     Interceptor.attach(pthread_create_addr,{
  7.         onEnter:function(args){
  8.             console.log(args[0], args[1], args[2], args[4]);
  9.         },onLeave:function(retval){
  10.             console.log("retval is =>",retval)
  11.         }
  12.     })
  13. }
复制代码

`hook_str_cmp`

  1. function hook_strcmp() {
  2.     var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
  3.     Interceptor.attach(pt_strcmp, {
  4.         onEnter: function (args) {
  5.             var str1 = args[0].readCString();
  6.             var str2 = args[1].readCString();
  7.             if (str2.indexOf("hh") !== -1) {
  8.                 console.log("strcmp-->", str1, str2);
  9.                 this.printStack = true;
  10.             }
  11.         }, onLeave: function (retval) {
  12.             if (this.printStack) {
  13.                 var stack = Thread.backtrace(this.context, Backtracer.ACCURATE)
  14.                     .map(DebugSymbol.fromAddress).join("\n");
  15.                 console.log("Stack trace:\n" + stack);
  16.             }
  17.         }
  18.     })
  19. }

复制代码

3.Hook_Libdl

`libdl.so`是一个处理动态链接和加载的标准库,它提供了`dlopen`、`dlclose`、`dlsym`等函数,用于在运行时动态地加载和使用共享库


`Hook_dlsym`获取jni静态注册方法地址

  1. function hook_dlsym() {
  2.     var dlsymAddr = Module.findExportByName("libdl.so", "dlsym");
  3.     Interceptor.attach(dlsymAddr, {
  4.         onEnter: function(args) {
  5.             this.args1 = args[1];
  6.         },
  7.         onLeave: function(retval) {
  8.             var module = Process.findModuleByAddress(retval);
  9.             if (module === null) return;
  10.             console.log(this.args1.readCString(), module.name, retval, retval.sub(module.base));
  11.         }
  12.     });
  13. }
复制代码


4.Hook_Linker
Linker是Android系统动态库so的加载器/链接器,通过android源码分析 init 和 init_array 是在 callConstructor 中被调用的

`hookInit和hookInitArray`
frida hook init_array自吐新解
经过安卓源码比对,从Android 8 ~ 14,结构体中`init_array`的位置都很稳定,提取部分头文件信息在CModule中定义一个soinfo结构体,接着定义一个接受一个`soinfo`指针参数和一个`callback`函数的函数,输出`init_array`信息
  1. function hook_call_constructors() {
  2.     // 初始化变量
  3.     let get_soname = null;
  4.     let call_constructors_addr = null;
  5.     let hook_call_constructors_addr = true;
  6.     // 根据进程的指针大小找到对应的linker模块
  7.     let linker = null;
  8.     if (Process.pointerSize == 4) {
  9.         linker = Process.findModuleByName("linker");
  10.     } else {
  11.         linker = Process.findModuleByName("linker64");
  12.     }
  13.     // 枚举linker模块中的所有符号
  14.     let symbols = linker.enumerateSymbols();
  15.     for (let index = 0; index < symbols.length; index++) {
  16.         let symbol = symbols[index];
  17.         // 查找名为"__dl__ZN6soinfo17call_constructorsEv"的符号地址
  18.         if (symbol.name == "__dl__ZN6soinfo17call_constructorsEv") {
  19.             call_constructors_addr = symbol.address;
  20.         // 查找名为"__dl__ZNK6soinfo10get_sonameEv"的符号地址,获取soname
  21.         } else if (symbol.name == "__dl__ZNK6soinfo10get_sonameEv") {
  22.             get_soname = new NativeFunction(symbol.address, "pointer", ["pointer"]);
  23.         }
  24.     }
  25.     // 如果找到了所有需要的地址和函数
  26.     if (hook_call_constructors_addr && call_constructors_addr && get_soname) {
  27.         // 挂钩call_constructors函数
  28.         Interceptor.attach(call_constructors_addr,{
  29.             onEnter: function(args){
  30.                 // 从参数获取soinfo对象
  31.                 let soinfo = args[0];
  32.                 // 使用get_soname函数获取模块名称
  33.                 let soname = get_soname(soinfo).readCString();
  34.                 // 调用tell_init_info函数并传递一个回调,用于记录构造函数的调用信息
  35.                 tell_init_info(soinfo, new NativeCallback((count, init_array_ptr, init_func) => {
  36.                     console.log(`[call_constructors] ${soname} count:${count}`);
  37.                     console.log(`[call_constructors] init_array_ptr:${init_array_ptr}`);
  38.                     console.log(`[call_constructors] init_func:${init_func} -> ${get_addr_info(init_func)}`);
  39.                     // 遍历所有初始化函数,并打印它们的信息
  40.                     for (let index = 0; index < count; index++) {
  41.                         let init_array_func = init_array_ptr.add(Process.pointerSize * index).readPointer();
  42.                         let func_info = get_addr_info(init_array_func);
  43.                         console.log(`[call_constructors] init_array:${index} ${init_array_func} -> ${func_info}`);
  44.                     }
  45.                 }, "void", ["int", "pointer", "pointer"]));
  46.             }
  47.         });
  48.     }
  49. }

复制代码

5.frida_rpc
frida 提供了一种跨平台的 rpc(就是Remote Procedure Call 远程过程调用) 机制,通过 frida rpc 可以在主机和目标设备之间进行通信,并在目标设备上执行代码,简单理解就是可以不需要分析某些复杂加密,通过传入参数获取返回值,进而来实现python或易语言来调用的一系列操作,多用于爬虫。

`包名附加进程`
  1. import frida, sys
  2. jsCode = """ ...... """
  3. script.exports.rpcfunc()
  4. process = frida.get_usb_device().attach('包名') [b][size=5]获取USB设备并附加到应用[/size][/b]
  5. script = process.create_script(jsCode) [b][size=5]创建并加载脚本[/size][/b]
  6. script.load()[b][size=5]执行脚本[/size][/b]
  7. sys.stdin.read()[b][size=5]保持脚本运行状态,防止它执行完毕后立即退出[/size][/b]
复制代码

`spawn方式启动`
  1. import frida, sys
  2. jsCode = """ ...... """
  3. script.exports.rpcfunc()
  4. device = frida.get_usb_device()
  5. pid = device.spawn(["包名"])    #以挂起方式创建进程
  6. process = device.attach(pid)
  7. script = process.create_script(jsCode)
  8. script.load()
  9. device.resume(pid)  #加载完脚本, 恢复进程运行
  10. sys.stdin.read()
复制代码

`连接非标准端口`
  1. import frida, sys
  2. jsCode = """ ...... """
  3. script.exports.rpcfunc()
  4. process = frida.get_device_manager().add_remote_device('192.168.1.22:6666').attach('包名')
  5. script = process.create_script(jsCode)
  6. script.load()
  7. sys.stdin.read()
复制代码

  1. function get_url() {
  2.     let ChallengeNinth = Java.use("com.zj.wuaipojie.ui.ChallengeNinth");
  3.     ChallengeNinth["updateUI"].implementation = function (list) {
  4.         let ret = this.updateUI(list);
  5.         // 获取List的大小
  6.         var size = list.size();
  7.         // 遍历并打印List中的每个ImageEntity对象
  8.         for (var i = 0; i < size; i++) {
  9.             var imageEntity = Java.cast(list.get(i), Java.use('com.zj.wuaipojie.entity.ImageEntity'));
  10.             console.log(imageEntity.name.value + imageEntity.cover.value);
  11.         }
  12.         return ret;
  13.     };
  14. }

复制代码


需要提前pip安装好的几个库
  1. frida-tools==9.2.4,uvicorn,fastapi,requests
复制代码
  1. #导入需要的库
  2. from fastapi import FastAPI
  3. from fastapi.responses import JSONResponse
  4. import frida, sys
  5. import uvicorn

  6. #创建FastAPI应用实例
  7. app = FastAPI()

  8. #定义一个GET请求的路由'/download-images/'
  9. @app.get("/download-images/")
  10. def download_images():

  11.     def on_message(message, data):
  12.         message_type = message['type']
  13.         if message_type == 'send':
  14.             print('[* message]', message['payload'])
  15.         elif message_type == 'error':
  16.             stack = message['stack']
  17.             print('[* error]', stack)
  18.         else:
  19.             print(message)


  20.     jsCode = """
  21.     function getinfo(){
  22.         var result = [];
  23.         Java.perform(function(){
  24.             Java.choose("com.zj.wuaipojie.ui.ChallengeNinth",{
  25.                 onMatch:function(instance){
  26.                     instance.setupScrollListener(); // 调用目标方法
  27.                 },
  28.                 onComplete:function(){}
  29.             });
  30.             
  31.             Java.choose("com.zj.wuaipojie.entity.ImageEntity",{
  32.                 onMatch:function(instance){
  33.                     var name = instance.getName();
  34.                     var cover = instance.getCover();
  35.                     result.push({name: name, cover: cover}); // 收集数据
  36.                 },
  37.                 onComplete:function(){}
  38.             });
  39.         });
  40.         return result; // 返回收集的结果
  41.     }
  42.     rpc.exports = {
  43.         getinfo: getinfo // 导出函数供外部调用
  44.     };
  45.     """


  46.     process = frida.get_usb_device().attach("com.zj.wuaipojie")

  47.     script = process.create_script(jsCode)
  48.     script.on("message", on_message)
  49.     script.load()  
  50.     getcovers = script.exports.getinfo()
  51.     print(getcovers)

  52.     return JSONResponse(content=getcovers)

  53. if __name__ == "__main__":
  54.     uvicorn.run(app, host="127.0.0.1", port=8000)


复制代码


  1. 写一段python的requests代码,访问http://127.0.0.1:8000/download-images/端口,会获得如下的json数据,按照名字把图片爬取到同目录的pic文件夹里,并写好注释
  2. [{
  3. "name": "霸王别姬",
  4. "cover": "https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c"
  5. },{
  6. "name": "这个杀手不太冷",
  7. "cover": "https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c"
  8. },{
  9. "name": "肖申克的救赎",
  10. "cover": "https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c"
  11. }]
复制代码

五、答疑

待更新

六、视频及课件地址
百度云
阿里云
哔哩哔哩
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压




已有6人评分好评 金币 理由
陌璃与鱼 + 1 + 1
烟雨漫江南 + 1 + 10 很给力!
虚伪的鲲 + 1 + 1
善始繁_克终寡 + 1 + 1
兔耳离 + 1 + 1 很给力!
肖博坤 + 1 + 1

查看全部评分 总评分:好评 +6  金币 +15 

回复

使用道具 举报

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-2-22 12:46:14 | 显示全部楼层  来自 福建
本帖最后由 正己 于 2024-5-11 16:27 编辑

字数太多了,剩下的这个楼层发

七、其他章节

《安卓逆向这档事》一、模拟器环境搭建  
《安卓逆向这档事》二、初识APK文件结构、双开、汉化、基础修改  
《安卓逆向这档事》三、初识smail,vip终结者  
《安卓逆向这档事》四、恭喜你获得广告&弹窗静默卡  
《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩  
《安卓逆向这档事》六、校验的N次方-签名校验对抗、PM代{过}{滤}理、IO重定向  
《安卓逆向这档事》七、Sorry,会Hook真的可以为所欲为-Xposed快速上手(上)模块编写,常用Api  
《安卓逆向这档事》八、Sorry,会Hook真的可以为所欲为-xposed快速上手(下)快速hook  
《安卓逆向这档事》九、密码学基础、算法自吐、非标准加密对抗  
《安卓逆向这档事》十、不是我说,有了IDA还要什么女朋友?  
《安卓逆向这档事》十二、大佬帮我分析一下  
《安卓逆向这档事》番外实战篇1-某电全家桶  
《安卓逆向这档事》十三、是时候学习一下Frida一把梭了(上)
《安卓逆向这档事》十四、是时候学习一下Frida一把梭了(中)
《安卓逆向这档事》十五、是时候学习一下Frida一把梭了(下)
《安卓逆向这档事》十六、是时候学习一下Frida一把梭了(终)
《安卓逆向这档事》十七、你的RPCvs佬的RPC
《安卓逆向这档事》番外实战篇2-【2024春节】解题领红包活动,启动!
八、参考文档
JNI_doc
安卓JNI精细化讲解,让你彻底了解JNI(二):用法解析
android逆向奇技淫巧二十六:基础库的hook&x音检测frida方式之一(十一)]
so逆向筑基-hook init init_array 和JNI_OnLoad
frida hook so
Android&Linker学习笔记
回复

使用道具 举报

19

主题

1872

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
4944
好评
24
信誉
133

考神

发表于 2024-2-22 12:47:57 来自手机  | 显示全部楼层  来自 福建
看看大佬
回复

使用道具 举报

0

主题

517

回帖

1475

积分

高中生

Rank: 4

金币
197
好评
0
信誉
100

考神MT论坛新人MT论坛帅哥MT论坛最佳新人

发表于 2024-2-22 12:48:57 来自手机  | 显示全部楼层  来自 广东
感谢分享
回复

使用道具 举报

6

主题

6282

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
1201
好评
5
信誉
102
发表于 2024-2-22 12:53:00 来自手机  | 显示全部楼层  来自 江苏
看看隐藏
回复

使用道具 举报

85

主题

2095

回帖

8050

积分

硕士生

Rank: 6Rank: 6

金币
3192
好评
38
信誉
85
发表于 2024-2-22 12:53:10 来自手机  | 显示全部楼层  来自 浙江
路过看看
回复

使用道具 举报

0

主题

1267

回帖

2764

积分

禁止发言

金币
2369
好评
0
信誉
100
发表于 2024-2-22 12:58:22 来自手机  | 显示全部楼层  来自 广西
看看
回复

使用道具 举报

0

主题

1551

回帖

5542

积分

硕士生

Rank: 6Rank: 6

金币
3526
好评
0
信誉
100
发表于 2024-2-22 12:59:07 来自手机  | 显示全部楼层  来自 湖南
感谢分享
回复

使用道具 举报

4

主题

765

回帖

2954

积分

大学生

冒泡

Rank: 5Rank: 5

金币
2709
好评
16
信誉
103

考神MT论坛新人MT论坛最佳新人MT论坛帅哥

发表于 2024-2-22 12:59:44 来自手机  | 显示全部楼层  来自 云南
还得是大佬
回复

使用道具 举报

20

主题

1777

回帖

5547

积分

硕士生

Rank: 6Rank: 6

金币
3880
好评
17
信誉
100
QQ
发表于 2024-2-22 13:00:39 来自手机  | 显示全部楼层  来自 河南
看看大佬
回复

使用道具 举报

1

主题

6029

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
8129
好评
0
信誉
100
发表于 2024-2-22 13:00:58 来自手机  | 显示全部楼层  来自 江苏
感谢大佬分享,学习
回复

使用道具 举报

8

主题

3679

回帖

8370

积分

硕士生

Rank: 6Rank: 6

金币
3565
好评
0
信誉
96

考神

发表于 2024-2-22 13:01:00 | 显示全部楼层  来自 河南
感谢分享
回复

使用道具 举报

106

主题

2904

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
5046
好评
33
信誉
176
发表于 2024-2-22 13:01:39 来自手机  | 显示全部楼层  来自 湖南
看看大佬
回复

使用道具 举报

3

主题

4430

回帖

1万

积分

博士生

https://0818.neocities.org/

Rank: 7Rank: 7Rank: 7

金币
1873
好评
6
信誉
102
发表于 2024-2-22 13:02:10 来自手机  | 显示全部楼层  来自 新疆
看看隐藏
回复

使用道具 举报

0

主题

3328

回帖

8016

积分

硕士生

Rank: 6Rank: 6

金币
1353
好评
0
信誉
100

考神

发表于 2024-2-22 13:04:04 来自手机  | 显示全部楼层  来自 广东
感谢分享
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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