Привет, думаю каждый знает утилиту proxychains, но не все могут понимать, как она работает под капотом, как её правильно пользоваться и почему не всегда в паре с ней работает nmap (и не только он).
Итак, Proxychains.
Утилита написана на C и давно не обновлялась. Сейчас используется её форк proxychains4 (или proxychains-ng), который мы и подразумеваем, опуская версию.
С её помощью можно запускать исполняемые файлы, трафик в которых будет перенаправлен через заданный прокси. Стандартный пример - пивотинг во внутреннюю сеть, в которой находится наш socks-прокси. Однако, её гибкий функционал также позволяет создавать и рандомизировать при каждом соединении цепочки прокси-серверов (наподобие сети TOR), для чего она, собственно, и создавалась.
Работает она, используя под капотом хуки API-функций. Да, как антивирусы и EDR. При вызове функции из glibc, например, “connect”, её управление перехватывается. В первом же абзаце README файла говориться, что данный способ буквально является взломом и может не всегда работать корректно.
Проведем в лабораторных условиях эксперимент с и без использования proxychains.
Поставим слушать tcp порт: echo hello | nc -lvnp 4444 и посмотрим на вывод strace:
1# Подключаемся к серверу
2kali$ strace -f -e network nc 127.0.0.1 4444
3strace: Symbol `_UPT_accessors' has different size in shared object, consider re-linking
4socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
5connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
6socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
7connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
8socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
9setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
10setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
11connect(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
12# сообщение от сервера
13hello
14^Cstrace: Process 13556 detached
15
16kali$
Далее, чтобы не использовать сторонние прокси-сервера, будем использовать магию 127.0.0.1. Не все обращают на это внимание, но к виртуальному loopback-интерфейсу привязана целая сеть 127.0.0.0/8. подключимся к себе же по ssh и создадим socks-прокси:
1# обратите внимание на адрес созданного сетевого сокета 127.13.37.1
2kali$ ssh kali@127.0.0.1 -D 127.13.37.1:1080
Поставим этот прокси в конфиге proxychain:
1socks5 127.13.37.1 1080
Смотрим на вывод strace и ищем изменения:
1kali$ strace -f -e network proxychains -q nc 127.0.0.1 4444
2strace: Symbol `_UPT_accessors' has different size in shared object, consider re-linking
3strace: Process 13857 attached
4# "= 3" изменилось на "= 7". Данное значение обозначает номер дескриптора. Все остальное одинаковое
5[pid 13856] socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 7
6[pid 13856] connect(7, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
7[pid 13856] socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 7
8[pid 13856] connect(7, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
9[pid 13856] socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 7
10[pid 13856] setsockopt(7, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
11[pid 13856] setsockopt(7, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
12# А вот тут идет магия proxychains. она перехватывает socket и connect и создаёт свой сокет на socks-прокси сервер
13[pid 13856] getsockopt(7, SOL_SOCKET, SO_TYPE, [1], [4]) = 0
14[pid 13856] socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 8
15# И далее она вызывает свой собственный connect
16[pid 13856] connect(8, {sa_family=AF_INET, sin_port=htons(1080), sin_addr=inet_addr("127.13.37.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
17# А также и getsocksopt, для записи вывода в дескриптор stdout
18[pid 13856] getsockopt(8, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
19hello
20^Cstrace: Process 13856 detached
21strace: Process 13857 detached
22
23kali$
Как мы видим, сравнивая эти 2 вывода, функция connect заменена на новую последовательность вызовов сетевых функций для перенаправления трафика.
Теперь попробуем с UDP-пакетами
Без proxychains
1kali$ strace -f -e network nc -u 127.0.0.1 4444
2strace: Symbol `_UPT_accessors' has different size in shared object, consider re-linking
3socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
4connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
5socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
6connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
7socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
8setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
9setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
10connect(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
11ping
12pong
13^Cstrace: Process 13622 detached
14strace: Process 13622 detached
15
16kali$
Вместе с proxychains
1$ strace -f -e network proxychains -q nc -u 127.0.0.1 4444
2strace: Symbol `_UPT_accessors' has different size in shared object, consider re-linking
3strace: Process 28653 attached
4[pid 28652] socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 7
5[pid 28652] connect(7, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
6[pid 28652] socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 7
7[pid 28652] connect(7, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
8[pid 28652] socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) = 7
9[pid 28652] setsockopt(7, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
10[pid 28652] setsockopt(7, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
11# начинается магия proxychains
12[pid 28652] getsockopt(7, SOL_SOCKET, SO_TYPE, [2], [4]) = 0
13# смотрим на адрес, а он... 127.0.0.1, так и остался без изменений
14[pid 28652] connect(7, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
15ping
16pong
17^Cstrace: Process 28652 detached
18strace: Process 28653 detached
Пупупууу. Трафик не перенаправился через ssh. Вот пруф, показывающий передачу данных в открытом виде, без обёртывания:
Перехват трафика от proxychains -q nc -u 127.0.0.1 4444
Проблема в том, что, даже не смотря на использование socks5, который, в отличие от socks4, поддерживает udp протокол, в proxychains не реализован функционал перехвата udp-трафика. Поэтому, что бы мы с ним не запускали (dig, nmap -sU, и т.д), он не будет должным образом обрабатывать трафик. Только TCP. Хорошим примером будет использования браузера, например, proxychains firefox . Все страницы будут обертываться в socks-прокси, но не dns-запросы. Они будут идти напрямую с нашего устройства, что может сильно нарушить анон***ость.
Собственно, вот мы и подобрались к проблеме проксирования nmap. Флаг -sU, Как мы поняли, в пивотинге не сработает. Похожая ситуация и с флагом -sS
1kali# strace -f -e network nmap -sS 127.0.0.1 -p 4444 -Pn -n --disable-arp
2
3# Для SYN-сканирования используются RAW-сокеты. Я убрал большую часть вызовов для лучшей читаемости
4<SNIP>
5
6Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-05-01 15:57 MSK
7
8<SNIP>
9
10# Тут создается этот "сырой" сокет.
11[pid 43883] socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 4
12[pid 43883] setsockopt(4, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
13
14<SNIP>
15# На этом шаге идёт отправка пакета с SYN флагом
16[pid 43883] sendto(4, "E\0\0,\346W\0\0%\6\261r\177\0\0\1\177\0\0\1\240%\21\\t\252zJ\0\0\0\0"..., 44, 0, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("127.0.0.1")}, 16) = 44
17
18<SNIP>
19
20Nmap scan report for 127.0.0.1
21Host is up (0.00011s latency).
22
23PORT STATE SERVICE
244444/tcp open krb524
25
26<SNIP>
27
28+++ exited with 0 +++
При использовании -sS используются другие системные вызовы, для более гибкой конфигурации пакета, которые proxychains не поддерживает и не изменяет, а вызов “connect” даже не используется. Следовательно, трафик из RAW-сокета также не будет обернут через socks-прокси. Поэтому запуск proxychains nmap от рута и от юзера будет отличатся, так как по умолчанию проверяется права на SYN- , а потом уже на *TCP-*сканирования.
Но это не всё. Так как я упоминал dns, сканирование nmap с использованием доменного имени (например, proxychains nmap -p80 example.com -Pn -n —disable-arp) в качестве скоупа приведет к непредвиденным результатом, обычно, к затупу сканирования. Это происходит, потому, что в proxychains4.conf по умолчанию включен proxy_dns, который некорректно работает с nmap. Предположительно потому, что в nmap реализован свой dns резолвер (но это не точно).
Если закомментировать эту строчку все dns-запросы пойдут через ваше устройство, что может опять же плохо сказать на anonymous и вызовет проблемы при пивонинге.
Также есть вариант вместо proxychains использовать встроенный прокси через --proxy socks4://127.0.0.1:1080, Но в этом случае не поддерживается socks5, а еще я также заметил утечку пакетов. Need Investigate…
Пруф утечки пакетов
На мой взгляд проще всего это фиксится отдельным резолвом домена и дальнейшей передачей ip-адреса в nmap. Ну или ресерчить исходники proxychains и nmap. Если найду что-нибудь, то дополню и оповещу. Спасибо за внимание.
Автор: 🔗@pyfffe
Наш Telegram канал: 🔗REDTalk