如何通过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验证:
- 启动gdb调试PHP进程:
gdb php - 设置断点:
break php_network_connect_socket_to_host - 运行测试脚本:
run your_ssl_test_script.php - 观察是否命中断点:如果未命中,说明实际调用路径并非该函数,需要重新定位目标符号;如果命中,说明问题出在你的包装逻辑。
内容的提问来源于stack exchange,提问作者N V
相关产品推荐
相关产品推荐

