W moim przypadku była to wielka miłość do programowania. Mimo, iż już od 4 lat tym się tak nie interesuję jak niegdyś, to postanowiłem trochę powspominać te piękne czasy i zaprezentować z nich to, co najciekawsze.
Wpis ten nie jest poradnikiem na temat łamania zabezpieczenia, choć może tak wyglądać. Jest to, mam nadzieję, niezwykle ciekawa informacja z dającym do myślenia morałem, jak łatwo oszukać komputer na swoją korzyść. Jest to również moja sentymentalna opowieść o tym, jak wiedza na temat tworzenia może zostać użyta do innych celów, takich jak łamanie programów. Jeżeli zastanawiałeś się kiedyś kim są Ci kolesie podpisujący się w notatniku i dostarczający małe pliczki o takiej samej nazwie jak aplikacja, z tym, że te dostarczone przez nich nie pytają o hasło. Od razu zaznaczam, że nie byłem żadnym crackerem, tylko interesowałem się tym z czystej ciekawości i w celu sprawdzenia swoich umiejętności. Tak więc opowiem o tym, jak to się robi oraz o tym, że nikt tak nie umie łamać programów jak sami programiści. Dlaczego? Bo nikt tak dobrze nie zna zasad działania programów, jak ich twórcy.
Moja przygoda z programowaniem zaczęła się dosyć szybko licząc od momentu posiadania pierwszego komputera. Programowaniem, a w szczególności w języku C++ zacząłem interesować się jakieś 10 lat temu. Uczyłem się z książek, bardzo grubych zresztą i nie jednej. Ta fascynacja trwała chyba jakieś 5 lat. W tym czasie poznałem bardzo wiele języków programowania i ciągle było mi mało. Po C++ przyszedł czas na Assemblera, choć dziś praktycznie nieużywany, to wydanej kasy na książkę ani poświęconego czasu nie żałuję. To była jedna z najciekawszych rzeczy jakich nauczyłem się o komputerach. Ach, to były czasy, aplikacja napisana w Assemblerze ważyła parę KB. W każdym bądź razie, po poznaniu sposobu w jaki tworzy się programy oraz jak system i procesor je wykonują, w którymś momencie mojej nauki dotarło do mnie, że wiem już tak dużo, iż mógłbym trochę namieszać w tych programach nawet nie mając ich kodu. Cracowanie nie występuje pod Linuksem bo sam możesz zmieniać kod. W Windows, nie mogłeś sprawdzić jak oni to zrobili pisząc swój program. Linux daje Ci taką możliwość, dlatego jest w pewnym sensie rajem dla programistów. Sam na niego przeszedłem między innymi z tych względów.
Kiedyś były takie programiki, które ściągało się z sieci w wersji trial. Zazwyczaj miały w menu pozycję „Pomoc” albo „About”, w której znajdowało się takie polecenie jak „Rejestracja”, gdzie należało wpisać swój klucz. Powiem szczerze, że zdarzało mi się kiedyś takie programy „rejestrować” ale to stary sposób zabezpieczeń, chyba dziś już niespotykany w nowych programach. Dlaczego to robiłem? Bo gdy przez parę lat myśli się o programowaniu to człowiek w pewnym momencie przekracza pewną granice abstrakcji i patrząc na przycisk widzi kod pod nim, patrząc na pole do wpisywania tekstu widzi kod pod nim itd. i to w dwóch językach, ludzkim - C++ i maszynowym - Assembler.
Dziś korzystając z okazji, że na VirtualBox ma zainstalowany Windows 7 (legalny, z MSDNAA )zainstaluję co trzeba, napisze programik proszący o hasło z przyciskiem sprawdzającym poprawność kodu. Oczywiście będzie on tworzony tylko do celów prezentacyjnych, więc wiele aspektów zostanie w nim pominiętych – absolutne minimum. Wszystko po to, aby pokazać jak łatwo znając doskonale zasady działania programów komputerowych można taki program oszukać. Kiedyś stwierdziłem, że zabezpieczający takie programy oraz ten, który próbuje je łamać mają takie same możliwości, ponieważ obaj działają na tym samym gruncie. Oczywiście Ci drudzy mają trudniejsze zadanie ale i tak wygra sprytniejszy. Przykład, który zaprezentuję udowodni jak w logiczny sposób złamie program i stworze cracka zmieniając tylko jedne bajt w kodzie programu. Wiele rzeczy dla wielu może okazać się niezrozumiałych i tak powinno być
Zanim jednak wyruszymy na poszukiwania tego jednego bajta musimy go najpierw stworzyć.
Zaczynamy od ściągnięcia Borland C++ Builder 6 Personal, aby napisać taki program szybko. Znajdziemy go np. tu. Dlaczego wybrałem C++ Builder? Lubię go i chcę uniknąć pisania w WinAPI ale to nie oznacza, że nie trzeba go znać, wręcz przeciwnie.
Projekt programu oraz kod przedstawiam na zdjęciu.
Jak widzimy program jest bardzo prosty, posiada pole (Edit) do wpisania hasła i przycisk (Button) do sprawdzenia czy hasło jest poprawne. Kod również jest prosty. Gdy wciśniesz przycisk, program pobierze zawartość pola tekstowego i sprawdzi czy jest takie jak powinno. Ja jawnie napisałem w kodzie jaka to ma być wartość: „password” ale tak się nie robi. Nie zmienia to faktu, że przed samym porównaniem program i tak będzie musiał znać wartości, które będzie porównywał, więc i tak byśmy ją dorwali Gdy wpiszemy w pole ciąg znaków „password” pokaże się komunikat o treści „kod prawidłowy”, w przeciwnym przypadku pokaże się komunikat „zły kod”.
Ok, mamy już te cudo. Jak wpisze zły kod to zobaczę komunikat o złym kodzie.
Tylko z samej obserwacji programu można założyć jak jest stworzony. Jego kod mógłby wyglądać tak:
if (to_co_wpisałem == to_co_powinno_być) // jedziesz dalej else // nigdzie nie pojedziesz
Co by się przed tą instrukcją nie działo, to program misi znać obie wartości już w tym momencie, chociażby ściągał poprawne hasło z FBI. Ale to nie wszystko, załóżmy specjalne okulary i zobaczymy jak to widzi procesor:
cmp reg, mem
je jump_to
Co w wolnym tłumaczeniu znaczy: porównaj dwie wartości (cmp - compare), jeżeli są równe skocz w wskazane miejsce (je - jump if equal), jeżeli nie, kontynuuj wykonywanie instrukcji. To tylko przykładowy kod wzięty z głowy, ten oryginalny może wyglądać inaczej, ale zasada działania będzie taka sama. To oczywiście nie jest takie proste, trzeba jeszcze uwzględnić zmianę kodu assemblerowego po optymalizacji kompilatora. Jak mówiłem to najprostszy przykład i dosyć okrojony, bo procesor przed wykorzystaniem zmiennych musi jeszcze załadować nimi rejestry itp..
Teraz należy się zastanowić co by się stało, gdybym odwrócił logikę kodu assemblera? Aby procesor wykonywał skok tylko wtedy, gdy porównywane wartości są różne. W takim wypadku program akceptowałby wszystkie hasła oprócz prawidłowego – chyba nie będziesz miła takiego pecha, że wpiszesz prawidłowe . Czyli nasz crack będzie działał na wszystko, oprócz poprawnego hasła, ale oni – użytkownicy – tego nie wiedzą hehehe. Czasami też się robi po prostu NOP-a ale to może mieć fatalne skutki w dalszym działaniu programu.
Teraz potrzebujemy debuggera, mój ulubiony to OllyDbg, bez problemu go znajdziecie. Skoro już go mamy, to uruchamiamy w nim nasz programik.
Teraz bez porządnej wiedzy się nie obędzie. Debugger załadował program, wybieramy polecenie ze zdjęcia.
Ważne jest, że jesteśmy w module Project1. Wyszukujemy wszystkie odwołania do innych modułów (funkcji bibliotek), z których korzysta program. W tym przypadku będzie to VLC. W innym przypadku byłoby to WinAPI i szukalibyśmy funkcji GetWindowText. My szukamy miejsca w kodzie, w którym pobierane jest to co wpisaliśmy. Była to funkcja GetTextBuf. Więc szukamy takiej i zastawiamy na nią pułapkę – breakpoint. W tym momencie znajomość funkcji, z których składa się program jest wyznacznikiem do ustalenia tego, czego szukamy. Tak naprawdę jest to o wiele bardziej skomplikowane niż przedstawiłem, zazwyczaj trzeba ustalić w czym został napisany program. Można też bazować na funkcjach systemowych, czyli WinAPI.
Wracamy do okna z kodem i uruchamiamy aplikacje. Wpisujemy w pole cokolwiek i wciskamy przycisk, debugger zatrzyma działanie programu na wywołaniu tej funkcji, teraz możemy przeanalizować kod.
Jak widzicie już w kodzie widać nasze hasło, ale zazwyczaj tak nie jest. Teraz używając klawisza F8 wykonujemy program krok po kroku ale bez wchodzenia w wywoływane funkcje, inaczej skończylibyśmy w jakiejś funkcji systemu operacyjnego, które zresztą należy dobrze znać. W przedstawionym miejscu już widzę w rejestrach procesora wartości, które za chwilę zostaną porównane (okno po prawej).
Przede mną widzę wywołanie funkcji strcmp(), której użyłem i wiem do czego używają jej inni programiści. Wiemy, że w kodzie jest ona w samym warunku if. Parę instrukcji dalej widzę jedyne wykonanie instrukcji skoku: JNE SHORT 00401BFF, a na samym dole okna widzę już wywołanie funkcji ShowMessage. Teraz wystarczy zmienić warunek.
Na krok przed samym skokiem dokonuje jego edycji, aby przetestować działanie programu przed stworzeniem cracka.
Zmieniam wartość 75 na 74 – są to wartości w systemie szesnastkowym oczywiście. Instrukcje assemblera oraz odpowiadające im kody również można znaleźć w sieci, a najlepiej na stronie Intela. Ja akurat pamiętałem na co mam zmienić.
W efekcie zmieniłem warunek z JNE „jeżeli nie równy” na JE „jeżeli równy”. Teraz odpalamy program dalej przyciskiem niebieskiej strzałki w pasku narzędzi lub wcisnąć F9. Powinniśmy zobaczyć pozytywne rozpatrzenie naszej sprawy :)
hehe
Zazwyczaj przy takich zmianach trzeba być bardzo uważnym i precyzyjnym. Ponieważ jakby nie było nasza zmiana będzie miała wpływ na późniejsze zachowanie programu i należy to umieć przewidzieć. Nie raz mi się zdarzało, że udawało mi się wyświetlić okno o udanej rejestracji ale program nadal był niezarejestrowany. Po prostu miał inne zabezpieczenia. Pamiętam taką sytuację, gdy podczas badania pewnego programu natrafiłem na wiadomość (zdjęcie) od programistów, która co tu dużo mówić dawała do zrozumienia, że oni są od Ciebie lepsi – ale wtedy to się uśmiałem hehe i odpuściłem sobie – respect.
Teraz potrzebujemy hex edytora i musimy ustalić czego należy w nim szukać. Jako edytora użyłem WinHex, również znajdziecie go bez problemu.
Teraz trzeba zinterpretować kod z debuggera. Po lewej stronie instrukcji widzimy odpowiadające im wartości heksadecymalne. Ale hola hola. Część z nich jest stała, a część zmienna – program poznaje te wartości dopiero w trakcie wykonywania. Niemniej jednak są to wartości zmienne ale o stałej wielkości. Ja to analizuje tak: instrukcje ADD, TEST i warunek JNE powinny być niezmieniane, szukam więc ciągu: 83 C4 08 85 C0
Ustawiam kursor na bajcie o wartości 75 i zmieniam go na 74. Zapisuję zmieniony plik pod inną nazwą np. Project1_crack.exe.
Teraz możesz odpalić zmodyfikowaną aplikacje i przetestować jak działa.
Przedstawiony przykład pokazuje jak niewiele trzeba, aby złamać program.