proxychains可以让命令通过指定的proxy访问网络。
例如:
wget www.baidu.com
由于防火墙的原因,直接访问不通。
如果已经有一个代理服务(socks5://127.0.0.1:1080),配置proxychains之后:
proxychians wget www.baidu.com
可以正常访问了
动态链接与LD_PRELOAD
静态链接与动态链接具体的可以看这里。简单理解就是:
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。
好了,梳理一下流程
简易的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千多行,有兴趣的可以研究一下。