Próba stworzenia mechanizmu autoochrony (SELF-DEFENCE)
#1
Przez ostatnie kilka dni siedziałem, wykorzystywałem przycisk "Search" w wyszukiwarce Google aż nazbyt dużo razy, gdyż próbowałem znaleźć jakiś sposób, na autoochronę w C#. Jest to strasznie ciekawe zagadnienie, dlatego postanowiłem o tym napisać. Od razu mówię, post nie będzie zbyt ładny, tekst będzie dość precyzyjny.

UWAGA
Aby zakończyć proces uruchomiony z użyciem następującego kodu, wymagane są uprawnienia administratora (ring 1), a więc sposób ten nie jest całkowicie efektywny. Krótko mówiąc, działa on na takiej zasadzie - uruchamia proces z wirtualnego konta innego użytkownika, co wykorzystuje Windowsowe restrykcje do zablokowania możliwości ubicia procesu, jeśli nie ma się wystarczających uprawnień.

Wykorzystane źródła (większość msdn, trochę pinvoke (bez którego trwało by to wieki (znajdowanie DLL z danymi funkcjami w systemowym API pod C# <3)) i jeden blog, gość pisał niemal to samo co ja (znalazłem jak szukałem informacji o RawSecurityDescriptor)):
  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]

  • [Aby zobaczyć linki, zarejestruj się tutaj]


Cały kod jest dostępny tutaj: 

[Aby zobaczyć linki, zarejestruj się tutaj]


A więc prócz zadeklarowanych przestrzeni nazw, musimy pobawić się trochę z WinAPI, a szczególnie dwoma bibliotekami - advapi32.dll oraz kernel32.dll. Będziemy potrzebować tylko kilku funkcji.

Zacznijmy od advapi32 i funkcji GetKernelObjectSecurity (boolean)
Kod:
       [DllImport("advapi32.dll", SetLastError = true)]
       private static extern bool GetKernelObjectSecurity(IntPtr handle, int secInfo, [Out] byte[] sDescriptor, uint len, out uint len2);

Dalej advapi32 i funckja SetKernelObjectSecurity (boolean, analogicznie - tu set, tam get)
Kod:
       [DllImport("advapi32.dll", SetLastError = true)]
       private static extern bool SetKernelObjectSecurity(IntPtr Handle, int secInfo, [In] byte[] psd);
Tym razem pseudo uchwyt do procesu, który weźmiemy z kernel32.dll
Kod:
       [DllImport("kernel32.dll")]
       private static extern IntPtr GetCurrentProcess();
Teraz tworzymy obiekt RawSecurityDesriptor (znajdujący się w System.Security.AccessControl), będący naszym swego rodzaju getterem (będzie zbierał właściwości procesu poprzez uchwyt do niego).
Kod:
private static RawSecurityDescriptor GetProcessSecurityDescriptor(IntPtr processHandle)
       {
           const int DACL_SECURITY_INFORMATION = 0x00000004;
           byte[] psecdesc = new byte[0];
           uint BytesNeed;

           GetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, psecdesc, 0, out BytesNeed);
           if (BytesNeed < 0 || BytesNeed > short.MaxValue)
               throw new Win32Exception();

           if (!GetKernelObjectSecurity(processHandle,
               DACL_SECURITY_INFORMATION,
               psecdesc = new byte[BytesNeed],
               BytesNeed, out BytesNeed)) throw new Win32Exception();
               

           return new RawSecurityDescriptor(psecdesc, 0);
       }

Co tu się dzieje? Jako argument funkcji mamy podać uchwyt do aktualnego procesu. Zgodnie z MSDN tworzymy stały typ int o wartości 0x00000004 (advice WinAPI), dalej tablicę bajtów o zerowej wielkości, i typ int bez znaku, w którym określimy potem ilość bajtów które muszą zostać sparsowane. Teraz korzystamy z naszego "gettera" -> getKernelObjSec (pierwszy omówiony dllimport) z takimi argumentami, które ta funkcja WinAPI wymaga. Wyrzuci ona nam jednak do naszego uinta BytesNeed pewną ilość, która jest nam potrzebna do poprawnego działania aplikacji. Jeżeli jest < 0 albo większe od  32767 wyrzuci ona nam wyjątek (MSDN), podobnie, jeżeli operacja "gettera" się nam nie uda (jest on typem BOOL). W przeciwnym razie (który dzieje się prawie zawsze) zostanie nam zwrócony poprawny RawSecurityDescriptor (dokładnie DACL (Discretionary Access Control List)), na którym możemy spokojnie operować.

Teraz stworzyliśmy coś w stylu gettera na DACL, teraz musimy mieć możliwość ustawienia go wedle naszych własnych preferencji.
Kod:
private static void setProcessSecurityDescriptor(IntPtr processHandle, RawSecurityDescriptor dacl) //ustawiamy DACL
       {
           const int DACL_SECURITY_INFORMATION = 0x00000004;
           byte[] rawSecurityDescriptor = new byte[dacl.BinaryLength];
           dacl.GetBinaryForm(rawSecurityDescriptor, 0);
           if (!SetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, rawSecurityDescriptor)) throw new Win32Exception();
       }
W argumentach pobieramy uchwyt do naszego procesu i wcześniej wczytany RSD/DACL. Jest to zwyczajna funkcja typu void, nad którą jak myślę nie trzeba się rozwodzić.

Teraz musimy stworzyć coś w stylu listy z wartościami wykorzystywanymi przez WinAPI, a dokładniej DACL i ACE (Access Control Entries). Nadaje się do tego idealnie enum, w którym to zhardcodujemy te wartości (a w zasadzie flagi). Jak zwykle MSDN jest naszym zbawieniem

Kod:
       [Flags]
       private enum PAR //flagi dostępu do procesów --> Process Access Rights po kolei z MSDN
       {
           PROCESS_CREATE_PROCESS = 0x0080,                                        //do tworzenia procesu
           PROCESS_CREATE_THREAD = 0x0002,                                         //do tworzenia wątku
           SYNCHRONIZE = 0x00100000,                                               //synchronizacja (?)
           PROCESS_DUP_HANDLE = 0x0040,                                            //do łapania uchwytu
           PROCESS_QUERY_INFORMATION = 0x0400,                                     //do zdobywania informacji o procesie (token etc.)
           PROCESS_QUERY_LIMITED_INFORMATION = 0x1000,                             //do zdobywania innych informacji, analogiczne
           PROCESS_SET_QUOTA = 0x0100,                                             //do ingerencji w working set
           PROCESS_SET_INFORMATION = 0x0200,                                       //do ustawiania dla procesu X informacji
           WRITE_OWNER = 0x00080000,                                               //process owner - "posiadacz" procesu - chodzi o konto
           PROCESS_SUSPEND_RESUME = 0x0800,                                        //jak wskazuje nazwa, wstrzymaj lub wznów
           PROCESS_TERMINATE = 0x0001,                                             //zakończ proces
           PROCESS_VM_OPERATION = 0x0008,                                          //zawiera w sobie funkcję - WriteProcessMemory (kernel32.dll -> zawiera się w <windows.h>)
           PROCESS_VM_READ = 0x0010,                                               //do odczytywania pamięci procesu
           PROCESS_VM_WRITE = 0x0020,                                              //do nadpisywania pamięci procesu
           DELETE = 0x00010000,                                                    //do usuwania niektórych rzeczy
           READ_CONTROL = 0x00020000,                                              //kontrola odczytu          
           WRITE_DAC = 0x00040000,                                                 //advice DACL
           STANDARD_RIGHTS_REQUIRED = 0x000f0000,                                  //nie mam pojęcia jak to wyjaśnić, ale nazwa STANDARD_RIGHTS_REQUIRED powinna być wystarczająca ;)
           PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF),  //reszta
       }
 
I w zasadzie tyle. Teraz pozostało nam tylko stworzyć nowe ACE i powiedzieć programowi, aby z nich korzystał. Robi się to mniej więcej tak (żywcem z tamtego bloga):
Kod:
           IntPtr hProcess = GetCurrentProcess();
           var dacl = GetProcessSecurityDescriptor(hProcess);
           dacl.DiscretionaryAcl.InsertAce(0, new CommonAce(AceFlags.None, AceQualifier.AccessDenied /* blokujemy dostęp do procesu */, (int)PAR.PROCESS_ALL_ACCESS, new SecurityIdentifier(WellKnownSidType.WorldSid, null), false, null));
           SetProcessSecurityDescriptor(hProcess, dacl);

Ja to sobie wrzuciłem do voida, i mam to jako funkcję. Spróbujmy teraz dany program tak uruchomiony "zabić". (polecenie taskkill /im <nazwapliku>)

[Aby zobaczyć linki, zarejestruj się tutaj]

Jak widać działa! Nie mamy możliwości zakończenia działania procesu bez uprawnień administratora.
To by było na tyle, jeżeli macie pomysły jak lepiej napisać taki mechanizm, proszę piszcie, także, jak czegoś nie rozumiecie również. Liczę na Ciebie @TW Tongue
0x DEADBEEF
Odpowiedz


Wiadomości w tym wątku
Próba stworzenia mechanizmu autoochrony (SELF-DEFENCE) - przez chomikos - 14.04.2016, 21:40
RE: Próba stworzenia mechanizmu autoochrony (SELF-DEFENCE) - przez Konto usunięte - 16.04.2016, 12:31

Skocz do:


Użytkownicy przeglądający ten wątek: 1 gości