ProxyChains 拦截了什么,又放过了什么
ProxyChains 在它启动的那个进程里挂钩库调用。用 maps、strace 和一次请求验证 TCP 覆盖范围,别再假设 DNS 或 UDP 也会跟着走。
ProxyChains-NG 并不建立隧道。它预先加载一个库,在它启动的那一个进程内部替换与网络相关的 libc 调用,所以边界是进程里的调用,而不是整台设备的路由。这个区别决定了成功的测试究竟证明了什么。经由链路跑通一次 curl 只确认了一次被挂钩的调用,并不确认名称解析、UDP 或每个子进程都走了同一条路。下面的步骤分三步验证覆盖范围,然后列出钩子够不到的地方。
预加载如何抵达一次连接
启动时,启动器把 libproxychains 放进 LD_PRELOAD,然后执行目标程序。这个库解析真正的 libc 符号,并用自己实现的 connect 和 getaddrinfo 等函数替换掉它们。经过这些符号的调用会被改写为配置文件里指定的 SOCKS 或 HTTP 代理。走别的路径的调用永远遇不到这个钩子,所以保证是按调用算的,不是按机器算的。
只有动态链接的程序才会参与,而在安全执行限制下的 setuid 二进制会完全忽略预加载。子进程会继承环境,但只在启动器把它留在原处期间。
确认这个库已经加载进那个进程
先发起请求,再检查活着的进程,而不是启动它的那个 shell。maps 文件会列出当前映射进该进程的所有共享对象。
proxychains4 -q curl -sS --max-time 8 https://example.com/ -o /dev/null &
pgrep -a curl
grep libproxychains /proc/$(pgrep -n curl)/maps匹配上只说明被挂钩的库确实存在,这是必要条件而非充分条件。结果为空通常意味着你查的是另一个进程,比如一个浏览器把 URL 交给了启动器之前就开好的实例。在追别的原因之前,先用 ldd 确认这个二进制是动态链接的,因为静态构建根本加载不了预加载。然后去读启动器实际用过的配置文件里的代理列表,因为过期路径或关闭的端口会给出能用的钩子,却给不出能用的链路。
追踪离开这个进程的连接
库的存在并不能证明每次调用都被改写,所以要跟住系统调用。在追踪器下跑一次请求,读出套接字实际去了哪里。
strace -f -e trace=connect -o /tmp/pc.trace proxychains4 -q curl -sS https://example.com/ -o /dev/null
grep -o "sin_port=htons([0-9]*)" /tmp/pc.trace | sort -u追踪结果应当只显示到你的代理端点的连接,并且端口与配置文件里的条目一致。如果目标站点的目的地址直接出现在追踪里,说明那次调用绕过了钩子,而这正是你要找的证据。要把端口号与代理列表比对,而不是相信文件名,因为别的项目残留的配置是静默直连的常见来源。把追踪结果留作验收记录;它只花几秒钟,却能终结关于覆盖范围的争论。
判断钩子覆盖不到什么
proxychains-ng 项目把重定向限定在 TCP,并提醒脚本、后台进程和已加载的模块可能与主程序表现不同。UDP 与 ICMP 在这个机制之外,任何依赖它们的东西都需要自己受支持的路径。DNS 取决于 proxy_dns 设置和应用程序自己的解析器行为,也就是说一次成功的 HTTPS 请求并不能说明名称解析的情况。证书校验也要留在它该在的位置:通过代理访问服务器,不是关掉身份校验的理由。
如果必须让直连回退不可能发生,就用一个控制流量本身的强制边界,比如网络命名空间、防火墙规则或 VPN,并把它与库钩子分开。钩子是针对某个指定程序的兼容工具,不是 kill switch。记录程序、确切命令、追踪里看到的代理端口,以及 DNS 是否在远端解析;这四点就是已核实范围与想当然范围之间的差别。