В арсе­нале безопас­ников — мно­жес­тво инс­тру­мен­тов для ана­лиза сетевой активнос­ти и выяв­ления атак, но иног­да мож­но обой­тись без них и получить не менее впе­чат­ляющие резуль­таты. В этой статье мы раз­берем, как по одно­му лишь сетево­му тра­фику вос­ста­новить пор­трет ата­кующе­го: понять, отку­да он при­шел, какой тех­никой поль­зует­ся и как ведет раз­ведку.

Мы прос­ледим, какие сиг­налы выда­ет сам хакер, как их интер­пре­тиро­вать и почему подоб­ные зна­ния важ­ны не мень­ше, чем уме­ние вов­ремя заметить попыт­ку взло­ма.

Enumeration — это искусс­тво сбо­ра информа­ции с целей. Спо­соб­ность узнать дос­таточ­но неоче­вид­ные вещи. Кру­пицы информа­ции, которые потом скла­дыва­ются в общую кар­тину и фор­миру­ют перед ата­кующим ту самую повер­хность атак, над которой ему затем и пред­сто­ит работать.

Тра­дици­онно enumeration счи­тал­ся хакер­ским ремес­лом. Но можем ли мы при­менить его в обратную сто­рону — про­тив хакеров? Встреч­ная раз­ведка ата­кующе­го может ока­зать­ся весь­ма полез­ной в опре­делен­ных обсто­ятель­ствах. И в этой статье мы, подоб­но Шер­локу Хол­мсу, сос­тавим при­мер­ный пор­трет ата­кующе­го, опи­раясь лишь на осо­бен­ности его тра­фика.

В сетевом тра­фике поч­ти каж­дой опе­раци­онной сис­темы скры­то мно­жес­тво сек­ретов. И в глу­бине сетевых пакетов мож­но отыс­кать очень инте­рес­ные поля, спо­соб­ные рас­крыть мас­су неоче­вид­ных све­дений об источни­ке тра­фика, о которых мало кто догады­вает­ся...

 

Узнаём, что ищет хакер

Лю­бая тар­гетиро­ван­ная хакер­ская ата­ка всег­да начина­ется с раз­ведки. И пер­вое, с чем стол­кнет­ся целевая сис­тема при попыт­ке про­ник­новения, — это ска­ниро­вание сетевых пор­тов. Но само по себе ска­ниро­вание пор­тов бес­полез­но без иден­тифика­ции сер­висов, прос­лушива­ющих эти самые пор­ты. Ведь имен­но в этих сер­висах хакеру и пред­сто­ит искать уяз­вимос­ти.

С помощью отправ­ки осо­бых сетевых пакетов хакер может вынуж­дать те или иные служ­бы отве­чать соот­ветс­тву­ющим обра­зом, тем самым выдавая тип служ­бы или про­токол.

Определение типичных служб на удаленном сервере
Оп­ределе­ние типич­ных служб на уда­лен­ном сер­вере

В этой области все неиз­менно уже нес­коль­ко десяти­летий, и с этой задачей все еще прек­расно справ­ляет­ся сетевой ска­нер Nmap, име­ющий богатую базу фин­гер­прин­тов.

Nmap использовали еще в далеком 1999-м, в фильме «Матрица»
Nmap исполь­зовали еще в далеком 1999-м, в филь­ме «Мат­рица»

Но что, если мы воору­жим­ся этой же самой базой и будем све­рять все при­ходя­щие сетевые пакеты и опре­делять, на какой про­токол или служ­бу они боль­ше все­го похожи? Так мы смо­жем понять, что пыта­ется най­ти хакер.

info

Эта статья осно­вана на фраг­менте кни­ги «Ха­кер­ская само­обо­рона. При­емы обна­руже­ния и пре­дот­вра­щения хакер­ских атак», которая сей­час готовит­ся к пуб­ликации.

Итак, давай напишем прос­тей­ший TCP- или UDP-лис­тенер, который будет прос­то слу­шать ука­зан­ный порт и мол­ча при­нимать дан­ные. Базу дан­ных Nmap, по которой мы будем находить соот­ветс­твие меж­ду при­няты­ми дан­ными и тем или иным про­токо­лом, мы можем пар­сить сле­дующим незамыс­ловатым обра­зом:

defence/proto.py
import socket
import difflib
probes = {}
with open("/usr/share/nmap/nmap-service-probes") as f:
#https://github.com/boy-hack/nmap-parser
for line in f.readlines():
line = line.strip()
if line.startswith("Probe "):
protocol = line[6:9]
if protocol not in ["TCP", "UDP"]:
continue
probename_start = 10
probename_end = line.index(" ", probename_start)
if probename_end - probename_start <= 0:
continue
probename = line[probename_start:probename_end]
probestring_start = line.index("q|", probename_end) + 1
probestring = line[probestring_start:].strip("|")
probes[probename] = probestring.encode().decode('unicode-escape').encode()
def get_nmap_probe(buf):
best_match = 0
probename = None
for probe in probes:
matcher = difflib.SequenceMatcher(a=probes[probe], b=buf)
match = matcher.find_longest_match(0, len(matcher.a), 0, len(matcher.b))
if match.size/len(buf) > best_match:
best_match = match.size/len(buf)
probename = probe
#matcher.a[match.a:match.a+match.size]
return probename,best_match
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", int(port)))
s.listen(10)
while True:
c,info = s.accept()
try:
buf = c.recv(1024)
probe,match = get_nmap_probe(packet)
except:
pass
c.close()

При этом мы дол­жны понимать, что стоп­роцен­тно­го соот­ветс­твия меж­ду при­нима­емы­ми бай­тами может и не быть. Поэто­му искать будем то, что наибо­лее похоже на тот или иной сетевой пакет, и выводить резуль­тат в про­цен­тном соот­ношении. Вклю­чим так­же в код GeoIP и Whois:

from geolite2 import geolite2 #pip3 install maxminddb-geolite2
geoip = geolite2.reader()
result = geoip.get(IP)
country = result['country']['names']['ru']
city = result['city']['names']['ru']
from ipwhois import IPWhois #pip3 install ipwhois
result = IPWhois(IP).lookup_whois()
netname = result['nets'][0]['name']
descr = result['nets'][0]['description']

В ито­ге, если этот лис­тенер кто‑то прос­каниру­ет, мы уви­дим все его попыт­ки обна­руже­ния тех или иных про­токо­лов.

Сходство входящих пакетов с известными протоколами (один порт)
Сходс­тво вхо­дящих пакетов с извес­тны­ми про­токо­лами (один порт)

Дос­таточ­но высокая сте­пень сходс­тва говорит о том, что ска­ниро­вали нас, ско­рее все­го, с помощью Nmap либо похожим ска­нером, исполь­зующим ту же базу фин­гер­прин­тов.

До сих пор мы слу­шали один порт, но хакеры ска­ниру­ют мно­жес­тво пор­тов. С помощью сле­дующих пра­вил меж­сетево­го экра­на мы можем завер­нуть все неис­поль­зуемые сетевые пор­ты в этот лис­тенер:

iptables -t nat -I PREROUTING -i eth0 -p tcp --dport 22 -j REDIRECT --to-ports 22 # Исключение
iptables -t nat -A PREROUTING -i eth0 -p tcp -m conntrack --ctstate NEW -j REDIRECT --to-ports 1234

Пос­ле того как мы изме­нили порт наз­начения и перенап­равили под­клю­чение на лис­тенер, нам нуж­но вос­ста­новить исходный номер пор­та. Опре­делить исходный номер пор­та на сокете, что был до редирек­та, мож­но с помощью такой фун­кции:

import struct
def get_original_dst(sock):
try:
sockaddr_in = sock.getsockopt(socket.SOL_IP, 80, 16)
(proto, port, a,b,c,d) = struct.unpack("!HHBBBB", sockaddr_in[:8])
dst_ip = "%d.%d.%d.%d" % (a,b,c,d)
dst_port = port
return (dst_ip, dst_port)
except:
pass

Пос­ле чего мы поч­ти в ту же секун­ду нач­нем видеть, какие служ­бы и про­токо­лы пыта­ются най­ти на нашем сер­вере уже на дру­гих пор­тах.

Сходство входящих пакетов с известными протоколами (все порты)
Сходс­тво вхо­дящих пакетов с извес­тны­ми про­токо­лами (все пор­ты)

Это может показать­ся уди­витель­ным — как минуту назад еще зак­рытые пор­ты теперь ста­ли кем‑то иссле­довать­ся. Имен­но так и выг­лядит тем­ный тра­фик в интерне­те.

Од­нако боль­шая часть сетевой активнос­ти, с которой мож­но стол­кнуть­ся, — это не реаль­ные хакеры, а лишь боты — прог­рам­мные роботы, ищу­щие что‑то в интерне­те. Воз­можно, имен­но они потом при­ведут уже реаль­ных хакеров, если те най­дут на нашем перимет­ре неч­то инте­рес­ное и пер­спек­тивное для про­ник­новения.

Боль­шая часть хакеров лома­ет то, что лома­ется. Ины­ми сло­вами, работа­ют в ширину и ищут уяз­вимые перимет­ры, которые мож­но лег­ко взло­мать. А не наобо­рот, ког­да изна­чаль­но выбира­ют ком­панию и потом ищут уяз­вимос­ти в ней, работая тем самым в глу­бину. Исклю­чение сос­тавля­ют лишь тар­гетиро­ван­ные ата­ки, ког­да хакер реаль­но заин­тересо­ван во взло­ме кон­крет­ной ком­пании. Такие зло­умыш­ленни­ки, ско­рее все­го, прос­каниру­ют все 65 535 пор­тов на всех тво­их сер­верах, так что у тебя еще будет воз­можность заметить это.

 

Узнаём ОС атакующего

В пре­дыду­щем раз­деле для наб­людения за тра­фиком мы исполь­зовали лис­тенер. Теперь приш­ла пора раз­работать свой мини­атюр­ный сниф­фер. Сниф­фер поз­волит нам видеть уже весь сетевой тра­фик, который при­ходит на нашу сис­тему. И к тому же мы смо­жем видеть не толь­ко полез­ные дан­ные, но и весь сетевой пакет, вклю­чая тран­спортный и сетевой про­токо­лы. С это­го момен­та мы прис­тупа­ем к ана­лизу стро­ения сетевых пакетов.

Ана­лиз осо­бых полей в TCP- или IP-сло­ях пакета может рас­ска­зать очень мно­гое об источни­ке тра­фика. Нап­ример, ана­лизи­руя раз­меры окна (поле window) в TCP-слое пакета, мож­но с той или иной уве­рен­ностью опре­делить тип и даже вер­сию ОС источни­ка тра­фика. С этой задачей прек­расно справ­ляет­ся ути­лита p0f, име­ющая хорошую базу фин­гер­прин­тов сетевых сте­ков. Встро­ить ее фун­кци­ональ­ность в код сниф­фера мож­но биб­лиоте­кой‑обер­ткой сле­дующим обра­зом:

from scapy.all import *
import scapy_p0f #pip3 install scapy-p0f
def p0f(packet):
os = ""; ver = ""
try:
(_,_,os,ver),_,_ = scapy_p0f.p0f(packet)
except:
pass
sniff(iface=iface, prn=p0f, store=0)

Чрез­вычай­но прос­то!

Од­нако, что­бы сиг­натуры p0f зарабо­тали, им тре­бует­ся пол­ноцен­ная TCP-сес­сия. Если же хакер про­изво­дит ска­ниро­вание TCP-SYN, не откры­вая пол­ноцен­ное соеди­нение, информа­ции в его TCP/IP-пакетах будет недос­таточ­но. Но поле IP.ttl, которое есть в каж­дом пакете, может поз­волить опре­делить тип ОС, пусть и на самом базовом уров­не: Linux, Windows или Cisco. Дело в том, что у каж­дого из этих семей­ств ОС есть свое началь­ное зна­чение TTL: 64, 128 и 255 соот­ветс­твен­но. Так что если мы добавим эту про­вер­ку в фун­кцию p0f(), то будем получать тип ОС для любого вхо­дяще­го пакета:

...
if not os and IP in packet:
if packet[IP].ttl <= 64:
os = "Linux"
elif 64 < packet[IP].ttl <= 128:
os = "Windows"
elif 128 < packet[IP].ttl <= 255:
os = "Cisco"
return os, ver
...

В тех слу­чаях, ког­да информа­ции в сетевых пакетах дос­таточ­но, p0f опре­деля­ет тип ОС даже с некото­рой вер­сией, в про­тив­ном слу­чае мы получим лишь семей­ство ОС.

Типы операционных систем узлов, отправляющих на нас трафик
Ти­пы опе­раци­онных сис­тем узлов, отправ­ляющих на нас тра­фик

Вер­сия ОС даст нам началь­ное пред­став­ление о том, кто нас ата­кует: более‑менее про­фи с Linux или новичок, запус­тивший Kali на вир­туал­ке под Windows.

 

Определяем таргетированность атаки

Се­тевой стек TCP/IP, который свя­зыва­ет ата­кующе­го с его целью, име­ет нес­коль­ко при­меча­тель­ных осо­бен­ностей. По ним мож­но сос­тавить при­мер­ную кар­тину об источни­ке ата­ки — отку­да и как она исхо­дит. Так, в IP-слое сетево­го пакета есть поле Identifier, которое у боль­шинс­тва опе­раци­онных сис­тем име­ет гло­баль­ный инкре­мен­таль­ный харак­тер. Ины­ми сло­вами, компь­ютер хакера, отправ­ляя сетевые пакеты сво­им целям, может авто­мати­чес­ки уве­личи­вать это поле на еди­ницу с каж­дым пакетом. Ана­лизи­руя это поле в при­нима­емых от хакера пакетах, мы смо­жем видеть, сколь­ко пакетов он отправ­ляет куда‑то еще, кро­ме нас. Ины­ми сло­вами, мы можем зак­лючить, тар­гетиро­ван­ная ли ата­ка или веер­ная на мно­жес­тво хос­тов.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии