ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

proxychains 实现原理

2019-07-28 14:35:29  阅读:550  来源: 互联网

标签:proxychains addr 实现 int so connect 原理 include buff


proxychains功能

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

wget www.google.com

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

proxychains wget www.google.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.google.com会失败,后续补上这一块内容(SOCKS5支持域名,实现起来也很容易)

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

标签:proxychains,addr,实现,int,so,connect,原理,include,buff
来源: https://blog.csdn.net/soralaro/article/details/97614227

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有