diff --git a/hook_file.c b/hook_file.c index a7e1b8b..0a66745 100644 --- a/hook_file.c +++ b/hook_file.c @@ -26,6 +26,7 @@ along with this program. If not, see . #include "misc.h" #include "ignore.h" #include "lookup.h" +#include "hook_file.h" #include "config.h" #define DUMP_FILE_MASK ((GENERIC_ALL | GENERIC_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | MAXIMUM_ALLOWED) & ~SYNCHRONIZE) @@ -605,25 +606,293 @@ HOOKDEF(NTSTATUS, WINAPI, NtDeviceIoControlFile, __out PVOID OutputBuffer, __in ULONG OutputBufferLength ) { + lasterror_t lasterrors; + get_lasterrors(&lasterrors); ULONG_PTR length; - NTSTATUS ret = Old_NtDeviceIoControlFile(FileHandle, Event, - ApcRoutine, ApcContext, IoStatusBlock, IoControlCode, - InputBuffer, InputBufferLength, OutputBuffer, - OutputBufferLength); + ULONG origbufferloglen = min((ULONG)buffer_log_max, InputBufferLength); + PCHAR origbuffer = malloc(origbufferloglen); + BOOLEAN hasorigbuffer = FALSE; + NTSTATUS ret; + + // Save off a copy of the buffer before calling the hook, as it may not be the same after the call + if (origbuffer) { + __try { + memcpy(origbuffer, InputBuffer, origbufferloglen); + hasorigbuffer = TRUE; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + hasorigbuffer = FALSE; + } + } + + + /* + * Check for AFD_SEND, we need to aggregate the buffer before calling the hook as calling the hook will clobber InputBuffer + * Simply do the logic to generate the buffer we want to log here, we'll log it later with other IOCTL logging. + */ + PCHAR aggregated_send_payload = NULL; + ULONG send_payload_size_to_log = 0; + if (IoControlCode == IOCTL_AFD_SEND) { + __try { + if (InputBuffer && InputBufferLength >= sizeof(AFD_SEND_INFO)) { + PAFD_SEND_INFO sendInfo = (PAFD_SEND_INFO)InputBuffer; + if (sendInfo->AfdBufferArray && sendInfo->AfdBufferCount > 0) { + ULONG total_send_size = 0; + for (ULONG i = 0; i < sendInfo->AfdBufferCount; i++) { + total_send_size += sendInfo->AfdBufferArray[i].len; + } + + if (total_send_size > 0) { + send_payload_size_to_log = min((ULONG)buffer_log_max, total_send_size); + aggregated_send_payload = (PCHAR)malloc(send_payload_size_to_log); + if (aggregated_send_payload) { + PCHAR current_pos = aggregated_send_payload; + ULONG bytes_copied = 0; + for (ULONG i = 0; i < sendInfo->AfdBufferCount && bytes_copied < send_payload_size_to_log; i++) { + PAFD_WSABUF current_source_buf = &sendInfo->AfdBufferArray[i]; + ULONG bytes_to_copy = min(current_source_buf->len, send_payload_size_to_log - bytes_copied); + if (bytes_to_copy > 0) { + memcpy(current_pos, current_source_buf->buf, bytes_to_copy); + current_pos += bytes_to_copy; + bytes_copied += bytes_to_copy; + } + } + } + } + } + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // On exception, null out the buffer (if needed) so we can log generically later + if (aggregated_send_payload) { + free(aggregated_send_payload); + aggregated_send_payload = NULL; + } + } + } + set_lasterrors(&lasterrors); + + ret = Old_NtDeviceIoControlFile(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, IoControlCode, InputBuffer, InputBufferLength, + OutputBuffer, OutputBufferLength); if (NT_SUCCESS(ret)) length = IoStatusBlock->Information; else length = 0; - LOQ_ntstatus("device", "phbb", "FileHandle", FileHandle, - "IoControlCode", IoControlCode, - "InputBuffer", InputBufferLength, InputBuffer, - "OutputBuffer", length, OutputBuffer); + + get_lasterrors(&lasterrors); + + wchar_t* fname = NULL; + fname = calloc(32768, sizeof(wchar_t)); + if (fname) { + path_from_handle(FileHandle, fname, 32768); + } + + switch (IoControlCode) { + case IOCTL_AFD_BIND: + if (InputBufferLength >= sizeof(AFD_BindDataStruct) && hasorigbuffer) { + PAFD_BindDataStruct buf = (PAFD_BindDataStruct)origbuffer; + __try { + in_sockaddr* sockAddr = &buf->SockAddr; + char ipString[16]; + our_inet_ntop(AF_INET, &sockAddr->sin_addr, ipString, sizeof(ipString)); + unsigned short port = our_ntohs(sockAddr->sin_port); + LOQ_ntstatus( + "network", "pFhsi", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "ip", ipString, + "port", port + ); + break; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // If we're here, just use generic logging for NtDeviceIoControlFile + goto generic_log; + } + } + else { + goto generic_log; + } + + case IOCTL_AFD_CONNECT: + if (InputBufferLength >= sizeof(AFD_ConnectDataStruct) && hasorigbuffer) { + AFD_ConnectDataStruct* buf = (AFD_ConnectDataStruct*)origbuffer; + __try { + in_sockaddr* sockAddr = &buf->SockAddr; + char ipString[16]; + our_inet_ntop(AF_INET, &sockAddr->sin_addr, ipString, sizeof(ipString)); + unsigned short port = our_ntohs(sockAddr->sin_port); + LOQ_ntstatus( + "network", "pFhsi", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "ip", ipString, + "port", port + ); + break; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // If we're here, just use generic logging for NtDeviceIoControlFile + goto generic_log; + } + } + else { + // Unexpected InputBufferLength for this IOCTL, goto generic log + goto generic_log; + } + + case IOCTL_AFD_RECV: + __try { + DWORD wait_status = -1; // Init to -1 as WAIT_OBJECT_0 is 0 + if (InputBuffer && InputBufferLength >= sizeof(AFD_RECV_INFO)) { + if (ret == STATUS_PENDING && Event) { + /* + * We make some assumptions here; If we have an event it is an async call so we also assume: + * 1) The callee reset the event prior to calling this NtDeviceIoControlFile API + * 2) The callee will use NtWaitForSingleObject or similar, after calling NtDeviceIoControlFile + * + * NtWaitForSingleObject will wait on a handle, and if the callee is expecting to use NtWaitForSingleObject + * (as done in NTSockets) we'll end up deadlocking unless we also use SetEvent. This is why we use our own + * function to check for the event being signaled as it we do not call any APIs which interfere with the + * status of the event itself, so it is safe for the above assumptions as well as cases where the assumption + * may be wrong. + */ + wait_status = wait_for_event_to_be_signaled(Event, 5000); // Allow 5 seconds of time for the event to trigger + if (wait_status == WAIT_OBJECT_0) { + ret = IoStatusBlock->Status; + if (NT_SUCCESS(ret)) + length = IoStatusBlock->Information; + else + length = 0; + } + } + if (length == 0) { + /* + * This can happen if we have a NTSTATUS of 0x00000103 (STATUS_PENDING) + * Unideal, likely in the background kernel has provided the recv buffer to the API + */ + if (wait_status == WAIT_TIMEOUT) { + LOQ_ntstatus( + "network", "pFhs", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "Event", "Timeout waiting for recv, buffer will likely missing from API logs" + ); + } + else { + LOQ_ntstatus( + "network", "pFhs", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "Event", "Zero-byte receive" + ); + } + break; + } + + PAFD_RECV_INFO recvInfo = (PAFD_RECV_INFO)InputBuffer; + if (recvInfo->AfdBufferArray && recvInfo->AfdBufferCount > 0) { + ULONG total_payload_to_log = min((ULONG)buffer_log_max, (ULONG)length); + PCHAR aggregated_payload = (PCHAR)malloc(total_payload_to_log); + if (!aggregated_payload) { + // Sanity check + goto generic_log; + } + + PCHAR current_pos = aggregated_payload; + ULONG bytes_copied = 0; + ULONG bytes_remaining_from_total = (ULONG)length; + for (ULONG i = 0; i < recvInfo->AfdBufferCount && bytes_copied < total_payload_to_log; i++) { + PAFD_WSABUF current_source_buf = &recvInfo->AfdBufferArray[i]; + ULONG bytes_in_source = min(bytes_remaining_from_total, current_source_buf->len); + ULONG bytes_to_copy = min(bytes_in_source, total_payload_to_log - bytes_copied); + if (bytes_to_copy > 0) { + memcpy(current_pos, current_source_buf->buf, bytes_to_copy); + current_pos += bytes_to_copy; + bytes_copied += bytes_to_copy; + } + bytes_remaining_from_total -= bytes_in_source; + } + + LOQ_ntstatus( + "network", "pFhbi", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "buffer", (size_t)bytes_copied, aggregated_payload, + "length", length + ); + free(aggregated_payload); + break; + } + } + else { + // If the initial InputBuffer validation fails, go to the generic logger. + goto generic_log; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // On exception, log generically + goto generic_log; + } + + case IOCTL_AFD_SEND: + // We parsed the send buffer prior to calling the hook, if the buffer is non-null, log it, otherwise log generically + if (aggregated_send_payload) { + LOQ_ntstatus( + "network", "pFhbi", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "buffer", (size_t)send_payload_size_to_log, aggregated_send_payload, + "length", (ULONG)length // Log the actual bytes sent from the result + ); + free(aggregated_send_payload); + break; + } + else { + goto generic_log; + } + default: + generic_log: + if (fname) { + LOQ_ntstatus( + "device", "pFhbb", + "FileHandle", FileHandle, + "HandleName", fname, + "IoControlCode", IoControlCode, + "InputBuffer", InputBufferLength, InputBuffer, + "OutputBuffer", length, OutputBuffer + ); + } + else { + LOQ_ntstatus( + "device", "phbb", + "FileHandle", FileHandle, + "IoControlCode", IoControlCode, + "InputBuffer", InputBufferLength, InputBuffer, + "OutputBuffer", length, OutputBuffer + ); + } + } if (!g_config.no_stealth && NT_SUCCESS(ret) && OutputBuffer) perform_device_fakery(OutputBuffer, (ULONG)length, IoControlCode); + if (origbuffer) + free(origbuffer); + + if (fname) + free(fname); + + set_lasterrors(&lasterrors); + return ret; } diff --git a/hook_file.h b/hook_file.h index a8dbb06..25d63f1 100644 --- a/hook_file.h +++ b/hook_file.h @@ -21,3 +21,9 @@ void file_init(); void file_close(HANDLE file_handle); void handle_duplicate(HANDLE old_handle, HANDLE new_handle); void remove_file_from_log_tracking(HANDLE fhandle); + +#define IOCTL_AFD_BIND 0x00012003 +#define IOCTL_AFD_CONNECT 0x00012007 +#define IOCTL_AFD_SEND 0x0001201f +#define IOCTL_AFD_RECV 0x00012017 +#define IOCTL_WMIQUERYALLDATA 0x00224000 diff --git a/misc.c b/misc.c index e224e72..37e73b4 100644 --- a/misc.c +++ b/misc.c @@ -51,6 +51,7 @@ _NtFreeVirtualMemory pNtFreeVirtualMemory; _LdrRegisterDllNotification pLdrRegisterDllNotification; _RtlNtStatusToDosError pRtlNtStatusToDosError; _RtlCompareMemory pRtlCompareMemory; +_NtQueryEvent pNtQueryEvent; void resolve_runtime_apis(void) { @@ -2358,3 +2359,106 @@ void prevent_module_reloading(PVOID *BaseAddress) { free(absolutepath); } + +static size_t append_octet(char** p, size_t* remaining, unsigned char octet) { + char* start = *p; + size_t written_chars = 0; + + // A temporary buffer to hold the characters of the octet (max 3 chars for 0-255) + char temp_buffer[3]; + int i = 0; + + // Handle 0 + if (octet == 0) { + temp_buffer[i++] = '0'; + } + else { + // Extract digits in reverse order + unsigned char val = octet; + while (val > 0) { + temp_buffer[i++] = (val % 10) + '0'; + val /= 10; + } + } + + // Write the digits to the destination buffer in the correct order + written_chars = i; + if (*remaining <= written_chars) { // Check if there's enough space (including null terminator) + return 0; + } + + while (i > 0) { + *(*p)++ = temp_buffer[--i]; + } + + *remaining -= written_chars; + return written_chars; +} + +const char* our_inet_ntop(int af, const void* src, char* dst, size_t size) { + if (src == NULL || dst == NULL) { + return NULL; + } + + if (af != AF_INET) { + return NULL; + } + + if (size < OUR_INET_ADDRSTRLEN) { + return NULL; + } + + // Cast the source to a pointer to raw bytes (unsigned char). + const unsigned char* p_addr = (const unsigned char*)src; + char* p = dst; + size_t remaining = size; + + for (int i = 0; i < 4; ++i) { + // Read the i-th byte directly from memory. This avoids all endianness problems. + unsigned char octet = p_addr[i]; + if (append_octet(&p, &remaining, octet) == 0) { + return NULL; + } + + if (i < 3) { + if (remaining <= 1) { + return NULL; + } + *p++ = '.'; + remaining--; + } + } + + *p = '\0'; + return dst; +} + +unsigned short our_ntohs(unsigned short netshort) { + return (netshort >> 8) | (netshort << 8); +} + +DWORD wait_for_event_to_be_signaled(HANDLE hEvent, DWORD dwTimeout) { + ULONGLONG startTime = raw_gettickcount(); + ULONGLONG currentTime; + NTSTATUS status; + EVENT_BASIC_INFORMATION eventInfo; + ULONG returnLength; + + while (TRUE) { + status = pNtQueryEvent(hEvent, EventBasicInformation, &eventInfo, sizeof(eventInfo), &returnLength); + if (status == STATUS_SUCCESS) { + // Check the state. 1 means signaled. + if (eventInfo.EventState == 1) { + return WAIT_OBJECT_0; + } + } + + // Check for timeout. + currentTime = raw_gettickcount(); + if ((currentTime - startTime) > dwTimeout) { + return WAIT_TIMEOUT; + } + + raw_sleep(250); + } +} diff --git a/misc.h b/misc.h index 19c0af7..1d99cb3 100644 --- a/misc.h +++ b/misc.h @@ -126,6 +126,28 @@ typedef SIZE_T (WINAPI *_RtlCompareMemory)( _In_ SIZE_T Length ); +typedef enum _EVENT_INFORMATION_CLASS { + EventBasicInformation +} EVENT_INFORMATION_CLASS; + +typedef enum _EVENT_TYPE { + NotificationEvent, // manual-reset event + SynchronizationEvent // auto-reset event +} EVENT_TYPE; + +typedef struct _EVENT_BASIC_INFORMATION { + EVENT_TYPE EventType; + LONG EventState; +} EVENT_BASIC_INFORMATION, * PEVENT_BASIC_INFORMATION; + +typedef NTSTATUS(NTAPI* _NtQueryEvent)( + _In_ HANDLE EventHandle, + _In_ EVENT_INFORMATION_CLASS EventInformationClass, + _Out_writes_bytes_(EventInformationLength) PVOID EventInformation, + _In_ ULONG EventInformationLength, + _Out_opt_ PULONG ReturnLength + ); + _NtSetInformationProcess pNtSetInformationProcess; _NtMapViewOfSection pNtMapViewOfSection; _NtUnmapViewOfSection pNtUnmapViewOfSection; @@ -269,3 +291,48 @@ struct envstruct { }; const char* GetLanguageName(LANGID langID); + +#define OUR_INET_ADDRSTRLEN 16 + +typedef struct _in_sockaddr { + short sin_family; // e.g. AF_INET + unsigned short sin_port; // e.g. htons(3490) + unsigned long sin_addr; // see struct in_addr, below + char sin_zero[8]; // zero this if you want to +} in_sockaddr; + +typedef struct _AFD_ConnectDataStruct +{ + DWORD dwUnknown1; + DWORD dwUnknown2; + DWORD dwUnknown3; + in_sockaddr SockAddr; +} AFD_ConnectDataStruct; + +typedef struct _AFD_BindDataStruct +{ + DWORD dwUnknown1; + in_sockaddr SockAddr; +} AFD_BindDataStruct, * PAFD_BindDataStruct; + +typedef struct _AFD_WSABUF { + ULONG len; + PCHAR buf; +} AFD_WSABUF, * PAFD_WSABUF; + +typedef struct _AFD_RECV_INFO { + PAFD_WSABUF AfdBufferArray; + ULONG AfdBufferCount; + ULONG AfdFlags; + ULONG TdiFlags; +} AFD_RECV_INFO, * PAFD_RECV_INFO; + +typedef struct _AFD_SEND_INFO { + PAFD_WSABUF AfdBufferArray; + ULONG AfdBufferCount; + ULONG TdiFlags; +} AFD_SEND_INFO, * PAFD_SEND_INFO; + +const char* our_inet_ntop(int af, const void* src, char* dst, size_t size); +unsigned short our_ntohs(unsigned short netshort); +DWORD wait_for_event_to_be_signaled(HANDLE hEvent, DWORD dwTimeout);