Go перебирать каталоги! Пишем собственный фаззер и разбираемся с многопоточностью в Go

Go перебирать каталоги! Пишем собственный фаззер и разбираемся с многопоточностью в Go

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

При­нятая в Go реали­зация кон­курен­тнос­ти через обмен сооб­щени­ями меж­ду незави­симы­ми друг от дру­га потока­ми называ­ется CSP (Communicating Sequential Processes). В ее осно­ве — каналы, переда­ющие сооб­щения от одно­го потока (горути­ны) к дру­гому.

Ес­ли ты зна­ком с кон­цепци­ей сис­темных каналов (pipes), то уже при­мер­но понима­ешь, что к чему. Одна­ко, ран­тайм Go не исполь­зует средс­тва опе­раци­онной сис­темы, у него име­ется собс­твен­ная реали­зация горутин и каналов.

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

Сло­варь мож­но взять, нап­ример, здесь , но я советую не брать очень боль­шой спи­сок, а написать свой бук­валь­но из десят­ка позиций — это­го более чем дос­таточ­но для начала.

Час­то модель CSP опи­сыва­ют одним утвер­жде­нием: «не реали­зовы­вай ком­муника­цию пос­редс­твом сов­мес­тно­го исполь­зования памяти; вмес­то это­го сов­мес­тно исполь­зуй память пос­редс­твом ком­муника­ции».

Мо­дель CSP раз­работа­на и опи­сана Чарль­зом Энто­ни Хоаром — воз­можно, ты уже слы­шал его имя в свя­зи с алго­рит­мом быс­трой сор­тиров­ки (qsort).

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

Разделяем код на модули

В файл main.go вста­вим сле­дующий код:

Как видишь, мы логичес­ки рас­пре­дели­ли фраг­менты кода меж­ду отдель­ными изо­лиро­ван­ными фун­кци­ями в соот­ветс­твии с ролями, которые на этот код воз­ложены.

Фун­кция produce( ) генери­рует задания для пос­леду­ющей обра­бот­ки. Она вычиты­вает стро­ки из спис­ка и фор­миру­ет URL, по которым будут осу­щест­влять­ся попыт­ки соеди­нения. Сама обра­бот­ка лежит вне зоны ответс­твен­ности этой фун­кции и никак не вли­яет на ее код. Дру­гие час­то исполь­зуемые име­на для такого кода: generate , source .

Фун­кция worker( ) обра­баты­вает задания. В дан­ном слу­чае, выпол­няет­ся GET зап­рос к сер­веру и сох­ранение кода отве­та. Как ты видишь, фун­кция «не зна­ет» ничего ни о про­исхожде­нии заданий, ни о даль­нейшей судь­бе резуль­татов — это к ней не отно­сит­ся. Дру­гие час­то встре­чающиеся име­на для такой роли: process , handle .

Фун­кция collect( ) вычиты­вает резуль­таты и сох­раня­ет их в файл. По ана­логии с пре­дыду­щими она ничего не зна­ет и не дол­жна знать о том, отку­да и каким обра­зом эти резуль­таты взя­лись. У нее есть толь­ко одна чет­ко очер­ченная зона ответс­твен­ности — соб­рать и сох­ранить. Дру­гие час­то встре­чающиеся име­на: consume , sink , save .

По­доб­ное раз­деление воп­лоща­ет прин­цип пос­тро­ения прог­рам­мных сис­тем loose couping, high cohesion (один из вари­антов перево­да: «сла­бая свя­зан­ность, силь­ное сцеп­ление»). Сог­ласно это­му прин­ципу, сис­тема стро­ится из отдель­ных ком­понен­тов, которые минималь­но зависят друг от дру­га (это и есть loose coupling). При этом каж­дый такой ком­понент выпол­няет чет­ко очер­ченную роль, и код, ответс­твен­ный за ту или иную фун­кцию, скон­цен­три­рован внут­ри «сво­его» ком­понен­та, а не раз­мазан по дру­гим (а это — high cohesion).

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

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

Раз­деление боль­шого монолит­ного кус­ка кода, реша­юще­го ту или иную задачу, на отдель­ные ком­понен­ты, называ­ется модуля­риза­цией (modularization). Не знаю, как тебе, а мне это труд­но даже про­изнести, поэто­му я пред­почитаю родс­твен­ное понятие — деком­позиция. Хотя, конеч­но, оно боль­ше про под­ход к решению задачи, нежели про струк­туриро­вание реали­зации это­го решения.

За­пус­кать эти фун­кции будем как отдель­ные горути­ны. При этом мы отка­жем­ся от «клас­сичес­кого» под­хода к обме­ну информа­цией через сов­мес­тно исполь­зуемые объ­екты, защищен­ные мьютек­сами. Вмес­то это­го вос­поль­зуем­ся ка­нала­ми — спе­циаль­ным инс­тру­мен­том ком­муника­ции меж­ду горути­нами. Каналы потоко­безо­пас­ны по опре­деле­нию и не тре­буют исполь­зования мьютек­сов или иных при­мити­вов син­хро­низа­ции.

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

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

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

9990 рублей 5000 р.

950 р.

В этой статье мы разберем, как команда KiberS прошла путь от разведки внешнего периметра д…

← Cybersecurity