├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── rules └── source │ └── format ├── debug └── memdump.py ├── doc ├── Makefile ├── make.bat └── source │ ├── architecture.rst │ ├── commandline.rst │ ├── conf.py │ ├── index.rst │ ├── installation.rst │ ├── introduction.rst │ ├── modules.rst │ ├── nitro.backends.linux.rst │ ├── nitro.backends.rst │ ├── nitro.backends.windows.rst │ ├── nitro.rst │ ├── resources │ └── nitro-architecture.svg │ ├── samples │ ├── testing-01.txt │ ├── testing-02.txt │ ├── tutorial-01.py │ ├── tutorial-02-output.txt │ ├── tutorial-02.py │ ├── tutorial-03-output.txt │ ├── tutorial-03.py │ └── tutorial-04.py │ ├── testing.rst │ └── tutorial.rst ├── libvmi.conf ├── main.py ├── nitro ├── __init__.py ├── backends │ ├── __init__.py │ ├── arguments.py │ ├── backend.py │ ├── factory.py │ ├── linux │ │ ├── __init__.py │ │ ├── arguments.py │ │ ├── backend.py │ │ └── process.py │ ├── process.py │ └── windows │ │ ├── __init__.py │ │ ├── arguments.py │ │ ├── backend.py │ │ ├── get_symbols.py │ │ ├── process.py │ │ └── types.py ├── event.py ├── kvm.py ├── listener.py ├── nitro.py └── syscall.py ├── requirements.txt ├── setup.py └── tests ├── .gitignore ├── README.md ├── binaries ├── createfile_all.exe ├── createfile_append.exe ├── createfile_execute.exe ├── createfile_file_execute.exe ├── createfile_read.exe ├── createfile_read_data.exe ├── createfile_write.exe ├── createfile_write_data.exe └── delete_file.exe ├── binaries_sources └── createfile │ ├── createfile.sln │ ├── createfile │ ├── ReadMe.txt │ ├── createfile.vcxproj │ ├── createfile.vcxproj.filters │ ├── foobar.txt │ └── main.cpp │ ├── createfile_all │ ├── createfile_all.vcxproj │ ├── createfile_all.vcxproj.filters │ └── main.cpp │ ├── createfile_append │ ├── createfile_append.vcxproj │ ├── createfile_append.vcxproj.filters │ └── main.cpp │ ├── createfile_execute │ ├── ReadMe.txt │ ├── createfile_execute.vcxproj │ ├── createfile_execute.vcxproj.filters │ └── main.cpp │ ├── createfile_file_execute │ ├── createfile_file_execute.vcxproj │ ├── createfile_file_execute.vcxproj.filters │ └── main.cpp │ ├── createfile_read_data │ ├── createfile_read_data.vcxproj │ ├── createfile_read_data.vcxproj.filters │ └── main.cpp │ ├── createfile_write_data │ ├── createfile_write_data.vcxproj │ ├── createfile_write_data.vcxproj.filters │ └── main.cpp │ ├── delete_file │ ├── delete_file.vcxproj │ ├── delete_file.vcxproj.filters │ └── main.cpp │ └── openfile_write │ ├── ReadMe.txt │ ├── main.cpp │ ├── openfile_write.vcxproj │ └── openfile_write.vcxproj.filters ├── cdrom.py ├── layers.py ├── linux_binaries ├── Makefile ├── test_open.c ├── test_unlink.c └── test_write.c ├── test_linux.py ├── test_windows.py ├── test_windows_debug.py ├── unittest.cfg ├── unittests ├── README.md ├── resources │ └── syscall_table_sample.bin └── test_linux.py ├── vm_templates ├── .gitignore ├── README.md ├── answer_files │ ├── 7 │ │ └── Autounattend.xml │ ├── 8 │ │ └── Autounattend.xml │ └── ubuntu │ │ └── preseed.cfg ├── http │ └── preseed.cfg ├── import_libvirt.py ├── linux │ ├── System.map-4.4.0-87-generic │ ├── cdrom.rules │ ├── cdrom_autoexec.service │ ├── cdrom_autoexec.sh │ ├── journald.conf │ └── rc.local ├── packer ├── template_domain.xml ├── ubuntu.json ├── ubuntu_1604_x64.json ├── windows.json ├── windows_7_x64.json └── windows_8_x64.json └── vmtest_helper.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | 5 | # Libraries 6 | *.lib 7 | *.a 8 | 9 | # Shared objects (inc. Windows DLLs) 10 | *.dll 11 | *.so 12 | *.so.* 13 | *.dylib 14 | 15 | # Executables 16 | *.out 17 | *.app 18 | 19 | # cache/backup 20 | *~ 21 | 22 | #KDevelop files 23 | *.kdev4 24 | 25 | # python 26 | __pycache__ 27 | *.pyc 28 | 29 | # memory dump 30 | *.raw 31 | 32 | # libnitro cmake build dir 33 | libnitro/_build 34 | libnitro/build 35 | 36 | # virtualenv 37 | venv/ 38 | 39 | # rekall cache 40 | rekall_cache/ 41 | 42 | events.json 43 | 44 | # ide 45 | .idea/ 46 | .vscode/ 47 | 48 | # package 49 | dist 50 | build 51 | *.egg-info 52 | libvmi_helper.log 53 | 54 | # tags 55 | TAGS 56 | GPATH 57 | GRTAGS 58 | GTAGS 59 | 60 | # Linux test binaries 61 | tests/linux_binaries/build/ 62 | 63 | # cffi 64 | *.c 65 | 66 | # sphinx build dir 67 | doc/build/ 68 | 69 | # Virtual env 70 | venv/ 71 | 72 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | install: 5 | - sudo apt-get update 6 | - sudo apt-get install -y libvirt-dev libjson-c-dev 7 | - pip install -r requirements.txt 8 | - git clone https://github.com/KVM-VMI/libvmi.git 9 | - pushd libvmi && autoreconf -vif && ./configure --prefix=/usr && make && sudo make install && popd 10 | jobs: 11 | include: 12 | - stage: test 13 | before_script: 14 | - cd tests/unittests 15 | script: 16 | - nose2 17 | - stage: docs 18 | before_script: 19 | - cd doc 20 | script: 21 | - make html 22 | deploy: 23 | provider: pages 24 | skip_cleanup: true 25 | local_dir: build/html 26 | github_token: $GITHUB_TOKEN 27 | target_branch: gh-pages 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nitro 2 | 3 | Virtual Machine Introspection for KVM. 4 | 5 | This is the userland component named `nitro`. 6 | It will receive the events generated by KVM and display them. 7 | 8 | # Requirements 9 | 10 | - `python 3` 11 | - `docopt` 12 | - `libvirt` 13 | - `ioctl-opt Python 3` 14 | - `cffi Python3` (optional) 15 | - `libvmi` (optional) 16 | - `rekall` (optional) 17 | 18 | # Setup 19 | 20 | - Setup a VM. Make sure to use the `qemu:///system` connection. 21 | Go to the `tests` folder to find a packer template and an import script if 22 | you don't have one already. 23 | 24 | (Nitro only supports for now `Windows XP x64` and `Windows 7 x64`, see the `Note` section below) 25 | 26 | 27 | # Usage 28 | 29 | - Make sure that you have loaded the modified kvm modules. 30 | (`cd kvm-vmi && make modules && make reload`) 31 | 32 | - Start the VM that you would like to monitor. 33 | 34 | - Wait for the desktop to be available on the VM. 35 | 36 | - Start `Nitro` with `./main.py `. 37 | 38 | ~~~ 39 | """Nitro. 40 | 41 | Usage: 42 | main.py [options] 43 | 44 | Options: 45 | -h --help Show this screen 46 | --nobackend Don't analyze events 47 | -o --output Output file (stdout if not specified) 48 | 49 | """ 50 | ~~~ 51 | 52 | Nitro monitors the given `` syscalls by activating a set of traps in KVM. 53 | The optional components listed above are needed only if you want to extract more information 54 | about the captured events. See the Backend section. 55 | 56 | Here i will assume that you have installed only the required ones. 57 | Therefore you have to run Nitro with the option `--nobackend`. 58 | 59 | It will run until the user sends a `CTRL+C` to stop it, in which case Nitro 60 | will unset the traps and write the captured events in a file named `events.json`. 61 | 62 | By defaults, Nitro will print events to stdout. If this is not desired `--out` 63 | can be used to redirect output into a file. 64 | 65 | An event should look like this output 66 | ~~~JSON 67 | { 68 | "direction": "enter", 69 | "rax": "0x1005", 70 | "vcpu": 0, 71 | "type": "syscall", 72 | "cr3": "0x1b965000" 73 | }, 74 | ~~~ 75 | 76 | 77 | A successful run should give the following output : 78 | 79 | ~~~ 80 | $ ./main.py --nobackend nitro_win7x64 81 | Setting traps to False 82 | Finding QEMU pid for domain nitro_win7x64 83 | Detected 1 VCPUs 84 | Setting traps to True 85 | Start listening on VCPU 0 86 | {'cr3': '0x6cdc000', 87 | 'direction': 'exit', 88 | 'rax': '0x3f', 89 | 'type': 'syscall', 90 | 'vcpu': 0} 91 | {'cr3': '0x6cdc000', 92 | 'direction': 'enter', 93 | 'rax': '0x138', 94 | 'type': 'syscall', 95 | 'vcpu': 0} 96 | {'cr3': '0x6cdc000', 97 | 'direction': 'exit', 98 | 'rax': '0x0', 99 | 'type': 'syscall', 100 | 'vcpu': 0} 101 | {'cr3': '0x6cdc000', 102 | 'direction': 'enter', 103 | 'rax': '0x58', 104 | 'type': 'syscall', 105 | 'vcpu': 0} 106 | {'cr3': '0x6cdc000', 107 | 'direction': 'exit', 108 | 'rax': '0x0', 109 | 'type': 'syscall', 110 | 'vcpu': 0} 111 | {'cr3': '0x6cdc000', 112 | 'direction': 'enter', 113 | 'rax': '0x138', 114 | 'type': 'syscall', 115 | 'vcpu': 0} 116 | {'cr3': '0x6cdc000', 117 | 'direction': 'exit', 118 | 'rax': '0x0', 119 | 'type': 'syscall', 120 | 'vcpu': 0} 121 | {'cr3': '0x6cdc000', 122 | 'direction': 'enter', 123 | 'rax': '0x5f', 124 | 'type': 'syscall', 125 | 'vcpu': 0} 126 | Setting traps to False 127 | ~~~ 128 | 129 | # Backend 130 | 131 | The Backend is supposed to analyze raw nitro events, and extract useful 132 | informations, such as: 133 | - process name 134 | - process PID 135 | - syscall name 136 | 137 | ## Rekall 138 | 139 | `Rekall` is used in `symbols.py` to extract the syscall table from 140 | the memory dump. 141 | 142 | Unfortunately, `Rekall` is not available as a Debian package. 143 | For now you will have to install it system-wide with `pip`. (`Python2`) 144 | 145 | ~~~ 146 | $ sudo pip2 install --upgrade setuptools pip wheel 147 | $ sudo pip2 install rekall 148 | ~~~ 149 | 150 | ## libvmi 151 | 152 | - Compile and install `libvmi`. See the [install notes](http://libvmi.com/docs/gcode-install.html) 153 | 154 | - Configure the file `libvmi.conf`, which is already provided in the repo 155 | 156 | Configure the name of your vm that you want to monitor : 157 | (only `Windows 7 x64` is supported here) 158 | 159 | ~~~ 160 | nitro_win7x64 { 161 | ostype = "Windows"; 162 | win_tasks = 0x188; 163 | win_pdbase = 0x28; 164 | win_pid = 0x180; 165 | win_pname = 0x2e0; 166 | } 167 | ~~~ 168 | 169 | At least, the following keys are required : 170 | - `win_tasks` 171 | - `win_pdbase` 172 | - `win_pid` 173 | - `win_pname` 174 | 175 | ## libvmi python wrapper 176 | 177 | The python wrapper on top of Libvmi is based on `CFFI` and needs to be compiled. 178 | 179 | ~~~ 180 | $ python3 nitro/build_libvmi.py 181 | ~~~ 182 | 183 | ## Running Nitro with the Backend 184 | 185 | If you have installed everything correctly, you can run Nitro : 186 | `./main.py nitro_win7x64` 187 | 188 | An event should now look like this: 189 | ~~~JSON 190 | { 191 | "event": { 192 | "cr3": "0xbda6000", 193 | "direction": "enter", 194 | "type": "syscall", 195 | "vcpu": 0, 196 | "rax": "0x14" 197 | }, 198 | "name": "nt!NtQueryValueKey", 199 | "process": { 200 | "name": "services.exe", 201 | "pid": 456 202 | } 203 | }, 204 | ~~~ 205 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | nitro (0.0.1) stable; urgency=medium 2 | 3 | * Initial release. 4 | 5 | -- Mathieu Tarral Fri, 17 Feb 2017 14:10:00 +0300 6 | 7 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: nitro 2 | Maintainer: Mathieu Tarral 3 | Section: python 4 | Priority: optional 5 | Build-Depends: python3-setuptools, 6 | python3-all, 7 | debhelper (>= 9), 8 | X-Python3-Version: >= 3.4 9 | Standards-Version: 3.9.4 10 | Vcs-Browser: https://github.com/KVM-VMI/nitro 11 | Vcs-Git: git@github.com:KVM-VMI/nitro.git 12 | 13 | Package: python3-nitro 14 | Architecture: all 15 | Depends: ${misc:Depends}, 16 | ${python3:Depends}, 17 | python3-docopt, 18 | python3-libvirt, 19 | python3-psutil, 20 | python, 21 | python-docopt, 22 | Description: Syscall interception library 23 | Nitro allows you to monitor your virtual machines using vmi techniques to 24 | intercept every system call and analyze them 25 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/debian/copyright -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | export PYBUILD_NAME=nitro 4 | %: 5 | dh $@ --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debug/memdump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """MemDump. 4 | 5 | Usage: 6 | memdump.py [options] 7 | 8 | Options: 9 | -h --help Show this screen. 10 | """ 11 | 12 | import os 13 | import stat 14 | from docopt import docopt 15 | import libvirt 16 | from tempfile import NamedTemporaryFile, TemporaryDirectory 17 | 18 | def main(args): 19 | vm_name = args[''] 20 | # get domain from libvirt 21 | con = libvirt.open('qemu:///system') 22 | domain = con.lookupByName(vm_name) 23 | 24 | path = os.path.join(os.getcwd(), '{}.raw'.format(vm_name)) 25 | with open(path, 'w') as f: 26 | # chmod to be r/w by everyone 27 | os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | 28 | stat.S_IRGRP | stat.S_IWGRP | 29 | stat.S_IROTH | stat.S_IWOTH) 30 | # take a ram dump 31 | flags = libvirt.VIR_DUMP_MEMORY_ONLY 32 | dumpformat = libvirt.VIR_DOMAIN_CORE_DUMP_FORMAT_RAW 33 | domain.coreDumpWithFormat(path, dumpformat, flags) 34 | 35 | if __name__ == '__main__': 36 | main(docopt(__doc__)) 37 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python3 -msphinx 7 | SPHINXPROJ = nitro 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | pushd %~dp0 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=python -msphinx 7 | ) 8 | set SOURCEDIR=source 9 | set BUILDDIR=build 10 | set SPHINXPROJ=nitro 11 | 12 | if "%1" == "" goto help 13 | 14 | %SPHINXBUILD% >NUL 2>NUL 15 | if errorlevel 9009 ( 16 | echo. 17 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 18 | echo.then set the SPHINXBUILD environment variable to point to the full 19 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 20 | echo.Sphinx directory to PATH. 21 | echo. 22 | echo.If you don't have Sphinx installed, grab it from 23 | echo.http://sphinx-doc.org/ 24 | exit /b 1 25 | ) 26 | 27 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 28 | goto end 29 | 30 | :help 31 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 32 | 33 | :end 34 | popd 35 | -------------------------------------------------------------------------------- /doc/source/architecture.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Architecture Overview 3 | ======================= 4 | 5 | In this chapter, we take a look at how the project is structured and how the 6 | different components fit together. 7 | 8 | .. figure:: resources/nitro-architecture.svg 9 | :align: center 10 | :alt: Nitro's architecture 11 | 12 | Main components of Nitro. Listener receives low-level events from the kernel 13 | and passes them to the user through the frameworks namesake :class:`~.Nitro` 14 | class. The user can then decide to request for additional analysis from one 15 | of Nitro's back ends. Back ends transform the low-level events into system 16 | call events that contain additional useful information such as the process 17 | where the system call originated and offer an easy way to access the 18 | arguments of the call. 19 | 20 | Virtual Machines 21 | ================ 22 | 23 | Nitro depends on `libvirt `__ to manage virtual machine 24 | life cycle and configuration for it. Through the libvirt API, Nitro can start, 25 | stop and pause virtual machines without having to directly deal with the 26 | hypervisor. 27 | 28 | Internally, QEMU makes use of Linux's `KVM 29 | `__ feature for 30 | supporting hardware assisted virtualization. 31 | 32 | libvirt's `documentation `__ offers additional 33 | instructions on how to install and use it to effectively manage virtual 34 | machines. 35 | 36 | Nitro 37 | ===== 38 | 39 | Framework's namesake :class:`~.Nitro` class acts as a frontend for accessing 40 | much of the functionality. By creating a Nitro object, we can start listening 41 | for events from the virtual machine and, optionally, access an analysis back end 42 | associated with the guest's operating system. 43 | 44 | Throughout the framework, you will find a heavy use of Python's `context 45 | managers `__ 46 | in the APIs to ease reasource management. For example, :class:`~.Nitro` objects 47 | automatically stop their associated :ref:`listeners ` and, if 48 | instantiated, :ref:`back ends `. 49 | 50 | .. _listeners: 51 | 52 | Event Listeners 53 | =============== 54 | 55 | Nitro's :class:`~.Listener` enables subscribing to events from the kernel 56 | regarding a particular virtual machine. Listener issues a set of Nitro specific 57 | `IOCTL `__ commands to the KVM in order to 58 | attach to a particular virtual CPU belonging to the virtual machine being 59 | monitored. 60 | 61 | Listener's :meth:`~.Listener.listen` method is a generator that produces a 62 | series of :class:`~.NitroEvent` objects. These objects represent the low-level 63 | state of the machine when the system call took place. They contain information 64 | about the registers and whether the machine was entering or exiting a system 65 | call. Additionally, the events record which of the (possibly many) virtual CPU's 66 | associated with the machine was responsible the event. 67 | 68 | .. _backends: 69 | 70 | Analysis Back ends 71 | ================== 72 | 73 | While knowing the register values is nice, for many real-world applications a 74 | higher-level view of the system if often preferred. :class:`~.Backend` classes 75 | transform the lower-level events that listeners produce into something more 76 | useful. Since back ends depend on intricate knowledge of operating system 77 | specific internals, they are specific to a particular operating system. Nitro 78 | ships with back ends for 64-bit Windows 7 Professional and Linux. The 79 | :class:`~.Nitro` class automatically initializes a suitable back end based on 80 | the virtual machine's operating system. 81 | 82 | Back ends :meth:`process_event` produces a :class:`~.Syscall` object based on 83 | the :class:`~.NitroEvent` object given to it. System call objects offer a 84 | higher-level representation of the machine's state by associating the event with 85 | a logical name (``open``, ``write``, ``NtCreateProcess``…) and finding the 86 | process that caused it. 87 | 88 | For the analysis to be possible, back ends have to have access to the virtual 89 | machines memory. This access is granted by the custom version of QEMU that Nitro 90 | depends on. The current back ends make use of `libvmi `__ to 91 | dissect virtual machine's memory. 92 | 93 | Process Info Objects 94 | ==================== 95 | 96 | As a whole, virtual machines typically produce a lot of system call events. For 97 | practical purposes, it is often useful to concentrate only on a tiny fraction of 98 | the events that the system produces. Knowing which process caused the event is 99 | useful for separating interesting events from the noise. 100 | 101 | Back ends associate each system call with the process that originated them. 102 | Process information is stored in :class:`~.Process` objects. These are specific 103 | to each back end as they can contain operating system specific process 104 | information. For example, the Windows specific :class:`~.WindowsProcess` class 105 | knows whether or not the process is running in the 32-bit mode. What is common 106 | between the different implementations is the process objects all contain some 107 | form of process ID and the name of the program binary associated with the 108 | process. 109 | 110 | System Call Argument Access 111 | =========================== 112 | 113 | With analysis back ends, we can have a rough idea about what the system is doing 114 | by looking at which system calls processes are invoking. However, this is often 115 | not enough as we would like to also know the arguments for each call. It is much 116 | more useful to know which file the monitored program is trying to access than to 117 | simply know that it is trying to access something. For this purpose, analysis 118 | back ends associate an :class:`~.ArgumentMap` with each system call event. 119 | 120 | :class:`~.ArgumentMap` allows inspecting and modifying the arguments passed to 121 | each system call. Argument map abstracts operating system specific system call 122 | conventions and makes it possible to access arguments through a list-like 123 | object. As with :class:`~.Process` objects, argument maps are specific to each 124 | back end as they depend on intricate knowledge about system call conventions. 125 | 126 | By indexing argument map objects, we can get the raw values passed in through 127 | registers and/or memory, depending on the calling conventions. However, it is 128 | noteworthy that Nitro does not currently try to further analyze the content of 129 | these values and this is left to the user if they require such functionality. 130 | 131 | .. note:: There is ongoing work on designing mechanisms for easier extraction of 132 | higher-level data structures from the raw arguments. This space is 133 | likely going to change as the work progresses further. 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /doc/source/commandline.rst: -------------------------------------------------------------------------------- 1 | Command-line Interface 2 | ====================== 3 | 4 | In addition to being usable as an API for virtual machine introspection, Nitro 5 | can be used as a command-line tool. In this form of operation, Nitro can attach 6 | to running virtual machines and output information about all the events it sees. 7 | 8 | The command line interface can be invoked using the ``nitro`` command. To attach 9 | to a running ``libvirt`` domain named ``nitro_ubuntu1604`` and saving the 10 | generated event stream into ``events.json``, run: 11 | 12 | :: 13 | 14 | $ nitro -o events.json nitro_ubuntu1604 15 | 16 | If the output file is not specified, Nitro defaults to printing the event stream 17 | to the standard output. 18 | 19 | .. cmdoption :: -o FILE, --out FILE 20 | 21 | Specify where the recorded events are saved. If not present, Nitro prints the 22 | events to the standard output. 23 | 24 | By default, Nitro tries to use a suitable backend based on the guest's operating 25 | system for semantic information and enrich the raw low-level events. The 26 | ``--nobackend`` option is provided to disable this semantic translation. 27 | 28 | .. cmdoption :: --nobackend 29 | 30 | Disable the use of analysis back ends. In this mode, Nitro will only gather 31 | low-level event data. 32 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # nitro documentation build configuration file, created by 5 | # sphinx-quickstart on Mon Aug 7 13:08:35 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # -- General configuration ------------------------------------------------ 17 | 18 | # Modify path 19 | import os, sys 20 | module_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir)) 21 | sys.path.insert(0, module_dir) 22 | 23 | # Mock some of the requirements as we do not really need them for documentation 24 | # generation. 25 | from unittest.mock import MagicMock 26 | 27 | for mod in ("libvirt", "nitro._libvmi", "nitro.build_libvmi", "psutil", "ioctl_opt", "docopt"): 28 | sys.modules[mod] = MagicMock() 29 | 30 | # If your documentation needs a minimal Sphinx version, state it here. 31 | # 32 | # needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = ['sphinx.ext.autodoc', 38 | 'sphinx.ext.autosummary', 39 | 'sphinx.ext.githubpages'] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # The suffix(es) of source filenames. 45 | # You can specify multiple suffix as a list of string: 46 | # 47 | # source_suffix = ['.rst', '.md'] 48 | source_suffix = '.rst' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = 'nitro' 55 | copyright = '' 56 | author = '' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = '0.0.1' 64 | # The full version, including alpha/beta/rc tags. 65 | release = '0.0.1' 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | # This patterns also effect to html_static_path and html_extra_path 77 | exclude_patterns = [] 78 | 79 | # The name of the Pygments (syntax highlighting) style to use. 80 | pygments_style = 'sphinx' 81 | 82 | # If true, `todo` and `todoList` produce output, else they produce nothing. 83 | todo_include_todos = False 84 | 85 | 86 | # -- Options for HTML output ---------------------------------------------- 87 | 88 | # The theme to use for HTML and HTML Help pages. See the documentation for 89 | # a list of builtin themes. 90 | # 91 | html_theme = 'sphinx_rtd_theme' 92 | html_theme_options = {} 93 | 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | # 99 | # html_theme_options = {} 100 | 101 | # Add any paths that contain custom static files (such as style sheets) here, 102 | # relative to this directory. They are copied after the builtin static files, 103 | # so a file named "default.css" will overwrite the builtin "default.css". 104 | # html_static_path = ['_static'] 105 | 106 | # Custom sidebar templates, must be a dictionary that maps document names 107 | # to template names. 108 | # 109 | # This is required for the alabaster theme 110 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 111 | html_sidebars = { 112 | '**': [ 113 | 'about.html', 114 | 'navigation.html', 115 | 'relations.html', # needs 'show_related': True theme option to display 116 | 'searchbox.html', 117 | 'donate.html', 118 | ] 119 | } 120 | 121 | 122 | # -- Options for HTMLHelp output ------------------------------------------ 123 | 124 | # Output file base name for HTML help builder. 125 | htmlhelp_basename = 'nitrodoc' 126 | 127 | 128 | # -- Options for LaTeX output --------------------------------------------- 129 | 130 | latex_elements = { 131 | # The paper size ('letterpaper' or 'a4paper'). 132 | # 133 | # 'papersize': 'letterpaper', 134 | 135 | # The font size ('10pt', '11pt' or '12pt'). 136 | # 137 | # 'pointsize': '10pt', 138 | 139 | # Additional stuff for the LaTeX preamble. 140 | # 141 | # 'preamble': '', 142 | 143 | # Latex figure (float) alignment 144 | # 145 | # 'figure_align': 'htbp', 146 | } 147 | 148 | # Grouping the document tree into LaTeX files. List of tuples 149 | # (source start file, target name, title, 150 | # author, documentclass [howto, manual, or own class]). 151 | latex_documents = [ 152 | (master_doc, 'nitro.tex', 'nitro Documentation', 153 | '', 'manual'), 154 | ] 155 | 156 | 157 | # -- Options for manual page output --------------------------------------- 158 | 159 | # One entry per manual page. List of tuples 160 | # (source start file, name, description, authors, manual section). 161 | man_pages = [ 162 | (master_doc, 'nitro', 'nitro Documentation', 163 | [author], 1) 164 | ] 165 | 166 | 167 | # -- Options for Texinfo output ------------------------------------------- 168 | 169 | # Grouping the document tree into Texinfo files. List of tuples 170 | # (source start file, target name, title, author, 171 | # dir menu entry, description, category) 172 | texinfo_documents = [ 173 | (master_doc, 'nitro', 'nitro Documentation', 174 | author, 'nitro', 'One line description of project.', 175 | 'Miscellaneous'), 176 | ] 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | Nitro -- Virtual Machine Introspection Framework 2 | ================================================ 3 | 4 | Nitro is a flexible framework for virtual machine introspection. It provides a 5 | Python API for analyzing and altering system call use of KVM-based virtual 6 | machines. With Nitro, you can monitor what the machines are doing without 7 | executing a single line of code inside a virtual machine as all the work happens 8 | on the host. 9 | 10 | Nitro is open source and the code for the project can be found from the 11 | project's `GitHub page `_. Pull requests 12 | welcome! 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | :caption: User Guide 17 | 18 | introduction 19 | installation 20 | commandline 21 | architecture 22 | tutorial 23 | testing 24 | 25 | .. include:: modules.rst 26 | 27 | Indices and tables 28 | ================== 29 | 30 | * :ref:`genindex` 31 | * :ref:`modindex` 32 | * :ref:`search` 33 | -------------------------------------------------------------------------------- /doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Nitro is a complex piece of software and, unfortunatelly, requires quite a bit 5 | of setup work to get everything up and running. Hopefully the process of 6 | installing Nitro will eventually get simpler, but for now, this document tries 7 | to provide enough information for you to succesfully setup the framework. 8 | Additional instruction can be found on the projects `README 9 | `__. 10 | 11 | Unfortunatelly, it is not feasable to explain everything here about how to 12 | compile and install custom kernels or how to neatly install software from 13 | sources as these may depend on the target system in question. For this reason, 14 | this chapter requires a certain level of technical expertise. While not 15 | everything is explained, hopefully this chapter still contains enough pointers 16 | for someone wishing to install the system. 17 | 18 | Installing the Kernel 19 | --------------------- 20 | 21 | Nitro depends on a modified version of the Linux kernel with additional 22 | functionality added to the KVM. The sources for this custom release of Linux can 23 | be found on the projects `GitHub repository 24 | `__. 25 | 26 | Setting up QEMU 27 | --------------- 28 | 29 | Nitro uses for QEMU for executing virtual machines. Unfortunately, the upstream 30 | QEMU does not currently provide means for efficiently accessing virtual 31 | machines' memory from the host. For this reason, Nitro requires a custom version 32 | of the QEMU virtualization platform. Sources can be found on the projects 33 | `GitHub repository `__. 34 | 35 | Getting libvmi 36 | -------------- 37 | 38 | Libvmi library offers building blocks for virtual machine introspection by 39 | providing an unified API for accessing virtual machine state such as memory and 40 | registers. Nitro requires a custom version of this library. You can find the 41 | sources for this from the projects `GitHub repository 42 | `__. 43 | 44 | Libvmi requires a configuration file describing the properties of virtual 45 | machines to be present before it can be used. This requirement applies to Nitro 46 | as well because it uses libvmi internally. Nitro's repository contains an 47 | example ``libvmi.conf`` file configuring the library for use with Windows 48 | guests, however, the exact configuration may vary between operating system 49 | releases. `Libvmi documentation `__ 50 | describes how to obtaining the correct configuration values in more detail. 51 | 52 | Setting up libvirt 53 | ------------------ 54 | 55 | Nitro uses the `libvirt toolkit `__ for setting up and 56 | managing virtual machines and their associated resources. Be sure to 57 | additionally install the python bindings for the libvirt API. For Ubuntu, they 58 | are in the ``python3-libvirt`` package. 59 | 60 | Python Dependencies 61 | ------------------- 62 | 63 | After external dependencies have been installed, Nitro itself has to be 64 | installed, along with the python libraries it depends on. Like other Python 65 | projects, this can be handled using the included ``setup.py`` script. 66 | 67 | :: 68 | 69 | sudo ./setup.py install 70 | 71 | This will install the package for all users. However, for development, it might 72 | make more sense to install Nitro in "development" mode and only for a single 73 | user: 74 | 75 | :: 76 | 77 | ./setup.py develop --user 78 | -------------------------------------------------------------------------------- /doc/source/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Nitro is flexible framework for intercepting and altering system calls inside 5 | virtual machines. It provides users with a stream of events about what the 6 | machine is doing and allows the user to perform arbitrary inside the virtual 7 | machine. Even better, all this is done in a way that is completely transparent 8 | to the virtual machine in question. 9 | 10 | On a more technical level, Nitro is a virtual machine introspection system that, 11 | through the use of clever kernel-level functionality, allows the host system to 12 | pause virtual machine execution when system calls happen. While the virtual 13 | machine execution is paused, Nitro carefully dissects critical operating system 14 | data structures from its memory to understand the state of the system: what the 15 | system was doing when the system call took place. All this information is then 16 | passed to the user who can decide what to do with it. For example, the user 17 | might want to extract further information from the virtual machine or perform 18 | additional analysis that is specific to their use case or simply save the 19 | gathered data into a database. 20 | 21 | For the end user, Nitro provides a simple Python API for attaching to virtual 22 | machines, subscribing to different kinds of events from them and a way to react 23 | to those events. 24 | 25 | Why You Might Want to Consider Nitro 26 | ------------------------------------ 27 | 28 | You might want to use Nitro when you need to have a low-level view of what your 29 | systems are doing. While Nitro aims to provide a lot of higher-level information 30 | on the system's state, at the most basic level Nitro offers you with tools for 31 | inspecting (and altering) virtual machine's CPU state and contents of its 32 | memory. If you are interested in what exactly the system is doing and want a 33 | comprehensive view of the entire machine, Nitro might be for you. 34 | 35 | Alternatively, you might already have system for monitoring your virtual 36 | machines but you are concerned about how trustworthy the reported monitoring 37 | information is. Maybe you have reasons to believe that something is altering the 38 | data your monitoring probes collect. As Nitro resides entirely outside the 39 | bounds of the virtual machine it monitors, it is well protected against attacks 40 | that might want to alter the information it collects. 41 | 42 | Or maybe you need a monitoring system that is transparent to the virtual 43 | machines, a system where the VM does not even know it is being monitored. For 44 | example, it is common for malware to try to actively detect if they are being 45 | monitored and change their behavior if they believe they are being watched. 46 | Maybe you do not want to or cannot alter the virtual machines but still want to 47 | be able to have some idea about what is going inside of them. Nitro can help you 48 | here. 49 | 50 | It is also possible that you want to ensure that your software operates 51 | correctly whatever happens. Nitro aims to help you there by providing you with 52 | the tools for simulating abnormal system behavior. For example, how do you test 53 | your software for hardware failures or uncommon system call return values that 54 | almost never happen. Traditionally, bugs related to these things have been hard 55 | to replicate and fix. One of goals for the project is to make this feasible by 56 | providing tools for faking strange and uncommon events. 57 | 58 | State of the Project 59 | -------------------- 60 | 61 | Nitro is still under heavy development and things are bound to change and evolve 62 | as the project progresses. While the core aspects of Nitro are in place, many of 63 | the finer features are still shaping up. APIs are likely to change as we iterate 64 | the design to find the best possible form for Nitro. 65 | -------------------------------------------------------------------------------- /doc/source/modules.rst: -------------------------------------------------------------------------------- 1 | Project Structure 2 | ================= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | nitro 8 | -------------------------------------------------------------------------------- /doc/source/nitro.backends.linux.rst: -------------------------------------------------------------------------------- 1 | nitro\.backends\.linux package 2 | ============================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | nitro\.backends\.linux\.arguments module 8 | ---------------------------------------- 9 | 10 | .. automodule:: nitro.backends.linux.arguments 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | nitro\.backends\.linux\.backend module 16 | -------------------------------------- 17 | 18 | .. automodule:: nitro.backends.linux.backend 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | nitro\.backends\.linux\.process module 24 | -------------------------------------- 25 | 26 | .. automodule:: nitro.backends.linux.process 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: nitro.backends.linux 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /doc/source/nitro.backends.rst: -------------------------------------------------------------------------------- 1 | nitro\.backends package 2 | ======================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | nitro.backends.linux 10 | nitro.backends.windows 11 | 12 | Submodules 13 | ---------- 14 | 15 | nitro\.backends\.arguments module 16 | --------------------------------- 17 | 18 | .. automodule:: nitro.backends.arguments 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | nitro\.backends\.backend module 24 | ------------------------------- 25 | 26 | .. automodule:: nitro.backends.backend 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | nitro\.backends\.factory module 32 | ------------------------------- 33 | 34 | .. automodule:: nitro.backends.factory 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | nitro\.backends\.process module 40 | ------------------------------- 41 | 42 | .. automodule:: nitro.backends.process 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: nitro.backends 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /doc/source/nitro.backends.windows.rst: -------------------------------------------------------------------------------- 1 | nitro\.backends\.windows package 2 | ================================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | nitro\.backends\.windows\.arguments module 8 | ------------------------------------------ 9 | 10 | .. automodule:: nitro.backends.windows.arguments 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | nitro\.backends\.windows\.backend module 16 | ---------------------------------------- 17 | 18 | .. automodule:: nitro.backends.windows.backend 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | nitro\.backends\.windows\.process module 24 | ---------------------------------------- 25 | 26 | .. automodule:: nitro.backends.windows.process 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | nitro\.backends\.windows\.types module 32 | -------------------------------------- 33 | 34 | .. automodule:: nitro.backends.windows.types 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: nitro.backends.windows 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /doc/source/nitro.rst: -------------------------------------------------------------------------------- 1 | nitro package 2 | ============= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | nitro.backends 10 | 11 | Submodules 12 | ---------- 13 | 14 | nitro\.event module 15 | ------------------- 16 | 17 | .. automodule:: nitro.event 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | nitro\.kvm module 23 | ----------------- 24 | 25 | .. automodule:: nitro.kvm 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | nitro\.libvmi module 31 | -------------------- 32 | 33 | .. automodule:: nitro.libvmi 34 | :members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | nitro\.listener module 39 | ---------------------- 40 | 41 | .. automodule:: nitro.listener 42 | :members: 43 | :undoc-members: 44 | :show-inheritance: 45 | 46 | nitro\.nitro module 47 | ------------------- 48 | 49 | .. automodule:: nitro.nitro 50 | :members: 51 | :undoc-members: 52 | :show-inheritance: 53 | 54 | nitro\.syscall module 55 | --------------------- 56 | 57 | .. automodule:: nitro.syscall 58 | :members: 59 | :undoc-members: 60 | :show-inheritance: 61 | 62 | 63 | Module contents 64 | --------------- 65 | 66 | .. automodule:: nitro 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | -------------------------------------------------------------------------------- /doc/source/samples/testing-01.txt: -------------------------------------------------------------------------------- 1 | tester@kvm-vmi ~/projects/nitro/tests/unittests $ nose2 --verbose 2 | test_associate_process (test_linux.TestLinux) 3 | Test process association. ... ok 4 | test_backend_creation (test_linux.TestLinux) 5 | Check that LinuxBackend can be created. ... ok 6 | test_check_caches_flushed (test_linux.TestLinux) 7 | Check that libvmi caches are flushed. ... ok 8 | test_clean_name (test_linux.TestLinux) 9 | Test that system call handler names are properly cleaned. ... ok 10 | test_process_event (test_linux.TestLinux) 11 | Test that the event handler returns a syscall object with somewhat sensible content ... ok 12 | test_syscall_name (test_linux.TestLinux) 13 | Check that syscall names can be extracted from system call table. ... ok 14 | 15 | ---------------------------------------------------------------------- 16 | Ran 6 tests in 0.007s 17 | 18 | OK 19 | -------------------------------------------------------------------------------- /doc/source/samples/testing-02.txt: -------------------------------------------------------------------------------- 1 | tester@kvm-vmi ~/projects/nitro/tests/vm_templates $ ./packer build --var-file ubuntu_1604_x64.json ubuntu.json 2 | qemu output will be in this color. 3 | 4 | ==> qemu: Downloading or copying ISO 5 | qemu: Downloading or copying: http://releases.ubuntu.com/16.04/ubuntu-16.04.2-server-amd64.iso 6 | ==> qemu: Creating floppy disk... 7 | qemu: Copying files flatly from floppy_files 8 | qemu: Copying file: http/preseed.cfg 9 | qemu: Done copying files from floppy_files 10 | qemu: Collecting paths from floppy_dirs 11 | qemu: Resulting paths from floppy_dirs : [] 12 | qemu: Done copying paths from floppy_dirs 13 | ==> qemu: Creating hard drive... 14 | ==> qemu: Starting HTTP server on port 8288 15 | ==> qemu: Found port for communicator (SSH, WinRM, etc): 3679. 16 | ==> qemu: Looking for available port between 5900 and 6000 on 127.0.0.1 17 | ==> qemu: Starting VM, booting from CD-ROM 18 | qemu: The VM will be run headless, without a GUI. If you want to 19 | qemu: view the screen of the VM, connect via VNC without a password to 20 | qemu: vnc://127.0.0.1:5933 21 | ==> qemu: Overriding defaults Qemu arguments with QemuArgs... 22 | ==> qemu: Waiting 10s for boot... 23 | ==> qemu: Connecting to VM via VNC 24 | ==> qemu: Typing the boot command over VNC... 25 | ==> qemu: Waiting for SSH to become available... 26 | ==> qemu: Connected to SSH! 27 | ==> qemu: Uploading linux/ => /tmp 28 | ==> qemu: Provisioning with shell script: /tmp/packer-shell277821116 29 | qemu: [sudo] password for vagrant: Generating grub configuration file ... 30 | qemu: Warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is no longer supported. 31 | qemu: Found linux image: /boot/vmlinuz-4.4.0-62-generic 32 | qemu: Found initrd image: /boot/initrd.img-4.4.0-62-generic 33 | qemu: done 34 | qemu: Removed symlink /etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service. 35 | ==> qemu: Gracefully halting virtual machine... 36 | qemu: [sudo] password for vagrant: 37 | ==> qemu: Converting hard drive... 38 | Build 'qemu' finished. 39 | 40 | ==> Builds finished. The artifacts of successful builds are: 41 | --> qemu: VM files in directory: output-qemu 42 | -------------------------------------------------------------------------------- /doc/source/samples/tutorial-01.py: -------------------------------------------------------------------------------- 1 | from nitro.nitro import Nitro 2 | 3 | with Nitro("Windows-VM", introspection=True) as nitro: 4 | self.nitro.listener.set_traps(True) 5 | -------------------------------------------------------------------------------- /doc/source/samples/tutorial-02-output.txt: -------------------------------------------------------------------------------- 1 | {'cr3': '0x493bb000', 'direction': 'enter', 'rax': '0x33', 'type': 'syscall', 'vcpu': 0} 2 | {'cr3': '0x493bb000', 'direction': 'exit', 'rax': '0x0', 'type': 'syscall', 'vcpu': 0} 3 | {'cr3': '0x493bb000', 'direction': 'enter', 'rax': '0x16', 'type': 'syscall', 'vcpu': 0} 4 | {'cr3': '0x493bb000', 'direction': 'exit', 'rax': '0x0', 'type': 'syscall', 'vcpu': 0} 5 | {'cr3': '0x493bb000', 'direction': 'enter', 'rax': '0x16', 'type': 'syscall', 'vcpu': 0} 6 | {'cr3': '0x493bb000', 'direction': 'exit', 'rax': '0x0', 'type': 'syscall', 'vcpu': 0} 7 | {'cr3': '0x493bb000', 'direction': 'enter', 'rax': '0x18d', 'type': 'syscall', 'vcpu': 0} 8 | {'cr3': '0x493bb000', 'direction': 'exit', 'rax': '0x0', 'type': 'syscall', 'vcpu': 0} 9 | -------------------------------------------------------------------------------- /doc/source/samples/tutorial-02.py: -------------------------------------------------------------------------------- 1 | from nitro.nitro import Nitro 2 | 3 | with Nitro("Windows-VM", introspection=True) as nitro: 4 | self.nitro.listener.set_traps(True) 5 | for event in nitro.listen(): 6 | print(event.as_dict()) 7 | -------------------------------------------------------------------------------- /doc/source/samples/tutorial-03-output.txt: -------------------------------------------------------------------------------- 1 | {'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'} 2 | {'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'} 3 | {'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'} 4 | {'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'} 5 | {'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'} 6 | {'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'} 7 | {'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'} 8 | {'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'} 9 | {'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'} 10 | {'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'} 11 | -------------------------------------------------------------------------------- /doc/source/samples/tutorial-03.py: -------------------------------------------------------------------------------- 1 | from nitro.nitro import Nitro 2 | from nitro.libvmi import LibvmiError 3 | 4 | with Nitro("Windows-VM", introspection=True) as nitro: 5 | self.nitro.listener.set_traps(True) 6 | for event in nitro.listen(): 7 | try: 8 | syscall = nitro.backend.process_event(event) 9 | except LibvmiError: 10 | print("Failed to analyze event :/") 11 | else: 12 | print(syscall.as_dict()) 13 | -------------------------------------------------------------------------------- /doc/source/samples/tutorial-04.py: -------------------------------------------------------------------------------- 1 | from nitro.nitro import Nitro 2 | from nitro.libvmi import LibvmiError 3 | 4 | with Nitro("Windows-VM", introspection=True) as nitro: 5 | self.nitro.listener.set_traps(True) 6 | for event in nitro.listen(): 7 | try: 8 | syscall = nitro.backend.process_event(event) 9 | except LibvmiError: 10 | print("Failed to analyze event :/") 11 | else: 12 | if syscall.process.name == "notepad.exe": 13 | print(syscall.as_dict()) 14 | -------------------------------------------------------------------------------- /doc/source/testing.rst: -------------------------------------------------------------------------------- 1 | Developing Nitro: Testing 2 | ========================= 3 | 4 | In this chapter we take a look at how Nitro is tested and what is required for 5 | running the tests. Testing is essential for ensuring the quality of Nitro and 6 | for protecting us from accidental regression. 7 | 8 | Unit Tests 9 | ---------- 10 | 11 | As a project, Nitro is highly dependent on multiple external components like the 12 | customized version of the Linux kernel and the extended QEMU virtual machine 13 | platform. While all this is necessary, it makes testing the project a bit more 14 | challenging than the average Python module. 15 | 16 | Unit tests try to break down this complexity by concentrating on individual 17 | components and features of them. We replace the real interfaces and dependencies 18 | with `mocked `_ impostors to remove 19 | the need for complex outside dependencies and to make the tests more 20 | deterministic. This limits the kinds of tests we can create but is ideal for 21 | verifying the correctness of core logic. 22 | 23 | Because of the self-contained nature of unit tests, running the test suite is 24 | simple. The unit test suite is located in ``tests/unittests`` directory and the 25 | tests can be run by simply invoking the ``nose2`` test runner there. 26 | 27 | .. literalinclude:: samples/testing-01.txt 28 | 29 | Because of the lax requirements for the testing environment, Nitro's unit tests 30 | are ideal for running in an automated fashion as a part of a continuous 31 | integration pipeline. 32 | 33 | Integration Tests 34 | ----------------- 35 | 36 | While unit tests are useful, it is often difficult to test how the system 37 | operates as a whole and how it interacts with a real guest operating systems. 38 | For this reason, Nitro includes a suite of integration tests that try out the 39 | different features in a test environment with virtual machines. The environment 40 | enables us to automatically run test binaries inside real virtual machines and 41 | checks that Nitro can correctly analyze their actions. 42 | 43 | Creating a Testing VM 44 | ~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | Before actual testing can take place, a virtual machine needs to be created. For 47 | tests to be deterministic, the VM must be constructed in a way that allows us to 48 | know exactly what gets included and what the result will be. This is to make 49 | sure we can reliably replicate problems that might arise during testing. 50 | Additionally, the virtual machine images we use for testing are specifically 51 | optimized for testing purposes with unnecessary services disabled. 52 | 53 | Nitro includes `Packer `_ virtual machine templates for 54 | building the test environment. The ``tests/vm_templates`` directory includes the 55 | ``packer`` binary itself and templates for Linux and Windows testing 56 | environments. With the templates in place, we can simply ask packer to create 57 | the VM for us: 58 | 59 | .. literalinclude:: samples/testing-02.txt 60 | 61 | After the process finishes, we have to import the created VM into ``libvirt``. 62 | This can be done automatically with the included ``import_libvirt.py`` script. 63 | Depending on the way your ``libvirt`` installation is configured, the script 64 | might require superuser privileges. To import the newly constructed VM run: 65 | 66 | :: 67 | 68 | # ./import_libvirt.py output-qemu/ubuntu1604 69 | 70 | The import script will create a new storage pool with the name ``nitro`` at the 71 | ``tests/images`` directory and move the generated VM image there from the 72 | ``output-qemu`` directory where Packer left it. Subsequently, the script will 73 | define a new ``libvirt`` domain for the machine and associate the image with it. 74 | The domain is created with system ``libvirt`` instance. Finally the script will 75 | remove the unnecessary ``output-qemu`` directory. 76 | 77 | Running the Tests 78 | ~~~~~~~~~~~~~~~~~ 79 | 80 | Once the virtual machine is in place, we can proceed to actual testing. Nitro's 81 | integration tests work by first restoring the testing virtual machine to a clean 82 | state from a snapshot. After this, the test runner packages the selected test 83 | binary into an ISO image that can be attached to the virtual machine. To run the 84 | tests, the test runner boots up the VM, waits for it to settle, and attaches the 85 | disc image to it. Each testing virtual machine contains special configuration 86 | for automatically executing the attached test images. Finally, test runner 87 | attaches Nitro to the virtual machine and monitors the execution. At the end, 88 | each test case can check the produced execution traces for features interesting 89 | to them. 90 | 91 | While all this might seem complicated, all the hard work is done automatically 92 | by the testing framework. To run the test suite, simply invoke ``nose2`` within 93 | the ``tests`` directory. Running all the tests can be time consuming, and 94 | therefore, it is often desirable to only run some of the tests. This can be 95 | achieved by specifying the test case manually: 96 | 97 | :: 98 | 99 | $ nose2 --verbose test_linux.TestLinux.test_open 100 | 101 | -------------------------------------------------------------------------------- /doc/source/tutorial.rst: -------------------------------------------------------------------------------- 1 | Tutorial: Finding Out What Notepad is Doing 2 | =========================================== 3 | 4 | In this chapter we will use Nitro to monitor Windows Notepad. While this might 5 | not be super interesting in itself, it demonstrates all the essential techniques 6 | that will enable you to use Nitro for tackling real-world challenges. 7 | 8 | Getting a Connection 9 | -------------------- 10 | 11 | The first thing to do is to initialize Nitro and attach it to a virtual machine. 12 | For the purposes of this tutorial, we expect the VM to be already running but we 13 | could of course use libvirt APIs for automating setting up the environment. 14 | Additionally, Nitro obviously has to be in Python's module search path for any 15 | of this to work. 16 | 17 | .. literalinclude:: samples/tutorial-01.py 18 | 19 | Here, we have imported the :class:`~.Nitro` class and used it to connect to a 20 | libvirt domain named "Windows-VM". The optional ``introspection`` argument 21 | indicates that we wish Nitro to create us a suitable analysis back end. 22 | 23 | Inside the ``with`` statement, we enable traps. After the traps have been set, 24 | we are ready to start listening for low-level :class:`~.NitroEvent` events from 25 | the virtual machine. 26 | 27 | So far, the code doesn't do anything too interesting as after the traps have 28 | been set, Nitro exits gracefully as there is nothing more to do. 29 | 30 | Letting the Events Flow 31 | ----------------------- 32 | 33 | Next, lets extend the code to get some events from the target machine. We can 34 | listen for events using :class:`~.Nitro` object's :meth:`~.Nitro.listen` method. 35 | Internally, Nitro will use the :class:`~.Listener` it initialized for us. 36 | 37 | .. literalinclude:: samples/tutorial-02.py 38 | 39 | :meth:`~.Nitro.listen` will give us a stream of :class:`~.NitroEvent` events. 40 | Each event describes a state of the machine when a system call entry or exit 41 | happened. Here, we simply print a representation of each event received. This 42 | should quickly print a lot of data about things happening inside the VM: 43 | 44 | .. literalinclude:: samples/tutorial-02-output.txt 45 | :language: python 46 | 47 | Each event line shows the most important properties of the event and, if we 48 | wanted, we could access even more information through the ``event`` object. 49 | 50 | Understanding the Data 51 | ---------------------- 52 | 53 | While having all this data is certainly interesting, it does little to help us 54 | in our mission to understand what Windows Notepad is doing while we use it to 55 | edit text. There is simply too much data with too little useful information for 56 | practical uses. 57 | 58 | We can remedy the situation by calling in help the analysis back end that Nitro 59 | helpfully created for us. Using the Windows analysis back end, we can transform 60 | all the low-level events into something more useful. 61 | 62 | .. literalinclude:: samples/tutorial-03.py 63 | 64 | We now invoke back end's :meth:`~.WindowsBackend.process_event` method for each 65 | incoming event to see what they are about. The method returns us a 66 | :class:`~.Syscall` event with all the associated information. It is worth noting 67 | that digging around virtual machine's memory for information about events is a 68 | potentially challenging task that may result in an error. It is a good practice 69 | to catch those. Here, if everything went well, we will print the analysis 70 | result. This should result in something little more understandable. 71 | 72 | .. literalinclude:: samples/tutorial-03-output.txt 73 | :language: python 74 | 75 | We can see that each event has been coupled with information about the process 76 | that caused it. We are already getting pretty close to our goal! 77 | 78 | Looking for a Notepad 79 | --------------------- 80 | 81 | Now we have everything we need to spot the bits of data that interest us. In 82 | this case, we are interested in what Windows Notepad is doing when we open it. 83 | 84 | .. literalinclude:: samples/tutorial-04.py 85 | 86 | To find out Notepad related events we simply inspect the process property 87 | associated with the event. 88 | -------------------------------------------------------------------------------- /libvmi.conf: -------------------------------------------------------------------------------- 1 | nitro_win7x64 { 2 | ostype = "Windows"; 3 | win_tasks = 0x188; 4 | win_pdbase = 0x28; 5 | win_pid = 0x180; 6 | win_pname = 0x2e0; 7 | } 8 | 9 | nitro_win8x64 { 10 | ostype = "Windows"; 11 | win_tasks = 0x2e8; 12 | win_pdbase = 0x28; 13 | win_pid = 0x2e0; 14 | win_pname = 0x438; 15 | } 16 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Nitro. 4 | 5 | Usage: 6 | nitro [options] 7 | 8 | Options: 9 | -h --help Show this screen 10 | --nobackend Don't analyze events 11 | -o FILE --out=FILE Output file (stdout if not specified) 12 | 13 | """ 14 | 15 | import logging 16 | import signal 17 | import json 18 | from pprint import pprint 19 | 20 | import libvirt 21 | from libvmi import LibvmiError 22 | from docopt import docopt 23 | 24 | from nitro.nitro import Nitro 25 | 26 | 27 | 28 | def init_logger(): 29 | logger = logging.getLogger() 30 | logger.addHandler(logging.StreamHandler()) 31 | logger.setLevel(logging.DEBUG) 32 | 33 | 34 | class NitroRunner: 35 | 36 | def __init__(self, vm_name, analyze_enabled, output=None): 37 | self.vm_name = vm_name 38 | self.analyze_enabled = analyze_enabled 39 | self.output = output 40 | # get domain from libvirt 41 | con = libvirt.open('qemu:///system') 42 | self.domain = con.lookupByName(vm_name) 43 | self.events = [] 44 | self.nitro = None 45 | # define new SIGINT handler, to stop nitro 46 | signal.signal(signal.SIGINT, self.sigint_handler) 47 | 48 | def run(self): 49 | self.nitro = Nitro(self.domain, self.analyze_enabled) 50 | self.nitro.listener.set_traps(True) 51 | for event in self.nitro.listen(): 52 | event_info = event.as_dict() 53 | if self.analyze_enabled: 54 | try: 55 | syscall = self.nitro.backend.process_event(event) 56 | except LibvmiError: 57 | logging.error("Backend event processing failure") 58 | else: 59 | event_info = syscall.as_dict() 60 | if self.output is None: 61 | pprint(event_info, width=1) 62 | else: 63 | self.events.append(event_info) 64 | 65 | 66 | if self.analyze_enabled: 67 | # we can safely stop the backend 68 | self.nitro.backend.stop() 69 | 70 | if self.output is not None: 71 | logging.info('Writing events') 72 | with open(self.output, 'w') as f: 73 | json.dump(self.events, f, indent=4) 74 | 75 | def sigint_handler(self, *args, **kwargs): 76 | logging.info('CTRL+C received, stopping Nitro') 77 | self.nitro.stop() 78 | 79 | 80 | def main(): 81 | init_logger() 82 | args = docopt(__doc__) 83 | vm_name = args[''] 84 | analyze_enabled = False if args['--nobackend'] else True 85 | output = args['--out'] 86 | runner = NitroRunner(vm_name, analyze_enabled, output) 87 | runner.run() 88 | 89 | 90 | if __name__ == '__main__': 91 | main() 92 | -------------------------------------------------------------------------------- /nitro/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/nitro/__init__.py -------------------------------------------------------------------------------- /nitro/backends/__init__.py: -------------------------------------------------------------------------------- 1 | from nitro.backends.factory import get_backend 2 | -------------------------------------------------------------------------------- /nitro/backends/arguments.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | from enum import Enum 4 | from nitro.event import SyscallType 5 | 6 | class SyscallArgumentType(Enum): 7 | register = 0 8 | memory = 1 9 | 10 | 11 | class ArgumentMap: 12 | """ 13 | ``ArgumentMap`` is a base class for providing access to system call arguments. 14 | """ 15 | 16 | ARG_SIZE = { 17 | SyscallType.syscall: 'P', # x64 -> 8 bytes 18 | SyscallType.sysenter: 'I' # x32 -> 4 bytes 19 | } 20 | 21 | __slots__ = ( 22 | "event", 23 | "process", 24 | "modified", 25 | "arg_size_format" 26 | ) 27 | 28 | def __init__(self, event, process): 29 | """ 30 | :param NitroEvent event: event that is used to access arguments 31 | :param Process process: process which address space is used for argument lookups 32 | """ 33 | #: Underlying event 34 | self.event = event 35 | #: Process associated with the ``ArgumentMap`` 36 | self.process = process 37 | self.modified = {} 38 | self.arg_size_format = self.ARG_SIZE[self.event.type] 39 | 40 | def get_argument_value(self, arg_type, opaque): 41 | if arg_type == SyscallArgumentType.register: 42 | value = self.event.get_register(opaque) 43 | else: 44 | # memory 45 | size = struct.calcsize(self.arg_size_format) 46 | addr = self.event.regs.rsp + (opaque * size) 47 | value, *rest = struct.unpack(self.arg_size_format, self.process.read_memory(addr, size)) 48 | return value 49 | 50 | def set_argument_value(self, arg_type, opaque, value): 51 | if arg_type == SyscallArgumentType.register: 52 | self.event.update_register(opaque, value) 53 | else: 54 | # memory 55 | size = struct.calcsize(self.arg_size_format) 56 | addr = self.event.regs.rsp + (opaque * size) 57 | buffer = struct.pack(self.arg_size_format, value) 58 | self.process.write_memory(addr, buffer) 59 | -------------------------------------------------------------------------------- /nitro/backends/backend.py: -------------------------------------------------------------------------------- 1 | """ 2 | Backends process stream of ``NitroEvent`` objects and produce higher-level 3 | ``Systemcall`` events with operating-system-specific information such as ``Process`` 4 | that generated the event and arguments. 5 | """ 6 | 7 | import logging 8 | import json 9 | from collections import defaultdict 10 | 11 | from nitro.event import SyscallDirection 12 | from libvmi import LibvmiError 13 | 14 | class Backend: 15 | """ 16 | Base class for Backends. ``Backend`` provides functionality for dispatching 17 | hooks and keeping statistics about processed events. 18 | """ 19 | 20 | __slots__ = ( 21 | "domain", 22 | "libvmi", 23 | "hooks", 24 | "stats", 25 | "listener", 26 | "syscall_filtering" 27 | ) 28 | 29 | def __init__(self, domain, libvmi, listener, syscall_filtering=True): 30 | """Create a new ``Backend``""" 31 | 32 | #: libvirt domain associated with the backend 33 | self.domain = domain 34 | #: handle to libvmi 35 | self.libvmi = libvmi 36 | #: ``Listener`` associated with the ``Backend`` 37 | self.listener = listener 38 | #: Is system call filtering enabled for the backend 39 | self.syscall_filtering = syscall_filtering 40 | #: Event hooks 41 | self.hooks = { 42 | SyscallDirection.enter: {}, 43 | SyscallDirection.exit: {} 44 | } 45 | #: Statistics about the backend 46 | self.stats = defaultdict(int) 47 | 48 | def dispatch_hooks(self, syscall): 49 | # TODO: don't dispatch if the process is None 50 | if syscall.process is None: 51 | return 52 | 53 | try: 54 | hook = self.hooks[syscall.event.direction][syscall.name] 55 | except KeyError: 56 | pass 57 | else: 58 | try: 59 | logging.debug('Processing hook %s - %s', 60 | syscall.event.direction.name, hook.__name__) 61 | hook(syscall, self) 62 | # FIXME: There should be a way for OS specific backends to report these 63 | # except InconsistentMemoryError: # 64 | # self.stats['memory_access_error'] += 1 65 | # logging.exception('Memory access error') 66 | except LibvmiError: 67 | self.stats['libvmi_failure'] += 1 68 | logging.exception('VMI_FAILURE') 69 | # misc failures 70 | except ValueError: 71 | self.stats['misc_error'] += 1 72 | logging.exception('Misc error') 73 | except Exception: 74 | logging.exception('Unknown error while processing hook') 75 | else: 76 | self.stats['hooks_completed'] += 1 77 | finally: 78 | self.stats['hooks_processed'] += 1 79 | 80 | def define_hook(self, name, callback, direction=SyscallDirection.enter): 81 | """ 82 | Register a new system call hook with the ``Backend``. 83 | 84 | :param str name: Name of the system call to hook. 85 | :param callable callback: Callable to call when the hook is fired. 86 | :param SyscallDirection direction: Should the hook fire when system call is entered or exited. 87 | """ 88 | logging.info('Defining %s hook on %s', direction.name, name) 89 | self.hooks[direction][name] = callback 90 | 91 | def undefine_hook(self, name, direction=SyscallDirection.enter): 92 | """ 93 | Unregister a hook. 94 | """ 95 | logging.info('Removing hook on %s', name) 96 | self.hooks[direction].pop(name) 97 | 98 | def __enter__(self): 99 | return self 100 | 101 | def __exit__(self, *args): 102 | self.stop() 103 | 104 | def stop(self): 105 | """Stop the backend""" 106 | logging.info(json.dumps(self.stats, indent=4)) 107 | self.libvmi.destroy() 108 | -------------------------------------------------------------------------------- /nitro/backends/factory.py: -------------------------------------------------------------------------------- 1 | from libvmi import VMIOS, Libvmi 2 | from nitro.backends.linux import LinuxBackend 3 | from nitro.backends.windows import WindowsBackend 4 | 5 | BACKENDS = { 6 | VMIOS.LINUX: LinuxBackend, 7 | VMIOS.WINDOWS: WindowsBackend 8 | } 9 | 10 | class BackendNotFoundError(Exception): 11 | pass 12 | 13 | def get_backend(domain, listener, syscall_filtering): 14 | """ 15 | Return a suitable backend based on guest operating system. 16 | 17 | :param domain: libvirt domain 18 | :returns: new backend instance 19 | :rtype: Backend 20 | :raises: BackendNotFoundError 21 | """ 22 | libvmi = Libvmi(domain.name()) 23 | os_type = libvmi.get_ostype() 24 | try: 25 | return BACKENDS[os_type](domain, libvmi, listener, syscall_filtering) 26 | except KeyError: 27 | raise BackendNotFoundError('Unable to find an appropritate backend for' 28 | 'this OS: {}'.format(os_type)) 29 | -------------------------------------------------------------------------------- /nitro/backends/linux/__init__.py: -------------------------------------------------------------------------------- 1 | from .backend import LinuxBackend 2 | from .arguments import LinuxArgumentMap 3 | -------------------------------------------------------------------------------- /nitro/backends/linux/arguments.py: -------------------------------------------------------------------------------- 1 | from nitro.event import SyscallType 2 | from nitro.backends.arguments import ArgumentMap, SyscallArgumentType 3 | 4 | class LinuxArgumentMap(ArgumentMap): 5 | """ 6 | ``LinuxArgumentMap`` provides read and write access to system call arguments. 7 | """ 8 | 9 | CONVENTION = { 10 | SyscallType.syscall: [ 11 | (SyscallArgumentType.register, 'rdi'), 12 | (SyscallArgumentType.register, 'rsi'), 13 | (SyscallArgumentType.register, 'rdx'), 14 | (SyscallArgumentType.register, 'r10'), 15 | (SyscallArgumentType.register, 'r8'), 16 | (SyscallArgumentType.register, 'r9'), 17 | ], 18 | SyscallType.sysenter: [ 19 | (SyscallArgumentType.register, 'rbx'), 20 | (SyscallArgumentType.register, 'rcx'), 21 | (SyscallArgumentType.register, 'rdx'), 22 | (SyscallArgumentType.register, 'rsi'), 23 | (SyscallArgumentType.register, 'rdi'), 24 | (SyscallArgumentType.register, 'rbp'), 25 | ], 26 | } 27 | 28 | def __getitem__(self, index): 29 | try: 30 | arg_type, opaque = self.CONVENTION[self.event.type][index] 31 | except KeyError as error: 32 | raise RuntimeError('Unknown convention') from error 33 | except IndexError: 34 | raise RuntimeError('Invalid argument index: Linux syscalls are ' 35 | 'limited to 6 parameters') 36 | 37 | return self.get_argument_value(arg_type, opaque) 38 | 39 | 40 | def __setitem__(self, index, value): 41 | try: 42 | arg_type, opaque = self.CONVENTION[self.event.type][index] 43 | except KeyError as error: 44 | raise RuntimeError('Unknown convention') from error 45 | except IndexError: 46 | raise RuntimeError('Invalid argument index: Linux syscalls are ' 47 | 'limited to 6 parameters') 48 | 49 | self.set_argument_value(arg_type, opaque, value) 50 | self.modified[index] = value 51 | -------------------------------------------------------------------------------- /nitro/backends/linux/process.py: -------------------------------------------------------------------------------- 1 | from nitro.backends.process import Process 2 | 3 | class LinuxProcess(Process): 4 | """Class representing a Linux process""" 5 | 6 | __slots__ = ( 7 | "task_struct", 8 | "name", 9 | "pid" 10 | ) 11 | 12 | def __init__(self, libvmi, cr3, task_struct): 13 | super().__init__(libvmi, cr3) 14 | pid_offset = self.libvmi.get_offset("linux_pid") 15 | name_offset = self.libvmi.get_offset("linux_name") 16 | 17 | #: Kernel task_struct for the process 18 | self.task_struct = task_struct 19 | #: Proces PID 20 | self.pid = self.libvmi.read_32(self.task_struct + pid_offset, 0) 21 | #: Name of the processs 22 | self.name = self.libvmi.read_str_va(self.task_struct + name_offset, 0) 23 | -------------------------------------------------------------------------------- /nitro/backends/process.py: -------------------------------------------------------------------------------- 1 | 2 | class Process: 3 | """ 4 | Base class for processes. ``Process`` provides accesss to information about a 5 | particular process as well as a method for reading and writing its memory. 6 | """ 7 | 8 | __slots__ = ( 9 | "libvmi", 10 | "cr3", 11 | ) 12 | 13 | def __init__(self, libvmi, cr3): 14 | self.libvmi = libvmi 15 | self.cr3 = cr3 16 | 17 | @property 18 | def pid(self): 19 | raise NotImplementedError("pid must be overridden by a subclass") 20 | 21 | @property 22 | def name(self): 23 | raise NotImplementedError("name must be overridden by a subclass") 24 | 25 | def as_dict(self): 26 | """ 27 | Returns a dictionary representing the process. 28 | 29 | :rtype: dict 30 | """ 31 | return { 32 | 'name': self.name, 33 | 'pid': self.pid 34 | } 35 | 36 | def read_memory(self, addr, count): 37 | """ 38 | Read ``count`` bytes at address ``addr`` from the process' address space. 39 | 40 | :raises: LibvmiError 41 | """ 42 | buffer, bytes_read = self.libvmi.read_va(addr, self.pid, count) 43 | if bytes_read != count: 44 | raise RuntimeError('Fail to read memory') 45 | return buffer 46 | 47 | def write_memory(self, addr, buffer): 48 | """ 49 | Write ``buffer`` at address ``addr`` in the process' address space. 50 | 51 | :raises: LibvmiError 52 | """ 53 | self.libvmi.write_va(addr, self.pid, buffer) 54 | -------------------------------------------------------------------------------- /nitro/backends/windows/__init__.py: -------------------------------------------------------------------------------- 1 | from .backend import WindowsBackend 2 | from .arguments import WindowsArgumentMap 3 | -------------------------------------------------------------------------------- /nitro/backends/windows/arguments.py: -------------------------------------------------------------------------------- 1 | from nitro.event import SyscallType 2 | from nitro.backends.arguments import ArgumentMap, SyscallArgumentType 3 | 4 | 5 | class WindowsArgumentMap(ArgumentMap): 6 | """ 7 | ``WindowsArgumentMap`` provides read and write access to system call arguments. 8 | """ 9 | 10 | CONVENTION = { 11 | SyscallType.syscall: [ 12 | (SyscallArgumentType.register, 'rcx'), 13 | (SyscallArgumentType.register, 'rdx'), 14 | (SyscallArgumentType.register, 'r8'), 15 | (SyscallArgumentType.register, 'r9'), 16 | (SyscallArgumentType.memory, 5), 17 | ], 18 | } 19 | 20 | def __getitem__(self, index): 21 | try: 22 | arg_type, opaque = self.CONVENTION[self.event.type][index] 23 | except KeyError as error: 24 | raise RuntimeError('Unknown convention') from error 25 | except IndexError: 26 | arg_type, opaque = self.CONVENTION[self.event.type][-1] 27 | opaque += index - len(self.CONVENTION[self.event.type]) + 1 28 | 29 | return self.get_argument_value(arg_type, opaque) 30 | 31 | def __setitem__(self, index, value): 32 | try: 33 | arg_type, opaque = self.CONVENTION[self.event.type][index] 34 | except KeyError as error: 35 | raise RuntimeError('Unknown covention') from error 36 | except IndexError: 37 | arg_type, opaque = self.CONVENTION[self.event.type][-1] 38 | opaque += index - len(self.CONVENTION[self.event.type]) + 1 39 | 40 | self.set_argument_value(arg_type, opaque, value) 41 | self.modified[index] = value 42 | -------------------------------------------------------------------------------- /nitro/backends/windows/get_symbols.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | """ 4 | 5 | Usage: 6 | symbols.py 7 | 8 | Options: 9 | -h --help Show this screen. 10 | 11 | """ 12 | 13 | import os 14 | import logging 15 | import StringIO 16 | import json 17 | from collections import defaultdict 18 | 19 | # logging.basicConfig(level=logging.DEBUG) 20 | 21 | 22 | from docopt import docopt 23 | from rekall import session 24 | from rekall import plugins 25 | 26 | def main(args): 27 | ram_dump = args[''] 28 | home = os.getenv('HOME') 29 | # we need to make sure the directory exists otherwise rekall will complain 30 | # when we specify it in the profile_path 31 | local_cache_path = os.path.join(home, '.rekall_cache') 32 | try: 33 | os.makedirs(local_cache_path) 34 | except OSError: # already exists 35 | pass 36 | 37 | s = session.Session( 38 | filename=ram_dump, 39 | autodetect=["rsds"], 40 | logger=logging.getLogger(), 41 | autodetect_build_local='none', 42 | format='data', 43 | profile_path=[ 44 | local_cache_path, 45 | "http://profiles.rekall-forensic.com" 46 | ]) 47 | 48 | symbols = {} 49 | output = StringIO.StringIO() 50 | s.RunPlugin("ssdt", output=output) 51 | symbols['syscall_table'] = json.loads(output.getvalue()) 52 | symbols['offsets'] = get_offsets(s) 53 | 54 | print(json.dumps(symbols)) 55 | 56 | 57 | def get_offsets(session): 58 | offsets = defaultdict(dict) 59 | 60 | offsets['KPROCESS']['DirectoryTableBase'] = session.profile.get_obj_offset('_KPROCESS', 61 | 'DirectoryTableBase') 62 | 63 | offsets['EPROCESS']['ActiveProcessLinks'] = session.profile.get_obj_offset('_EPROCESS', 64 | 'ActiveProcessLinks') 65 | 66 | offsets['EPROCESS']['ImageFileName'] = session.profile.get_obj_offset('_EPROCESS', 67 | 'ImageFileName') 68 | 69 | offsets['EPROCESS']['UniqueProcessId'] = session.profile.get_obj_offset('_EPROCESS', 70 | 'UniqueProcessId') 71 | 72 | offsets['EPROCESS']['InheritedFromUniqueProcessId'] = \ 73 | session.profile.get_obj_offset('_EPROCESS', 'InheritedFromUniqueProcessId') 74 | 75 | offsets['EPROCESS']['Wow64Process'] = \ 76 | session.profile.get_obj_offset('_EPROCESS', 'Wow64Process') 77 | 78 | offsets['EPROCESS']['CreateTime'] = \ 79 | session.profile.get_obj_offset('_EPROCESS', 'CreateTime') 80 | 81 | offsets['EPROCESS']['SeAuditProcessCreationInfo'] = \ 82 | session.profile.get_obj_offset('_EPROCESS', 'SeAuditProcessCreationInfo') 83 | 84 | offsets['SE_AUDIT_PROCESS_CREATION_INFO']['ImageFileName'] = \ 85 | session.profile.get_obj_offset('_SE_AUDIT_PROCESS_CREATION_INFO', 'ImageFileName') 86 | 87 | offsets['OBJECT_NAME_INFORMATION']['Name'] = \ 88 | session.profile.get_obj_offset('_OBJECT_NAME_INFORMATION', 'Name') 89 | 90 | offsets['EPROCESS']['Peb'] = session.profile.get_obj_offset('_EPROCESS', 'Peb') 91 | 92 | offsets['PEB']['ProcessParameters'] = \ 93 | session.profile.get_obj_offset('_PEB', 'ProcessParameters') 94 | 95 | offsets['RTL_USER_PROCESS_PARAMETERS']['CommandLine'] = \ 96 | session.profile.get_obj_offset('_RTL_USER_PROCESS_PARAMETERS', 'CommandLine') 97 | 98 | return offsets 99 | 100 | if __name__ == '__main__': 101 | main(docopt(__doc__)) 102 | -------------------------------------------------------------------------------- /nitro/backends/windows/process.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from nitro.backends.process import Process 3 | from nitro.backends.windows.types import PEB, UnicodeString, LargeInteger 4 | 5 | WINDOWS_TICK = 10000000 6 | SEC_TO_UNIX_EPOCH = 11644473600 7 | 8 | class WindowsProcess(Process): 9 | 10 | __slots__ = ( 11 | "eproc", 12 | "name", 13 | "pid", 14 | "iswow64", 15 | "create_time", 16 | "path", 17 | "command_line", 18 | "parent_pid", 19 | "symbols" 20 | ) 21 | 22 | def __init__(self, libvmi, cr3, eproc, symbols): 23 | super().__init__(libvmi, cr3) 24 | self.eproc = eproc 25 | self.symbols = symbols 26 | 27 | # get name 28 | image_file_name_off = self.symbols['offsets']['EPROCESS']['ImageFileName'] 29 | image_file_name_addr = self.eproc + image_file_name_off 30 | self.name = self.libvmi.read_str_va(image_file_name_addr, 0) 31 | # get pid 32 | unique_processid_off = self.symbols['offsets']['EPROCESS']['UniqueProcessId'] 33 | unique_processid_addr = self.eproc + unique_processid_off 34 | self.pid = self.libvmi.read_addr_va(unique_processid_addr, 0) 35 | # get command line 36 | peb_off = self.symbols['offsets']['EPROCESS']['Peb'] 37 | peb_addr = self.libvmi.read_addr_va(self.eproc + peb_off, 0) 38 | peb = PEB(peb_addr, self) 39 | self.command_line = peb.ProcessParameters.CommandLine.Buffer 40 | # get full path 41 | seauditprocess_off = self.symbols['offsets']['EPROCESS']['SeAuditProcessCreationInfo'] 42 | seauditprocesscreationinfo_offs = self.eproc + seauditprocess_off 43 | 44 | sapci = self.libvmi.read_addr_va(seauditprocesscreationinfo_offs, 0) 45 | fullpath = UnicodeString(sapci, self) 46 | self.path = fullpath.Buffer 47 | # get create time 48 | create_time_addr = self.eproc + self.symbols['offsets']['EPROCESS']['CreateTime'] 49 | ct = LargeInteger(create_time_addr, self) 50 | # Converts Windows 64-bit time to UNIX time, the below code has been taken from Volatility 51 | ct = ct.QuadPart / WINDOWS_TICK 52 | ct = ct - SEC_TO_UNIX_EPOCH 53 | self.create_time = datetime.datetime.fromtimestamp(ct)\ 54 | .strftime("%Y-%m-%d %H:%M:%S") 55 | # get parent PID 56 | parent_pid_off = self.symbols['offsets']['EPROCESS']['InheritedFromUniqueProcessId'] 57 | parent_pid_addr = self.eproc + parent_pid_off 58 | 59 | ppid = self.libvmi.read_addr_va(parent_pid_addr, 0) 60 | self.parent_pid = ppid 61 | # get iswow64, if value is non-zero then iswow64 is true 62 | wow64_off = self.symbols['offsets']['EPROCESS']['Wow64Process'] 63 | self.iswow64 = self.libvmi.read_addr_va(self.eproc + wow64_off, 0) != 0 64 | 65 | def as_dict(self): 66 | parent = super().as_dict() 67 | parent["parent_pid"] = self.parent_pid 68 | parent["command_line"] = self.command_line 69 | parent["iswow64"] = self.iswow64 70 | parent["path"] = self.path 71 | parent["create_time"] = self.create_time 72 | return parent 73 | -------------------------------------------------------------------------------- /nitro/backends/windows/types.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | 4 | class InconsistentMemoryError(Exception): 5 | pass 6 | 7 | 8 | class WinStruct(object): 9 | _fields_ = [] 10 | 11 | def __init__(self, addr, process): 12 | # logging.debug('Building %s from %s', self.__class__.__name__, hex(addr)) 13 | for f_offset, f_name, f_format in self._fields_: 14 | if isinstance(f_format, str): 15 | # logging.debug('Field {}, {}, at {} + {}' 16 | # .format(f_name, f_format, hex(addr), hex(f_offset))) 17 | f_size = struct.calcsize(f_format) 18 | content = process.read_memory(addr + f_offset, f_size) 19 | f_value, *rest = struct.unpack(f_format, content) 20 | # logging.debug('Value: {}'.format(hex(f_value))) 21 | else: 22 | # our struct 23 | # f_format is a class 24 | f_value = f_format(addr + f_offset, process) 25 | setattr(self, f_name, f_value) 26 | 27 | 28 | class ObjectAttributes(WinStruct): 29 | __slots__ = ( 30 | 'Length', 31 | 'RootDirectory', 32 | 'ObjectName', 33 | ) 34 | 35 | _fields_ = [ 36 | (0, 'Length', 'I'), 37 | (0x8, 'RootDirectory', 'P'), 38 | (0x10, 'ObjectName', 'P'), 39 | ] 40 | 41 | def __init__(self, addr, process): 42 | super().__init__(addr, process) 43 | if self.Length != 0x30: 44 | # memory inconsistent 45 | raise InconsistentMemoryError() 46 | self.ObjectName = UnicodeString(self.ObjectName, process) 47 | 48 | 49 | class ClientID(WinStruct): 50 | __slots__ = ( 51 | 'UniqueProcess', 52 | 'UniqueThread' 53 | ) 54 | 55 | _fields_ = [ 56 | (0, 'UniqueProcess', 'P'), 57 | (8, 'UniqueThread', 'P'), 58 | ] 59 | 60 | def __init__(self, addr, process): 61 | super().__init__(addr, process) 62 | 63 | 64 | class LargeInteger(WinStruct): 65 | __slots__ = ( 66 | 'LowPart', 67 | 'HighPart' 68 | 'QuadPart' 69 | ) 70 | 71 | _fields_ = [ 72 | (0, 'LowPart', 'I'), 73 | (4, 'HighPart', 'I'), 74 | (0, 'QuadPart', 'q') 75 | ] 76 | 77 | def __init__(self, addr, process): 78 | super().__init__(addr, process) 79 | 80 | 81 | class UnicodeString(WinStruct): 82 | __slots__ = ( 83 | 'Length', 84 | 'MaximumLength', 85 | 'Buffer', 86 | ) 87 | 88 | _fields_ = [ 89 | (0, 'Length', 'H'), 90 | (0x2, 'MaximumLength', 'H'), 91 | (0x8, 'Buffer', 'P'), 92 | ] 93 | 94 | def __init__(self, addr, process): 95 | super().__init__(addr, process) 96 | buffer = process.read_memory(self.Buffer, self.Length) 97 | try: 98 | string = buffer.decode('utf-16-le') 99 | except UnicodeDecodeError: 100 | raise ValueError('UnicodeDecodeError') 101 | self.Buffer = string 102 | 103 | 104 | class PEB(WinStruct): 105 | __slots__ = ( 106 | 'ProcessParameters' 107 | ) 108 | 109 | _fields_ = [ 110 | (0x20, 'ProcessParameters', 'P') 111 | ] 112 | 113 | def __init__(self, addr, process): 114 | super().__init__(addr, process) 115 | self.ProcessParameters = RtlUserProcessParameters( 116 | self.ProcessParameters, process) 117 | 118 | 119 | class RtlUserProcessParameters(WinStruct): 120 | __slots__ = ( 121 | 'ImagePathName', 122 | 'CommandLine' 123 | ) 124 | 125 | _fields_ = [ 126 | (0x60, 'ImagePathName', UnicodeString), 127 | (0x70, 'CommandLine', UnicodeString) 128 | ] 129 | 130 | def __init__(self, addr, process): 131 | super().__init__(addr, process) 132 | 133 | 134 | class AccessMask: 135 | STANDARD_RIGHTS = [ 136 | (1 << 16, "DELETE"), 137 | (1 << 17, "READ_CONTROL"), 138 | (1 << 18, "WRITE_DAC"), 139 | (1 << 19, "WRITE_OWNER"), 140 | (1 << 20, "SYNCHRONIZE"), 141 | (1 << 24, "ACCESS_SYSTEM_SECURITY"), 142 | (1 << 25, "MAXIMUM_ALLOWED"), 143 | (1 << 28, "GENERIC_ALL"), 144 | (1 << 29, "GENERIC_EXECUTE"), 145 | (1 << 30, "GENERIC_WRITE"), 146 | (1 << 31, "GENERIC_READ"), 147 | ] 148 | 149 | def __init__(self, desired_access): 150 | self.rights = [] 151 | self.rights.extend([right for mask, right in self.STANDARD_RIGHTS if 152 | desired_access & mask]) 153 | 154 | 155 | class FileAccessMask(AccessMask): 156 | SPECIFIC_RIGHTS = [ 157 | (1 << 0, "FILE_READ_DATA"), 158 | (1 << 1, "FILE_WRITE_DATA"), 159 | (1 << 2, "FILE_APPEND_DATA"), 160 | (1 << 3, "FILE_READ_EA"), 161 | (0x10, "FILE_WRITE_EA"), 162 | (0x20, "FILE_EXECUTE"), 163 | (0x80, "FILE_READ_ATTRIBUTES"), 164 | (0x100, "FILE_WRITE_ATTRIBUTES"), 165 | ] 166 | 167 | def __init__(self, desired_access): 168 | super().__init__(desired_access) 169 | self.rights.extend([right for mask, right in self.SPECIFIC_RIGHTS if 170 | desired_access & mask]) 171 | 172 | 173 | class FileRenameInformation(WinStruct): 174 | __slots__ = ( 175 | 'ReplaceIfExists', 176 | 'RootDirectory', 177 | 'FileNameLength', 178 | 'FileName' 179 | ) 180 | 181 | _fields_ = [ 182 | (0x0, 'ReplaceIfExists', "B"), 183 | (0x8, 'RootDirectory', "q"), 184 | (0x10, 'FileNameLength', "I"), 185 | (0x14, 'FileName', "B") 186 | 187 | ] 188 | 189 | def __init__(self, addr, process): 190 | super().__init__(addr, process) 191 | buffer = process.read_memory(addr + 0x14, self.FileNameLength) 192 | try: 193 | string = buffer.decode('utf-16-le') 194 | self.FileName = string 195 | except: 196 | raise ValueError('UnicodeDecodeError') 197 | 198 | 199 | class FileDispositionInformation(WinStruct): 200 | __slots__ = ( 201 | 'DeleteFile' 202 | ) 203 | 204 | _fields_ = [ 205 | (0, 'DeleteFile', "B") 206 | ] 207 | 208 | def __init__(self, addr, process): 209 | super().__init__(addr, process) 210 | 211 | 212 | class FileBasicInformation(WinStruct): 213 | __slots__ = ( 214 | 'CreationTime', 215 | 'LastAccessTime', 216 | 'LastWriteTime', 217 | 'ChangeTime', 218 | 'FileAttributes' 219 | ) 220 | 221 | _fields_ = [ 222 | (0x0, 'CreationTime', LargeInteger), 223 | (0x8, 'LastAccessTime', LargeInteger), 224 | (0x10, 'LastWriteTime', LargeInteger), 225 | (0x18, 'ChangeTime', LargeInteger), 226 | (0x20, 'FileAttributes', 'I') 227 | 228 | ] 229 | 230 | def __init__(self, addr, process): 231 | super().__init__(addr, process) 232 | -------------------------------------------------------------------------------- /nitro/event.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from enum import Enum 3 | 4 | 5 | class SyscallDirection(Enum): 6 | """System call direction""" 7 | enter = 0 8 | exit = 1 9 | 10 | 11 | class SyscallType(Enum): 12 | """System call mechanism""" 13 | sysenter = 0 14 | syscall = 1 15 | 16 | 17 | class NitroEvent: 18 | """ 19 | ``NitroEvent`` represents a low-level system event. It contains information 20 | about the state of the machine when the system was stopped. 21 | """ 22 | 23 | __slots__ = ( 24 | 'direction', 25 | 'type', 26 | 'regs', 27 | 'sregs', 28 | 'vcpu_nb', 29 | 'vcpu_io', 30 | 'time', 31 | ) 32 | 33 | def __init__(self, nitro_event_str, vcpu_io): 34 | #: Event direction. Are we entering or exiting a system call 35 | self.direction = SyscallDirection(nitro_event_str.direction) 36 | #: System call mechanism used 37 | self.type = SyscallType(nitro_event_str.type) 38 | #: Register state 39 | self.regs = nitro_event_str.regs 40 | #: Special register state 41 | self.sregs = nitro_event_str.sregs 42 | #: Handle to the VCPU where the event originated 43 | self.vcpu_io = vcpu_io 44 | #: VCPU number 45 | self.vcpu_nb = self.vcpu_io.vcpu_nb 46 | self.time = datetime.datetime.now().isoformat() 47 | 48 | def __str__(self): 49 | type_msg = self.type.name.upper() 50 | dir_msg = self.direction.name.upper() 51 | cr3 = hex(self.sregs.cr3) 52 | rax = hex(self.regs.rax) 53 | msg = 'vcpu: {} - type: {} - direction: {} - cr3: {} - rax: {}'.format( 54 | self.vcpu_nb, type_msg, dir_msg, cr3, rax) 55 | return msg 56 | 57 | def as_dict(self): 58 | """Return dict representation of the event""" 59 | info = { 60 | 'vcpu': self.vcpu_nb, 61 | 'type': self.type.name, 62 | 'direction': self.direction.name, 63 | 'cr3': hex(self.sregs.cr3), 64 | 'rax': hex(self.regs.rax), 65 | 'time': self.time 66 | } 67 | return info 68 | 69 | def get_register(self, register): 70 | """Get register value from the event""" 71 | try: 72 | value = getattr(self.regs, register) 73 | except AttributeError: 74 | raise RuntimeError('Unknown register') 75 | else: 76 | return value 77 | 78 | def update_register(self, register, value): 79 | """Change individual register's values""" 80 | # get latest regs, to avoid replacing EIP by value before emulation 81 | self.regs = self.vcpu_io.get_regs() 82 | # update register if possible 83 | try: 84 | setattr(self.regs, register, value) 85 | except AttributeError: 86 | raise RuntimeError('Unknown register') 87 | else: 88 | # send new register to KVM VCPU 89 | self.vcpu_io.set_regs(self.regs) 90 | -------------------------------------------------------------------------------- /nitro/listener.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import psutil 5 | import logging 6 | import time 7 | import threading 8 | from queue import Queue, Empty 9 | from concurrent.futures import ThreadPoolExecutor, wait 10 | 11 | from nitro.event import NitroEvent 12 | from nitro.kvm import KVM, VM 13 | 14 | class QEMUNotFoundError(Exception): 15 | pass 16 | 17 | def find_qemu_pid(vm_name): 18 | """ 19 | Find QEMU's PID that is associated with a given virtual machine 20 | 21 | :param str vm_name: libvirt domain name 22 | :rtype: int 23 | """ 24 | logging.info('Finding QEMU pid for domain %s', vm_name) 25 | libvirt_vm_pid_file = '/var/run/libvirt/qemu/{}.pid'.format(vm_name) 26 | try: 27 | with open(libvirt_vm_pid_file, 'r') as f: 28 | content = f.read() 29 | pid = int(content) 30 | return pid 31 | except IOError: 32 | for proc in psutil.process_iter(): 33 | cmdline = proc.cmdline()[1:] 34 | if proc.name() == "qemu-system-x86_64" and \ 35 | next((True for k, v in zip(cmdline, cmdline[1:]) if k == "-name" and vm_name in v), False): 36 | return proc.pid 37 | logging.critical('Cannot find QEMU') 38 | raise QEMUNotFoundError('Cannot find QEMU') 39 | 40 | class Listener: 41 | """ 42 | Class for listening to events from a virtual machine. 43 | """ 44 | 45 | __slots__ = ( 46 | 'domain', 47 | 'pid', 48 | 'kvm_io', 49 | 'vm_io', 50 | 'vcpus_io', 51 | 'stop_request', 52 | 'futures', 53 | 'queue', 54 | 'current_cont_event', 55 | ) 56 | 57 | def __init__(self, domain): 58 | #: Libvirt domain that the Listener is monitoring 59 | self.domain = domain 60 | #: Pid of the QEMU instance that is being monitored 61 | self.pid = find_qemu_pid(domain.name()) 62 | # init KVM 63 | self.kvm_io = KVM() 64 | # get VM fd 65 | vm_fd = self.kvm_io.attach_vm(self.pid) 66 | self.vm_io = VM(vm_fd) 67 | # get VCPU fds 68 | self.vcpus_io = self.vm_io.attach_vcpus() 69 | logging.info('Detected %s VCPUs', len(self.vcpus_io)) 70 | self.stop_request = None 71 | self.futures = None 72 | self.queue = None 73 | self.current_cont_event = None 74 | 75 | def set_traps(self, enabled): 76 | if self.domain.isActive(): 77 | self.domain.suspend() 78 | self.vm_io.set_syscall_trap(enabled) 79 | self.domain.resume() 80 | 81 | def __enter__(self): 82 | return self 83 | 84 | def __exit__(self, *args, **kwargs): 85 | self.stop() 86 | 87 | def stop(self, synchronous=True): 88 | """Stop listening for system calls""" 89 | self.set_traps(False) 90 | self.stop_request.set() 91 | if synchronous: 92 | # wait for threads to exit 93 | wait(self.futures) 94 | self.kvm_io.close() 95 | 96 | def listen(self): 97 | """Generator yielding NitroEvents from the virtual machine""" 98 | self.stop_request = threading.Event() 99 | pool = ThreadPoolExecutor(max_workers=len(self.vcpus_io)) 100 | self.futures = [] 101 | self.queue = Queue(maxsize=1) 102 | self.current_cont_event = None 103 | for vcpu_io in self.vcpus_io: 104 | # start to listen on this vcpu and report events in the queue 105 | f = pool.submit(self.listen_vcpu, vcpu_io, self.queue) 106 | self.futures.append(f) 107 | 108 | # while a thread is still running 109 | while [f for f in self.futures if f.running()]: 110 | try: 111 | (event, continue_event) = self.queue.get(timeout=1) 112 | except Empty: 113 | # domain has crashed or is shutdown ? 114 | if not self.domain.isActive(): 115 | self.stop_request.set() 116 | else: 117 | if not self.stop_request.is_set(): 118 | yield event 119 | continue_event.set() 120 | 121 | # raise listen_vcpu exceptions if any 122 | for f in self.futures: 123 | if f.exception() is not None: 124 | raise f.exception() 125 | logging.info('Stop Nitro listening') 126 | 127 | def listen_vcpu(self, vcpu_io, queue): 128 | """Listen to an individual virtual CPU""" 129 | logging.info('Start listening on VCPU %s', vcpu_io.vcpu_nb) 130 | # we need a per thread continue event 131 | continue_event = threading.Event() 132 | while not self.stop_request.is_set(): 133 | try: 134 | nitro_raw_ev = vcpu_io.get_event() 135 | except ValueError as e: 136 | if not self.vm_io.syscall_filters: 137 | # if there are no filters, get_event should not timeout 138 | # since we capture all system calls 139 | # so log the error 140 | logging.debug(str(e)) 141 | else: 142 | e = NitroEvent(nitro_raw_ev, vcpu_io) 143 | # put the event in the queue 144 | # and wait for the event to be processed, 145 | # when the main thread will set the continue_event 146 | item = (e, continue_event) 147 | queue.put(item) 148 | continue_event.wait() 149 | # reset continue_event 150 | continue_event.clear() 151 | vcpu_io.continue_vm() 152 | 153 | logging.debug('stop listening on VCPU %s', vcpu_io.vcpu_nb) 154 | 155 | def add_syscall_filter(self, syscall_nb): 156 | """Add system call filter to a virtual machine""" 157 | self.vm_io.add_syscall_filter(syscall_nb) 158 | 159 | def remove_syscall_filter(self, syscall_nb): 160 | """Remove system call filter form a virtual machine""" 161 | self.vm_io.remove_syscall_filter(syscall_nb) 162 | -------------------------------------------------------------------------------- /nitro/nitro.py: -------------------------------------------------------------------------------- 1 | from nitro.listener import Listener 2 | from nitro.backends import get_backend 3 | 4 | class Nitro: 5 | 6 | def __init__(self, domain, introspection=True, syscall_filtering=True): 7 | self.listener = Listener(domain) 8 | self.introspection = introspection 9 | self.backend = None 10 | if self.introspection: 11 | self.backend = get_backend(domain, self.listener, syscall_filtering) 12 | 13 | def listen(self): 14 | yield from self.listener.listen() 15 | 16 | def __enter__(self): 17 | return self 18 | 19 | def __exit__(self, *args, **kwargs): 20 | self.stop() 21 | 22 | def stop(self, synchronous=True): 23 | self.listener.stop(synchronous) -------------------------------------------------------------------------------- /nitro/syscall.py: -------------------------------------------------------------------------------- 1 | class Syscall: 2 | """ 3 | Class representing system call events. 4 | 5 | In contrast to NitroEvent events, Syscall class offers a higher-level view 6 | of what is happening inside the virtual machine. The class enables access to 7 | information about the process that created the event and makes it possible 8 | to access call's arguments. 9 | """ 10 | 11 | __slots__ = ( 12 | "event", 13 | "full_name", 14 | "name", 15 | "process", 16 | "args", 17 | "hook" 18 | ) 19 | 20 | def __init__(self, event, full_name, name, process, args): 21 | #: Associated low-level NitroEvent 22 | self.event = event 23 | #: Full name of the systme call handler (eg. SyS_write) 24 | self.full_name = full_name 25 | #: Short "cleaned up" name of the system call handler (eg. write) 26 | self.name = name 27 | #: Process that produced the event 28 | self.process = process 29 | #: Arguments passed to the call 30 | self.args = args 31 | #: Hook associated with the event 32 | self.hook = None 33 | 34 | def as_dict(self): 35 | """Retrieve a dict representation of the system call event.""" 36 | info = { 37 | "full_name": self.full_name, 38 | "name": self.name, 39 | "event": self.event.as_dict(), 40 | } 41 | if self.process: 42 | info['process'] = self.process.as_dict() 43 | if self.hook: 44 | info['hook'] = self.hook 45 | if self.args is not None and self.args.modified: 46 | info['modified'] = self.args.modified 47 | return info 48 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | psutil 2 | docopt 3 | ioctl_opt 4 | sphinx 5 | sphinx_rtd_theme 6 | nose2 7 | libvirt-python 8 | six 9 | libvmi 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name="nitro", 7 | version="0.0.1", 8 | author="F-Secure Corporation", 9 | author_email="mathieu.tarral@gmail.com", 10 | description="Hypervisor based tracing and monitoring prototype to trap guest syscalls and analyze them", 11 | url="https://github.com/KVM-VMI/nitro", 12 | packages=find_packages(), 13 | entry_points={ 14 | "console_scripts": [ 15 | "nitro = main:main" 16 | ] 17 | }, 18 | install_requires=[ 19 | 'docopt', 20 | 'libvirt-python', 21 | 'ioctl_opt', 22 | 'psutil' 23 | ], 24 | extras_require={ 25 | "docs": ["sphinx", "sphinx_rtd_theme"], 26 | "tests": ["nose2"] 27 | }, 28 | keywords=["nitro", "hyperisor", "monitoring", "tracing", "syscall"] 29 | ) 30 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore nitro storage pool 2 | images/ 3 | # ignore test output 4 | test_*/ 5 | 6 | # visual studio 7 | Debug/ 8 | *.pdb 9 | .vs 10 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | This directory contains the test suite for Nitro, based on the `Nose2` framework. 4 | 5 | # Requirements 6 | 7 | - `nose2` 8 | - `genisoimage` 9 | - dedicated test VM (_see Building test VMs_ section) 10 | 11 | # Setup 12 | 13 | The tests only targets `Windows_7_x64` for now. 14 | 15 | The test procedure expects to find a VM named `nitro_win7x64` in libvirt `qemu:///system`. 16 | 17 | In the `vm_templates` directory you will find a `packer` binary and templates 18 | to build ready to test Windows VMs. 19 | 20 | Check `vm_templates/README` for detailed instructions. 21 | 22 | 23 | # Running tests 24 | 25 | Tests are run under the `nose2` framework. (_[documentation](http://nose2.readthedocs.io/en/latest/getting_started.html)_) 26 | 27 | Note: If you install the debian package `python3-nose2`, the executable is named `nose2-3`. 28 | 29 | Remember that you need a custom test VM named `nitro_win7x64` to run the tests. 30 | 31 | ## Usage 32 | 33 | This will run all tests available in all the test files, quitely and display the overall result 34 | ~~~ 35 | $ nose2 36 | ~~~ 37 | 38 | You can ask `nose` to be a bit verbose and display which tests it is running with `-v` 39 | ~~~ 40 | $ nose2 -v 41 | ~~~ 42 | 43 | You can also ask him to be more verbose by capturing the `logging` output, useful to debug and understand what is really happenning during the test. 44 | ~~~ 45 | $ nose2 -v --log-capture 46 | ~~~ 47 | 48 | ## Running a specific test 49 | 50 | The `Nose2` test name is `test_file.TestClass.test_name` 51 | 52 | For example, if you want to run `test_hook_openkey` located in the `TestWindows` class inside the `test_windows.py`: 53 | 54 | ~~~ 55 | $ nose2 --log-capture test_windows.TestWindows.test_hook_openkey 56 | ~~~ 57 | 58 | ## Test output 59 | 60 | Each test which is run will create a `` directory under `tests/`, containing a least the `test.log` logging output. 61 | 62 | The Nitro events are also dumped there, usually in a file named `events.json`, if specified during the test. 63 | 64 | ## General test behavior 65 | 66 | A test consists of the following operations: 67 | 68 | 1. start the domain `nitro_win7x64` 69 | 2. wait for the DHCP request to get the ip address 70 | 3. wait for WinRM service to be available (_port_ `5985`) 71 | 4. set nitro traps and start listening to syscall events 72 | 5. configure and insert a CDROM to execute a binary or a script (_this is your test configuration_) 73 | 6. wait for WinRM service to be closed 74 | 7. stop the domain and the test 75 | 76 | # Developing new tests 77 | 78 | A Nitro test code is composed the following steps 79 | 1. configure the CDROM to be injected 80 | 2. define the nitro callbacks 81 | 3. run Nitro and get the events 82 | 4. analyze the events and validate the test 83 | 84 | Test code example 85 | ~~~Python 86 | def test_01(self): 87 | # 1. configure the cdrom 88 | # a custom binary 89 | self.vm.cdrom.set_executable('binary_path') 90 | # a batch script 91 | script = 'dir C:\\windows\\system32' 92 | self.vm.cdrom.set_script(script) 93 | # a powershell script 94 | script = 'Get-ChildItem -Path C:\\windows\\system32' 95 | self.vm.cdrom.set_script(script, powershell=True) 96 | 97 | # 2. define your Nitro callbacks 98 | def enter_NtOpenFile(syscall, backend): 99 | logging.info('enter in NtOpenFile') 100 | syscall.hook = 'foobar' 101 | 102 | hooks = { 103 | 'NtOpenFile': enter_NtOpenFile, 104 | } 105 | 106 | # 3. run the test and get the events 107 | # This will start Nitro in the background and wait for the test to be executed 108 | events, exec_time = self.vm.run_test(hooks=hooks) 109 | 110 | # optional: log nitro events 111 | logging.debug('Writing events...') 112 | with open('events.json', 'w') as f: 113 | json.dump(events, f, indent=4) 114 | 115 | # 4. analyze events and validate 116 | event_found = [e for e in events if e.get('hook') and e['hook'] == "foobar"] 117 | self.assertTrue(event_found) 118 | ~~~ 119 | 120 | ## Controlling Nitro loop in the test 121 | 122 | If you want to directly control the nitro loop by yourself, here is how to do it. 123 | 124 | ~~~Python 125 | def test_loop(self): 126 | script = 'Get-ChildItem -Path C:\\windows\\system32' 127 | self.vm.cdrom.set_script(script, powershell=True) 128 | 129 | events = [] 130 | with Backend(self.domain, True) as backend: 131 | backend.nitro.set_traps(True) 132 | stop_event = self.vm.run_test(wait=False) 133 | for event in backend.nitro.listen(): 134 | syscall = backend.process_event(event) 135 | 136 | if syscall.name == "NtOpenFile": 137 | logging.info('NtOpenFile') 138 | 139 | events.append(syscall.info()) 140 | 141 | if stop_event.is_set(): 142 | break 143 | ~~~ -------------------------------------------------------------------------------- /tests/binaries/createfile_all.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/createfile_all.exe -------------------------------------------------------------------------------- /tests/binaries/createfile_append.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/createfile_append.exe -------------------------------------------------------------------------------- /tests/binaries/createfile_execute.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/createfile_execute.exe -------------------------------------------------------------------------------- /tests/binaries/createfile_file_execute.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/createfile_file_execute.exe -------------------------------------------------------------------------------- /tests/binaries/createfile_read.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/createfile_read.exe -------------------------------------------------------------------------------- /tests/binaries/createfile_read_data.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/createfile_read_data.exe -------------------------------------------------------------------------------- /tests/binaries/createfile_write.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/createfile_write.exe -------------------------------------------------------------------------------- /tests/binaries/createfile_write_data.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/createfile_write_data.exe -------------------------------------------------------------------------------- /tests/binaries/delete_file.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries/delete_file.exe -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "createfile_read", "createfile\createfile.vcxproj", "{FC8193F3-832A-40CD-AB38-994265157574}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "createfile_write", "openfile_write\openfile_write.vcxproj", "{E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "createfile_execute", "createfile_execute\createfile_execute.vcxproj", "{783B31EE-5CD6-4353-AC4B-0A5AF028F872}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "createfile_all", "createfile_all\createfile_all.vcxproj", "{FD46C0B1-A188-4DF4-9150-46FEB6798F26}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "createfile_append", "createfile_append\createfile_append.vcxproj", "{73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}" 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "createfile_read_data", "createfile_read_data\createfile_read_data.vcxproj", "{8F549E1F-157A-4EFB-88E3-4366F34F267C}" 17 | EndProject 18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "createfile_write_data", "createfile_write_data\createfile_write_data.vcxproj", "{65AEA6BC-6838-4FE5-B883-ADA3098B2A87}" 19 | EndProject 20 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "createfile_file_execute", "createfile_file_execute\createfile_file_execute.vcxproj", "{93F71160-86BC-4100-90E4-DD64C14AE365}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|x64 = Debug|x64 25 | Debug|x86 = Debug|x86 26 | Release|x64 = Release|x64 27 | Release|x86 = Release|x86 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {FC8193F3-832A-40CD-AB38-994265157574}.Debug|x64.ActiveCfg = Debug|x64 31 | {FC8193F3-832A-40CD-AB38-994265157574}.Debug|x64.Build.0 = Debug|x64 32 | {FC8193F3-832A-40CD-AB38-994265157574}.Debug|x86.ActiveCfg = Debug|Win32 33 | {FC8193F3-832A-40CD-AB38-994265157574}.Debug|x86.Build.0 = Debug|Win32 34 | {FC8193F3-832A-40CD-AB38-994265157574}.Release|x64.ActiveCfg = Release|x64 35 | {FC8193F3-832A-40CD-AB38-994265157574}.Release|x64.Build.0 = Release|x64 36 | {FC8193F3-832A-40CD-AB38-994265157574}.Release|x86.ActiveCfg = Release|Win32 37 | {FC8193F3-832A-40CD-AB38-994265157574}.Release|x86.Build.0 = Release|Win32 38 | {E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}.Debug|x64.ActiveCfg = Debug|x64 39 | {E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}.Debug|x64.Build.0 = Debug|x64 40 | {E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}.Debug|x86.ActiveCfg = Debug|Win32 41 | {E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}.Debug|x86.Build.0 = Debug|Win32 42 | {E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}.Release|x64.ActiveCfg = Release|x64 43 | {E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}.Release|x64.Build.0 = Release|x64 44 | {E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}.Release|x86.ActiveCfg = Release|Win32 45 | {E2075DC6-DD45-4793-895D-7BA8F0EE4B8F}.Release|x86.Build.0 = Release|Win32 46 | {783B31EE-5CD6-4353-AC4B-0A5AF028F872}.Debug|x64.ActiveCfg = Debug|x64 47 | {783B31EE-5CD6-4353-AC4B-0A5AF028F872}.Debug|x64.Build.0 = Debug|x64 48 | {783B31EE-5CD6-4353-AC4B-0A5AF028F872}.Debug|x86.ActiveCfg = Debug|Win32 49 | {783B31EE-5CD6-4353-AC4B-0A5AF028F872}.Debug|x86.Build.0 = Debug|Win32 50 | {783B31EE-5CD6-4353-AC4B-0A5AF028F872}.Release|x64.ActiveCfg = Release|x64 51 | {783B31EE-5CD6-4353-AC4B-0A5AF028F872}.Release|x64.Build.0 = Release|x64 52 | {783B31EE-5CD6-4353-AC4B-0A5AF028F872}.Release|x86.ActiveCfg = Release|Win32 53 | {783B31EE-5CD6-4353-AC4B-0A5AF028F872}.Release|x86.Build.0 = Release|Win32 54 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26}.Debug|x64.ActiveCfg = Debug|x64 55 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26}.Debug|x64.Build.0 = Debug|x64 56 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26}.Debug|x86.ActiveCfg = Debug|Win32 57 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26}.Debug|x86.Build.0 = Debug|Win32 58 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26}.Release|x64.ActiveCfg = Release|x64 59 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26}.Release|x64.Build.0 = Release|x64 60 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26}.Release|x86.ActiveCfg = Release|Win32 61 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26}.Release|x86.Build.0 = Release|Win32 62 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}.Debug|x64.ActiveCfg = Debug|x64 63 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}.Debug|x64.Build.0 = Debug|x64 64 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}.Debug|x86.ActiveCfg = Debug|Win32 65 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}.Debug|x86.Build.0 = Debug|Win32 66 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}.Release|x64.ActiveCfg = Release|x64 67 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}.Release|x64.Build.0 = Release|x64 68 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}.Release|x86.ActiveCfg = Release|Win32 69 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF}.Release|x86.Build.0 = Release|Win32 70 | {8F549E1F-157A-4EFB-88E3-4366F34F267C}.Debug|x64.ActiveCfg = Debug|x64 71 | {8F549E1F-157A-4EFB-88E3-4366F34F267C}.Debug|x64.Build.0 = Debug|x64 72 | {8F549E1F-157A-4EFB-88E3-4366F34F267C}.Debug|x86.ActiveCfg = Debug|Win32 73 | {8F549E1F-157A-4EFB-88E3-4366F34F267C}.Debug|x86.Build.0 = Debug|Win32 74 | {8F549E1F-157A-4EFB-88E3-4366F34F267C}.Release|x64.ActiveCfg = Release|x64 75 | {8F549E1F-157A-4EFB-88E3-4366F34F267C}.Release|x64.Build.0 = Release|x64 76 | {8F549E1F-157A-4EFB-88E3-4366F34F267C}.Release|x86.ActiveCfg = Release|Win32 77 | {8F549E1F-157A-4EFB-88E3-4366F34F267C}.Release|x86.Build.0 = Release|Win32 78 | {65AEA6BC-6838-4FE5-B883-ADA3098B2A87}.Debug|x64.ActiveCfg = Debug|x64 79 | {65AEA6BC-6838-4FE5-B883-ADA3098B2A87}.Debug|x64.Build.0 = Debug|x64 80 | {65AEA6BC-6838-4FE5-B883-ADA3098B2A87}.Debug|x86.ActiveCfg = Debug|Win32 81 | {65AEA6BC-6838-4FE5-B883-ADA3098B2A87}.Debug|x86.Build.0 = Debug|Win32 82 | {65AEA6BC-6838-4FE5-B883-ADA3098B2A87}.Release|x64.ActiveCfg = Release|x64 83 | {65AEA6BC-6838-4FE5-B883-ADA3098B2A87}.Release|x64.Build.0 = Release|x64 84 | {65AEA6BC-6838-4FE5-B883-ADA3098B2A87}.Release|x86.ActiveCfg = Release|Win32 85 | {65AEA6BC-6838-4FE5-B883-ADA3098B2A87}.Release|x86.Build.0 = Release|Win32 86 | {93F71160-86BC-4100-90E4-DD64C14AE365}.Debug|x64.ActiveCfg = Debug|x64 87 | {93F71160-86BC-4100-90E4-DD64C14AE365}.Debug|x64.Build.0 = Debug|x64 88 | {93F71160-86BC-4100-90E4-DD64C14AE365}.Debug|x86.ActiveCfg = Debug|Win32 89 | {93F71160-86BC-4100-90E4-DD64C14AE365}.Debug|x86.Build.0 = Debug|Win32 90 | {93F71160-86BC-4100-90E4-DD64C14AE365}.Release|x64.ActiveCfg = Release|x64 91 | {93F71160-86BC-4100-90E4-DD64C14AE365}.Release|x64.Build.0 = Release|x64 92 | {93F71160-86BC-4100-90E4-DD64C14AE365}.Release|x86.ActiveCfg = Release|Win32 93 | {93F71160-86BC-4100-90E4-DD64C14AE365}.Release|x86.Build.0 = Release|Win32 94 | EndGlobalSection 95 | GlobalSection(SolutionProperties) = preSolution 96 | HideSolutionNode = FALSE 97 | EndGlobalSection 98 | EndGlobal 99 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | CONSOLE APPLICATION : createfile Project Overview 3 | ======================================================================== 4 | 5 | AppWizard has created this createfile application for you. 6 | 7 | This file contains a summary of what you will find in each of the files that 8 | make up your createfile application. 9 | 10 | 11 | createfile.vcxproj 12 | This is the main project file for VC++ projects generated using an Application Wizard. 13 | It contains information about the version of Visual C++ that generated the file, and 14 | information about the platforms, configurations, and project features selected with the 15 | Application Wizard. 16 | 17 | createfile.vcxproj.filters 18 | This is the filters file for VC++ projects generated using an Application Wizard. 19 | It contains information about the association between the files in your project 20 | and the filters. This association is used in the IDE to show grouping of files with 21 | similar extensions under a specific node (for e.g. ".cpp" files are associated with the 22 | "Source Files" filter). 23 | 24 | createfile.cpp 25 | This is the main application source file. 26 | 27 | ///////////////////////////////////////////////////////////////////////////// 28 | Other standard files: 29 | 30 | StdAfx.h, StdAfx.cpp 31 | These files are used to build a precompiled header (PCH) file 32 | named createfile.pch and a precompiled types file named StdAfx.obj. 33 | 34 | ///////////////////////////////////////////////////////////////////////////// 35 | Other notes: 36 | 37 | AppWizard uses "TODO:" comments to indicate parts of the source code you 38 | should add to or customize. 39 | 40 | ///////////////////////////////////////////////////////////////////////////// 41 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile/createfile.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Source Files 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile/foobar.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/binaries_sources/createfile/createfile/foobar.txt -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile/main.cpp: -------------------------------------------------------------------------------- 1 | // createfile.cpp : Defines the entry point for the console application. 2 | // 3 | 4 | #include 5 | 6 | 7 | int main() 8 | { 9 | LPCTSTR path = L"foobar.txt"; 10 | DWORD desired_access = GENERIC_READ; 11 | DWORD share_mode = NULL; 12 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 13 | DWORD creation_disposition = CREATE_ALWAYS; 14 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 15 | HANDLE template_file = NULL; 16 | 17 | HANDLE f = CreateFile(path, 18 | desired_access, // desired access 19 | share_mode, // share mode 20 | security_attributes, // security attributes 21 | creation_disposition, // creation disposition 22 | flags_and_attributes, 23 | template_file 24 | ); 25 | 26 | return 0; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_all/createfile_all.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {FD46C0B1-A188-4DF4-9150-46FEB6798F26} 24 | Win32Proj 25 | createfile_all 26 | 10.0.14393.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v141 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v141 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v141 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v141 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | Level3 90 | Disabled 91 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | MultiThreadedDebug 93 | 94 | 95 | Console 96 | 97 | 98 | 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | 106 | 107 | Console 108 | 109 | 110 | 111 | 112 | Level3 113 | 114 | 115 | MaxSpeed 116 | true 117 | true 118 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | 120 | 121 | Console 122 | true 123 | true 124 | 125 | 126 | 127 | 128 | Level3 129 | 130 | 131 | MaxSpeed 132 | true 133 | true 134 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | 136 | 137 | Console 138 | true 139 | true 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_all/createfile_all.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_all/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | int main() 5 | { 6 | LPCTSTR path = L"foobar.txt"; 7 | DWORD desired_access = GENERIC_ALL; 8 | DWORD share_mode = NULL; 9 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 10 | DWORD creation_disposition = CREATE_ALWAYS; 11 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 12 | HANDLE template_file = NULL; 13 | 14 | HANDLE f = CreateFile(path, 15 | desired_access, // desired access 16 | share_mode, // share mode 17 | security_attributes, // security attributes 18 | creation_disposition, // creation disposition 19 | flags_and_attributes, 20 | template_file 21 | ); 22 | 23 | return 0; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_append/createfile_append.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {73B9DC10-7DD7-4AB7-A529-1AA4CAE6D5BF} 24 | Win32Proj 25 | createfile_append 26 | 10.0.14393.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v141 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v141 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v141 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v141 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | Level3 90 | Disabled 91 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | MultiThreadedDebug 93 | 94 | 95 | Console 96 | 97 | 98 | 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | 106 | 107 | Console 108 | 109 | 110 | 111 | 112 | Level3 113 | 114 | 115 | MaxSpeed 116 | true 117 | true 118 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | 120 | 121 | Console 122 | true 123 | true 124 | 125 | 126 | 127 | 128 | Level3 129 | 130 | 131 | MaxSpeed 132 | true 133 | true 134 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | 136 | 137 | Console 138 | true 139 | true 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_append/createfile_append.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_append/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | int main() 5 | { 6 | LPCTSTR path = L"foobar.txt"; 7 | DWORD desired_access = FILE_APPEND_DATA; 8 | DWORD share_mode = NULL; 9 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 10 | DWORD creation_disposition = CREATE_ALWAYS; 11 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 12 | HANDLE template_file = NULL; 13 | 14 | HANDLE f = CreateFile(path, 15 | desired_access, // desired access 16 | share_mode, // share mode 17 | security_attributes, // security attributes 18 | creation_disposition, // creation disposition 19 | flags_and_attributes, 20 | template_file 21 | ); 22 | 23 | return 0; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_execute/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | CONSOLE APPLICATION : createfile_execute Project Overview 3 | ======================================================================== 4 | 5 | AppWizard has created this createfile_execute application for you. 6 | 7 | This file contains a summary of what you will find in each of the files that 8 | make up your createfile_execute application. 9 | 10 | 11 | createfile_execute.vcxproj 12 | This is the main project file for VC++ projects generated using an Application Wizard. 13 | It contains information about the version of Visual C++ that generated the file, and 14 | information about the platforms, configurations, and project features selected with the 15 | Application Wizard. 16 | 17 | createfile_execute.vcxproj.filters 18 | This is the filters file for VC++ projects generated using an Application Wizard. 19 | It contains information about the association between the files in your project 20 | and the filters. This association is used in the IDE to show grouping of files with 21 | similar extensions under a specific node (for e.g. ".cpp" files are associated with the 22 | "Source Files" filter). 23 | 24 | createfile_execute.cpp 25 | This is the main application source file. 26 | 27 | ///////////////////////////////////////////////////////////////////////////// 28 | Other standard files: 29 | 30 | StdAfx.h, StdAfx.cpp 31 | These files are used to build a precompiled header (PCH) file 32 | named createfile_execute.pch and a precompiled types file named StdAfx.obj. 33 | 34 | ///////////////////////////////////////////////////////////////////////////// 35 | Other notes: 36 | 37 | AppWizard uses "TODO:" comments to indicate parts of the source code you 38 | should add to or customize. 39 | 40 | ///////////////////////////////////////////////////////////////////////////// 41 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_execute/createfile_execute.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Source Files 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_execute/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | int main() 5 | { 6 | LPCTSTR path = L"foobar.txt"; 7 | DWORD desired_access = GENERIC_EXECUTE; 8 | DWORD share_mode = NULL; 9 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 10 | DWORD creation_disposition = CREATE_ALWAYS; 11 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 12 | HANDLE template_file = NULL; 13 | 14 | HANDLE f = CreateFile(path, 15 | desired_access, // desired access 16 | share_mode, // share mode 17 | security_attributes, // security attributes 18 | creation_disposition, // creation disposition 19 | flags_and_attributes, 20 | template_file 21 | ); 22 | 23 | return 0; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_file_execute/createfile_file_execute.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_file_execute/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | int main() 5 | { 6 | LPCTSTR path = L"foobar.txt"; 7 | DWORD desired_access = FILE_EXECUTE; 8 | DWORD share_mode = NULL; 9 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 10 | DWORD creation_disposition = CREATE_ALWAYS; 11 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 12 | HANDLE template_file = NULL; 13 | 14 | HANDLE f = CreateFile(path, 15 | desired_access, // desired access 16 | share_mode, // share mode 17 | security_attributes, // security attributes 18 | creation_disposition, // creation disposition 19 | flags_and_attributes, 20 | template_file 21 | ); 22 | 23 | return 0; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_read_data/createfile_read_data.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {8F549E1F-157A-4EFB-88E3-4366F34F267C} 24 | Win32Proj 25 | createfile_read_data 26 | 10.0.14393.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v141 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v141 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v141 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v141 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | Level3 90 | Disabled 91 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | MultiThreadedDebug 93 | 94 | 95 | Console 96 | 97 | 98 | 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | 106 | 107 | Console 108 | 109 | 110 | 111 | 112 | Level3 113 | 114 | 115 | MaxSpeed 116 | true 117 | true 118 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | 120 | 121 | Console 122 | true 123 | true 124 | 125 | 126 | 127 | 128 | Level3 129 | 130 | 131 | MaxSpeed 132 | true 133 | true 134 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | 136 | 137 | Console 138 | true 139 | true 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_read_data/createfile_read_data.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_read_data/main.cpp: -------------------------------------------------------------------------------- 1 | // createfile.cpp : Defines the entry point for the console application. 2 | // 3 | 4 | #include 5 | 6 | 7 | int main() 8 | { 9 | LPCTSTR path = L"foobar.txt"; 10 | DWORD desired_access = FILE_READ_DATA; 11 | DWORD share_mode = NULL; 12 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 13 | DWORD creation_disposition = CREATE_ALWAYS; 14 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 15 | HANDLE template_file = NULL; 16 | 17 | HANDLE f = CreateFile(path, 18 | desired_access, // desired access 19 | share_mode, // share mode 20 | security_attributes, // security attributes 21 | creation_disposition, // creation disposition 22 | flags_and_attributes, 23 | template_file 24 | ); 25 | 26 | return 0; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_write_data/createfile_write_data.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/createfile_write_data/main.cpp: -------------------------------------------------------------------------------- 1 | // openfile_write.cpp : Defines the entry point for the console application. 2 | // 3 | 4 | #include 5 | 6 | 7 | int main() 8 | { 9 | LPCTSTR path = L"foobar.txt"; 10 | DWORD desired_access = FILE_WRITE_DATA; 11 | DWORD share_mode = NULL; 12 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 13 | DWORD creation_disposition = CREATE_ALWAYS; 14 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 15 | HANDLE template_file = NULL; 16 | 17 | HANDLE f = CreateFile(path, 18 | desired_access, // desired access 19 | share_mode, // share mode 20 | security_attributes, // security attributes 21 | creation_disposition, // creation disposition 22 | flags_and_attributes, 23 | template_file 24 | ); 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/delete_file/delete_file.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {198583DF-8952-4913-AD9F-F123DD03C837} 24 | Win32Proj 25 | delete_file 26 | 10.0.14393.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v141 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v141 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v141 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v141 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | Level3 90 | Disabled 91 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | MultiThreadedDebug 93 | 94 | 95 | Console 96 | 97 | 98 | 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | 106 | 107 | Console 108 | 109 | 110 | 111 | 112 | Level3 113 | 114 | 115 | MaxSpeed 116 | true 117 | true 118 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | 120 | 121 | Console 122 | true 123 | true 124 | 125 | 126 | 127 | 128 | Level3 129 | 130 | 131 | MaxSpeed 132 | true 133 | true 134 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | 136 | 137 | Console 138 | true 139 | true 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/delete_file/delete_file.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/delete_file/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | int main() 5 | { 6 | LPCTSTR path = L"foobar.txt"; 7 | DWORD desired_access = FILE_WRITE_DATA; 8 | DWORD share_mode = NULL; 9 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 10 | DWORD creation_disposition = CREATE_ALWAYS; 11 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 12 | HANDLE template_file = NULL; 13 | 14 | HANDLE f = CreateFile(path, 15 | desired_access, // desired access 16 | share_mode, // share mode 17 | security_attributes, // security attributes 18 | creation_disposition, // creation disposition 19 | flags_and_attributes, 20 | template_file 21 | ); 22 | 23 | DeleteFile(path); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/openfile_write/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | CONSOLE APPLICATION : openfile_write Project Overview 3 | ======================================================================== 4 | 5 | AppWizard has created this openfile_write application for you. 6 | 7 | This file contains a summary of what you will find in each of the files that 8 | make up your openfile_write application. 9 | 10 | 11 | openfile_write.vcxproj 12 | This is the main project file for VC++ projects generated using an Application Wizard. 13 | It contains information about the version of Visual C++ that generated the file, and 14 | information about the platforms, configurations, and project features selected with the 15 | Application Wizard. 16 | 17 | openfile_write.vcxproj.filters 18 | This is the filters file for VC++ projects generated using an Application Wizard. 19 | It contains information about the association between the files in your project 20 | and the filters. This association is used in the IDE to show grouping of files with 21 | similar extensions under a specific node (for e.g. ".cpp" files are associated with the 22 | "Source Files" filter). 23 | 24 | openfile_write.cpp 25 | This is the main application source file. 26 | 27 | ///////////////////////////////////////////////////////////////////////////// 28 | Other standard files: 29 | 30 | StdAfx.h, StdAfx.cpp 31 | These files are used to build a precompiled header (PCH) file 32 | named openfile_write.pch and a precompiled types file named StdAfx.obj. 33 | 34 | ///////////////////////////////////////////////////////////////////////////// 35 | Other notes: 36 | 37 | AppWizard uses "TODO:" comments to indicate parts of the source code you 38 | should add to or customize. 39 | 40 | ///////////////////////////////////////////////////////////////////////////// 41 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/openfile_write/main.cpp: -------------------------------------------------------------------------------- 1 | // openfile_write.cpp : Defines the entry point for the console application. 2 | // 3 | 4 | #include 5 | 6 | 7 | int main() 8 | { 9 | LPCTSTR path = L"foobar.txt"; 10 | DWORD desired_access = GENERIC_WRITE; 11 | DWORD share_mode = NULL; 12 | LPSECURITY_ATTRIBUTES security_attributes = NULL; 13 | DWORD creation_disposition = CREATE_ALWAYS; 14 | DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; 15 | HANDLE template_file = NULL; 16 | 17 | HANDLE f = CreateFile(path, 18 | desired_access, // desired access 19 | share_mode, // share mode 20 | security_attributes, // security attributes 21 | creation_disposition, // creation disposition 22 | flags_and_attributes, 23 | template_file 24 | ); 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /tests/binaries_sources/createfile/openfile_write/openfile_write.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Source Files 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/cdrom.py: -------------------------------------------------------------------------------- 1 | import os 2 | import textwrap 3 | import stat 4 | import shutil 5 | import logging 6 | import subprocess 7 | from pathlib import Path 8 | from tempfile import TemporaryDirectory, NamedTemporaryFile 9 | 10 | class CDROM: 11 | def __init__(self): 12 | # create cdrom dir 13 | self.cdrom_dir_tmp = TemporaryDirectory() 14 | self.tmp_dir = TemporaryDirectory() 15 | self.cdrom_iso_tmp = None 16 | # give qemu permission to execute and read in this directory 17 | os.chmod(self.tmp_dir.name, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | 18 | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) 19 | self.cdrom_dir = self.cdrom_dir_tmp.name 20 | self.cdrom_iso_tmp = None 21 | 22 | def __enter__(self): 23 | return self 24 | 25 | def __exit__(self, *args): 26 | self.cleanup() 27 | 28 | def cleanup(self): 29 | if self.cdrom_iso_tmp: 30 | self.cdrom_iso_tmp.close() 31 | self.tmp_dir.cleanup() 32 | self.cdrom_dir_tmp.cleanup() 33 | 34 | def add_file_from_str(self, name, content, executable=False, convert_nl=False, dedent=False): 35 | path = os.path.join(self.cdrom_dir, name) 36 | if dedent: 37 | content = textwrap.dedent(content) 38 | if convert_nl: 39 | content = content.replace("\n", "\r\n") 40 | with open(path, "w") as f: 41 | f.write(content) 42 | if executable: 43 | current = os.stat(path) 44 | os.chmod(path, current.st_mode | stat.S_IEXEC) 45 | 46 | def add_file(self, path): 47 | source = Path(path) 48 | destination = os.path.join(self.cdrom_dir, source.name) 49 | shutil.copyfile(str(source), destination) 50 | shutil.copymode(str(source), destination) 51 | 52 | def generate_iso(self, cleanup=True): 53 | self.cdrom_iso_tmp = NamedTemporaryFile(delete=False, dir=self.tmp_dir.name) 54 | cdrom_iso = self.cdrom_iso_tmp.name 55 | # chmod to be r/w by everyone 56 | # so we can remove the file even when qemu takes the ownership 57 | 58 | tools = { 59 | "genisoimage": self.__genisoimage, 60 | "mkisofs": self.__mkisofs 61 | } 62 | 63 | available = next(bin for bin in tools.keys() 64 | if shutil.which(bin) is not None) 65 | 66 | # generate iso 67 | if available is None: 68 | raise Exception('Cannot find tools for creating ISO images') 69 | 70 | tools[available](cdrom_iso) 71 | 72 | logging.debug('ISO generated at %s', cdrom_iso) 73 | # cleanup 74 | if cleanup: 75 | self.cdrom_dir_tmp.cleanup() 76 | return cdrom_iso 77 | 78 | def __genisoimage(self, cdrom_iso): 79 | args = ['genisoimage', '-o', cdrom_iso, '-iso-level', '4', '-r', self.cdrom_dir] 80 | subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 81 | 82 | def __mkisofs(self, cdrom_iso): 83 | args = ['mkisofs', '-o', cdrom_iso, '-iso-level', '4', '-r', self.cdrom_dir] 84 | subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 85 | 86 | class WindowsCDROM(CDROM): 87 | def __init__(self): 88 | super().__init__() 89 | self.write_autorun() 90 | self.write_run_bat() 91 | 92 | def set_executable(self, exe_path): 93 | exe_path = Path(exe_path) 94 | self.add_file(exe_path) 95 | self.add_file_from_str("test.bat", exe_path.name, convert_nl=True) 96 | 97 | def write_autorun(self): 98 | # write autorun.inf 99 | content = """ 100 | [autorun] 101 | open=run.bat 102 | """[1:] 103 | self.add_file_from_str("autorun.inf", content, convert_nl=True) 104 | 105 | def write_run_bat(self): 106 | content = """ 107 | CALL test.bat 108 | sc stop winrm 109 | """[1:] 110 | self.add_file_from_str("run.bat", content, convert_nl=True) 111 | 112 | def set_script(self, script, powershell=False): 113 | script = script.replace('\n', '\r\n') 114 | if powershell: 115 | test_bat_content = "powershell -File test.ps1" 116 | self.add_file_from_str("test.ps1", script, convert_nl=True) 117 | else: 118 | test_bat_content = script 119 | self.add_file_from_str("test.bat", test_bat_content, convert_nl=True) 120 | 121 | class LinuxCDROM(CDROM): 122 | def __init__(self): 123 | super().__init__() 124 | self.write_autoexec_sh() 125 | 126 | def write_autoexec_sh(self): 127 | # write autoexec.sh script that executes the supplied test and stops sshd 128 | content = """ 129 | #!/usr/bin/env bash 130 | "$(dirname "$(realpath "$0")")/test.sh" 131 | systemctl stop sshd 132 | """[1:] 133 | self.add_file_from_str("autoexec.sh", content, executable=True, dedent=True) 134 | 135 | def set_executable(self, exe_path): 136 | exe_path = Path(exe_path) 137 | self.add_file(exe_path) 138 | bash_script = """ 139 | #!/usr/bin/env bash 140 | "$(dirname "$(realpath "$0")")/{}" 141 | """.format(exe_path.name)[1:] 142 | self.add_file_from_str("test.sh", bash_script, executable=True, dedent=True) 143 | 144 | # It's a bit ugly that these do not share the same signature 145 | def set_script(self, script, interpreter="/usr/bin/env bash"): 146 | content = """ 147 | #!{} 148 | {} 149 | """.format(interpreter, script)[1:] 150 | self.add_file_from_str("test.sh", content, dedent=True, executable=True) 151 | -------------------------------------------------------------------------------- /tests/layers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import shutil 4 | import datetime 5 | import libvirt 6 | 7 | from vmtest_helper import WindowsVMTestHelper, LinuxVMTestHelper 8 | 9 | class LoggingLayer(object): 10 | 11 | @classmethod 12 | def testSetUp(cls, test_class): 13 | # clean old test directory 14 | test_dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), test_class._testMethodName) 15 | shutil.rmtree(test_dir_path, ignore_errors=True) 16 | os.makedirs(test_dir_path, exist_ok=True) 17 | test_class.script_dir = os.path.dirname(os.path.realpath(__file__)) 18 | # chdir into this directory for the test 19 | test_class.origin_wd = os.getcwd() 20 | os.chdir(test_dir_path) 21 | # create logging file handler 22 | test_class.f_handler = logging.FileHandler('test.log', mode='w') 23 | logging.getLogger().addHandler(test_class.f_handler) 24 | logging.info('Starting test at {}'.format(datetime.datetime.now())) 25 | 26 | @classmethod 27 | def testTearDown(cls, test_class): 28 | # chdir back to original wd 29 | os.chdir(test_class.origin_wd) 30 | # remove file handler 31 | logging.info('Ending test at {}'.format(datetime.datetime.now())) 32 | logging.getLogger().removeHandler(test_class.f_handler) 33 | 34 | 35 | class VMLayer(LoggingLayer): 36 | @classmethod 37 | def testSetUp(cls, test_class): 38 | con = libvirt.open('qemu:///system') 39 | test_class.domain = con.lookupByName(test_class.domain_name) 40 | test_class.vm = test_class.test_helper(test_class.domain) 41 | 42 | @classmethod 43 | def testTearDown(cls, test_class): 44 | test_class.vm.stop() 45 | -------------------------------------------------------------------------------- /tests/linux_binaries/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | CFLAGS += -std=gnu99 -Wall -O2 3 | OUTDIR = build 4 | SOURCES = $(wildcard *.c) 5 | TARGETS = $(patsubst %.c, $(OUTDIR)/%, $(SOURCES)) 6 | 7 | all: outdir $(TARGETS) 8 | 9 | outdir: 10 | mkdir -p $(OUTDIR) 11 | 12 | $(OUTDIR)/%: %.c 13 | $(CC) $(CFLAGS) -o $@ $< 14 | 15 | .PHONY: clean 16 | 17 | clean: 18 | rm -f $(TARGETS) 19 | -------------------------------------------------------------------------------- /tests/linux_binaries/test_open.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define PATH "/proc/cpuinfo" 6 | 7 | int main(void) { 8 | int fd = open(PATH, O_RDONLY); 9 | if (fd < 0) { 10 | perror("open failed"); 11 | return EXIT_FAILURE; 12 | } 13 | return EXIT_SUCCESS; 14 | } 15 | -------------------------------------------------------------------------------- /tests/linux_binaries/test_unlink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define PATH "/tmp/test_unlink.tmp" 8 | 9 | int main(void) { 10 | int fd = open(PATH, O_CREAT | O_TRUNC | O_WRONLY, S_IRWXU); 11 | if (fd < 0) { 12 | perror("open failed"); 13 | return EXIT_FAILURE; 14 | } 15 | if (close(fd) < 0) { 16 | perror("close failed"); 17 | return EXIT_FAILURE; 18 | } 19 | if (unlink(PATH) < 0) { 20 | perror("unlink failed"); 21 | return EXIT_FAILURE; 22 | } 23 | return EXIT_SUCCESS; 24 | } 25 | -------------------------------------------------------------------------------- /tests/linux_binaries/test_write.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define PATH "/dev/null" 8 | 9 | const char *buf = "Hello World!"; 10 | 11 | int main(void) { 12 | int fd = open(PATH, O_WRONLY); 13 | if (fd < 0) { 14 | perror("open failed"); 15 | return EXIT_FAILURE; 16 | } 17 | if (write(fd, buf, strlen(buf)) < 0) { 18 | perror("write failed"); 19 | return EXIT_FAILURE; 20 | } 21 | return EXIT_SUCCESS; 22 | } 23 | -------------------------------------------------------------------------------- /tests/test_linux.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | import unittest 4 | import logging 5 | import json 6 | from layers import VMLayer 7 | from vmtest_helper import LinuxVMTestHelper 8 | 9 | class TestLinux(unittest.TestCase): 10 | domain_name = "nitro_ubuntu1604" 11 | test_helper = LinuxVMTestHelper 12 | layer = VMLayer 13 | 14 | def test_open(self): 15 | """Execute a program that invokes open system call and check that it appears in the event stream""" 16 | 17 | found = False 18 | needle = "/proc/cpuinfo" 19 | 20 | def open_hook(syscall, backend): 21 | nonlocal found 22 | process = syscall.process 23 | if process is not None and process.name == "test_open": 24 | path_addr = syscall.args[0] 25 | path = process.libvmi.read_str_va(path_addr, process.pid) 26 | logging.debug("open: %s", path) 27 | if path == needle: 28 | found = True 29 | 30 | hooks = {"open": open_hook} 31 | self.run_binary_test("test_open", hooks) 32 | self.assertTrue(found) 33 | 34 | def test_write(self): 35 | """Look for a write system call with a predetermined buffer""" 36 | 37 | last_handle = None 38 | found = False 39 | needle = b"Hello World!" 40 | 41 | # This is not really a good strategy 42 | # We cannot match this system call with the entry and connect path with its fd 43 | # Now we just hope that the process does not open anything else between this and the write call 44 | # In this case, it should be fine 45 | def open_hook(syscall, backend): 46 | nonlocal last_handle 47 | process = syscall.process 48 | if process is not None and process.name == "test_write": 49 | logging.debug("open returned: %s", syscall.event.regs.rax) 50 | last_handle = syscall.event.regs.rax 51 | 52 | def write_hook(syscall, backend): 53 | nonlocal found 54 | process = syscall.process 55 | if process is not None and process.name == "test_write": 56 | handle = syscall.args[0] 57 | buf_addr = syscall.args[1] 58 | buf_len = syscall.args[2] 59 | buf = process.libvmi.read_va(buf_addr, process.pid, buf_len) 60 | logging.debug("write (handle: %s, buffer size %d): \"%s\"", handle, buf_len, buf) 61 | if buf == needle and handle == last_handle: 62 | found = True 63 | 64 | enter_hooks = {"write": write_hook} 65 | exit_hooks = {"open": open_hook} 66 | self.run_binary_test("test_write", enter_hooks, exit_hooks) 67 | self.assertTrue(found) 68 | 69 | def test_unlink(self): 70 | """Look for unlink with predefined path name""" 71 | 72 | found = False 73 | needle = "/tmp/test_unlink.tmp" 74 | 75 | def unlink_hook(syscall, backend): 76 | nonlocal found 77 | process = syscall.process 78 | if process is not None and process.name == "test_unlink": 79 | path_addr = syscall.args[0] 80 | path = process.libvmi.read_str_va(path_addr, process.pid) 81 | logging.debug("unlink: %s", path) 82 | if path == needle: 83 | found = True 84 | 85 | hooks = {"unlink": unlink_hook} 86 | self.run_binary_test("test_unlink", hooks) 87 | self.assertTrue(found) 88 | 89 | def run_binary_test(self, binary, enter_hooks=None, exit_hooks=None): 90 | binary_path = os.path.join(self.script_dir, "linux_binaries", "build", binary) 91 | self.vm.cdrom.set_executable(binary_path) 92 | 93 | events, exec_time = self.vm.run_test(enter_hooks=enter_hooks, exit_hooks=exit_hooks) 94 | 95 | with open("{}.json".format(binary), "w") as f: 96 | json.dump(events, f, indent=4) 97 | 98 | if exec_time is not None: 99 | logging.info("Test execution time %s", exec_time) 100 | 101 | return events 102 | -------------------------------------------------------------------------------- /tests/unittest.cfg: -------------------------------------------------------------------------------- 1 | [unittest] 2 | plugins = nose2.plugins.layers 3 | nose2.plugins.attrib 4 | 5 | [layer-reporter] 6 | always-on = False 7 | colors = True 8 | 9 | -------------------------------------------------------------------------------- /tests/unittests/README.md: -------------------------------------------------------------------------------- 1 | This directory is for unit tests that do require full nitro installation. 2 | -------------------------------------------------------------------------------- /tests/unittests/resources/syscall_table_sample.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/unittests/resources/syscall_table_sample.bin -------------------------------------------------------------------------------- /tests/unittests/test_linux.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import struct 4 | import sys 5 | import unittest 6 | import logging 7 | 8 | from unittest.mock import Mock, patch 9 | 10 | # TODO: 11 | # Make sure we do not end up importing anything that might cause problems 12 | # in CI environment. 13 | 14 | # We do not want to import libvirt 15 | # Could we do this in a cleaner way? 16 | sys.modules["libvirt"] = Mock() 17 | sys.modules["nitro.backends.linux.process"] = Mock() 18 | sys.modules["nitro.backends.linux.arguments"] = Mock() 19 | 20 | # local 21 | sys.path.insert(1, os.path.realpath('../..')) 22 | from nitro.backends.linux import LinuxBackend 23 | from nitro.backends.linux.backend import clean_name as linux_clean_name 24 | from nitro.libvmi import Libvmi 25 | from nitro.event import SyscallDirection 26 | from nitro.syscall import Syscall 27 | 28 | # Mock common backend objects with some defaults 29 | 30 | def translate_v2ksym(symbol): 31 | return { 32 | 0xffffffff8120f6a0: "SyS_read", 33 | 0xffffffff8120f760: "SyS_write", 34 | 0xffffffff8120ba50: "SyS_close", 35 | 0xffffffff8120db70: "SyS_open" 36 | }[symbol] 37 | 38 | def translate_ksym2v(symbol): 39 | return { 40 | "sys_call_table": 0xc0ffee, 41 | "init_task": 0x1000 42 | }[symbol] 43 | 44 | def get_offset(symbol): 45 | return { 46 | "linux_tasks": 0x350, 47 | "linux_mm": 0x3a0, 48 | "linux_pid": 0x448, 49 | "linux_pgd": 0x40 50 | }[symbol] 51 | 52 | # Is this safe, will this work without libvmi installed? 53 | libvmi = Mock(spec=Libvmi, 54 | **{ 55 | "translate_ksym2v.side_effect": translate_ksym2v, 56 | "translate_v2ksym.side_effect": translate_v2ksym, 57 | "get_offset.side_effect": get_offset 58 | }) 59 | 60 | domain = Mock(**{ 61 | "vcpus.return_value": [[2]] 62 | }) 63 | 64 | listener = Mock() 65 | 66 | LinuxBackend.build_syscall_name_map = lambda _: {} 67 | 68 | def get_resource_path(name): 69 | return pathlib.Path(__file__).parent.joinpath("resources", name) 70 | 71 | class TestLinux(unittest.TestCase): 72 | def test_backend_creation(self): 73 | """Check that LinuxBackend can be created.""" 74 | backend = LinuxBackend(domain, libvmi, listener) 75 | 76 | # Check that the created object gets its attributes from libvmi 77 | # Not really that useful... 78 | self.assertEqual(backend.tasks_offset, get_offset("linux_tasks")) 79 | self.assertEqual(backend.mm_offset, get_offset("linux_mm")) 80 | self.assertEqual(backend.pgd_offset, get_offset("linux_pgd")) 81 | 82 | def test_syscall_name(self): 83 | """Check that syscall names can be extracted from system call table.""" 84 | backend = LinuxBackend(domain, libvmi, listener) 85 | # The sample file is extracted from the Ubuntu image used integration tests 86 | with get_resource_path("syscall_table_sample.bin").open("rb") as handle: 87 | memory = handle.read() 88 | base = translate_ksym2v("sys_call_table") 89 | def read_addr_va(addr, pid): 90 | start = addr - base 91 | return struct.unpack("P", memory[start:start+8])[0] 92 | with patch.object(backend.libvmi, "read_addr_va", side_effect=read_addr_va): 93 | self.assertEqual(backend.get_syscall_name(0), "SyS_read") 94 | self.assertEqual(backend.get_syscall_name(1), "SyS_write") 95 | self.assertEqual(backend.get_syscall_name(2), "SyS_open") 96 | self.assertEqual(backend.get_syscall_name(3), "SyS_close") 97 | 98 | def test_associate_process(self): 99 | """Test process association.""" 100 | 101 | # This is kind of silly, but I think it codifies some of the 102 | # relationships between addresses that the backend uses. 103 | # Obviously a more robust test would be desirable 104 | 105 | backend = LinuxBackend(domain, libvmi, listener) 106 | init_task = translate_ksym2v("init_task") 107 | mm_offset = get_offset("linux_mm") 108 | pgd_offset = get_offset("linux_pgd") 109 | tasks_offset = get_offset("linux_tasks") 110 | init_task_mm = 0x6060 111 | init_task_mm_pgd = 0x7070 112 | 113 | # Fake memory 114 | def read_addr_va(addr, pid): 115 | return { 116 | init_task + mm_offset: init_task_mm, # mm for init task 117 | init_task_mm + pgd_offset: init_task_mm_pgd, 118 | init_task + tasks_offset: init_task + tasks_offset 119 | }[addr] 120 | 121 | def translate_kv2p(pgd): 122 | return pgd + 0x100 123 | 124 | with patch.object(backend.libvmi, "read_addr_va", side_effect=read_addr_va), \ 125 | patch.object(backend.libvmi, "translate_kv2p", side_effect=translate_kv2p): 126 | process = backend.associate_process(init_task_mm_pgd + 0x100) 127 | self.assertIsNotNone(process) 128 | 129 | def test_check_caches_flushed(self): 130 | """Check that libvmi caches are flushed.""" 131 | backend = LinuxBackend(domain, libvmi, listener) 132 | event = Mock(direction=SyscallDirection.exit, vcpu_nb=0) 133 | 134 | with patch.object(LinuxBackend, "associate_process"), \ 135 | patch.object(LinuxBackend, "get_syscall_name", return_value="SyS_write"): 136 | backend.process_event(event) 137 | 138 | libvmi.v2pcache_flush.assert_called_once_with() 139 | libvmi.pidcache_flush.assert_called_once_with() 140 | libvmi.rvacache_flush.assert_called_once_with() 141 | libvmi.symcache_flush.assert_called_once_with() 142 | 143 | def test_process_event(self): 144 | """Test that the event handler returns a syscall object with somewhat sensible content""" 145 | backend = LinuxBackend(domain, libvmi, listener) 146 | event = Mock(direction=SyscallDirection.enter, vcpu_nb=0) 147 | 148 | with patch.object(LinuxBackend, "associate_process"), \ 149 | patch.object(LinuxBackend, "get_syscall_name", return_value="SyS_write"): 150 | syscall = backend.process_event(event) 151 | 152 | self.assertEqual(syscall.name, "write") 153 | self.assertEqual(syscall.full_name, "SyS_write") 154 | self.assertIsInstance(syscall, Syscall) 155 | 156 | def test_clean_name(self): 157 | """Test that system call handler names are properly cleaned.""" 158 | self.assertEqual(linux_clean_name("SyS_foo"), "foo") 159 | self.assertEqual(linux_clean_name("sys_bar"), "bar") 160 | 161 | 162 | -------------------------------------------------------------------------------- /tests/vm_templates/.gitignore: -------------------------------------------------------------------------------- 1 | output-qemu/ 2 | packer_cache/ 3 | -------------------------------------------------------------------------------- /tests/vm_templates/README.md: -------------------------------------------------------------------------------- 1 | # VM templates for Nitro 2 | 3 | 4 | # build 5 | 6 | Use `packer` with a `var file` 7 | ~~~ 8 | $ ./packer build --var-file= 9 | ~~~ 10 | 11 | Example for `Windows 7` 12 | ~~~ 13 | $ ./packer build --var-file=windows_7_x64.json windows.json 14 | ~~~ 15 | 16 | `var files` 17 | - `windows_7_x64.json` 18 | - `windows_8_x64.json` 19 | - `ubuntu_1604_x64.json` 20 | 21 | `templates` 22 | - `windows.json` 23 | - `ubuntu.json` 24 | 25 | # Import in libvirt 26 | 27 | Use `import_libvirt.py` to import your generated `qcow` as a defined 28 | `libvirt` domain. 29 | 30 | ~~~ 31 | # ./import_libvirt.py output_qemu/win7x64 32 | ~~~ 33 | 34 | This script will do the following actions 35 | 1. create a `nitro` pool storage in `nitro/tests/images` 36 | 2. move the qcow to from `output-qemu` to this new pool 37 | 2. define a new domain in `qemu:///system` named `nitro_` 38 | 3. remove `output-qemu` directory 39 | 40 | To specify a custom QEMU, if the `kvm-vmi` one is not installed in `/usr/bin/qemu-system-x86`, 41 | use the `--qemu` switch 42 | ~~~ 43 | ./import_libvirt.py --qemu /home/developer/kvm-vmi/qemu/x86_64-softmmu/qemu-system-x86_64 packer-windows/output-qemu/win7x64 44 | ~~~ 45 | -------------------------------------------------------------------------------- /tests/vm_templates/answer_files/ubuntu/preseed.cfg: -------------------------------------------------------------------------------- 1 | choose-mirror-bin mirror/http/proxy string 2 | d-i base-installer/kernel/override-image string linux-server 3 | d-i clock-setup/utc boolean true 4 | d-i clock-setup/utc-auto boolean true 5 | d-i finish-install/reboot_in_progress note 6 | d-i grub-installer/only_debian boolean true 7 | d-i grub-installer/with_other_os boolean true 8 | d-i partman-auto-lvm/guided_size string max 9 | d-i partman-auto/choose_recipe select atomic 10 | d-i partman-auto/method string lvm 11 | d-i partman-lvm/confirm boolean true 12 | d-i partman-lvm/confirm boolean true 13 | d-i partman-lvm/confirm_nooverwrite boolean true 14 | d-i partman-lvm/device_remove_lvm boolean true 15 | d-i partman/choose_partition select finish 16 | d-i partman/confirm boolean true 17 | d-i partman/confirm_nooverwrite boolean true 18 | d-i partman/confirm_write_new_label boolean true 19 | d-i pkgsel/include string openssh-server cryptsetup build-essential libssl-dev libreadline-dev zlib1g-dev linux-source dkms nfs-common 20 | d-i pkgsel/install-language-support boolean false 21 | d-i pkgsel/update-policy select none 22 | d-i pkgsel/upgrade select full-upgrade 23 | d-i time/zone string UTC 24 | tasksel tasksel/first multiselect standard, ubuntu-server 25 | 26 | d-i console-setup/ask_detect boolean false 27 | d-i keyboard-configuration/layoutcode string us 28 | d-i keyboard-configuration/modelcode string pc105 29 | d-i debian-installer/locale string en_US 30 | 31 | # Create vagrant user account. 32 | d-i passwd/user-fullname string vagrant 33 | d-i passwd/username string vagrant 34 | d-i passwd/user-password password vagrant 35 | d-i passwd/user-password-again password vagrant 36 | d-i user-setup/allow-password-weak boolean true 37 | d-i user-setup/encrypt-home boolean false 38 | d-i passwd/user-default-groups vagrant sudo 39 | d-i passwd/user-uid string 900 40 | -------------------------------------------------------------------------------- /tests/vm_templates/http/preseed.cfg: -------------------------------------------------------------------------------- 1 | choose-mirror-bin mirror/http/proxy string 2 | d-i debian-installer/framebuffer boolean false 3 | d-i debconf/frontend select noninteractive 4 | d-i base-installer/kernel/override-image string linux-server 5 | d-i clock-setup/utc boolean true 6 | d-i clock-setup/utc-auto boolean true 7 | d-i finish-install/reboot_in_progress note 8 | d-i grub-installer/only_debian boolean true 9 | d-i grub-installer/with_other_os boolean true 10 | d-i partman-auto/method string regular 11 | d-i partman/choose_partition select finish 12 | d-i partman/confirm boolean true 13 | d-i partman/confirm_nooverwrite boolean true 14 | d-i partman/confirm_write_new_label boolean true 15 | d-i pkgsel/include string openssh-server 16 | d-i pkgsel/install-language-support boolean false 17 | d-i pkgsel/update-policy select none 18 | d-i pkgsel/upgrade select full-upgrade 19 | d-i time/zone string UTC 20 | d-i user-setup/allow-password-weak boolean true 21 | d-i user-setup/encrypt-home boolean false 22 | tasksel tasksel/first multiselect standard, ubuntu-server 23 | -------------------------------------------------------------------------------- /tests/vm_templates/import_libvirt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Usage: 5 | import_libvirt.py [options] 6 | 7 | Options: 8 | -h --help Show this screen. 9 | --qemu= Path to custom QEMU binary 10 | --open-vnc Open VNC on all interfaces (0.0.0.0) 11 | --kvmi Use kvmi enabled domain template 12 | """ 13 | 14 | import os 15 | import sys 16 | import logging 17 | import shutil 18 | import xml.etree.ElementTree as tree 19 | 20 | import libvirt 21 | from docopt import docopt 22 | 23 | NITRO_POOL_NAME = 'nitro' 24 | PACKER_OUTPUT_DIR = 'output-qemu' 25 | SNAPSHOT_XML = """ 26 | 27 | base 28 | 29 | """ 30 | 31 | QEMU_ARGS_XML = """ 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | """ 43 | 44 | def prepare_domain_xml(domain_name, qemu_bin_path, nitro_image_path, open_vnc, enable_kvmi): 45 | with open("template_domain.xml") as templ: 46 | domain_xml = templ.read() 47 | domain_xml = domain_xml.format(domain_name=domain_name, 48 | qemu_bin_path=qemu_bin_path, 49 | nitro_image_path=nitro_image_path) 50 | root = tree.fromstring(domain_xml) 51 | if open_vnc: 52 | # search for graphics element 53 | graphics_elem = root.findall("./devices/graphics")[0] 54 | graphics_elem.attrib['listen'] = '0.0.0.0' 55 | if enable_kvmi: 56 | tree.register_namespace("qemu", "http://libvirt.org/schemas/domain/qemu/1.0") 57 | kvmi_args = tree.fromstring(QEMU_ARGS_XML) 58 | argument = kvmi_args.find("./*[2]") 59 | argument.attrib["value"] = argument.attrib["value"].format( 60 | path="/tmp/{}-introspector".format(domain_name)) 61 | root.append(kvmi_args) 62 | domain_xml = tree.tostring(root).decode() 63 | return domain_xml 64 | return None 65 | 66 | def main(args): 67 | logging.basicConfig(level=logging.DEBUG, format='%(message)s') 68 | qemu_image = args[''] 69 | open_vnc = args['--open-vnc'] 70 | kvmi_enabled = args['--kvmi'] 71 | # check root 72 | if os.geteuid() != 0: 73 | logging.critical('Must be root to run this script') 74 | sys.exit(1) 75 | con = libvirt.open('qemu:///system') 76 | script_dir = os.path.dirname(os.path.realpath(__file__)) 77 | storage_path = os.path.join(script_dir, '..', 'images') 78 | # check for storage pool nitro 79 | try: 80 | storage = con.storagePoolLookupByName(NITRO_POOL_NAME) 81 | except libvirt.libvirtError: 82 | # build nitro pool xml 83 | path_elem = tree.Element('path') 84 | path_elem.text = storage_path 85 | target_elem = tree.Element('target') 86 | target_elem.append(path_elem) 87 | name_elem = tree.Element('name') 88 | name_elem.text = NITRO_POOL_NAME 89 | pool_elem = tree.Element('pool', attrib={'type': 'dir'}) 90 | pool_elem.append(name_elem) 91 | pool_elem.append(target_elem) 92 | pool_xml = tree.tostring(pool_elem).decode('utf-8') 93 | # define it 94 | storage = con.storagePoolDefineXML(pool_xml) 95 | storage.setAutostart(True) 96 | # create dir 97 | os.makedirs(storage_path, exist_ok=True) 98 | # make sure storage is running 99 | if not storage.isActive(): 100 | storage.create() 101 | # check if domain is already defined 102 | image_name = os.path.basename(qemu_image) 103 | domain_name = 'nitro_{}'.format(image_name) 104 | try: 105 | domain = con.lookupByName(domain_name) 106 | except libvirt.libvirtError: 107 | # default system qemu 108 | qemu_bin_path = shutil.which('qemu-system-x86_64') 109 | # set custom qemu if needed 110 | if args['--qemu']: 111 | qemu_bin_path = args['--qemu'] 112 | # move image to nitro pool 113 | nitro_image_path = os.path.join(storage_path, '{}.qcow2'.format(image_name)) 114 | shutil.move(qemu_image, nitro_image_path) 115 | domain_xml = prepare_domain_xml(domain_name, qemu_bin_path, nitro_image_path, open_vnc, kvmi_enabled) 116 | con.defineXML(domain_xml) 117 | logging.info('Domain {} defined.'.format(domain_name)) 118 | domain = con.lookupByName(domain_name) 119 | # take base snapshot 120 | domain.snapshotCreateXML(SNAPSHOT_XML) 121 | # remove output-qemu 122 | output_qemu_path = os.path.join(script_dir, PACKER_OUTPUT_DIR) 123 | shutil.rmtree(output_qemu_path) 124 | else: 125 | logging.info('Domain {} already defined'.format(domain_name)) 126 | 127 | 128 | 129 | if __name__ == '__main__': 130 | args = docopt(__doc__) 131 | logging.basicConfig(level=logging.DEBUG) 132 | main(args) 133 | -------------------------------------------------------------------------------- /tests/vm_templates/linux/cdrom.rules: -------------------------------------------------------------------------------- 1 | SUBSYSTEM=="block", ENV{ID_CDROM}=="?*", ACTION=="change", RUN+="/bin/systemctl --no-block start cdrom_autoexec.service" 2 | -------------------------------------------------------------------------------- /tests/vm_templates/linux/cdrom_autoexec.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Mount and execute CDROMs 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=/usr/local/sbin/cdrom_autoexec.sh -------------------------------------------------------------------------------- /tests/vm_templates/linux/cdrom_autoexec.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | NAME="cdrom_autoexec.sh" 6 | DEVNAME="/dev/sr0" 7 | MOUNT_POINT="/mnt/cdrom" 8 | AUTOEXEC_SCRIPT="autoexec.sh" 9 | 10 | exec 1>/dev/kmsg 2>&1 11 | 12 | blkid "$DEVNAME" > /dev/null 2>&1 13 | 14 | if [[ "$?" -eq 0 ]]; then 15 | echo "${NAME}: mounting the device" 16 | mkdir -p "$MOUNT_POINT" 17 | mount -o ro "$DEVNAME" "$MOUNT_POINT" || exit 1 18 | 19 | if [[ -x "${MOUNT_POINT}/${AUTOEXEC_SCRIPT}" ]]; then 20 | echo "${NAME}: executing ${AUTOEXEC_SCRIPT} from ${MOUNT_POINT}" 21 | "${MOUNT_POINT}/${AUTOEXEC_SCRIPT}" 22 | echo "${NAME}: script finished with status code $?" 23 | fi 24 | else 25 | echo "${NAME}: unmounting the device" 26 | umount "$MOUNT_POINT" || exit 1 27 | rm -rf "$MOUNT_POINT" 28 | fi 29 | 30 | -------------------------------------------------------------------------------- /tests/vm_templates/linux/journald.conf: -------------------------------------------------------------------------------- 1 | [Journal] 2 | ForwardToKMsg=yes 3 | ForwardToConsole=yes 4 | TTYPath=/dev/ttyS0 5 | MaxLevelKMsg=debug 6 | MaxLevelConsole=debug 7 | -------------------------------------------------------------------------------- /tests/vm_templates/linux/rc.local: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | dmesg -n 8 4 | echo 12000 > /sys/block/sr0/device/timeout 5 | 6 | exit 0 7 | -------------------------------------------------------------------------------- /tests/vm_templates/packer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KVM-VMI/nitro/53427a68d981bef68bdbdb5631fa192fa7137f94/tests/vm_templates/packer -------------------------------------------------------------------------------- /tests/vm_templates/template_domain.xml: -------------------------------------------------------------------------------- 1 | 2 | {domain_name} 3 | 1500 4 | 1 5 | 6 | hvm 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | destroy 20 | restart 21 | restart 22 | 23 | 24 | 25 | 26 | 27 | {qemu_bin_path} 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 41 | 42 |
43 | 44 | 45 | 46 |
47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 |
59 | 60 | 61 |
62 | 63 | 64 | 65 | 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 |
84 | 85 |