{
	"id": "287a1859-3836-4bec-b1ec-fec808f0e447",
	"created_at": "2026-04-10T03:20:26.080649Z",
	"updated_at": "2026-04-10T03:22:17.246033Z",
	"deleted_at": null,
	"sha1_hash": "cef445a22ce4637c0ef2a4ba2947e36ef3420168",
	"title": "Emulating NotPetya bootloader with Miasm |",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 943646,
	"plain_text": "Emulating NotPetya bootloader with Miasm |\r\nPublished: 2020-08-28 · Archived: 2026-04-10 02:35:45 UTC\r\n29 Aug 2020 in Blog\r\nNotPetya is a famous malware of the Petya family. It appeared in June 2017. The part running from the Master\r\nBoot Record (MBR) has been statically and dynamically studied, using for instance the Bochs debugger from\r\nIDA. Is another approach possible? This article’s goal is to show that we can emulate this bootloader using\r\nMiasm.\r\nThis article has been written by Jordan Bouyat (@la_F0uin3) and Adrien Guinet (@adriengnt). It has originally\r\nbeen published in the MISC magazine n°98 in French. This is a slightly updated and English translation of this\r\narticle.\r\nIntroduction Permalink\r\nThis Petya variant first appeared in June 2017 in Ukraine. According to Mikko Hyppönen, Chief Research Officer\r\nat F-Secure, the infection vector would be the update mechanism of the accountability software M.E.Doc, widely\r\ndeployed within Eastern countries.\r\nThis malware family has the particularity of overwriting the bootloader of the compromised machine in order to\r\nencrypt parts of the hard drive when it reboots. This article uses this bootloader as a pretext for a tutorial\r\nconcerning the emulation and reverse engineering of these little beasts thanks to the Miasm framework. The\r\nassociated code is available here: https://github.com/aguinet/miasm-bootloader/. It contains a Python\r\nimplementation of a subset of the interfaces of a classic x86 PC BIOS. The code was written in a way that is easily\r\nreusable for other cases, or even to help the development / debugging of bootloaders in general.\r\nMany articles have already studied the behaviour of the NotPetya MBR, as well as its various cryptographic\r\nimplementations and mechanisms (and their faults). Here are some significant ones:\r\nMISC n°86 : “Pleased to meet you, my name is Petya !”, written by Damien Schaeffer in July 2016\r\nMISC n°93 : “Petya or Not Petya, that is the question”, written by “Teddy and Benjamin” in September\r\n2017, with a very thorough static reverse engineering of the bootloader.\r\nCrowdstrike : Full Decryption of Systems Encrypted by Petya/NotPetya. Study of an implementation error\r\nwithin the Salsa20 algorithm embedded in the bootloader (more on that subject later in the article).\r\nNotPetya Permalink\r\nThis section deals only in a very general way with the malware’s cycle of life. It allows us to highlight the part\r\nstudied in this article.\r\nOnce NotPetya has run on the victim’s machine, it generates an AES encryption key that will be used to achieve\r\nthe first encryption phase. This key is itself encrypted with an RSA public key.\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 1 of 19\n\nThe malware then checks that the system uses a classical partition scheme and, if it has admin rights, enters its\r\nown 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\r\nsystem uses UEFI (with a GPT partition scheme), the malware skips this step. The machine then is restarted and\r\nthe NotPetya bootloader executed: a Salsa20 key and a nonce are generated. These secrets are used to encrypt the\r\nMaster File Table (MFT) 1 of NTFS file system. This data structure contains the metadata needed to find the data\r\nassociated with each file. This operation looks like a classical “chkdsk”. Once this operation is done, the machine\r\nrestarts one last time and then displays the ransom message.\r\nMiasm Permalink\r\nMiasm is a reverse engineering framework developed in Python. It has many features, among which:\r\nopen, modify and generate binaries in PE, ELF 32, 64 LE, BE.\r\nassemble/disassemble x86, ARM, MIPS, SH4, PPC and MSP430 code.\r\nlift instruction semantics into a custom intermediate representation.\r\nemulate this intermediate representation, with various JIT (Just-in-time) compilers to speed things up.\r\nsimplify/modify this intermediate representation, to de-obfuscate code for instance.\r\nWhy emulate NotPetya with Miasm? Permalink\r\nThere are various ways to emulate a bootloader. A classical approach is to use QEMU (or any other\r\nvirtualization/emulation solution) by writing the bootloader on a virtual hard disk, but it makes it difficult to\r\ninstrument the bootloader code. Such a thing is however possible via IDA’s Bochs debugger. This approach was\r\nadopted by Teddy and Benjamin in MISC No. 93, but also by Saurabh Sharma 2. This method usually works well\r\nand makes debugging a bootloader an easy task.\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 2 of 19\n\nIn the article associated with the presentation of his Miasm tool at SSTIC in 2012 3\r\n, Fabrice Desclaux showed\r\nMiasm possibilities. One of the proposed applications was the emulation of a bootloader.\r\nThe ability to fully emulate a bootloader (until the BIOS interruption) with a framework like Miasm gives a\r\nsharper control over what’s happening, possibly allow de-obfuscation, and use all the tools developed in Miasm\r\nfor this task. It becomes for example very simple to instrument the code in order to see the data read/written on the\r\ndisk, the secrets generated, etc.\r\nEventually, NotPetya’s bootloader code is succinct, non-obfuscated and extremely simple (it runs in real mode, in\r\n16 bits and calls only a few BIOS interruptions), so it is a nice case study to play with Miasm!\r\nPC/x86 bootloader Permalink\r\nIntroduction Permalink\r\nWe will only discuss here the inner workings of “old-school” BIOS bootloaders. We will not talk about UEFI.\r\nOn x86 PCs, when the machine starts, the BIOS loads the first disk sector (named Master Boot Record, or MBR)\r\nat 0x7C00 , and then jumps to this address. The MBR hence contains the bootloader code. At this moment, the\r\nprocessor only supports 16-bit instructions and can only address memory in real mode 4.\r\nAs a reminder, one disk sector contains 512 bytes. Therefore, it is not possible to store a lot of code on this sector\r\nonly. That’s why bootloaders are usually designed in several stages. Indeed, the code in the first sector (the first\r\nstage) will load the stage 2 code from the hard drive , and then jump into it.\r\nBelow is the MBR’s structure written by NotPetya:\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 3 of 19\n\nNotPetya case Permalink\r\nNotPetya works exactly this way. The bootstrap code (in green in the figure above) is the assembly code below.\r\nThe code at address 0x7C38 (that we named disk_read_stage2 ), writes data in sectors 2 to 34 (inclusive) in\r\nmemory at 0x8000 , and then jumps to this address:\r\nseg000:7C00 cli\r\nseg000:7C01 xor ax, ax\r\nseg000:7C03 mov ds, ax\r\nseg000:7C05 mov ss, ax\r\nseg000:7C07 mov es, ax\r\nseg000:7C09 lea sp, start\r\nseg000:7C0D sti\r\nseg000:7C0E mov eax, 32\r\nseg000:7C14 mov byte ptr ds:word_7C93, dl\r\nseg000:7C18 mov ebx, 1\r\nseg000:7C1E mov cx, 8000h\r\nseg000:7C21 call disk_read_stage2\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 4 of 19\n\nseg000:7C24 dec eax\r\nseg000:7C26 cmp eax, 0\r\nseg000:7C2A jnz short loc_7C21\r\nseg000:7C2C mov eax, dword ptr ds:8000h\r\nseg000:7C30 jmp far ptr 0:8000h\r\nEmulation with Miasm Permalink\r\nInstallation Permalink\r\nThe system used for these tests is Linux-based. Windows 10 users should be able to make it work by using\r\nWindows Subsystem for Linux (WSL), by installing for example Ubuntu using the Windows Store 5.\r\nWe recommend using the version of Miasm specified in the README file from the GitHub repository. At the time\r\nof writing lines, the version used is v0.1.1. To recover this specific version, do:\r\n$ git clone --depth=1 --branch=v0.1.1 https://github.com/cea-sec/miasm/\r\nWe use the LLVM-based Miasm JIT engine, which needs the llvmlite python package. Other needed\r\ndependencies are installable directly through the provided requirements.txt file:\r\n$ cd /path/to/src \u0026\u0026 pip install -r requirements.txt\r\nThen just install Miasm:\r\n$ cd /path/to/miasm \u0026\u0026 pip install -r requirements.txt \u0026\u0026 python ./setup.py install\r\nImplementation Permalink\r\nAll the techniques described in this article can be tried thanks to the src/emulate_mbr.py script in the\r\naforementioned GitHub repository.\r\nMultiple options are provided, some of them could be used to win some time during your experiments:\r\n--dry : simulates the success of disk writings, but actually writes nothing.\r\n--skip-encryption : the encryption function (which is the hottest one) will be ignored (actually\r\ntransforming it into a function that does nothing).\r\n--verbose-bios-data : dumps log messages from our BIOS implementation, with a dump of read and\r\nwritten disk data.\r\n--verbose-bios : same as --verbose-bios-data , without the read and written disk data.\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 5 of 19\n\nThe --help flag can be used to have a more detailed list of available options.\r\nThe 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.\r\nCreation of a test disk Permalink\r\nWe performed our tests with virtual machines running Windows XP and Windows 10. The underlying hypervisor\r\ndoes not matter (VMWare, VirtualBox), as long as the disk created has a fixed size and is using VMDK. The\r\nemulation of the bootloader is done directly on the virtual machine’s disk. An advantage to this method is that\r\nthere is no need to extract the bootloader from the original malware DLL or from the generated VMDK.\r\n Emulation scenario\r\nThe test scenario is the following:\r\n1. voluntary infection of the virtual machine with NotPetya\r\n2. wait for at least 10s (the machine shouldn’t reboot by itself, or the bootloader will actually launch its\r\nencryption code)\r\n3. shutdown the virtual machine: the MBR has been replaced\r\n4. run the emulation: the MFT is encrypted by the bootloader which then displays the ransom\r\nIf your virtual machine is not using a flat VMDK representation, you can convert it using QEMU:\r\n$ qemu-img convert -f vmdk mydisk.vmdk -O raw mydisk.raw\r\nWe also give a test image in the aforementioned Git repository (file disk.raw.bz2 ). Once unzipped, it is a 1GB\r\nfile, and contains a simple NTFS partition with some test files.\r\nWe can now emulate the NotPetya bootloader. In order to do this, we need to emulate a BIOS capable of:\r\nreading/writing disk sectors\r\nshowing characters on the screen\r\ncapturing key strokes\r\nbooting on an MBR (“light” boot/reboot)\r\nWe are going to see how to implement this using Miasm.\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 6 of 19\n\nSystem abstraction Permalink\r\nWe implement an abstraction of a simple system as seen by the BIOS. It contains:\r\na virtual disk (the HardDrive class)\r\na video screen, which goes through a classical Unix terminal, using the stdout pipe\r\na keyboard, which uses the stdin pipe to gather key strokes (functions in async_kb.py )\r\nAbstraction is implemented in the System class, of which one instance is used during the emulation. This\r\ninstance is initialized alongside the Miasm VM.\r\nMiasm virtual machine initialization Permalink\r\nAs explained in the introduction, the MBR code is loaded and executed by the BIOS at the address 0x7C00 .\r\nThen, this code will load and write its second stage at the address 0x8000 . The left space is dedicated to the\r\nstack. It begins at the address 0x500 and ends at the address 0x07C00 . Therefore, the corresponding space is\r\n[0x00000500:0x00007BFF] .\r\nFirst, we need to declare these memory spaces to the Miasm virtual machine:\r\nHD0 = HardDrive(hd_path)\r\nsys_ = System([HD0])\r\nmbr = HD0.read_sector(0)\r\nstage1_addr = 0x07C00\r\nstage2_addr = 0x08000\r\njitter.vm.add_memory_page(stage1_addr, PAGE_READ | PAGE_WRITE | PAGE_EXEC, mbr, \"NotPetyaS1\")\r\njitter.vm.add_memory_page(stage2_addr, PAGE_READ | PAGE_WRITE | PAGE_EXEC, \"\\x00\"*SECTOR_LEN*32, \"NotPetyaS2\")\r\njitter.vm.add_memory_page(0x500, PAGE_READ | PAGE_WRITE, \"\\x00\"*(0x7C00-0x500+1), \"Stack\")\r\n# Pretty print of the memory layout\r\nprint(jitter.vm)\r\nNow, the memory layout of the Miasm virtual machine is the following:\r\nAddr Size Access Comment\r\n0x500 0x7700 RW_ Stack\r\n0x7C00 0x200 RWX NotPetyaS1\r\n0x8000 0x4000 RWX NotPetyaS2\r\nNotPetya loads 32 sectors from the disk to the memory when executing the first stage. This is why the allocated\r\nmemory for the second stage is 32 sectors long (32*512 bytes).\r\nBIOS interruption handling in Miasm Permalink\r\nMiasm allows us to specify an interruption handler which will be called whenever an INT instruction is\r\nexecuted. To do so, we have to tell Miasm to call our BIOS interruption handler exception_int with the help of\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 7 of 19\n\nadd_exception_handler of the current used jitter:\r\njitter.add_exception_handler(EXCEPT_INT_XX, lambda jitter: exception_int(jitter, sys_))\r\nInterruption support Permalink\r\nNow, we have to implement the different BIOS interruption handlers. We can split them into four main families :\r\nINT 10h : access to the screen (write characters, change colors…),\r\nINT 13h : access to the disk (read/write sectors, get disk geometry…),\r\nINT 16h : access to the keyboard (read keystroke…),\r\nINT 19h : boot on the disk’s MBR.\r\nINT 13h Permalink\r\nHere is an example of the INT 13h interruption, with the 0x43 code function ( Extended Read Sectors From\r\nDrive ). This code implements the instruction to load multiple sectors from the disk to the memory:\r\n@func(disk_interrupts, 0x42)\r\ndef extended_read_sectors(jitter, sys_):\r\n drive_idx = get_xl(jitter.cpu.DX)\r\n print \"Extended read sectors, drive idx 0x%x\" % drive_idx\r\n dap = jitter.vm.get_mem((jitter.cpu.DS \u003c\u003c 4)+jitter.cpu.SI, 16)\r\n dap_size, _, num_sect, buff_addr, abs_sect = struct.unpack(\"\u003cBBHIQ\", dap)\r\n hd = sys_.hd(drive_idx)\r\n print(\" Read %d sectors from sector %d\" % (num_sect, abs_sect))\r\n size = num_sect * SECTOR_LEN\r\n data = hd.read(abs_sect * SECTOR_LEN, size)\r\n jitter.cpu.cf = 0 # No error\r\n # AL is the number of sectors read\r\n # AH is the return code, 0 = successful completion\r\n jitter.cpu.AX = set_16bit_reg(low=int(len(data) / SECTOR_LEN), high=0)\r\n jitter.vm.set_mem(buff_addr, data)\r\nNote: this Python code doesn’t include error management for readability reasons. The sys_ object is the system\r\nabstraction explained in System abstraction.\r\nSectors can be loaded from disk in two different ways by using a different kind of addressing mechanism for the\r\nsame INT 13h interruption:\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 8 of 19\n\n1. CHS (Cylinder, Head, Sector) addressing mechanism, used by the 02h / 03h codes. It can read/write one\r\nor many sectors by specifying the index of the cylinder and the head,\r\n2. LBA (Logical Bloc Addressing) addressing mechanism, used by the 42h / 43h codes. It can read/write\r\none or several sectors by specifying the corresponding sector in a absolute way, i.e. by specifying the offset\r\nfrom the first sector number on the disk regardless of heads/cylinders.\r\nNotPetya uses the LBA addressing mechanism. This method needs to fill a DAP (Disk Address Packet) structure.\r\nThis structure describes which sectors to read/write and where to read/write them into live memory.\r\nOne can see that an extended LBA structure exists to read or write multiple sectors at the same time:\r\n0 1 Packet size\r\n1 1 Zeroed field\r\n2 2 Number of sectors to load\r\n4 4 Buffer address to load sectors to (seg:off)\r\n8 8 Absolute offset of the first sector to read\r\nTo sum up:\r\n1. the DAP is parsed,\r\n2. data is read from the virtual disk,\r\n3. the read data is stored in the corresponding memory page of the instantiated Miasm virtual machine.\r\nThe writing mechanism is the exact opposite: the specified buffer address in the DAP contains the data to write.\r\nINT 19h Permalink\r\nThe second chosen example is the INT 19h interruption (diskboot). It reboots the machine 67 and is used in two\r\nlocations :\r\n1. at address 0x892E , which is called if a fatal error occurs,\r\n2. at address 0x820D , when the machine reboots after the MFT encryption.\r\nThe INT 19h interruption is called right after the POST (Power On Self Test) procedure by the BIOS. After that,\r\nthe MBR code is loaded into live memory at Ox7C00 . Then, the BIOS jumps at this address.\r\nSo we can say here that it is used as a sort of soft reboot because the reboot is not a complete one. This instruction\r\nis part of the boot process after BIOS execution. Some BIOS can handle boot medium priority while others just\r\nloop over available mediums and boot on the first one it can.\r\nHere, we will emulate this instruction simply by loading again the MBR code into the memory page dedicated to\r\nit (stage 1), and then jump onto it (at address 0x7C00 ):\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 9 of 19\n\ndiskboot_interrupts = FuncTable(\"INT 19h (diskboot)\")\r\n@func(diskboot_interrupts, 0x02)\r\ndef reboot(jitter, sys_):\r\n # Here, we assume only one bootable disk (index 0)\r\n hd = sys_.hd(0)\r\n mbr = hd.read_sector(0)\r\n jitter.vm.set_mem(0x7C00, mbr)\r\n jitter.pc = 0x7C00\r\nFor a few more hacks… Permalink\r\nThe STI (Set Interrupt Flag) instruction is used at address 0x7C0D . It can activate masked interruptions (flag\r\nIF and offset 9 of the FLAGS register). This flag doesn’t have any effect on non-maskable interruptions.\r\nBecause hardware interruptions are fully emulated, Miasm doesn’t contain (legitimately) semantics for this\r\ninstruction.\r\nSo we simply decided to ignore it by setting a breakpoint at its corresponding address:\r\njitter.add_breakpoint(0x7C0D, handle_sti)\r\nThen, we redirect the execution flow to the next instruction. Because this instruction is only 1 byte long, a simple\r\nincrementation of the program counter does the trick:\r\ndef handle_sti(jitter):\r\n jitter.pc += 1\r\n return True\r\nYippie kay yay motherfucker ! Permalink\r\nNow that the useful handlers are implemented and the MBR code is loaded and mapped in Miasm virtual\r\nmachine, emulation of NotPetya can begin:\r\njitter.init_run(stage1_addr)\r\njitter.continue_run()\r\nIf the --verbose-bios-data flag is set (see Implementation), output of the script prints the content of the various\r\nread and write operations on the disk. For example, here is the content of the second sector (of the 32 loaded\r\nsectors by the bootloader at 0x8000 ):\r\nExtended read sectors, drive idx 0x0\r\n Read 1 sectors from sector 2\r\n00000000: 50 FF 76 04 E8 91 0A 83 C4 0A E8 3B 07 CD 19 5E P.v........;...^\r\n00000010: C9 C3 6A 0E E8 39 07 5B 68 70 9C E8 C0 03 5B C3 ..j..9.[hp....[.\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 10 of 19\n\n00000020: C8 04 04 00 56 6A 00 6A 01 6A 00 6A 20 8D 86 FC ....Vj.j.j.j ...\r\n00000030: FD 50 8A 46 06 50 E8 21 0A 83 C4 0C 6A 00 68 8E .P.F.P.!....j.h.\r\n[...]\r\nThe loaded code matchs stage 2. Also, one can easily see the content loaded from sector 32:\r\nExtended read sectors, drive idx 0x80\r\n Read 1 sectors from sector 32\r\n00000000: 00 AA 92 E7 82 11 15 D3 20 96 A7 75 51 C0 36 08 ........ ..uQ.6.\r\n00000010: E8 65 42 8C 73 9F 06 53 77 CB C5 95 60 C8 38 69 .eB.s..Sw...`.8i\r\n00000020: 9B 0D A4 99 E0 13 12 30 79 31 4D 7A 37 31 35 33 .......0y1Mz7153\r\n00000030: 48 4D 75 78 58 54 75 52 32 52 31 74 37 38 6D 47 HMuxXTuR2R1t78mG\r\n00000040: 53 64 7A 61 41 74 4E 62 42 57 58 00 00 00 00 00 SdzaAtNbBWX.....\r\n00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n000000A0: 00 00 00 00 00 00 00 00 00 48 34 79 5A 73 77 56 .........H4yZswV\r\n000000B0: 54 64 43 6B 43 77 55 68 72 31 4D 52 6D 4A 65 69 TdCkCwUhr1MRmJei\r\n000000C0: 76 31 34 46 4B 39 6A 5A 6A 4D 36 36 4C 44 79 65 v14FK9jZjM66LDye\r\n000000D0: 71 52 4C 64 6B 38 53 58 53 53 73 53 53 45 78 34 qRLdk8SXSSsSSEx4\r\n000000E0: 44 51 57 4E 47 00 00 00 00 00 00 00 00 00 00 00 DQWNG...........\r\n000000F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n[...]\r\n000001F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...............\r\nThis sector is where NotPetya stores data. According to the description done in MISC 805 n°93, we can deduct its\r\ncontent :\r\n0x00 is the encrypted disk flag,\r\nAA 92 E7 82 11 15 D3 20 96 A7 75 51 C0 36 08 E8 65 42 8C 73 9F 06 53 77 CB C5 95 60 C8 38 69\r\nis the 32 bytes long Salsa20 key,\r\n0D A4 99 E0 13 12 30 79 is a 8 bytes long nonce,\r\nnext data is the random string printed when the malware was executed on Windows at the beginning.\r\nAfter the encryption is done by the bootloader, key and nonce are erased from disk with 32 successive writings of\r\nzeros.\r\nMoreover, we can see that sector 35 is used to store the number of total encrypted MFT entries. For example, here\r\nis the content of sector 35 right after the MFT header encryption:\r\nExtended write sectors, drive idx 0x80\r\n Write 1 sectors at offset 35 (from memory at 0x5C74)\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 11 of 19\n\n00000000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\r\n[...]\r\nRetrieving secrets in memory Permalink\r\nOne of the advantages of emulation is the ability to easily analyze memory pages. In our case, it is possible to\r\nretrieve the Salsa20 key used, even after the MFT table has been encrypted (during the “false chkdsk”, see\r\nNotPetya).\r\nIn fact, as explained in section INT 19h; after the encryption is over, the bootloader executes a “soft reboot” with\r\nthe help of the interruption INT 19h . It doesn’t reboot completely the computer therefore the BIOS is not\r\nexecuted again. The data present in memory before the “soft reboot” is not tampered with. If the computer goes\r\nthrough hard reboot or reset, there would be great chances for the BIOS to overwrite data present on the stack with\r\nits own, including the precious Salsa20 key.\r\nSo, if the computer has not been rebooted or reset, it is pretty interesting to see if the Salsa20 key is still in\r\nmemory. To do so, we simply read the key written in sector 32 (see this section) and store its value. Then, we\r\nplace a breakpoint on the instructions in charge to show the ransom message, at address 0x85AF :\r\nkey = HD0.read(32*SECTOR_LEN + 1, 32)\r\njitter.add_breakpoint(0x85AF, functools.partial(find_key_in_mem, key=key))\r\nThe find_key_in_mem function browses the virtual machine memory to find the key stored in the previous step:\r\ndef find_key_in_mem(jitter, key):\r\n # Find if the salsa20 key is still in memory!\r\n mem = jitter.vm.get_all_memory()\r\n print \u003e\u003esys.stderr, \"\\n[+] Looking for key %s in memory...\" % key.encode(\"hex\")\r\n for addr,v in mem.iteritems():\r\n idx = v['data'].find(key)\r\n if idx == -1:\r\n continue\r\n print \u003e\u003esys.stderr, \"[+] Key found at address %s!\" % hex(addr + idx)\r\n break\r\n else:\r\n print \u003e\u003esys.stderr, \"[-] Key not found in memory!\"\r\n return True\r\nThis operation can be activated in the script using --hook=find_key option, like this:\r\n$ python ./emulate-mbr.py --hook=find_key disk.raw\r\nRepairing file system on C:\r\n[... encryption happends here ...]\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 12 of 19\n\n[+] Looking for key [your salsa20 key] in memory...\r\n[+] Key found at address 0x674a!\r\nTo speed up the process, the --skip-encryption option can be used (see Implementation). Be careful, even if\r\nthis option is used, the encryption flag in sector 32 is still set. The flag --dry prevents this behaviour.\r\nBecause we know the address where the key is stored ( 0x674A ), we can put a breakpoint on a write access at this\r\nlocation, allowing us to know which part of the bootloader writes this key:\r\ndef print_ip(jitter):\r\n print(hex(jitter.pc))\r\n return False\r\njitter.exceptions_handler.callbacks[EXCEPT_BREAKPOINT_MEMORY] = []\r\njitter.add_exception_handler(EXCEPT_BREAKPOINT_MEMORY, print_ip)\r\njitter.vm.add_memory_breakpoint(0x674a, 1, PAGE_WRITE)\r\nBecause there is no ASLR or equivalent mechanism, this address will always be the same!\r\nBootloader modification to decrypt the MFT Permalink\r\nIf we have a mechanism to write directly into the memory of the machine (for example by using a PCI Express\r\ncard 8, or other interfaces like FireWire or Thunderbolt 9), it is possible to decrypt the MFT data. The attack\r\nconsists in patching the bootloader memory so that its uses the remaining key on the stack. This section simulates\r\nthis attack using Miasm.\r\nTo do so, we will inject some code at address 0x82A8 . This function checks that the key entered is the expected\r\none. Given that it has been erased from the hard drive, and that the ransom text is completely random 10, the\r\nbootloader has in theory no way to know if the entered key is the right one. This function will always return 0\r\n(incorrect key). The injected code will copy the key Salsa20 from the 0x674A address to a specific location on\r\nthe stack, so that the decryption function at 0x835A will use this key. We will then jump on this function.\r\nAssociated assembly code is the following:\r\n ; Save registers on the stack\r\n PUSHA\r\n LEA DI, WORD PTR [BP-0x44]\r\n LEA BX, WORD PTR [key_addr]\r\n XOR CX,CX\r\n ; Copy the key that remains on the stack to [bp-0x44]\r\nloop:\r\n MOV EAX, DWORD PTR [BX]\r\n MOV DWORD PTR [DI], EAX\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 13 of 19\n\nADD DI, 4\r\n ADD BX, 4\r\n INC CX\r\n CMP CX,8\r\n JNZ loop\r\n ; Restore previously saved registers\r\n POPA\r\n ; Jump on the decryption function (CS:OFFSET =\u003e using an absolute address)\r\n JMP 0000:0x835A\r\nWe use Miasm to assemble it using the following function:\r\ndef asm_shellcode(asm, labels = None):\r\n machine = Machine(\"x86_16\")\r\n symbol_pool = asmblock.AsmSymbolPool()\r\n # Assemble\r\n blocks, symbol_pool = parse_asm.parse_txt(machine.mn, 16, asm, symbol_pool)\r\n # Set custom labels\r\n if not labels is None:\r\n for name,value in labels.iteritems():\r\n sym = symbol_pool.getby_name(name)\r\n symbol_pool.set_offset(sym, value)\r\n # Resolve all the labels\r\n patches = asmblock.asm_resolve_final(machine.mn,\r\n blocks,\r\n symbol_pool)\r\n # Patch the final code with the label values\r\n shellcode = StrPatchwork()\r\n for offset, raw in patches.items():\r\n shellcode[offset] = raw\r\n return str(shellcode)\r\nLet’s take a look at this function. The code is first assembled using an x86 16-bit assembler. Given labels are then\r\nassociated to concrete values using the symbol_pool.set_offset function. Remaining labels (in our case loop )\r\nare resolved with the asmblock.asm_resolve_final function, which returns assembly code for each block. We\r\nfinally use the StrPatchwork function to assemble the final “shellcode”.\r\nThe read_key_and_patch function loads the key in memory, dumps it and writes the freshly assembled code in\r\nmemory:\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 14 of 19\n\ndef read_key_and_patch(jitter):\r\n # Key is still in the stack, at 0x674A. You can find this value by activating the\r\n # find_key_in_mem breakpoint!\r\n key_addr = 0x674A\r\n key = jitter.vm.get_mem(key_addr, 32)\r\n print \u003e\u003esys.stderr, \"\\n[+] Key from memory: %s\" % key.encode(\"hex\")\r\n # Assemble our \"shellcode\" thanks to Miasm!\r\n shellcode = \"\"\"\r\n...\r\n \"\"\"\r\n shellcode = asm_shellcode(shellcode, {\"key_addr\": key_addr})\r\n # Patch the bootloader in memory to decrypt using the key\r\n jitter.vm.set_mem(0x82A8, shellcode)\r\n return True\r\nThe remaining thing to do is to put a breakpoint at the same address as in section Retrieving secrets in memory\r\n( 0x85AF ) to call this function:\r\njitter.add_breakpoint(0x85AF, read_key_and_patch)\r\nEverything is now set up. When the bootloader asks for the decryption key, user will just have to press enter . -\r\n-hook=patch_bootloader flag of the emulate_mbr script performs this attack.\r\nIt is worth mentioning that we actually tried this at Synacktiv’s headquarters using vulnerabilities in HP’s iLO4 to\r\ngather the Salsa20 key from memory, patch the bootloader and decrypt MFT data.\r\nEncryption keystream study Permalink\r\nThe encryption algorithm used is Salsa20 stream cipher. The general principle is: a random data flow based on a\r\nkey - commonly called the keystream - is generated, and this stream is XORed with the data which will be\r\nencrypted. An advantage of stream ciphers is that the data to be encrypted do not need to be padded. On the other\r\nhand, one needs to be careful not to use the same parts of this stream twice.\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 15 of 19\n\nData encryption with Salsa20\r\nWe can verify this using Miasm, by looking at the data before and after encryption, and by showing their XOR\r\ndifference.\r\nIn order to do this, we already know how to put breakpoints. The beginning of the encryption function is at\r\naddress 0x9798 , and the end at address 0x9877 . We are going to out the first breakpoint just after ‘enter\r\ninstruction, and the second just before leave` statement, in order to have the stack properly aligned to\r\nrecover data before and after encryption. The associated code is the following:\r\n_last_buf = None\r\ndef encrypt_start(jitter, options):\r\n global _last_buf\r\n buf_ptr = upck16(jitter.vm.get_mem((jitter.cpu.SS \u003c\u003c 4) + jitter.cpu.BP + 0xC, 2))\r\n buf_size = upck16(jitter.vm.get_mem((jitter.cpu.SS \u003c\u003c 4) + jitter.cpu.BP + 0xE, 2))\r\n _last_buf = jitter.vm.get_mem((jitter.cpu.DS \u003c\u003c 4) + buf_ptr, buf_size)\r\n return True\r\ndef encrypt_end(jitter, options):\r\n global _last_buf\r\n buf_ptr = upck16(jitter.vm.get_mem((jitter.cpu.SS \u003c\u003c 4) + jitter.cpu.BP + 0xC, 2))\r\n buf_size = upck16(jitter.vm.get_mem((jitter.cpu.SS \u003c\u003c 4) + jitter.cpu.BP + 0xE, 2))\r\n encr_buf = jitter.vm.get_mem((jitter.cpu.DS \u003c\u003c 4) + buf_ptr, buf_size)\r\n keystream = ''.join(chr(ord(a)^ord(b)) for a,b in zip(_last_buf,encr_buf)).encode(\"hex\")\r\n keystream = ' '.join(keystream[i:i+4] for i in xrange(0,len(keystream),4))\r\n print \u003e\u003esys.stderr, \"Keystream for next 2 sectors: %s\" % keystream\r\n return True\r\njitter.add_breakpoint(0x979C, functools.partial(encrypt_start, options=options))\r\njitter.add_breakpoint(0x9876, functools.partial(encrypt_end, options=options))\r\nThe --dump-keystream flag of the emulate_mbr script enables this.\r\nBy looking at the output, we can see that between two sectors ( 2*512 bytes), the keystream is only shifted by\r\ntwo bytes, instead of the normally required 2*512 bytes. This shift is schematized in the image below:\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 16 of 19\n\nWe can also see that on a screenshot of the output of the emulate_mbr script below:\r\nScreenshot of the keystream\r\nThus, parts of the keystream are reused between sectors, which may help to recover some of its original data.\r\nIndeed, if we consider p to be the clear text, k the keystream and c the encrypted text, then the encryption\r\nfunction E is defined as E (p) = p xor k = c . A part of the MFT structures being invariant and known, it is\r\ntherefore possible, in two sectors, to find part of the keystream used for these two sectors. This one is reused for\r\nthe two following sectors by being simply shifted by two bytes, so some of the clear text from these other areas\r\ncan be found.\r\nThis vulnerability in the Salsa20 implementation of the bootloader has been exploited by CrowdStrike to recover a\r\nlarge portion of MFT’s original data (between 98.10% and 99.97% depending on the method).\r\nConclusion Permalink\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 17 of 19\n\nEmulation of the NotPetya bootloader code allows the verification of various assumptions and the understanding,\r\nin a very tangible way, of the different steps related to the encryption of MFT entries. In addition, it allows to\r\neasily find the bias in the Salsa20 keystream implementation (without having to statically reverse the algorithm),\r\nor to simulate the recovery of the key, which remains in memory after the encryption.\r\nThis article only shows a small subset of Miasm’s possibilities, and we hope that the approach adopted in this\r\narticle will encourage uninitiated readers to try and play with it :).\r\nAcknowledgments Permalink\r\nWe would like to thank gapz for his initial encouragement. Big thanks also to Camille Mougey and Fabrice\r\nDesclaux for their help and thorough reviews of this article! Thanks to Thomas Chauchefoin and zerk for their\r\ncomments, and to Yseult for her help with the English translation.\r\nAppendix: application using vulnerabilities in HP iLO 4 Permalink\r\nWith Alexandre Gazet \u0026 Fabien Perigaud, we spent some time in Synacktiv’s offices to combine the attacks\r\ndescribed in this article with vulnerabilities they found with Joffrey Czarny on HP iLO 4’s management engine.\r\nThese vulnerabilities allowed us to read and write the memory of an infected server stuck at NotPetya’s bootloader\r\nstage, so that we were able to recover the encryption key and patch the bootloader in order to decrypt the MFT.\r\nA full write up of the experiment can be read on Airbus seclab website.\r\n1. https://fr.wikipedia.org/wiki/Master_File_Table ↩\r\n2. https://shasaurabh.blogspot.fr/2017/07/debugging-mbr-ida-pro-and-bochs-emulator.html ↩\r\n3. https://www.sstic.org/2012/presentation/miasm_framework_de_reverse_engineering/ ↩\r\n4. https://fr.wikipedia.org/wiki/Mode_r%C3%A9el ↩\r\n5. https://docs.microsoft.com/en-us/windows/wsl/install-win10 ↩\r\n6. https://wiki.osdev.org/ATA_in_x86_RealMode_(BIOS)) ↩\r\n7. http://webpages.charter.net/danrollins/techhelp/0243.HTM ↩\r\n8. 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 ↩\r\n9. https://github.com/carmaa/inception ↩\r\n10. https://www.crowdstrike.com/blog/petrwrap-ransomware-technical-analysis-triple-threat-file-encryption-mft-encryption-credential-theft/ ↩\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 18 of 19\n\nSource: https://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nhttps://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html\r\nPage 19 of 19\n\nExtended read Read 1 sectors sectors, drive idx from sector 2 0x0 \n00000000: 50 FF 76 04 E8 91 0A 83 C4 0A E8 3B 07 CD 19 5E P.v........;...^\n00000010: C9 C3 6A 0E E8 39 07 5B 68 70 9C E8 C0 03 5B C3 ..j..9.[hp....[.\n   Page 10 of 19",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://aguinet.github.io//blog/2020/08/29/miasm-bootloader.html"
	],
	"report_names": [
		"miasm-bootloader.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775791226,
	"ts_updated_at": 1775791337,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/cef445a22ce4637c0ef2a4ba2947e36ef3420168.pdf",
		"text": "https://archive.orkl.eu/cef445a22ce4637c0ef2a4ba2947e36ef3420168.txt",
		"img": "https://archive.orkl.eu/cef445a22ce4637c0ef2a4ba2947e36ef3420168.jpg"
	}
}