proxychains是怎么工作的
proxychains功能
proxychains可以讓命令通過指定的proxy訪問網絡。
例如:
由于防火墻的原因,直接訪問不通。
如果已經有一個代理服務(socks5://127.0.0.1:1080),配置proxychains之后:
可以正常訪問了
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; //versionbuff[1] = 1; //nomber of methodsbuff[2] = 0; // no auth methodif(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; // versionbuff[buff_iter++] = 1; // connectbuff[buff_iter++] = 0; // reservedbuff[buff_iter++] = 1; // ip v4uint32_t ip = addr->sin_addr.s_addr;memcpy(buff + buff_iter, &ip, 4); // dest hostbuff_iter += 4;unsigned short port = addr->sin_port;memcpy(buff + buff_iter, &port, 2); // dest portbuff_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 fdn_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_MACsnprintf(buf, sizeof(buf), "%s/%s", ".", "libmyproxy.so");setenv("LD_PRELOAD", buf, 1); #elsesnprintf(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);*/ #endifexecvp(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是怎么工作的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于Spring boot + Myba
- 下一篇: 如何用TXT文档数据批量生成条形码