Reverse engineering Windows 95 itself
(Understanding our trade)

by +Rcg

(17 August 1997, slightly edited by fravia)


Courtesy of fravia's page of reverse engineering

Well, this is an important addition to our +HCU didactic material... This you should read, and head, and pray with me that +RCG sends us more!

FIRST PART

1. Introduction
2. First Step
3. Memory Addressing
4. Control Transfers
5. Interruptions/Exceptions
6. Hardware Multitasking

SECOND PART

7. V86 Mode 
8. In/Out ports
9. Debugging


THIRD PART

10. Reversing Engineer of W95 itself
INTRODUCTION I have been thinking for a long time how to write this document, because I would want it very easy to follow, and of course, so that you may take profit of it. This doc deals with i386 and above architecture, and is the first step to understand how all the OS based on this kind of processors work (and specially W95). There are a lot of places where you can find info about this, but my personal experience is that this information is not very useful, because the writer has most of the time another point of view, quite different from our. For example we don't want to enter, nor exit, from PM because we don't want to write a program (quite the countrary, we want to "reconstruct" programs, and Windoze will do it for us if needs be (this doesn't mean we do not want to learn it, it means that our accents are different. We will use WinIce to learn, just like I did on my Amiga with Action Replay: I learned assembler on my Amiga modifying my favourite games, working with running programs and modify them. Try everything you imagine, and when you have all understood make your own program using all the new things you have learnt. My initial idea was to write about VxD, but due to the scarce information (and the absolute MS dependence) I think the best solution is to analize how W95 works and create your own utilities working with full privileges. To make this, we must understand first of all how our processor works and then take profit of it (for cracking or for protecting purposes, it amounts to the same :-)
FIRST STEP And now a little theory, don't worry, only a little. These 'new' processors support via hardware multitasking, that is a 'grossomodo' way to run more than one program at the same time with a single processor... different tasks must run sequentially each for a short time, so to our eyes, these tasks will seem to run simultaneously. Other new features are the new memory management, the hardware debugging, and some other features that we will analyze later. One of the problems associated to this is that task one can modify (maliciously or unvolontarly) the code or the data of task two. To solve this problem, the CPU has some protection mechanisms. Basically the protections mechanims deals with the privilege levels. When you enter at Protected Mode, you must create a table (GDT), where you will assign to every selector (part of memory) differents characteristics. A selector is for example this: CS=28 (like W95) then in the GDT (Global Descriptor Table) you will say for example that the Selector 28 is the memory between 00000000 and 00003000, where we will put Code working at privilege level 2 (with some other characteristics). Think of that like if it were a Real Mode Segment, but with the limit of 4Gb (not only 64Kb) and with some attributes, like if it is data or code, or if it can be modified or not ... So, we must define some selectors and their attributes, minimum for the Data, Code, Stack and some others. Let's see that in real life: Fire Winice and then write: GDT you will see more or less these lines: GDTbase=C011E37C Limit=01F7 Sel Type Base Limit DPL Attributes 0008 Code16 0000DF40 0000FFFF 0 P RE 0010 ...... ........ ........ . . .. 0018 TSS32 C000AEBC 0000AEBC 0 P B 0020 ...... ........ ........ . . .. 0028 Code32 00000000 FFFFFFFF 0 P RE 0030 Data32 00000000 FFFFFFFF 0 P RW .. .. .. 00C0 LDT 80095000 00002FFF 0 P .. .. .. and more... Let's first intruduce some important new registers and structures. Registers: There are 4 new ( called control) registers, named CR0, CR1, CR2 and CR3. CR0: Machine Control Register (bits important for us) bit #15: Pagind enabled (memory control) bit #0: Protected Mode Enabling (1=enabled) CR1: Not used (at least in the i486) CR2: Page Fault Linear Address Register Holds the 32-bit linear address that caused the page fault. CR3: Page Directory Base Address Register (more later) bits #31-12: Page Directory Base Register New flags register called EFLAGS (Extended Flags) bit #17: Virtual V86 mode (1=V86 mode) bit #16: Resume Flag bit #14: Nested Task bit #12-13: Input/Output Privilege level Debug Registers (DR0-DR7) System Address Registers (GDTR, IDTR, LDTR, TR). Structures: GDT structure is an array of 8 byte, there are some types of Segment Descriptor we can define: Data Segment bits #0-15: Segment Limit bits #15-31: Segment Base 15..0 bits #32-39: Segment Base 16..23 bit #40: Accessed bit #41: Writable bit #42: Expand-Down bit #43: 0 bit #44: 1 bit #45-46: DPL (Descriptor privilege level) bit #47: Present bit #48-51: Segment Limit 16..19 bit #52: Available for programmer use bit #53: 0 bit #54: Big bit #55: Granularity bit #56-63: Segment Base 24..31 Executable Segment bits #0-15: Segment Limit bits #15-31: Segment Base 15..0 bits #32-39: Segment Base 16..23 bit #40: Accessed bit #41: Readable bit #42: Conforming bit #43: 1 bit #44: 1 bit #45-46: DPL (Descriptor privilege level) bit #47: Present bit #48-51: Segment Limit 16..19 bit #52: Available for programmer use bit #53: 0 bit #54: Default operation size bit #55: Granularity bit #56-63: Segment Base 24..31 Task Gate Descriptor bits #0-15: Reserved bits #15-31: TSS Segment Descriptor bits #32-39: Reserved bit #40: 1 bit #41: 0 bit #42: 1 bit #43: 0 bit #44: 0 bit #45-46: DPL (Descriptor privilege level) bit #47-63: Reserved Call Gate Descriptor bits #0-15: Offset in Segment 15..0 bits #15-31: Segment Selector bits #32-36: Count bit #37-41: 0 bit #42: 1 bit #43: X (0=286 Call Gate 1=486 Call Gate) bit #44: 0 bit #45-46: DPL (Descriptor privilege level) bit #47: Present bit #48-63: Offset in Segment 31..16 Interrupt/Trap Gate Descriptor bits #0-15: Offset in Segment 15..0 bits #15-31: Segment Selector bits #32-36: Reserved bit #37-38: 0 bit #39: T (0=286 Int. Gate 1=486 Int. Gate) bit #40-42: 1 bit #43: X (0=286 1=486) bit #44: 0 bit #45-46: DPL (Descriptor privilege level) bit #47: Present bit #48-63: Offset in Segment 31..16 LDT Descriptor (Local Descrip. Table) bits #0-15: Segment Limit bits #15-31: Segment Base 15..0 bits #32-39: Segment Base 16..23 bit #40: 0 bit #41: 1 bit #42: 0 bit #43: 0 bit #44: 0 bit #45-46: DPL (Descriptor privilege level) bit #47: Present bit #48-51: Segment Limit 16..19 bit #52: Available for programmer use bit #53: 0 bit #54: 0 bit #55: Granularity bit #56-63: Segment Base 24..31 TSS Descriptor bits #0-15: Segment Limit bits #15-31: Segment Base 15..0 bits #32-39: Segment Base 16..23 bit #40: 1 bit #41: Busy bit #42: 0 bit #43: X (0= 286 TSS 1=486 TSS) bit #44: 0 bit #45-46: DPL (Descriptor privilege level) bit #47: Present bit #48-51: Segment Limit 16..19 bit #52: Available for programmer use bit #53: 0 bit #54: 0 bit #55: Granularity bit #56-63: Segment Base 24..31 Now try at your own to get the same values that WinIce gives us. How? Write GDT, and if it says GDTbase=C0113E7C, then E C0113E7C, ah!!! by the way the first entry (selector 0) must be all zeroes. All Ok?.....Let's continue....... GDTbase is the memory location of the GDT table, and can be obtained using: lea esi,_64bitbuffer sgdt fword [esi] ;Store GDT at [esi] This can be done only if your descriptor code segment is running at privilege level 0. Segment Selector Format: bit #0-1:RPL (Requested Privilege Level) bit #2: Table Indicator bit #3-15:Index CS=28h ==> 101000b (Kernel) Index = 101 => 5 (Entry number 5) Table Indicator = 0 (Global Table) Privilege Level = 0 CS=137h ==> 100110111b (Aplication Programms) Index = 26h = 38 Table Indicator = 1 (Local Table) Privilege Level = 3 (Opps!!!) You can see how W95 assigns to our programs, the lowest privilege level (3), this is in order that our programs don't affect seriously the OS, because we have some restrictions. Restrictions the i486 has: * Some instructions are disabled, i.e. sgdt., and can be executed only at level 0. * Data or Code from a higher priv. level cannot be read nor modified. * Code from a lower level can't jump directly to a higher level. * Every Segment Descriptor has a limit, so if an aplication tries to write outside of the limits, then the processor generate an exception (which will be intercepted by the OS, which in turn will perform some operations to solve the problem). * The Stack Selector must have exactly the same Privilege Level than the CS Selector. * There is one exception to these rules, you could see that the Executable Descriptor can be marked as a Conforming, this means that you can load this Selector in the DATA segment register without any restriction. At this time you can be asking yourself how an aplication (level 3) can execute the OS funtions (level 0) if you can't jump to them. But, before answering this question, let's analyze first the memory addressing.
MEMORY ADDRESSING There are three concepts you must undertand first of all: 1. Logical Address 2. Linear Address 3. Physical Address Physical memory is the 'real' address, the number that the processor puts in the bus to access a data in memory, so the interrupt table is always at the beginning of the memory at 0, so if the CPU wants access to this memory location, it must put 0 on the address BUS to read the value stored in it. Another example, the video memory is at A0000, so the CPU puts that value on the BUS. Obviously, the best way to access memory is putting directly this value into a register and get the value stored in it, this is the way Motorola processors work, yet Intel's do not work this way. x86 processors combine a segment register with an offsset to obtain the physical address, this is the solution they took in 16 bits processors times... now that the registers are 32bit long this way is obsolete, but Intel decided to prserve continuity with the past manteining compatibility with the old DOS programms, so in Protected Mode the segment:offset method is used too, but in a different way. Logical address is the combination between segment:offset, for example if you have a GDT like this: Sel Type Base Limit DPL Attributes 0008 Data32 00000000 FFFFFFFF 0 P RW 0010 Data32 000A0000 0000FFFF 0 P RW .. .. if you want to read the video memory, you can do it in two ways: mov ax,08h mov es,ax ;Selector 8 mov ax,es:[A0000] or: mov ax,10h mov es,ax ;Selector 10h mov ax,es:[0] In both examples, es:[????????] is the logical address, and after translating it through the Descriptor Table, we get the Linear address: In the first example: Linear Address = Base of Selector 8 + offset => 0+A0000 = A0000 In the second: Linear Address = Base of Selector 10h + offset => A0000+0 = A0000 After that, x86 have two ways to translate a linear address into a physical address, checking bit 31 of CR0 register, the CPU knows if it is in Segment or in Paging mode (W95 works in Paging mode). If the mode is Segmentation, the the Phys. Add. is the Linear Add., but if we are in Paging mode, the CPU will made another translation. PAGING TRANSLATION This mode is a way to manage memory though an index, just supose you have a very big library, and when you want read a book, you go to your book index and look where it is stored, and then go to your library and pick up it. Now, you lost one book, and buy another different, then simply you store the new book in the hollow location of the other one and write down in the index the new book location, this is a good way to exploit the library space without wastes. Well, the i486 books (pages) are 4Kb long, so if theoretically we can address up to 4Gb, we need a 4Gb/4Kb=1Mb of indexes if each is 4 bytes long, our index will be 4Mb long, that is too much for us, we can't waste as much as this. So, the solution is create a new index only when really necessary, so we can have a main index and a secondary index, so long as the secondary index is not full we don't create a new main index (let's better understand it with an example). Linear Address in paging mode has the following meaning: Bit31-22: Directory Entry (Main index) Bit21-12: Page Entry (Secondary Index) Bit11-00: Offset The Page Entry format is: Bit31-12: Page Address Bit11-09: Available for the OS Bit08-07: Must be 00 Bit6: Dirty (Page has been modified) Bit5: Accessed Bit04-03: Must be 00 Bit2: User (0) /Supervisor (1) Bit1: Read/Write (1) or Read (0) Bit0: Present The physical address of the Page Directory is stored in the CR3 register. Suppose that our CR3 register reads 5DE000h. So if our linear address is: 403499 ====> 10000000011010011001b Directory Entry = 1h Page Entry = 3h Offset = 499h Phys. Add. Data stored in memory (remember that are inverted) 5DE000 67,C2,00,C0 (0) 5DE004 67,C2,55,C9 (1) 5DE008 .. .. .. .. .. 5DEFFC .. (4095) Directory Entry 1 is: C955C267 ('uninverted') Physical Page Address is: C955C000 (4Kb aligned) Attributes are: 267h = 001 00 11 00 111 OS flag=001 Dirty=1 Accessed=1 User=1 Read/Write=1 Present=1 W95 define the OS flag as: 000=VM 001=System 010=Reserved 011=Private 100=Reserved 101=Relock 110=Instance 111=Hooked Phys. Add. Data stored in memory (remember that are inverted) C955C000 67,C2,30,C2 (0) C955C004 67,C2,25,C4 (1) C955C008 .. C955C010 67,C2,34,C3 (3) .. .. .. .. .. .. C955CFFC .. (4095) Page Entry 3 is: C425C267 ('uninverted') Physical Page Address is: C425C000 (4Kb aligned) Attributes are: 267h = 001 00 11 00 111 And finally if we add the offset to the Phys. Add. we obtain the desired value: Phys. Add. = C425C000+499h=C425C499 Remember that a page is only 4Kb long. And now, think a little and answer these questions? Why all the W95 programs are executed with: CS= 137h EIP= 401000-7FFFFF range ? Does this fact mean that all the programs are in the same physical memory location (very illogical)? Answer: 137h=100110111b ==> Selector Index=26h Table Indicator=1 (Local Table) DPL=3 (Lowest privilege level) Windows uses Paging mode, so the linear address of 137:401000 mean: Directory Entry = 1h Page Entry = 1h Offset = 000h When paging is enabled, a task can only have one page directory table but several second level page tables. So to perform a task swap, W95 only needs to modify the page directory second entry (1h) to point to the new task physical address, but to our eyes all tasks are running at the same linear address. Note that a program has a consecutive linear address, but not necessary consecutive physical address, for example: cs:401FFA cmp eax,1505h cs:402002 jne 402957 the first instruction can be at C734FFA phys. address and the second can be at C335002 phys. address, it depends on the second table (remember the lost book). Finally, if the program accesses a page that is marked as not present, a fault exception is generated, then the OS will make the necesary actions (like load it from the exchange file). A practical example: I have used Zmud but you can use other programs or even windows 95 itself. bpx GetLocalTime fire zmud after a few F11 you will be at: 137:40749E E8E3DCFFFF CALL KERNEL32!GetLocalTime 137:407499 668B4C240E MOV CX,[ESP+0E] 7*4=1Ch OFFSET=499 Now type: CPU and you will obtain: EAX= ..... ESI= ..... DS = ..... CR0=8000001B PE MP ..... CR2=.. CR3=005DE000 .. .. for us is only important that we are in Paging Mode, bit31 of CR0 register and the CR3 register, the physical address of the Directory Page Base. Now type: phys 5DE000 (obtain the linear address of a phys. add.) you will obtain: (I think that is always the same linear address, but I'm not too sure) C197A000 FFBFE000 (Ignore this) now type: e C197A000 you can also obtain this using the peek command. Type: peek d 5DE000 (for the first entry 0h) peek d 5DE004 (for the second entry 1h) the answer for the second entry is: (in my case) 0xC1C267 ===> So the Secondary table phys. add. is: C12000+1Ch=C1201Ch typing again: peek d C1201C we get: 0xC1F267 ===> The real phys. add. is: C1F000+offset (499h) = C1F499h typing finally: peek d C1F499: 0x244C8B66 that corresponds to the opcodes of our actual eip. Last question, if W95 operates in Paging Mode, How Sice obtain the value stored in memory if we give it a phys. address, remember that the CPU transforms the value to a phys. address, reading CR3 register we obtain a phys. address, but how we can transform it to a linear add. if we don't know where is the directory table? I thpought about this for a while, then I tried the following: Fire Sice when it executes a ring0 code, then put these instructions and execute them: mov eax,CR0 mov esi,CR3 and eax,7fffffff ;Segment Mode mov CR0,eax mov ebx,[esi] ;Now it is the phys. add. ;because we are in seg. mode or ax,80000000 mov CR0,eax ;Paging Mode the result was a miserable crash, why? Because in the inst. next to the mode change EIP points to a unexpected memory location so the inst. executed wasn't the mov ebx,[esi]. I think that Sice has part of it code in the Conventional memory, so if you change from Paging to Segment Mode, the memory location is the same, so it don't crash.....do you agree with me? Do you know the correct answer? Do you have other ideas?
CONTROL TRANSFERS There are three kinds of control transfers: 1. From one privilege level to another in the same task. 2. From one task to another (not necesary at the same privilege level, Windows95 does not use it). 3. From PM to V86 mode (or viceversa). Remember that each privilege level in one task must have its own stack segment with the same privilege level. TRANSFERS BETWEEN DIFFERENT LEVELS IN THE SAME TASK A task has four levels, every one of them is independent from the others. Each level must define its own Stack and Code segments. Any transfer that changes the CPL (current privilege level) must change the the privilege level of the stack at the same time. HIGHER TO LOWER LEVEL Every time the OS gives control to an application, this transfer does not check the privilege levels. Note that CS and SS must be loaded at the same time! To be able to make this, we store in the stack the SS,ESP,CS and EIP and then with the RETF and IRET we give control to the lower level code (remember that these instructions load the registers at the same time). LOWER TO HIGHER LEVEL An application needs to execute OS Code, this transfer is dangerous for the integrity of the OS, so it must be controlled, How? Using the GATE Descriptors, these descriptors points directely to a function of the OS (see the GATE descriptor format) that 'regains' the control. The GATE can be a Interrupt Gate, a Task Gate, or a Call Gate. W95 uses the Interrupt GATE, so any time that an application wants to return control to the OS (or execute part of its code) it will execute a GATE Interrupt (W95 uses Int. 30). (Extracted from Soft-Ice manual) ------------------------------------------------------- Understanding Transitions From Ring-3 to Ring-0 Many times when tracing into code, under Windows 95, you arrive at either an INT 0x30 or an ARPL. Both are methods for making a transition from Ring-3 to Ring-0. When you wish to follow the ring transition, you can save yourself the time and effort of stepping through a large amount of VMM code by using the G(o) command to execute up to the address shown in the disassembly. Windows 95 uses the following methods to transition Ring-3 code to Ring-0 code: &127; For V86 code, Windows 95 uses the ARPL instruction, which causes an invalid opcode fault. The invalid opcode handler then passes control to the appropriate VxD. The ARPL instruction is usually in ROM. Windows 95 uses only one ARPL and it varies the V86 segment:offset to indicate different VxD addresses. For example, if the ARPL is at FFFF:0, Windows 95 uses the addresses FFFF:0, FFFE:10, FFFD:20, FFFC:30 and so on. The following example shows sample output for disassembling an ARPL: FDD2:220D ARPL DI,BP ; #0028:C0078CC9 IFSMgr(01)+0511 &127; For PM code, Windows 95 uses interrupt 0x30h. Segment 0x3B contains nothing but interrupt 0x30 instructions, each of which transfers control to a VxD. The following example shows sample output for disassembling segment:offset 3B:31A: 003B:031A INT30 ; #0028:C008D4F4 VPICD(01)+0A98 003B:031C INT30 ; #0028:C007F120 IOS(01)+0648 003B:031E INT30 ; #0028:C02C37FC VMOUSE(03))00F0 003B:0320 INT30 ; #0028:C02C37FC VMOUSE(03))00F0 003B:0322 INT30 ; #0028:C023B022 BIOSXLAT(05)=0022 003B:0324 INT30 ; #0028:C230F98 BIOSXLAT(04)=0008 003B:0326 INT30 ; #0028:C023127C BIOSXLAT(04)=02EC ------------------------------------------------------ W95 gets the Return address after the Int. and using it with a table gets the VxD entry function. Exception to this is the Code Segment marked as CONFORMING, so if you make a JUMP/CALL to this king of Segment, the CPU reacts giving this Segment the same privilege level of the Caller (and executes the Code without problems). INTERRUPTIONS/EXCEPTIONS In PM interrupts and exceptions are used as a control-transfer methods. Exceptions are generated by the CPU during the execution of an instruction. There are maskable/unmaskable interrupts. Unmaskable can be enabled/disabled using the EFLAGS register (IF=0 means that the int. will not occur). Maskable int. are Ints. from 0-20h. Unmaskable from 21h-0FFh. As the same maner as the GDT, there are an Interrupt Descritor Table (IDT) where the CPU will get the Code it must execute and if can be done (it depens on the DPL assigned). Using SoftIce write IDT and: Int Type Sel:Offset Attributes Symbol/Owner IDTbase=C000ABBC Limit=02FF 0000 IntG32 0028:C0001200 DPL=0 P VMM(01)+0200 0001 IntG32 0028:C0001210 DPL=3 P VMM(01)+0210 0002 IntG32 0028:C00EEDFC DPL=0 P VTBS(01)+1D04 0003 IntG32 0028:C0001220 DPL=3 P VMM(01)+0220 0004 IntG32 0028:C0001230 DPL=3 P VMM(01)+0230 0005 IntG32 0028:C0001240 DPL=3 P VMM(01)+0240 0006 IntG32 0028:C0001250 DPL=0 P VMM(01)+0250 0007 IntG32 0028:C0001260 DPL=0 P VMM(01)+0260 0008 TaskG 0068:00000000 DPL=0 P 0009 IntG32 0028:C000126C DPL=0 P VMM(01)+026C 000A IntG32 0028:C000128C DPL=0 P VMM(01)+028C .. .. .. 0030 IntG32 0028:C001B04 DPL=3 P Alloc_PM_Call_Back+4E .. .. Now see how the Int. 30 can be called by applications at level 3 (if you modify and put DPL=0 Windows 95 will crash due to Protecting Problems, try it!!!!). How? IDTbase=C000ABBC, so E C000ABBC+(30*8) Now change the EE with 8E, again IDT and you will see Int. 30 DPL=0. x and ...... W95 crash inmediately. Because Int./Exc. can occur at any time and the DPL is unpredictable, it is necessary to avoid this situation... there are two possible solutions: putting the handler at level 0 or in a CONFORMING Code Segment. After entering to the handler, the stack contain the next registers: No Priv. Level Change Priv. Level Change old eflags old ss old cs old esp old eip old eflags error code old cs old eip error code because when there is a level change, the CPU changes the stack selector according to the level (this information is stored in the TSS segment of the current task, (look at the next chapter). Vector Number Description Error Code 0 Divide Error No 1 Debug Exception No 2 NMI Interrupt No 3 Instruction Breakpoint No 4 INTO-detected overflow No 5 BOUND range excedeed No 6 Invalid Opcode No 7 Copro. not available No 8 Double Fault Yes 9 Copro. Segment Overrun No 10 Invalid TSS Yes 11 Segment not present No 12 Stack Fault Yes 13 General Protection Yes 14 Page Fault Yes 15 Reserved -- 16 Copro Error No 17-31 Reserved -- 31-255 Maskable Interrupt -- Error Code format: bit #0: External (1 means an external event causes the exception). bit #1: I (Exception relates to the IDT) bit #2: Table Indicator (0=GDT 1=LDT) bit #3-15: Selector that caused the exception. bit #16-31: Reserved Page Fault Error Code format: bit #0: (1 means that the page-level protection violation. 0 means page not present) bit #1: (1 Write operation fault. 0 Read ) bit #2: (1 Supervisor fault. 0 User fault) bit #3-31: Undefined.
HARDWARE MULTITASKING Multitasking is suported via Hardware directly (although W95 does not use it, due to speed problems), but how? Well in our GDT we can define TSS Descriptors, where the processor will store some important parameters before the task switch (registers, and some others like LDT, CR3...). The Task State Segment (TSS) format is: byte #00-01: Back Link to previous TSS byte #02-03: Reserved byte #04-07: ESP0 byte #08-09: SS0 byte #10-11: Reserved byte #12-15: ESP1 byte #16-17: SS1 byte #18-19: Reserved byte #20-23: ESP2 byte #24-25: SS2 byte #26-27: Reserved byte #28-31: CR3 byte #32-35: EIP byte #36-39: EFLAGS byte #40-43: EAX byte #44-47: ECX byte #48-51: EDX byte #52-55: EBX byte #56-59: ESP byte #50-63: EBP byte #64-67: ESI byte #68-71: EDI byte #72-73: ES byte #74-75: Reserved byte #76-77: CS byte #78-79: Reserved byte #80-81: SS byte #82-83: Reserved byte #84-85: DS byte #86-87: Reserved byte #88-89: FS byte #90-91: Reserved byte #92-93: GS byte #94-95: Reserved byte #96-97: LDT byte #98-99: Reserved byte #100-101: Reserved byte #101-102: I/O Map Base So if you want to switch between tasks, you must first load the Task Register (TR) with the current task segment selector (TSS) and the jmp or call to the other task segment descriptor. mov ax,task0_TSS_selector ltr ax call/jump far task1_TSS_selector You can create for every task you have a different Task Selector. Corrections, addings and comments are welcomed. +Rcg 1997
(c) +Rcg 1997. All rights reserved
You are deep inside fravia's page of reverse engineering, choose your way out:

homepage links red anonymity +ORC students' essays academy database
tools cocktails antismut CGI-scripts search_forms mail_fravia
Is reverse engineering illegal?