Hook--拦截IP地址访问

Hook–拦截IP地址访问:

1、需求

  1. 不允许任何进程(真实存在的进程)访问黑名单里的网站;
  2. 用日志(进程名+时间+访问对象+结果 的形式)记录被拦截的访问记录及其他访问成功的记录;
  3. 定期检查黑名单是否发生变化。

2、需求分析

1. 目标:
- 实现一个网络访问过滤器,允许或拒绝网络连接和接受请求,基于一个黑名单中的IP地址列表。

2. 主要功能:

- 黑名单管理:程序维护一个IP地址的黑名单,用于存储禁止访问的IP地址。
    - 连接过滤:拦截与黑名单中的IP地址的连接尝试。
    - 访问记录:记录成功和被拦截的网络访问事件,包括时间戳、进程ID、操作类型、IP地址和端口信息。
    - 动态黑名单:支持动态更新黑名单,从外部文件(blacklist.txt)读取IP地址,以及定期检查是否有更新。
    - 基于动态链接:使用`dlopen`和`dlsym`从libc.so.6动态链接系统库中的`connect`和`accept`函数,以便拦截网络连接。

3. 代码结构:

  • main函数中,程序初始化了黑名单、获取系统库的句柄并动态链接connectaccept函数。
  • connect函数用于拦截网络连接,检查连接请求的IP地址是否在黑名单中,记录访问事件,然后根据结果决定是否允许连接。(暂时只支持ipv4地址)
  • accept函数类似地用于拦截接受请求,并进行相同的操作。

4. 黑名单管理:

  • 黑名单的IP地址列表在程序中以blacklist数组表示,可以在程序运行时从外部文件blacklist.txt加载新的IP地址。
  • 黑名单文件每隔一小时进行检查,如果有新的IP地址,程序将加载它们并更新黑名单。

5. 访问记录:

- 成功连接和被拦截的访问事件都会被记录到`ip_access_log.txt`日志文件,包括时间戳、进程ID、操作类型、IP地址和端口信息。

6. 动态链接:

- 程序使用`dlopen`和`dlsym`来动态链接系统库中的`connect`和`accept`函数,以便能够拦截这些函数的调用并添加自定义的过滤逻辑。

7. 异常处理:

  • 如果无法读取黑名单文件,程序会输出错误信息。

8. 测试地址:

  • 提供了一些测试用的IP地址,包括本机地址、百度、Google的公共DNS服务器和Cloudflare的公共DNS服务器。

3、代码部署

1、确保当前目录下有黑名单文件:blacklist.txt
2、确保当前目录下有日志文件:ip_access_log.txt
3、查询几个可供测试的ip地址进行命令行的测试:telnet x.x.x.x
   这里提供: 本机地址:127.0.0.1
   			百度:220.181.38.149
   			Google的公共DNS服务器:8.8.8.8
   			Cloudflare的公共DNS服务器:1.1.1.1
4、使用以下命令 将ip.c文件编译成动态链接库
   gcc ip.c -fpic -shared -ldl -o ip.so
5、使用以下命令将ip.so动态库注入当前目录
   export LD_PRELOAD=./ip.so
6、测试
   telnet 127.0.0.1
   telnet 220.181.38.149
   telnet 8.8.8.8
   telnet 1.1.1.1

4、代码

#define _GNU_SOURCE // 启用GNU扩展特性
#include <stdio.h>
#include <sys/types.h>
#include <dlfcn.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

/*
可供测试的IP地址:
    本机地址:127.0.0.1
    百度:220.181.38.149
    Google的公共DNS服务器:8.8.8.8
    Cloudflare的公共DNS服务器:1.1.1.1
*/

// 定义黑名单,包含禁止访问的IP地址
const char *blacklist[] = {
    "127.0.0.1"};

// 黑名单中IP地址的数量
static int numBlacklist = sizeof(blacklist) / sizeof(blacklist[0]);

// 记录上次黑名单检查的时间
static time_t lastBlacklistCheckTime = 0;

// 定义黑名单文件的路径
static const char *blacklistFile = "blacklist.txt";

// 记录访问记录到日志文件
void logAccess(const char *action, int sockfd, const char *ip, int port);

// 检查黑名单是否发生变化
void checkBlacklistChanges();

// 判断接入的网络是否为过滤IP,如果是则提前返回(针对客户端发起连接的IP地址)
typedef int (*new_socket)(int, const struct sockaddr *, socklen_t);
typedef int (*new_accept)(int, struct sockaddr *, socklen_t *);

// (针对客户端发起连接的IP地址)拦截连接请求
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
    void *handle = NULL;
    new_socket old_connect = NULL; // 保存成old_connect以备调用

    // 获得libc.so.6的句柄
    handle = dlopen("libc.so.6", RTLD_LAZY);

    // 返回open函数在libc.so.6中的加载时的地址
    old_connect = (new_socket)dlsym(handle, "connect");

    char ip[128];
    memset(ip, 0, sizeof(ip)); // ip地址
    int port = -1;             // 端口号

    // 如果传入的地址是IPv4地址
    if (AF_INET == addr->sa_family)
    {
        // 将传入的地址强制转换为IPv4地址结构
        struct sockaddr_in *sa4 = (struct sockaddr_in *)addr;
        // 使用inet_ntop函数将IPv4地址转换为人类可读的字符串形式(IP地址)
        inet_ntop(AF_INET, (void *)(struct sockaddr *)&sa4->sin_addr, ip, sizeof(ip));
        // 提取端口号,并将其从网络字节序转换为主机字节序
        port = ntohs(sa4->sin_port);
        // 打印IP地址和端口号
        printf("\nAF_INET IP===%s:%d\n", ip, port);
    }
    checkBlacklistChanges();
    // 记录访问信息到日志文件
    logAccess("connect", sockfd, ip, port);
    for (int i = 0; i < numBlacklist; ++i)
    {
        if (0 == strcmp(ip, blacklist[i]))
        {
            printf("\n===%s netfilter...connect failed!\n", ip);
            // 记录被拦截的访问信息到日志文件
            logAccess("connect (Blocked)", sockfd, ip, port);
            return -1;
        }
    }

    printf("\nPID:%d, socket:%d, %s Successfully connected!\n", getpid(), sockfd, ip);
    // 记录成功的访问信息到日志文件
    logAccess("connect (Success)", sockfd, ip, port);
    return old_connect(sockfd, addr, addrlen);
}

// 监控和控制通过套接字发起的连接请求(针对服务端发起连接的IP地址)拦截接受请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
    void *handle = NULL;
    new_accept old_accept = NULL; // 保存old_accept以备调用

    // 获得libc.so.6的句柄
    handle = dlopen("libc.so.6", RTLD_LAZY);

    // 返回accept函数在libc.so.6中的加载时的地址
    old_accept = (new_accept)dlsym(handle, "accept");

    char ip[128];
    memset(ip, 0, sizeof(ip)); // Ip地址
    int clientfd = -1;         // 客户端socketfd

    if (AF_INET == addr->sa_family)
    {
        // 将传入的地址强制转换为IPv4地址结构
        struct sockaddr_in *sa4 = (struct sockaddr_in *)addr;
        // 使用 inet_ntoa 函数将 IPv4 地址转换为 IP 地址字符串,并将其复制到 ip 变量中
        strcpy(ip, inet_ntoa(sa4->sin_addr));
    }
    checkBlacklistChanges();
    // 记录访问信息到日志文件
    logAccess("accept", sockfd, ip, -1);

    for (int i = 0; i < numBlacklist; ++i)
    {
        if (0 == strcmp(ip, blacklist[i]))
        {
            printf("\nPID:%d  ===%s=== netfilter...accept failed!\n", getpid(), ip);
            // 记录被拦截的访问信息到日志文件
            logAccess("accept (Blocked)", sockfd, ip, -1);
            return -1;
        }
    }
    // 放行  调用原始的 accept 函数以接受客户端的连接请求
    clientfd = old_accept(sockfd, addr, addrlen);
    printf("\nPID:%d, clientfd:%d, %s Successfully accepted!\n", getpid(), clientfd, ip);
    // 记录成功的访问信息到日志文件
    logAccess("accept (Success)", sockfd, ip, -1);
    return clientfd;
}
// 记录访问结果到日志文件
void logAccess(const char *action, int sockfd, const char *ip, int port)
{
    time_t now;
    struct tm *tm_info;
    char timestamp[20];

    time(&now);                                            // 获取当前时间
    tm_info = localtime(&now);                             // 将当前时间转换为本地时间结构
    strftime(timestamp, 20, "%Y-%m-%d %H:%M:%S", tm_info); // 格式化时间为字符串(年-月-日 时:分:秒)

    char logEntry[256]; // 存储日志条目
    snprintf(logEntry, sizeof(logEntry), "[%s] [PID:%d] [%s] [IP:%s] [Port:%d]\n", timestamp, getpid(), action, ip, port);

    // 打开日志文件,将信息追加到文件末尾
    FILE *logFile = fopen("ip_access_log.txt", "a");
    if (logFile != NULL)
    {
        fputs(logEntry, logFile);
        fclose(logFile);
    }
}

// 检查黑名单是否发生变化
void checkBlacklistChanges()
{
    time_t current_time;
    time(&current_time);
    // 检查黑名单变化的时间间隔,这里设置为1小时
    if (current_time - lastBlacklistCheckTime >= 3600)
    {
        FILE *file = fopen(blacklistFile, "r");
        if (file)
        {
            // 读取黑名单文件中的内容
            char line[256];
            int numBlacklistCount = 0; // 跟踪黑名单中的ip地址数量
            while (fgets(line, sizeof(line), file))
            {
                // 去掉行末的换行符
                line[strcspn(line, "\n")] = '\0';
                // 将新的ip地址加入黑名单
                blacklist[numBlacklistCount] = strdup(line);
                numBlacklistCount++;
            }
            numBlacklist = numBlacklistCount;
            fclose(file);
            // 更新黑名单中的IP地址数量
            for (int i = numBlacklistCount; i < sizeof(blacklist) / sizeof(blacklist[0]); ++i)
            {
                blacklist[i] = NULL;
            }
            lastBlacklistCheckTime = current_time; // 更新上次检查时间
        }
    }
    else
    {
        perror("Error reading blacklist file");
    }
}
  • 21
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霜晨月c

谢谢老板地打赏~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值