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

Add Windows Kernel structs #1219

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 151 additions & 9 deletions qiling/loader/pe.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Any, Dict, MutableMapping, NamedTuple, Optional, Mapping, Sequence, Tuple, Union

from unicorn import UcError
from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8
from unicorn.x86_const import UC_X86_REG_CR4, UC_X86_REG_CR8, UC_X86_REG_GS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UC_X86_REG_GS is imported but never used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be used to write the kernel structures pointers to the GS register with their correct offset but I didn't manage to get it working so I wrote to the value manually.


from qiling import Qiling
from qiling.arch.x86_const import FS_SEGMENT_ADDR, GS_SEGMENT_ADDR
Expand Down Expand Up @@ -629,16 +629,152 @@ def init_ki_user_shared_data(self):

# initialize an instance with a few key fields
kusd_obj = kust_struct.volatile_ref(self.ql.mem, kusd_addr)
kusd_obj.ImageNumberLow = 0x014c # IMAGE_FILE_MACHINE_I386
kusd_obj.ImageNumberHigh = 0x8664 # IMAGE_FILE_MACHINE_AMD64
kusd_obj.NtSystemRoot = self.ql.os.windir
kusd_obj.NtProductType = sysconf.getint('productType')
kusd_obj.NtMajorVersion = sysconf.getint('majorVersion')
kusd_obj.NtMinorVersion = sysconf.getint('minorVersion')
kusd_obj.KdDebuggerEnabled = 0
kusd_obj.NXSupportPolicy = 0 # NX_SUPPORT_POLICY_ALWAYSOFF
kusd_obj.ImageNumberLow = 0x014c # IMAGE_FILE_MACHINE_I386
kusd_obj.ImageNumberHigh = 0x8664 # IMAGE_FILE_MACHINE_AMD64
kusd_obj.NtSystemRoot = self.ql.os.windir
kusd_obj.NtProductType = sysconf.getint('productType')
kusd_obj.NtMajorVersion = sysconf.getint('majorVersion')
kusd_obj.NtMinorVersion = sysconf.getint('minorVersion')
kusd_obj.KdDebuggerEnabled = 0
kusd_obj.NXSupportPolicy = 0 # NX_SUPPORT_POLICY_ALWAYSOFF

self.ql.os.KUSER_SHARED_DATA = kusd_obj

def init_kpcr(self):
'''
Initialisation function for KPCR structure.

This structure's pointer should always be at gs:[0x18]
'''

sysconf = self.ql.os.profile['SYSTEM']
osconf = self.ql.os.profile[f'OS{self.ql.arch.bits}']

kpcr_addr = osconf.getint('KPCR')
kpcr_struct = KPCR
self.ql.mem.map(kpcr_addr, self.ql.mem.align_up(kpcr_struct.sizeof()), info='[kpcr]')

# Get address for KPRCB structure
kprcb_addr = osconf.getint('KPRCB')

# Initialize an instance with a few key fields
# @TODO: initialize StackBase & StackLimit
kpcr_obj = kpcr_struct.volatile_ref(self.ql.mem, kpcr_addr)
kpcr_obj.MajorVersion = sysconf.getint('majorVersion')
kpcr_obj.MinorVersion = sysconf.getint('minorVersion')
kpcr_obj.CurrentPrcb = kprcb_addr # Writes _KPRCB pointer to CurrentPrcb field
kpcr_obj.Self = kpcr_addr # Writes _KPCR pointer to Self field
kpcr_obj.Prcb = kprcb_addr # Writes _KPRCB pointer to Prcb field

# Writes KPCR pointer into GS:[0x18]
self.ql.mem.write_ptr(0x6000018, kpcr_addr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refrain from using magic numbers; there is a constant for GS that can be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a magic number. That is the exact offset for the KPRCB pointer in Windows KM. I agree that the constant + offset should be used, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, that's what I meant.


# @NOTE: Tests for writing structure pointers.
# Please don't remove.
self.ql.log.warn(f"KPCR CurrentPrcb: {kpcr_obj.CurrentPrcb:x}")
self.ql.log.warn(f"KPCR Self: {kpcr_obj.Self:x}")
self.ql.log.warn(f"KPCR Prcb: {kpcr_obj.Prcb:x}")

self.ql.os.KPCR = kpcr_obj

def init_kthread(self):
'''
Initialisation function for KTHREAD structure.

This structures pointer should always be at gs:[0x188]
'''

sysconf = self.ql.os.profile['SYSTEM']
osconf = self.ql.os.profile[f'OS{self.ql.arch.bits}']

kthread_addr = osconf.getint('KTHREAD')
kthread_struct = KTHREAD
self.ql.mem.map(kthread_addr, self.ql.mem.align_up(kthread_struct.sizeof()), info='[kthread]')

# Initialize an instance with key fields
kthread_obj = kthread_struct.volatile_ref(self.ql.mem, kthread_addr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This structure is initialized with magic numbers whose origin is unclear.
Is there a place [a Qilign object] we can take the values from? Consistency is key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure which numbers you're referring to are magic here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh if you mean the 0x188 for GS, then same as above. Should be changed to constant + offset

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually referred to the kthread_obj fields values: 11, 0x1000, 0x1500, 0x2000?

kthread_obj.ThreadLock = 11
kthread_obj.PreviousMode = 0
kthread_obj.InitialStack = 0x1000
kthread_obj.StackBase = 0x1500
kthread_obj.StackLimit = 0x2000
kthread_obj.MiscFlags |= 0x400
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value of MiscFlags is OR-ed with the existing one, but what is it...? That field was never initialized.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MiscFlags can be removed in this instance, I was using this for my own purpose and it won't be useful for Qiling generically.


# Writes KTHREAD pointer into GS:[0x188]
self.ql.mem.write_ptr(0x6000188, kthread_addr)

self.ql.os.KTHREAD = kthread_obj

def init_kprocess(self):
'''
Initialisation function for KPROCESS structure.
'''

sysconf = self.ql.os.profile['SYSTEM']
osconf = self.ql.os.profile[f'OS{self.ql.arch.bits}']

kproc_addr = osconf.getint('KPROCESS')
kproc_struct = KPROCESS
self.ql.mem.map(kproc_addr, self.ql.mem.align_up(kproc_struct.sizeof()), info='[kprocess]')

# Initialize an instance with key fields
kproc_obj = kproc_struct.volatile_ref(self.ql.mem, kproc_addr)

self.ql.os.KPROCESS = kproc_obj

def init_kprcb(self):
'''
Initialisation function for KPCRB structure.

This structures pointer should always be at gs:[0x20]
'''

sysconf = self.ql.os.profile['SYSTEM']
osconf = self.ql.os.profile[f'OS{self.ql.arch.bits}']

kprcb_addr = osconf.getint('KPRCB')
kprcb_struct = KPRCB
self.ql.mem.map(kprcb_addr, self.ql.mem.align_up(kprcb_struct.sizeof()), info='[kprcb]')

# Get address for KTHREAD & KNODE structures
kthread_addr = osconf.getint('KTHREAD')
knode_addr = osconf.getint('KNODE')

# Initialize and instance with a few key fields
kprcb_obj = kprcb_struct.volatile_ref(self.ql.mem, kprcb_addr)
kprcb_obj.CurrentThread = kthread_addr # Writes _KTHREAD pointer to CurrentThread field
kprcb_obj.IdleThread = kthread_addr # Writes _KTHREAD pointer to IdleThread field
kprcb_obj.ParentNode = knode_addr # Writes _KNODE pointer to ParentNode field

# Writes KPRCB pointer into GS:[0x20]
self.ql.mem.write_ptr(0x6000020, kprcb_addr)

# @NOTE: Tests for writing structure pointers.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What this is about? Why is it so important it shouldn't be removed?
Also, Qiling debugging info should be logged as debug rather than warn.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove these debug statements, I just had those there for my local branch and forgot to remove them when I submitted PR.

# please don't remove.
self.ql.log.warn(f"KPRCB IdleThread: {kprcb_obj.IdleThread:x}")
self.ql.log.warn(f"KPRCB CurrentThread: {kprcb_obj.CurrentThread:x}")
self.ql.log.warn(f"KPRCB ParentNode: {kprcb_obj.ParentNode:x}")

self.ql.os.KPRCB = kprcb_obj

def init_knode(self):
'''
Initialisation function for KNODE structure.
'''

sysconf = self.ql.os.profile['SYSTEM']
osconf = self.ql.os.profile[f'OS{self.ql.arch.bits}']

knode_addr = osconf.getint('KNODE')
knode_struct = KNODE
self.ql.mem.map(knode_addr, self.ql.mem.align_up(knode_struct.sizeof()), info='[knode]')

# Initialize struct with a few key fields
knode_obj = knode_struct.volatile_ref(self.ql.mem, knode_addr)

self.ql.os.KNODE = knode_obj



def init_security_cookie(self, pe: pefile.PE, image_base: int):
if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG'):
Expand Down Expand Up @@ -745,6 +881,12 @@ def load(self, pe: Optional[pefile.PE]):
self.init_registry_path()
self.init_eprocess()

self.init_kprcb()
self.init_kpcr()
self.init_kthread()
self.init_kprocess()
self.init_knode()

# set IRQ Level in CR8 to PASSIVE_LEVEL
self.ql.arch.regs.write(UC_X86_REG_CR8, 0)

Expand Down
2 changes: 1 addition & 1 deletion qiling/os/windows/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# See: https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types

LONG = PARAM_INTN
ULONG = PARAM_INTN
ULONG = PARAM_INT32
elicn marked this conversation as resolved.
Show resolved Hide resolved
CHAR = PARAM_INT8
UCHAR = PARAM_INT16
SHORT = PARAM_INT16
Expand Down
Loading