Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Displays the Windows Interrupt Descriptor Table #974

Closed
Ma1icious opened this issue Jun 28, 2023 · 12 comments
Closed

Displays the Windows Interrupt Descriptor Table #974

Ma1icious opened this issue Jun 28, 2023 · 12 comments

Comments

@Ma1icious
Copy link

Ma1icious commented Jun 28, 2023

There is a plugin showing windows IDT in volatility 2, but not in volatility 3.
I tried to analyze the code in volatility 2 and migrate it to volatility 3. But I have a problem.
In volatility 2, we can use in _KDDEBUGGER_DATA64 class "self.KiProcessorBlock.Dereference()" to get the cpu_array, but returned in the volatility of 3 different results.
In volatility 2:

>>> cpu_array = self.KiProcessorBlock.dereference()
>>> type(cpu_array)
<class 'volatility.obj.Array'>

In volatility 3:

>>> kernel = self.context.modules[self.config["kernel"]]
>>> layer_name = kernel.layer_name
>>> symbol_table = kernel.symbol_table_name
>>> kvo = context.layers[layer_name].config["kernel_virtual_offset"]
>>> ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
>>> KiProcessorBlock = ntkrnlmp.object(
...            object_type="pointer",
...            layer_name=layer_name,
...            offset=ntkrnlmp.get_symbol("KiProcessorBlock").address)

>>> type(KiProcessorBlock.dereference())
<class 'volatility3.framework.objects.Void'>

Does anyone know how to solve this problem? Or does anyone know how to get IDT content?

@eve-mem
Copy link
Contributor

eve-mem commented Jun 28, 2023

Hello,

This is likely because when you made the object you specifically created a pointer type.

I don't have a windows sample to hand to double check, but if you do this i think you'll get the result you are after.

KiProcBlock = ntkrnlmp.object_from_symbol(symbol_name="KiProcessorBlock")

(On mobile, please forgive any typos!)

Edit: also I don't think you'll need to build the module yourself, you should be able to grab it directly from the context by name. Here's a simple example from the Linux pslist plugin as an example, you should be able to do the same with your windows plugin.

https://github.com/volatilityfoundation/volatility3/blob/develop/volatility3/framework/plugins/linux/pslist.py#L140

@Ma1icious
Copy link
Author

Ma1icious commented Jun 28, 2023

Hello,

This is likely because when you made the object you specifically created a pointer type.

I don't have a windows sample to hand to double check, but if you do this i think you'll get the result you are after.

KiProcBlock = ntkrnlmp.object_from_symbol(symbol_name="KiProcessorBlock")

(On mobile, please forgive any typos!)

Edit: also I don't think you'll need to build the module yourself, you should be able to grab it directly from the context by name. Here's a simple example from the Linux pslist plugin as an example, you should be able to do the same with your windows plugin.

https://github.com/volatilityfoundation/volatility3/blob/develop/volatility3/framework/plugins/linux/pslist.py#L140

Thank you for your reply. I tried the solution you mentioned before, but it produced an error:

>>> KiProcessorBlock = ntkrnlmp.object_from_symbol("KiProcessorBlock")
File "*", line 303, in object_from_symbol
    raise TypeError(f"Symbol {symbol_val.name} has no associated type")
TypeError: Symbol KiProcessorBlock has no associated type

@eve-mem
Copy link
Contributor

eve-mem commented Jun 28, 2023

Ah okay, in the ISF this symbol doesn't have a type. This is where my lack of windows knowledge won't help.

If "cpu_array" is a special type in windows you should be able to create that type at the address for KiProcessorBlock. (Like you did with the pointer at first)

Otherwise if it's more of a basic list you can likely do the same as the windows plugin here.

https://github.com/volatilityfoundation/volatility3/blob/develop/volatility3/framework/plugins/windows/pslist.py#L186

If it is just an array of pointers there is another option, but I can't find an example on my phone.

I'll be able to give you some better ideas when i get a windows sample in front of me, in the meantime hopefully someone with more windows knowledge can jump in.

@Ma1icious
Copy link
Author

Ma1icious commented Jun 28, 2023

Ah okay, in the ISF this symbol doesn't have a type. This is where my lack of windows knowledge won't help.

If "cpu_array" is a special type in windows you should be able to create that type at the address for KiProcessorBlock. (Like you did with the pointer at first)

Otherwise if it's more of a basic list you can likely do the same as the windows plugin here.

https://github.com/volatilityfoundation/volatility3/blob/develop/volatility3/framework/plugins/windows/pslist.py#L186

If it is just an array of pointers there is another option, but I can't find an example on my phone.

I'll be able to give you some better ideas when i get a windows sample in front of me, in the meantime hopefully someone with more windows knowledge can jump in.

volatility 2:

def kpcrs(self):
    if self.obj_native_vm.profile.metadata.get('memory_model', '32bit') == '32bit':
        prcb_member = "PrcbData"
    else:
        prcb_member = "Prcb"
    cpu_array = self.KiProcessorBlock.dereference()

    for p in cpu_array:
        print(p)
        # Terminate the loop if an item in the array is 
        # invalid (ie paged) or if the pointer is NULL. 
        if p == None or p == 0:
            break

        kpcrb = p.dereference_as("_KPRCB")

        kpcr = obj.Object("_KPCR", offset = kpcrb.obj_offset -
                self.obj_native_vm.profile.get_obj_offset("_KPCR", prcb_member),
                vm = self.obj_native_vm,
                parent = self,
                )

        if kpcr.is_valid():
            yield kpcr

output:

4292866336
4152873248
4152906016
4152938784
4152971552
4153004320

volatility 3

KiProcessorBlock = ntkrnlmp.object(
    object_type="pointer",
    layer_name=layer_name,
    offset=ntkrnlmp.get_symbol("KiProcessorBlock").address)
print(KiProcessorBlock.dereference().vol.offset)

output:

4292866336

As you can see, I've been able to get the first element of the cpu_array array in volatility 2.
I have another problem. In volalitity 2, self.KiProcessorBlock.dereference() actually returns an array object, but in volatility 3, KiProcessorBlock.dereference() just returns a Void type variable.
Since I'm not familiar with the layer Settings in volatility 3, I'm curious how can I make it return an Array object? How does it determine the type of the target object?


Edit: I see that volatility 3 uses self.vol.subtype to create new objects. Can I modify the subtype of the KiProcessorBlock object to make it create an Array object? I'll give it a try!


Edit: I tried to set KiProcessorBlock.vol.subtype to volatility3.framework.objects.Array, but it didn't work in KiProcessorBlock.dereference(). 😭

@Ma1icious
Copy link
Author

Ma1icious commented Jun 29, 2023

Ah okay, in the ISF this symbol doesn't have a type. This is where my lack of windows knowledge won't help.

If "cpu_array" is a special type in windows you should be able to create that type at the address for KiProcessorBlock. (Like you did with the pointer at first)

Otherwise if it's more of a basic list you can likely do the same as the windows plugin here.

https://github.com/volatilityfoundation/volatility3/blob/develop/volatility3/framework/plugins/windows/pslist.py#L186

If it is just an array of pointers there is another option, but I can't find an example on my phone.

I'll be able to give you some better ideas when i get a windows sample in front of me, in the meantime hopefully someone with more windows knowledge can jump in.

I can currently get the first IDT record from the first CPU, and I'm not sure how I can get all of them.

def _generator(self):
    kernel = self.context.modules[self.config["kernel"]]
    layer_name = kernel.layer_name
    symbol_table = kernel.symbol_table_name
    kvo = self.context.layers[layer_name].config["kernel_virtual_offset"]
    ntkrnlmp = self.context.module(symbol_table, layer_name=layer_name, offset=kvo)

    KiProcessorBlock = ntkrnlmp.object(
        object_type="pointer",
        layer_name=layer_name,
        offset=ntkrnlmp.get_symbol("KiProcessorBlock").address)
    
    kpcrb_offset = KiProcessorBlock.dereference().vol.offset
    
    kpcr_offset = ntkrnlmp.get_type("_KPCR").relative_child_offset("PrcbData")
    kpcr = ntkrnlmp.object(
        object_type="_KPCR",
        layer_name=layer_name,
        offset=kpcrb_offset - kpcr_offset,
        absolute=True)

    idt_struct = kpcr.IDT.dereference()
    idt = _KIDTENTRY(idt_struct)
    module = self.get_module(self.context, layer_name, symbol_table, idt.Address)
    yield (0, (format_hints.Hex(idt.Selector), format_hints.Hex(idt.Address), module))

Output:

Selector	Value	        Module
0x8	        0x805431a0	ntoskrnl.exe

Its output is the same as the first output I got in volatility 2.
The problem now is that in volatility 2 *.dereference() returns an Array object, but in volatility 3 it returns a non-array type object (e.g. Void, StructType).

@Ma1icious
Copy link
Author

I got all the IDT entries in the first CPU by adding an offset to the address of the first IDT entry. Now maybe I should try to get all the IDT entries in the CPU.🥳

@Ma1icious
Copy link
Author

I solved the problem.

@eve-mem
Copy link
Contributor

eve-mem commented Jun 29, 2023

Great stuff @Ma1icious - glad you managed to make something that works for you. You beat me to getting a Windows sample to test with!

If you do make a plugin that you are happy with, do consider making a pull request to share your knowledge with everyone. I'd also recommend taking a look at the volatility slack channel, it's sometimes easier to ask a quick question there.

@Ma1icious
Copy link
Author

Ma1icious commented Jun 29, 2023

Great stuff @Ma1icious - glad you managed to make something that works for you. You beat me to getting a Windows sample to test with!

If you do make a plugin that you are happy with, do consider making a pull request to share your knowledge with everyone. I'd also recommend taking a look at the volatility slack channel, it's sometimes easier to ask a quick question there.

I tested it and it was able to output the same results as in volatility 2 (x86 only, of course). I'm going to submit the code to share with everyone, although I didn't write it elegantly.

@eve-mem
Copy link
Contributor

eve-mem commented Jun 29, 2023

That's great, making a pull request allows everyone to take a look at the code and offer suggestions.

For example, I think yiu can get your kpcrb_offset directly without having to do the .dereference().vol.offset part.

e.g. These provide the same output:

(layer_name) >>> KiProcessorBlock
4292866336
(layer_name) >>> KiProcessorBlock.dereference().vol.offset
4292866336

@Ma1icious
Copy link
Author

That's great, making a pull request allows everyone to take a look at the code and offer suggestions.

For example, I think yiu can get your kpcrb_offset directly without having to do the .dereference().vol.offset part.

e.g. These provide the same output:

(layer_name) >>> KiProcessorBlock
4292866336
(layer_name) >>> KiProcessorBlock.dereference().vol.offset
4292866336

Oh, right! They are the same! Thank you for pointing out, I will make corresponding modifications.

@ikelos
Copy link
Member

ikelos commented Jul 1, 2023

Thank you for your reply. I tried the solution you mentioned before, but it produced an error:

>>> KiProcessorBlock = ntkrnlmp.object_from_symbol("KiProcessorBlock")
File "*", line 303, in object_from_symbol
    raise TypeError(f"Symbol {symbol_val.name} has no associated type")
TypeError: Symbol KiProcessorBlock has no associated type

Sorry it took me so long to get round to this, @eve-mem is right, it should be possible to just construct the type from its name. Sadly the link between the name and the type isn't present in windows PDBs (as far as I'm aware), and we haven't allowed the convenience function to specify a type, but that's something I think we can add without too much trouble, so eventually it would look something like ntkrnlmp.object_from_symbol("KiProcessorBlock", object_type = "<NAME_OF_OBJECT_TYPE>")...

volatility 3

KiProcessorBlock = ntkrnlmp.object(
    object_type="pointer",
    layer_name=layer_name,
    offset=ntkrnlmp.get_symbol("KiProcessorBlock").address)
print(KiProcessorBlock.dereference().vol.offset)

Here, you're only getting one response, because you asked volatility to make pointer at the position of the first element of the array (so you only got one item). To make an array (which you should then be able to iterate over) should be able to specify "array" as the object_type and then specify a subtype which will be what each element should be. Unfortunately, if you want that to be a pointer to something, it becomes cumbersome to construct the pointer type and then pass them in as the subtype, so we made a convenience function for that...

from volatility3.framework.objects import utility

KiProcessors = utility.array_of_pointers(context = context, array = KiProcessorBlock, count = cpu_count, subtype = <WHATEVER_TYPE_THE_POINTER_POINTS_TO>)

Which should turn your single pointer into a full array that you can iterate over. Sadly it needs to act an existing object of some kind, because it's essentially a call to cast, but any initial type (include void) should be fine. 5:)

Hope all that helps! Feel free to ask more here, or catch us on slack if you'd like more interactive answers... 5:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants