`
lovnet
  • 浏览: 6969611 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

程序行为追踪(API TRACING)

阅读更多

程序行为追踪(API TRACING)

对木马类程序处理多了,就渐渐觉得静/动态手工分析过程在很大程度上都是重复劳动。总要先花半个钟头了解程序特性,手工分析时还生怕漏掉某项隐蔽的关键操作,导致最终清除不彻底。其实只要在主动安装木马的时候将API调用序列及相应参数做完整记录,就能极大减轻分析和清除木马的工作量。

  以前曾写过一个利用 API HOOKING 原理记录可疑程序对文件、注册表、服务和网络操作的小工具。API HOOKING 方式的优点在于,当调用 CreateFile 时可将文件名与句柄关联,等调用 WriteFile 对句柄操作时便能轻易取到文件名,对 hKey、socket 等句柄操作亦是如此。但该方式的缺点也显而易见,首先必须为每个感兴趣的 API 函数编写代码,“体力工作”繁重;其次我们不可能 HOOK 所有的 API 函数,由于缺乏完整的 API 调用序列作参考,在分析日志时很可能漏掉某些小动作。
  另一种思路是采用调试技术,在所有被引入的 DLL 的各函数入口处预先设置断点,调试期间再通过堆栈信息获取参数。IDA pro 和 OllyDbg 都可用于动态调试,同时还提供了脚本/插件功能。上周在北京开会的时候,我利用酒醒的时间写了一个简单的 OllyDbg 插件,仅从 CALL 指令处通过 ESP 指针获取8个函数参数,不对函数返回后的 EAX 及堆栈内容进行记录,在对普通(未加壳)程序的测试中效果还算理想。只要先在“Search for -> All intermodular calls”窗口中执行“Set breakpoint on every command”设置断点,再运行插件的“Fast trace”功能即可。日志文件片断如下:

  -------------------------------------------------------------------
  004099EC: CALL DWORD PTR DS:[<&KERNEL32.GetModuleFileNameA>] (kernel32.GetModuleFileNameA)
  -------------------------------------------------------------------
    ESP+00 (0012F704): 00000000
    ESP+04 (0012F708): 0012F824    ""
    ESP+08 (0012F70C): 00000104    00000104 ???
    ESP+0C (0012F710): 0012FA6D    ""
    ESP+10 (0012F714): 00000001    00000001 ???
    ESP+14 (0012F718): 00000000
    ESP+18 (0012F71C): 575C3A43    575C3A43 ???
    ESP+1C (0012F720): 4F444E49    4F444E49 ???
  
  -------------------------------------------------------------------
  00409A00: CALL DWORD PTR DS:[<&KERNEL32.CopyFileA>] (kernel32.CopyFileA)
  -------------------------------------------------------------------
    ESP+00 (0012F704): 0012F824    "E:\trojan.exe"
    ESP+04 (0012F708): 0012F71C    "C:\WINDOWS\system32\trojan.exe"
    ESP+08 (0012F70C): 00000000
    ESP+0C (0012F710): 0012FA6D    ""
    ESP+10 (0012F714): 00000001    00000001 ???
    ESP+14 (0012F718): 00000000
    ESP+18 (0012F71C): 575C3A43    575C3A43 ???
    ESP+1C (0012F720): 4F444E49    4F444E49 ???
  
  -------------------------------------------------------------------
  00409A94: CALL DWORD PTR DS:[<&ADVAPI32.OpenSCManagerA>] (ADVAPI32.OpenSCManagerA)
  -------------------------------------------------------------------
    ESP+00 (0012F704): 00000000
    ESP+04 (0012F708): 00000000
    ESP+08 (0012F70C): 000F003F    000F003F ???
    ESP+0C (0012F710): 0012FA6D    ""
    ESP+10 (0012F714): 00000001    00000001 ???
    ESP+14 (0012F718): 00000000
    ESP+18 (0012F71C): 575C3A43    575C3A43 ???
    ESP+1C (0012F720): 4F444E49    4F444E49 ???
  
  -------------------------------------------------------------------
  00409ACF: CALL DWORD PTR DS:[<&ADVAPI32.CreateServiceA>] (ADVAPI32.CreateServiceA)
  -------------------------------------------------------------------
    ESP+00 (0012F6DC): 0014F9C0
      F8 F9 14 00 98 BA DC FE 00 00 00 00 B4 F9 CC 53   ...............S
      82 6C FC 42 BF 8C 55 14 00 44 14 F4 AB AB AB AB   .l.B..U..D......
      AB AB AB AB EE FE EE FE 00 00 00 00 00 00 00 00   ................
      20 00 07 00 09 07 18 00 58 FA C3 77 EF CD AB 89   .......X..w....
      00 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00   ................
      00 00 00 00 00 00 00 00 05 00 00 00 01 00 00 00   ................
      00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
      06 00 00 00 0A 00 00 00 00 00 00 00 30 FB 14 00   ............0...
    ESP+04 (0012F6E0): 0042008C    "trojan"
    ESP+08 (0012F6E4): 004200C0    "Back door for testing"
    ESP+0C (0012F6E8): 000F01FF    000F01FF ???
    ESP+10 (0012F6EC): 00000120    00000120 ???
    ESP+14 (0012F6F0): 00000002    00000002 ???
    ESP+18 (0012F6F4): 00000001    00000001 ???
    ESP+1C (0012F6F8): 0012F71C    "C:\WINDOWS\system32\trojan.exe -start"
  
    ......

这种简单粗糙的日志对我来说已经够用了。若希望以更友好的形式显示参数信息,就必须有一些数据文件来描述各 API 函数的调用方式、返回值类型、参数个数等内容。比如这样:

  int LoadLibraryA([in] char *lpLibFileName);
  int LoadLibraryW([in] wchar *lpLibFileName);
  void *GetProcAddress([in] int hModule, [in] char *lpProcName);
  int GetModuleFileNameA([in] int hModule, [out] char *lpFilename, [in] int nSize);
  int GetModuleFileNameW([in] int hModule, [out] wchar *lpFilename, [in] int nSize);

编写一个简单的词法解析模块直接解析 VC 自带的 .h 文件,对使用者来说就更省事了。经过参数类型解析后的输出信息会好看很多:

  ------------------------------------------------------
  004099F2 -> GetModuleFileNameA(
    int             hModule: 0 (unsigned = 0 / hex = 0),
    char*          lpFilename: [0012F824] = "",
    int              nSize: 260 (unsigned = 260 / hex = 104),
     13 << results
    int             hModule: 0 (unsigned = 0 / hex = 0),
    char*          lpFilename: [0012F824] = "e:\trojan.exe" in stack of Thread,
    int              nSize: 260 (unsigned = 260 / hex = 104)
  );
  ------------------------------------------------------
  00409A06 -> CopyFileA(
    char*      lpExistingFileName: [0012F824] = "e:\trojan.exe" in stack of Thread,
    char*         lpNewFileName: [0012F71C] = "C:\WINDOWS\system32\trojan.exe" in stack of Thread,
    int          bFailIfExists: 0 (unsigned = 0 / hex = 0),
      1 << results
    char*      lpExistingFileName: [0012F824] = "",
    char*         lpNewFileName: [0012F71C] = "",
    int          bFailIfExists: 0 (unsigned = 0 / hex = 0)
  );
  ------------------------------------------------------
  00409A9A -> OpenSCManagerA(
    char*         lpMachineName: [00000000] = (null),
    char*        lpDatabaseName: [00000000] = (null),
    int         dwDesiredAccess: 983103 (unsigned = 983103 / hex = F003F),
   1374656 << results
    char*         lpMachineName: [00000000] = "",
    char*        lpDatabaseName: [00000000] = "",
    int         dwDesiredAccess: 983103 (unsigned = 983103 / hex = F003F)
  );
  ------------------------------------------------------
  00409AD5 -> CreateServiceA(
    int           hSCManager: 1374656 (unsigned = 1374656 / hex = 14F9C0),
    char*         lpServiceName: [0042008C] = "trojan" in main image (.data),
    char*         lpDisplayName: [004200C0] = "Back door for testing" in main image (.data),
    int         dwDesiredAccess: 983551 (unsigned = 983551 / hex = F01FF),
    int          dwServiceType: 288 (unsigned = 288 / hex = 120),
    int           dwStartType: 2 (unsigned = 2 / hex = 2),
    int         dwErrorControl: 1 (unsigned = 1 / hex = 1),
    char*       lpBinaryPathName: [0012F71C] = "C:\WINDOWS\system32\trojan.exe -start" in stack of Thread,
    char*       lpLoadOrderGroup: [00000000] = (null),
    int*           lpdwTagId: 00000000,
    char*        lpDependencies: [004201C4] = "" in main image (.data),
    char*      lpServiceStartName: [00000000] = (null),
    char*          lpPassword: [00000000] = (null),
   1370392 << results
    int           hSCManager: 1374656 (unsigned = 1374656 / hex = 14F9C0),
    char*         lpServiceName: [0042008C] = "",
    char*         lpDisplayName: [004200C0] = "",
    int         dwDesiredAccess: 983551 (unsigned = 983551 / hex = F01FF),
    int          dwServiceType: 288 (unsigned = 288 / hex = 120),
    int           dwStartType: 2 (unsigned = 2 / hex = 2),
    int         dwErrorControl: 1 (unsigned = 1 / hex = 1),
    char*       lpBinaryPathName: [0012F71C] = "",
    char*       lpLoadOrderGroup: [00000000] = "",
    int*           lpdwTagId: 00000000,
    char*        lpDependencies: [004201C4] = "",
    char*      lpServiceStartName: [00000000] = "",
    char*          lpPassword: [00000000] = ""
  );
  ......

dumbug 是一个开源的 API TRACING 工具,但被设计为仅对 trace 文件中定义的 API 调用进行跟踪。要想通过原始的 dumbug 获得完整 API 调用序列,工作量一点也不比 API HOOKING 方式小。而且就分析木马程序来说,我们并不需要记录 kernel32.dll 等系统链接库内部的 API 调用序列,所以还应根据 EXE 和 DLL 的入口地址、代码段长度进行过滤,最大限度减少冗余信息。在 dumbug 中,只要为 Tracer 对象的 ActivateTraces() 方法添加一些代码,并在其他地方也做相应的小修改,就可以输出上面的结果了。

  附1 - dumbug 的源代码可以从这里获得:

  [url]http://www.phenoelit.de/dumbug/dumbugVegasRelease.zip[/url]

  附2 - 简单的 ApiTracing-plugin for OllyDbg 源代码:

  // ApiTracing.c
  
  #define STRICT             // Avoids some type mismatches
  #include <windows.h>
  #include <stdio.h>
  #include <dir.h>
  #include "plugin.h"
  
  #define VERSIONHI    1        // High plugin version
  #define VERSIONLO    0        // Low plugin version
  #define LOG_FILENAME  "TraceApi.log" // Log filename
  
  static HINSTANCE hinst;         // DLL instance
  static BOOL bFastTracing = TRUE;
  static BOOL bStartTrace = FALSE;
  
  int Execute(char *text,char *answer);
  
  
  BOOL WINAPI DllEntryPoint(HINSTANCE hi, DWORD reason, LPVOID reserved)
  {
    FILE *fLog;
    if (reason == DLL_PROCESS_ATTACH) {
      hinst = hi;           // Mark plugin instance
      fLog = fopen(LOG_FILENAME, "w");
      if (fLog) {
        fprintf(fLog, "API tracing plugin v%i.%02i, written by glacier_at_xfocus.org\n",
          VERSIONHI, VERSIONLO);
        fclose(fLog);
      }
  
    }
    return 1;              // Report success
  }
  
  // Report plugin name and return version of plugin interface.
  extc int _export cdecl ODBG_Plugindata(char shortname[32])
  {
    strcpy(shortname, "API tracing");  // Name of command line plugin
    return PLUGIN_VERSION;
  }
  
  extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw, ulong *features)
  {
    // This plugin uses some newest features,
    // check that version of OllyDbg is correct.
    if (ollydbgversion < PLUGIN_VERSION)
      return -1;
  
    return 0;
  }
  
  extc void _export cdecl ODBG_Pluginmainloop(DEBUG_EVENT *debugevent) {
  }
  
  // Function adds items to main OllyDbg menu (origin=PM_MAIN).
  extc int _export cdecl ODBG_Pluginmenu(int origin, char data[4096], void *item)
  {
    if (origin != PM_MAIN)
      return 0;            // No pop-up menus in OllyDbg's windows
    strcpy(data, "0 &Fast trace,1 &Slow trace|2 &About");
    return 1;
  }
  
  // Receives commands from main menu.
  extc void _export cdecl ODBG_Pluginaction(int origin, int action, void *item)
  {
    char szLine[MAX_PATH] = {0};
    if (origin != PM_MAIN)
      return;
  
    switch (action) {
    case 0:               // Fast tracing
      bFastTracing = TRUE;
      bStartTrace = TRUE;
      Sendshortcut(PM_MAIN, 0, WM_KEYDOWN, 0, 0, VK_F9);
      break;
    case 1:               // Slow tracing
      bFastTracing = FALSE;
      bStartTrace = TRUE;
      Sendshortcut(PM_MAIN, 0, WM_KEYDOWN, 0, 0, VK_F7);
      break;
    case 2:               // "About", displays plugin info
      sprintf(szLine, "API tracing plugin v%i.%02i",
        VERSIONHI, VERSIONLO);
      MessageBox(0, szLine, "API tracing", MB_OK|MB_ICONINFORMATION);
      break;
    default: break;
    }
  }
  
  // User opens new or restarts current application.
  extc void _export cdecl ODBG_Pluginreset(void)
  {
    bStartTrace = FALSE;
  }
  
  extc int _export cdecl ODBG_Pluginclose(void)
  {
    return 0;
  }
  
  extc void _export cdecl ODBG_Plugindestroy(void)
  {
  }
  
  // 记录二进制内容
  void LogBinToFile(char *szFileName, const char *pBuf, int nSize)
  {
    FILE *fLog;
    int i, j;
    unsigned const char *ptr = (unsigned const char *)pBuf;
  
    fLog = fopen(szFileName, "a+");
    if (!fLog) return;
  
    if (nSize == 0)
      nSize = strlen(pBuf);
    for (i=0; i<nSize; i=i+0x10) {
      fprintf(fLog, "\t\t");
      for (j=i; j<i+0x10 && j<nSize; j++)
        fprintf(fLog, "%02X ", ptr[j]);
      fprintf(fLog, "\t");
      for (j=i; j<i+0x10 && j<nSize; j++) {
        if (IsCharAlpha(ptr[j]) || (ptr[j]>=0x20 && ptr[j]<0x7F))
          fprintf(fLog, "%c", ptr[j]);
        else
          fprintf(fLog, "%c", '.');
      }
      fprintf(fLog, "\n");
    }
    fclose(fLog);
  }
  
  // 格式化记录日志
  void LogToFile(char *szFileName, char *szFmt, ...)
  {
    FILE *fLog;
    char buff[1024];
    va_list arglist;
    va_start(arglist, szFmt);
    _vsnprintf(buff, sizeof(buff), szFmt, arglist);
    va_end(arglist);
  
    fLog = fopen(szFileName, "a+");
    if (!fLog) return;
  
    fprintf(fLog, "%s", buff);
    fclose(fLog);
  }
  
  // 检查是否为ASCII字符串
  BOOL CheckCharAlpha(char *szLine)
  {
    int i = 0;
    while (szLine[i]) {
      if (!IsCharAlpha(szLine[i]) && (szLine[i]<0x20 || szLine[i]>=0x7F))
        return FALSE;
      i++;
    }
    return TRUE;
  }
  
  extc int _export cdecl ODBG_Paused(int reason, t_reg *reg)
  {
    char szSrcDec[1024] = {0};
    char szLine[1024] = {0};
    unsigned long uEsp = 0, uAddr = 0, uTemp = 0;
    int nSize = 0, i = 0;
  
    if (!bStartTrace) return 0;
    if (!reg) {
      ShellExecute(0, "open", "notepad.exe", LOG_FILENAME, NULL, SW_SHOW);
      return 0;
    }
    
    // 读取断点处指令
    nSize = Readcommand(reg->ip, szLine);
    if (nSize > 0) {

      t_disasm disasm;
      
      // 反汇编二进制指令
      Disasm(szLine, nSize, reg->ip, szSrcDec, &disasm, DISASM_ALL, 0);
  
      if (strstr(disasm.result, "CALL ")) {  // 若为CALL指令
        LogToFile(LOG_FILENAME, "\n%s\n",
          "------------------------------------------------------");
        LogToFile(LOG_FILENAME, "%08X: %s (%s)", reg->ip, disasm.result, disasm.comment);
        LogToFile(LOG_FILENAME, "\n%s\n",
          "------------------------------------------------------");
  
        uEsp = reg->r[4];
  
        // 由ESP读取8个堆栈参数
        uTemp = uEsp;
        for (i=0; i<8; i++) {
          Readmemory(&uAddr, uTemp, sizeof(uAddr), MM_SILENT);
          LogToFile(LOG_FILENAME, "\tESP+%02X (%08X): %08X", i*4, uTemp, uAddr);
  
          if (uAddr == 0) nSize = 0;
          else nSize = Decodeascii(uAddr, szLine, sizeof(szLine)-1, DASC_ASCII);
          if (nSize > 0 && CheckCharAlpha(szLine)) {
            LogToFile(LOG_FILENAME, "\t\t%s\n", szLine);
          }
          else {
            memset(szLine, 0, sizeof(szLine));
            nSize = Readmemory(szLine, uAddr, 128, MM_SILENT);
            if (nSize > 0) {
              LogToFile(LOG_FILENAME, "\n");
              LogBinToFile(LOG_FILENAME, szLine, nSize);
            }
            else
              LogToFile(LOG_FILENAME, "\n");
          }
          uTemp += 4;
        }
      }

      // 继续执行
      if (bFastTracing)
        Sendshortcut(PM_MAIN, 0, WM_KEYDOWN, 0, 0, VK_F9);
      else {
        if (strstr(disasm.result, ".0")) {
          Go(0, reg->ip, STEP_IN, 1, 0);
        }
        else {
          Go(0, reg->ip, STEP_OVER, 1, 0);
        }
      }
    }
    return 0;
  }

分享到:
评论

相关推荐

    程序行为记录与跟踪

    在Windows系统里,至少有三种技术可以实现程序行为的记录,甚至控制程序的某些行为,分别是“虚拟机”(VM)、“API钩子”(API Hooking)和“API跟踪”(API Tracing)。 应用广泛的虚拟机技术 经常提到的...

    前端项目-tracing.js.zip

    `tracing.js`就是这样一个工具,它作为一个库或实用程序,专为开发者提供了强大的追踪和调试功能。下面我们将深入探讨`tracing.js`的核心概念、功能以及如何在实际项目中应用。 `tracing.js`的核心思想是通过提供...

    Python库 | opentracing_utils-0.17-py3-none-any.whl

    标题中的"Python库 | opentracing_utils-0.17-py3-none-any.whl"指的是一款基于Python的开源工具库,名为`opentracing_utils`,版本...通过使用这个库,开发者能够更好地理解和改进他们的应用程序在生产环境中的行为。

    Using Logging and Tracing on the SAP Web AS Java

    SAP为Java提供了专门的日志记录和追踪API,该API提供了丰富的功能,可以帮助开发者轻松地集成日志记录和追踪功能。 - **API简介**: SAP Logging & Tracing API允许开发者在应用程序中插入日志记录和追踪语句。这些...

    microprofile-opentracing:微轮廓开放追踪

    MicroProfile OpenTracing MicroProfile OpenTracing规范定义了行为以及用于访问JAX-RS应用程序中与OpenTracing兼容的Tracer对象的API。 行为指定如何自动创建传入和传出请求的OpenTracing Span。 API定义了如何显式...

    光线追踪RayTracer源码(基于OpenGL)

    光线追踪(Ray Tracing)是一种高级的计算机图形学技术,用于模拟光的物理行为,以创建逼真的图像。在这个基于OpenGL实现的光线追踪程序中,我们主要关注的是如何利用光线投射来创建图像,以及如何在OpenGL环境中...

    python-block_tracing-1.0.1.tar_python_

    6. **API设计**:理解如何使用库的API是关键,包括如何注册回调、如何开启和关闭追踪、如何获取和解析追踪结果等。 7. **线程和并发**:在多线程或多进程环境中,块追踪可能需要考虑同步问题,确保正确地记录每个...

    行业分类-设备装置-基于windows平台的跨层次数据流追踪方法.zip

    1. ETW(Event Tracing for Windows):这是Windows内置的一种事件追踪机制,允许开发者记录和分析系统中的事件流。ETW具有低开销、高效率的特点,适用于实时监控和诊断。 2. 数据包捕获(Packet Capture):通过...

    grabl-tracing:用于性能分析的Grabl Tracing客户端API

    Grabl Tracing是一款用于性能分析和监控的工具,其客户端API是开发者用来集成到Java应用程序中的关键组件。这个工具的主要目标是帮助开发人员理解和优化他们的系统性能,通过收集和可视化微服务之间的调用关系和延迟...

    vertx-tracing:Vertx与跟踪库的集成

    总之,Vert.x-tracing是Vert.x生态系统的一个重要组成部分,它使开发人员能够在非阻塞、反应式的Java应用程序中实现高级的跟踪功能,从而提高系统的可观察性和可维护性。通过集成OpenTracing和Zipkin,开发者可以...

    Ptrace, Utrace, Uprobes: Lightweight, Dynamic Tracing of User Apps

    这些API为动态追踪用户空间的应用程序提供了更加灵活、高效且低侵入性的方案。 #### Ptrace:传统的调试工具 `ptrace`是长久以来用于调试用户空间应用程序的主要工具之一。它通过一系列系统调用实现对目标进程的...

    log_dll.zip_Run Time

    7. **工具和库**:许多开发工具和库提供了API调用追踪的功能,例如Microsoft的ETW(Event Tracing for Windows)、Linux的strace和dtrace等。 8. **日志格式和解析**:生成的日志可能需要特定的格式,以便于分析...

    轻量级分布式追踪系统的设计与实现.pdf

    轻量级分布式追踪系统的设计与实现遵循了OpenTracing标准,这是一个开放的、厂商无关的追踪标准,它允许开发者通过简单的API集成不同的追踪系统。该标准有利于与各种运维监控系统进行集成,提高系统的可用性和灵活性...

    go-jaeger-kafka-client:基于Golang的opentracing(或jaeger)kafka transport实现

    OpenTracing是一个社区驱动的规范,定义了API,使开发人员能够在不关心具体实现的情况下在应用程序中插入追踪代码。它提供了一种统一的方式来收集服务间调用的延迟数据,这对于理解和优化分布式系统至关重要。Jaeger...

    skywalking讲义.pdf

    SkyWalking还支持OpenTracing标准,OpenTracing提供了一套平台无关、厂商无关的API,允许开发者能够更容易地添加或更换追踪系统的实现。开发者可以基于OpenTracing标准,将SkyWalking集成到应用中,实现对应用的监控...

    spectator-ext-sandbox-0.41.0.zip

    Jaeger绑定为Java OpenTracing API提供了接口和工具,使得开发者能够轻松地在Java应用程序中添加分布式追踪功能。 【标签】"开源项目" 表明这两个库都是开放源代码的,这意味着开发人员可以查看、修改和分发这些...

    Oracle Solaris 11.3 DTrace (Dynamic Tracing) Guide-468

    Oracle Solaris 11.3 DTrace (动态追踪) 指南是Oracle公司发布的一份详尽的技术文档,旨在帮助系统管理员、开发者和性能分析人员深入理解和利用DTrace工具来诊断和优化操作系统的行为。DTrace是Oracle Solaris操作...

    dotnet-收集NET应用程序的网络跟踪

    在.NET开发过程中,有时我们需要对应用程序的网络活动进行监控和调试,以便了解程序在通信时的行为,找出可能存在的性能瓶颈或错误。"dotnet-收集NET应用程序的网络跟踪"这个主题就聚焦于如何有效地实现这一目标。在...

    Torus-Ray-Tracing

    光线追踪是一种先进的渲染技术,它模拟了真实世界中光线的行为,使得生成的图像具有高度的现实感。 在计算机图形学中,光线追踪的基本思想是从相机位置发出虚拟光线,然后追踪这些光线与场景中物体的交互。当光线...

    2013-NS3-visualization-tutorial.pdf

    用户可以通过NS-3提供的API来全局启用pcap追踪,或者针对每个设备单独启用。 - 外部pcap工具(External pcap tools):NS-3支持使用外部pcap工具来分析网络数据包。文档提到了Wireshark和Shawn Ostermann开发的tcp...

Global site tag (gtag.js) - Google Analytics