sobota, 31 lipca 2010

Zdałem egzamin DB2 9 Fundamentals

Wczoraj miałem okazje aby podejść do egzaminu DB2 9 Database and Application Fundamentals. Egzamin był przygotowywany przez firmę IBM we współpracy z Politechnika Wrocławską w ramach jakiś tam praktyk studenckich. W samych praktykach nie brałem udziału, ale mogłem podejść do tego egzaminu.
Ogółem wcześniej nie zajmowałem się DB2 i nic nie wiedziałem na temat ich ścieżek egzaminacyjnych. O możliwości robienia certyfikatu dowiedziałem sie od przyjaciółki (dzięki Agata) 5 dni wcześniej. No ale skoro nic nie płace, nic nie tracę więc czemu nie? Zawieszam chwilowo pracę nad projektem zwanym magisterką i biorę sie za DB2.

Dla studentów biorących udział w DB2 Academic Associate Workshop były zorganizowane 2 wykłady i ćwiczenia laboratoryjne. Na ćwiczeniach dostawało się książkę i płytę. W książce były slajdy z wykładów przeplatane ćwiczeniami do wykonania. Na płytce był obraz VMware systemu SUSE z zainstalowaną bazą danych. Co prawda wszystkich ćwiczeń na tym obrazie nie można było wykonać (wersja czegoś tam się nie zgadzała) ale w laboratorium był dostępny właściwy obraz. Zgrałem go sobie na laptopa i mogłem się później w domu bawić. Książkę i płytkę odebrałem we wtorek i miałem do piątku czas aby się przygotować do egzaminu.

Poświęciłem trochę czasu, aby zaznajomić się z książką i zawartymi w niej ćwiczeniami. W większości przypadków wiązało się to z przepisywaniem komend i obserwowaniem rezultatów. Przez niektóre ćwiczenia się szło jak burza, a przez niektóre o trochę wolniej (z powoku konieczności przepisywania długich zapytań SQLowych).

Ciekawostką i nowością dla mnie był typ kolumny przechowujący XML'e i zapytania operujące na nich. Przydało by się lepsze wytłumaczenie co i jak gdyż jest to świeży wynalazek i puki co na studiach jeszcze o tym dokładniej nie uczą. Po za omawianą książką przyglądałem się również przykładowym pytaniom i odpowiedziom. Dało mi to dużą wiedzę i wyjaśniło pewne sprawy.

W piątek był egzamin który zdawała spora ilość studentów podzielonych na kilka tur. Po załatwieniu spraw formalnych w końcu można było przystąpić do egzaminu. Na egzamin było przeznaczone 1,5h i 60 pytań. Było to w formie testu jednokrotnego wyboru i trochę pytań się powtórzyło z tych przykładowych. Ogółem było łatwo i w porównaniu z SCJP egzamin był o 2 rzędy wielkości trudniejszy (ciekaw jestem jakiej skali do wyrażenia trudności można by tu użyć). Na egzaminie z DB2 nie było pytań gdzie w dopowiedziach trzeba wybrać czy wskazany kod jest błędny czy robi cos innego. Na SCJP nie dość że kod może być błędny to jeszcze należy wskazać czy to błąd czasu wykonania czy kompilacji - dlatego m.in. uznałem że SCJP jest o 2 rzędy trudniejszy.

Jako wynik uzyskałem 61.67% przy 60% wymaganych do zdania (dwie błędne odpowiedzi więcej i bym nie zdał). Czy jestem zadowolony z wyniku? Niespecjalnie. Po pierwsze uzyskałem niski wynik (choć biorąc pod uwagę czas który miałem na naukę to fuks że zdałem).  Dużo jest jeszcze do nauczenia jeśli rzeczywiście chciałbym korzystać z tej bazy danych w przyszłości. Po drugie niespecjalnie podoba mi się idea takich masowych egzaminów. Dla studentów początkowych lat studiów może być to fajnie (WOW patrz zdałem certyfikat…). Może to również być nowe doświadczenie taki egzamin i wielki prezent od IBM’a, że zorganizował taki egzamin za darmo. Z drugiej strony taka masówka obniża wartość takiego certyfikatu (bo go ma dużo osób i nie był specjalnie trudny) i nie czyni go takim fajnym, z którego zdania można być dumnym.

Podsumowując będzie kolejny wpis w CV, ale nie był to dla mnie tak ważny (i wartościowy) egzamin jak SCJP. Trzeba teraz trochę odpocząć i wrócić do magisterki.

poniedziałek, 26 lipca 2010

Dekompilacja kodu w J2ME

Kiedyś ściągnąłem pewną aplikację (napisaną w J2ME) na mój telefon i po pewnym czasie użytkowania okazało się, że należy podać do niej kod aktywacyjny (za który oczywiście należy zapłacić). Zastanawiałem się, czy da się jakoś to ominąć i poniżej opiszę rezultaty mojej rozkminki, jak sobie z tym poradzić.

Jak wiadomo kod Javy jest kompilowany do bytecode'u, a ten uruchamiany na wirtualnej maszynie Javy (eng. Java Virtual Machine, JVM). Dzięki temu kompilujemy raz, a uruchamiamy wszędzie (tzn. tam gdzie jest Java Wirtual Machine). Ilustruje to poniższy obrazek:




Skoro kod nie jest kompilowany do natywnego kodu języka danego urządzenia, to możemy skorzystać z tak zwanej wstecznej inżynierii (ang. reverse engineering). Właściwie to każdy plik wykonywalny można zdekompilować, tylko w przypadku aplikacji pisanych nie w Javie, zazwyczaj po dekompilacji mamy odczynienia z kodem asemblerowym, a ten nie należy do przyjemnych w czytaniu. W przypadku Javy mamy trochę lepiej.

W sieci dostępnych jest kilka dekompilatorów kodu Javowego (DJ Java Decompiler  Java Decompiler  Mocha i inne). Ja posłużę się tym pierwszym. Na cele tego artykułu stworzyłem niewielką aplikację, aby pokazać jak skorzystać z możliwości wstecznej dekompilacji. Nie chciałem łamać jakiejś innej aplikacji, aby nie być posądzonym o ingerencje w nią, co pewnie się wiąże z łamaniem prawa. Z własną aplikacją mogę robić co chcę :P

Zacznijmy więc. Na początek należy zawsze uruchomić aplikację i zobaczyć czego szukamy. Jak nie chce nam sie wrzucać jej na telefon, a mamy zainstalowane Java ME platform SDK możemy odpalić ją na komputerze w ten sposób:

<Java ME platform SDK path>\bin\emulator.exe -Xdescriptor:<JAD file path>

Oczywiście należy podać odpowiednie ścieżki, gdzie mamy zainstalowane SDK i gdzie leży nasza aplikacja. W innych SDK (starszych wersjach, lub innych producentów) mogą być graficzne narzędzia umożliwiające odpalenie aplikacji J2ME na komputerze. Poniżej przedstawiam jak wygląda moja aplikacja odpalona na emulatorze:



Po lewej mamy ekran startowy (specjalnie dodałem kilka opcji, aby coś było), a po prawej ekran po wybraniu opcji "Activation". W ekranie tym należy wpisać odpowiedni kod aktywacyjny. To jest miejsce na którym nam zależy i które chcemy złamać.

Odpalamy DJ Java Decompiler. Następnie File -> Open jako typ pliku wybieramy: Java Archive Files(*.jar) i wskazujemy naszego JARa. Ukazuje nam się poniżej okno podobne do poniższego:


Wciskamy Ctrl + A aby zaznaczyć wszystkie klasy i klikamy Decompile. Wskazujemy miejsce docelowe i w pojawiającym sie komunikacie klikamy Yes i po chwili w wybranym folderze otrzymujemy wynik. Plik *.class jest plikiem otrzymanym bezpośrednio przez wypakowanie JARa (gdyz de facto to zwykłe archiwum), a w pliku *.jad mamy zdekompilowany kod. Ciekaw jestem czemu akurat takie rozszerzenie zostało wybrane przez twórców aplikacji, skoro jest ono używane w procesie instalacji aplikacji komórkowych? Można było przecież utworzyć pliki *.java.

No dobra zajrzyjmy do zdekompilowanego pliku. Kod wygląda czytelnie. Porównałem go KDiff3'em z oryginalnym kodem i po za modyfikacją importów, zmianą standardu kodowania, niewielkimi modyfikacjami i dodanymi kilkoma komentarzami kod sie zasadniczo nie różnił. Utworzyłem nowy projekt i wrzuciłem ten "odzyskany" kod i się poprawnie skompilował i poprawnie działa!

Przejdźmy do analizy kodu. Na początku ukazuje nam się konstruktor klasy (część linii pominięto i zachowano oryginalne formatowanie):




Na początku są tworzone obiekty typu Command i widzimy, że to co nas interesuje ("Activation") jest dodawane do List'y. Następnie dodawane jest okCommand i co najważniejsze wywołanie setCommandListener z przekazaniem obiektu this. Oznacza to, że nasza klasa implementuje CommandListener, do którego są kierowane zdarzenia z naszej formatki. Sprawdzamy powyżej:



Rzeczywiście. Przejdźmy więc do metody zdefiniowanej przez ten interfejs:



Na początku jest sprawdzenie czy obiekt command odpowiada naszej liście opcji. Jeśli tak, to sprawdzane jest czy była to komenda okCommand. Jeśli tak to pobierany jest index aktualnie zaznaczonej opcji i coś się dalej dzieje. Jako, że opcja "Activation" byla na 3ciej pozycji w menu (widać to na screenie), to patrzymy co się dzieje pod dwójką ;)



Wywoływana jest jakaś metoda i następnie na wyświetlaczu jest ustawiane do wyświetlenia activationForm. Dowiedzmy się więcej o tym obiekcie. Wróćmy do konstruktora:



Do activationForm dodawane są dwie kontrolki i znów CommandListener ustawiony na this. Wróćmy wiec do metody commandAction() i zjedźmy trochę niżej:



Tak ten warunek to miejsce gdzie trafia wykonanie naszej aplikacji, gdy wciśniemy Ok na activationForm. Zobaczmy co się dzieje poniżej:



Najpierw pobierany jest tekst z kontrolki, która jest odpowiedzialna za wprowadzenie kodu aktywacyjnego. Następnie sprawdzana jest długość pobranego tekstu i gdy jest ona różna od 12 to wywoływana jest metoda showBadActivationCodeMessage(). Sama nazwa juz sugeruje, że nie jest to nic dobrego. Jaki wniosek z tego? Nasz kod aktywacyjny musi mieć 12 znaków długości. Sprawdźmy co się dzieje gdy warunek ten jest spełniony:



Pobierane są kolejno 3ci 8my i 11sty znak z naszego kodu aktywacyjnego. I jeśli odpowiadają one odpowiednio literom 'A', 'B' i 'C' to odnosimy sukces.

Sprawdźmy więc. Odpalamy naszą aplikację na emulatorze (lub telefonie) i wpisujemy np.: xxxAxxxxBxxCx i prosze: Code OK. Złamaliśmy w ten sposób aplikację J2ME.

Jak widać można w prosty sposób podejrzeć kod obecny w aplikacjach J2ME. Jak sie jednak ustrzec przed tego typu ingerencją w oprogramowanie? Właściwie nie można w 100% się ustrzec przed tego typu atakami. Można jedynie utrudnić śmiałkom zadanie.

Najprostszym sposobem ochrony naszego kodu jest stosowanie tzw. obfluskacji (eng. Obfuscation). Polega ona na "upiększaniu kodu", w taki sposób, aby po dekompilacji ciężej było go odczytać. Jest kilka tego typu narzędzi na rynku. Jedno jest dołączone do Netbeansa. Jeśli mamy projekt J2ME utworzony w tym środowisku klikamy prawym przyciskiem myszy na nasz projekt i przechodzimy do Properties. Następnie klikamy na Obfluscating. Tutaj możemy juz sobie ustawić żądany poziom obfluskacji:



Klikamy OK i przebudowujemy nasz projekt. Teraz po wstecznej dekompilacji kod jest mniej czytelny, np.: pole options zmieniło nazwę na a_javax_microedition_lcdui_List_fld, a inne pola skróciły się do jednoliterowych nazw. Jest to trudniejsze w czytaniu, ale narzędzia pozwalające kolorować składnię trochę nam pomagają. Tutaj pokazuję przykład działania na prostym przykładzie i może efekt nie jest powalający. Przy dużych projektach narzędzia takie potrafią namieszać tak, że kod uzyskany za pomocą wstecznej inżynierii nie uda sie ponownie skompilować!!!

Dlaczego tak sie dzieje? Otóż to co jest dozwolone z poziomu kompilatora javy jest również dozwolone w bytecode'dzie. Jednak są pewne elementy bytecode'u, które nie są dozwolone z poziomu składni języka Java. Np. w bytecode'dzie mogą być metody o tej samej nazwie, przyjmujące te same argumenty a zwracające różną wartość! Odpowiednia metoda zostanie wywołana na podstawie typu do którego będzie rezultat przypisywany, lub jeszcze jakiś innych przesłanek. Analiza takiego kodu jest o wiele trudniejsza i bez dobrego refaktoringu nie da się obejść.

Na nasze szczęście (lub nie) klasa rozszerzająca MIDlet możne być poddana tylko częściowej obfluskacji, więc zawsze łatwo będzie nam znaleźć miejsce wejścia do programu:) Również nazwy metod dostarczanych przez javę (np. w klasie String) nie mogą być zmienione, co trochę ułatwia analizę takiego kodu.

Jak jeszcze można się bronić przed wsteczną dekompilacją? W J2ME nie ma mechanizmu ClassLoader'a, więc w ten sposób nie zabezpieczymy naszej aplikacji. Jedyne co po za obfluskacją przychodzi mi do głowy to własna implementacja odpowiednich mechanizmów zabezpieczających.

Poniżej zamieszczam jeszcze spakowany projekt, na podstawie którego prezentowałem zagadnienie. Będziecie mogli się sami pobawić i zobaczyć jak to działa.


PasswordNeed.zip (17KB)

środa, 7 lipca 2010

Wyciąganie informacji o schemacie bazy danych

Znalazłem w końcu trochę czasu, aby coś napisać na bloga. Ostatnio przeprowadzka trochę czasu z życia mi zabrała (koniec mieszkania w akademiku), przez co nie było jak pisać. Przygotowuję obecnie jakieś artykuły na bloga, ale będzie to kilkuczęściowa seria wpisów i chcę poczekać do jej ukończenia. Tymczasem opowiem o wydobywaniu metadanych (czy też metainformacji) z baz danych.

Czym są metadane? Są to "dane o danych". Innymi słowy są to informacje o sposobie przechowywania danych, ich strukturze itp. W przypadku baz danych chodzi tu o schemat bazy danych.

Tylko po co nam schemat bazy danych? Zazwyczaj jak tworzymy jakąś aplikację to bardzo dobrze znamy schemat bazy danych i na nim operujemy. Czasami jednak chcemy napisać narzędzie wspomagające prace z bazą danych, które będzie uniwersalne i działało niezależnie od schematu bazy. Może to być np. narzędzie do automatycznego generowania kodu klas persystętnych, na podstawie schematu bazy danych. O takim rozwiązaniu słyszałem na konferencji 4Developers podczas prezentacji Marka Berkana pt. Automatyczne generowanie kodu. Podobne rozwiązanie jak przedstawiane na prelekcji wykorzystuję obecnie w swoim pewnym projekcie.

Jako przykład bazy danych wykorzystam Oracle 10g XE. Chcąc się dowiedzieć, jakie tabele mamy zdefiniowane w bazie danych możemy wykorzystać następujące zapytanie:



Dostaniemy wówczas wszystkie tabele zdefiniowane przez zalogowanego użytkownika. Tabela USER_OBJECTS zawiera również wiele innych niekoniecznie dla nas ciekawych informacji o naszych tabelach.

Po pewnym czasie używania tego zapytania zauważyłem, że zaczęły pojawiać się w wynikach zapytania tabele o nazwie typu: BIN$m4Gt7PLlTv6r1dCsd2+ddA==$0. Próba uzyskania dostępu do takiej tabeli kończy się błędem ORA-00903: invalid table name. Skąd się więc bierze ta nazwa? A no z mechanizmu odzyskiwania usuniętych tabel. W Oracle mamy możliwość przywrócenia (przypadkiem) usuniętych tabel. Informacje o tych tabelach są również przechowywane w USER_OBJECTS, tylko że pod taką dziwaczną nazwą.

Chcąc się pozbyć usuniętych tabel z naszego zbioru wyników, możemy wykonać drugie zapytanie:



które zwróci nam nazwy tabel usuniętych. Wyniki wystarczy potem odfiltrować. Możemy to również scalić i wykonać w jednym zapytaniu:



Powyższe zapytania operowały na tabelach danego user'a. Chcąc dostać wszystkie tabele (wraz ze specjalnymi / systemowymi - nie wiem jak się je powinno nazwać) należy USER_OBJECTS zamienić na ALL_OBJECTS (lub DBA_OBJECTS) w powyższych zapytaniach. Tabela USER_OBJECTS jest widokiem tabeli ALL_OBJECTS - różni się tylko jedną kolumną.

Przygotowując ten artykuł znalazłem jeszcze inny, prostszy sposób na wydobycie nazw tabel w naszej bazie. Rozwiązanie poniżej:



To zapytanie zwraca tabele danego user'a. W wyniku zapytania nie ma już usuniętych tabel. Tutaj również mamy możliwość wyboru i zamiast USER_TABLES możemy użyć DBA_TABLES lub ALL_TABLES zależnie od naszych potrzeb.

No dobra, wiemy już jakie mamy tabele w bazie, czas na kolumny. O dostępnych kolumnach ich nazwach typach itp. możemy się dowiedzieć za pomocą:



Innym sposobem jest zapytanie:



jednak w tym przypadku nie dostajemy informacji o typie kolumny. Jeśli zależy nam na typie kolumny możemy użyć zapytania:



jednak znów dostaniemy (w gratisie) kolumny z tabel usuniętych. Wynik należało by więc odfiltrować.

Jeśli korzystamy z JDBC w naszej aplikacji mamy jeszcze inną możliwość. Wykonujemy dowolne zapytanie na interesującej nas tabeli (np. SELECT * FROM "NAZWA_TABELI") i otrzymujemy obiekt implementujący interfejs ResultSet. Udostępnia on metodę getMetaData(), która zwraca ResultSetMetaData. Wówczas nazwę kolumny możemy pobrać za pomocą:



Typ kolumny możemy pobrać za pomocą:



Metoda ta zwraca pełną nazwę klasy Javowej na którą może być zrzutowana dana kolumna. Tutaj co ciekawe, to to, że typy liczbowe, niezależnie czy zdefiniowane jako NUMBER(p, s), NUMBER(p), czy NUMBER w bazie Oracle, zawsze są rzutowane do typu java.math.BigDecimal. Jeśli chcemy rzutować do typów prymitywnych, musimy skorzystać z jednego powyższych zapytań i wydobyć dokładną precyzję liczby oraz odpowiednio zrzutować.

To tyle jeśli chodzi o wydobywanie metadanych z bazy Oracle. Jeśli znacie jeszcze inne, prostsze sposoby na wydobywanie informacji o tabelach i kolumnach z bazy danych to zachęcam do komentowania wpisu.