Пишемо ARP Spoofer під Android

Що ж, ми трохи відхилилися від теми, і багатьом, напевно, почала порядком набридати моя балаканина (а внизу букв ще багато). Тому пропоную перейти безпосередньо до нашої теми.

Насамперед скажу, що тут ми не розбиратимемо IP-маршрутизацію, роботу ARP-протоколу і теорію самого Spoofing'а (на цю тему я бачив пару прекрасних статей на Хабрі). Також передбачається, що ви знаєте мови С, Java і маєте хоча б мінімальні навички розробки під Android. Перейдемо одразу до практики, у нашому випадку до реалізації. Спочатку розберемося з інструментами. Особисто я користуюся Eclipse з плагіном ADT і встановленим Android NDK (у нашому випадку більша частина коду буде писатися якраз у нейтіві). Можливо, ви будете редагувати сорці в блокноті і збирати ручками через термінал, або використовувати Android Studio, або що-небудь ще. У цьому випадку може виявитися, що деякі мої рекомендації можна буде опустити. У цій статті ж я хочу розповісти в тому числі про деякі підводні камені та граблі, на які настав, коли взявся за свій перший проект під Android. Отже, нам на думку спало написати простенький ARP Spoofer під Android. Що ж нам потрібно? Для початку пригадаємо, що наша програма-оболонка буде написана на Java (NativeActivity ми чіпати не будемо). Але Java не дасть нам необхідного функціоналу для реалізації задуманого. Багатьом на думку могло спасти «JNI». Ні. Це також не підійде. Щоб наша нативна програма мала 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 відповідей:

Тепер напишемо функцію main. Тут все досить очевидно. Єдине, хочу звернути вашу увагу на виклики system(), де ми додаємо до iptables правило forward accept, дозволяємо маршрутизацію (одиничка в ip_forward). Так само, коли я переносив свій проект під Android 5.x, то виявив у iptables правила DROP all у табличці natctrl_FORWARD. Це ми теж врахуємо (до речі, це правило, наскільки мені відомо, зустрічається і на Android 4.4. На попередніх версіях я цього не зустрічав).

Справа в тому, що щоб ми могли запустити нашу програму, файл, що виконується, повинен знаходитися в папці програми. Тобто, для початку він має потрапити до нашого пакета при складанні. Потім його не повиненвикинути розпакувальник APK під час встановлення на кінцевий пристрій. Це два основні моменти, через які спочатку можуть виникнути складності, якщо проект містить не лібу, а файл, що виконується. Не забуваймо, що збираючи наш бінарник не як бібліотеку, а як файл, що ми виконуємо, ми отримаємо на виході файл з назвою, що не відповідає тому, як повинна називатися бібліотека. Це означає, що такий файл просто не потрапить у наш пакет при складанні (йдеться в даному випадку про папку lib проекту). Якщо ж просто додати розширення .so, задавши ім'я модуля, в APK файл він потрапить, але буде викинуто програмою встановлення на кінцевому пристрої. А додати до module name приставку lib нам не дають. Така ось проблема, тямущого опису/рішення якої свого часу я ніде не знайшов. Хоча, можливо, просто погано шукав. Але, можливо, це пов'язано зі «спецфічністю» подібних програм, адже нам дають JNI інтерфейс, і для складання програми з бібліотечкою зайвих дій від нас дійсно не потрібно. А от як бути, якщо в проект повинні входити файли, що виконуються? Отже, у нас 2 варіанти, кожен зі своїми плюсами та мінусами:

1) Не додавати в проект підтримку JNI, а ручками створити в папці libs проекту необхідні папки (armeabi, x86 у разі потреби). Компілювати нейтів код в іншому проекті, а потім (! важливо) копіювати кожен бінарник в /libs/armeabi або x86 в наступному вигляді: libІМ'Я_ПРОГРАМИ.so. Варіант непоганий, сам спочатку його використав. Але хотілося все ж таки складання всього відразу однією кнопочкою з середовища розробки. Тим більше, основна робота то в моєму випадку була якраз у native, і було дуже незручно переносити купу бінарників моїх утиліт і перейменовувати їх при цьому (або по одному, залежно від того, скільки змін було внесено і куди). Плюс методу полягає в тому,що якщо ви використовували, скажімо, вже готові сорці, і змінювати їх у процесі роботи не буде потрібно (якщо робота йде лише над Java частиною), то цей метод дійсно буде менш витратним.

2) Додати в проект підтримку jni, як завжди, але після опису збірки кожного файлу приписати наступний код: $(shell mv $/НАЗВА_БІНАРНИКА $/libНАЗВА_БІНАРНИКА.so) а в самому початку нашого Android.mk задати SHELL:=/bin/sh Тобто. відразу після складання кожен бінар буде перейменовуватись у потрібний нам формат. Недолік цього способу один — при кожній збірці проекту, бінарники перейменовуються в початковий вигляд самою програмою збірки. Щоб відпрацював наш скрипт, останнім зміненим файлом має бути Android.mk. Тобто перед кожним складання проекту вам доведеться відкривати його, ставити, наприклад, прогалину, потім уже збирати. Те ж саме стосується експорту в APK файл для маркету. Правимо Android.mk, зберігаємо, експортуємо. У цьому випадку все буде гаразд. Все ж таки при використанні цього способу рекомендую стежити за розміром кінцевого APK файлу, якщо наші файли таки туди не потрапили з якоїсь причини, розмір його буде менше (наскільки - залежить від кількості збірок, чи використовуєте ви статичну лінківку і т.д.). , але зміни розміру будуть у будь-якому випадку).

Так само хочу зазначити, що для Android 4.0 і нижче варто так само використовувати статичну лінковку (ключ -static для LOCAL_LDFLAGS), для 4.1 краще використовувати динамічну збірку.

Що ж, зі збиранням ми розібралися. Метод «зберігання» нашого файлу в пакеті вибрали. Точніше сказати, спосіб попадання того самого файлу в необхідну нам папку. Тепер наведу вам обіцяну функцію, яку викликає обробник нашої кнопки spoof:

Тут все просто, запускаємо Thread, в якомузапускаємо новий процес із командним інтерпретатором під рутом(su) і через output stream пишемо свою команду. Самою ж командою буде повний шлях до нашого файлу, що виконується + ключі. Повним шляхом до файлу відповідно буде /data/data/ім'я_пакета/lib/назва_файла.

Отже, залишилося лише одне питання – як завершувати наш спуфер? Найпростіший варіант - викликати з-під рута killall -SIGKILL libarpspoof.so при натисканні на іншу кнопочку, призначену для завершення. Так само можна відловлювати, скажімо, SIGINT у самій програмі та робити вихід із циклу в main'і. Якщо ви пишете складнішу програму, що взаємодіє з оболонкою, можете надсилати pid процесу при запуску, потім викликати власну реалізацію kill, і передавати їй як ключ отриманий pid. Цей метод я використовував у Network Utilities, щоб зробити програму менш залежною від busybox (аплет killall таки є не у всіх), а нативна програма, що незавершена, м'яко кажучи, не є гуд. Але для нашої навчальної реалізації згодиться і такий метод. А от якщо ви пишите додаток, яким користуватиметеся не тільки ви, рекомендую використовувати власну програму-завершалку (або хоча б перевіряти наявність необхідного аплету busybox). Загалом, для даної програми ви можете використовувати будь-який варіант, що вам сподобався. Думаю, з написанням обробника другої завершальної кнопки ви впораєтеся і без мене. Якщо раптом що — дивимося в сорцях, під статтею. Так само, щоб трохи «прикрасити» програму, ви можете робити неактивною кнопку старту при натисканні на неї, і знову робити її активною при натисканні на стоп. Або, скажімо, використовувати ToggleButton. Це вже на вашу думку. Моє ж завдання полягає у наданні максимально простого прикладу. Мусолити тут особливо нема чого, тому підемодалі. Залишилося додати необхідні пермішні до AndroidManifest. А це будуть:

Перш ніж перейти до завершальної частини статті, я хотів би дати вам ще пару порад на майбутнє: 1) Якщо вашу програму потрібно завершувати вручну (як у даному прикладі), краще подбайте про те, щоб нативна програма обов'язково завершувалася при виході . Наприклад, у обробнику кнопки виходу із програми. 2) Сторонні програми інсталяції пакетів (наприклад, які входять до деяких файлових менеджерів) при установці можуть не задати атрибути виконання виконуваним файлам, що входять до вашої програми. Сам свого часу настав на ці граблі. Найкраще під час запуску перевіряйте атрибути та виставляйте вручну за потреби.