• Please review our updated Terms and Rules here

Opcode puzzle

alank2

Veteran Member
Joined
Aug 3, 2016
Messages
2,265
Location
USA
Messing around with some older Borland compilers last night for fun I can see that something they did to invalidate an OBJ file for the build process was to set the date/time to 0. Using intspy I found this:

set_file_date_and_time(handle=0x0006,time=0x0000,date=0x0000)

This affects Turbo C 1.0, 1.5, 2.0, Turbo C++ 1.0, 3.0, and Borland C++ 2.0.

Finally in Borland C++ 3.1, it was fixed to this which is a valid date/time:

set_file_date_and_time(handle=0x0006,time=0x0001,date=0x0021)

This works fine in DOS, but NT 4.0's VDM rejects it and doesn't change the time of the OBJ. So then a build results in an up to date result and does not occur at all when requested.

Using IDA I found the section where this happens:

seg000:CA1B mov ax, 96FDh
seg000:CA1E push ax
seg000:CA1F xor ax, ax
seg000:CA21 push ax
seg000:CA22 push ax

seg000:CA23 call sub_11CE4

In this example I replaced CA1F with mov ah,0 which left it with a date/time of 00FD from the above CA1B instruction, which is valid, which works, but this was for Turbo C 2.0 and others I looked at didn't have the mov ax,96FD to count on above it. Ideally I would want to sex AX to something like mov ax,0021 but this takes 3 bytes and I only have 2 bytes in this spot. I'd like to find a patch technique that could work for different versions, so what other ideas are there?
 
If you don't care about 8086/88 compatibility, you can do this:

Code:
seg000:CA1F  6A 01  push 01h
seg000:CA21  6A 21  push 21h
 
Don't forget that the 6A instruction sign extends the immediate byte (when extended to a word) on the stack. You are fine fir your example though...

Dave
 
I am aware of that. Since the goal is to set a valid timestamp that is as low as possible, sign extension isn't a problem.

Code:
DOS date/time format:

bit 15 ..  9 = year-1980
     8 ..  5 = month
     4 ..  0 = day

bit 15 .. 11 = hour
    10 ..  5 = minute
     4 ..  0 = second/2

Day and month are 1-based. I believe early versions of DOS and the FAT filesystem defined all zero as "unknown", but that seems to be no longer supported.
 
>If you don't care about 8086/88 compatibility, you can do this:

Good thoughts everyone; unfortunately I do care about 8086/8088 compatibility as for some reason I'm entertained by how minimally I can make a machine that can use these things.

I had another idea that also seems to work, but I' am still interested in patching too so I may explore both directions.

I found an example of some a TSR skeleton that hooks int21 and modified it to test for ax=5701, cx=0, dx=0 and to replace that with what Borland did in 3.1. I'm sure you guys probably have some improvements to it as my x86 assembly skills are very basic. Used nasm.

Code:
;[cpu 8086]
[bits 16]
[org 256]

start:
        jmp init

        align 4
int21old:
        dd 0

int21handler:
    cmp ax, 5701h
    jne skip
    cmp cx, 0000h
    jne skip
    cmp dx, 0000h
    jne skip
    mov cx, 0001h
    mov dx, 0021h

skip:
        jmp far [cs:int21old]

end_of_resident:

init:
        mov ax, 3521h
        int 21h
        mov word [int21old + 2], es
        mov word [int21old], bx

        mov ax, 2521h
        mov dx, int21handler
        int 21h

        mov ax, 3100h
        mov dx, (end_of_resident - start + 256 + 15) >> 4
        int 21h

It compiles into a 65 byte .com file that loads and works under NT 4.0.
 
Good thoughts everyone; unfortunately I do care about 8086/8088 compatibility as for some reason I'm entertained by how minimally I can make a machine that can use these things.

In that case there's probably no solution, except for shortening some of the original code in order to get that extra byte of space!

I had another idea that also seems to work, but I' am still interested in patching too so I may explore both directions.

I found an example of some a TSR skeleton that hooks int21 and modified it to test for ax=5701, cx=0, dx=0 and to replace that with what Borland did in 3.1. I'm sure you guys probably have some improvements to it as my x86 assembly skills are very basic. Used nasm.

Was actually going to suggest that too, but figured it would be too much effort if it only has to run under NTVDM :)

Another option would be to replace the compiler with a "loader" program that hooks int 21h before running the original EXE.

Some improvements (COM file is 73 bytes but uses less memory):

Code:
org 0100h

    jmp    init

int21:
    cmp    ax,5701h
    je    .fn5701
    ;fall through in case it's another function
.chain:
    db    0eah        ;jmp far
.old:    dd    0

.fn5701:
    test    dx,dx        ;date is zero?
    jnz    .chain
    mov    cx,0001h
    mov    dl,21h        ;DH already zero
    jmp    .chain
.length    equ $-int21

init:
    mov    ah,49h        ;free environment block
    mov    es,[2ch]
    int    21h

    mov    ax,3521h
    int    21h
    mov    [int21.old + 0],bx
    mov    [int21.old + 2],es

    ;move resident part as low as possible
    cld
    push    ds
    pop    es
    mov    si,int21
    mov    di,005ch    ;usable space in PSP
    mov    cx,int21.length
    mov    dx,di        ;for int 21h below
    rep    movsb

    mov    ax,2521h
    int    21h

    mov    ax,3100h
    mov    dx,(005ch + int21.length + 15) >> 4
    int    21h
 
Is there some unused space nearby (or not nearby) that you can overwrite the xor/push/push/call with a CALL to that unused location, and in that location, get the values you want on the stack and then JMP to 11ce4?
 
Back
Top