Python mit ASM funktioniert nicht.

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
funkheld
User
Beiträge: 258
Registriert: Sonntag 31. Oktober 2010, 09:26

Hallo, guten Tag.

Ich suche bitte den Fehler in dieser Demo , weil bei :
r = f(42)
print (r)
der Wert "5" rauskommt oder ein anderer Wert der hier drin steht : b'\x83\xc0\x05'
statt 42+5

Wer kann bitte helfen?
Python 3.... und WIN10

Danke.

Code: Alles auswählen

import ctypes

asm_function = (
    b'\x8b\xc1'      # mov eax, ecx
    b'\x83\xc0\x05'  # add eax, 5
    b'\xc3'          # ret
)

# https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc#MEM_COMMIT
MEM_COMMIT = 0x00001000
# https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc#MEM_RESERVE
MEM_RESERVE = 0x00002000
# https://learn.microsoft.com/en-us/windows/win32/memory/memory-protection-constants#PAGE_EXECUTE_READWRITE
PAGE_EXECUTE_READWRITE = 0x40
# https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
ctypes.windll.kernel32.VirtualAlloc.argtypes = (
    ctypes.c_void_p,  # LPVOID
    ctypes.c_size_t,  # SIZE_T
    ctypes.c_long,    # DWORD
    ctypes.c_long,    # DWORD
)
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p  # LPVOID

memory_buffer = ctypes.windll.kernel32.VirtualAlloc(
    0,                         # lpAddress - NULL
    len(asm_function),         # dwSize
    MEM_COMMIT | MEM_RESERVE,  # flAllocationType
    PAGE_EXECUTE_READWRITE     # flProtect
)

if not memory_buffer:  # VirtualAlloc returned NULL
    print("VirtualAlloc call failed. Error code:", ctypes.GetLastError())
    exit(-1)

c_buffer = ctypes.c_char_p(asm_function)

# https://learn.microsoft.com/en-us/windows/win32/devnotes/rtlmovememory
ctypes.windll.kernel32.RtlMoveMemory.argtypes = (
    ctypes.c_void_p,  # VOID*
    ctypes.c_void_p,  # VOID*
    ctypes.c_size_t   # SIZE_T
)

ctypes.windll.kernel32.RtlMoveMemory(
    memory_buffer,     # Destination
    c_buffer,          # Source
    len(asm_function)  # Length
)

f = ctypes.cast(
    memory_buffer,
    ctypes.CFUNCTYPE(
        ctypes.c_int,  # return type
        ctypes.c_int   # argument type
    )
)

r = f(42)
print(r)
__deets__
User
Beiträge: 14533
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum schreibst du die Funktion nicht in Python? Oder, wenn es assembler sein soll, benutzt eine Sprache wie C++, die das relative einfach integriert.
funkheld
User
Beiträge: 258
Registriert: Sonntag 31. Oktober 2010, 09:26

C++ ist nicht meine Sache . Mit ASM kenne ich mich besser aus.
Bloß das hineinbringen der Variablen mit ctypes macht mir schwierigkeiten.

Danke.
__deets__
User
Beiträge: 14533
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dann wird deine Neugierde sich auf das debuggen solcher Anwendungen ausdehnen müssen. Denn es wird nie so sein, dass du “einfach” Assembler schreiben können wirst, und den dann ausführen. Was hier kracht ist das CFFI, und das ist komplex, und will umfänglich verstanden sein, wenn man sowas machen will. Schmeiß also WinDBG an, und schau, was da so passiert.
__deets__
User
Beiträge: 14533
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nachtrag: wenn das Ergebnis immer das immediate-Argument ist, dann muss folgerichtig eax 0 sein. Und warum das 0 und nicht 42 ist, liegt eben am CFFI, was durch ctypes genutzt wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13099
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mehr als aus Neugier wird man das rein in Assembler ja sowieso nicht machen wollen. Das ist kompliziert und fehleranfällig, funktioniert so dann nur auf einer Plattform, also Windows *und* 32-Bit schränken das ein, und man wird sich der Einfachheit halber sowieso mit C beschäftigen müssen, denn das c in `ctypes` steht ja für C.

Werden bei der Windows, 32-Bit ABI Argumente überhaupt über Register übergeben? Wer muss den Stack aufräumen? Aufrufer oder Aufgerufener?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14533
Registriert: Mittwoch 14. Oktober 2015, 14:29

Gute Frage. Kann man ja mal mit rumspielen, Matt Godbolt sei Dank: https://godbolt.org/z/11G8ava6d

Ich wuerde das erstmal als via Stack uebertragen interpretieren.

Wenn man Assembler machen will, dann ist C oder C++ dafuer schlicht um Groessenordnungen besser geeignet. Weil das Tooling schon gleich dabei ist, ein Debugger dafuer kann immer auch Assembler darstellen, und man sieht ueberhaupt, was da passiert. Python ist da wirklich die schlechtmoeglichste Wahl.
Sirius3
User
Beiträge: 17745
Registriert: Sonntag 21. Oktober 2012, 17:20

__deets__: der Stack wird nur für die lokalen Variablen verwendet.
Die optimierte Version sieht so aus:

Code: Alles auswählen

   lea     eax, DWORD PTR [rcx+42]
   ret
AMD64 hat genug Register, dass man die meisten Parameter ohne Stack übergeben kann.
Ich habe kein Windows um das selbst testen zu können, sehe aber keinen offensichtlichen Fehler.

Unter Linux würde das so aussehen

Code: Alles auswählen

import ctypes
import mmap

CODE = b"\x8d\x47\x2a\xc3"

code_mem = mmap.mmap(-1, len(CODE), mmap.MAP_PRIVATE, mmap.PROT_EXEC | mmap.PROT_READ | mmap.PROT_WRITE)
code_mem[:] = CODE

function = ctypes.cast(
    (ctypes.c_char*len(CODE)).from_buffer(code_mem),
    ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
)

print(function(17))
Zuletzt geändert von Sirius3 am Samstag 21. Januar 2023, 13:52, insgesamt 1-mal geändert.
Benutzeravatar
__blackjack__
User
Beiträge: 13099
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das wäre dann wohl die 64 Bit ABI und da wird das erste int-Argument offenbar in ECX übergeben. Dann müsste der Code im ersten Beitrag aber richtig sein. Der Godbold-Code ist ja nicht optimiert, da wird erst ECX in den Stapelrahmen mit den lokalen Variablen geschrieben um den Wert danach gleich wieder von dort auszulesen. Optimiert macht der Compiler da was nettes und ”missbraucht” LEA für die Addition:

Code: Alles auswählen

        lea     eax, DWORD PTR [rcx+42]
        ret     0
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
funkheld
User
Beiträge: 258
Registriert: Sonntag 31. Oktober 2010, 09:26

---------------------------------
mov eax,23
add eax,12
ret
--------------------------------

asm_function = (
b'\xB8\x17\x00\x00\x00\x83\xC0\x0C\xC3'
)

Das funktioniert oben.

Warum kann man nicht die Variable in eax reinbringen mit : mov eax, ecx ?
Kann man irgendwie eine Adresse dort reinbringen von Python wo die Variable reingelegt wird?

Ich versteh das nicht , was da passiert.

Danke
Zuletzt geändert von funkheld am Samstag 21. Januar 2023, 19:28, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14533
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn du keine Parameter von Python übergeben willst, kannst du auch gleich nasm benutzen…
Sirius3
User
Beiträge: 17745
Registriert: Sonntag 21. Oktober 2012, 17:20

@funkheld: Dein ursprünglicher Code funktioniert einwandfrei. Um nochmal sicher zu gehen: Du benutzt Python3 in 64bit oder in 32bit?
Sirius3
User
Beiträge: 17745
Registriert: Sonntag 21. Oktober 2012, 17:20

Noch Anmerkungen zu Deinem Code: ctypes kennt bereits memmove, das selbst zu definieren ist also nicht nötig.
Ich persönlich bevorzuge ein char-Array, weil das klarer und sicherer ist:

Code: Alles auswählen

import ctypes

# https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc#MEM_COMMIT
MEM_COMMIT = 0x00001000
# https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc#MEM_RESERVE
MEM_RESERVE = 0x00002000
# https://learn.microsoft.com/en-us/windows/win32/memory/memory-protection-constants#PAGE_EXECUTE_READWRITE
PAGE_EXECUTE_READWRITE = 0x40
# https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
virtual_alloc = ctypes.windll.kernel32.VirtualAlloc
virtual_alloc.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_long, ctypes.c_long)
virtual_alloc.restype = ctypes.c_void_p  # LPVOID


asm_function = (
    b'\x8b\xc1'      # mov eax, ecx
    b'\x83\xc0\x05'  # add eax, 5
    b'\xc3'          # ret
)

memory_buffer = virtual_alloc(
    0,                         # lpAddress - NULL
    len(asm_function),         # dwSize
    MEM_COMMIT | MEM_RESERVE,  # flAllocationType
    PAGE_EXECUTE_READWRITE     # flProtect
)

if not memory_buffer:  # VirtualAlloc returned NULL
    raise RuntimeError("VirtualAlloc call failed. Error code:", ctypes.GetLastError())

code_ptr = (ctypes.c_char * len(asm_function)).from_address(memory_buffer)
code_ptr.raw = asm_function

function = ctypes.cast(
    code_ptr,
    ctypes.CFUNCTYPE(
        ctypes.c_int,  # return type
        ctypes.c_int   # argument type
    )
)

print(function(42))
funkheld
User
Beiträge: 258
Registriert: Sonntag 31. Oktober 2010, 09:26

Oh , danke für die Info , ich habe 32bit Python 3.9.0
Wie kann man das Programm bitte anpassen, das 32bit-Variablen erkannt werden?
Oder muss ich das Python mit 64bit installieren?
Benutzeravatar
__blackjack__
User
Beiträge: 13099
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Naja wenn die 64-Bit ABI funktioniert, wird das ein 64-Bit-Python gewesen sein. Und das erklärt dann natürlich auch warum das bei Dir nicht funktioniert. Bei der 32 ABI kommt das Argument tatsächlich über den Stack mit einem Versatz von 4 Bytes zum Stapelzeiger. Die Routine müsste also aus diesen Bytes bestehen: b"\x8b\x44\x24\x04\x83\xc0\x2a\xc3"
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
funkheld
User
Beiträge: 258
Registriert: Sonntag 31. Oktober 2010, 09:26

Hallo, danke an alle.

Jetzt funktioniert es mit Python 3.9.0 32bit
------------------------------------
b"\x8b\x44\x24\x04\x83\xc0\x2a\xc3"
-----------------------------------
Wie sieht das jetzt oben bitte mit Stack mit einem Versatz von 4 Bytes zum Stapelzeiger aus?

Werde dann mal meine experiemente machen.
Benutzeravatar
__blackjack__
User
Beiträge: 13099
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@funkheld: Die Argumente liegen auf dem Stack. Und als erstes liegen dort 4 Bytes Rückkehradresse vom CALL, darum der Versatz. Warum willst Du doch gleich noch mal Assembler verwenden wenn Du nicht weisst, wie man in Assembler Aufrufe mit Argumenten macht‽

Code: Alles auswählen

    mov eax, [esp+4]
    add eax, 42
    ret
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
funkheld
User
Beiträge: 258
Registriert: Sonntag 31. Oktober 2010, 09:26

Ich habe mir jetzt das Buch von Günter Born ( ASM X86) als PDF geladen.
Muss mich jetzt mal tiefer reinknien mit 73 jahren.

Mit welchen einfachen Assembler kann man compilieren und der auch zusätzlich bitte diesen Code erzeugt : \x8B\x44\x24\x04\x8B\x5C\x24\x08\x01\xD8\xC3

Danke.
Benutzeravatar
__blackjack__
User
Beiträge: 13099
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@funkheld: Welches Buch denn? Das was ich von dem Autor als PDF gefunden habe nennt/behandelt drei Assembler (A86, MASM, TASM) ist aus den 90ern und behandelt soweit ich das sehe 16-Bit DOS Programmierung. Und warum überhaupt Assembler? Welches tatsächliche Problem denkst Du damit lösen zu können das man nicht in Python oder einer anderen Programmiersprache lösen kann? Eine Funktion die 5 oder 42 auf eine andere Zahl addiert ist es ja eher nicht, das geht mit Python einfacher als wenn man da versucht etwas in Maschinensprache zu schreiben, und das funktioniert dann auch auf allen Plattformen auf denen Python läuft, und nicht nur unter einem 32-Bit-x86 Python für Windows.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
funkheld
User
Beiträge: 258
Registriert: Sonntag 31. Oktober 2010, 09:26

Ich mache das aus Spass zur Sache.
Wüsste auch kein Problem um etwas dringend zu suchen als Sprache was damit erledigt werden muss mit dem Computer.
Antworten