Как читать ввод с клавиатуры, не потребляя его в сборке x86 DOS?

Мне нужно написать своего рода функцию кейлоггера, которую можно вызывать из кода C. это означает, что программа на языке C вызовет функцию ассемблера, называемую startlog, которая укажет, что нужно начать регистрацию нажатых клавиш до тех пор, пока не будет вызвана функция endlog. ведение журнала должно работать следующим образом: записать значение ascii любой нажатой клавиши, не нарушая код C между startlog и endlog, что означает, что если коду C также необходимо прочитать ввод (скажем, через scanf, это будет работать нормально).

Мне удалось написать регистратор, изменив 9-ю запись вектора прерывания (прерывание по нажатию клавиатуры) на написанную мной функцию, которая записывает значения в файл, и она отлично работает. однако код C не получает ввод. В основном то, что я сделал, это прочитать нажатую клавишу с помощью int 21h, однако после чтения значения ascii оно «потребляется», поэтому мне нужен способ либо снова имитировать нажатие клавиши, либо читать значение без «потребления», поэтому в следующий раз ключ читается, он читает тот же ключ. (я описал код на английском, потому что это длинный и неуклюжий ассемблерный код)


person Bob    schedule 12.11.2011    source источник
comment
Вы вызвали int 21 из обработчика int 9, и он не взорвался?   -  person ninjalj    schedule 12.11.2011
comment
если их правильно назвать, то ничего не взрывается...   -  person Bob    schedule 12.11.2011


Ответы (2)


Вот как это сделать:

// 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;
}

Вывод (запуск в Windows XP):

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

КЕЙЛОГ.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 "?"

Здесь есть несколько проблем. Вы не можете использовать некоторые функции DOS, когда он занят. Вот почему я проверяю флаг InDos. В то же время InDos может показывать, что DOS занята, даже когда она ожидает таких простых вещей, как ввод с клавиатуры (например, в gets()).

Вот почему существует кольцевой буфер для кодов сканирования, который накапливает их, пока программа не может безопасно вызывать подпрограммы ввода-вывода файлов DOS. EndLog() ждет, пока буфер не опустеет. Возможно, вам также придется принудительно слить воду раньше.

Я также пытался подключить int 28h в качестве альтернативы int 1Ch, но мой ISR для int 28h так и не вызвался, не знаю почему.

Я избегаю использования C fopen() и fwrite()/fprintf() для файла журнала, чтобы не мешать основной программе, которая не знает о том, что происходит в фоновом режиме. По той же причине в ISR используются только самые простые стандартные функции C.

person Alexey Frunze    schedule 12.11.2011
comment
Примечание. С "??" этот код практически использует триггеры. Возможно, убедитесь, что двойные '?' разделены, например: "??" "1234567890-=" "??" ... - person chux - Reinstate Monica; 27.08.2015

Если INT 9 — это прерывание клавиатуры, и вы меняете этот вектор, чтобы он указывал на ваш собственный код для перехвата символов, почему вы не можете просто сохранить старый вектор и перейти к нему в конце вашего кода ловушки?

person Martin James    schedule 12.11.2011
comment
Я делаю это, перехожу к исходному INT 9, чтобы он обрабатывал ввод с клавиатуры, а затем вызываю int21h для его чтения. - person Bob; 12.11.2011
comment
Хорошо, похоже, вам нужно буферизовать символы и перехватить int 21. - person Martin James; 12.11.2011