Wednesday, October 3, 2012

MoVP 4.3 Recovering Master Boot Records (MBRs) from Memory

Month of Volatility Plugins

Given that we are still recovering from an amazing Open Memory Forensics Workshop, today's post will continue the theme of short and sweet. This post will focus on recovering interesting disk artifacts from memory. In particular, it will demonstrate how the Master Boot Record (MBR) can be recovered from Windows memory samples and why this is important to digital forensics and incident response investigators.

Background

One thing that people may not be aware of is the breadth of interesting disk artifacts that often manifest themselves in memory. These artifacts can be useful in investigations, for example checking for changed bootcode in an MBR or getting MAC times and full file paths for a malicious file. In the past, an investigator would have to manually carve, parse or process these items from a memory sample. Now we can accomplish processing these artifacts within the Volatility framework.

Master Boot Record (MBR)

The MBR loads after POST operations. Its purpose is to find and load a bootable partition. It's also a known infection vector for malware. On disk the MBR is found in the first 512 bytes of the drive. It contains executable bootcode, a disk signature, partition entries and an ending signature. We can see the structures for these items below.

>>> dt("PARTITION_ENTRY")
'PARTITION_ENTRY' (16 bytes)
0x0   : BootableFlag                   ['char']
0x1   : StartingCHS                    ['array', 3, ['unsigned char']]
0x4   : PartitionType                  ['char']
0x5   : EndingCHS                      ['array', 3, ['unsigned char']]
0x8   : StartingLBA                    ['unsigned int']
0xc   : SizeInSectors                  ['int']

>>> dt("PARTITION_TABLE")
'PARTITION_TABLE' (512 bytes)
0x1b8 : DiskSignature                  ['array', 4, ['unsigned char']]
0x1bc : Unused                         ['unsigned short']
0x1be : Entry1                         ['PARTITION_ENTRY']
0x1ce : Entry2                         ['PARTITION_ENTRY']
0x1de : Entry3                         ['PARTITION_ENTRY']
0x1ee : Entry4                         ['PARTITION_ENTRY']
0x1fe : Signature                      ['unsigned short']

The bootcode itself, obviously does not have a "structure" to it, as it contains executable code and error strings so it is not represented above. The bootcode typically is the first 440 bytes of the MBR, but can actually take up less than this. It contains instructions for loading its own code into a second place in memory, finding the bootable partition, and loading the bootable partitions Volume Boot Record (VBR). Bootcode is the same for systems of the same architecture, for example Windows XP x86 systems have the same bootcode; Vista x86 systems have the same bootcode, etc. This allows a pretty easy verification method for checking disks of systems for compromise: an analyst could have a whitelist of bootcode hashes to compare against the MBR of a suspect system. Any variation in the bootcode would flag as a potential problem. It's not as simple in memory. Example bootcode can be seen below:

0x00000000: 33c0                             XOR AX, AX
0x00000002: 8ed0                             MOV SS, AX
0x00000004: bc007c                           MOV SP, 0x7c00
0x00000007: fb                               STI
0x00000008: 50                               PUSH AX
0x00000009: 07                               POP ES
0x0000000a: 50                               PUSH AX
0x0000000b: 1f                               POP DS
0x0000000c: fc                               CLD
0x0000000d: be1b7c                           MOV SI, 0x7c1b
0x00000010: bf1b06                           MOV DI, 0x61b
0x00000013: 50                               PUSH AX
0x00000014: 57                               PUSH DI
0x00000015: b9e501                           MOV CX, 0x1e5
0x00000018: f3a4                             REP MOVSB
0x0000001a: cb                               RETF
0x0000001b: bdbe07                           MOV BP, 0x7be
0x0000001e: b104                             MOV CL, 0x4
0x00000020: 386e00                           CMP [BP+0x0], CH
0x00000023: 7c09                             JL 0x2e
[snip]

We can leverage a number of clues to find where the MBR may reside in memory after a computer boots up. After the computer executes POST, the BIOS loads the MBR into physical memory at 0x7c00 and transfers control to this code. Looking at the assembly from that point, you can see that the MBR code reloads itself into a second location since it must overwrite the memory at its current offset (0x7c00) with the Volume Boot Record of the bootable volume. There are slight differences between system as to how much of the MBR will be copied into this new location. Prior to Windows XP, the entire MBR is copied, for Windows XP everything after the currently executed code is copied (this starts at offset 0x1b if 0x0 is the start of the executable code). For Vista and later systems, the entire MBR is copied again. The location in memory where the MBR is copied to starts at physical offset 0x600; for Windows XP systems this offset is actually 0x61b since it skips the executed code as mentioned above. Knowing this, we are given a good starting point for investigating MBRs in memory since we know where it should reside.

In the above bootcode disassembly, we can tell that this is from a Windows XP machine since we can see that code is being moved from offset 0x7c1b to 0x61b. Looking at an MBR in memory we can see how it differs from above. One thing to notice is that the first section of the bootcode is zeroed out (consistent with the fact that it was not copied over) and then we see that the disassembly doesn't quite line up on our disassembly from disk (above) on instruction ADD [DI+0x7be], BH, but then lines up on the following line until the end (not shown here).

0x00000000: 0000                             ADD [BX+SI], AL
0x00000002: 0000                             ADD [BX+SI], AL
0x00000004: 0000                             ADD [BX+SI], AL
0x00000006: 0000                             ADD [BX+SI], AL
0x00000008: 0000                             ADD [BX+SI], AL
0x0000000a: 0000                             ADD [BX+SI], AL
0x0000000c: 0000                             ADD [BX+SI], AL
0x0000000e: 0000                             ADD [BX+SI], AL
0x00000010: 0000                             ADD [BX+SI], AL
0x00000012: 0000                             ADD [BX+SI], AL
0x00000014: 0000                             ADD [BX+SI], AL
0x00000016: 0000                             ADD [BX+SI], AL
0x00000018: 0000                             ADD [BX+SI], AL
0x0000001a: 00bdbe07                         ADD [DI+0x7be], BH
0x0000001e: b104                             MOV CL, 0x4  ;ok from here
0x00000020: 386e00                           CMP [BP+0x0], CH
0x00000023: 7c09                             JL 0x2e

If we line up the disassembly to start at physical offset 0x61b, then we get the disassembly that we were expecting:

0x00000000: bdbe07                           MOV BP, 0x7be
0x00000003: b104                             MOV CL, 0x4
0x00000005: 386e00                           CMP [BP+0x0], CH
0x00000008: 7c09                             JL 0x13 ;differs by 0x1b
[snip]

So we've seen how an MBR looks, in memory, for a Windows XP system.  Now, lets discuss how it differs on other versions of Windows. It should be pretty easy to figure out the MBR for a Vista, 2008 or Windows 7 machine since the entire MBR is copied to offset 0x600. However, in reality it's not exactly the same as an MBR on disk. Below is an example from a Windows 7 machine:

                 Memory                                                 Disk

0x000000fb: 666807bb0000   PUSH DWORD 0xbb07         0x000000fb: 666807bb0000   PUSH DWORD 0xbb07
0x00000101: 000c           ADD [SI], CL              0x00000101: 666800020000   PUSH DWORD 0x200 
0x00000103: 0000           ADD [BX+SI], AL
0x00000105: 000c           ADD [SI], CL
0x00000107: 0000           ADD [BX+SI], AL           0x00000107: 666808000000   PUSH DWORD 0x8
0x00000109: 000c           ADD [SI], CL
0x0000010b: 0000           ADD [BX+SI], AL
0x0000010d: 000c           ADD [SI], CL              0x0000010d: 6653           PUSH EBX
0x0000010f: 0000           ADD [BX+SI], AL           0x0000010f: 6653           PUSH EBX    
0x00000111: 000c           ADD [SI], CL              0x00000111: 6655           PUSH EBP    
0x00000113: 0000           ADD [BX+SI], AL           0x00000113: 666800000000   PUSH DWORD 0x0
0x00000115: 000c           ADD [SI], CL
0x00000117: 0000           ADD [BX+SI], AL
0x00000119: 6668007c0000   PUSH DWORD 0x7c00         0x00000119: 6668007c0000   PUSH DWORD 0x7c00
0x0000011f: 6661           POPA                      0x0000011f: 6661           POPA
0x00000121: 680000         PUSH WORD 0x0             0x00000121: 680000         PUSH WORD 0x0
0x00000124: 07             POP ES                    0x00000124: 07             POP ES
0x00000125: cd1a           INT 0x1a                  0x00000125: cd1a           INT 0x1a    
0x00000127: 5a             POP DX                    0x00000127: 5a             POP DX

So here we can see that the disassembly differs in memory and we cannot assume that it will be exactly the same. If you want to build a whitelist of MBRs in memory, you could take a clean machine and obtain the copy of the MBR in memory.

Methodology

So we've seen where we can find the MBR in memory and we've seen that sometimes it differs from that on disk. We could have different tactics for finding malicious MBRs: check the known offset where it should reside or scan for potential MBRs.

Assuming that we know what a clean MBR should look like for a particular running machine we could basically carve out the MBR from the appropriate offset (0x600 or 0x61b) and look for discrepancies. This is a fast way to verify if there are any differences. However, sometimes you may miss information because the malicious MBR may not actually be at this offset (possibly overwritten with a clean MBR later). Often with MBR infections you can find more than one copy of an MBR in memory, either clean or malicious.

There are two methods for scanning that we can use: 1) Scan for a known pattern that is integral to all MBRs or 2) Use the hash of the bootcode. Since we know what an MBR "looks like" and that it has to have this signature of "\x55\xaa" and that it must have a valid bootable partition, we can use this knowledge to find potential MBRs in memory with less false positives. Since bootcode is unique, we can use the hash of the bootcode to choose which results to yield. We have to consider how the executable code looks in the bootcode section, however, since this section contains error strings in addition to code and since the code does not have to use up its entire 440 byte space, the hash should not just be of the entire 440 bytes of bootcode. One possible solution would be to use the bootcode instructions up until a RET instruction.

Example: tdl4

In experiments, a Windows XP x86 VM was infected with a tdl4 variant, the machine was rebooted and suspended so that the machine could be observed in a state after the malcious MBR has executed. The malcious MBR was extracted from disk for comparison. So we can see the MBR at offset 0x600:

0x00000000: 33c0                             XOR AX, AX
0x00000002: 8ed0                             MOV SS, AX
0x00000004: bc007c                           MOV SP, 0x7c00
0x00000007: 8ec0                             MOV ES, AX
0x00000009: 8ed8                             MOV DS, AX
0x0000000b: be007c                           MOV SI, 0x7c00
0x0000000e: bf0006                           MOV DI, 0x600
0x00000011: b90002                           MOV CX, 0x200
0x00000014: fc                               CLD
0x00000015: f3a4                             REP MOVSB
0x00000017: 50                               PUSH AX
0x00000018: 681c06                           PUSH WORD 0x61c
0x0000001b: bdbe07                           MOV BP, 0x7be
0x0000001e: b104                             MOV CL, 0x4

We can automatically see that there's something amiss with this MBR just on first glance. Since this is a Windows XP machine, we know that all code until offset 0x1b should be zeroed out. Looking a little further at the instructions we can see why it isn't. So here we have seen how we can quickly identify differences in the loaded MBR that is found in memory. Scanning for all MBRs we can see that there are other malicious MBRs in memory. Looking at the MBR from disk and hashing the bootcode up to the RET instruction, we obtain a hash that we can use to scan for other copies of this malicious MBR in memory. Looking at the difference in the error string section we can see why the full bootcode hash (2839639fa37b8353e792a2a30a12ced3) differs from that of the sample (de1996b5390bac8242e23168f828c750).

$ python vol.py -f tdl4.vmem mbrparser -M 5429921523056d910bf95f32da2b2ab4
Volatile Systems Volatility Framework 2.3_alpha
Potential MBR at physical offset: 0x60f00
Disk Signature: 19-df-19-df
Bootcode md5: 5429921523056d910bf95f32da2b2ab4
Bootcode (full) md5: 2839639fa37b8353e792a2a30a12ced3
Disassembly of Bootable Code:
0x00000000: 33c0                             XOR AX, AX
0x00000002: 8ed0                             MOV SS, AX
0x00000004: bc007c                           MOV SP, 0x7c00
0x00000007: 8ec0                             MOV ES, AX
0x00000009: 8ed8                             MOV DS, AX
0x0000000b: be007c                           MOV SI, 0x7c00
0x0000000e: bf0006                           MOV DI, 0x600
0x00000011: b90002                           MOV CX, 0x200
0x00000014: fc                               CLD
0x00000015: f3a4                             REP MOVSB
0x00000017: 50                               PUSH AX
0x00000018: 681c06                           PUSH WORD 0x61c
0x0000001b: cb                               RETF
0x0000001c: fb                               STI
0x0000001d: 60                               PUSHA
0x0000001e: b94701                           MOV CX, 0x147
0x00000021: bd2a06                           MOV BP, 0x62a

[snip]

0x000000b7: 4c                               DEC SP
0x000000b8: 7504                             JNZ 0xbe
0x000000ba: 02af8f80                         ADD CH, [BX+0x808f]
0x000000be: 8903                             MOV [BP+DI], AX
0x000000c0: 62                               DB 0x62
0x000000c1: ff98f611                         CALL FAR WORD [BX+SI+0x11f6]
0x000000c5: f9                               STC
0x000000c6: 93                               XCHG BX, AX
0x000000c7: 1cfd                             SBB AL, 0xfd
0x000000c9: c3                               RET

0x000000ca: ba 3e 51 f8 93 1c 04 04 00 32 31 7c 54 b6 e4 07   .>Q......21|T...
0x000000da: 44 eb 4e 70 44 36 e4 07 23 bf d9 57 20 a4 ad 88   D.NpD6..#..W....
0x000000ea: 6b bf 78 57 ee 22 3c 72 04 22 c3 37 40 fa 45 08   k.xW."<r.".7@.E.
0x000000fa: 5d 00 40 f0 b5 78 e4 08 87 ad c0 37 40 c4 ff fe   ].@..x.....7@...
0x0000010a: e1 a2 f1 27 38 00 91 89 e3 a2 b5 27 38 22 1b 72   ...'8......'8".r
0x0000011a: 83 22 f5 27 38 00 d3 30 f6 62 f9 a8 6c c9 0e 30   .".'8..0.b..l..0
0x0000012a: 06 91 49 57 d6 85 87 66 98 30 1d f3 ff 06 7c a2   ..IW...f.0....|.
0x0000013a: 04 d0 88 47 68 99 ff 0e 4a 02 cc 38 f0 62 10 00   ...Gh...J..8.b..
0x0000014a: f5 79 31 dc dd 40 00 be 54 02 7e 6a 49 bb 0b c9   .y1..@..T.~jI...
0x0000015a: 3a 83 20 ec 1c 1b 20 29 f4 40 f9 98 4f 2d ea ea   :......).@..O-..
0x0000016a: e1 1b 8c 27 89 d8 00 69 6e 67 20 73 79 73 74 65   ...'...ing.syste
0x0000017a: 6d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   m...............
0x0000018a: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0x0000019a: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0x000001aa: 00 00 00 00 00 00 00 00 00 00 00 2c 44 63         ...........,Dc

===== Partition Table #1 =====
Boot flag: 0x80 (Bootable)
Partition type: 0x7 (NTFS)
Starting Sector (LBA): 0x38 (56)
Starting CHS: Cylinder: 0 Head: 1 Sector: 1
Ending CHS: Cylinder: 732 Head: 254 Sector: 56
Size in sectors: 0x9fb770 (10467184)

===== Partition Table #2 =====
Boot flag: 0x0 
Partition type: 0x0 (Empty)
Starting Sector (LBA): 0x0 (0)
Starting CHS: Cylinder: 0 Head: 0 Sector: 0
Ending CHS: Cylinder: 0 Head: 0 Sector: 0
Size in sectors: 0x0 (0)

===== Partition Table #3 =====
Boot flag: 0x0 
Partition type: 0x0 (Empty)
Starting Sector (LBA): 0x0 (0)
Starting CHS: Cylinder: 0 Head: 0 Sector: 0
Ending CHS: Cylinder: 0 Head: 0 Sector: 0
Size in sectors: 0x0 (0)

===== Partition Table #4 =====
Boot flag: 0x0 
Partition type: 0x0 (Empty)
Starting Sector (LBA): 0x0 (0)
Starting CHS: Cylinder: 0 Head: 0 Sector: 0
Ending CHS: Cylinder: 0 Head: 0 Sector: 0
Size in sectors: 0x0 (0)

Looking at the error strings section from the MBR on disk we can see that it differs:

0x000000ca:  ba 3e 51 f8 93 1c 04 04 00 32 31 7c 54 b6 e4 07   .>Q......21|T...
0x000000da:  44 eb 4e 70 44 36 e4 07 23 bf d9 57 20 a4 ad 88   D.NpD6..#..W....
0x000000ea:  6b bf 78 57 ee 22 3c 72 04 22 c3 37 40 fa 45 08   k.xW."<r.".7@.E.
0x000000fa:  5d 00 40 f0 b5 78 e4 08 87 ad c0 37 40 c4 ff fe   ].@..x.....7@...
0x0000010a:  e1 a2 f1 27 38 00 91 89 e3 a2 b5 27 38 22 1b 72   ...'8......'8".r
0x0000011a:  83 22 f5 27 38 00 d3 30 f6 62 f9 a8 6c c9 0e 30   .".'8..0.b..l..0
0x0000012a:  06 91 49 57 d6 85 87 66 98 30 1d f3 ff 06 7c a2   ..IW...f.0....|.
0x0000013a:  04 d0 88 47 68 99 ff 0e 4a 02 cc 38 f0 62 10 00   ...Gh...J..8.b..
0x0000014a:  f5 79 31 dc dd 40 00 be 54 02 7e 6a 49 bb 0b c9   .y1..@..T.~jI...
0x0000015a:  3a 83 20 ec 1c 1b 20 29 f4 40 f9 98 4f 2d ea ea   :......).@..O-..
0x0000016a:  e1 1b 8c 27 89 d8 00 69 6f 6e 20 74 61 62 6c 65   ...'...ion.table
0x0000017a:  00 45 72 72 6f 72 20 6c 6f 61 64 69 6e 67 20 6f   .Error.loading.o
0x0000018a:  70 65 72 61 74 69 6e 67 20 73 79 73 74 65 6d 00   perating.system.
0x0000019a:  4d 69 73 73 69 6e 67 20 6f 70 65 72 61 74 69 6e   Missing.operatin
0x000001aa:  67 20 73 79 73 74 65 6d 00 00 00 63 7b 9a         g.system...c{.

Mebromi

There are occasions when there is no RET instruction before hitting the error strings block. In these cases you will notice that the first md5 hash (up to the RET) matches the full bootcode hash (full 440 bytes). Looking at the mbrparser output we can see that there is no RET instruction found in the bootcode.

$ ./vol.py –f mebromi.vmem mbrparser -M 4ad444d4e7efce9485a94186c3f4b157
Volatile Systems Volatility Framework 2.3_alpha
Potential MBR at physical offset: 0x60f00
Disk Signature: 19-df-19-df
Bootcode md5: 4ad444d4e7efce9485a94186c3f4b157
Bootcode (full) md5: 4ad444d4e7efce9485a94186c3f4b157
Disassembly of Bootable Code:
0x00000000: 33c0                             XOR AX, AX
0x00000002: 8ed0                             MOV SS, AX
0x00000004: bc007c                           MOV SP, 0x7c00
0x00000007: fb                               STI
0x00000008: 50                               PUSH AX
0x00000009: 07                               POP ES
0x0000000a: 50                               PUSH AX
0x0000000b: 1f                               POP DS
0x0000000c: fc                               CLD
0x0000000d: 50                               PUSH AX
0x0000000e: be007c                           MOV SI, 0x7c00
0x00000011: bf0006                           MOV DI, 0x600
0x00000014: b90002                           MOV CX, 0x200

[snip]

0x00000198: 0000                             ADD [BX+SI], AL
0x0000019a: 0000                             ADD [BX+SI], AL
0x0000019c: 0000                             ADD [BX+SI], AL
0x0000019e: 0000                             ADD [BX+SI], AL
0x000001a0: 0000                             ADD [BX+SI], AL
0x000001a2: 0000                             ADD [BX+SI], AL
0x000001a4: 0000                             ADD [BX+SI], AL
0x000001a6: 0000                             ADD [BX+SI], AL
0x000001a8: 0000                             ADD [BX+SI], AL
0x000001aa: 0000                             ADD [BX+SI], AL
0x000001ac: 0000                             ADD [BX+SI], AL
0x000001ae: 0000                             ADD [BX+SI], AL
0x000001b0: 0000                             ADD [BX+SI], AL
0x000001b2: 0000                             ADD [BX+SI], AL
0x000001b4: 002c                             ADD [SI], CH
0x000001b6: 44                               INC SP
0x000001b7: 63                               DB 0x63

===== Partition Table #1 =====
Boot flag: 0x80 (Bootable)
Partition type: 0x7 (NTFS)
Starting Sector (LBA): 0x38 (56)
Starting CHS: Cylinder: 0 Head: 1 Sector: 1
Ending CHS: Cylinder: 732 Head: 254 Sector: 56
Size in sectors: 0x9fb770 (10467184)

[snip]

Conclusion

We are able to find evidence of malicious MBRs in memory. We can use this information about known infections to find other potential suspect machines as well. For example, if we know that a few machines in the enterprise are infected with a particular MBR infector we can use this information to scan other machines to look for this type of infection. The mbrparser will be released soon in Volatility 2.3 (and possibly later this week).

No comments:

Post a Comment