Skip to content

Improve log args for a few hooks: #94

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

Open
wants to merge 2 commits into
base: capemon
Choose a base branch
from

Conversation

KillerInstinct
Copy link
Contributor

  • Log Process ID in NtReadVirtualMemory/NtWriteVirtualMemory/ReadProcessMemory/WriteProcessMemory if the process handle used does not represent the current running process
  • Remove a dupe arg from NtOpenThread
  • Better logging for NtGetContextThread; logs have appropriate arguments based on the context flags
  • Cloned this logic to NtSetContextThread as well
  • Added PID/TID to NtGet/SetContextThread

Changes inspired by trying to detect https://research.checkpoint.com/2025/waiting-thread-hijacking/

I've found it's quite difficult to keep track of all all of the handles being mapped to various identifiers (PID/TID) from the various APIs in a signature without these improvements.

- Log Process ID in NtReadVirtualMemory/NtWriteVirtualMemory/ReadProcessMemory/WriteProcessMemory if the process handle used does not represent the current running process
- Remove a dupe arg from NtOpenThread
- Better logging for NtGetContextThread; logs have appropriate arguments based on the context flags
- Cloned this logic to NtSetContextThread as well
- Added PID/TID to NtGet/SetContextThread

Changes inspired by trying to detect https://research.checkpoint.com/2025/waiting-thread-hijacking/

I've found it's quite difficult to keep track of all all of the handles being mapped to various identifiers (PID/TID) from the various APIs in a signature without these improvements.
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Hello @KillerInstinct, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

This pull request focuses on improving the logging capabilities of several hook functions related to process and thread memory access. The primary goal is to enhance the ability to track handles and identifiers (PID/TID) across different APIs, which is crucial for detecting advanced exploitation techniques like thread hijacking. The changes involve adding process IDs to log messages when the process handle used doesn't represent the current running process, removing duplicate arguments from NtOpenThread logging, and providing more detailed logging for NtGetContextThread and NtSetContextThread based on context flags. The changes affect hook_process.c and hook_thread.c.

Highlights

  • Enhanced Logging for Memory Access Hooks: The NtReadVirtualMemory, ReadProcessMemory, NtWriteVirtualMemory, and WriteProcessMemory hooks now log the process ID when the handle refers to a different process than the current one. This helps in identifying cross-process memory operations.
  • Improved Thread Context Logging: The NtGetContextThread and NtSetContextThread hooks now log relevant register values (Instruction Pointer, RAX, RBX, RCX, RDX, RSP) and process/thread IDs based on the context flags, providing more detailed information for debugging and security analysis.
  • NtOpenThread Logging: The logging for NtOpenThread has been corrected to remove a duplicate argument.

Changelog

  • hook_process.c
    • Modified NtReadVirtualMemory to log the process ID if the process handle doesn't belong to the current process (lines 901-912).
    • Modified ReadProcessMemory to log the process ID if the process handle doesn't belong to the current process (lines 937-947).
    • Modified NtWriteVirtualMemory to log the process ID if the process handle doesn't belong to the current process (lines 977-988).
    • Modified WriteProcessMemory to log the process ID if the process handle doesn't belong to the current process (lines 1025-1036).
  • hook_thread.c
    • Removed a duplicate argument from the LOQ_ntstatus call in NtOpenThread (lines 289-290).
    • Enhanced logging in NtGetContextThread to include register values and process/thread IDs based on context flags (lines 309-385).
    • Enhanced logging in NtSetContextThread to include register values and process/thread IDs based on context flags (lines 440-516).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


A handle's a key,
To memory's domain we see,
Logs whisper the tale,
Of reads that prevail,
In process's vast decree.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request aims to enhance the logging capabilities of several hook functions, providing more detailed information for debugging and analysis, particularly in the context of thread hijacking detection. The changes generally improve the clarity and usefulness of the logs. However, there are some areas where the logic can be simplified and potential issues addressed.

Summary of Findings

  • Redundant logging in conditional blocks: The LOQ_* macros are called in both the if and else blocks when pid != GetCurrentProcessId(). This results in redundant logging, as the only difference is the inclusion of the ProcessId argument in the if block. This can be simplified by moving the LOQ_* call outside the conditional and conditionally including the ProcessId argument.
  • Incorrect function call in NtWriteVirtualMemory/WriteProcessMemory: The code uses GetCurrentProcess() instead of GetCurrentProcessId() in the conditional check for NtWriteVirtualMemory and WriteProcessMemory. This is incorrect as GetCurrentProcess() returns a pseudo-handle, not the process ID. This needs to be corrected to ensure the conditional logic works as intended.
  • Inconsistent logging format in ReadProcessMemory: The LOQ_bool macro in ReadProcessMemory includes the ProcessId as the last argument, while the other memory read/write functions include it earlier in the argument list. This inconsistency should be addressed for better readability and maintainability.

Merge Readiness

The pull request introduces valuable logging improvements that aid in debugging and analysis. However, the identified issues regarding redundant logging, incorrect function calls, and inconsistent formatting should be addressed before merging. I am unable to directly approve this pull request, and other reviewers should also review this code before merging. At a minimum, the high severity issues should be addressed before merging.

hook_process.c Outdated
Comment on lines 979 to 980
if (pid != GetCurrentProcess()) {
LOQ_ntstatus(

Choose a reason for hiding this comment

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

high

The code uses GetCurrentProcess() instead of GetCurrentProcessId(). GetCurrentProcess() returns a pseudo-handle, not the process ID. This is incorrect.

Suggested change
if (pid != GetCurrentProcess()) {
LOQ_ntstatus(
if (pid != GetCurrentProcessId()) {

hook_process.c Outdated
@@ -970,8 +1024,27 @@ HOOKDEF(BOOL, WINAPI, WriteProcessMemory,

pid = pid_from_process_handle(hProcess);

LOQ_bool("process", "ppBhs", "ProcessHandle", hProcess, "BaseAddress", lpBaseAddress,
"Buffer", lpNumberOfBytesWritten, lpBuffer, "BufferLength", *lpNumberOfBytesWritten, "StackPivoted", is_stack_pivoted() ? "yes" : "no");
if (pid != GetCurrentProcess()) {

Choose a reason for hiding this comment

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

high

The code uses GetCurrentProcess() instead of GetCurrentProcessId(). GetCurrentProcess() returns a pseudo-handle, not the process ID. This is incorrect.

Suggested change
if (pid != GetCurrentProcess()) {
if (pid != GetCurrentProcessId()) {

Comment on lines +903 to +921
if (pid != GetCurrentProcessId()) {
LOQ_ntstatus(
"process", "piphB",
"ProcessHandle", ProcessHandle,
"ProcessId", pid,
"BaseAddress", BaseAddress,
"Size", NumberOfBytesToRead,
"Buffer", NumberOfBytesRead, Buffer
);
}
else {
LOQ_ntstatus(
"process", "piphB",
"ProcessHandle", ProcessHandle,
"BaseAddress", BaseAddress,
"Size", NumberOfBytesToRead,
"Buffer", NumberOfBytesRead, Buffer
);
}

Choose a reason for hiding this comment

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

medium

The LOQ_ntstatus calls are identical in both the if and else blocks, except for the inclusion of ProcessId in the if block. This redundancy can be eliminated by moving the LOQ_ntstatus call outside the conditional and conditionally including the ProcessId argument in the format string.

Suggested change
if (pid != GetCurrentProcessId()) {
LOQ_ntstatus(
"process", "piphB",
"ProcessHandle", ProcessHandle,
"ProcessId", pid,
"BaseAddress", BaseAddress,
"Size", NumberOfBytesToRead,
"Buffer", NumberOfBytesRead, Buffer
);
}
else {
LOQ_ntstatus(
"process", "piphB",
"ProcessHandle", ProcessHandle,
"BaseAddress", BaseAddress,
"Size", NumberOfBytesToRead,
"Buffer", NumberOfBytesRead, Buffer
);
}
DWORD pid = pid_from_process_handle(ProcessHandle);
LOQ_ntstatus(
"process", pid != GetCurrentProcessId() ? "piphB" : "pphB",
"ProcessHandle", ProcessHandle,
pid != GetCurrentProcessId() ? "ProcessId" : NULL, pid != GetCurrentProcessId() ? pid : 0,
"BaseAddress", BaseAddress,
"Size", NumberOfBytesToRead,
"Buffer", NumberOfBytesRead, Buffer
);

Comment on lines +939 to +957
if (pid != GetCurrentProcessId()) {
LOQ_bool(
"process", "pphB",
"ProcessHandle", hProcess,
"BaseAddress", lpBaseAddress,
"Size", nSize,
"Buffer", lpNumberOfBytesRead, lpBuffer,
"ProcessId", pid
);
}
else {
LOQ_bool(
"process", "pphB",
"ProcessHandle", hProcess,
"BaseAddress", lpBaseAddress,
"Size", nSize,
"Buffer", lpNumberOfBytesRead, lpBuffer,
"ProcessId", pid
);

Choose a reason for hiding this comment

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

medium

Similar to the previous comment, the LOQ_bool calls are redundant. Move the call outside the conditional and conditionally include the ProcessId argument.

Suggested change
if (pid != GetCurrentProcessId()) {
LOQ_bool(
"process", "pphB",
"ProcessHandle", hProcess,
"BaseAddress", lpBaseAddress,
"Size", nSize,
"Buffer", lpNumberOfBytesRead, lpBuffer,
"ProcessId", pid
);
}
else {
LOQ_bool(
"process", "pphB",
"ProcessHandle", hProcess,
"BaseAddress", lpBaseAddress,
"Size", nSize,
"Buffer", lpNumberOfBytesRead, lpBuffer,
"ProcessId", pid
);
DWORD pid = pid_from_process_handle(hProcess);
LOQ_bool(
"process", pid != GetCurrentProcessId() ? "pphB" : "pphB",
"ProcessHandle", hProcess,
"BaseAddress", lpBaseAddress,
"Size", nSize,
"Buffer", lpNumberOfBytesRead, lpBuffer,
pid != GetCurrentProcessId() ? "ProcessId" : NULL, pid
);

Comment on lines +980 to +998
LOQ_ntstatus(
"process", "pipBhs",
"ProcessHandle", ProcessHandle,
"ProcessId", pid,
"BaseAddress", BaseAddress,
"Buffer", NumberOfBytesWritten, Buffer,
"BufferLength", is_valid_address_range((ULONG_PTR)NumberOfBytesWritten, 4) ? *NumberOfBytesWritten : 0,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);
}
else {
LOQ_ntstatus(
"process", "pipBhs",
"ProcessHandle", ProcessHandle,
"BaseAddress", BaseAddress,
"Buffer", NumberOfBytesWritten, Buffer,
"BufferLength", is_valid_address_range((ULONG_PTR)NumberOfBytesWritten, 4) ? *NumberOfBytesWritten : 0,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);

Choose a reason for hiding this comment

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

medium

Similar to the previous comments, the LOQ_ntstatus calls are redundant. Move the call outside the conditional and conditionally include the ProcessId argument in the format string.

Suggested change
LOQ_ntstatus(
"process", "pipBhs",
"ProcessHandle", ProcessHandle,
"ProcessId", pid,
"BaseAddress", BaseAddress,
"Buffer", NumberOfBytesWritten, Buffer,
"BufferLength", is_valid_address_range((ULONG_PTR)NumberOfBytesWritten, 4) ? *NumberOfBytesWritten : 0,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);
}
else {
LOQ_ntstatus(
"process", "pipBhs",
"ProcessHandle", ProcessHandle,
"BaseAddress", BaseAddress,
"Buffer", NumberOfBytesWritten, Buffer,
"BufferLength", is_valid_address_range((ULONG_PTR)NumberOfBytesWritten, 4) ? *NumberOfBytesWritten : 0,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);
pid = pid_from_process_handle(ProcessHandle);
LOQ_ntstatus(
"process", pid != GetCurrentProcessId() ? "pipBhs" : "ppBhs",
"ProcessHandle", ProcessHandle,
pid != GetCurrentProcessId() ? "ProcessId" : NULL, pid != GetCurrentProcessId() ? pid : 0,
"BaseAddress", BaseAddress,
"Buffer", NumberOfBytesWritten, Buffer,
"BufferLength", is_valid_address_range((ULONG_PTR)NumberOfBytesWritten, 4) ? *NumberOfBytesWritten : 0,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);

Comment on lines +1028 to +1046
LOQ_bool(
"process", "pipBhs",
"ProcessHandle", hProcess,
"ProcessId", pid,
"BaseAddress", lpBaseAddress,
"Buffer", lpNumberOfBytesWritten, lpBuffer,
"BufferLength", *lpNumberOfBytesWritten,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);
}
else {
LOQ_bool(
"process", "ppBhs",
"ProcessHandle", hProcess,
"BaseAddress", lpBaseAddress,
"Buffer", lpNumberOfBytesWritten, lpBuffer,
"BufferLength", *lpNumberOfBytesWritten,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);

Choose a reason for hiding this comment

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

medium

Similar to the previous comments, the LOQ_bool calls are redundant. Move the call outside the conditional and conditionally include the ProcessId argument.

Suggested change
LOQ_bool(
"process", "pipBhs",
"ProcessHandle", hProcess,
"ProcessId", pid,
"BaseAddress", lpBaseAddress,
"Buffer", lpNumberOfBytesWritten, lpBuffer,
"BufferLength", *lpNumberOfBytesWritten,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);
}
else {
LOQ_bool(
"process", "ppBhs",
"ProcessHandle", hProcess,
"BaseAddress", lpBaseAddress,
"Buffer", lpNumberOfBytesWritten, lpBuffer,
"BufferLength", *lpNumberOfBytesWritten,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);
pid = pid_from_process_handle(hProcess);
LOQ_bool(
"process", pid != GetCurrentProcessId() ? "pipBhs" : "ppBhs",
"ProcessHandle", hProcess,
pid != GetCurrentProcessId() ? "ProcessId" : NULL, pid != GetCurrentProcessId() ? pid : 0,
"BaseAddress", lpBaseAddress,
"Buffer", lpNumberOfBytesWritten, lpBuffer,
"BufferLength", *lpNumberOfBytesWritten,
"StackPivoted", is_stack_pivoted() ? "yes" : "no"
);

@KillerInstinct
Copy link
Contributor Author

I don't have a cape rig to actually test changes, but you can use this PoC I made on public CAPE :)
https://capesandbox.com/analysis/2238/

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

Successfully merging this pull request may close these issues.

1 participant