Go ломать хеши! Пишем свой хешкрекер на Golang и изучаем тонкости языка

Go ломать хеши! Пишем свой хешкрекер на Golang и изучаем тонкости языка

Содержание статьи

В зак­лючитель­ной статье о кон­курен­тнос­ти в Go раз­берем инс­трук­цию select , сиг­наль­ные каналы и инс­тру­мен­ты пакета context . С их помощью удоб­но обме­нивать­ся сооб­щени­ями, управлять дед­лай­нами и отме­нять запущен­ные опе­рации. В качес­тве прак­тичес­кого при­мера реали­зуем прос­той механизм кре­кин­га хеша MD5: будем под­бирать пароль из сло­варя, пока его хеш не сов­падет с нуж­ным.

Сло­вари мож­но взять, нап­ример, в ре­пози­тории SecLists . Заранее выбери любое сло­во не из пер­вых строк сло­варя и запиши его MD5-хеш. Нап­ример, для пароля netadmin он будет таким:

Это при­годит­ся для экспе­римен­тов с кодом.

Соз­дай новый модуль:

Вставь в main. go такой код:

Здесь мы опре­дели­ли горути­ны, из которых соберем кон­вей­ер обра­бот­ки. Что­бы не переда­вать в фун­кции кучу аргу­мен­тов, исполь­зуем струк­туру PipelineConfig : в ней хра­ним все парамет­ры кон­вей­ера. Канал JobCh нужен для переда­чи сооб­щений, которые надо обра­ботать, то есть слов, счи­тан­ных из сло­варя. В ResultCh отпра­вим сло­во, чей хеш сов­пал с иско­мым, если такое сло­во най­дет­ся.

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

Фун­кция produce( ) генери­рует задания для даль­нейшей обра­бот­ки: чита­ет стро­ки из спис­ка, валиди­рует их и отправ­ляет в канал. Сама обра­бот­ка — вычис­ление хешей — в задачи этой фун­кции не вхо­дит и на ее код не вли­яет.

Фун­кция worker( ) обра­баты­вает задания: вычис­ляет хеш, срав­нива­ет его с целевым зна­чени­ем и, если они сов­пада­ют, отправ­ляет сло­во в канал резуль­татов и завер­шает­ся. Обра­ти вни­мание: ког­да ты парал­лель­но запус­каешь нес­коль­ко вор­керов, сов­падение най­дет один из них и завер­шится, но осталь­ные об этом не узна­ют и про­дол­жат грин­дить дан­ные, пока те не закон­чатся. Поэто­му нам нужен отдель­ный сиг­нал завер­шения, что­бы передать всем горути­нам «стоп».

Фун­кция collect( ) чита­ет резуль­таты и выводит их в кон­соль. Здесь мы ждем толь­ко один резуль­тат: так устро­ена задача. Поэто­му цик­ла нет — прос­то ждем пер­вое сооб­щение или зак­рытие канала, смот­ря что слу­чит­ся рань­ше.

О чте­нии из канала мы говори­ли в пре­дыду­щей статье , но вкрат­це напом­ню, если ты вдруг забыл: val, ok : = < -ch бло­киру­ет текущую горути­ну, пока из канала ch не при­дет зна­чение val . Если канал зак­роют, val получит zero value для типа канала, а ok ста­нет false . Мы исполь­зуем про­вер­ку if ! ok , что­бы раз­делить две ситу­ации — получе­ние сооб­щения и зак­рытие канала — и выпол­нить нуж­ные дей­ствия.

Как и в пре­дыду­щей статье, собира­ем кон­вей­ер обра­бот­ки из отдель­ных горутин: одна генери­рует задания, пул горутин парал­лель­но обра­баты­вает их, а еще одна собира­ет резуль­таты:

Что­бы сэконо­мить мес­то, я не буду заново раз­бирать, как работа­ет кон­вей­ер. Если нуж­но — вер­нись к пре­дыду­щей статье . Крат­ко напом­ню пос­ледова­тель­ность запус­ка: мы ини­циали­зиро­вали нуж­ные каналы и струк­туру с нас­трой­ками.

Го­рути­на produce (для наг­ляднос­ти я буду называть горути­ны по име­нам соот­ветс­тву­ющих фун­кций) запус­кает­ся и работа­ет, пока не про­чита­ет файл srcFileName .

Го­рути­на collect запус­кает­ся и работа­ет, пока не зак­роет­ся канал resultCh или пока она не получит резуль­тат вычис­лений.

Го­рути­ны worker запус­кают­ся и работа­ют, пока не зак­роет­ся канал jobCh .

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

Еще в одной отдель­ной горути­не ждем завер­шения кон­вей­ера и зак­рыва­ем канал оши­бок.

Тем вре­менем поток выпол­нения идет даль­ше: main( ) заходит в бес­конеч­ный цикл чте­ния из канала оши­бок errCh и оста­ется там, пока канал не зак­роет­ся. Пос­ле это­го по зна­чению фла­га hadError выбира­ется код воз­вра­та.

И пос­ледова­тель­ность оста­нов­ки:

Пос­ле чте­ния фай­ла produce завер­шает­ся, а канал jobCh зак­рыва­ется.

Цикл чте­ния из jobCh внут­ри worker завер­шает­ся, ког­да канал зак­рыва­ется. Пос­ле это­го worker( ) воз­вра­щает управле­ние. В отдель­ной горути­не мы ждем, пока завер­шатся все вор­керы, и зак­рыва­ем канал resultCh .

Чте­ние из resultCh внут­ри collect тоже завер­шает­ся: либо мы получи­ли иско­мое зна­чение, либо все вор­керы отра­бота­ли и канал resultCh зак­рылся. Пос­ле воз­вра­та из collect( ) зак­рыва­ем канал doneCh — это самый важ­ный момент, но о нем чуть поз­же.

В отдель­ной горути­не ждем, пока оста­новит­ся кон­вей­ер, то есть завер­шатся produce и collect , а затем зак­рыва­ем канал errCh .

Пос­ле зак­рытия errCh глав­ная горути­на выходит из цик­ла чте­ния.

Сигнальный канал и select

Раз­берем код фун­кции worker( ) под­робнее:

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

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

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

9990 рублей 5000 р.

950 р.

12 января 2027 года в Microsoft официально прекратят расширенную поддержку Windows Server …

← Cybersecurity