Tagi:  •    •  

Wygodne ściąganie z serwisów typu Rapidshare czy netload.in, bez konieczności czekania między pobieraniem kolejnych plików? Okazuje się, że w pewnych przypadkach to jest możliwe, przy użyciu skryptu resetującego router. Ta notka ma na celu przybliżenie tworzenia takich skryptów.

Zanim jednak zagłębisz się nieco dalej, muszę rzucić w Ciebie trochę zimnych faktów. Po pierwsze, musisz mieć łącze ze zmiennym IP. Po drugie, musisz mieć dostęp do panelu administracyjnego routera (i nie chodzi o dostęp read-only, ale taki który pozwala na zmianę ustawień). Po trzecie, router musi mieć opcję restartu. Oczywistym wydaje się fakt, że nie mogę podać jednego, uniwersalnego sposobu na restart każdego routera, z tej prostej przyczyny, że routerów na świecie jest zbyt dużo aby mogło to się udać komukolwiek, kto zajmuje się sieciami na co dzień, a co dopiero takiemu szaremu programiście jakim jestem ja.

Im gorzej znasz protokół HTTP, tym gorzej będzie Ci czytać poniższą notkę. Brak znajomości Ruby nie powinno powodować problemów, ponieważ wykorzystanie tego języka ograniczy się w sumie jedynie do wysyłania zapytań HTTP. Oprócz tego, znajomość HTML (czyli czegoś, czym większość wielkich programistów gardzi), JavaScriptu (nie Javy) i manipulacji na strukturze DOM strony będzie ułatwiało kojarzenie faktów i myślenie podczas czytania notki.

W skrócie, każdy router powinien mieć dostęp do opcji administracyjnych po protokole HTTP, czyli przez przeglądarkę internetową. Niektóre posiadają też dostęp przez telnet, ssh, lub inne, mniej lub bardziej znane protokoły, ale najlepiej jest skoncentrować się właśnie na protokole HTTP, ponieważ wydaje się on być w większości standardem, obecnym na każdym (lub na większości) routerze. Poza tym, ten protokół jest wspierany praktycznie out-of-the-box przez większość środowisk skryptowych, a już napewno przez Ruby, w którym pisane będą przykłady. Nawet jednak jeśli HTTP nie jest supportowane przez Twój framework, zawsze możesz wykorzystać operacje na zwykłych socketach TCP na porcie 80.

Zasada pisania skryptu restartującego router jest prosta. Najpierw należy odnaleźć w opcjach routera przycisk Reconnect, lub Disconnect, w zależności od tego, co jest dostępne, potem wyłuskać odpowiednie, konkretne zapytanie HTTP, które powoduje wykonanie odpowiedniej akcji, a na koniec napisać skrypt, który będzie po prostu to zapytanie wysyłał. Brzmi prosto, i w większości przypadków tak jest, choć czasem dojść może konieczność wysłania dodatkowych zapytań HTTP np. logujących nasz skrypt.

Analiza

Analiza opiera się na wyłuskaniu konkretnego zapytania HTTP, które zresetuje nam router. Najlepiej dojść do niego przez przyglądanie się interfejsowi użytkownika w panelu administracyjnym, przyjrzeniu się jakie zapytania HTTP wysyłane są po którym kliknięciu i zadecydowanie, które zapytanie resetuje router. Zapytania HTTP możesz przechwycać swoim ulubionym snifferem - np. Wireshark (dawniej Ethereal).

Po wejściu na stronę routera wita nas okno z pytaniem o nazwę użytkownika i hasło. Jest to okno wywołane przez przeglądarkę, nie stronę – na screenshocie wywołane zostało przez Firefoxa. Samo to jest dość wartościową informacją, ponieważ sugeruje to użycie podstawowej autoryzacji HTTP przy decyzji o wpuszczeniu użytkownika do środka, czy nie. Jeśli po wejściu na panel ukazałaby nam się strona HTML, która prosi nas o nazwę użytkownika i hasło (lub same hasło), wtedy autoryzacja najprawdopodobniej opierałaby się na innej implementacji autoryzacji przez kontrolę odpowiednich ciasteczek (cookies) – to byłby nieco bardziej skomplikowany przypadek (choć nadal prosty to obejścia), dlatego zostawię jego opis na później. Aby nie mieszać nikomu w głowach, wrócę jednak do HTTP Basic Authorization, które napotkałem w modemach/routerach Pentagram. Wpisz więc swój username i password w odpowiednie pola, aby się zalogować.

Teraz szukamy opcji restartu. Różne routery mają tą opcję schowaną w różnych miejscach, poszukaj więc dobrze. Na początek zrób mały ręczny test, czy ten restart rzeczywiście zmieni Twój adres IP. Czasami opcje restartu routera rzeczywiście ten router w jakiś sposób restartują, jednak nie zmienia to w żaden sposób jego numeru IP przydzielanego przez ISP (np. wan adsl reset – thx msthhk). Jeśli znalazłeś tą opcję, pora na obczajenie co i w jaki sposób dzieje się przy naciśnięciu przycisku Restart.

Na początku zawsze warto dokonać statycznej analizy. Do takich celów doskonale nadaje się plugin Firebug do Firefoxa, który jest doskonałym narzędziem pracy nie tylko niejednego webmastera i webdevelopera, ale też i każdego, kto chce wgłębić się nieco w jakąkolwiek stronę którą napotka w czeluściach sieci. Dla tych, którym ideologia zabrania korzystania z Firefoxa, przydać się może Opera i jej plugin Dragonfly (Tools->Developer Tools), lub też dead listing HTML w notatniku, wszystko jedno. Sęk w tym, aby odnaleźć przycisk RESTART, oraz potrafić powiedzieć co dzieje się po jego naciśnięciu.

W przypadku mojego routera (Cerberus P 6311-07A), przycisk RESTART jest przyciskiem typu type=”submit”, tak więc prosty wniosek jest taki, że znajduje się on w formularzu <FORM>. Szybka nawigacja po hierarchii tagów prowadzi nas do deklaracji formularza: <form name="System_Restore" action="/Forms/tools_system_1" method="post">. Deklaracja ta opisuje w skrócie kilka faktów: do wysyłania danych z formularza na serwer routera przeglądarka ma wykorzystać metodę POST (druga metoda to GET), obiektem docelowym jest /Forms/tools_system_1, oraz nazwa formularza to System_Restore, co jedynie w nielicznych przypadkach ma znaczenie. W kodzie nie widać żadnych wywołań JavaScript (choć to nie jest na nic dowód), dlatego jest szansa że ich nie będzie w ogóle. Na pierwszy rzut oka widać więc, że naciśnięcie przycisku wysyła żądanie typu HTTP POST (method=”post”) pod URL $serwer/Forms/tools_system_1. W formularzu znajdują się też dwa elementy typu RadioButton2 o nazwie restoreFlag, tak więc wygląda na to, że do serwera przesyłany jest formularz o treści restoreFlag=XXX&Restart=RESTART (zgodnie z konwencją serializacji formularzy <FORM> przy wysyłaniu ich metodą POST). Te założenia są jednak tylko teoretyczne, ponieważ po pierwsze: może istnieć kod JavaScript podczepiony pod jakieś zdarzenie w jakimś tagu, który będzie to żądanie modyfikował, dodając, usuwając, lub modyfikując elementy formularza (co wpłynie na treść danych wysyłanych do routera), lub po prostu coś mogło umknąć naszej uwadze. Nie ma to jednak teraz większego znaczenia, ponieważ istotne informacje uzbieramy w kolejnym kroku, a nasza statyczna analiza ma na celu jedynie poszerzenie naszej wiedzy na temat mechaniki restartowania tego routera.

Zbierzmy teraz konkretne, przydatne w praktyce, informacje. Uruchom sniffer Wireshark i zacznij sniffować port 80. Wireshark posiada własny system zapisu regułek filtrowania pakietów, który pozwala na łapanie tylko tych pakietów które chcemy łapać, omijając wszystkie inne. Wybierając z menu Capture->Options, wybierz interfejs Twojej karty sieciowej i w polu Capture Filter wpisz „tcp port 80”.

Kliknij wtedy przycisk Restart. Sniffer powinien zarejestrować żądanie użytkownika. Pora nieco bliżej zerknąć na to, co się dzieje.

Po kliknięciu RESTART możesz wyłączyć sniffer i rzucić okiem na pakiety, które zebrał. Odnajdź w tej liście żądania HTTP POST, ponieważ dzięki analizie wiemy już, że właśnie tego typu żądanie najprawdopodobniej resetuje pudełko. Znajduje się ono tutaj, i – co więcej – odnosi się do znanego przez nas celu, czyli /Forms/tools_system_1 (co też znamy i kojarzymy z analizy statycznej). Nie można więc wątpić w to, że stanowi to szukaną przez nas wskazówkę. Wireshark potrafi w łatwy sposób zrekonstruować konwersację między jednostkami sieciowymi za pomocą opcji Follow TCP Stream, którą sugeruję teraz użyć.

Czerwonym kolorem oznaczone są stringi, które zostały wysłane przez nasz komputer, natomiast niebieskim – otrzymane (czyli wysłane przez router). Widzimy takie zapytanie HTTP POST:

POST /Forms/tools_system_1 HTTP/1.1
Host: 192.168.1.100
User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.4) Gecko/2008111318 Linux Mint/5 (Elyssa) Firefox/3.0.4
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: pl,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: hxxp://192.168.1.100/maintenance/tools_system.htm
Authorization: Basic ??????????????????????
Content-Type: application/x-www-form-urlencoded
Content-Length: 29

restoreFlag=0&Restart=RESTART

oraz odpowiedź routera na to zapytanie:

HTTP/1.1 303 See Other
Location: hxxp://192.168.1.100/maintenance/tools_system.htm
Content-Length: 0
Server: RomPager/4.07 UPnP/1.0
EXT:

Przyjrzyjmy się bliżej naszemu zapytaniu. Pierwsza linijka oznacza metodę („POST”), ścieżkę obiektu docelowego („/Forms/tools_system_1”) oraz wersję protokołu („HTTP/1.1”) używanego do komunikacji. Ścieżka obiektu docelowego w najprostszym przypadku jest po prostu ścieżką do strony HTML, coraz częściej jednak zamiast statycznej strony HTML obiektem jest jakiś skrypt, np. PHP (rozszerzenie nie ma znaczenia – obiekt z końcówką .htm z powodzeniem może być skryptem PHP), JSP, serwletem Java, Ruby On Rails, ColdFusion, czy nawet programem pisanym w asemblerze x86 korzystając z rozszerzeń ISAPI. To jednak nie jest w naszym przypadku ważne.

Kolejna linia, Host, oznacza serwer docelowy, do którego wysyłamy zapytanie, i ma znaczenie jedynie wtedy, gdy serwer obsługuje tzw. serwery wirtualne, czyli różne domeny podpięte pod ten sam IP. Serwer HTTP decyduje wtedy, który document root użyć jako ścieżkę bazową relatywnej ścieżki obiektu docelowego (z linii pierwszej). Routery raczej nie są wyposażone w tak zaawansowane serwery HTTP (choć głowy za to nie dam), a nawet jeśli by były, to z uwagi na to że żadna domena nie jest tu wykorzystywana, linię tą można z powodzeniem pominąć.

User-Agent: jak łatwo zauważyć jest to źródło inwigilacji i szpiegostwa przez rosnący w potęgę internetowy Babilon. Nazwa tego nagłówka jest niezwykle trafna, ponieważ każda strona, na którą wchodzimy, zamienia się w Agenta, który swoim ciemnym wzrokiem obserwuje Usera, zapisując wszystkie kroki, które biedak odważył się poczynić. Linia zawiera informacje o przeglądarce (to dla tych, którzy zastanawiają się skąd firmy biorą statsy korzystania z przeglądarek), jej wersji, systemie operacyjnym, silniku renderowania, czasem też – jak widać w moim przypadku – o dystrybucji Linuxa i procesorze. Niektóre usługi niestety sprawdzają ten string (choć można je policzyć pewnie na palcach jednej ręki; na chwilę obecną nie przypominam sobie kiedy i gdzie się z tym spotkałem, ale pamiętam, że tak było), dlatego warto go zostawić tak jak jest.

Accept: deklaruje jakie typy plików nasza przeglądarka rozumie. My piszemy prosty skrypt i chcemy otrzymywać tylko dokumenty HTML, więc zostawienie text/html wydaje się być dobrym wyjściem. Z drugiej strony, deklarować sobie możemy, ale w Internecie, jak na prawdziwą anarchię przystało, nie zawsze nasze deklaracje są respektowane, więc możemy deklarować chęć otrzymania dokumentu text/html, a faktycznie dostać image/png... choć czasem serwery patrzą na tą linijkę i dostosowują się do naszych deklaracji.

Accept-Language: sytuacja podobna do tej powyżej – deklarujemy które języki chce oglądać użytkownik przeglądarki (dla tych którzy głowią się skąd Google wie, jaki język na stronie głównej wybrać). Dla naszych celów kompletnie bezużyteczne, więc opuszczamy to w całości.

Accept-Encoding: jaki typ danych przyjmuje nasza przeglądarka? „Gzip,deflate” oznacza, że przeglądarka potrafi rozpakować sobie dane spakowane gzipem i algorytmem deflate. Niektóre serwery wysyłają strony w postaci skompresowanej, np. Wirtualna Polska, jeśli tylko przeglądarka obsługuje taki typ danych. Zmniejsza to stanowczo ilość danych które należy przesłać, poprawiając wydajność połączenia internetowego, kosztem większej utylizacji CPU serwera na żądanie, tym jednak niech martwią się administratorzy. Nasz skrypt niestety nie będzie potrafił rozpakowywać takich danych, choć prawdopodobnie serwer i tak nie wspiera kompresji danych, dlatego opuszczamy całkiem tą linijkę, co będzie swoistą sugestią dla serwera, aby nie kombinował z pakowaniem danych, tylko przesłał nam je takimi jakie są.

Accept-Charset: do tej pory powinieneś wiedzieć już, co znaczy ta linijka, oraz wiedzieć również, że dla naszych celów nie jest użyteczna i można ją z powodzeniem opuścić.

Keep-Alive: 300, plus kolejna: „Connection: keep-alive”; Jako, że HTTP powoduje ogromną ilość połączeń przy ładowaniu jednej strony (wszystkie rysunki ładowane są za pomocą nowego połączenia), ludzie wpadli na pomysł jak można nieco zoptymalizować ten proces. „Connection: keep-alive” oznacza, aby serwer nie zamykał połączenia HTTP po prawidłowym jego obsłużeniu, ale żeby czekał na kolejne zapytanie na tym samym sockecie. Oszczędza to wiele czasu i bajtów przy czekaniu na kolejny TCP handshake, skutkując szybszym czasem załadowania strony z dużą ilością detali. Dla nas jednak ta przydatna opcja jest bezużyteczna, skoro chcemy wysłać tylko jedno zapytanie. Inna wartość, Close, jest w tym przypadku lepsza od Keep-Alive; mówi ona serwerowi, aby każde obsłużone zadanie zakończył, zamykając połączenie, dlatego zapisujemy Connection: close i wyrzucamy Keep-Alive: 300, który jest jedynie argumentem dla Connection: Keep-Alive.

Referer: Najczęściej sprawdzane pole przy sajtach warezowych, wyszukiwarkach różnego rodzaju i różnych usługach. „Zapobiega” to wykorzystaniu danego serwisu bezpośrednio „z zewnątrz”, powodując wrażenie, że jedynie strony sajta są w stanie korzystać z jakiejś usługi. Błąd. Wystarczy podać w nagłówku Referer do wewnętrznej strony, aby usługa myślała, że została przez nią wywołana. Przy okazji, w ten sposób np. Google Analytics wie, jakiej frazy użyto podczas wyszukiwania naszej strony; po przejściu ze strony wyszukiwarki na naszą stronę, pole Referer wskazuje na http://www.google.com/search?q=Tu+jest+fraza+szukania. Czasem jest przydane, w różnych celach. My zostawiamy jak jest, może być sprawdzane przez serwer. Jeśli nie jest, można opuścić, ale wysłanie go nie zaszkodzi.

Authorization: Pamiętasz to, co pisałem o HTTP Basic Authorization? Zdekoduj ten string algorytmem base64, a dowiesz się, w jaki sposób serwer „autoryzuje” Twoje żądanie. Ten nagłówek wysyłany jest przy każdym zapytaniu HTTP, więc jeśli do tej pory myślałeś, że HTTP Basic Authorization jest jakimkolwiek zabezpieczeniem przed atakami MITM, pomyśl ponownie.

Content-Type: Wartość jest stała, zgodnie ze standardem wysyłania danych z formularza. Nie należy zmieniać, trzeba zostawić jak jest.

Content-Length: Ilość bajtów danych do wysłania, najlepiej zostawić jak jest.

Pod tymi nagłówkami znajduje się znak entera, a następnie dane w takiej postaci:

restoreFlag=0&Restart=RESTART

Dane w formularzach oddziela się znakiem &, tak więc widać, ze powyższa linijka przetrzymuje dwie pary nazwa=wartość:

restoreFlag=0
Restart=RESTART

Napiszmy więc mały skrypt w Ruby, który będzie łączył się przez HTTP z obiektem http://192.168.1.100/Forms/tools_system_1, następnie wysyłał pod otwarte połączenie podane wyżej nagłówki, razem z podanymi wyżej danymi.

Implementacja

Do połączenia HTTP wykorzystam klasę HTTP, która znajduje się w pakiecie Net. Na początek należy zaimportować odpowiednie pakiety.

6 require 'net/http'
7 require 'base64'

a następnie zdefiniować jedyną funkcję w programie, wysyłającą dane pod wskazany adres.

9 def main

Stwórzmy zmienne przetrzymujące login i hasło do panelu administracyjnego.

10 login = "admin"
11 pass = "pass"

Zakodujmy parę admin:pass algorytmem base64, aby można było tak powstany ciąg znaków umieścić w nagłówku Authorization.

13 b64_auth = Base64.encode64("%s:%s" % [ login, pass ])

Składnia powyższej linii jest wbrew pozorom prosta, ale ci, którzy piszą w Pythonie już to wiedzą :). Przypomina ona działanie funkcji printf() ze standardowej biblioteki C, choć w przypadku Ruby jest częścią języka. Tak więc takie wyrażenie w Ruby: str = „%d %d %d” % [ arg1, arg2, arg3 ] jest mniej więcej równoznaczne takiemu wyrażeniu języka C: sprintf(str, „%d %d %d”, arg1, arg2, arg3).

Idąc dalej, tworzymy zmienną, która będzie przetrzymywać obiekt docelowy:

14 path = '/Forms/tools_system_1'

Tworzymy też tablicę asocjacyjną, która zawierać będzie informacje o nagłówkach:

15 headers = {
16 "User-Agent" => "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.4) Gecko/2008111318 Linux Mint/5 (Elyssa) Firefox/3.0.4",
17 "Accept" => "text/html",
18 "Accept-Charset" => "ISO-8859-2",
19 "Connection" => "close",
20 "Referer" => "http://192.168.1.100/navigation-maintenance.html",
21 "Authorization" => ("Basic %s" % b64_auth)
22 }

Pole Authorization tworzone jest przy użyciu operatora %, opisanego wyżej, przy linii 13. Kolejna linia to tworzenie obiektu HTTP, który pozwala na komunikowanie się na nieco wyższym poziomie abstrakcji niż przez sockety. Pierwszy argument to nazwa hosta, drugi to port (usługa HTTP działa zwykle na porcie 80).

24 http = Net::HTTP.new "192.168.1.100", 80

Wysyłamy zapytanie HTTP o typie POST pod obiekt docelowy wskazywany przez zmienną path, podając również dane formularza w drugim argumencie.

25 res = http.request_post path, "restoreFlag=0&Restart=RESTART", headers

Jeśli zapytanie HTTP nie powiedzie się, to znaczy zwróci status inny niż kod 200 (OK), wtedy wyjdź z funkcji zwracając false.

26 return res.code == '200'

Pod tą linijką można dopisać dodatkowy kod, który może np. czekać na to, aż router zrestartuje się całkowicie i dopiero wtedy oddawać kontrolę do powłoki, lub też dopisać kilka prostych linijek z wyświetleniem kilku prostych napisów – co kto woli :). Na końcu tego kodu należy jednak zamknąć blok funkcji:

27 end

oraz wywołać zdefiniowaną powyżej funkcję main.

29 main

To wszystko. Uruchomienie skryptu powoduje restart routera, czyli nasz cel został osiągnięty... w części. Teraz należy jedynie dopisać kod, który będzie sprawdzał okresowo, czy router został prawidłowo zrestartowany. Najłatwiej jest wysyłać po prostu zapytania HTTP do strony z właściwościami połączenia routera, na której obecny jest numer IP. Jeśli nie dostajemy żadnej odpowiedzi od routera, to znaczy, że jest aktualnie restartowany i usługa HTTP jeszcze nie działa. Jeśli dostajemy odpowiedź, to będzie to strona z numerem IP, który możemy wyłuskać za pomocą np. wyrażeń regularnych (które są wbudowane w język Ruby) i sprawdzić, czy nie zawiera on przypadkiem np. numeru 0.0.0.0. Jeśli tak jest, oznacza to, że router łączy się z ISP i nie jest jeszcze w pełni zrestartowany. Jeśli numer IP jest inny, wtedy powinien być już zrestartowany, a połączenie na naszym desktopie powinno być w pełni sprawne.

A teraz kody źródłowe w Ruby.

Pentagram Cerberus P 6311-07A
Pentagram Cerberus P 6331-6

Inne materiały:
- Use CURL to Login to Webpages with a Script (flash video, 2:30 min)
- Board JDownloader'a (po polsku) - fajnie, że verdo wykorzystał część mojej pracy, ale mniej fajnie że nazwał mnie anotononem :D

x

Komentuj

Zawartość tego pola nie będzie udostępniana publicznie.
  • Adresy internetowe są automatycznie zamieniane w klikalne odnośniki.
  • Use <!--pagebreak--> to create page breaks.
  • You may post block code using <blockcode [type="language"]>...</blockcode> tags. You may also post inline code using <code [type="language"]>...</code> tags.
  • Use <fn>...</fn> to insert automatically numbered footnotes.

Więcej informacji na temat formatowania