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

Designing a API to read/write syscall's arguments #8

Open
Wenzel opened this issue May 25, 2017 · 3 comments
Open

Designing a API to read/write syscall's arguments #8

Wenzel opened this issue May 25, 2017 · 3 comments

Comments

@Wenzel
Copy link
Member

Wenzel commented May 25, 2017

Problem 1 : an API to access the syscall arguments

We need to design a API on top of the Sycall object that we are building from a Nitro eventl to provide a transparent way to get the syscall arguments for the user accross different operating systems and conventions.

Let's take for example NtOpenFile MSDN definition:

NTSTATUS NtOpenFile(
  _Out_ PHANDLE            FileHandle,
  _In_  ACCESS_MASK        DesiredAccess,
  _In_  POBJECT_ATTRIBUTES ObjectAttributes,
  _Out_ PIO_STATUS_BLOCK   IoStatusBlock,
  _In_  ULONG              ShareAccess,
  _In_  ULONG              OpenOptions
);

The arguments will be passed to the kernel in a specific way, which depends on

  • The operating system calling convention
  • The system call type (syscall or sysenter)

Windows/Linux syscall arguments passing

Windows sysenter Windows syscall Linux sysenter Linux syscall
arg 1 edx rcx ebx rdi
arg 2 edx + 4 rdx ecx rsi
arg 3 edx + 8 r8 edx rdx
arg 4 edx + 12 r9 esi r10
arg 5 edx + 16 rsp + ? edi r8
arg 6 edx + 20 rsp + ? + 8 ebp r9
arg 7 edx + 24 rsp + ? + 16 no no

References

Key findings

  • Linux syscalls have a maximum of 6 parameters, always
  • Linux always uses registers for the syscall argument passing
  • Windows uses registers and memory.

Problem 2 : Modifying the syscall arguments

An interesting feature of Nitro is the ability to modify the syscall arguments on the fly, and change the behavior of the guest.

Following what we discovered previously, if we want to provide such an API, it has to cover 2 cases:

  • argument is stored in a register
  • argument is stored in memory (Windows)

Respectively, we have 2 solutions :

  • make use of nitro_set_regs ioctl to replace the registers value
  • make use of libvmi's vmi_write_va API to write the memory

Current design

This is how a Nitro syscall callback currently looks like

        def enter_NtCreateFile(syscall):
            KeyHandle, DesiredAccess, object_attributes = syscall.collect_args(3)
            obj = ObjectAttributes(object_attributes, syscall.process)
            buffer = obj.ObjectName.Buffer
            access = FileAccessMask(DesiredAccess)
            syscall.hook = {
                'object_name': buffer,
                'access': access.rights
            }

Issues

  1. collect_args is limited to the Windows/syscall convention, which is hardcoded right now
  2. we are forcing the developer to unpack all values, even if he just wants the 3rd argument, he will have to write: *rest, object_attributes = syscall.collect_args(3)

Proposal

A new callback interface has been designed previously.
The backend is now sent as a parameter.

Apart from that, if we want the Syscall object to be able to modify the arguments, we need to pass the Nitro object which provides the API over nitro's ioctls, as well as libvmi.

Also, it might be interesting to make use of Python's __getitem__ and __setitem__ methods, to provide a list like API for the developer. He could access the arguments directly index them and this solves the previous issue raised about collect_args.

What a use case could look like:

        def enter_NtCreateFile(backend, syscall):
            object_attributes = syscall.args[2] # getting the 3rd argument
            # modifying a value
            syscall.args[2] = 0 # setting object_attributes pointer to NULL

-> Behing the scene, how could we design the Syscall class to make an interface accross the various calling conventions that we saw at the beginning ?

@noxdafox
Copy link

noxdafox commented May 29, 2017

Have you explored the idea of OS specific SysCalls? Something like NTSysCall and UnixSysCall or WindowsSysCall and LinuxSyscall?

It is important to choose the right level of abstraction when designing such APIs. And at this level of abstraction we are very close to the OS implementation details.

This is just an example:

class SysCall:
    __slots__ = 'name', 'params'

class WindowsSysCall(SysCall):
    __slots__ = 'name', 'params', 'mem_params'

where mem_params attribute allows to resolve parameters in memory.

I do agree with the proposal of leaving the parameters as a list. The End User could build an extra abstraction layer to map his/her own SysCalls with the parameters names.

class NtOpenFile(SysCall):
    def __init__(self):
        self.map = {'FileHandle': 0, 'DesiredAccess': 1, ... }

    def __getattr__(self, attr):
        index = self._map[attr]
        return self.params[index]
    
syscall = NtOpenFile()
syscall.FileHandle = 42    

@Wenzel
Copy link
Member Author

Wenzel commented May 31, 2017

The PR #11 brings a first answer to this issue.

The syscall class has now an ArgumentMap, which hides the translation of the OS convention.

It is defined in this way:

class ArgumentMap:

    CONVENTION = {
        SyscallType.syscall: [
            (SyscallArgumentType.register, 'rcx'),
            (SyscallArgumentType.register, 'rdx'),
            (SyscallArgumentType.register, 'r8'),
            (SyscallArgumentType.register, 'r9'),
            (SyscallArgumentType.memory, 5),
        ],
}

The argument can be mapped in a register, or in memory.

If the argument index is out of bounds, it will be fetched in memory anyway, as we don't know precisely what is the maximum number of parameters allow for a Windows syscall.

@shlomopongratz
Copy link

Hi,

I think there is another issue to be addressed,
I refer to issue 227 you opened on Rekall i.e. https://github.com/google/rekall/issues/227
The simplest workaround is to ignore all multiple entries in SSDT.

Best regards,

S.P.

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