Principle of proxychains implementation

Keywords: network Google socket firewall

proxychains function

proxychains allow commands to access the network through the specified proxy.
For example:

wget www.google.com

Due to the firewall, direct access is not available.  
If you already have a proxy service (socks5://127.0.0.1:1080), after configuring proxychains:

proxychains wget www.google.com

It's ready for normal access.

How does proxychains work?

Dynamic Link and LD_PRELOAD

Static links and dynamic links can be seen in detail Here . The simple understanding is:

  • Static links write down the call addresses of all dependent methods at compile time
  • Dynamic links are the dependencies of programs loaded at run time. Dependencies are usually provided by so libraries. Programs dynamically find and load so libraries at run time.

The LD_PRELOAD environment variable allows you to define dynamic link libraries that are loaded first before the program runs.

For example, the main program relies on a.so library, which contains method1() methods. At this point, we write a b.so, also package function signature exactly the same method1() method, and then we specify LD_PRELOAD=b.so, then main program calls method1() in B.So at run time.

Programs in linux environment will eventually use the network functions provided by libc.so at the bottom when accessing the network. For example, TCP protocol will use the connect function to establish TCP connection. If we rewrite the connect function and compile it into whatever.so file, and then set LD_PRELOAD to whatever.so, our program will call our rewritten connect function when establishing TCP connection.

dlsym function

dlsym(dynamic library symbol)

Returns the address corresponding to the symbol according to the handle and symbol of the dynamic link library. Using this function, you can get not only the function address, but also the variable address.

Through dlsym, we can get the address of the real connect function in libc, and then use the real connect function to establish a connection with the proxy server, and send the data package to the proxy server.

dup2 function realizes redirection

In-depth understanding of DUP and dup2 functions can be seen Here

Simply put, dup2 can be used to redirect file descriptor A to file descriptor B. That is to say, all writes to A will eventually be written to B.

Okay, sort out the process.

  • Fake connect function, return file descriptor fd_a
  • Use dlsym to get the real connect function, establish a connection with proxy, and get the file descriptor fd_b
  • Using dup2 to redirect fd_a to fd_b
  • Packets sent to fd_a are sent to proxy
  • Once the data is on proxy, the rest of the task is left to proxy.

A Simple Implementation of proxychains

After understanding proxychains, read the source code of proxychains, you can implement a simple proxychains by yourself. Here is the source code:

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;
}

Compile libproxy.c first

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

Now compiled main.c

gcc -o main main.c

Test one

./main wget www.baidu.com

Simple implementation without dns parsing,. / main wget www.google.com will fail, follow-up to add this piece of content (SOCKS5 support domain name, it is also easy to achieve)

proxychains Source code There are only more than 1,000 lines, which you can study if you are interested.

Posted by birdie on Sun, 28 Jul 2019 00:04:34 -0700