bsod

In this post we'll try to reverse Windows 7 BSoD using REVEN Axion in order to generate an image from memory and port accesses.

Find which video mode is used

As a first step, we will need to know which video mode is used by the BSoD. To achieve this we will use REVEN Axion to find VGA registers values and then we will develop a minimal kernel to make sure we found the right video mode. Finally, we'll generate an image from the VGA VRAM using REVEN Python API.

What is a port?

Programs can communicate with hardware through input-output ports. A port is an access to an external device, on 8, 16 or 32 bits, identified by an address between 0 and 65535.

In our case, ports are generally paired: there's one index port and one data port, both on 8 bits. Therefore, you need to write an index to the corresponding port first, then you can write data on the data port, allowing multiples registers on a single port pair. You can also send index and data to the index port at once as a 16 bit value, which will automatically forward the data to the data port since data port = index port + 1 most of the time.

However, there are some particular cases like the port 0x3c0 which is for both indexes and data, so you can't send the data to this port in a single access.

You can find a list of all VGA registers by port/index here.

Find memory/port accesses with REVEN Axion

Searching accesses filtered by device is quite easy thanks to Axion. We only have to select a Device query and choose the vga device.

search_widget_1

This returns all the memory/port accesses. Every colored bar shows either a memory access or a port access.

search_widget_2

On the following screenshot, we selected a write to port 0x3c4 in the Search widget for the example. In the disassembler window, we can see out dx, ax and in the CPU widget we can see that ax = 0xf02 and dx = 0x3c4. So the value 0xf is written to the index 0x02 of the port 0x3c4. According to the previous link, this is the Map Mask Register.

full_axion_port_write

Remember that REVEN Axion is timeless, meaning you can go back in the past of the execution. This will be really useful for the next part which consists in navigating through port accesses to find matches between the OSDev table and our case.

Finding the VGA mode

OSDev presents a table describing the values of relevant VGA registers according to their respective video mode. The first step is to find if the BSoD is using a text mode or a graphics mode.

Bit 0 of the Attribute Mode Control register (port 0x3c0, index 0x10) determines whether the graphics mode is enabled. So, we queried for the VGA - 3c0 subdevice and looked at the results: the bit is set, we are in graphics mode.

We repeated this operation for the other registers in this table, and determined the BSoD is displayed using VGA mode 0x12 (640x480 planar 16-bit color).

Interpret accesses in order to recreate an image

Dump memory accesses

We're going to dump all memory accesses between physical addresses 0xa0000 and 0xaffff (VGA VRAM) using this python code:

import reven
import struct

p = reven.Project("localhost", 13370)
t = p.traces()[0]

accesses = {}
for acc in t.search_memory_access(0xa0000, 0xffff):
    accesses[acc.point] = acc
accesses.sort(key=lambda tup:tup[0])

Generate image from VGA VRAM

We are now ready to generate the buffer:

buffer = [0] * 640 * 480
for p, acc in accesses:
    if not acc.is_write:
        continue
    offset = acc.physical_address & 0xffff
    buffer[offset] = acc.content

And then the black and white image:

rgb_array = [(0, 0, 0)] * 640 * 480
for x in range(640):
    for y in range(480):
        byte = buffer[y * 640 / 8 + x / 8]
        bit = 7 - x % 8
        color = (byte >> bit) & 1
        rgb_array[y * 640 + x] = (255 * color, 255 * color, 255 * color)

from PIL import Image
img = Image.new('RGB', (640, 480))
img.putdata(rgb_array)
img.save('out.png')

And that's what we get:

script_output_1

It's a little weird, right? Seems like we're missing something.

How VGA mode 0x12 works

Write modes

There are 4 different write modes, however only mode 1 and 2 are of interest in our case so we will summarize how they work. See this link for more detailed information about write modes.

In VGA you cannot write to the framebuffer directly, even in graphics mode. Mode 0x12 consists of four planes which each contain 1 bit of the 4-bit color index for a pixel. So if you, say, write an 8-bit value to plane 0 at position 0, you're actually writing bit 0 of the first 8 pixels of the plane. But again, you can't write to the planes directly: you're actually writing a value to a logical address (0xA0000-0xAFFFF in our case) which triggers an MMIO access. This host value is passed to the hardware pipeline which will, according to various settings, transform this value and output it to the planes.

In write mode 1, the host data is not used by the pipeline. Instead, it will directly use the content of the latch, which is filled with the contents of the last read access. Since we don't really need to emulate the latch for now, we will ignore all values if this mode is enabled. We didn't in the previous script, which is why we can't see horizontal lines sometimes.

In write mode 2, bits 0 to 3 of the data are duplicated across 8 bits of, respectively, planes 0 to 3. Then the bit mask is used to select which bits to keep.

Palette

The DAC palette can contain up to 256 16-bit colors, which are indexed in the 16 registers of the attribute controller, which in turn are referenced by the 4-bit color index value stored for each pixel.

The advantage of this system is that you can change colors simply by changing attribute controller's indexes, which avoids storing and redefining palette colors each time.

Reproducing accesses in a minimal kernel

In order to check that we have found the right video mode (and also for the sheer fun of it), I developed a minimal kernel which displays colored pixels. Again, I took this table as an example and looked for the values written during the BSoD in Axion to reproduce the content of the registers.

I released the code of what I've done here and it looks like this:

vga_kernel

Using bit masks

Now that we know and understand the BSoD write modes, let's get back to our script and try to reproduce the screen content correctly.

Actually, we need to get bit masks in order to render the text, because we're in write mode 2 as I mentioned above. So we need to reproduce the same steps we did in Axion earlier, but this time with Python code:

bit_masks = {}
def found_data(point, mask, name, previous_data):
    global bit_masks
    if previous_data != mask:
        bit_masks[point] = mask
    return mask

def browse_ports(register_list, search_devices):
    for vga_register in register_list:
        vga_register += [False, None]

    for point in t.search_point([reven.DeviceCriterion("vga", dev) for dev in search_devices], None, None, False):
        point = point.next().next()
        cpu = point.cpu()
        for vga_register in register_list:
            (port, index, name, selected_index, previous_data) = vga_register

            # Get the index and port values, whether it's a single access or two
            if cpu['ecx'] & 0xffff == port:
                selected_index = False
                if cpu['eax'] & 0xff == index:
                    selected_index = True
                    if "ax" in str(point):
                        previous_data = found_data(point, cpu['eax'] >> 8 & 0xff, name, previous_data)
            if cpu['ecx'] & 0xffff == port+1 and selected_index:
                previous_data = found_data(point, cpu['eax'] & 0xff, name, previous_data)

            vga_register[3] = selected_index
            vga_register[4] = previous_data

browse_ports([[0x3ce, 0x08, "0x3ce:8 Bit mask"]], ["VGA - 3c0"])

This script may look a tad complicated, but we're simply extracting the actual VGA register values from the port accesses, and sorting them out by execution points.

Next, we will merge accesses and bit masks in a single array and sort them:

sorted_bit_masks = []
for p in sorted(bit_masks.keys()):
    sorted_bit_masks.append((p, bit_masks[p]))

sorted_accesses = []
for p in sorted(accesses.keys()):
    sorted_accesses.append((p, accesses[p]))

sorted_all = sorted_bit_masks + sorted_accesses
sorted_all.sort(key=lambda tup:tup[0])

To generate the buffer, we'll need to apply the bit mask first, so our generation code becomes:

buffer = [0] * 640 * 480
current_mask = None
for p, acc in sorted_all:
    if type(acc) is int:
        current_mask = acc
        continue
    if not acc.is_write:
        continue
    offset = acc.physical_address & 0xffff
    if acc.content & 1:
        buffer[offset] |= current_mask
    else:
        buffer[offset] &= ~current_mask

And finally, we can generate our image using the same code than above:

script_output_2

Now it's perfect. We saw earlier that the data written in write mode 1 is ignored. If we take a look at the data written in this mode with Axion, we see that only zeroes are written, so we don't need to take it into account.

Note that we didn't care about the palette and planes 1 to 3 in this example.

Conclusion

We saw an example of reversing using Axion, then using the Python API. I made a widget for Axion using what we've done in this article, which is in the integration phase. It allows to see pixels displaying one by one with full palette handling.

I hope you enjoyed reading this, if you have any question feel free to post a comment.


Comments

comments powered by Disqus