Rock ‘n’ Roll All Night, Pt. 1

# My Super Sweet 16-Bit Malware *MS-DOS Edition * [project]

Rock ‘n’ Roll All Night, Pt. 1:

The Memory-Resident Afterparty, Chez TSR

What is a TSR? A Terribly Secret Rendez-vous?

A fun new microservice offered by {*insert ur fav cloud provider here*}?

Another goddamn unnecessary franchise reboot?? (Nope, sry bb, there’s only room for one of those in this cyber toxic waste dump, and that spot belongs to this project.)

A Terminate-and-Stay-Resident program (TSR, no I don’t know why they didn’t capitalize the A and make it TASR. Probably because TASR sounds like “taser” and that just seems like a bad vibe and a marketing faux-pas. Best to stick with TSR. That’s slick, that’s appealing to the masses.)

is a clever workaround to the limitations of DOS being a single-task operating system, which allowed a program to “terminate and stay resident.” Since repeating the name back at you isn’t sufficient as a definition, let’s deconstruct this. We love a Derrida moment, so cute, so in right now.

Terminate - the program will terminate itself (v punk) and give control back to the OS; this is when a program ends and we return to our lovely little DOS prompt

Stay Resident - retain a copy of the program in working memory (RAM) to be fetched later upon subsequent invocations of that program/its methods

A TSR’s functionality will differ in one major way from a regular program: it terminates (this is generally done for a non-TSR program using a call to Interrupt 21h sub-function 4C, though other options are, of course, on the table), but before doing so, the TSR makes sure to save a copy of itself to RAM, so that it can be quickly retrieved at a later point.

What is the purpose of this?

DOS was a single-task operating system, so if a virus wanted to spawn additional copies of itself or reside in a different region of memory (i.e. for the purposes of persistence), and perform additional tasks in the background cued by certain events (i.e. interrupts, like keyboard presses — INT 16h or 9h — etc.), after it had been executed, then that program had to find a way to remain in memory. One of the most popular techniques (and the one that was created as a feature for DOS) was the Terminate and Stay Resident functionality.

The terminate and stay functionality wasn’t a feature that was created *for* vx authors. Of course.

But it was a feature of great interest to vx authors. Of course.

Okay, right but then what was the purpose of a TSR? Why did it exist as … *something* to the OS?

This architecture is weird! I’m tired! Where’s Win32??

Win32 is dead and MS-DOS is in charge now.

Yikes, that got dark.

Let me set the stage and better contextualize the TSR by explaining why using a single-task OS presented some unique challenges for vx authors. In order to do that, we’ll have to gain a better understanding of how a regular normie user would interact with MS-DOS. If we want to know how a vx-er would approach hacking the OS, we have to know how that system is expected to behave (idiosyncrasies and all) under normal conditions. We have to get to know that OS very well. Let’s begin.

EXECutive summary

First, let’s review the technical overview of the memory layout for a program invoked from the shell — that shell in this case being COMMAND.COM

COMMAND.COM is divided into roughly three parts:

  1. 1. A Resident Portion: contains routines that handle lower-level system call processing (i.e. Ctrl-C handler, Ctrl-Break handler, program termination routines), as well as error handling (including outputting messages about those errors to a user);
  2. 2. An initialization portion: used to invoke the programs listed in the AUTOEXEC.BAT file upon system boot; after completion of processing that file, the initialization portion is discarded (buh-byeeeee)
  3. 3. A Transient portion, can be used for processing input related to user applications (i.e. command line parameters, filenames) and also computing the checksum of the COMMAND.COM program itself after an extrinsic program terminates and returns control back to the shell.

[* Ray Duncan, ”Advanced MS-DOS Programming,” page 14-15]

And of course, there are different types of commands that can be taken as input to COMMAND.COM

  1. 1. Internal (intrinsic) commands: think CLS, DIR, COPY, etc. etc. All your fav DOS baddies.
  2. 2. External (extrinsic) commands; basically everyone else: all your programs on disk.
  3. 3. Batch files; honestly I’m tired. If you don’t already know what this is, then just read about it on Wikipedia. Batch files are the least relevant and don’t really matter for our purposes here.

[* Ray Duncan, ”Advanced MS-DOS Programming,” page 14-15]

A few important things to note: External (extrinsic) commands are not kept in RAM (…typically), so they must be reloaded from their location on disk every time they are invoked by a user. In contrast to intrinsic commands, external/extrinsic (user applications!! that’s what these are. Don’t worry, I won’t let you get lost in the brambly thicket of this jargon) commands are loaded into the Transient Program Area of memory (see the diagram below) when called. After external/extrinsic programs have finished doing their thing and want to terminate, they do so by calling the MS-DOS system call INT 21h (with sub-function 4Ch, under normal circumstances; alternatively, INT 20h, if a DOS programmer is trying to *spice things up*); this termination routine will take care of freeing the transient program’s memory, doing any necessary housekeeping and then returning control back to COMMAND.COM.

Memory map of MS-DOS after conclusion of the boot process

Out of all of that, here is the most important thing to remember, to solidify our understanding of memory and OS resource management under MS-DOS [remember that a transient program is a user-mode application, stored on disk]:

“A transient program has nearly complete control of the system’s resources while it is executing. The only other tasks that are accomplished are those performed by interrupt handlers (such as the keyboard driver and the runtime clock) and operations that the transient program requests from the operating system. MS-DOS does not support sharing of the central processor among several tasks executing concurrently, nor can it request control away from a program when it crashes or executes for too long.”

Ray Duncan, “Advanced MS-DOS Programming,” Page 16

A normal, non-resident user-mode (transient) program is launched using our fav interrupt, INT 21h, subfunction 4B — this is the system call for the EXEC function. EXEC could be used for various purposes, the most common/most important for our purposes here, is using a parent program to load and execute a "child program;" in the case of launching a transient/user-mode program, the parent program is the shell itself (COMMAND.COM) and the child program is the transient-mode programwe are executing (i.e. Arachne, a HELLOMSDOS.COM program (with a shorter name to fit the 8-char limit, of course. So let's call it HEYMSDOS.COM), a virus, etc.). In this scenario, using EXEC to launch a user-mode application, Int 21h. subfunction 4B must be called with the following parameters:

💾 Int 21h 4Bh - Execute Program (EXEC)

Execute Program

"Allows an application program to run another program, regaining control when it is finished. Can also be used to load overlays, although it is uncommon” (page 441, “Microsoft MS-DOS Programmer’s Reference,” Chapter 8: MS-DOS Functions Reference, Microsoft Corporation, 2nd ed.: version 6.0., Microsoft Press, 1993)

(page 219, “Microsoft MS-DOS Programmer’s Reference,” Chapter 8: The EXEC Function, Microsoft Corporation, 2nd ed.: version 6.0., Microsoft Press, 1993)

Called with:

AH = 4Bh

AL = 00h

DS:DX = segment:offset of pathname for program

ES:BX = segment:offset of parameter block for program

The EXEC function could also be used to load program overlays. We will not be delving into the details of this aspect of EXEC's functionality, other than to note that loading an overlay using EXEC was done with a load type of 03h in AL (meaning that the value of AX would be 4B03h when using EXEC to load an overlay).

During the lifetime of a transient/user-mode program's execution, it resides only in the Transient Program Area, before returning control to COMMAND.COM and disappearing from RAM 4ever (or, at least until the next time it is called). While executing, the external/extrinsic user-mode application has nearly complete control of system resources, but it is limited in how it can use those resources (relatable).

Alright, I believe that is sufficient in terms of the technical background. Let’s now turn our focus to the actual ~user experience~

So normally, if you were typing a document in Microsoft Word 3.0 and you wanted to look up an article on your fav web browser, Arachne, you had to do the following: save your file in Word, exit the program, open Arachne, look up the article and then close that when you wanted to use another application.

Basically, you could only have the application window for one program on the screen at a given time (so the user can only ever see one thing in the “foreground” of the image plane, as it were. (What about the background of the image? There isn’t one! That’s the point. It’s a single flat 2D plane in Cartesian space.)), and you couldn’t easily toggle back and forth between multiple different running applications. This is because DOS would only ever run one application at a time; switching between programs required the slow and tedious process of saving/closing, opening the next, and then saving/closing. Rinse repeat.

the greatest browser of all time (and still criminally under-appreciated). greetz to Arachne, a goddamn legend <3

The notion of this presents an almost absurdly disparate understanding of memory management from what we almost always experience and interact with presently: a multitasking operating system (an OS where you can run multiple programs at the same time; i.e. I have Spotify playing while I am typing this on a note in an app on a browser, these can each be broadly/crudely thought of as different tasks that the OS is running at the same time, acknowledging that discussions of concurrency and parallelism are happening, but we’re putting them on pause for the moment(and yes, I am aware that there are many many layers of both a desktop app and the web app/browser combo and that describing each of these as a “single task” isn’t realistic from the perspective of the process manager or any other component of the OS. I am using this analogously (we love a Claude Shannon moment, even three layers deep into the parenthetical) so humor me here.)) is so prevalent that it is difficult to remember using a computer where there weren’t multiple tabs or ughhh *Windows* open. Is that why it’s called Windows?? Yes.

Arachne understands my vibe. IE could never.

Can we see the parallel here? Are we tracking this analogy? I hope so because I am worn out from doing some Daedalus footwork to escape that recent labyrinthine parenthetical mayhem.

It’s exhausting, it’s tedious. Who would stand for this? Why weren’t MS-DOS users rioting in protest of this??

Actually, at the time, no one was probably that bothered, based on the fact that personal computing was still so new. And who doesn’t love just retreating into the peaceful solitude of Microsoft Paintbrush 1989 every now and then?

if your paint application isn’t creating an ambience that screams “emo but liminal,” then is it even worth using?

So a single-task OS is fine for a fun chill time, surfing the net on Arachne or making moody drawings in Microsoft Paintbrush 1989 (yes I will use the entire name every time. The whole name is a vibe.) But what if you want to do more?

For instance, for performing certain functionality, it would be helpful if the limitations of the single-task operating system could be momentarily, hmm how shall I put this? Bypassed? No… Reconfigured? No…let’s just call it how it is: hacked.


Imagine a conversation between two programmers, trying to design this feature for MS-DOS…


Imaginary MS-DOS programmer A:

“What we need is essentially a daemon, a program that can install itself in RAM and that will be triggered to execute a certain task by an interrupt. This kind of daemon (not what it is or what it will be called, but this might be easier to think of for the moment) can perform important OS maintenance tasks that would (most likely) not significantly hinder the ~user experience~.”


Imaginary MS-DOS programmer B:

“You’re right… We could totally do that. Just hook an interrupt in the IVT and presto, new way to call the program from RAM. It would be a handy little feature that no one but the most dedicated readers of the programming manual would know about. And let’s call it … a Terminate and Stay Resident program, or a TSR. That’s slick, that’s marketable.”


Did this conversation actually happen? Probabilistically no (though DOS preceded me so who knows), but for the sake of linguistic party tricks, I hope you’re still playing along at home.

Because yes, Terminate and Stay Resident was, in fact, a marketing choice (insofar as the naming conventions of major OS features and functionality can be considered a marketing choice, which I think they absolutely are, so we’re going with that) because it was the name of a popular feature of MS-DOS: the Paleolithic version of a daemon was a well-documented *feature* of the OS.

Now, why would a TSR be a documented feature? Well, for one thing, it was used by common utility programs of the OS (the Print Spooler, PRINT.COM was a TSR). It was helpful for managing routine tasks and you know, doing the fun things to keep the OS running smoothly.

As we all know, daemons aren’t that spooky. Daemons are our friends. They’re prevalent in every modern operating system in a variety of flavors, the most obvious of which is every Linux system process which is a daemon ends in a d (systemd, etc. etc.). Daemons permit a great amount of flexibility for someone (i.e. a programmer, a user, a vx-er perhaps) to mold the OS into an optimally desired form. A great number of OS housekeeping functionality is the result of tireless daemons.

And in fact, having TSRs implement this daemonic functionality (we’re going to once again return to calling TSRs using the correct terminology of TSRs; mostly, because I think this daemon analogy has gone off the rails and I can’t be Buster Keaton running along this rogue locomotive forever) through the Interrupt Vector Table (IVT) is a clever design choice.

A TSR can leverage a data structure that is defined at a fixed location in memory and that is created and partially populated before the OS has even been loaded by the boot code.

We’ll come back to this concept later, when we explore bootkits. Just keep it in the back of your mind now that setting up the IVT is done very early in the boot process, and that many of the entries are set for BIOS interrupts before the OS is loaded; the remaining interrupts are set by the OS after it has been loaded, i.e. INT 21h for MS-DOS is not defined until the end of the OS boot process, but there is an interim period during early phases of the boot process where only the BIOS interrupts *are* defined. Got it? No? There will be pictures later to guide you. Let’s continue.

So TL;DR: a TSR is a feature of MS-DOS that allows a user to bypass the limitations of a single-task OS by installing a persistent program in RAM, which will be invoked by subsequent interrupts.

Though it’s strange, I have this funny feeling that this could go badly… because it reminds me of the fact that a lot of common utility programs in later versions of Windows were also “daemons” (i.e. Windows services, which is the Windows equivalent of a daemon. Why not just use the same term as other OSes?? HA. Windows isn’t *like* the other Operating Systems. Hair flip. jfc this is unhinged, let’s proceed) because they performed functionality in kernel space, after being invoked by a user-mode application, while running in the background and minimizing their presence on the system. And wow, it’s funny, but the SSDT (System Service Descriptor Table) and the IDT (Interrupt Descriptor Table) are also useful structures to leverage for hooking those system calls. And hooking the SSDT and IDT were techniques employed by rootkits of the Windows NT rootkit heyday [”Rootkits: Subverting the Windows Kernel” by Greg Hoglund and James Butler, page 82-95, “Chapter 4:The Age-Old Art of Hooking.”] Wow, what an odd coincidence.

But that wouldn’t have any relation to what we’re talking about. Of course not. Because DOS viruses certainly would not have done anything as destructive or complex as say, rootkits of the Windows NT rootkit heydey, right?


Things are about to get devious. If you’re the type of person who balks at that type of thing, best to turn away now. This is your warning. Though, if we’re being honest, if you’re still reading, then we both know that you’re the type of person who was waiting for this. Don’t worry, rambling intro is over. Let the rambling analysis commence.