# Emulating NotPetya bootloader with Miasm **[aguinet.github.io//blog/2020/08/29/miasm-bootloader.html](https://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html)** 29 Aug 2020 in Blog NotPetya is a famous malware of the Petya family. It appeared in June 2017. The part running from the Master Boot Record (MBR) has been statically and dynamically studied, using for instance the Bochs debugger from IDA. Is another approach possible? This article’s goal is to show that we can emulate this bootloader using Miasm. _[This article has been written by Jordan Bouyat (@la_F0uin3) and](https://twitter.com/la_F0uin3)_ _Adrien Guinet_ _[(@adriengnt). It has originally been published in the MISC magazine n°98 in French. This is](https://connect.ed-diamond.com/MISC/MISC-098/Emulation-du-bootloader-de-NotPetya-avec-Miasm)_ _a slightly updated and English translation of this article._ ## Introduction This Petya variant first appeared in June 2017 in Ukraine. According to Mikko Hyppönen, Chief Research Officer at F-Secure, the infection vector would be the update mechanism of the accountability software M.E.Doc, widely deployed within Eastern countries. This malware family has the particularity of overwriting the bootloader of the compromised machine in order to encrypt parts of the hard drive when it reboots. This article uses this bootloader as a pretext for a tutorial concerning the emulation and reverse engineering of these little beasts thanks to the Miasm framework. The associated code is available here: [https://github.com/aguinet/miasm-bootloader/. It contains a Python implementation of a](https://github.com/aguinet/miasm-bootloader/) subset of the interfaces of a classic x86 PC BIOS. The code was written in a way that is easily reusable for other cases, or even to help the development / debugging of bootloaders in general. ## Related works Many articles have already studied the behaviour of the NotPetya MBR, as well as its various cryptographic implementations and mechanisms (and their faults). Here are some significant ones: MISC n°86 : “Pleased to meet you, my name is Petya !”, written by Damien Schaeffer in July 2016 MISC n°93 : “Petya or Not Petya, that is the question”, written by “Teddy and Benjamin” in September 2017, with a very thorough static reverse engineering of the bootloader. [Crowdstrike : Full Decryption of Systems Encrypted by Petya/NotPetya. Study of an](https://www.crowdstrike.com/blog/full-decryption-systems-encrypted-petya-notpetya/) implementation error within the Salsa20 algorithm embedded in the bootloader (more on that subject later in the article). ----- ## NotPetya This section deals only in a very general way with the malware’s cycle of life. It allows us to highlight the part studied in this article. Once NotPetya has run on the victim’s machine, it generates an AES encryption key that will be used to achieve the first encryption phase. This key is itself encrypted with an RSA public key. The malware then checks that the system uses a classical partition scheme and, if it has admin rights, enters its own data on the first sectors of the disc (from 1 to 18, then 32 to 34), with its own MBR in the first sector. If the system uses UEFI (with a GPT partition scheme), the malware skips this step. The machine then is restarted and the NotPetya bootloader executed: a Salsa20 key and a nonce are generated. These secrets are used to encrypt the Master File Table (MFT) 1 of NTFS file system. This data structure contains the metadata needed to find the data associated with each file. This operation looks like a classical “chkdsk”. Once this operation is done, the machine restarts one last time and then displays the ransom message. ## Miasm Miasm is a reverse engineering framework developed in Python. It has many features, among which: open, modify and generate binaries in PE, ELF 32, 64 LE, BE. assemble/disassemble x86, ARM, MIPS, SH4, PPC and MSP430 code. lift instruction semantics into a custom intermediate representation. emulate this intermediate representation, with various JIT (Just-in-time) compilers to speed things up. simplify/modify this intermediate representation, to de-obfuscate code for instance. ## Why emulate NotPetya with Miasm? ----- There are various ways to emulate a bootloader. A classical approach is to use QEMU (or any other virtualization/emulation solution) by writing the bootloader on a virtual hard disk, but it makes it difficult to instrument the bootloader code. Such a thing is however possible via IDA’s Bochs debugger. This approach was adopted by Teddy and Benjamin in MISC No. 93, but also by Saurabh Sharma 2. This method usually works well and makes debugging a bootloader an easy task. In the article associated with the presentation of his Miasm tool at SSTIC in 2012 3, Fabrice Desclaux showed Miasm possibilities. One of the proposed applications was the emulation of a bootloader. The ability to fully emulate a bootloader (until the BIOS interruption) with a framework like Miasm gives a sharper control over what’s happening, possibly allow de-obfuscation, and use all the tools developed in Miasm for this task. It becomes for example very simple to instrument the code in order to see the data read/written on the disk, the secrets generated, etc. Eventually, NotPetya’s bootloader code is succinct, non-obfuscated and extremely simple (it runs in real mode, in 16 bits and calls only a few BIOS interruptions), so it is a nice case study to play with Miasm! ## PC/x86 bootloader Introduction We will only discuss here the inner workings of “old-school” BIOS bootloaders. We will not talk about UEFI ----- On x86 PCs, when the machine starts, the BIOS loads the first disk sector (named Master Boot Record, or MBR) at `0x7C00, and then jumps to this address. The MBR hence` contains the bootloader code. At this moment, the processor only supports 16-bit instructions and can only address memory in real mode 4. As a reminder, one disk sector contains 512 bytes. Therefore, it is not possible to store a lot of code on this sector only. That’s why bootloaders are usually designed in several stages. Indeed, the code in the first sector (the first stage) will load the stage 2 code from the hard drive, and then jump into it. Below is the MBR’s structure written by NotPetya: ## NotPetya case NotPetya works exactly this way. The bootstrap code (in green in the figure above) is the assembly code below. The code at address `0x7C38 (that we named` `disk_read_stage2 ),` writes data in sectors 2 to 34 (inclusive) in memory at `0x8000, and then jumps to this` address: ----- ``` seg000:7C00 cli seg000:7C01 xor ax, ax seg000:7C03 mov ds, ax seg000:7C05 mov ss, ax seg000:7C07 mov es, ax seg000:7C09 lea sp, start seg000:7C0D sti seg000:7C0E mov eax, 32 seg000:7C14 mov byte ptr ds:word_7C93, dl seg000:7C18 mov ebx, 1 seg000:7C1E mov cx, 8000h seg000:7C21 call disk_read_stage2 seg000:7C24 dec eax seg000:7C26 cmp eax, 0 seg000:7C2A jnz short loc_7C21 seg000:7C2C mov eax, dword ptr ds:8000h seg000:7C30 jmp far ptr 0:8000h ## Emulation with Miasm Installation ``` The system used for these tests is Linux-based. Windows 10 users should be able to make it work by using Windows Subsystem for Linux (WSL), by installing for example Ubuntu using the Windows Store .5 We recommend using the version of Miasm specified in the `README file from the GitHub` repository. At the time of writing lines, the version used is v0.1.1. To recover this specific version, do: ``` $ git clone --depth=1 --branch=v0.1.1 https://github.com/cea-sec/miasm/ ``` We use the LLVM-based Miasm JIT engine, which needs the `llvmlite python package.` Other needed dependencies are installable directly through the provided ``` requirements.txt file: $ cd /path/to/src && pip install -r requirements.txt ``` Then just install Miasm: ``` $ cd /path/to/miasm && pip install -r requirements.txt && python ./setup.py install ## Implementation ``` All the techniques described in this article can be tried thanks to the `src/emulate_mbr.py` script in the aforementioned GitHub repository. Multiple options are provided, some of them could be used to win some time during your experiments: ----- ``` --dry : simulates the success of disk writings, but actually writes nothing. --skip-encryption : the encryption function (which is the hottest one) will be ignored ``` (actually transforming it into a function that does nothing). ``` --verbose-bios-data : dumps log messages from our BIOS implementation, with a ``` dump of read and written disk data. ``` --verbose-bios : same as --verbose-bios-data, without the read and written ``` disk data. The `--help flag can be used to have a more detailed list of available options.` The activation of Miasm’s logs can considerably slow down the performances of the overall script. The `--log-miasm-newblocks option only dumps blocks that have never been` “jitted” by Miasm. ## Creation of a test disk We performed our tests with virtual machines running Windows XP and Windows 10. The underlying hypervisor does not matter (VMWare, VirtualBox), as long as the disk created has a fixed size and is using VMDK. The emulation of the bootloader is done directly on the virtual machine’s disk. An advantage to this method is that there is no need to extract the bootloader from the original malware DLL or from the generated VMDK. Emulation scenario The test scenario is the following: 1. voluntary infection of the virtual machine with NotPetya 2. wait for at least 10s (the machine shouldn’t reboot by itself, or the bootloader will actually launch its encryption code) 3. shutdown the virtual machine: the MBR has been replaced 4. run the emulation: the MFT is encrypted by the bootloader which then displays the ransom ----- If your virtual machine is not using a flat VMDK representation, you can convert it using QEMU: ``` $ qemu-img convert -f vmdk mydisk.vmdk -O raw mydisk.raw ``` We also give a test image in the aforementioned Git repository (file `disk.raw.bz2 ). Once` unzipped, it is a 1GB file, and contains a simple NTFS partition with some test files. We can now emulate the NotPetya bootloader. In order to do this, we need to emulate a BIOS capable of: reading/writing disk sectors showing characters on the screen capturing key strokes booting on an MBR (“light” boot/reboot) We are going to see how to implement this using Miasm. ## System abstraction We implement an abstraction of a simple system as seen by the BIOS. It contains: a virtual disk (the `HardDrive class)` a video screen, which goes through a classical Unix terminal, using the `stdout pipe` a keyboard, which uses the `stdin pipe to gather key strokes (functions in` ``` async_kb.py ) ``` Abstraction is implemented in the `System class, of which one instance is used during the` emulation. This instance is initialized alongside the Miasm VM. ## Miasm virtual machine initialization As explained in the introduction, the MBR code is loaded and executed by the BIOS at the address `0x7C00 . Then, this code will load and write its second stage at the address` ``` 0x8000 . The left space is dedicated to the stack. It begins at the address 0x500 and ends ``` at the address `0x07C00 . Therefore, the corresponding space is` ``` [0x00000500:0x00007BFF] . ``` First, we need to declare these memory spaces to the Miasm virtual machine: ----- ``` HD0 HardDrive(hd_path) sys_ = System([HD0]) mbr = HD0.read_sector(0) stage1_addr = 0x07C00 stage2_addr = 0x08000 jitter.vm.add_memory_page(stage1_addr, PAGE_READ | PAGE_WRITE | PAGE_EXEC, mbr, "NotPetyaS1") jitter.vm.add_memory_page(stage2_addr, PAGE_READ | PAGE_WRITE | PAGE_EXEC, "\x00"*SECTOR_LEN*32, "NotPetyaS2") jitter.vm.add_memory_page(0x500, PAGE_READ | PAGE_WRITE, "\x00"*(0x7C00-0x500+1), "Stack") # Pretty print of the memory layout print(jitter.vm) ``` Now, the memory layout of the Miasm virtual machine is the following: ``` Addr Size Access Comment 0x500 0x7700 RW_ Stack 0x7C00 0x200 RWX NotPetyaS1 0x8000 0x4000 RWX NotPetyaS2 ``` NotPetya loads 32 sectors from the disk to the memory when executing the first stage. This is why the allocated memory for the second stage is 32 sectors long (32*512 bytes). ## BIOS interruption handling in Miasm Miasm allows us to specify an interruption handler which will be called whenever an `INT` instruction is executed. To do so, we have to tell Miasm to call our BIOS interruption handler ``` exception_int with the help of add_exception_handler of the current used jitter: jitter.add_exception_handler(EXCEPT_INT_XX, lambda jitter: exception_int(jitter, sys_)) ## Interruption support ``` Now, we have to implement the different BIOS interruption handlers. We can split them into four main families : ``` INT 10h : access to the screen (write characters, change colors…), INT 13h : access to the disk (read/write sectors, get disk geometry…), INT 16h : access to the keyboard (read keystroke…), INT 19h : boot on the disk’s MBR. ### INT 13h ``` Here is an example of the `INT 13h interruption, with the` `0x43 code function ( Extended` ``` Read Sectors From Drive ). This code implements the instruction to load multiple sectors ``` from the disk to the memory: ----- ``` @func(disk_interrupts, 0x42) def extended_read_sectors(jitter, sys_): drive_idx = get_xl(jitter.cpu.DX) print "Extended read sectors, drive idx 0x%x" % drive_idx dap = jitter.vm.get_mem((jitter.cpu.DS << 4)+jitter.cpu.SI, 16) dap_size, _, num_sect, buff_addr, abs_sect = struct.unpack(">sys.stderr, "\n[+] Looking for key %s in memory..." % key.encode("hex") for addr,v in mem.iteritems(): idx = v['data'].find(key) if idx == -1: continue print >>sys.stderr, "[+] Key found at address %s!" % hex(addr + idx) break else: print >>sys.stderr, "[-] Key not found in memory!" return True ``` This operation can be activated in the script using `--hook=find_key option, like this:` ``` $ python ./emulate-mbr.py --hook=find_key disk.raw Repairing file system on C: [... encryption happends here ...] [+] Looking for key [your salsa20 key] in memory... [+] Key found at address 0x674a! ``` To speed up the process, the `--skip-encryption option can be used (see` Implementation). Be careful, even if this option is used, the encryption flag in sector 32 is still set. The flag `--dry prevents this behaviour.` Because we know the address where the key is stored ( 0x674A ), we can put a breakpoint on a write access at this location, allowing us to know which part of the bootloader writes this key: ----- ``` def print_ip(jitter): print(hex(jitter.pc)) return False jitter.exceptions_handler.callbacks[EXCEPT_BREAKPOINT_MEMORY] = [] jitter.add_exception_handler(EXCEPT_BREAKPOINT_MEMORY, print_ip) jitter.vm.add_memory_breakpoint(0x674a, 1, PAGE_WRITE) ``` Because there is no ASLR or equivalent mechanism, this address will always be the same! ## Bootloader modification to decrypt the MFT If we have a mechanism to write directly into the memory of the machine (for example by using a PCI Express card 8, or other interfaces like FireWire or Thunderbolt 9), it is possible to decrypt the MFT data. The attack consists in patching the bootloader memory so that its uses the remaining key on the stack. This section simulates this attack using Miasm. To do so, we will inject some code at address `0x82A8 . This function checks that the key` entered is the expected one. Given that it has been erased from the hard drive, and that the ransom text is completely random 10, the bootloader has in theory no way to know if the entered key is the right one. This function will always return 0 (incorrect key). The injected code will copy the key Salsa20 from the `0x674A address to a specific location on the stack,` so that the decryption function at `0x835A will use this key. We will then jump on this` function. Associated assembly code is the following: ``` ; Save registers on the stack PUSHA LEA DI, WORD PTR [BP-0x44] LEA BX, WORD PTR [key_addr] XOR CX,CX ; Copy the key that remains on the stack to [bp-0x44] loop: MOV EAX, DWORD PTR [BX] MOV DWORD PTR [DI], EAX ADD DI, 4 ADD BX, 4 INC CX CMP CX,8 JNZ loop ; Restore previously saved registers POPA ; Jump on the decryption function (CS:OFFSET => using an absolute address) JMP 0000:0x835A ``` We use Miasm to assemble it using the following function: ----- ``` def asm_shellcode(asm, labels None): machine = Machine("x86_16") symbol_pool = asmblock.AsmSymbolPool() # Assemble blocks, symbol_pool = parse_asm.parse_txt(machine.mn, 16, asm, symbol_pool) # Set custom labels if not labels is None: for name,value in labels.iteritems(): sym = symbol_pool.getby_name(name) symbol_pool.set_offset(sym, value) # Resolve all the labels patches = asmblock.asm_resolve_final(machine.mn, blocks, symbol_pool) # Patch the final code with the label values shellcode = StrPatchwork() for offset, raw in patches.items(): shellcode[offset] = raw return str(shellcode) ``` Let’s take a look at this function. The code is first assembled using an x86 16-bit assembler. Given labels are then associated to concrete values using the `symbol_pool.set_offset` function. Remaining labels (in our case `loop ) are resolved with the` ``` asmblock.asm_resolve_final function, which returns assembly code for each block. We ``` finally use the `StrPatchwork function to assemble the final “shellcode”.` The `read_key_and_patch function loads the key in memory, dumps it and writes the` freshly assembled code in memory: ``` def read_key_and_patch(jitter): # Key is still in the stack, at 0x674A. You can find this value by activating the # find_key_in_mem breakpoint! key_addr = 0x674A key = jitter.vm.get_mem(key_addr, 32) print >>sys.stderr, "\n[+] Key from memory: %s" % key.encode("hex") # Assemble our "shellcode" thanks to Miasm! shellcode = """ ... """ shellcode = asm_shellcode(shellcode, {"key_addr": key_addr}) # Patch the bootloader in memory to decrypt using the key jitter.vm.set_mem(0x82A8, shellcode) return True ``` The remaining thing to do is to put a breakpoint at the same address as in section Retrieving secrets in memory ( 0x85AF ) to call this function: ----- ``` jitter.add_breakpoint(0x85AF, read_key_and_patch) ``` Everything is now set up. When the bootloader asks for the decryption key, user will just have to press `enter .` `--hook=patch_bootloader flag of the` `emulate_mbr script` performs this attack. It is worth mentioning that [we actually tried this at](https://airbus-seclab.github.io/ilo/Whitepaper-Defeating_NotPetya_from_your_iLO4-guinet-perigaud-gazet-czarny.pdf) [Synacktiv’s headquarters using](https://synacktiv.com/) vulnerabilities in HP’s iLO4 to gather the Salsa20 key from memory, patch the bootloader and decrypt MFT data. ## Encryption keystream study The encryption algorithm used is Salsa20 stream cipher. The general principle is: a random data flow based on a key - commonly called the keystream - is generated, and this stream is XORed with the data which will be encrypted. An advantage of stream ciphers is that the data to be encrypted do not need to be padded. On the other hand, one needs to be careful not to use the same parts of this stream twice. Data encryption with Salsa20 We can verify this using Miasm, by looking at the data before and after encryption, and by showing their XOR difference. In order to do this, we already know how to put breakpoints. The beginning of the encryption function is at address `0x9798, and the end at address` `0x9877 . We are going to out the` first breakpoint just after ‘enter instruction, and the second just before leave` statement, in order to have the stack properly aligned to recover data before and after encryption. The associated code is the following: ----- ``` _last_buf None def encrypt_start(jitter, options): global _last_buf buf_ptr = upck16(jitter.vm.get_mem((jitter.cpu.SS << 4) + jitter.cpu.BP + 0xC, 2)) buf_size = upck16(jitter.vm.get_mem((jitter.cpu.SS << 4) + jitter.cpu.BP + 0xE, 2)) _last_buf = jitter.vm.get_mem((jitter.cpu.DS << 4) + buf_ptr, buf_size) return True def encrypt_end(jitter, options): global _last_buf buf_ptr = upck16(jitter.vm.get_mem((jitter.cpu.SS << 4) + jitter.cpu.BP + 0xC, 2)) buf_size = upck16(jitter.vm.get_mem((jitter.cpu.SS << 4) + jitter.cpu.BP + 0xE, 2)) encr_buf = jitter.vm.get_mem((jitter.cpu.DS << 4) + buf_ptr, buf_size) keystream = ''.join(chr(ord(a)^ord(b)) for a,b in zip(_last_buf,encr_buf)).encode("hex") keystream = ' '.join(keystream[i:i+4] for i in xrange(0,len(keystream),4)) print >>sys.stderr, "Keystream for next 2 sectors: %s" % keystream return True jitter.add_breakpoint(0x979C, functools.partial(encrypt_start, options=options)) jitter.add_breakpoint(0x9876, functools.partial(encrypt_end, options=options)) ``` The `--dump-keystream flag of the` `emulate_mbr script enables this.` By looking at the output, we can see that between two sectors ( 2*512 bytes), the keystream is only shifted by two bytes, instead of the normally required `2*512 bytes. This` shift is schematized in the image below: We can also see that on a screenshot of the output of the `emulate_mbr script below:` ----- Screenshot of the keystream Thus, parts of the keystream are reused between sectors, which may help to recover some of its original data. Indeed, if we consider `p to be the clear text, k the keystream and` `c the encrypted text,` then the encryption function `E is defined as E (p) = p xor k = c . A part of the MFT` structures being invariant and known, it is therefore possible, in two sectors, to find part of the keystream used for these two sectors. This one is reused for the two following sectors by being simply shifted by two bytes, so some of the clear text from these other areas can be found. This vulnerability in the Salsa20 implementation of the bootloader has been exploited by [CrowdStrike to recover a large portion of MFT’s original data (between 98.10% and 99.97%](https://www.crowdstrike.com/blog/full-decryption-systems-encrypted-petya-notpetya/) depending on the method). ## Conclusion Emulation of the NotPetya bootloader code allows the verification of various assumptions and the understanding, in a very tangible way, of the different steps related to the encryption of MFT entries. In addition, it allows to easily find the bias in the Salsa20 keystream implementation (without having to statically reverse the algorithm), or to simulate the recovery of the key, which remains in memory after the encryption. This article only shows a small subset of Miasm’s possibilities, and we hope that the approach adopted in this article will encourage uninitiated readers to try and play with it :). ## Acknowledgments ----- We would like to thank gapz for his initial encouragement. Big thanks also to Camille Mougey and Fabrice Desclaux for their help and thorough reviews of this article! Thanks to Thomas Chauchefoin and zerk for their comments, and to Yseult for her help with the English translation. ## Appendix: application using vulnerabilities in HP iLO 4 [With Alexandre Gazet & Fabien Perigaud, we spent some time in Synacktiv’s offices to](https://twitter.com/0xf4b) [combine the attacks described in this article with vulnerabilities they found with](https://github.com/airbus-seclab/ilo4_toolbox) Joffrey Czarny on HP iLO 4’s management engine. These vulnerabilities allowed us to read and write the memory of an infected server stuck at NotPetya’s bootloader stage, so that we were able to recover the encryption key and patch the bootloader in order to decrypt the MFT. [A full write up of the experiment can be read on Airbus seclab website.](https://airbus-seclab.github.io/ilo/Whitepaper-Defeating_NotPetya_from_your_iLO4-guinet-perigaud-gazet-czarny.pdf) [1. https://fr.wikipedia.org/wiki/Master_File_Table](https://fr.wikipedia.org/wiki/Master_File_Table) ↩ 2. https://shasaurabh.blogspot.fr/2017/07/debugging-mbr-ida-pro-and-bochs emulator.html ↩ [3. https://www.sstic.org/2012/presentation/miasm_framework_de_reverse_engineering/](https://www.sstic.org/2012/presentation/miasm_framework_de_reverse_engineering/) ↩ [4. https://fr.wikipedia.org/wiki/Mode_r%C3%A9el](https://fr.wikipedia.org/wiki/Mode_r%C3%A9el) ↩ [5. https://docs.microsoft.com/en-us/windows/wsl/install-win10](https://docs.microsoft.com/en-us/windows/wsl/install-win10) ↩ [6. https://wiki.osdev.org/ATA_in_x86_RealMode_(BIOS))](https://wiki.osdev.org/ATA_in_x86_RealMode_(BIOS))) ↩ [7. http://webpages.charter.net/danrollins/techhelp/0243.HTM](http://webpages.charter.net/danrollins/techhelp/0243.HTM) ↩ 8. https://www.sstic.org/media/SSTIC2011/SSTIC-actes/attaques_dma_peer-to [peer_et_contremesures/SSTIC2011-Article-attaques_dma_peer-to-](https://www.sstic.org/media/SSTIC2011/SSTIC-actes/attaques_dma_peer-to-peer_et_contremesures/SSTIC2011-Article-attaques_dma_peer-to-peer_et_contremesures-lone-sang_duflot_nicomette_deswarte.pdf) peer_et_contremesures-lone-sang_duflot_nicomette_deswarte.pdf ↩ [9. https://github.com/carmaa/inception](https://github.com/carmaa/inception) ↩ 10. https://www.crowdstrike.com/blog/petrwrap-ransomware-technical-analysis-triple threat-file-encryption-mft-encryption-credential-theft/ ↩ -----