proxychains 实现原理

颛孙麻雀
2023-12-01

proxychains功能

proxychains可以让命令通过指定的proxy访问网络。
例如:

wget  www.baidu.com

由于防火墙的原因,直接访问不通。 
如果已经有一个代理服务(socks5://127.0.0.1:1080),配置proxychains之后:

proxychians wget www.baidu.com

可以正常访问了

proxychains怎么实现的?

动态链接与LD_PRELOAD

静态链接与动态链接具体的可以看这里。简单理解就是:

  • 静态链接在编译的时候就把所有依赖的方法的调用地址都写死了
  • 动态链接就是程序的依赖在运行的时候才载入,依赖通常都是以so库提供,程序在运行时动态的找到so库并载入。

LD_PRELOAD环境变量允许你定义在程序运行前优先加载的动态链接库。

比如,程序main依赖a.so库,a.so中包含有method1()方法。此时,我们自己写一个b.so,也包函数签名完全一样的method1()方法,然后我们指定LD_PRELOAD=b.so,这时main程序在运行时调用的method1()就是b.so中的method1()

linux环境下的程序在访问网络的时候最终都会用到底层的libc.so提供的网络函数,如,TCP协议一定会用到connect函数来建立TCP连接。如果把connect函数重写并编译成whatever.so文件,再把LD_PRELOAD设置成whatever.so,这样我们的程序在建立TCP连接的时候就会调到我们重写的connect函数。

dlsym函数

dlsym(dynamic library symbol)

根据 动态链接库 操作句柄(handle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。

通过dlsym可以拿到libc中的真实的connect函数的地址,然后用真实的connect函数与代理服务器建立连接,把数据包发送到代理服务器。

dup2函数实现重定向

深入理解dup和dup2函数可以看这里

简单来说,用dup2可以实现把文件描述符A重定向到文件描述符B。也就是说,所有对A有写入,最终都会写入到B。

好了,梳理一下流程

  • 伪造connect函数,返回文件描述符fd_a
  • 利用dlsym拿到真实的connect函数,与proxy建立连接,拿到文件描述符fd_b
  • 利用dup2把fd_a重定向到fd_b
  • 发到fd_a的数据包都被发送到了proxy上
  • 数据到了proxy上面之后,剩下的任务就交给proxy了

简易的proxychains实现

理解了proxychains之后,再阅读proxychains的源码,可以自己实现一个简易的proxychains了,下面是源码:

libproxy.c

#undef _GNU_SOURCE
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/cdefs.h>
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <sys/utsname.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <time.h>
#include <sys/time.h>
#include <stdarg.h>
#include <assert.h>
/*#include <regex.h>*/



#define     satosin(x)      ((struct sockaddr_in *) &(x))
#define     SOCKFAMILY(x)     (satosin(x)->sin_family)

#define BUFF_SIZE 8*1024  // used to read responses from proxies.
#define DNS_NAME_SIZE 100  // used to read responses from proxies.


static int poll_retry(struct pollfd *fds, nfds_t nfsd, int timeout)
{
    int ret;
    int time_remain = timeout;
    int time_elapsed = 0;

    struct timeval start_time;
    struct timeval tv;

    gettimeofday(&start_time, NULL);

    do {
        //printf("Retry %d\n", time_remain);
        ret = poll(fds, nfsd, time_remain);
        gettimeofday(&tv, NULL);
        time_elapsed = ((int)(tv.tv_sec - start_time.tv_sec) * 1000 + (int)(tv.tv_usec - start_time.tv_usec) / 1000);
        //printf("Time elapsed %d\n", time_elapsed);
        time_remain = timeout - time_elapsed;
    } while(ret == -1 && errno == EINTR && time_remain > 0);

    //if (ret == -1)
    //printf("Return %d %d %s\n", ret, errno, strerror(errno));
    return ret;
}

static size_t write_n_bytes(int fd, char *buff, size_t size) {
    size_t i = 0;
    size_t wrote = 0;
    for(;;) {
        i = (size_t) write(fd, &buff[wrote], size - wrote);
        if(i <= 0)
            return i;
        wrote += i;
        if(wrote == size)
            return wrote;
    }
}

static size_t read_n_bytes(int fd, char *buff, size_t size) {
    int ready;
    size_t i;
    struct pollfd pfd[1];
    int tcp_read_time_out = 4 * 1000;

    pfd[0].fd = fd;
    pfd[0].events = POLLIN;
    for(i = 0; i < size; i++) {
        pfd[0].revents = 0;
        ready = poll_retry(pfd, 1, tcp_read_time_out);
        if(ready != 1 || !(pfd[0].revents & POLLIN) || 1 != read(fd, &buff[i], 1))
            return 0;
    }
    return size;
}

int socks5_auth(int sock, struct sockaddr_in *addr){

    int len = 0;
    unsigned char buff[BUFF_SIZE];
    buff[0] = 5;    //version
    buff[1] = 1;    //nomber of methods
    buff[2] = 0;    // no auth method
    if(3 != write_n_bytes(sock, (char *) buff, 3)){
        return 1;
    }

    if(buff[0] != 5 || (buff[1] != 0 && buff[1] != 2)) {
        if(buff[0] == 5 && buff[1] == 0xFF)
            return 2;
    }

    size_t buff_iter = 0;
    buff[buff_iter++] = 5;    // version
    buff[buff_iter++] = 1;    // connect
    buff[buff_iter++] = 0;    // reserved


    buff[buff_iter++] = 1;    // ip v4
    uint32_t ip = addr->sin_addr.s_addr;
    memcpy(buff + buff_iter, &ip, 4);    // dest host
    buff_iter += 4;

    unsigned short port = addr->sin_port;
    memcpy(buff + buff_iter, &port, 2);    // dest port
    buff_iter += 2;


    if(buff_iter != write_n_bytes(sock, (char *) buff, buff_iter)){
        return 3;
    }

    if(4 != read_n_bytes(sock, (char *) buff, 4)){
        return 4;
    }

    if(buff[0] != 5 || buff[1] != 0){
        return 5;
    }

    switch (buff[3]) {

        case 1:
            len = 4;
            break;
        case 4:
            len = 16;
            break;
        case 3:
            if(1 != read_n_bytes(sock, (char *) &len, 1)){
                return 6;
            }
            break;
        default:
            return 7;
    }

    if(len + 2 != read_n_bytes(sock, (char *) buff, len + 2)){
        return 8;
    }

    return 0;
}

int connect(int sockfd, const struct sockaddr *addr, socklen_t len)
{

    int  (*real_connect) (int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    real_connect = dlsym (RTLD_NEXT, "connect");

    int socktype = 0;
    socklen_t optlen = 0;
    optlen = sizeof(socktype);
    getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &socktype, &optlen);
    if(!(SOCKFAMILY(*addr) == AF_INET && socktype == SOCK_STREAM))
        return real_connect(sockfd, addr, len);

    int n_sfd = 0; //new socker fd
    n_sfd = socket (AF_INET, SOCK_STREAM, 0);
    if (n_sfd < 0){
        perror("error new socket:");
        exit(EXIT_FAILURE);
    }

    dup2(n_sfd, sockfd);

    struct sockaddr_in  serv;
    serv.sin_family = AF_INET;
    serv.sin_port = htons(13291);
    serv.sin_addr.s_addr = inet_addr("127.0.0.1");

    int ret = real_connect(n_sfd, (struct sockaddr *) &serv, sizeof(serv));
    if (ret < 0){
        perror("connect:");
        exit (EXIT_FAILURE);
    }

    int res = socks5_auth(n_sfd, (struct sockaddr_in *) addr);
    if(res != 0){
        struct sockaddr_in* tmp = (struct sockaddr_in *) addr;
        printf("ip addr: %zu", tmp->sin_addr.s_addr);
        printf("ip port: %d", tmp->sin_port);
        printf("auth result: %d", res);
        return -1;
    }
    return 0;
}

main.c

/*#include <sys/types.h>*/
/*#include <sys/param.h>*/

/*#include <errno.h>*/
#include <stdio.h>
#include <stdlib.h>
/*#include <string.h>*/
#include <unistd.h>

/*#include <sys/wait.h>*/

int main(int argc, char *argv[])
{

    char buf[100];
#ifndef IS_MAC
    snprintf(buf, sizeof(buf), "%s/%s", ".", "libmyproxy.so");
    setenv("LD_PRELOAD", buf, 1);
#else
    snprintf(buf, sizeof(buf), "%s/%s", ".", "libmyproxy.so");
    putenv("DYLD_FORCE_FLAT_NAMESPACE=1");
#define LD_PRELOAD_ENV "DYLD_INSERT_LIBRARIES"
#define LD_PRELOAD_SEP ":"
    /*setenv("DYLD_INSERT_LIBRARIES", buf, 1);*/
    /*setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1);*/
#endif
    execvp(argv[1], &argv[1]);
    return 0;
}

先编译libproxy.c

gcc --shared -fPIC -o libmyproxy.so libmyproxy.c -ldl

现编译main.c

gcc -o main main.c

测试一把

./main wget www.baidu.com

简单的实现里面没有加上dns解析,./main wget www.baidu.com会失败,后续补上这一块内容(SOCKS5支持域名,实现起来也很容易)

proxychains的源码只有1千多行,有兴趣的可以研究一下。

 类似资料: