Linux 代理工具: ProxyChains 与 Graftcp

ProxyChains / ProxyChains-ng 是一个 Linux 下的命令行代理工具,可为命令行工具(如 curl wget git 等)配置代理,当然上述命令行也可通过指定参数等方式配置代理,但不是很方便。

Windows 下可以使用 Proxifier ,为不支持代理的 exe 程序手动配置代理。

在 Linux 系统中,大多数程序都是采用动态链接方式构建的。动态链接的程序运行时,会根据可执行文件中记录的信息,加载所依赖的共享库(动态链接库)。动态链接器(通常是 ld-linux.so 或 ld.so)负责解析依赖并将其映射到内存中。环境变量 LD_PRELOAD 允许在程序加载其他共享库之前,先加载一个指定的共享库(下文称"指定共享库")。

如果用户设置了 LD_PRELOAD,动态链接器会先加载该变量中列出的指定共享库。指定共享库中的符号会在后续符号解析时被先查找到。如果指定共享库中定义了与系统库中同名的函数(如 connect()、getaddrinfo() 等),动态链接器就会将程序中对这些函数的调用解析到用户提供的实现上,而不是原始系统库中的实现。

利用这一特性,用户可以在指定共享库中重新实现某个函数(比如对 TCP 连接建立的 connect() 函数),在函数内部可以添加日志、修改参数或改变行为(如重定向到代理服务器)。当目标程序调用 connect() 时,实际执行的是用户在 LD_PRELOAD 指定的共享库中定义的版本,这就是所谓的“劫持”函数。

  • 劫持实现: 替换原有的函数实现,在替换后的函数中可以加入自定义逻辑,比如将目标 IP 或端口改为代理服务器的地址,从而实现 TCP 连接的重定向。
  • 局限性: 这种方法仅适用于动态链接的程序,因为静态链接的程序(如 Golang 默认静态编译)在编译时就将所需的函数代码直接嵌入到可执行文件中,不依赖运行时共享库,因此无法通过 LD_PRELOAD 劫持。

安装 ProxyChains

bash

sudo apt install proxychains

修改配置文件 /etc/proxychains.conf 的最后一行,将修改为特定代理 IP +端口,此处笔者修改为本地的 SOCKS5 代理

bash

socks5  127.0.0.1 7897

bash

# 不使用 ProxyChains 为 curl 配置代理情况下,无法访问 Google,
curl https://www.google.com

# 使用 ProxyChains,可以访问 Google
proxychains curl https://www.google.com

ProxyChains-3.1 (http://proxychains.sf.net)
|DNS-request| www.google.com
|S-chain|-<>-127.0.0.1:7897-<><>-4.2.2.2:53-<><>-OK
|DNS-response| www.google.com is 142.250.72.164
|S-chain|-<>-127.0.0.1:7897-<><>-142.250.72.164:443-<><>-OK
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en">

graftcp 可以把任何指定程序(应用程序、脚本、shell 等)的 TCP 连接重定向到 SOCKS5 或 HTTP 代理。与 Tsocks、ProxyChains 等工具不同,GraftCP 不使用 LD_PRELOAD 技巧,而是通过 ptrace(2) 系统调用来跟踪或修改任意指定程序的连接信息,因此对任何程序都有效。

注:很多项目为方便用户使用,会提供一键安装脚本,但这些脚本大多不支持添加代理参数,如笔者在安装 EMBA 时就遇到这种情况,已经配置了 Docker 代理且使用相关命令能从 DockerHub 正常拉取镜像,但脚本始终报错。

ptrace 是 Unix/Linux 系统提供的一个系统调用,允许一个进程(通常是调试器)监控和控制(包括修改内存/寄存器)另一个进程的执行。利用 ptrace,可以拦截目标程序发起的系统调用(如 connect()),并查看或修改其参数。

  • 跟踪实现: graftcp 通过 ptrace 监控目标程序的执行,当检测到程序调用 connect() 系统调用时,可以捕获其参数(如目标 IP、端口)并进行修改,将其重定向到代理服务器。
  • 优势: 因为该方法直接在系统调用层面进行拦截和修改,所以不受程序是动态链接还是静态链接的影响,能够对所有程序生效。

更详细的实现原理请参考 Graftcp 的 Github Repository

方法一:使用 Release 中提供的二进制文件(版本并非最新)

bash

# 下载
wget https://github.com/hmgle/graftcp/releases/download/v0.4.0/graftcp_0.4.0-1_amd64.deb

# 安装
dpkg -i graftcp_0.4.0-1_amd64.deb

# 启动服务,默认会监听本机所有网卡的 TCP 2233 端口
systemctl start graftcp-local.service

其他 Linux 发行版,以 Fedora 为例

bash

# 安装 zstd
dnf -y install zstd

# 下载编译好的二进制文件
wget https://github.com/hmgle/graftcp/releases/download/v0.4.0/graftcp-0.4.0-1-x86_64.pkg.tar.zst

# 解压
zstd -d graftcp-0.4.0-1-x86_64.pkg.tar.zst

注:若不喜欢将 Graftcp 注册为系统服务,可以只保留解压后 usr/bin/ 下的可执行文件graftcpgraftcp-local

解压到根目录(不推荐)

bash

# 解压归档文件,原归档文件中是按照 Linux 文件结构组织的,可以直接解压到根目录,文件会被放置到相关目录下
tar -xvf graftcp-0.4.0-1-x86_64.pkg.tar -C /

# 启动服务,默认会监听本机所有网卡的 TCP 2233 端口
systemctl start graftcp-local.service

注:对于从互联网下载的归档文件,使用 tar -xvf xxx.tar -C / 命令直接解压到根目录下,其存在安全风险(比如归档文件中存在恶意可执行文件,用于替换系统文件),并且笔者后续操作中还添加了服务和开机启动项,不建议直接参考。

上述归档文件中:

  • 可执行文件解压到 /usr/bin/,为 graftcp、graftcp-local、mgraftcp

  • 配置文件解压到 /etc/graftcp-local/graftcp-local.conf

  • Systemd 服务配置文件 /usr/lib/systemd/system/graftcp-local.service

方法二:源码编译

Graftcp 的 Github Repository 中有非常详细的编译步骤,此处不再赘述,编译好的 graftcp 位于项目根目录,graftcp-local 位于项目的 local 目录。

  • 本地编译时会下载 Golang 相关依赖,然而不装 Graftcp 下载依赖时会遇到网络问题 什么死锁….

  • 非本地编译:可以在 VPS,或者 GCP 提供的免费 Cloud Shell 中编译(推荐)

Graftcp 的配置文件为 /etc/graftcp-local/graftcp-local.conf

bash

# 安全考虑:关闭对外监听端口,只监听本地回环网卡端口
# listen = :2233
listen = 127.0.0.1:2233

# 修改上游的 SOCKS5 端口,默认为1080
socks5 = 127.0.0.1:7897

如果已经注册了系统服务,开启系统服务后直接使用 graftcp 命令即可

bash

# 开启 graftcp-local 服务
systemctl start graftcp-local.service

# 使用 
graftcp curl https://www.google.com
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head>.....

若未注册系统服务:保留如下两个可执行文件 graftcp、graftcp-local,无需创建配置文件直接通过传参指定参数

bash

# 开启 graftcp-local,本机监听端口、配置上游 SOCKS5 代理
./graftcp-local -socks5 "127.0.0.1:7897" -listen "127.0.0.1:2233"

# 使用代理获取数据
./graftcp curl https://www.google.com
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head>.....

# 通过 graftcp 打开一个新的 Shell,该 Shell 中的所有流量都会走代理
./graftcp bash --rcfile <(echo 'PS1="(graftcp) $PS1"')
(graftcp) $ curl https://www.google.com