Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructing pointer chains with ctypes

Tags:

python

ctypes

These are simple variable declarations in cpp, how do I do it in Python ctypes?

B *C = (B *) A;
D *E = (D *)(A + sizeof(B));

Assume that B and D are structs and A is uint8_t A[42];. Where do I start from here?

I tried using cast functions but maybe I'm wrong, can you help me?

from ctypes import POINTER, byref, addressof, sizeof, cast

C = cast(byref(A), POINTER(B)).contents
E = cast(addressof(A) + sizeof(B), POINTER(D)).contents
like image 294
Carman Zyre Bautista Avatar asked Oct 13 '25 01:10

Carman Zyre Bautista


1 Answers

Listing [Python.Docs]: ctypes - A foreign function library for Python.

In short, to:

  • Dereference a pointer - cts_ptr_var.contents

  • Reference a variable (to a pointer) - ctypes.pointer(cts_var) (to later pass it as an argument to a function - ctypes.byref(cts_var))

  • Get its (C) address - ctypes.addressof(cts_var)

Now, regarding your question, a nicer way would be to use (each CTypes type's) from_address (and avoid those pointers).

code00.py:

#!/usr/bin/env python

import ctypes as cts
import sys


I8Arr42 = cts.c_uint8 * 42


class S0(cts.Structure):
    if len(sys.argv) > 1:  # @TODO - cfati: Lame (for demo purposes only)
        _pack_ = 1
    _fields_ = (
        ("ui80", cts.c_uint8),
        ("ui81", cts.c_uint8),
        ("ui320", cts.c_uint32),
        ("ui82", cts.c_uint8),
    )

PS0 = cts.POINTER(S0)


class S1(cts.Structure):
    if len(sys.argv) > 1:  # @TODO
        _pack_ = 1
    _fields_ = (
        ("ui80", cts.c_uint8),
        ("ui320", cts.c_uint32),
        ("ui81", cts.c_uint8),
    )

PS1 = cts.POINTER(S1)


def main(*argv):
    size0 = cts.sizeof(S0)
    arr = I8Arr42(*range(42))
    #arr = I8Arr42()
    print(arr, hex(cts.addressof(arr)), hex(id(arr)))
    print(f"Initial values: {arr[0]}, {arr[size0]}, {arr[-1]}\n")

    print("FromAddress")

    s00 = S0.from_address(cts.addressof(arr))  # Initialize structure from memory contents at address
    print("First structure:")
    for name, _ in s00. _fields_:
        print(f"  {name}: 0x{getattr(s00, name):02X}")

    s10 = S1.from_address(cts.addressof(arr) + size0)  # Initialize structure from memory contents at address
    print("\nSecond structure:")
    for name, _ in s10. _fields_:
        print(f"  {name}: 0x{getattr(s10, name):02X}")

    s00.ui80 = 100
    s10.ui80 = 101
    print(f"\nModified values: {arr[0]}, {arr[size0]}, {arr[-1]}\n")

    print("\nCast")

    s01 = cts.cast(cts.addressof(arr), PS0).contents
    print("First structure:")
    for name, _ in s01. _fields_:
        print(f"  {name}: 0x{getattr(s01, name):02X}")

    s11 = cts.cast(cts.addressof(arr) + size0, PS1).contents
    print("\nSecond structure:")
    for name, _ in s11. _fields_:
        print(f"  {name}: 0x{getattr(s11, name):02X}")

    s01.ui80 = 200
    s11.ui80 = 201
    print(f"\nModified values: {arr[0]}, {arr[size0]}, {arr[-1]}\n")


if __name__ == "__main__":
    print(
        "Python {:s} {:03d}bit on {:s}\n".format(
            " ".join(elem.strip() for elem in sys.version.split("\n")),
            64 if sys.maxsize > 0x100000000 else 32,
            sys.platform,
        )
    )
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q078494178]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> :: No argument - default (structure) packing
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32

<__main__.c_ubyte_Array_42 object at 0x000001E9E8C04140> 0x1e9e8fffdb0 0x1e9e8c04140
Initial values: 0, 12, 41

FromAddress
First structure:
  ui80: 0x00
  ui81: 0x01
  ui320: 0x7060504
  ui82: 0x08

Second structure:
  ui80: 0x0C
  ui320: 0x13121110
  ui81: 0x14

Modified values: 100, 101, 41


Cast
First structure:
  ui80: 0x64
  ui81: 0x01
  ui320: 0x7060504
  ui82: 0x08

Second structure:
  ui80: 0x65
  ui320: 0x13121110
  ui81: 0x14

Modified values: 200, 201, 41


Done.


[prompt]> :: Dummy argument - #pragma pack(1)
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py dummy
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32

<__main__.c_ubyte_Array_42 object at 0x000001A85B484140> 0x1a85b87ff30 0x1a85b484140
Initial values: 0, 7, 41

FromAddress
First structure:
  ui80: 0x00
  ui81: 0x01
  ui320: 0x5040302
  ui82: 0x06

Second structure:
  ui80: 0x07
  ui320: 0xB0A0908
  ui81: 0x0C

Modified values: 100, 101, 41


Cast
First structure:
  ui80: 0x64
  ui81: 0x01
  ui320: 0x5040302
  ui82: 0x06

Second structure:
  ui80: 0x65
  ui320: 0xB0A0908
  ui81: 0x0C

Modified values: 200, 201, 41


Done.

For future tasks, you might want to check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for a common pitfall when working with CTypes (calling functions).

like image 83
CristiFati Avatar answered Oct 14 '25 18:10

CristiFati