Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ SHARED_LIBS = $(LDSO_PATHNAME)
ALL_LIBS = $(SHARED_LIBS)
PXCHAINS = proxychains4
PXCHAINS_D = proxychains4-daemon
PXCHAINS_CLI = src/proxychains-cli
ALL_TOOLS = $(PXCHAINS) $(PXCHAINS_D)
ALL_CONFIGS = src/proxychains.conf
ZSH_COMPLETION = completions/zsh/_proxychains4
ZSH_COMPLETION = completions/zsh/_proxychains4 completions/zsh/_proxychains-cli

-include config.mak

Expand All @@ -74,10 +75,13 @@ $(DESTDIR)$(zshcompletiondir)/%: completions/zsh/%
$(INSTALL) -D -m 644 $< $@

install-libs: $(ALL_LIBS:%=$(DESTDIR)$(libdir)/%)
install-tools: $(ALL_TOOLS:%=$(DESTDIR)$(bindir)/%)
install-tools: $(ALL_TOOLS:%=$(DESTDIR)$(bindir)/%) $(DESTDIR)$(bindir)/proxychains-cli
install-config: $(ALL_CONFIGS:src/%=$(DESTDIR)$(sysconfdir)/%)
install-zsh-completion: $(ZSH_COMPLETION:completions/zsh/%=$(DESTDIR)$(zshcompletiondir)/%)

$(DESTDIR)$(bindir)/proxychains-cli: $(PXCHAINS_CLI)
$(INSTALL) -D -m 755 $< $@

clean:
rm -f $(ALL_LIBS)
rm -f $(ALL_TOOLS)
Expand All @@ -102,5 +106,4 @@ $(PXCHAINS): $(OBJS)
$(PXCHAINS_D): $(DOBJS)
$(CC) $^ $(FAT_BIN_LDFLAGS) $(USER_LDFLAGS) -o $@


.PHONY: all clean install install-config install-libs install-tools install-zsh-completion
.PHONY: all clean install install-config install-libs install-tools install-zsh-completion uninstall
243 changes: 243 additions & 0 deletions README-CLI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# proxychains-cli

A CLI wrapper for `proxychains4` that generates configuration dynamically without needing a config file.

## Installation

After building proxychains-ng:
```bash
make install
```

The `proxychains-cli` script is installed to the same location as `proxychains4`.

## Basic Usage

```bash
proxychains-cli [options] program [args...]
```

## Options

| Option | Default | Description |
|---------------------------------------|--------------|-----------------------------------------------|
| `-h, --help` | | Print help and exit |
| `-v, --version` | | Print version and exit |
| `-c, --chain MODE` | strict | Chain mode: strict/dynamic/random/round_robin |
| `-l, --chain-len N` | | Chain length (for random/round_robin) |
| `-q, --quiet` | | Quiet mode |
| `-d, --dns MODE` | proxy | DNS: proxy/old/off/IP:PORT |
| `-S, --dns-subnet N` | 224 | Remote DNS subnet (0-255) |
| `-R, --read-timeout MS` | 15000 | TCP read timeout (ms) |
| `-T, --connect-timeout MS` | 8000 | TCP connect timeout (ms) |
| `-n, --localnet CIDR` | | Bypass proxy for CIDR (repeatable) |
| `--dnat "SRC DST"` | | DNAT mapping (repeatable) |
| `-P, --proxy "IP PORT [ USER PASS ]"` | | Add proxy (repeatable) |
| `--config` | | Print config and exit |
| `--proxychains-path PATH` | proxychains4 | Path to proxychains4 binary |

## Examples

### Example 1: Basic SOCKS5 proxy

```bash
proxychains-cli -P "socks5 127.0.0.1 1080" curl example.com
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo we should only advertise protocol://[user:pass@]ip:port syntax in the cli tool

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok lets implement this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rofl0r should cli resolve hostname if URL using hostname:port instead of ip:port?

```

**Config passed to proxychains4 (fd 3):**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which fd it's using is irrelevant to a user, it's an implementation detail

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to update this README, when using fd, I can not pass content to child process in this case:

1. main.c exec node (nodejs) with PROXYCHAINS_CONFIG_FD=3
   ↓
2. dyld loads libproxychains4.dylib (DYLD_INSERT_LIBRARIES)
   ↓
3. gcc_init() constructor run BEFORE main()
   ↓
4. do_init() → get_chain_data() → setenv("PROXYCHAINS_CONFIG_FD", "5", 1) (nodejs used fd 3, 4 => we use 5)
   ↓
5. [proxychains] DLL init: proxychains-ng...  ← log printed
   ↓
6. node main() started
   ↓  
7. Debugger listening...  ← node start debugger
   ↓
8. [proxychains] failed to read... ← debugger subprocess still see PROXYCHAINS_CONFIG_FD is 3

it happened because

┌─────────────────────────────────────────────────────────────┐
│ Library loading (before main)                               │
├─────────────────────────────────────────────────────────────┤
│ 1. libc.dylib loads                                         │
│ 2. libnode.dylib loads ──► Node's C++ static init           │
│    └── NodeJS snapshot environment HERE!                    │
│ 3. libproxychains4.dylib loads                              │
│    └── run, clone fd, write new value to env but late       │
└─────────────────────────────────────────────────────────────┘
                    ↓
              main() starts

ofcourse I can use a larger fd() but not sure that program dont use that fd
=> I decided to use env PROXYCHAINS_CONFIG_DATA_ENV_VAR to handle config data
will update README

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean the issue is the quiet mode ? just pass the right env var just like main.c does then

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not related to quiet mode.
proxychains4 uses env var to pass data to libproxychains, which is injected and runs before the program's main().
Everything works when the program doesn't spawn child processes, example: I'm using fd3 with PROXYCHAINS_DATA_FD=3.
But when the main program creates child processes, it may forward fd3/fd4 to them. In that case, I must switch libproxychains to use fd5 to avoid conflict with the child process behavior, and update the env to PROXYCHAINS_DATA_FD=5.
The issue when use proxychains to run Node.js (node) in debug mode:
node takes a snapshot of all env at startup and uses that snapshot for all child processes. As a result, child processes still receive PROXYCHAINS_DATA_FD=3 instead of 5, causing parsing errors.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see. will think about the best solution for that.

```
strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
socks5 127.0.0.1 1080
```

### Example 2: Proxy with authentication

```bash
proxychains-cli -P "socks5 127.0.0.1 1080 myuser mypass" wget file.tar.gz
```

**Config (fd 3):**
```
strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
socks5 127.0.0.1 1080 myuser mypass
```

### Example 3: Multiple proxies (chain)

```bash
proxychains-cli \
-c dynamic \
-P "socks5 10.0.0.1 1080" \
-P "http 10.0.0.2 8080" \
curl example.com
```

**Config (fd 3):**
```
dynamic_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
socks5 10.0.0.1 1080
http 10.0.0.2 8080
```

### Example 4: Random chain with length

```bash
proxychains-cli \
-c random \
-l 2 \
-P "socks5 10.0.0.1 1080" \
-P "socks5 10.0.0.2 1080" \
-P "socks5 10.0.0.3 1080" \
firefox
```

**Config (fd 3):**
```
random_chain
chain_len = 2
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
socks5 10.0.0.1 1080
socks5 10.0.0.2 1080
socks5 10.0.0.3 1080
```

### Example 5: With localnet bypass

```bash
proxychains-cli \
-n 192.168.0.0/16 \
-n 10.0.0.0/8 \
-P "socks5 proxy.example.com 1080" \
ssh server.local
```

**Config (fd 3):**
```
strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
localnet 192.168.0.0/16
localnet 10.0.0.0/8
[ProxyList]
socks5 proxy.example.com 1080
```

### Example 6: Custom timeouts and DNS

```bash
proxychains-cli \
-q \
-d old \
-R 30000 \
-T 15000 \
-P "socks5 127.0.0.1 9050" \
curl example.com
```

**Config (fd 3):**
```
strict_chain
quiet_mode
proxy_dns_old
remote_dns_subnet 224
tcp_read_time_out 30000
tcp_connect_time_out 15000
[ProxyList]
socks5 127.0.0.1 9050
```

### Example 7: DNAT mapping

```bash
proxychains-cli \
--dnat "1.1.1.1 2.2.2.2" \
--dnat "8.8.8.8:53 10.0.0.1:5353" \
-P "socks5 127.0.0.1 1080" \
curl 1.1.1.1
```

**Config (fd 3):**
```
strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
dnat 1.1.1.1 2.2.2.2
dnat 8.8.8.8:53 10.0.0.1:5353
[ProxyList]
socks5 127.0.0.1 1080
```

## Special Cases

### Special characters in username/password

Use shell quoting to handle special characters:

```bash
# Password contains single quote: pass'word
proxychains-cli -P "socks5 127.0.0.1 1080 user pass'word" curl example.com

# Password contains double quote: pass"word
proxychains-cli -P 'socks5 127.0.0.1 1080 user pass"word' curl example.com

# Both quotes: user'name and pass"word
proxychains-cli -P $'socks5 127.0.0.1 1080 user\'name pass"word' curl example.com
```

### Viewing generated config

Use `--config` to print the configuration without running:

```bash
proxychains-cli -P "socks5 127.0.0.1 1080" --config
```

### Using different proxychains4 binary

```bash
proxychains-cli \
--proxychains-path /opt/proxychains/bin/proxychains4 \
-P "socks5 127.0.0.1 1080" \
curl example.com
```

## How it works

1. `proxychains-cli` parses command-line options
2. Generates a proxychains.conf format configuration
3. Passes the config to `proxychains4` via file descriptor 3
4. `proxychains4` reads from `PROXYCHAINS_CONFIG_FD=3` instead of a file
5. The target program is executed with proxy hooks

No temporary files are created.

## Zsh Completion

Copy `completions/zsh/_proxychains-cli` to your zsh completions directory:

```bash
cp completions/zsh/_proxychains-cli /usr/local/share/zsh/site-functions/
```
26 changes: 26 additions & 0 deletions completions/zsh/_proxychains-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#compdef proxychains-cli

_proxychains_cli() {
local curcontext="$curcontext" state line
typeset -A opt_args

_arguments -C \
'(-c --chain)'{-c,--chain}'[Chain mode]:mode:(strict dynamic random round_robin)' \
'(-l --chain-len)'{-l,--chain-len}'[Chain length for random/round_robin]:length:' \
'(-q --quiet)'{-q,--quiet}'[Quiet mode]' \
'(-d --dns)'{-d,--dns}'[DNS mode]:mode:(proxy old off)' \
'(-S --dns-subnet)'{-S,--dns-subnet}'[Remote DNS subnet (0-255)]:subnet:' \
'(-R --read-timeout)'{-R,--read-timeout}'[TCP read timeout in ms]:ms:' \
'(-T --connect-timeout)'{-T,--connect-timeout}'[TCP connect timeout in ms]:ms:' \
'*'{-n,--localnet}'[Bypass proxy for CIDR]:cidr:' \
'*--dnat[DNAT mapping "SRC DST"]:mapping:' \
'*'{-P,--proxy}'[Add proxy "TYPE IP PORT [USER PASS]"]:proxy:' \
'--config[Print generated config and exit]' \
'--proxychains-path[Path to proxychains4]:path:_files' \
'(-h --help)'{-h,--help}'[Show help]' \
'(-v --version)'{-v,--version}'[Show version]' \
'(-):command:_command_names -e' \
'*::arguments:_normal'
}

_proxychains_cli "$@"
1 change: 1 addition & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#define PROXYCHAINS_CONF_FILE_ENV_VAR "PROXYCHAINS_CONF_FILE"
#define PROXYCHAINS_QUIET_MODE_ENV_VAR "PROXYCHAINS_QUIET_MODE"
#define PROXYCHAINS_CONFIG_DATA_ENV_VAR "PROXYCHAINS_CONFIG_DATA"
#define PROXYCHAINS_CONF_FILE "proxychains.conf"
#define LOG_PREFIX "[proxychains] "
#ifndef SYSCONFDIR
Expand Down
24 changes: 18 additions & 6 deletions src/libproxychains.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,24 @@ static void get_chain_data(proxy_data * pd, unsigned int *proxy_count, chain_typ
tcp_connect_time_out = 10 * 1000;
*ct = DYNAMIC_TYPE;

env = get_config_path(getenv(PROXYCHAINS_CONF_FILE_ENV_VAR), buf, sizeof(buf));
if( ( file = fopen(env, "r") ) == NULL )
{
perror("couldnt read configuration file");
exit(1);
}
/* Try to get config from environment variable first */
char *config_data = getenv(PROXYCHAINS_CONFIG_DATA_ENV_VAR);
if (config_data && *config_data) {
/* Config data in env - use fmemopen to read it */
file = fmemopen(config_data, strlen(config_data), "r");
if (!file) {
fprintf(stderr, LOG_PREFIX "failed to open config from environment\n");
exit(1);
}
}
if (!file) {
env = get_config_path(getenv(PROXYCHAINS_CONF_FILE_ENV_VAR), buf, sizeof(buf));
file = fopen(env, "r");
}
if (!file) {
perror("couldnt read configuration file");
exit(1);
}

while(fgets(buf, sizeof(buf), file)) {
buff = buf;
Expand Down
17 changes: 11 additions & 6 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,19 @@ int main(int argc, char *argv[]) {
if(start_argv >= argc)
return usage(argv);

/* check if path of config file has not been passed via command line */
path = get_config_path(path, pbuf, sizeof(pbuf));
/* Handle config: check if config data is already in env, otherwise read from file */
char *config_data = getenv(PROXYCHAINS_CONFIG_DATA_ENV_VAR);

if(!quiet)
fprintf(stderr, LOG_PREFIX "config file found: %s\n", path);
if (!config_data || !*config_data) {
/* No config data in env - read from file */
path = get_config_path(path, pbuf, sizeof(pbuf));

if(!quiet)
fprintf(stderr, LOG_PREFIX "config file found: %s\n", path);

/* Set PROXYCHAINS_CONF_FILE to get proxychains lib to use new config file. */
setenv(PROXYCHAINS_CONF_FILE_ENV_VAR, path, 1);
/* Set PROXYCHAINS_CONF_FILE to get proxychains lib to use new config file. */
setenv(PROXYCHAINS_CONF_FILE_ENV_VAR, path, 1);
}

if(quiet)
setenv(PROXYCHAINS_QUIET_MODE_ENV_VAR, "1", 1);
Expand Down
Loading