解密:最全面!一文让你看懂无侵入的微服务探针原理!!

优采云 发布时间: 2022-10-08 22:07

  解密:最全面!一文让你看懂无侵入的微服务探针原理!!

  前言

  随着微服务架构的兴起,应用行为的复杂性显着增加。为了提高服务的可观测性,分布式监控系统变得非常重要。

  基于谷歌的Dapper论文,开发了很多知名的监控系统:Zipkin、Jaeger、Skywalking、OpenTelemetry,想要统一江湖。一群厂商和开源爱好者围绕采集、监控数据的采集、存储和展示做了很多优秀的设计。

  如今,即使是个人开发者也可以依靠开源产品轻松构建完整的监控系统。但作为监控服务商,需要做好与业务的解绑工作,降低用户接入、版本更新、问题修复、业务止损等成本。因此,一个可插拔的、非侵入式的采集器成为了很多厂商的必备。

  为了获取服务之间的调用链信息,采集器通常需要在方法前后进行埋藏。在Java生态中,常见的埋点方式有两种:依靠SDK手动埋点;使用Javaagent技术做无创跟踪。下面对无创埋点的技术和原理进行全面的介绍。

  侵入式 采集器(探测)

  在分布式监控系统中,模块可以分为:采集器(Instrument)、Transmitter(TransPort)、Collector(Collector)、Storage(Srotage)、Display(API&UI)。

  zipkin的架构图示例

  采集器将采集到的监控信息从应用端发送给采集器,采集器存储,最后提供给前端查询。

  采集器采集信息,我们称之为Trace(调用链)。一条跟踪有一个唯一标识符 traceId,它由自上而下的树跨度组成。除了spanId,每个span还有traceId和父spanId,这样就可以恢复完整的调用链关系。

  为了生成跨度,我们需要在方法调用前后放置埋点。比如对于一个http调用,我们可以在execute()方法前后添加埋点,得到完整的调用方法信息,生成一个span单元。

  在Java生态中,常见的埋点方式有两种:依靠SDK手动埋点;使用Javaagent技术做无创跟踪。许多开发者在接触分布式监控系统时就开始使用 Zipkin。最经典的就是了解X-B3 trace协议,使用Brave SDK,手动埋点生成trace。但是,SDK中的埋点方式无疑是深深依赖于业务逻辑的。升级埋点时,必须进行代码更改。

  那么如何将其与业务逻辑解绑呢?

  Java还提供了另一种方式:依靠Javaagent技术修改目标方法的字节码,实现无创埋葬。这种使用Javaagent 的采集器 方式也称为探针。在应用启动时使用-javaagent,或者在运行时使用attach(pid)方法,可以将探针包导入应用,完成埋点的植入。以非侵入方式,可以实现无意义的热升级。用户无需了解深层原理即可使用完整的监控服务。目前很多开源监控产品都提供了丰富的java探针库,进一步降低了作为监控服务商的开发成本。

  开发一个非侵入式探针,可以分为三个部分:Javaagent、字节码增强工具、跟踪生成逻辑。下面将介绍这些。

  基本概念

  在使用JavaAgent之前,让我们先了解一下Java相关的知识。

  什么是字节码?

  自 1994 年 Sun 发明类 C 语言 Java 以来,凭借“编译一次,到处运行”的特性,它迅速风靡全球。与 C++ 不同的是,Java 先将所有源代码编译成类(字节码)文件,然后依靠各种平台上的 JVM(虚拟机)来解释和执行字节码,从而与硬件解绑。class文件的结构是一个table表,由很多struct对象组成。

  类型

  姓名

  阐明

  长度

  u4

  魔法

  幻数,识别Class文件格式

  4字节

  u2

  次要版本

  次要版本号

  2 个字节

  u2

  主要版本

  主要版本号

  2 个字节

  u2

  常量池计数

  常量池计算器

  2 个字节

  cp_info

  常量池

  常量池

  n 字节

  u2

  访问标志

  访问标志

  2 个字节

  u2

  这节课

  类索引

  2 个字节

  u2

  超类

  父索引

  2 个字节

  u2

  接口数

  

  接口计数器

  2 个字节

  u2

  接口

  接口索引集合

  2 个字节

  u2

  字段数

  字段数

  2 个字节

  字段信息

  字段

  字段集合

  n 字节

  u2

  方法数

  方法计数器

  2 个字节

  方法信息

  方法

  方法集合

  n 字节

  u2

  属性计数

  额外的物业柜台

  2 个字节

  属性信息

  属性

  附加属性集合

  n 字节

  字节码的字段属性

  让我们编译一个简单的类 `Demo.java`

  package com.httpserver;public class Demo { private int num = 1; public int add() { num = num + 2; return num; }}

  16进制打开Demo.class文件,解析出来的字段也是由很多struct字段组成的:比如常量池、父类信息、方法信息等。

  JDK自带的解析工具javap可以将class文件以人类可读的方式打印出来,结果和上面的一致

  什么是JVM?

  JVM(Java Virtual Machine),一种能够运行Java字节码的虚拟机,是Java架构的一部分。JVM有自己完整的硬件架构,如处理器、栈、寄存器等,也有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需要生成运行在JVM上的目标代码(字节码),无需修改即可运行在各种平台上。这是“一次性编译”。,到处跑”的真正意思。

  作为一种编程语言虚拟机,它不仅专用于Java语言,只要生成的编译文件符合JVM对加载和编译文件格式的要求,任何语言都可以被JVM编译运行。

  同时,JVM技术规范并没有定义使用的垃圾回收算法和优化Java虚拟机指令的内部算法等,只是描述了应该提供的功能,主要是为了避免过多的麻烦和对实施者的限制。正是因为描述得当,才给厂商留下了展示的空间。

  维基百科:现有 JVM 的比较

  其中性能较好的HotSpot(Orcale)和OpenJ9(IBM)受到广大开发者的喜爱。

  JVM的内存模型

  JVM部署完成后,每一个Java应用启动,都会调用JVM的lib库申请资源,创建一个JVM实例。JVM 将内存划分为不同的区域。下面是JVM运行时的内存模型:

  父委托加载机制

  当 Java 应用程序启动并运行时,一个重要的操作是加载类定义并创建一个实例。这依赖于 JVM 自己的 ClassLoader 机制。

  家长委托

  一个类必须由一个ClassLoader加载,对应的ClassLoader和父ClassLoader,寻找一个类定义会从下往上搜索,这就是父委托模型。

  JVM为了节省内存,并没有把所有的类定义都放到内存中,而是

  这个设计提醒我们,如果可以在加载时或者直接替换加载的类定义,就可以完成神奇的增强。

  JVM工具接口

  晦涩难懂的 JVM 屏蔽了底层的复杂性,让开发人员可以专注于业务逻辑。除了启动时通过java -jar的内存参数外,其实还有一套专门提供给开发者的接口,即JVM工具接口。

  JVM TI 是一个双向接口。JVM TI Client 也称为代理,基于事件事件机制。它接受事件并执行对 JVM 的控制,还可以响应事件。

  它有一个重要的特性——Callback(回调函数)机制:JVM可以产生各种事件,面对各种事件,它提供了一个Callback数组。每个事件执行的时候都会调用Callback函数,所以写JVM TI Client的核心就是放置Call​​back函数。

  正是这种机制允许我们向 JVM 发送指令以加载新的类定义。

  Java代理

  现在让我们试着想一想:如何神奇地改变应用程序中的方法定义?

  这有点像把大象放在冰箱里,然后走几步:

  

  根据字节码的规范生成一个新的类

  使用 JVM TI,命令 JVM 将类加载到相应的内存中。

  更换后,系统将使用我们的增强方法。

  这并不容易,还好jdk为我们准备了这样一个上层接口指令包。它也很容易使用。我们将通过一个简单的agent例子来说明指令包的关键设计。

  Javaagent的简单示例

  javaagent有两种使用方式:

  使用第一种方法的demo

  public class PreMainTraceAgent { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new DefineTransformer(), true); } static class DefineTransformer implements ClassFileTransformer{ @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("premain load Class:" + className); return classfileBuffer; } }}

  清单版本:1.0

  可以重新定义类:真

  可以重新转换类:真

  Premain 类:PreMainTraceAgent

  然后在resources目录下新建一个目录:META-INF,在这个目录下新建一个文件:MANIFREST.MF:

  最后打包成agent.jar包

  到了这里,你会发现增强字节码就是这么简单。

  字节码生成工具

  通过前面的理解,有一种感觉就是修改字节码就是这样^_^!!!但是我们要注意另一个问题,字节是如何产生的?

  大佬:熟悉JVM规范,理解每个字节码的含义。我可以手动更改类文件,所以我为此编写了一个库。

  专家:我知道客户端的框架,我修改源代码,重新编译,把二进制替换进去。

  小白:我看不懂字节码。我可以使用大佬写的库。

  下面将介绍几种常用的字节码生成工具

  ASM

  ASM 是一个纯字节码生成和分析框架。它具有完整的语法分析、语义分析,可用于动态生成类字节码。不过,这个工具还是太专业了。用户必须非常了解 JVM 规范,并且必须确切地知道应该在类文件中进行哪些更改以替换函数。ASM 提供了两组 API:

  如果你对字节码和JVM内存模型有初步的了解,你可以根据官方文档简单的生成类。

   ASM 十分强大,被应用于 <br /> 1. OpenJDK的 lambda语法 <br /> 2. Groovy 和 Koltin 的编译器 <br /> 3. 测试覆盖率统计工具 Cobertura 和 Jacoco <br /> 4. 单测 mock 工具,比如 Mockito 和 EasyMock <br /> 5. CGLIB ,ByteBuddy 这些动态类生成工具。

  字节好友

  ByteBuddy 是一款优秀的运行时字节码生成工具,基于 ASM 实现,提供更易用的 API。许多分布式监控项目(如 Skywalking、Datadog 等)使用它作为 Java 应用程序的探针以 采集 监控信息。

  下面是与其他工具的性能比较。

  在我们实际使用中,ByteBuddy的API真的很友好,基本满足了所有字节码增强需求:接口、类、方法、静态方法、构造方法、注解等的修改。另外,内置的Matcher接口支持模糊匹配,并且您可以根据名称匹配修改符合条件的类型。

  但也有不足之处。官方文件比较陈旧,中文文件很少。很多重要的特性,比如切面等,没有详细介绍,经常需要阅读代码注释和测试用例才能理解真正的含义。如果你对ByteBuddy感兴趣,可以关注我们的公众号,下面文章将对ByteBuddy做专题分享。

  跟踪数据的生成

  通过字节码增强,我们可以实现非侵入式埋葬,那么与trace的生成逻辑的关联就可以看作是灵魂注入。下面我们用一个简单的例子来说明这样的组合是如何完成的。

  示踪剂 API

  这是一个用于生成跟踪消息的简单 API。

  public class Tracer { public static Tracer newTracer() { return new Tracer(); } public Span newSpan() { return new Span(); } public static class Span { public void start() { System.out.println("start a span"); } public void end() { System.out.println("span finish"); // todo: save span in db } }}

  只有一种方法 sayHello(String name) 目标类 Greeting

  public class Greeting { public static void sayHello(String name) { System.out.println("Hi! " + name); }}

  手动生成trace消息,需要在方法前后添加手动埋点

  ... public static void main(String[] args) { Tracer tracer = Tracer.newTracer(); // 生成新的span Tracer.Span span = tracer.newSpan(); // span 的开始与结束 span.start(); Greeting.sayHello("developer"); span.end();}...

  无侵入埋点

  字节增强允许我们不修改源代码。现在我们可以定义一个简单的aspect,将span生成逻辑放入aspect中,然后使用Bytebuddy植入埋点。

  跟踪建议

  将跟踪生成逻辑放入切面

  public class TraceAdvice { public static Tracer.Span span = null; public static void getCurrentSpan() { if (span == null) { span = Tracer.newTracer().newSpan(); } } /** * @param target 目标类实例 * @param clazz 目标类class * @param method 目标方法 * @param args 目标方法参数 */ @Advice.OnMethodEnter public static void onMethodEnter(@Advice.This(optional = true) Object target, @Advice.Origin Class clazz, @Advice.Origin Method method, @Advice.AllArguments Object[] args) { getCurrentSpan(); span.start(); } /** * @param target 目标类实例 * @param clazz 目标类class * @param method 目标方法 * @param args 目标方法参数 * @param result 返回结果 */ @Advice.OnMethodExit(onThrowable = Throwable.class) public static void onMethodExit(@Advice.This(optional = true) Object target, @Advice.Origin Class clazz, @Advice.Origin Method method, @Advice.AllArguments Object[] args, @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result) { span.end(); span = null; }}

  onMethodEnter:方法进入时调用。Bytebuddy 提供了一系列注解,带有@Advice.OnMethodExit 的静态方法,可以插入到方法开始的节点中。我们可以获取方法的详细信息,甚至可以修改传入的参数以跳过目标方法的执行。

  OnMethodExit:方法结束时调用。类似于onMethodEnter,但可以捕获方法体抛出的异常并修改返回值。

  植入建议

  将 Javaagent 获得的 Instrumentation 句柄传递给 AgentBuilder(Bytebuddy 的 API)

  public class PreMainTraceAgent { public static void premain(String agentArgs, Instrumentation inst) { // Bytebuddy 的 API 用来修改 AgentBuilder agentBuilder = new AgentBuilder.Default() .with(AgentBuilder.PoolStrategy.Default.EXTENDED) .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(new WeaveListener()) .disableClassFormatChanges(); agentBuilder = agentBuilder // 匹配目标类的全类名 .type(ElementMatchers.named("baidu.bms.debug.Greeting")) .transform(new AgentBuilder.Transformer() { @Override public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { return builder.visit( // 织入切面 Advice.to(TraceAdvice.class) // 匹配目标类的方法 .on(ElementMatchers.named("sayHello")) ); } }); agentBuilder.installOn(inst); } // 本地启动 public static void main(String[] args) throws Exception { ByteBuddyAgent.install(); Instrumentation inst = ByteBuddyAgent.getInstrumentation(); // 增强 premain(null, inst); // 调用 Class greetingType = Greeting.class. getClassLoader().loadClass(Greeting.class.getName()); Method sayHello = greetingType.getDeclaredMethod("sayHello", String.class); sayHello.invoke(null, "developer"); }

  除了制作agent.jar,我们可以在本地调试的时候在main函数中启动,如上所示。本地调试

  打印结果

  WeaveListener onTransformation : baidu.bms.debug.Greetingstart a spanHi! developerspan finishDisconnected from the target VM, address: '127.0.0.1:61646', transport: 'socket'

  如您所见,我们在目标方法之前和之后添加了跟踪生成逻辑。

  在实际业务中,我们往往只需要捕获应用程序使用的帧,比如Spring的RestTemplate方法,就可以获取准确的Http方法调用信息。这种依靠这种字节码增强的方式,最大程度地实现了与业务的解耦。

  还有什么?

  在实际业务中,我们也积累了很多踩坑的经验:

  1、有没有好的探针框架可以让我“哼哼哼”地写业务?

  2、如何实现无意义的热升级,让用户在产品上轻松设置埋点?

  3. ByteBuddy如何使用,切面的注解是什么意思?

  4、Javaagent+Istio如何让Dubbo微服务治理框架毫无意义地迁移到ServiceMesh?

  解密:渗透测试之信息收集

  港口信息采集

  端口作为服务器和客户端之间的接口,起着非常重要的作用。

  一些常用端口标识服务器启用了哪些功能。常见的135、137、138、139、445,这些端口经常会爆发漏洞。以下是部分服务端口的漏洞。

  扫描端口,可以使用 Nmap 和 masscan 进行扫描和检测。采集尽可能多的开放端口和对应的服务版本。得到准确的服务版本后,可以搜索对应版本的漏洞。

  nmap 扫描的精度很高,但是扫描速度比较慢。

  Masscan 扫描速度更快,但准确性较低。

  Nmap -O 192.168.1.1

  ( )

  侧站C段查询

  侧站信息:侧站是与目标网站在同一服务器上的另一个网站。如果主站无法获取权限,可以将侧站作为攻击入口,然后想办法穿越服务器。到真正的目标站点目录。

  C段:C段是与目标机器ip在同一C段的其他机器;

  通过目标所在的C段中的任何其他机器,找到一种方法可以穿越到我们的目标机器。对于红蓝对抗和网保,C段扫描更有意义。

  但是对于单独的 网站 渗透测试,C 段扫描几乎没有意义。

  每个 IP 有四个段 ABCD。比如192.168.0.1,segment A是192,segment B是168,segment C是0,segment D是1,segment C嗅探就是拿下同C网段中的一台服务器,也就是,段 D 1-255 中的服务器,然后使用工具嗅探来关闭服务器。

  目录扫描

  由于发布网站时服务器配置问题,可以打开目录浏览器,造成信息泄露和安全隐患。

  在信息采集过程中,需要采集的敏感目录/文件包括:

  提到网站敏感目录时,要注意robots.txt文件。

  robots.txt 文件是专门为搜索引擎机器人编写的纯文本文件。我们可以在 网站 中指定我们不想被该文件中的机器人访问的目录。

  这样我们的网站内容可以部分或全部从搜索引擎收录中排除,或者搜索引擎只能收录指定内容。

  因此,我们可以使用robots.txt来阻止Google的机器人访问我们网站上的重要文件,GoogleHack的威胁是不存在的。

  如果 robots.txt 文件内容如下:

  &nbsp;

  用户代理: *

  禁止:/数据/

  禁止:/db/

  禁止:/admin/

  禁止:/经理/

  &nbsp;

  “Disallow”参数后面是禁止robot收录部分的路径,

  比如我们想让机器人封禁收录网站目录下的“data”文件夹,

  只需在 Disallow 参数后添加 /data/ 即可。

  如果要添加其他目录,只需继续以这种格式添加即可。

  文件写入后,将其上传到 网站 的根目录,以使 网站 远离 Google Hack。

  尽管 robots 文件的目的是防止搜索蜘蛛想要爬取它们想要保护的页面,

  但是如果我们知道robots文件的内容,就可以知道目标网站那些文件夹是不允许访问的。从侧面看,这些文件夹非常重要。

  检测目标网站后端目录的工具:

  网站指纹识别

  在渗透测试中,对目标服务器进行指纹识别是非常有必要的,因为只有识别出对应的Web容器或者cms,才能找到相关的漏洞,然后才能进行对应的渗透操作。

  cms又称全站系统。

  常见的cms有:WordPress、Dedecms、Discuz、PhpWeb、PhpWind、Dvbbs、Phpcms、ECShop、、SiteWeaver、Aspcms、Empire、Z-Blog等待。

  在线指纹网站:

  内容敏感信息泄露

  我们使用谷歌语法查找敏感信息

  

  搜索文件

  查找参数

  找到登录点:

  找到目录:

  寻找重要的东西:

  Github 信息泄露

  Github 不仅可以托管代码,还可以搜索代码。在上传和发布代码时,一些敏感的配置信息文件会被公开。

  Github 主要合集:

  网站架构

  但是要确定目标网站服务器的具体版本,可以使用nmap扫描,-O和-A参数可以扫描。

  1、Access的全称是Microsoft Office Access,是微软公司发布的关联数据库管理系统。

  对于小型数据库,当数据库达到 100M 左右时,性能会下降。数据库后缀:.mdb 一般是使用access数据库的asp网页文件

  2、SQL Server是微软开发和推广的关系数据库管理系统(DBMS),是一个比较大的数据库。端口号为1433。数据库后缀.mdf

  3.MySQL是关系型数据库管理系统,由瑞典MySQL AB公司开发,目前是Oracle的产品。

  MySQL 是最流行的关系数据库管理系统。MySQL是WEB应用中最好的应用软件之一。大多数 MySQL 数据库都是 php 页面。默认端口为 3306

  4.Oracle又称Oracle RDBMS,简称Oracle。

  它是 Oracle Corporation 的关系数据库管理系统。通常用于较大的 网站。默认端口为 1521

  首先是成本的区别,访问是免费的,mysql也是开源的,sql server一般要几千,Oracle要几万。

  其次,处理能力,access支持千级以内的访问,sql server支持千级到几万级的访问,Oracle支持海量访问。

  再次,从数据库规模来看,access是小型数据库,mysql是中小型数据库,sql server是中型数据库,oracle是大型数据库。

  知道了这些信息后,我们需要知道 网站 使用的是什么类型的 web 服务器:Apache、Nginx、Tomcat 或 IIS。

  在知道了web服务器是什么类型之后,我们还需要检测web服务器的具体版本。

  比如Ngnix版本

  1.可以根据网站URL判断

  2. 站点:xxx 文件类型:php

  3.可以根据火狐浏览器的插件来判断

  教学具体工具 SubDomainsBrute subDomainsBrute 的特点 subDomainsBrute 安装

  1.首先,你的电脑需要有python环境。如果没有,可以按照下面的链接下载。这里推荐使用python2.7.10。

  python2.7.10下载地址

  或者下载首页也可以下载python2.7.10按照以上提示步骤依次安装。

  安装后,需要添加环境变量。

  2.下载subDomainsBrute到python根目录。下载地址如下:

  subDomainsBrute 下载地址:

  3.检查python27文件夹下是否有Script文件夹

  里面有一些easy_install相关的内容,直接安装setuptools可以自动生成Script文件夹。

  下载 ez_setup.py 并在 cmd 中运行。

  进入命令行,然后将目录切换到python安装目录下的Script文件夹,运行python ez_setup.py生成scripts文件夹。

  4、在Script文件所在路径下输入cmd,在调用的命令行中安装需要的库。您可以使用 pip 直接安装它。命令是 pip install dnspython gevent

  子域Brutez 使用

  Usage: subDomainsBrute.py [options] target.com<br />Options:<br /> --version show program's version number and exit<br /> -h, --help show this help message and exit<br /> -f FILE File contains new line delimited subs, default is<br /> subnames.txt.<br /> --full Full scan, NAMES FILE subnames_full.txt will be used<br /> to brute<br /> -i, --ignore-intranet<br /> Ignore domains pointed to private IPs<br /> -t THREADS, --threads=THREADS<br /> Num of scan threads, 200 by default<br /> -p PROCESS, --process=PROCESS<br /> Num of scan Process, 6 by default<br /> -o OUTPUT, --output=OUTPUT<br /> Output file name. default is {target}.txt

  

  层子域挖掘器

  Layer子域矿机是一款域名查询工具,提供网站子域查询服务;

  界面简洁,操作方式简单,支持服务接口、暴力搜索、同服挖矿三种模式,支持打开网站、复制域名、复制IP、复制CDN、导出域名、导出IP ,导出域名+IP,导出域名+IP+WEB服务器,导出生存网站!

  使用说明

  列出百度下的子域

  网站使用后台扫描工具御剑

  御剑是一款好用的网站后台扫描工具,带有图形化页面,易于使用。

  我们使用Edgeworth Scanner,主要是扫描网站敏感目录,包括网站backends等。

  扫描原理也是爆破,就是通过敏感目录的字典进行匹配。

  在御剑后台扫描之前,爬虫会访问robots txt文件。

  工具介绍

  御剑安装使用

  1.下载解压后双击打开软件。

  2、打开后在域名输入框中输入要扫描的后台地址。

  3. 在下面的选项中,您可以选择扫描线程、扫描超时和文件类型。

  4.全部选好后,就可以点击开始扫描了。

  5. 等待扫描后,下方会出现扫描结果。

  御剑的使用非​​常简单,简单配置后即可进行扫描,但缺点是御剑无法导出扫描报告。

  当然御剑也有很多版本,功能略有不同,比如指纹识别、后台扫描、获取真实IP、检测注入等。

  使用 dirbuster

  工具介绍

  dirbuster的安装和使用

  1. 下载最新版本的 DirBuster。解压后在Windows中双击DirBuster-0.12.jar,或者直接打开kali自带的DirBuster(命令:root@kali:~# dirbuster)。

  2、启动DirBuser后,主界面如下:

  注意:如果您的扫描目标是,

  然后在URL中填写“/admin/{dir}”进行fuzz,

  表示可以在“{dir}”前后拼接任何你想要的目录或后缀,

  例如输入“:/admin/{dir}.php”表示扫描admin目录下的所有php文件。

  回顾过去的内容

  扫码获取卖淫视频+工具+群访问+靶场等资料

  扫描二维码免费卖淫!

  还有免费匹配的靶场和交流群!

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线