我试图覆盖“php_network_connect_socket_to_host”并且无法做到这一点,尽管我可以覆盖“connect”。
这是wrapper.c的源代码:
#define _GNU_SOURCE 1
#include <stdio.h>
#include <arpa/inet.h>
#include <dlfcn.h>
#include <string.h>
#include "php.h"
#include "php_network.h"
// libc
int (*real_connect)(int, const struct sockaddr *, socklen_t);
// php-src/main/network.c
int (*real_php_network_connect_socket_to_host)(const char *, unsigned short,
int, int, struct timeval *, zend_string **,
int *, const char *, unsigned short, long);
void _init (void)
{
const char *err;
real_connect = dlsym (RTLD_NEXT, "connect");
if ((err = dlerror()) != NULL) printf("dlsym (connect): %s\n", err);
void *handle = dlopen("", RTLD_GLOBAL);
real_php_network_connect_socket_to_host = dlsym (handle, "php_network_connect_socket_to_host");
if ((err = dlerror()) != NULL) printf("dlsym (php_network_connect_socket_to_host): %s\n", err);
}
int connect(int fd, const struct sockaddr *sk, socklen_t sl)
{
static struct sockaddr_in *sk_in;
sk_in = (struct sockaddr_in *)sk;
printf("[+] connect wrapper: %d %s:%d\n", fd, inet_ntoa(sk_in->sin_addr), ntohs(sk_in->sin_port));
return real_connect(fd, sk, sl);
}
int php_network_connect_socket_to_host(const char *host, unsigned short port,
int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string,
int *error_code, const char *bindto, unsigned short bindport, long sockopts)
{
printf("[+] php_network_connect_socket_to_host wrapper\n");
return real_php_network_connect_socket_to_host(host, port, socktype, asynchronous, timeout, error_string, error_code, bindto, bindport, sockopts);
}
我使用以下命令编译 .so:
gcc `php-config --includes` -nostartfiles -fpic -shared wrapper.c -o wrapper.so -ldl
符号名称看起来正常并且与wrapper.c中的值匹配。
以下是该命令的输出: readelf -Ws /usr/bin/php8.2 | grep -E“连接|php_network_connect_socket_to_host”:
Symbol table '.dynsym' contains 3015 entries:
Num: Value Size Type Bind Vis Ndx Name
217: 0000000000000000 0 FUNC GLOBAL DEFAULT UND connect@GLIBC_2.2.5 (5)
1388: 000000000029f910 1519 FUNC GLOBAL DEFAULT 14 php_network_connect_socket_to_host
...
ldd /usr/bin/php8.2:
linux-vdso.so.1 (0x00007ffd38b75000)
libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fc9f5ccb000)
libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007fc9f5cc6000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc9f5be7000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc9f5be2000)
libxml2.so.2 => /lib/x86_64-linux-gnu/libxml2.so.2 (0x00007fc9f5454000)
libssl.so.1.1 => /lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007fc9f53c1000)
libcrypto.so.1.1 => /lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fc9f5000000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007fc9f5327000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc9f5bc1000)
libsodium.so.23 => /lib/x86_64-linux-gnu/libsodium.so.23 (0x00007fc9f4fa6000)
libargon2.so.1 => /lib/x86_64-linux-gnu/libargon2.so.1 (0x00007fc9f5bb7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc9f4dc5000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc9f5cff000)
libicuuc.so.72 => /lib/x86_64-linux-gnu/libicuuc.so.72 (0x00007fc9f4bc7000)
liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007fc9f5b86000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc9f5322000)
libicudata.so.72 => /lib/x86_64-linux-gnu/libicudata.so.72 (0x00007fc9f2c00000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fc9f2800000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc9f5302000)`
这是 php 脚本本身(test.php):
<?php
$host = 'ssl://google.com:443';
$sock = stream_socket_client($host, $errNo, $errStr, 3, STREAM_CLIENT_CONNECT);
if (!$sock) {
echo "ERROR: $errNo - $errStr \n";
}
else {
echo "connection established\n\n";
echo "local socket name: ". stream_socket_get_name($sock, false) . "\n";
}
并以这种方式运行 PHP 脚本:
LD_PRELOAD=./wrapper.so /usr/bin/php8.2 test.php
这是 test.php 的输出:
dlsym() returned successfully for connect
dlsym() returned successfully for php_network_connect_socket_to_host
[+] connect wrapper: 5 216.58.212.46:443
connection established
local socket name: 192.168.0.34:42078
从输出中可以看出,connect() 被成功覆盖,没有给出错误,当 PHP 代码尝试执行 connect() 时,它会执行包装器,这正是我想要的。
但是,php_network_connect_socket_to_host() 的情况并非如此。 dlsym() 成功返回并且没有记录任何错误,但看起来该函数从未被覆盖,因为在 PHP 调用“stream_socket_client”之后,C 中的包装器不会被调用。我确实检查了stream_socket_client实现是否在内部使用“php_network_connect_socket_to_host”,我可以确认这是因为我运行了C代码并遵循了执行流程。
如果我尝试以下代码,则会收到无法找到符号的错误:
real_connect = dlsym (RTLD_NEXT, "php_network_connect_socket_to_host");
这就是我首先尝试使用 dlopen() 的原因。
也许问题与我加载.so的方式有关。我还看到 readelf 输出中的几个字段的值在 connect 和 php_network_connect_socket_to_host 之间有很大差异(字段是:值、大小、Ndx)任何帮助表示赞赏。
谢谢!
这是因为
real_php_network_connect_socket_to_host
是在 php 二进制文件本身中定义的,而不是像 connect
那样在外部共享对象中定义。
PHP 二进制文件几乎肯定是通过编译时已知的偏移量来调用它,而不是通过运行时查找(例如通过
dlsym
)。
如果您想劫持该函数,您也许可以进行一些运行时修补,以将函数体替换为程序集跳转到替换实现。