C# połączenie z "panelem" routera

0

Witam,

zawsze mam stres-a przed napisaniem na forum bo myślę, że zadając pytanie i przedstawiając problem - wygłupię się. Ale cóż - zaryzykować trzeba.

Chciałem napisać taką "treningową" aplikację która będzie działała w tle i jeżeli stracę połączenie z internetem to zaloguje się do routera i spróbuje go zresetować. Przyda mi się coś takiego w paru przypadkach, a przy okazji dowiem się czegoś o C#. Pierwszą część mam załatwione i wykrywam pomyślnie utratę połączenia z internetem. Ale teraz druga sprawa - logowanie się do routera. Wyczytałem gdzieś, że można to zrobić używając Basic Authentication i metody GET. Patrzę jak to wygląda w routerze i faktycznie chyba jest to metoda GET (bo przecież jeszcze jest POST. Ale gdy robię metodą POST to router odpowiada mi kodem 501). Gdzieś w sieci wyczaiłem, że trzeba jeszcze modyfikować nagłówek ["Authorization"]. Do kodu - mój kod wygląda tak:

// sUserLogin = TestUser 
// sUserPassword= TestPassword
Uri uri = new Uri("http://192.168.1.1/");
var webRequest = WebRequest.Create(uri);
webRequest.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(sUserLogin + ":" + sUserPassword));
webRequest.Method = "GET";
webRequest.Credentials = new NetworkCredential(sUserLogin, sUserPassword);

try
{
    using (var webResponse = webRequest.GetResponse())
    {
         // Log który zrzuca mi zawartość Headers
         Console.WriteLine(webResponse.Headers.ToString());
         using (var responseStream = webResponse.GetResponseStream())
         {
               var webResponseReader = new StreamReader(responseStream);
               var result = webResponseReader.ReadToEnd();
               
               // W konsoli zrzucam sobie odpowiedz oddzielając ja abym lepiej widział
               Console.WriteLine("--------------------------------------------");
               Console.WriteLine("");
               Console.WriteLine(result);
               Console.WriteLine("");
               Console.WriteLine("--------------------------------------------");

               webResponseReader.Close();
               webResponse.Close();
          }
    }
}
catch (Exception we)
{
       Console.WriteLine(we.ToString());
}

W logach z Headers dostaję:
Connection: close
Content-Type: text/html
Server: Router Webserver
WWW-Authenticate: Basic realm="TP-LINK LTE Wireless N Router MR6400"

a ten log z odpowiedzi zrzuca mi stronę logowania do routera. Zmieniam url na taki http://TestUser:[email protected] - i dostaję taką samą sytuację. Zauważyłem, że gdy zaloguję się przez przeglądarkę - to w url panelu admina routera mam ciąg znaków np. http://192.168.1.1/**WOOEJDLBJESVYRAA**/userRpm/Index.htm - to jest pewnie coś zakodowanego w jakiś sposób (zapewne Base64). Tak myślę. Ale może przez to nie mogę się zalogować? Może teraz głupio napiszę ale zastanawia mnie to jak takie logowanie powinno wyglądać - czy w logu gdzie mam odpowiedź powinienem dostać kod strony panelu administracyjnego? Dlaczego jestem ciągle przekierowywany na stronę logowania? Czy ogólnie da się w ten sposób zalogować?

Na pewno błąd wynika z mojej niewiedzy. Przyznam, że działam troszkę na ślepo. Proszę uprzejmie o jakieś wskazówki. Wiadomo - nie wymagam gotowego rozwiązania itp. Jeżeli wygłupiłem się tym poście lub w tym poście to proszę mimo wszystko proszę o zachowanie kultury.

PS. Myślałem też o tym aby pobierać elementy z strony, wpisywać dane, programowo wciskać przycisk itp. Ale najpierw chciałbym ogarnąć takie logowanie.

Pozdrawiam

1

Chwila, a nie da się tego zrobić ustawiając odpowiednia opcję w routerze, może słabo szukałeś, można by wypróbować resetować komendą z wiersza poleceń jeżeli się to da zrobić. Jest co nieco na internecie o łączeniu się telnetem do routera, może by tak to zrobić.

1

https://stackoverflow.com/questions/37640733/c-sharp-script-for-rebooting-tp-link-router w ostatnim poście masz przykład jak zresetować router programistycznie za pomocą usługi windows z poziomu visual basic. W C# będzie to samo chyba sobie przetłumaczysz.

0

Ruszyło! Coś się udało :). To będzie dłuższy wpis

Z góry przepraszam za moje milczenie. Z powodów osobistych nie mogłem zająć się wystarczająco problemem - wiem, że mogłem dać znać w wątku ale nie chciałem spamować.

Odniosę się do odpowiedzi w wątku - użytkownik @Grzegorz Świdwa miał rację - chciałbym się nauczyć pisać coś takiego w C#. Ale rozwiązanie, sposób, pomysł jaki proponuje @usm_auriga jest jak najbardziej w porządku - jednak dalej będę upierał się przy wykorzystaniu języka C#. Ale @usm_auriga bardzo dziękuję za udzielanie się w temacie i szukanie rozwiązania.

Myślenie użytkownika @Afish - podpowiedziało mi trop jakiego się zacząłem trzymać przy rozwiązywaniu kłopotu.

Do rzeczy z mojej strony: W końcu udało mi się posiedzieć nad problemem i zrobiłem to tak, że zgodnie z logiczną wskazówką @Afish zacząłem przyglądać się ruchowi sieciowemu jaki odbywa się gdy loguję się do swojego routera. Przez narzędzia "Zbadaj element" od przeglądarki, ale później przez program Fiddler (który pozwolił mi na dokładniejsze spojrzenie na nagłówki żądań) - zobaczyłem jak to wszystko wygląda od środka. Zamiast próbować odtworzyć wszystko poprzez curl - zacząłem odtwarzać żądanie bezpośrednio w C#. Żądanie - zwracane przez Fiddler w momencie logowania wygląda tak:

**Client**
	Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
	Accept-Encoding: gzip, deflate
	Accept-Language: pl,en-US;q=0.7,en;q=0.3
	User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0

**Cookies**
	Cookie
		Authorization=Basic%20<tutaj ciąg znaków - nie podaję bo wrażliwe dane>%3D%3D
		
**Miscellaneous**
	Refer: http://192.168.1.1/

**Security**
	Upgrade-Insecure-Requests: 1

**Transport**
	Connection: keep-alive
	Host: 192.168.1.1

Za nim jednak zobaczyłem to żądanie przyjrzałem się bliżej temu co zwraca mi konsola mimo moich nieudanych prób logowania się do routera. Mianowicie zwracała mi oczywiście kod mojej strony. Aby było wygodniej zajrzałem w źródło w samej przeglądarce i tam zobaczyłem taką funkcję:

function PCSubWin()
{
	if((httpAutErrorArray[0] == 2) || (httpAutErrorArray[0] == 3))
	{
		if(true == CheckUserPswInvalid())
		{
			var username = $("userName").value;
			var password = $("pcPassword").value;
			if(httpAutErrorArray[1] == 1)
			{
				password = hex_md5($("pcPassword").value);
			}
			var auth = "Basic "+ Base64Encoding(username + ":" + password);
			document.cookie = "Authorization="+escape(auth)+";path=/";
			location.href ="/userRpm/LoginRpm.htm?Save=Save";
			//$("loginForm").submit();
			//location.href ="../userRpm/Index.htm";
			return true;
		}
		else
		{
			$("note").innerHTML = "NOTE:";
			$("tip").innerHTML = "Username and password can contain between 1 - 15 characters and may not include spaces.";
		}
	}
	return false;
}

W tej funkcji dokładnie widać jaka jest procedura postępowania z danymi do logowania. Warto zwrócić uwagę, że samo hasło jest "haszowane" (tak chyba można to powiedzieć co?) przez md5. Następnie login wraz z dwukropkiem oraz "zahaszowanym" hasłem (kłania się konstrukcja basic authentication czyli login:hasło@url) jest poddany kodowaniu Base64 i jest sklejane z słowem "Basic " (i spacją po tym słowie!); Ta linia to pokazuje:

if(httpAutErrorArray[1] == 1)
			{
				password = hex_md5($("pcPassword").value);
			}
			var auth = "Basic "+ Base64Encoding(username + ":" + password);

Następnie jak można zauważyć wszystko jest zapisywane do ciasteczka! Nazwa ciasteczka to "Authorization" a zawartość to nasze sklejone zdanie poddane funkcji escape z JavaScript (The deprecated escape() function computes a new string in which certain characters have been replaced by a hexadecimal escape sequence. Use encodeURI or encodeURIComponent instead.)

Od razu myślę - trzeba to odtworzyć. Najpierw utworzenie i pisanie do ciasteczek w C#. Sprawa okazała się prosta i zaraz będzie widoczne to w kodzie - ale została nam sprawa tego aby poprawnie najpierw md5 na haśle, a potem encoding base64 na loginie:hasło. Sprawę odtworzenia funkcji md5 na haśle załatwiłem przez wbudowane funkcje z Cryptography C#. Napisałem swoją funkcję, ale ta zwracała inny hasz hasła niż powinien być. Ale zobaczyłem, że w kodzie javascript z źródła strony jest to funkcja hex_md5(passowrd). Czyli trzeba stringa z haszem zwrócić heksadecymalnie. Zrobiłem to poprzez metodę C# .ToString("X2");
Kod całej funkcji poniżej. Od razu mówię, że tą funkcję można znaleźć w internecie - ale gdzieś tam doszedłem do tego również sam. Kod rozumiem - a to jest najważniejsze:

public string calculateMD5Hash(string input)
        {
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
            byte[] hash = md5.ComputeHash(inputBytes);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("X2"));
            }

            return sb.ToString();
        }

Czyli mamy hasz hasła. Następny krok to poddanie go kodowaniu do stringa Base64. Załatwiłem to przez skorzystanie w C# z Convert,ToBase64String(), a dokładniej

Convert.ToBase64String(Encoding.Default.GetBytes(sUserLogin + ":" + hashedString))

Pozostała kwestia jeszcze odtworzenia funkcji escape. Tutaj zaczęły się poszukiwania w internecie. Natrafiłem na coś takiego:

public static string escape(string str)
        {
            string str2 = "0123456789ABCDEF";
            int length = str.Length;
            StringBuilder builder = new StringBuilder(length * 2);
            int num3 = -1;
            while (++num3 < length)
            {
                char ch = str[num3];
                int num2 = ch;
                if ((((0x41 > num2) || (num2 > 90)) &&
                     ((0x61 > num2) || (num2 > 0x7a))) &&
                     ((0x30 > num2) || (num2 > 0x39)))
                {
                    switch (ch)
                    {
                        case '@':
                        case '*':
                        case '_':
                        case '+':
                        case '-':
                        case '.':
                        case '/':
                            goto Label_0125;
                    }
                    builder.Append('%');
                    if (num2 < 0x100)
                    {
                        builder.Append(str2[num2 / 0x10]);
                        ch = str2[num2 % 0x10];
                    }
                    else
                    {
                        builder.Append('u');
                        builder.Append(str2[(num2 >> 12) % 0x10]);
                        builder.Append(str2[(num2 >> 8) % 0x10]);
                        builder.Append(str2[(num2 >> 4) % 0x10]);
                        ch = str2[num2 % 0x10];
                    }
                }
            Label_0125:
                builder.Append(ch);
            }
            return builder.ToString();
        }

Nie wiem czy to dobrze, że tej funkcji zaufałem - ale działa. Po części ale tej mniejszej ją rozumiem jednak jest tutaj troszkę więcej dla mnie magii.

Czyli mamy zrobione. Teraz mogę pokazać kod z zapisem ciasteczka oraz działania z funkcjami:

// Tworzę kontener na ciasteczka - gdzieś wyczytałem, że należy umieścić go przed tworzeniem requesta ale to chyba znaczenia nie ma
CookieContainer cookieContainer = new CookieContainer();

// Tworzę request
var webRequest = (HttpWebRequest)WebRequest.Create(uri);

// ta część kodu później. Część tycząca się ciasteczek:

// MD5 na haśle
string hashedString = calculateMD5Hash(sUserPassword);

// Tworzę ciasteczko! 
Cookie cookieAuth = new Cookie("Authorization", escape("Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(sUserLogin + ":" + hashedString)))) { Domain = uri.Host };

// Próba zapisu ciasteczka
 try
            {

                cookieContainer.Add(cookieAuth);
                webRequest.CookieContainer = cookieContainer;
                Console.WriteLine("Cookies was delicious");
            }
            catch(Exception ex)
            {
                Console.WriteLine("EXCEPTION COOKIE: " + ex.ToString());
                return;
            }

Tak wygląda mój zapis odpowiednio przygotowanego ciasteczka do requesta.
Dalej zaczęła się walka z tym aby odtworzyć całą zawartość żądania z Fiddlera. Odpowiednie modyfikowanie nagłówka i patrzenie na logi pomogły mi w odtworzeniu tego co chciałem. Kod całości na końcu. Chciałem wspomnieć o dwóch ważnych rzeczach:

Pierwsza to odpowiedni link:
Używałem czegoś takiego:

Uri uri = new Uri("http://192.168.1.1/");

Jednak zobaczyłem w kodzie strony oraz logach z Fiddlera, że strona na jaką zostaję przekierowany ma taką postać http://192.168.1.1/userRpm/LoginRpm.htm?Save=Save.
Od razu zmieniłem link na:

Uri uri = new Uri("http://192.168.1.1/userRpm/LoginRpm.htm?Save=Save");

Drugą ważną rzeczą bez której nie chciało to działać to ta linijka kodu:

webRequest.AllowAutoRedirect = true;

Wiem o co chodzi ale gdybym miał tłumaczyć to pewnie ośmieszyłbym się bardzo mocno.

Na wszelki wypadek również dodałem coś takiego:

webRequest.Method = "GET";

Chociaż chyba domyślnie zawsze jest to GET - jednak wolałem to dodać do kodu.

Dobra za nim kod to jeszcze - czy to w ogóle działa? Zadziałało! Tak mi się wydaje - bo w konsoli dostałem w końcu zwrot taki:

<body><script language="javaScript">window.parent.location.href = "http://192.168.1.1/TOGSONLASQSKVCWB/userRpm/Index.htm";
</script></body></html>

**W końcu otrzymałem ciąg znaków znaków w linku o którym wspominałem w pierwszym poście. Czyli mogę się dostać do ustawień routera. To znaczy mam taką nadzieję - bo temat będę dalej kontynuował i dalej zgłębiał aby osiągnąć główny cel - restart routera. Mam nadzieję, że to co otrzymałem - jest poprawnym wynikiem i da się z tym coś zrobić. Zdobyłem dość sporo wiedzy ale dalej za mało aby być 100% pewien dalszego postępowania. Ale chyba przez odpowiednie przejścia do stron - linków i wywołania metod - się uda. Zobaczymy. Chyba, że właśnie źle rozumuję i to nie jest poprawny wynik. Dlaczego tego nie sprawdziłem? Bo piszę odpowiedź na szybko po udanej walce :).

Kod całości funkcji łączącej się z routerem:


public void ConnectWithRouter()
{
	// Adres url
	Uri uri = new Uri("http://192.168.1.1/userRpm/LoginRpm.htm?Save=Save");

	// Tworzę kontener na ciasteczka
	CookieContainer cookieContainer = new CookieContainer();

	// Tworzenie żądania z użyciem metody GET
	var webRequest = (HttpWebRequest)WebRequest.Create(uri);

	// Flaga bez której nie działało
	webRequest.AllowAutoRedirect = true;

	// Uzupełnianie Nagłówka
	webRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
        webRequest.Headers.Add("Accept-Encoding", "gzip, deflate");
        webRequest.Headers.Add("Accept-Language", "pl,en-US;q=0.7,en;q=0.3");
        webRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0";
        webRequest.Headers.Add("Upgrade-Insecure-Requests: 1");
        webRequest.Referer = "http://192.168.1.1/";

	// Tworzenie poświadczeń
	webRequest.Credentials = new NetworkCredential(sUserLogin, sUserPassword);

	// Określanie metody
	webRequest.Method = "GET";

	// Haszowanie hasła czyli md5 zwracane w postaci hex
        string hashedString = calculateMD5Hash(sUserPassword);

	// Tworzenie ciasteczka
	Cookie cookieAuth = new Cookie("Authorization", escape("Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(sUserLogin + ":" + hashedString)))) { Domain = uri.Host };
	
	// Próba zapisu ciasteczka
	try
            {

                cookieContainer.Add(cookieAuth);
                webRequest.CookieContainer = cookieContainer;
                Console.WriteLine("Cookies was delicious!");
            }
            catch(Exception ex)
            {
                Console.WriteLine("EXCEPTION COOKIE: " + ex.ToString());
                return;
            }

	// Tutaj log ciasteczka - ale to niepotrzebne jest:
	CookieCollection cookieColl = webRequest.CookieContainer.GetCookies(uri);
            Console.WriteLine("Cookie before get response (from webRequest): ");
            foreach (Cookie cook in cookieColl)
            {
                Console.WriteLine("Cookie:");
                Console.WriteLine($"{cook.Name} = {cook.Value}");
                Console.WriteLine($"Domain: {cook.Domain}");
                Console.WriteLine($"Path: {cook.Path}");
                Console.WriteLine($"Port: {cook.Port}");
                Console.WriteLine($"Secure: {cook.Secure}");

                Console.WriteLine($"When issued: {cook.TimeStamp}");
                Console.WriteLine($"Expires: {cook.Expires} (expired? {cook.Expired})");
                Console.WriteLine($"Don't save: {cook.Discard}");
                Console.WriteLine($"Comment: {cook.Comment}");
                Console.WriteLine($"Uri for comments: {cook.CommentUri}");
                Console.WriteLine($"Version: RFC {(cook.Version == 1 ? 2109 : 2965)}");

                // Show the string representation of the cookie.
                Console.WriteLine($"String: {cook}");
            }

	// Próba wysłania żądania i odczytanie odpowiedzi
	try
            {
                using (var webResponse = (HttpWebResponse)webRequest.GetResponse())
                {
                    Console.WriteLine(webResponse.Headers.ToString());
                    using (var responseStream = webResponse.GetResponseStream())
                    {
                        var webResponseReader = new StreamReader(responseStream);
                        var result = webResponseReader.ReadToEnd();

                        Console.WriteLine("--------------------------------------------");
                        Console.WriteLine("");
                        Console.WriteLine(result);
                        Console.WriteLine("");
                        Console.WriteLine("--------------------------------------------");

                        webResponseReader.Close();
                        webResponse.Close();
                    }
                }
            }
            catch (Exception we)
            {
                Console.WriteLine(we.ToString());
            }
}

Tak wygląda kod który działa i zwraca mi wynik jaki pokazywałem wyżej. Wygląda na to, że ta część kodu działa. Gdy pójdę dalej do celu - to poinformuję oczywiście w tym temacie. Mam nadzieję, że komuś to się przyda. Raz jeszcze dziękuję użytkownikom którzy dotychczas próbowali ze mną walczyć. Jedzeimy dalej do celu!!! Oby to było łatwiejsze :P

1 użytkowników online, w tym zalogowanych: 0, gości: 1