Tutorial: Finding Out What Notepad is Doing

In this chapter we will use Nitro to monitor Windows Notepad. While this might not be super interesting in itself, it demonstrates all the essential techniques that will enable you to use Nitro for tackling real-world challenges.

Getting a Connection

The first thing to do is to initialize Nitro and attach it to a virtual machine. For the purposes of this tutorial, we expect the VM to be already running but we could of course use libvirt APIs for automating setting up the environment. Additionally, Nitro obviously has to be in Python’s module search path for any of this to work.

from nitro.nitro import Nitro

with Nitro("Windows-VM", introspection=True) as nitro:
    self.nitro.listener.set_traps(True)

Here, we have imported the Nitro class and used it to connect to a libvirt domain named “Windows-VM”. The optional introspection argument indicates that we wish Nitro to create us a suitable analysis back end.

Inside the with statement, we enable traps. After the traps have been set, we are ready to start listening for low-level NitroEvent events from the virtual machine.

So far, the code doesn’t do anything too interesting as after the traps have been set, Nitro exits gracefully as there is nothing more to do.

Letting the Events Flow

Next, lets extend the code to get some events from the target machine. We can listen for events using Nitro object’s listen() method. Internally, Nitro will use the Listener it initialized for us.

from nitro.nitro import Nitro

with Nitro("Windows-VM", introspection=True) as nitro:
    self.nitro.listener.set_traps(True)
    for event in nitro.listen():
        print(event.as_dict())

listen() will give us a stream of NitroEvent events. Each event describes a state of the machine when a system call entry or exit happened. Here, we simply print a representation of each event received. This should quickly print a lot of data about things happening inside the VM:

{'cr3': '0x493bb000', 'direction': 'enter', 'rax': '0x33', 'type': 'syscall', 'vcpu': 0}
{'cr3': '0x493bb000', 'direction': 'exit', 'rax': '0x0', 'type': 'syscall', 'vcpu': 0}
{'cr3': '0x493bb000', 'direction': 'enter', 'rax': '0x16', 'type': 'syscall', 'vcpu': 0}
{'cr3': '0x493bb000', 'direction': 'exit', 'rax': '0x0', 'type': 'syscall', 'vcpu': 0}
{'cr3': '0x493bb000', 'direction': 'enter', 'rax': '0x16', 'type': 'syscall', 'vcpu': 0}
{'cr3': '0x493bb000', 'direction': 'exit', 'rax': '0x0', 'type': 'syscall', 'vcpu': 0}
{'cr3': '0x493bb000', 'direction': 'enter', 'rax': '0x18d', 'type': 'syscall', 'vcpu': 0}
{'cr3': '0x493bb000', 'direction': 'exit', 'rax': '0x0', 'type': 'syscall', 'vcpu': 0}

Each event line shows the most important properties of the event and, if we wanted, we could access even more information through the event object.

Understanding the Data

While having all this data is certainly interesting, it does little to help us in our mission to understand what Windows Notepad is doing while we use it to edit text. There is simply too much data with too little useful information for practical uses.

We can remedy the situation by calling in help the analysis back end that Nitro helpfully created for us. Using the Windows analysis back end, we can transform all the low-level events into something more useful.

from nitro.nitro import Nitro
from nitro.libvmi import LibvmiError

with Nitro("Windows-VM", introspection=True) as nitro:
    self.nitro.listener.set_traps(True)
    for event in nitro.listen():
        try:
            syscall = nitro.backend.process_event(event)
        except LibvmiError:
            print("Failed to analyze event :/")
        else:
            print(syscall.as_dict())

We now invoke back end’s process_event() method for each incoming event to see what they are about. The method returns us a Syscall event with all the associated information. It is worth noting that digging around virtual machine’s memory for information about events is a potentially challenging task that may result in an error. It is a good practice to catch those. Here, if everything went well, we will print the analysis result. This should result in something little more understandable.

{'event': {'cr3': '0x11b27000', 'type': 'syscall', 'direction': 'exit', 'vcpu': 0, 'rax': '0x0'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\mcbuilder.exe', 'command_line': 'C:\\Windows\\system32\\mcbuilder.exe', 'iswow64': False, 'create_time': '2017-10-05 02:23:15', 'pid': 1476, 'parent_pid': 1908, 'name': 'mcbuilder.exe'}, 'full_name': 'nt!NtCreateFile', 'name': 'NtCreateFile'}
{'event': {'cr3': '0x11b27000', 'type': 'syscall', 'direction': 'enter', 'vcpu': 0, 'rax': '0x47'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\mcbuilder.exe', 'command_line': 'C:\\Windows\\system32\\mcbuilder.exe', 'iswow64': False, 'create_time': '2017-10-05 02:23:15', 'pid': 1476, 'parent_pid': 1908, 'name': 'mcbuilder.exe'}, 'full_name': 'nt!NtCreateSection', 'name': 'NtCreateSection'}
{'event': {'cr3': '0x11b27000', 'type': 'syscall', 'direction': 'exit', 'vcpu': 0, 'rax': '0x0'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\mcbuilder.exe', 'command_line': 'C:\\Windows\\system32\\mcbuilder.exe', 'iswow64': False, 'create_time': '2017-10-05 02:23:15', 'pid': 1476, 'parent_pid': 1908, 'name': 'mcbuilder.exe'}, 'full_name': 'nt!NtCreateSection', 'name': 'NtCreateSection'}
{'event': {'cr3': '0x11b27000', 'type': 'syscall', 'direction': 'enter', 'vcpu': 0, 'rax': '0x25'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\mcbuilder.exe', 'command_line': 'C:\\Windows\\system32\\mcbuilder.exe', 'iswow64': False, 'create_time': '2017-10-05 02:23:15', 'pid': 1476, 'parent_pid': 1908, 'name': 'mcbuilder.exe'}, 'full_name': 'nt!NtMapViewOfSection', 'name': 'NtMapViewOfSection'}
{'event': {'cr3': '0x11b27000', 'type': 'syscall', 'direction': 'exit', 'vcpu': 0, 'rax': '0x0'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\mcbuilder.exe', 'command_line': 'C:\\Windows\\system32\\mcbuilder.exe', 'iswow64': False, 'create_time': '2017-10-05 02:23:15', 'pid': 1476, 'parent_pid': 1908, 'name': 'mcbuilder.exe'}, 'full_name': 'nt!NtMapViewOfSection', 'name': 'NtMapViewOfSection'}
{'event': {'cr3': '0x493bb000', 'type': 'syscall', 'direction': 'exit', 'vcpu': 0, 'rax': '0x102'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\svchost.exe', 'command_line': 'C:\\Windows\\system32\\svchost.exe -k netsvcs', 'iswow64': False, 'create_time': '2017-10-05 01:52:40', 'pid': 836, 'parent_pid': 452, 'name': 'svchost.exe'}, 'full_name': 'nt!NtWaitForSingleObject', 'name': 'NtWaitForSingleObject'}
{'event': {'cr3': '0x493bb000', 'type': 'syscall', 'direction': 'enter', 'vcpu': 0, 'rax': '0x5c'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\svchost.exe', 'command_line': 'C:\\Windows\\system32\\svchost.exe -k netsvcs', 'iswow64': False, 'create_time': '2017-10-05 01:52:40', 'pid': 836, 'parent_pid': 452, 'name': 'svchost.exe'}, 'full_name': 'nt!NtPowerInformation', 'name': 'NtPowerInformation'}
{'event': {'cr3': '0x493bb000', 'type': 'syscall', 'direction': 'exit', 'vcpu': 0, 'rax': '0x0'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\svchost.exe', 'command_line': 'C:\\Windows\\system32\\svchost.exe -k netsvcs', 'iswow64': False, 'create_time': '2017-10-05 01:52:40', 'pid': 836, 'parent_pid': 452, 'name': 'svchost.exe'}, 'full_name': 'nt!NtPowerInformation', 'name': 'NtPowerInformation'}
{'event': {'cr3': '0x493bb000', 'type': 'syscall', 'direction': 'enter', 'vcpu': 0, 'rax': '0x5c'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\svchost.exe', 'command_line': 'C:\\Windows\\system32\\svchost.exe -k netsvcs', 'iswow64': False, 'create_time': '2017-10-05 01:52:40', 'pid': 836, 'parent_pid': 452, 'name': 'svchost.exe'}, 'full_name': 'nt!NtPowerInformation', 'name': 'NtPowerInformation'}
{'event': {'cr3': '0x493bb000', 'type': 'syscall', 'direction': 'exit', 'vcpu': 0, 'rax': '0x0'}, 'process': {'path': '\\Device\\HarddiskVolume1\\Windows\\System32\\svchost.exe', 'command_line': 'C:\\Windows\\system32\\svchost.exe -k netsvcs', 'iswow64': False, 'create_time': '2017-10-05 01:52:40', 'pid': 836, 'parent_pid': 452, 'name': 'svchost.exe'}, 'full_name': 'nt!NtPowerInformation', 'name': 'NtPowerInformation'}

We can see that each event has been coupled with information about the process that caused it. We are already getting pretty close to our goal!

Looking for a Notepad

Now we have everything we need to spot the bits of data that interest us. In this case, we are interested in what Windows Notepad is doing when we open it.

from nitro.nitro import Nitro
from nitro.libvmi import LibvmiError

with Nitro("Windows-VM", introspection=True) as nitro:
    self.nitro.listener.set_traps(True)
    for event in nitro.listen():
        try:
            syscall = nitro.backend.process_event(event)
        except LibvmiError:
            print("Failed to analyze event :/")
        else:
            if syscall.process.name == "notepad.exe":
                print(syscall.as_dict())

To find out Notepad related events we simply inspect the process property associated with the event.