使用API Monitor工具巧妙探测C++程序中监听某端口的模块

目录

1、问题说明

2、API Monitor工具介绍

2.1、API Monitor主要用途

2.2、如何使用API Monitor工具

3、使用API Monitor监测程序对bind函数的调用,定位启用2620端口的模块

3.1、为啥要监控socket API函数bind

3.2、编写演示代码进行说明

3.3、使用API Monitor进行监测 

4、使用API Monitor工具的注意事项

4.1、如何在Call Stack窗口中查看到更多的函数调用?

4.2、如果Start Monitoring失败怎么办?

5、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       当我们没法确定软件中的某个端口是哪个模块启用时,可以使用API Monitor工具去监测软件中对相关socket函数的调用去搞清楚。本文将详细讲述如何使用API Monitor工具去监测对端口的启用情况。

1、问题说明

       最近在对我们的C++软件做安全测试,使用专用工具对软件使用的端口号进行扫描,发现有模块启用了2620端口,安全测试组的同事需要明确搞清楚这个端口是用来做什么的,但询问了多个开发组,都没人认领这个端口。

       后来想到可以尝试用API Monitor工具监测一下软件对socket API函数bind的调用(因为要开启监听端口,一般先要调用bind函数去绑定端口),如果能监测到记录,查看对应的函数调用堆栈就能确定是哪个模块启用了这个端口。

2、API Monitor工具介绍

2.1、API Monitor主要用途

        API Monitor可以用来监测程序对Windows系统API函数的调用,也可以监测对第三方dll库接口的调用。监测到函数调用时,可以查看到给函数传递的参数值,还可以查看当前函数调用所在线程的函数调用堆栈。

       使用该工具去窥探一些主流软件对系统API函数的调用情况,可以看到调用函数时传入的参数值,这样就能模仿一些主流软件去实现一些类似的功能。我们在项目中多次使用过这个方法,比如我们在处理安装包中创建桌面快捷方式时遇到的若干问题时就用过。

2.2、如何使用API Monitor工具

        API Monitor工具的主界面如下所示:

一般我们在使用该工具监控程序对API函数的调用时,首先在左边的函数列表中搜索到要监控的系统API函数,然后勾选上该函数。

       接着,在左下角的进程列表中找到要监测的目标进程,右键点击,在弹出的右键菜单中点击“Start Monitoring”开始监控该进程。如果目标程序在运行的过程中调用了被监控的函数,右上方的列表中就会显示对应的记录。选中某条记录时,可以查看调用目标函数时传入的参数值,还可以看到此时所在线程的函数调用堆栈。

       和Windbg类似的,开启对目标程序的监测有两种方式:

1)可以在左下方的进程列表中找到已经启动的目标进程,然后右键点击,在弹出的右键菜单中开启监控。
2)可以通过API Monitor去启动目标进程,点击主界面中的“Monitor New Process”,找到目标程序的路径,启动即可。这种方式适用于监测程序启动过程中的函数调用活动。

3、使用API Monitor监测程序对bind函数的调用,定位启用2620端口的模块

3.1、为啥要监控socket API函数bind

       一般负责网络通信的模块,无论是TCP还是UDP,底层最终调用的都是socket API函数。我们要监测哪个模块启用2620端口,一般监听端口时需要先调用bind函数绑定2620端口,所以我们监测对bind函数的调用即可。

       我们可以在MSDN上对bind函数说明页面中看到相关示例代码:

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include 
#include 
#include 

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

int main()
{

    // Declare some variables
    WSADATA wsaData;

    int iResult = 0;            // used to return function results

    // the listening socket to be created
    SOCKET ListenSocket = INVALID_SOCKET;

    // The socket address to be passed to bind
    sockaddr_in service;

    //----------------------
    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR) {
        printf("Error at WSAStartup()\n");
        return 1;
    }
    //----------------------
    // Create a SOCKET for listening for 
    // incoming connection requests
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket function failed with error: %u\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    //----------------------
    // The sockaddr_in structure specifies the address family,
    // IP address, and port for the socket that is being bound.
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr("127.0.0.1");
    service.sin_port = htons(2620);

    //----------------------
    // Bind the socket.
    iResult = bind(ListenSocket, (SOCKADDR *) &service, sizeof (service));
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error %u\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    else
        wprintf("bind returned success\n");

    WSACleanup();
    return 0;
}

从上面代码可以看出,在调用bind函数时,传入的是sockaddr_in结构体对象地址,然后强转成SOCKADDR*。

对于Windows C++程序员,在使用系统API函数遇到问题时,要会到MSDN上查看函数声明、参数含义以及函数的remarks说明,这是基本的要求!

3.2、编写演示代码进行说明

       此处不便展示项目中的相关模块和接口,也为了方便日后的视频课程的讲解,我特意写了一些测试代码,来讲述整个问题的监测过程。

       我在测试程序启动时调用bind函数绑定2620端口号,相关代码如下所示:

    // Declare some variables
    WSADATA wsaData;

    int iResult = 0;            // used to return function results

    // the listening socket to be created
    SOCKET ListenSocket = INVALID_SOCKET;

    // The socket address to be passed to bind
    sockaddr_in service;

    //----------------------
    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR) {
        printf("Error at WSAStartup()\n");
        return 1;
    }
    //----------------------
    // Create a SOCKET for listening for 
    // incoming connection requests
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket function failed with error: %u\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    //----------------------
    // The sockaddr_in structure specifies the address family,
    // IP address, and port for the socket that is being bound.
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr("127.0.0.1");
    service.sin_port = htons(2620);

    //----------------------
    // Bind the socket.
    iResult = bind(ListenSocket, (SOCKADDR *) &service, sizeof (service));

3.3、使用API Monitor进行监测 

       因为是在程序启动过程中绑定端口的,所以需要使用API Monitor工具启动目标程序,要从程序启动时就开始监控。

首先,在左边的函数列表中搜索到bind函数,因为可能很多函数名称中都包含bind子串,所以搜索时为了快速搜到目标函数,可以勾上“全字匹配”和“大小写匹配”。

        然后,点击“Monitor New Process”,找到目标程序的路径,启动程序并开始监测。程序在运行过程中调用了bind函数,就会显示出对应的记录(如果有多次调用,会有多个记录与之对应),如下所示:

在我们的项目软件中,实际上监测到了多条记录,我们如何确定哪条记录操作的端口号是2620呢?

       这时我们就需要查看调用bind函数时传入的参数了!选中某条记录时,Parameters窗口中就会显示调用该函数时传入的参数值,并且Call Stack窗口中会显示所在线程的函数调用堆栈。

       这个地方注意一下,Parameters窗口中显示的是SOCKADDR*指针,但我们在调用bind函数时传入的是sockaddr_in结构体对象数据,其中端口是设置到该结构体的sin_port字段中的,那么sin_port结构体和SOCKADDR结构体有什么的对应关系呢?我们可以在Visual Studio中go到这两个结构体的定义处,如下所示:

//
// Structure used to store most addresses.
//
typedef struct sockaddr {

#if (_WIN32_WINNT < 0x0600)
    u_short sa_family;
#else 
    ADDRESS_FAMILY sa_family;           // Address family.
#endif //(_WIN32_WINNT < 0x0600)

    CHAR sa_data[14];                   // Up to 14 bytes of direct address.
} SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;
//
// IPv4 Socket address, Internet style
//

typedef struct sockaddr_in {

#if(_WIN32_WINNT < 0x0600)
    short   sin_family;    
#else //(_WIN32_WINNT < 0x0600)
    ADDRESS_FAMILY sin_family;
#endif //(_WIN32_WINNT < 0x0600)

    USHORT sin_port;
    IN_ADDR sin_addr;
    CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;

这两个结构体的第一个字段都是sin_family,sockaddr_in结构体的第二个字段就是存放端口号的sin_port字段,所以在调用bind函数将sockaddr_in结构体对象地址强转成SOCKADDR对象地址,那么端口号实际上是对应到SOCKADDR结构体中的sa_data数组内存,端口号占两个字节,所以端口号值是存放在sa_data[0]和sa_data[1]内存中,所以我们在Parameters窗口中查看sa_data[0]和sa_data[1]内存中的值,看能否和2620端口号对应起来。

       我们可以使用Windows系统自带的计算器工具将十进制的2620转成16进制数据:

所以两个字节存放的数据为0x0A和0x3C,转换成10进制就是10和60,这样拿这两个值到Parameters窗口中比对,就能确定对应的条目了。

       确定对应的条目后,到Call Stack窗口中查看函数调用堆栈,就可以确定是哪个模块启动了2620端口了。

4、使用API Monitor工具的注意事项

4.1、如何在Call Stack窗口中查看到更多的函数调用?

        注意,API Monitor的函数调用堆栈窗口中默认只显示5行最近的函数调用,如果要显示更多的函数调用,需要到设置中去设置一下。具体的入口是,点击菜单栏中的Tools -> Options,在打开的设置窗口中,点击Monitoring按钮,显示对应的子页面,在页面中可以修改函数调用堆栈中显示的条目数目:

      此处注意一下,API Monitor工具不支持加载pdb文件,不像Process Explorer和Process Monitor那样支持加载pdb文件! 

4.2、如果Start Monitoring失败怎么办?

       如果在左下角的进程列表中右键点击目标进程,在弹出的右键菜单中点击“Start Monitoring”开始监控,结果报错,提示开启监控失败。可能是目标进程是以管理员权限运行的,而当前的API Monitor没有管理员权限运行,而是以标准用户权限运行的。因为目标进程的运行权限(管理员权限)与API Monitor(标准用户权限)不对等,所以在低权限的API Monitor中不能操作高权限的目标进程。

       解决办法是,关闭当前的API Monitor程序,然后右键以管理员权限重新启动API Monitor,即可开启对目标进程的监控了。

5、最后

       本例通过API Monitor去监控程序对bind函数的调用,去巧妙定位启用2620端口的模块。文中详细讲解了API Monitor定位问题的完整过程,希望能给大家提供一个借鉴或参考。

你可能感兴趣的:(C/C++技术分享,API,Monitor,监测API接口调用,bind,占用端口,函数调用堆栈,pdb符号文件)