0

I need to write a sort of key-logger function that can be called from C code. what it means is a c program would call an assembly function called startlog that would indicate to start logging the keys pressed until a function called endlog is called. the logging should work like this: write the ascii value of any key pressed without disturbing the C code between startlog and endlog, meaning that if the C code also needs to read the input (let's say by scanf, it would work ok).

I managed to write the logger by changing the interrupt vector 9th entry (interrupt for keyboard press) to a function I wrote that writes the values to a file, and it works fine. however the C code does not get the input. Basically what i did is read the key pressed using int 21h, however after reading the ascii value it is "consumed" so I need a way to either simulate the key press again or read the value without "consuming" it so next time a key is read it reads the same key. (I described the code in english because it is long and clumsy assembly code)

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Bob
  • 2,586
  • 7
  • 20
  • 33

2 Answers2

2

Here's how you can do it:

// Compile with Borland's Turbo C++ 1.01

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>

const char ScanToChar[] =
  "??1234567890-=??"
  "QWERTYUIOP[]??AS"
  "DFGHJKL;\"`?\\ZXCV"
  "BNM,./??? ";

void interrupt (*pOldInt9)(void);
void interrupt (*pOldInt1C)(void);
char far* pInDosFlag;

#define SCAN_BUF_SIZE 1024
volatile unsigned char ScanBuf[SCAN_BUF_SIZE];
volatile unsigned ScanReadIdx = 0;
volatile unsigned ScanWriteIdx = 0;

volatile unsigned LogFileHandle;
void DosWriteFile(unsigned handle, void* data, size_t size);

volatile unsigned InDos0cnt = 0;

void TryToSaveLog(void)
{
  unsigned cnt;

  if (*pInDosFlag)
    return;

  cnt = (ScanWriteIdx - ScanReadIdx) & (SCAN_BUF_SIZE - 1);

  InDos0cnt++;

  while (cnt--)
  {
    static const char hex[] = "0123456789ABCDEF";
    char s[80] = "0xXX \"?\"\r\n";
    unsigned char scanCode = ScanBuf[ScanReadIdx];

    s[2] = hex[scanCode >> 4];
    s[3] = hex[scanCode & 0xF];

    if ((scanCode & 0x7F) < strlen(ScanToChar))
    {
      s[6] = ScanToChar[scanCode & 0x7F];
    }

    DosWriteFile(LogFileHandle, s, strlen(s));

    ScanReadIdx++;
    ScanReadIdx &= SCAN_BUF_SIZE - 1;
  }
}

void interrupt NewInt9(void)
{
  unsigned char scanCode = inp(0x60);

  ScanBuf[ScanWriteIdx++] = scanCode;
  ScanWriteIdx &= SCAN_BUF_SIZE - 1;

  pOldInt9();
}

volatile unsigned int1Ccnt = 0;

void interrupt NewInt1C(void)
{
  int1Ccnt++;
  pOldInt1C();
  TryToSaveLog();
}

unsigned DosCreateFile(const char* name)
{
  union REGS regs;
  struct SREGS sregs;

  regs.h.ah = 0x3C;
  regs.x.cx = 0;

  sregs.ds = FP_SEG(name);
  regs.x.dx = FP_OFF(name);

  intdosx(&regs, &regs, &sregs);

  return regs.x.cflag ? 0 : regs.x.ax;
}

void DosWriteFile(unsigned handle, void* data, size_t size)
{
  union REGS regs;
  struct SREGS sregs;

  if (!size) return;

  regs.h.ah = 0x40;
  regs.x.bx = handle;
  regs.x.cx = size;

  sregs.ds = FP_SEG(data);
  regs.x.dx = FP_OFF(data);

  intdosx(&regs, &regs, &sregs);
}

void DosCloseFile(unsigned handle)
{
  union REGS regs;
  struct SREGS sregs;

  regs.h.ah = 0x3E;
  regs.x.bx = handle;

  intdosx(&regs, &regs, &sregs);
}

void StartLog(const char* FileName)
{
  union REGS regs;
  struct SREGS sregs;

  LogFileHandle = DosCreateFile(FileName);

  regs.h.ah = 0x34; // get InDos flag address
  intdosx(&regs, &regs, &sregs);
  pInDosFlag = MK_FP(sregs.es, regs.x.bx);

  pOldInt1C = getvect(0x1C);
  setvect(0x1C, &NewInt1C);

  pOldInt9 = getvect(9);
  setvect(9, &NewInt9);
}

void EndLog(void)
{
  setvect(9, pOldInt9);

  while (ScanWriteIdx != ScanReadIdx);

  setvect(0x1C, pOldInt1C);

  DosCloseFile(LogFileHandle);
  LogFileHandle = 0;
}

int main(void)
{
  char str[256];

  StartLog("keylog.txt");

  printf("please enter some text:\n");
  gets(str);
  printf("you have entered \"%s\"\n", str);

  EndLog();

  printf("int 1Ch count: %u\n", int1Ccnt);
  printf("InDos=0 count: %u\n", InDos0cnt);

  return 0;
}

Output (run on Windows XP):

please enter some text:
qweasdzxc123
you have entered "qweasdzxc123"
int 1Ch count: 175
InDos=0 count: 1

KEYLOG.TXT:

0x10 "Q"
0x90 "Q"
0x11 "W"
0x91 "W"
0x12 "E"
0x92 "E"
0x1E "A"
0x9E "A"
0x1F "S"
0x9F "S"
0x20 "D"
0xA0 "D"
0x2C "Z"
0xAC "Z"
0x2D "X"
0xAD "X"
0x2E "C"
0xAE "C"
0x02 "1"
0x82 "1"
0x03 "2"
0x83 "2"
0x04 "3"
0x84 "3"
0x1C "?"

There're a few problems here. You can't use some DOS functions when it's busy. This is why I'm checking the InDos flag. At the same time InDos can indicate that DOS is busy even when it's waiting for such simple things as keyboard input (e.g. in gets()).

This is why there's a circular buffer for the scan codes that accumulates them while the program can't safely call DOS file I/O routines. EndLog() waits until the buffer is drained. You may need to force draining earlier as well.

I've also tried hooking int 28h as an alternative to int 1Ch, but my ISR for int 28h got never invoked, not sure why.

I'm avoiding the use of C's fopen() and fwrite()/fprintf() for the log file so as not to interfere with the main program that's unaware of the things happening in the background. Only the most trivial standard C functions are used in the ISRs for the same reason.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
  • Note: With `"??"`, this code nearly uses [trigrpahs](https://en.wikipedia.org/wiki/Digraphs_and_trigraphs#C). Perhaps insure double `'?'` are separated such as : `"??" "1234567890-=" "??" ...` – chux - Reinstate Monica Aug 27 '15 at 14:35
1

If INT 9 is the keyboard interrupt and you are changing this vector to point at you own code to intercept chars, why can you not just store the old vector and jump to it at the end of your hook code?

Martin James
  • 24,453
  • 3
  • 36
  • 60