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#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*#include */#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*//*#include *//*#include */#include #include /*#include */#include /*#include */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千多行,有兴趣的可以研究一下。