You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过LD_PRELOAD与dlsym覆写PHP的php_network_connect_socket_to_host

无法覆写PHP的php_network_connect_socket_to_host函数,但connect可正常覆写的解决思路

核心问题定位

你遇到的问题本质是:系统调用connect属于标准库符号,LD_PRELOAD的替换逻辑能正常生效,但PHP内部的php_network_connect_socket_to_host符号的替换受限于动态链接的查找规则,导致包装函数无法触发。

可行解决方向

1. 修正符号查找方式,针对主程序符号做替换

RTLD_NEXT仅会在当前共享库之后加载的动态库中查找符号,但php_network_connect_socket_to_host大概率存在于PHP主程序(php可执行文件)而非动态库中,因此RTLD_NEXT无法定位到该符号。

替换方案:使用dlopen(NULL, RTLD_NOW)加载当前进程的全局符号表(包含主程序的所有导出符号),再通过dlsym获取原函数指针:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>

// 声明与PHP原函数完全匹配的函数类型
typedef int (*php_network_connect_socket_to_host_t)(
    char *host, 
    int port, 
    struct sockaddr *sa, 
    socklen_t salen, 
    long timeout, 
    int flags, 
    char **error_string, 
    zend_string **connect_error
);

static php_network_connect_socket_to_host_t original_php_func = NULL;

int php_network_connect_socket_to_host(
    char *host, 
    int port, 
    struct sockaddr *sa, 
    socklen_t salen, 
    long timeout, 
    int flags, 
    char **error_string, 
    zend_string **connect_error
) {
    // 初始化原函数指针(仅执行一次)
    if (!original_php_func) {
        void *proc_handle = dlopen(NULL, RTLD_NOW);
        original_php_func = (php_network_connect_socket_to_host_t)dlsym(proc_handle, "php_network_connect_socket_to_host");
        dlclose(proc_handle);
    }

    // 插入你的钩子逻辑
    fprintf(stderr, "Hooked php_network_connect_socket_to_host: connecting to %s:%d\n", host, port);

    // 调用原函数
    return original_php_func(host, port, sa, salen, timeout, flags, error_string, connect_error);
}

// 你的connect包装函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
    printf("Wrapper triggered for connect\n");
    typedef int (*connect_t)(int, const struct sockaddr *, socklen_t);
    static connect_t original_connect = NULL;
    if (!original_connect) {
        original_connect = (connect_t)dlsym(RTLD_NEXT, "connect");
    }
    return original_connect(sockfd, addr, addrlen);
}

2. 严格匹配函数签名

必须确保包装函数的参数类型、返回值、调用约定与PHP原函数完全一致。如果签名存在任何差异(比如漏写参数、类型不匹配),动态链接器会判定为不同函数,包装逻辑不会生效。

你可以通过PHP源码或nm -D /path/to/php | grep php_network_connect_socket_to_host结合C语言类型推导确认完整签名。

3. 调整编译与加载参数

  • 去掉编译时的-nostartfiles选项:该选项会跳过标准启动代码,可能导致动态链接初始化逻辑异常,改用以下编译命令:
    gcc `php-config --includes` -fPIC -shared wrapper.c -o wrapper.so -ldl -Wl,--no-as-needed
    
  • 确保LD_PRELOAD的加载优先级:执行测试脚本时明确指定加载你的共享库:
    LD_PRELOAD=./wrapper.so php your_ssl_test_script.php
    

4. 验证函数调用路径是否符合预期

虽然你认为stream_socket_client会调用php_network_connect_socket_to_host,但SSL场景下可能存在分支逻辑(比如使用openssl的底层连接函数),可以通过gdb验证:

  1. 启动gdb调试PHP进程:gdb php
  2. 设置断点:break php_network_connect_socket_to_host
  3. 运行测试脚本:run your_ssl_test_script.php
  4. 观察是否命中断点:如果未命中,说明实际调用路径并非该函数,需要重新定位目标符号;如果命中,说明问题出在你的包装逻辑。

内容的提问来源于stack exchange,提问作者N V

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 19:38:12