Ocena bezpieczeństwa kontrolek SAP GUI przy użyciu Windows API w Pythonie

Michał Majchrowicz

Podczas przeprowadzania oceny bezpieczeństwa lub testów penetracyjnych w środowiskach SAP GUI, interakcja z elementami sterującymi systemu Windows jest często konieczna do wyodrębnienia informacji lub automatyzacji. Windows API zapewnia potężne metody dostępu do elementów GUI i manipulowania nimi, umożliwiając specjalistom ds. bezpieczeństwa skuteczniejszą analizę aplikacji SAP GUI.

W tym wpisie pokażemy, w jaki sposób Python, w połączeniu z funkcjami Windows API, takimi jak GetWindowTextA i WM_GETTEXT, może być wykorzystywany do testowania i automatyzacji bezpieczeństwa SAP GUI.

Interakcja z SAP GUI przy użyciu Windows API

SAP GUI działa jako aplikacja oparta na systemie Windows, co oznacza, że dostęp do elementów interfejsu użytkownika można uzyskać za pomocą wywołań API na poziomie systemu. Jest to szczególnie przydatne w testach penetracyjnych, w których wymagane jest pobieranie poufnych informacji lub automatyzacja kontroli bezpieczeństwa. Powszechnie używane funkcje API obejmują:

  • FindWindow / FindWindowEx – lokalizuje główne okno SAP GUI i jego elementy potomne.
  • GetWindowTextA – pobiera tekst z kontrolek okna, co jest przydatne do wyodrębniania tytułów okien lub komunikatów dialogowych.
  • SendMessage(WM_GETTEXT) – wyodrębnia zawartość z pól tekstowych, które nie udostępniają danych przez GetWindowTextA.

Interakcja z Windows API może być zrealizowana z wykorzystaniem pythonowych modułów: ctypes i win32gui. Są one niezbędnymi narzędziami do interakcji z Windows API, co czyni je cennymi przy przeprowadzaniu testów penetracyjnych obejmujących aplikacje GUI, takie jak SAP. Moduł ctypes pozwala na bezpośredni dostęp do bibliotek DLL systemu Windows, umożliwiając interakcję z komponentami systemu poprzez wywoływanie funkcji niskiego poziomu w celu pobierania tekstu okna, wysyłania naciśnięć klawiszy lub manipulowania pamięcią. Z drugiej strony, win32gui, część pakietu pywin32, zapewnia bardziej Pythoniczny sposób interakcji z interfejsem graficznym użytkownika systemu Windows, oferując funkcje do enumracji okien, pobierania uchwytów i wyodrębniania informacji z kontrolek. Razem moduły te umożliwiają badaczom bezpieczeństwa analizę zachowania SAP GUI, automatyzację testów bezpieczeństwa i identyfikację luk, które mogą zostać wykorzystane przez atakujących.

Dostęp do SAP GUI można uzyskać za pomocą funkcji Windows API, co ma kluczowe znaczenie dla testów penetracyjnych. Moduły pythona ctypes i win32gui umożliwiają automatyzację interakcji z SAP GUI, pomagając w wydobywaniu danych, analizowaniu luk i automatyzacji testów bezpieczeństwa.

Wyodrębnianie tekstu z kontrolek okna SAP

Poniższy skrypt napisany w języku Python pokazuje, jak pobrać tytuł okna SAP GUI, który może być przydatny w ocenie bezpieczeństwa w celu identyfikacji otwartych sesji lub nieautoryzowanego dostępu:

import ctypes
import win32gui

# Pobranie uchwytu do okna SAP
hwnd = win32gui.FindWindow(None, "SAP Logon 760")  # Dostosuj tytuł do swojego przypadku

if hwnd:
    length = win32gui.GetWindowTextLength(hwnd)
    buffer = ctypes.create_string_buffer(length + 1)
    ctypes.windll.user32.GetWindowTextA(hwnd, buffer, length + 1)
    print("SAP Window Title:", buffer.value.decode('utf-8'))
else:
    print("SAP GUI not found.")Code language: PHP (php)

Uzyskanie dostępu do kontrolek z wykorzystaniem WM_GETTEXT

Podczas testów penetracyjnych możemy napotkać scenariusze, w których GetWindowTextA nie działa na niektórych elementach interfejsu użytkownika, takich jak pola haseł lub dynamicznie aktualizowane dane wejściowe. W takich przypadkach, do wyodrębnienia ukrytych informacji w celu dalszej analizy bezpieczeństwa, można użyć SendMessage(WM_GETTEXT).

import ctypes
from ctypes import wintypes

user32 = ctypes.WinDLL("user32", use_last_error=True)

WM_GETTEXT = 0x000D
WM_GETTEXTLENGTH = 0x000E

def get_control_text(hwnd):
    length = user32.SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0)
    buffer = ctypes.create_unicode_buffer(length + 1)
    user32.SendMessageW(hwnd, WM_GETTEXT, length + 1, ctypes.byref(buffer))
    return buffer.value

# Przykładowe użycie:
hwnd_sap_field = win32gui.FindWindowEx(hwnd, None, "Edit", None)  # Odnalezienie pola wejściowego
if hwnd_sap_field:
    print("SAP Input Field Text:", get_control_text(hwnd_sap_field))Code language: PHP (php)

Praktyczny przykład: Wykorzystanie Windows API do wstrzyknięcia transakcji SAP

Możemy zastosować tę teorię w praktyce podczas oceny bezpieczeństwa lub testów penetracyjnych aplikacji SAP. Poniższy skrypt Pythona został zaprojektowany do automatyzacji interakcji z graficznym interfejsem użytkownika SAP przy użyciu funkcji Windows API. Wykorzystuje on moduł ctypes do bezpośredniego łączenia się z bibliotekami systemowymi Windows, umożliwiając lokalizowanie okien SAP, interakcję z polami tekstowymi i symulowanie wprowadzania danych przez użytkownika. Takie techniki automatyzacji mogą być bardzo przydatne, ponieważ umożliwiają badaczom bezpieczeństwa ocenę kontroli dostępu, identyfikację nieautoryzowanych transakcji i usprawnienie analizy luk w zabezpieczeniach.

import ctypes, sys
from ctypes import wintypes
import time, string

# Wczytanie wymaganych bibliotek Windows API
user32 = ctypes.windll.user32

# Define constants 
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
VK_RETURN = 0x0D
# Constants for messages
WM_CHAR = 0x0102

# Definicja funkcji callback dla EnumWindows
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)


def find_window_by_title(target_title):
    found_hwnd = []

    def callback(hwnd, lParam):
        length = user32.GetWindowTextLengthW(hwnd)
        if length > 0:
            buffer = ctypes.create_unicode_buffer(length + 1)
            user32.GetWindowTextW(hwnd, buffer, length + 1)
            if target_title in buffer.value:
                print(buffer.value)
                found_hwnd.append(hwnd)
                return False  # Stop enumeration when the window is found
        return True  # kontynuacja enumeracji

    tmp=user32.EnumWindows(EnumWindowsProc(callback), 0)
    return found_hwnd[0] if found_hwnd else None

WM_GETTEXT = 0x000D
WM_GETTEXTLENGTH = 0x000E

def get_window_caption(hwnd):
    """Pozyskanie tekstu/caption dla okna z wykorzystaniem SendMessage."""
    # pobranie długości tekstu:
    text_length = user32.SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0)
    if text_length > 0:
        # Alokacja bufora dla odebranego tekstu
        buffer = ctypes.create_unicode_buffer(text_length + 1)
        # użycie SendMessage w celu pobrania tekstu
        user32.SendMessageW(hwnd, WM_GETTEXT, text_length + 1, ctypes.byref(buffer))
        return buffer.value
    return "" 

def find_child_window(parent_hwnd, target_text):
    """Odnajduje potomne okno na podstawie tekstu dla wskazanego okna."""
    found_child_hwnd = []

    def callback(hwnd, lParam):
        length = user32.GetWindowTextLengthW(hwnd)
        #print(hex(hwnd))
        window_caption=get_window_caption(hwnd)
        if length > 0:
            buffer = ctypes.create_unicode_buffer(length + 1)
            user32.GetWindowTextW(hwnd, buffer, length + 1)
            #print(buffer.value)
            if target_text in buffer.value:
                #print(buffer.value)
                found_child_hwnd.append(hwnd)
                return False  # zatrzymanie enumeracji jeżeli okno zostało odnalezione
        if window_caption and len(window_caption) > 0:
            #print(window_caption)
            if target_text in window_caption:
                    found_child_hwnd.append(hwnd)
                    return False
        return True  # kontynuacja enumeracji

    user32.EnumChildWindows(parent_hwnd, EnumWindowsProc(callback), 0)
    return found_child_hwnd[0] if found_child_hwnd else None

def send_enter_key(hwnd):
    """Wysłanie wartości przycisku ENTER do wskazanego okna."""
    # Wyciągnięcie okna na wierzch
    user32.SetForegroundWindow(hwnd)
    time.sleep(0.1)  # Odczekanie aby okno stało się aktywne

    # Wysłanie wiadomości WM_KEYDOWN i WM_KEYUP dla przycisku ENTER
    user32.PostMessageW(hwnd, WM_KEYDOWN, VK_RETURN, 0)
    user32.PostMessageW(hwnd, WM_KEYUP, VK_RETURN, 0)

def send_chars_to_window(hwnd, string):
    for char in string:
        user32.SendMessageW(hwnd, WM_CHAR, ord(char), 0)

# Główna funkcja programu
if __name__ == "__main__":
    print("\nSAP Transaction Check via Error Message Helper Tool v0.2.1 by Michał Majchrowicz AFINE Team\n")
    
    if len(sys.argv) < 2:
        print(f"{sys.argv[0]} <text_field_text> [<value_prefix>]\n");
        exit(-1)
    
    found_str=""
    if len(sys.argv) > 2:
        found_str=sys.argv[2]

    #main_window_title = "SAP Easy Access  -"
    main_window_title = "110 SAP Easy Access"
    child_window_text=sys.argv[1]
    mainHwnd = find_window_by_title(main_window_title)
    if mainHwnd:
        print(f"Main Window found: {hex(mainHwnd)}")
        
        # Odnalezienie okna potomka na podstawie tekstu
        child_window_hwnd = find_child_window(mainHwnd, child_window_text)
        if child_window_hwnd:
            print(f"Text field with text '{child_window_text}' found: {hex(child_window_hwnd)}")
            
            # Wysłanie symulacji wciśnięcia przycisku ENTER do okna potomnego
            send_chars_to_window(child_window_hwnd,"/nSAP_404")
            send_enter_key(child_window_hwnd)
            time.sleep(2)
    footer_window_hwnd = find_child_window(mainHwnd, "Footer")
    if footer_window_hwnd:
        print(f"Footer found: {hex(footer_window_hwnd)}")
        results_label_hwnd = find_child_window(footer_window_hwnd, "Transaction SAP_404")
    
    if results_label_hwnd:
        print(f"Results label found: {hex(results_label_hwnd)}\n")
    
    with open("C:\\Users\\Public\\Documents\\list.txt","r") as file:
        transaction_list=[line.strip() for line in file.readlines()]
    #transaction_name="SAP_TEST_404"
    for transaction_name in transaction_list:
        send_chars_to_window(child_window_hwnd,"/n"+transaction_name)
        send_enter_key(child_window_hwnd)
        time.sleep(2)
        results_window_caption = get_window_caption(results_label_hwnd)
        if not "does not exist" in results_window_caption:
            print(transaction_name)
    print("")Code language: PHP (php)

Znajdowanie okien i elementów sterujących SAP

Skrypt rozpoczyna działanie od zdefiniowania funkcji do wyszukiwania okien SAP i ich elementów podrzędnych na podstawie ich tytułów lub wyświetlanego tekstu. Korzystając z funkcji EnumWindows oraz EnumChildWindows iteruje przez otwarte okna, aby znaleźć główne okno SAP GUI i odpowiednie pola wejściowe. W celu wyodrębnienia tekstu wykorzystuje komunikat WM_GETTEXT upewniając się, że interakcja następuje z prawidłowymi elementami. Funkcja jest przydatna w testach penetracyjnych, w których specjaliści ds. bezpieczeństwa mogą potrzebować przeanalizować mechanizmy uwierzytelniania i autoryzacji oparte na GUI.

Symulowanie wprowadzania danych przez użytkownika w systemie SAP

Gdy skrypt zlokalizuje niezbędne komponenty SAP GUI, automatyzuje interakcję z nimi wysyłając symulowane naciśnięcia klawiszy oraz polecenia. Funkcja SendMessageW służy do wprowadzania tekstu, podczas gdy PostMessageW symuluje naciśnięcie klawisza ENTER. Dzięki temu, skrypt może automatycznie wprowadzać kody transakcji oraz weryfikować czy określone transakcje są dozwolone lub ograniczone na podstawie odpowiedzi. Taka automatyzacja może pomóc zidentyfikować błędnie skonfigurowane prawa dostępu, w których użytkownicy posiadają uprawnienia do wykonania operacji, których nie powinni móc wykonać.

Poszukiwanie nieautoryzowanych transakcji

Skrypt odczytuje z pliku listę kodów transakcji i iteruje po nich, próbując uzyskać dostęp do każdej z nich w interfejsie SAP GUI. Sprawdzając komunikaty odpowiedzi określa, czy transakcja jest dostępna, czy niedozwolona. Jeśli transakcja jest nieoczekiwanie dostępna, może to wskazywać na błędną konfigurację zabezpieczeń lub potencjalne ryzyko eskalacji uprawnień.

Wykorzystując funkcje Windows API do interakcji z SAP GUI, skrypt ten stanowi użyteczne narzędzie dla specjalistów ds. bezpieczeństwa do automatyzacji ocen bezpieczeństwa opartych na GUI.

W tym rozdziale wyjaśniono, w jaki sposób skrypt automatyzuje walidację transakcji w interfejsie graficznym SAP, odczytując listę kodów i sprawdzając odpowiedzi dostępu.

Uruchomienie skryptu

Skrypt wymaga pliku wejściowego zawierającego listę kodów transakcji SAP. Każdy wiersz pliku powinien zawierać jeden kod transakcji. Poniżej znajduje się przykład takiego pliku wejściowego:

SU3
PPOSE
SU53
SU56
TEST
FOOBAR
SUIM
AFINE

Aby uruchomić skrypt, należy wykonać następujące polecenie:

PS C:\Program Files\Python312> .\python.exe .\sap_transaction_check.py FOOBARCode language: CSS (css)

Powyższe polecenie uruchamia skrypt i używa „FOOBAR” jako tekstu do wyszukania w interfejsie graficznym SAP. Następnie skrypt próbuje zlokalizować okno SAP GUI, znaleźć odpowiednie pole wejściowe, wprowadzić kody transakcji i przeanalizować odpowiedzi.

Wyniki

Po wykonaniu skryptu wygenerowano następujący output:

SAP Transaction Check via Error Message Helper Tool v0.2.1 by Michał Majchrowicz AFINE Team

SAP Easy Access  -  User Menu for AFINE
Main Window found: 0x704a4
Text field with text 'FOOBAR' found: 0x60470
Footer found: 0x5048c
Results label found: 0x6047a

SU3
PPOSE
SU53
SU56
SUIMCode language: JavaScript (javascript)

Skrypt pomyślnie wykrył główne okno SAP, zidentyfikował docelowe pole wejściowe oraz odnalazł stopkę, w której wyświetlane są wyniki transakcji. Następnie sprawdził każdą transakcję z pliku wejściowego. Wynik przedstawia listę kodów transakcji, które istnieją i są dostępne w systemie.

Co ciekawe, niektóre transakcje z pliku wejściowego – takie jak „TEST”, „FOOBAR” i „AFINE” – nie pojawiły się w wynikach. Oznacza to, że albo nie istnieją, albo ich dostępność jest ograniczona.

Podsumowanie

Wykorzystanie wywołań Windows API w Pythonie umożliwia interakcję z kontrolkami SAP GUI, co czyni tę technikę skutecznym narzędziem do oceny bezpieczeństwa. Niezależnie od tego, czy chodzi o ekstrakcję tekstu, identyfikację nieautoryzowanych punktów dostępu, metody te dostarczają cennych informacji specjalistom ds. bezpieczeństwa SAP. Aby jeszcze bardziej zwiększyć poziom zabezpieczeń SAP, połączenie technik opartych na Windows API z SAP GUI Scripting lub narzędziami automatyzacji, takimi jak pywinauto, może pomóc w wykrywaniu podatności i poprawie ogólnego bezpieczeństwa systemu.

Michał Majchrowicz
Offensive Security Engineer

Czy Twoja firma jest bezpieczna w sieci?

Dołącz do grona naszych zadowolonych klientów i zabezpiecz swoją firmę przed cyberzagrożeniami już dziś!

Zostaw nam swoje dane kontaktowe, a nasz zespół skontaktuje się z Tobą, aby omówić szczegóły i dopasować ofertę do Twoich potrzeb. Dbamy o pełną dyskrecję i poufność Twoich danych, dlatego możesz nam zaufać.

Chciałbyś od razu zadać pytanie? Odwiedź naszą stronę kontaktową.