Boxing Day

Since it’s a holiday week, I’m just going to do a quick followup to a loose end from last week. In that post, I hypothesized that the function located at CS:3A30 in my run-time copy of the Neuromancer executable existed solely to create a delay for the user. Today, I’d like to test that hypothesis by examining some more assembly code.

Assembly code

If one disassembles the machine code at CS:3A30 (e.g. with DOS DEBUG – see the last several posts for more details on how to locate and disassemble particular fragments of code at run-time using this tool) one finds the following short sequence of instructions:

13DB:3A30 55            PUSH    BP
13DB:3A31 8BEC          MOV     BP, SP
13DB:3A33 83EC04        SUB     SP, +04
13DB:3A36 E8DDA1        CALL    DC16
13DB:3A39 8BC8          MOV     CX, AX
13DB:3A3B 8B4604        MOV     AX, [BP+04]
13DB:3A3E 8BDA          MOV     BX, DX
13DB:3A40 99            CWD
13DB:3A41 03C1          ADD     AX, CX
13DB:3A43 13D3          ADC     DX, BX
13DB:3A45 8946FC        MOV     [BP-04], AX
13DB:3A48 8956FE        MOV     [BP-02], DX
13DB:3A4B E8C8A1        CALL    DC16
13DB:3A4E 3B56FE        CMP     DX, [BP-02]
13DB:3A51 7F09          JNLE    3A5C
13DB:3A53 7C05          JL      3A5A
13DB:3A55 3B46FC        CMP     AX, [BP-04]
13DB:3A58 7302          JNB     3A5C
13DB:3A5A EBEF          JMP     3A4B
13DB:3A5C 8BE5          MOV     SP, BP
13DB:3A5E 5D            POP     BP
13DB:3A5F C3            RET

We can quickly analyze these instructions.

Stack setup

13DB:3A30 55            PUSH    BP
13DB:3A31 8BEC          MOV     BP, SP
13DB:3A33 83EC04        SUB     SP, +04

Setup the stack frame, by storing the previous frame’s Base Pointer on the stack, setting the current frame’s Base Pointer to the current Stack Pointer, and allocating 4 bytes of local memory on the stack, addressable as [BP-04] through [BP-01].

Mystery function

13DB:3A36 E8DDA1        CALL    DC16

Call the function at CS:DC16, passing no arguments to it. At the moment, the significance of this function is not clear.

DWORD shuffling

13DB:3A39 8BC8          MOV     CX, AX
13DB:3A3B 8B4604        MOV     AX, [BP+04]
13DB:3A3E 8BDA          MOV     BX, DX
13DB:3A40 99            CWD

Store the 32-bit return value of CS:DC16 (passed, in the C calling convention, in DX:AX) in BX:CX, and store the sign-extended argument to this function in DX:AX.

DWORD addition

13DB:3A41 03C1          ADD     AX, CX
13DB:3A43 13D3          ADC     DX, BX
13DB:3A45 8946FC        MOV     [BP-04], AX
13DB:3A48 8956FE        MOV     [BP-02], DX

Add BX:CX to DX:AX, and store the 32-bit result in LSB order at [BP-04]. The DWORD at [BP-04] is now equal to the DWORD return value of CS:DC16 plus the sign-extended WORD argument to this function.

Mystery function (redux)

13DB:3A4B E8C8A1        CALL    DC16

Another call to CS:DC16.

Test (part 1)

13DB:3A4E 3B56FE        CMP     DX, [BP-02]
13DB:3A51 7F09          JNLE    3A5C

Jump to the end of this function iff the high WORD of the new CS:DC16 return value is greater than the high WORD calculated in the DWORD addition step.

Test (part 2)

13DB:3A53 7C05          JL      3A5A

Jump to the bottom of the current loop (for, as we’ll soon see, we’re in a loop) iff the high WORD of the new CS:DC16 return value is less than the high WORD calculated in the DWORD addition step.

Test (part 3)

13DB:3A55 3B46FC        CMP     AX, [BP-04]
13DB:3A58 7302          JNB     3A5C

Jump to the end of this function iff the low WORD of the new CS:DC16 return value is greater than or equal to the low WORD calculated in the DWORD addition step.

Loop

13DB:3A5A EBEF          JMP     3A4B

Loop back to the CS:DC16 call at CS:3A4B – busywait, basically. This instruction is reached iff the most recent CS:DC16 return value (passed in DX:AX) is less than the DWORD stored at [BP-04]. Assuming that the CS:DC16 function returns some sort of timer, this behaviour is consistent with our hypothesis that this function exists simply to create a delay.

Stack teardown

13DB:3A5C 8BE5          MOV     SP, BP
13DB:3A5E 5D            POP     BP

Restore the caller’s stack frame, by setting the Stack Pointer to this frame’s Base Pointer, and then popping the caller’s Base Pointer off
the stack.

Return

13DB:3A5F C3            RET

Return to caller

CS:DC16

At this point, the only question left is whether or not the CS:DC16 function returns some sort of measure of the current time. Let’s check:

13DB:DC16 2BC0          SUB     AX, AX
13DB:DC18 CD1A          INT     1A
13DB:DC1A 8BC2          MOV     AX, DX
13DB:DC1C 8BD1          MOV     DX, CX
13DB:DC1E C3            RET

This code invokes the 00h subfunction of the 0x1A interrupt, and moves its 32-bit return value from CX:DX to DX:AX, for return to the caller. According to Ralf Brown’s interrupt list, this interrupt returns the number of clock ticks since the most recent midnight in CX:DX – there are 0x1800B0 ticks per 24hr period.

Conclusions

CS:3A30 is, indeed, a delay function, creating a pause in processing approximately equal to its argument, divided by 20, in seconds. It’s interesting to note that this function contains a game-killing hazard: If invoked shortly before midnight, the function can enter an infinite loop.

The CS:DC16 function on which CS:3A30 relies returns values between 0 and 0x1800AF. If the sum of the argument to CS:3A30 and the return value of the first call to CS:DC16 are greater than 0x1800AF, then this function will wait for CS:DC16 to return a value that it never will.

For instance, if this function is invoked 5s before midnight, with a 200 tick (~11s) delay:

  • The first call to CS:DC16 will return 0x180055
  • 0x180055 plus 200 equals 0x18011d
  • CS:3A30 will loop until CS:DC16 returns a value greater than or equal to 0x18011d
  • CS:DC16 can never return a value greater than 0x1800AF
  • Therefore, CS:3A30 will never return

This is unlikely to happen in practice, but it’s interesting in theory. (For some value of “interesting”.)

Merry Christmas, and a Happy New Year.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in Reverse Engineering, Uncategorized. Bookmark the permalink.

Comments are closed.