#!/usr/bin/env python3 # Look through 'file' for a NetWare 2.0a serial number. Find code reading the # serial number from a key card and replace it with code which requires no # hardware. # # Can be used for patching both floppy images and individual NET$OS.EXE files. # Highly likely to work on NET$OS.OBJ as well. # # Copyright (C) 2025 The OS/2 Museum # # Script version 1.1; preserve 16-bit checksum but not 8-bit # import os import sys # Calculate a 16-bit checksum of a byte array. # The starting offset determines whether the checksum starts # on an odd or an even byte. Only the low bit of the starting # offset is significant. def checksum_16(ba, start_ofs): sum16 = 0 i = start_ofs & 1 b16 = 0 for b in ba: if (not (i & 1)): b16 = b sum16 = (sum16 + b) & 0xFFFF else: b16 = b16 + (b * 256) sum16 = (sum16 + b * 256) & 0xFFFF i = i + 1 return sum16 def usage(): print('usage: ' + sys.argv[0] + ' ') args = sys.argv[1:] if len(args) != 1 : usage() sys.exit(1) # Get the arguments fname = args[0] # Initialize search patterns # Find the actual serial number sn_pat = ("S/N=").encode() # Find the function reading from key card. We're only going # to replace part of it, since the replacement code is much # shorter. old_kc_pat = bytearray( b"\xB1\x04" # mov cl, 4 b"\xBA\x3C\x02" # mov dx, 23Ch b"\xEC" # in al, dx b"\xF6\xD0" # not al b"\x8A\xD8" # mov bl, al b"\xD2\xE8" # shr al, cl b"\x88\x45\x02" # mov [di+2], al b"\x80\xE3\x0F" # and bl, 0Fh b"\x88\x5D\x01" # mov [di+1], bl b"\x42" # inc dx b"\xEC" # in al, dx b"\xF6\xD0" # not al b"\x8A\xD8" # mov bl, al b"\x24\xF0" # and al, 0F0h b"\x88" #\x05" # mov [di], al ) # ES and DS both point at the data segment; DI points to the location # where the hardware key should be stored (6 bytes). # Registers AX, BX, CX, DX may be destroyed; possibly SI as well. # Registers ES, DS, DI must be preserved. new_kc_pat = bytearray( b"\x1E" # push ds ; save registers b"\x56" # push si b"\x57" # push di b"\x0E" # push cs ; point DS to code b"\x1F" # pop ds b"\xE8\x00\x00" # call L1ドル ; get IP # L1ドル: b"\x5E" # pop si b"\x83\xC6\x0D" # mov ax,0x000f ; add distance to S/N b"\xB9\x03\x00" # mov cx,0x0003 ; copy serial b"\xF3\xA5" # rep movsw b"\x5F" # pop di ; restore and quit b"\x5E" # pop si b"\x1F" # pop ds b"\xC3" # ret b"\x00\x00\x00\x00\x00\x00" # Serial number to be patched in b"\x00" # 8-bit checksum byte b"\x00\x00" # 16-bit checksum word ) # Sanity check if len(old_kc_pat) != len(new_kc_pat): print('Broken script, replacement pattern not the same size as original!') print(len(old_kc_pat), '!=', len(new_kc_pat)) sys.exit(10) # Read the source file fs = open(fname, "r+b") buf = fs.read() # Look for S/N pattern cnt = buf.count(sn_pat) if cnt == 0: print('Serial number not found, cannot proceed.') sys.exit(2) # Find serial number offset sn_ofs = buf.find(sn_pat) + 4 sn = buf[sn_ofs:sn_ofs+6] print('S/N:', sn[:4].hex(), 'Application Number:', sn[4:5].hex(), sn[5:].hex()) # Find the code pattern to replace cnt = buf.count(old_kc_pat) if cnt == 0: print('Code pattern not found, nothing to do.') sys.exit(3) print('Patching', buf.count(old_kc_pat), 'occurrence(s).') # Save a backup, unless backup file already exists fb_name = fname + '.old' fb = open(fb_name, "a+b") if os.fstat(fb.fileno()).st_size: print('Backup file', fb_name, 'already exists, aborting.') sys.exit(4) fb.write(buf) fb.close() # Patch the serial number into replacement code. ns_ofs = len(new_kc_pat) - 9 new_kc_pat[ns_ofs:ns_ofs+6] = sn[:6] # Calculate 8-bit checksum of the old and new blocks. # This is not strictly necessary, but we ensure that the replacement # block has the same checksum as the original, so that the OMF checksums # all match when patching NET$OS.OBJ. Not that the linker really cares. sum8_old = 0 for b in old_kc_pat: sum8_old = (sum8_old + b) & 0xFF # Calculate checksum of new code/data block. sum8_new = 0 for b in new_kc_pat: sum8_new = (sum8_new + b) & 0xFF ############ #print('Old S8:', hex(sum8_old), 'New S8:', hex(sum8_new), 'Diff:', hex(sum8_old - sum8_new)) ############ # 1.1 change: The 8-bit checksum is not critical because the linker # does not complain when it doesn't match. # # Insert checksum adjustment into replacement block. #new_kc_pat[ns_ofs+6] = (sum8_old - sum8_new) & 0xFF # The 16-bit checksum calculation depends on whether the block # to be replaced starts on an odd or even byte (either is a real # possibility). kc_ofs = buf.find(old_kc_pat) print('Found at offset', hex(kc_ofs)) # Calculate 16-bit checksum of the old and new blocks. sum16_old = checksum_16(old_kc_pat, kc_ofs) sum16_new = checksum_16(new_kc_pat, kc_ofs) # Adjust for lack of wraparound sum16_diff = sum16_old - sum16_new if sum16_diff < 0: sum16_diff = 0x10000 + sum16_diff ############ #print('Old S16:', hex(sum16_old), 'New S16:', hex(sum16_new), 'Diff:', hex(sum16_diff)) ############ # 1.1 change: The NetWare cold boot loader validates the checksum # of NET$OS.EXE, which is something that DOS does not do. # If we're patching already linked NET$OS.EXE, we must preserve # the 16-bit checksum of the patch code (otherwise we'd have to # adjust the checksum in the EXE header, which we'd much rather # not do). # Note that preserving both the 8-bit and 16-bit checksums at the # same time is quite non-trivial. # # Insert checksum adjustment into replacement block. # If the block is at an odd address, the 16-bit checksum # must be at an odd address too! ns_ofs = ns_ofs - (kc_ofs & 1) new_kc_pat[ns_ofs+7] = sum16_diff & 0xFF new_kc_pat[ns_ofs+8] = (sum16_diff>> 8) & 0xFF ############ # Calculate checksum of new code/data block. #sum16_new = checksum_16(new_kc_pat, kc_ofs) #print('Fixed S16:', hex(sum16_new)) ############ # Replace the keycard reading code. buf = buf.replace(old_kc_pat, new_kc_pat) # And finally write back modified file fs.seek(0) fs.write(buf) fs.close()

AltStyle によって変換されたページ (->オリジナル) /