android平台redSocks+ipTable+socks5实现tcp的转发
要求android系统有root权限,可以执行程序,修改iptable。
一、redsocks编译
1. libevent编译
redsocks依赖于libevnet,需要首先编译libevent的android库;
- a. libevent源码下载: wget https://github.com/libevent/libevent/releases/download/release-2.0.22-stable/libevent-2.0.22-stable.tar.gz
- b. 解压进入libevent目录
- c. 执行./configure --prefix=/Users/wangyan/Documents/study/httpproxy/libevent-out --host=arm-linux-androideabi CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++。需要根据自己目录替换prefix、CC和CXX路径;
- d. 修改源码 evutil_rand.c,在最后添加如下代码,由于arc4randomaddrandom没有实现,所以需要注释这段代码才能编译成功;
#ifdef HAVE_ARC4RANDOM_ADDRANDOM
void
evutil_secure_rng_add_bytes(const char *buf, size_t n)
{
arc4random_addrandom((unsigned char*)buf,
n>(size_t)INT_MAX ? INT_MAX : (int)n);
}
#endif
- e. 执行make命令,编译完成后,执行make install,将生产的头文件和动态库install到prefix目录;
- f. 在生成的lib库中修改动态库名字,保证编译redsocks时不链接到动态库就行,因为android系统本身并没有实现libevent库(链接动态库也可以,不过需要把对应的动态库放在android系统下的/system/lib目录)
2. 编译redsocks
- a. redsocks源码下载:wget https://github.com/darkk/redsocks/archive/release-0.5.tar.gz
- b. 在redsocks中修改Makefile,在CFLAGS后面添加编译选项 -fPIE -pie(android系统运行时要求添加的编译选项) -I(添加libevent中生成目录下的include) -L(添加libevent中生成目录下的lib)
e.g. CFLAGS += -fPIE -pie -I/Users/wangyan/Documents/study/httpproxy/libevent-out/include -L/Users/wangyan/Documents/study/httpproxy/libevent-out/lib
- c. 在$(CONF)下面添加自己系统的选项,支持iptable,默认只支持generic,我这里uname系统是Darwin
$(CONF):
@case `uname` in \
Darwin) \
echo "#define USE_IPTABLES" >$(CONF) \
;; \
Linux*) \
echo "#define USE_IPTABLES" >$(CONF) \
;; \
OpenBSD) \
echo "#define USE_PF" >$(CONF) \
;; \
*) \
echo "Unknown system, only generic firewall code is compiled" 1>&2; \
echo "/* Unknown system, only generic firewall code is compiled */" >$(CONF) \
;; \
esac
- d. 添加CC,export CC=arm-linux-androideabi-gcc
- e. 执行make编译生成redsocks可执行文件;
二、转发系统网络数据
- 在pc上面写好配置文件redsocks.conf,其中redsocks配置socks5代理信息,可以直接在pc上面配置socks5代理,这里填写对局域网ip和端口;
base{
log_debug = on;
log_info = on;
log = "file:/sdcard/temp/reddi.log";
daemon = on;
redirector = iptables;
}
redsocks {
local_ip = 127.0.0.1;
local_port = 12345; //记住这个端口,这个是redsocks运行的端口
ip = 192.168.50.233;
port = 20183; //编辑这个端口值,修改为本地socks5运行的端口
type = socks5;
}
- 首先用adb push redsocks /sdcard/,同样把redsocks.conf也push到sdcard下,然后adb shell进入android系统shell进入root用户下,运行redsocks。
cd /data/
mkdir temp
mv /sdcard/redsocks .
mv /sdcard/redsocks.conf .
chmod +x redsocks
./redsocks -c redsocks.conf
- 使用iptables对tcp实现透明代理
#不重定向目的地址为服务器的包
iptables -t nat -A OUTPUT -d 192.168.50.233 -j RETURN
#不重定向私有地址的流量
iptables -t nat -A OUTPUT -d 10.0.0.0/8 -j RETURN
iptables -t nat -A OUTPUT -d 172.16.0.0/16 -j RETURN
iptables -t nat -A OUTPUT -d 192.168.0.0/16 -j RETURN
#不重定向保留地址的流量,这一步很重要
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN
#重定向所有不满足以上条件的流量到redsocks监听的12345端口
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 12345
#保存iptables配置
iptables-save > proxy.iptablesv
#回复iptables配置
iptables-restore proxy.iptables
三、配置socks5接收转发包
- 在github上面找到一个轻量级的socks5转发,方便读懂代码根据自己需求对转发过来的包进行随机丢弃或者屏蔽等。https://github.com/fgssfgss/socks_proxy.git。
- 需要根据redsocks设置的port修改socks5的对应端口号,我这里是20183;
- gcc -o socks5 socks5.c直接手动编译
#include <sys/types.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <netdb.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <pthread.h>
#define BUFSIZE 65536
#define IPSIZE 4
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
unsigned short int port = 1080;
int auth_type;
char *arg_username;
char *arg_password;
FILE *log_file;
pthread_mutex_t lock;
enum socks {
RESERVED = 0x00,
VERSION = 0x05
};
enum socks_auth_methods {
NOAUTH = 0x00,
USERPASS = 0x02,
NOMETHOD = 0xff
};
enum socks_auth_userpass {
AUTH_OK = 0x00,
AUTH_VERSION = 0x01,
AUTH_FAIL = 0xff
};
enum socks_command {
CONNECT = 0x01
};
enum socks_command_type {
IP = 0x01,
DOMAIN = 0x03
};
enum socks_status {
OK = 0x00,
FAILED = 0x05
};
void log_message(const char *message, ...)
{
char vbuffer[255];
va_list args;
va_start(args, message);
vsnprintf(vbuffer, ARRAY_SIZE(vbuffer), message, args);
va_end(args);
time_t now;
time(&now);
char *date = ctime(&now);
date[strlen(date) - 1] = '';
if (errno != 0) {
pthread_mutex_lock(&lock);
fprintf(log_file, "[%s] Critical: %s - %s\n", date, vbuffer,
strerror(errno));
errno = 0;
pthread_mutex_unlock(&lock);
} else {
fprintf(log_file, "[%s] Info: %s\n", date, vbuffer);
}
fflush(log_file);
}
int readn(int fd, void *buf, int n)
{
int nread, left = n;
while (left > 0) {
if ((nread = read(fd, buf, left)) == 0) {
return 0;
} else {
left -= nread;
buf += nread;
}
}
return n;
}
void app_thread_exit(int ret, int fd)
{
close(fd);
pthread_exit((void *)&ret);
}
int app_connect(int type, void *buf, unsigned short int portnum)
{
int fd;
struct sockaddr_in remote;
char address[16];
memset(address, 0, ARRAY_SIZE(address));
fd = socket(AF_INET, SOCK_STREAM, 0);
if (type == IP) {
char *ip = buf;
snprintf(address, ARRAY_SIZE(address), "%hhu.%hhu.%hhu.%hhu",
ip[0], ip[1], ip[2], ip[3]);
memset(&remote, 0, sizeof(remote));
remote.sin_family = AF_INET;
remote.sin_addr.s_addr = inet_addr(address);
remote.sin_port = htons(portnum);
if (connect(fd, (struct sockaddr *)&remote, sizeof(remote)) < 0) {
log_message("connect() in app_connect");
return -1;
}
return fd;
} else if (type == DOMAIN) {
char portaddr[6];
struct addrinfo *res;
snprintf(portaddr, ARRAY_SIZE(portaddr), "%d", portnum);
log_message("getaddrinfo: %s %s", (char *)buf, portaddr);
int ret = getaddrinfo((char *)buf, portaddr, NULL, &res);
if (ret == 0) {
const struct addrinfo *r;
for (r = res; r != NULL || ret != 0; r = r->ai_next) {
ret =
connect(fd, res->ai_addr, res->ai_addrlen);
if (ret == 0) {
freeaddrinfo(res);
return fd;
}
}
}
freeaddrinfo(res);
return -1;
}
}
int socks5_invitation(int fd)
{
char init[2];
readn(fd, (void *)init, ARRAY_SIZE(init));
if (init[0] != VERSION) {
log_message("Incompatible version!");
app_thread_exit(0, fd);
}
log_message("Initial %hhX %hhX", init[0], init[1]);
return init[1];
}
char *socks5_auth_get_user(int fd)
{
unsigned char size;
readn(fd, (void *)&size, sizeof(size));
char *user = malloc(sizeof(char) * size + 1);
readn(fd, (void *)user, (int)size);
user[size] = 0;
return user;
}
char *socks5_auth_get_pass(int fd)
{
unsigned char size;
readn(fd, (void *)&size, sizeof(size));
char *pass = malloc(sizeof(char) * size + 1);
readn(fd, (void *)pass, (int)size);
pass[size] = 0;
return pass;
}
int socks5_auth_userpass(int fd)
{
char answer[2] = { VERSION, USERPASS };
write(fd, (void *)answer, ARRAY_SIZE(answer));
char resp;
readn(fd, (void *)&resp, sizeof(resp));
log_message("auth %hhX", resp);
char *username = socks5_auth_get_user(fd);
char *password = socks5_auth_get_pass(fd);
log_message("l: %s p: %s", username, password);
if (strcmp(arg_username, username) == 0
&& strcmp(arg_password, password) == 0) {
char answer[2] = { AUTH_VERSION, AUTH_OK };
write(fd, (void *)answer, ARRAY_SIZE(answer));
free(username);
free(password);
return 0;
} else {
char answer[2] = { AUTH_VERSION, AUTH_FAIL };
write(fd, (void *)answer, ARRAY_SIZE(answer));
free(username);
free(password);
return 1;
}
}
int socks5_auth_noauth(int fd)
{
char answer[2] = { VERSION, NOAUTH };
write(fd, (void *)answer, ARRAY_SIZE(answer));
return 0;
}
void socks5_auth_notsupported(int fd)
{
char answer[2] = { VERSION, NOMETHOD };
write(fd, (void *)answer, ARRAY_SIZE(answer));
}
void socks5_auth(int fd, int methods_count)
{
int supported = 0;
int num = methods_count;
for (int i = 0; i < num; i++) {
char type;
readn(fd, (void *)&type, 1);
log_message("Method AUTH %hhX", type);
if (type == auth_type) {
supported = 1;
}
}
if (supported == 0) {
socks5_auth_notsupported(fd);
app_thread_exit(1, fd);
}
int ret = 0;
switch (auth_type) {
case NOAUTH:
ret = socks5_auth_noauth(fd);
break;
case USERPASS:
ret = socks5_auth_userpass(fd);
break;
}
if (ret == 0) {
return;
} else {
app_thread_exit(1, fd);
}
}
int socks5_command(int fd)
{
char command[4];
readn(fd, (void *)command, ARRAY_SIZE(command));
log_message("Command %hhX %hhX %hhX %hhX", command[0], command[1],
command[2], command[3]);
return command[3];
}
unsigned short int socks5_read_port(int fd)
{
unsigned short int p;
readn(fd, (void *)&p, sizeof(p));
log_message("Port %hu", ntohs(p));
return p;
}
char *socks5_ip_read(int fd)
{
char *ip = malloc(sizeof(char) * IPSIZE);
readn(fd, (void *)ip, IPSIZE);
log_message("IP %hhu.%hhu.%hhu.%hhu", ip[0], ip[1], ip[2], ip[3]);
return ip;
}
void socks5_ip_send_response(int fd, char *ip, unsigned short int port)
{
char response[4] = { VERSION, OK, RESERVED, IP };
write(fd, (void *)response, ARRAY_SIZE(response));
write(fd, (void *)ip, IPSIZE);
write(fd, (void *)&port, sizeof(port));
}
char *socks5_domain_read(int fd, unsigned char *size)
{
unsigned char s;
readn(fd, (void *)&s, sizeof(s));
char *address = malloc((sizeof(char) * s) + 1);
readn(fd, (void *)address, (int)s);
address[s] = 0;
log_message("Address %s", address);
*size = s;
return address;
}
void socks5_domain_send_response(int fd, char *domain, unsigned char size,
unsigned short int port)
{
char response[4] = { VERSION, OK, RESERVED, DOMAIN };
write(fd, (void *)response, ARRAY_SIZE(response));
write(fd, (void *)&size, sizeof(size));
write(fd, (void *)domain, size * sizeof(char));
write(fd, (void *)&port, sizeof(port));
}
void app_socket_pipe(int fd0, int fd1)
{
int maxfd, ret;
fd_set rd_set;
size_t nread;
char buffer_r[BUFSIZE];
maxfd = (fd0 > fd1) ? fd0 : fd1;
while (1) {
FD_ZERO(&rd_set);
FD_SET(fd0, &rd_set);
FD_SET(fd1, &rd_set);
ret = select(maxfd + 1, &rd_set, NULL, NULL, NULL);
if (ret < 0 && errno == EINTR) {
continue;
}
if (FD_ISSET(fd0, &rd_set)) {
nread = recv(fd0, buffer_r, BUFSIZE, 0);
if (nread <= 0)
break;
send(fd1, (const void *)buffer_r, nread, 0);
}
if (FD_ISSET(fd1, &rd_set)) {
nread = recv(fd1, buffer_r, BUFSIZE, 0);
if (nread <= 0)
break;
send(fd0, (const void *)buffer_r, nread, 0);
}
}
}
void *app_thread_process(void *fd)
{
int net_fd = *(int *)fd;
char auth_methods = socks5_invitation(net_fd);
socks5_auth(net_fd, auth_methods);
int command = socks5_command(net_fd);
int inet_fd = -1;
if (command == IP) {
char *ip = socks5_ip_read(net_fd);
unsigned short int p = socks5_read_port(net_fd);
inet_fd = app_connect(IP, (void *)ip, ntohs(p));
if (inet_fd == -1) {
app_thread_exit(1, net_fd);
}
socks5_ip_send_response(net_fd, ip, p);
free(ip);
} else if (command == DOMAIN) {
unsigned char size;
char *address = socks5_domain_read(net_fd, &size);
unsigned short int p = socks5_read_port(net_fd);
inet_fd = app_connect(DOMAIN, (void *)address, ntohs(p));
if (inet_fd == -1) {
app_thread_exit(1, net_fd);
}
socks5_domain_send_response(net_fd, address, size, p);
free(address);
}
app_socket_pipe(inet_fd, net_fd);
close(inet_fd);
app_thread_exit(0, net_fd);
}
int app_loop()
{
int sock_fd, net_fd, pid;
int optval = 1;
struct sockaddr_in local, remote;
socklen_t remotelen;
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
log_message("socket()");
exit(1);
}
if (setsockopt
(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval,
sizeof(optval)) < 0) {
log_message("setsockopt()");
exit(1);
}
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(port);
if (bind(sock_fd, (struct sockaddr *)&local, sizeof(local)) < 0) {
log_message("bind()");
exit(1);
}
if (listen(sock_fd, 5) < 0) {
log_message("listen()");
exit(1);
}
remotelen = sizeof(remote);
memset(&remote, 0, sizeof(remote));
log_message("Listening port %d...", port);
pthread_t worker;
while (1) {
if ((net_fd =
accept(sock_fd, (struct sockaddr *)&remote,
&remotelen)) < 0) {
log_message("accept()");
exit(1);
}
if (pthread_create(&worker, NULL, &app_thread_process, (void *)&net_fd) == 0) {
pthread_detach(worker);
} else {
log_message("pthread_create()");
}
}
}
void usage(char *app)
{
printf
("USAGE: %s [-h][-n PORT][-a AUTHTYPE][-u USERNAME][-p PASSWORD][-l LOGFILE]\n",
app);
printf("AUTHTYPE: 0 for NOAUTH, 2 for USERPASS\n");
printf
("By default: port is 1080, authtype is no auth, logfile is stdout\n");
exit(1);
}
int main(int argc, char *argv[])
{
int ret;
log_file = stdout;
auth_type = NOAUTH;
arg_username = "user";
arg_password = "pass";
pthread_mutex_init(&lock, NULL);
while ((ret = getopt(argc, argv, "n:u:p:l:a:h")) != -1) {
switch (ret) {
case 'n':{
port = atoi(optarg) & 0xffff;
break;
}
case 'u':{
arg_username = strdup(optarg);
break;
}
case 'p':{
arg_password = strdup(optarg);
break;
}
case 'l':{
freopen(optarg, "wa", log_file);
break;
}
case 'a':{
auth_type = atoi(optarg);
break;
}
case 'h':
default:
usage(argv[0]);
}
}
log_message("Starting with authtype %X", auth_type);
if (auth_type != NOAUTH) {
log_message("Username is %s, password is %s", arg_username,
arg_password);
}
app_loop();
return 0;
}