
/**
 * @file: syscall.c
 *
 * Tento soubor implementuje zaznamenavani informaci o probihajicich 
 * systemovych volani. Dale se vnem nachazi i vlastni rutiny Prehandler
 * a Posthandler, ktere jsou volany behem kazdeho systemoveho volani pro
 * hlavni modul jadra a sbiraji potrebne informace.
 */

#include <ntddk.h>
#include "driver.h"
#include "ssdt.h"
#include "general_handler.h"
#include "syscall.h"

// Udaje o systemovych volani, ktere jeste nebyly predany do uzivatelskeho
// rezimu.
static LIST_ENTRY SyscallRecordList;
static KSPIN_LOCK SyscallRecordListLock;

// Seznam s informacemi o jeste nedokoncenych systemovych volani (jeste
// neni znama jejich navratova hodnota.
static LIST_ENTRY UnprocessedList;
static KSPIN_LOCK UnprocessedListLock;

// TRUE - probiha zaznamenavani
static BOOLEAN LoggingEnabled;
// PID procesu, ktery odebira udaje o systemovem volani. Tento proces
// sam monitorovan neni.
static HANDLE MonitorPID;
static KSPIN_LOCK LoggingLock;

// Pole instanci metarutiny, kterymi jsou nahrazeny jednotlive systemove
// sluzby v SSDT.
static PGENERAL_HANDLER *Handlers;

// Pocet systemovych sluzeb pro hlavni modul jadra.
static ULONG NumberOfServices = 0;



/** Odebere prvni zaznam ze seznamu jiz zpracovanych zaznamu o systemovych
 * volani.
 *
 * @return Funkce vraci adresu struktury SYSCALL_RECORD prislusneho zaznamu.
 * Pokud je seznam hotovych zaznamu prazdny, rutina vrati NULLL.
 */
PSYSCALL_RECORD GetSyscallRecord(VOID)
{
  KIRQL OldIrql;
  PSYSCALL_RECORD ret = NULL;
  
  KeAcquireSpinLock(&SyscallRecordListLock, &OldIrql);
  if (!IsListEmpty(&SyscallRecordList)) {
    ret = (PSYSCALL_RECORD)RemoveHeadList(&SyscallRecordList);
    RtlZeroMemory(&ret->Entry, sizeof(ret->Entry));
  }
  
  KeReleaseSpinLock(&SyscallRecordListLock, OldIrql);

  return ret;
}


/** Zapne zaznamenavani systemovych volani.
 *
 * @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech
 * operace.
 */
NTSTATUS SyscallLoggingStart(VOID)
{
  KIRQL OldIrql;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("syscall.c: SyscallLoggingStart()\n"));

  KeAcquireSpinLock(&LoggingLock, &OldIrql);
  if (MonitorPID == NULL) {
    MonitorPID = PsGetCurrentProcessId();
    LoggingEnabled = TRUE;
    Status = STATUS_SUCCESS;
  } else Status = STATUS_ACCESS_DENIED;

  KeReleaseSpinLock(&LoggingLock, OldIrql);

  KdPrint(("syscall.c: SyscallLoggingStart(-):0x%x\n", Status));
  return Status;
}


/** Vypne zaznamenavani systemovych volani.
 */
VOID SyscallLoggingStop(VOID)
{
  KIRQL OldIrql;
  KdPrint(("syscall.c: SyscallLoggingStop()\n"));

  KeAcquireSpinLock(&LoggingLock, &OldIrql);
  MonitorPID = NULL;
  LoggingEnabled = FALSE;
  KeReleaseSpinLock(&LoggingLock, OldIrql);

  KdPrint(("syscall.c: LoggingStop(-)\n"));
  return;
}



/** Tuto rutinu instance metarutiny obecne obsluhy systemoveho volani
 * pred vykonanim samotne systemove sluzby.
 *
 * Rutina zjisti vsechny potrebne udaje, vytvori zaznam SYSCALL_RECORD
 * a vlozi jej do seznamu nedokoncenych zaznamu, protoze jeste je nutne
 * pockat na navratovou hodnotu ze systemoveho volani.
 *
 * @param Index Cislo systemoveho volani.
 * @param Args Adresa pole argumentu systemoveho volani. Toto pole se
 * nachazi cele na zasobniku a obecna obsluha systemoveho volani jej
 * po skonceni rutiny Prehandler pouzije pri volani originani systemove
 * sluzby.
 * @param Count Pocet polozek v poli Args.
 *
 * @return Hodnota NTSTATUS vracena touto funkci rozhoduje o chovani
 * obecne obsluhy systemovych volani. Pokud funkce vrati STATUS_SUCCESS,
 * je volan i kod originalni sluzby. V opacnem pripade obecna obsluha
 * systemovych volani zavola pouze rutinu Posthandler. Tato funkce
 * vraci vzdy STATUS_SUCCESS, protoze pouze monitoruje.
 */
NTSTATUS PreHandler(IN ULONG Index, IN PVOID *Args, IN ULONG Count)
{
  KIRQL OldIrql;
  HANDLE curr_pid = NULL;
  PSYSCALL_RECORD record = NULL;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;

  if (LoggingEnabled && MonitorPID != NULL) {
    curr_pid = PsGetCurrentProcessId();
    if (curr_pid != MonitorPID) {
      record = ExAllocatePoolWithTag(NonPagedPool, sizeof(SYSCALL_RECORD),POOL_TAG);
      if (record != NULL) {
        LARGE_INTEGER systime;

        record->PID = curr_pid;
        record->TID = PsGetCurrentThreadId();
        record->ServiceIndex = Index;
        KeQuerySystemTime(&systime);
        ExSystemTimeToLocalTime(&systime, &record->Time);        
        KeAcquireSpinLock(&UnprocessedListLock, &OldIrql);
        InsertTailList(&UnprocessedList, &record->Entry);
        KeReleaseSpinLock(&UnprocessedListLock, OldIrql);
      }
    }
  }

  Status = STATUS_SUCCESS;

  return Status;
}


/** Obecna obsluha systemovych volani vola tuto rutinu po skonceni vykonavani
 *  kodu originalni systemove sluzby.
 *
 *  Rutina najde v seznamu nezpracovanych zaznamu polozku odpovidajici
 *  aktualnimu systemovemu volani, doplni ji o navratovou hodnotu a 
 *  zaznam vlozi do seznamu dokoncenych.
 *
 *  @param Index Cislo systemove sluzby.
 *  @param Args Pole argumentu systemoveho volani.
 *  @param Count Pocet argumentu systemoveho volani.
 *  @param Status Navratova hodnota originalni systemove sluzby.
 *
 *  @return Navratova hodnota funkce je vracena i entite, ktera dane
 *  systemove volani vyvolala. Protoze ovladac syscallmon.sys pouze
 *  monitoruje, funkce vraci navratovou hodnotu originalni systemove
 *  sluzby.
 */
NTSTATUS PostHandler(IN ULONG Index, IN PVOID *Args, IN ULONG Count, IN NTSTATUS Status)
{
  KIRQL OldIrql;
  HANDLE curr_pid = NULL;
  HANDLE curr_tid = NULL;
  PSYSCALL_RECORD record = NULL;

  if (LoggingEnabled && MonitorPID != NULL) {
    curr_pid = PsGetCurrentProcessId();
    if (curr_pid != MonitorPID) {
      curr_tid = PsGetCurrentThreadId();
      KeAcquireSpinLock(&UnprocessedListLock, &OldIrql);
      record = (PSYSCALL_RECORD)UnprocessedList.Flink;
      while (&record->Entry != &UnprocessedList) {
        if (record->TID == curr_tid && record->PID == curr_pid && record->ServiceIndex == Index)
          break;

        record = (PSYSCALL_RECORD)record->Entry.Flink;
      }

      if (&record->Entry != &UnprocessedList) {
        record->Entry.Blink->Flink = record->Entry.Flink;
        record->Entry.Flink->Blink = record->Entry.Blink;
        KeReleaseSpinLock(&UnprocessedListLock, OldIrql);
        record->Status = Status;
        InitializeListHead(&record->Entry);
        KeAcquireSpinLock(&SyscallRecordListLock, &OldIrql);
        InsertTailList(&SyscallRecordList, &record->Entry);
        KeReleaseSpinLock(&SyscallRecordListLock, OldIrql);
      } else KeReleaseSpinLock(&UnprocessedListLock, OldIrql);
    }
  }

  return Status;
}


/** Inicializuje modul zaznamenavani systemovych volani.
 *
 * Rutina zjisti parametry tabulky systemovych volani pro hlavni modul
 * jadra, rozkopiruje obecnou obsluhu systemovych volani na pocet instanci
 * rovny poctu systemovych sluzeb pro hlavni modul jadra a adresy vsech
 * systemovych sluzeb prepise na tyto instance.
 *
 * @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech operace.
 */
NTSTATUS SyscallLoggingInit(VOID)
{
  ULONG i = 0;
  ULONG j = 0;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("syscall.c: SyscallLoggingInit()\n"));

  MonitorPID = NULL;
  LoggingEnabled = FALSE;
  KeInitializeSpinLock(&LoggingLock);
  KeInitializeSpinLock(&UnprocessedListLock);
  InitializeListHead(&UnprocessedList);
  KeInitializeSpinLock(&SyscallRecordListLock);
  InitializeListHead(&SyscallRecordList);
  NumberOfServices = GetNumberOfServices();
  Status = STATUS_SUCCESS;
  if (NumberOfServices > 0) {
    Handlers = ExAllocatePoolWithTag(NonPagedPool, sizeof(PVOID)*NumberOfServices, POOL_TAG);
    if (Handlers != NULL) {
      RtlZeroMemory(Handlers, sizeof(PGENERAL_HANDLER)*NumberOfServices);
      for (i = 0; i < NumberOfServices; i++) {
        Status = GeneralHandlerCreate(&Handlers[i], i, &PreHandler, &PostHandler);
        if (!NT_SUCCESS(Status)) {
          for (j = 0; j < i; j++)
            GeneralHandlerDestroy(Handlers[j]);

          ExFreePoolWithTag(Handlers, POOL_TAG);
          break;
        }
      }
    } else Status = STATUS_INSUFFICIENT_RESOURCES;
  }

  KdPrint(("syscall.c: SyscallLoggingInit(-):0x%x\n", Status));
  return Status;
}
