Писане на ARP Spoofer за Android
Е, малко се отклонихме от темата и със сигурност мнозина започнаха да се отегчават от моето бърборене (и все още има много писма по-долу). Затова предлагам да преминем директно към нашата тема.
Първо, ще кажа, че тук няма да анализираме IP маршрутизирането, работата на ARP протокола и самата теория за Spoofing (видях няколко отлични статии на Habré по тази тема). Също така се предполага, че знаете C, Java и имате поне минимални умения за разработка на Android. Нека да преминем към практиката, в нашия случай към изпълнението. Първо, нека да разгледаме инструментите. Лично аз използвам Eclipse с инсталиран плъгин ADT и Android NDK (в нашия случай по-голямата част от кода ще бъде написан на нативен). Може би ще редактирате сортиране в бележника и ще компилирате с химикалки през терминала, или ще използвате Android Studio, или нещо друго. В този случай може да се окаже, че някои от моите препоръки могат да бъдат пропуснати. В тази статия искам също да говоря за някои от клопките и капаните, в които стъпих, когато се заех с първия си проект за Android. И така, хрумна ни да напишем прост ARP Spoofer за Android. Какво ще ни трябва? Първо, не забравяйте, че нашата обвиваща програма ще бъде написана на Java (няма да докосваме NativeActivity). Но Java няма да ни даде необходимата функционалност, за да реализираме нашите планове. Много хора биха могли да се сетят за "JNI". Не. Това също няма да работи. За да може нашата нативна програма да има Root привилегии, ще трябва да стартираме отделен процес и да стартираме нашата програма от root. Ако за *nix потребителите на подобни системи това е съвсем очевидно, тогава за останалите бих искал незабавно да подчертая този момент,за да няма повече въпроси. Е, ние го решихме. Нека напишем родна програма (не библиотека), която ще работи от Java код, под Root. Имаме нужда от привилегии на суперпотребител, за да работим с Raw сокети, както и след това да добавим необходимите правила към iptables, но повече за това по-късно.
Предлагам да започнете с най-родната програма. Засега можете да създадете нов проект и нищо друго. Не бързайте да добавяте JNI поддръжка към проекта (ще направим това, след като разгледаме няколко клопки). Междувременно предлагам да създадем нашия източник, наречете го arpspoof.c. Ще се обърнем и към Android.mk малко по-късно.
По принцип всичко е ясно тук. Нека обясня само няколко:
ar_hrd - за Ethernet ще бъде ARPHDR_ETHER; ar_pro - в нашия случай, ETH_P_IP; arp_op - ще изпратим ARP отговори, така че ARPOP_REPLY.
Е, разбрахме ARP хедъра. В нашия пакет той ще следва Ethernet заглавката. Нека да разгледаме и това. Нека погледнем в if_ether.h:
И накрая, нашата функция за изпращане на ARP отговори:
Сега нека напишем основната функция. Тук всичко е доста тривиално. Единственото нещо, което искам да насоча вниманието ви към системните () извиквания, където добавяме правилото за приемане на препращане към iptables, позволяваме маршрутизиране (едно в ip_forward). Освен това, когато прехвърлих моя проект под Android 5.x, намерих DROP всички правила в таблицата natctrl_FORWARD в iptables. Ние също ще вземем това предвид (между другото, доколкото знам, това правило се среща и в Android 4.4. Не видях това в по-ранните версии).
Факт е, че за да стартираме нашата програма, изпълнимият файл трябва да се намира в папката на приложението. Тоест, за начало, той трябва да влезе в нашия пакет по време на монтажа. Тогава не трябваизхвърлете програмата за разопаковане на APK, когато е инсталирана на крайното устройство. Това са две основни точки, които могат да причинят трудности в началото, ако проектът съдържа не lib, а изпълним файл. Не забравяйте, че когато изграждаме нашия двоичен файл не като библиотека, а като изпълним файл, ще получим файл с име, което не съответства на това как трябва да се нарича библиотеката. Това означава, че такъв файл просто няма да влезе в нашия пакет по време на асемблирането (в този случай говорим за папката lib на проекта). Ако просто добавите разширението .so, указвайки името на модула, то ще влезе в APK файла, но ще бъде изхвърлено от инсталатора на крайното устройство. И не ни е позволено да добавяме префикса lib към името на модула. Такъв е проблемът, чието обяснително описание / решение не намерих никъде по мое време. Въпреки че може би просто търсих зле. Но може би това се дължи и на „спецификата“ на такива програми, защото ни е даден JNI интерфейс и наистина не се нуждаем от допълнителни действия, за да изградим програма с библиотека. Но какво ще стане, ако проектът трябва да включва изпълними файлове? И така, имаме 2 възможности, всяка със своите плюсове и минуси:
1) Не добавяйте поддръжка на JNI към проекта, но създайте ръчно необходимите папки в папката libs на проекта (armeabi, x86, ако е необходимо). Компилирайте собствения код в друг проект и след това (! важно) копирайте всеки двоичен файл в /libs/armeabi или x86, както следва: libPROGRAM_NAME.so. Варианта не е лош, аз самият го използвах в началото. Но все пак исках да създам всичко наведнъж с един бутон от средата за разработка. Освен това основната работа в моя случай беше само в родния език и беше много неудобно да прехвърлям куп бинарни файлове на моите помощни програми и да ги преименувам едновременно (или една по една, в зависимост от това колко промени са направени и къде). Предимството на метода е, чече ако сте използвали, да речем, готови сортове и не е необходимо да ги променяте по време на работа (ако работата е само върху частта на Java), тогава този метод наистина ще бъде по-евтин.
2) Добавете поддръжка на jni към проекта, всичко е както обикновено, но след като опишете асемблирането на всеки файл, добавете следния код: $(shell mv $/BINARY_NAME $/libBINARY_NAME.so) и в самото начало на нашия Android.mk задайте SHELL:=/bin/sh Т.е. веднага след асемблирането всеки двоичен файл ще бъде преименуван във формата, от който се нуждаем. Този метод има един недостатък - при всяка компилация на проекта двоичните файлове се преименуват в оригиналната им форма от самата програма за компилация. За да работи нашия скрипт, последният модифициран файл трябва да е Android.mk. Тоест, преди всяко изграждане на проекта, ще трябва да го отворите, да поставите например интервал, след което да го изградите. Същото важи и за експортиране в APK файл за пазара. Ние редактираме Android.mk, запазваме, експортираме. В този случай всичко ще бъде наред. Въпреки това, когато използвате този метод, препоръчвам ви да наблюдавате размера на крайния APK файл, ако нашите файлове не стигнат до там по някаква причина, размерът му ще бъде по-малък (колко зависи от броя на асемблиранията, дали използвате статично свързване и т.н., но промените в размера ще бъдат във всеки случай).
Също така искам да отбележа, че за Android 4.0 и по-стари също си струва да използвате статично свързване (ключът -static за LOCAL_LDFLAGS), за 4.1 е по-добре да използвате динамично сглобяване.
Е, разбрахме сглобката. Беше избран методът за "съхранение" на нашия изпълним файл в пакета. Или по-скоро методът за получаване на същия файл в папката, от която се нуждаем. Сега ще ви дам обещаната функция, която манипулаторът на нашия spoof бутон извиква:
Тук всичко е просто, започваме нишка, в коятостартираме нов процес с команден интерпретатор под root (su) и записваме нашата команда през изходния поток. Самата команда ще бъде пълният път до нашия изпълним файл + ключове. Пълният път до файла съответно ще бъде /data/data/PACKAGE_NAME/lib/FILE_NAME.
И така, остава само един въпрос - как да завършим нашия спуфер? Най-лесният вариант е да извикате killall -SIGKILL libarpspoof.so като root, когато щракнете върху друг бутон, предназначен да приключи. Можете също така да хванете, да речем, SIGINT в самата програма и да излезете от цикъла в main. Ако пишете по-сложна програма, която взаимодейства с обвивката, можете да предоставите pid на процеса при стартиране, след това да извикате собствената си реализация на kill и да предадете получения pid като ключ. Използвах този метод в Network Utilities, за да направя програмата по-малко зависима от busybox (не всеки има аплета killall) и нетерминираща родна програма, меко казано, не бръмчи. Но за нашето обучение този метод също ще работи. Но ако пишете приложение, което ще се използва не само от вас, препоръчвам да използвате собствена програма за завършване (или поне да проверите за наличието на необходимия аплет busybox). Като цяло за тази програма можете да използвате всяка опция, която харесвате. Мисля, че можете да се справите с писането на манипулатор за втория последен бутон без мен. Ако изведнъж нещо - гледаме в сортовете, под статията. Освен това, за да „украсите“ малко програмата, можете да направите бутона за стартиране неактивен, когато щракнете върху него, и да го направите отново активен, когато щракнете върху стоп. Или, да речем, използвайте ToggleButton. Това зависи от вас. Целта ми е да дам възможно най-простия пример. Тук няма нищо особено за отлагане, така че да тръгвамепо-нататък. Остава да добавим необходимите разрешения към AndroidManifest. И това ще бъдат:
Преди да преминете към последната част на статията, бих искал да ви дам няколко съвета за бъдещето: 1) Ако вашата програма трябва да бъде прекратена ръчно (както в този пример), по-добре е да се уверите, че естествената програма е прекратена при излизане. Например в манипулатора на бутона за изход за приложението. 2) Инсталаторите на пакети от трети страни (като тези, включени в някои файлови мениджъри) може да не задават атрибути за изпълнение за изпълними файлове, включени във вашата програма по време на инсталацията. Самият навреме стъпи на това гребло. Най-добре е да проверите атрибутите при стартиране и да ги зададете ръчно, ако е необходимо.