使用ctypes检索本机基类的地址 [英] Retrieving address of native base class with ctypes

查看:264
本文介绍了使用ctypes检索本机基类的地址的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望能够在不需要临时文件的情况下将证书传递给Python的ssl库。看来Python ssl模块无法做到这一点

I want to be able to pass a certificate to Python's ssl library without requiring a temporary file. It seems that the Python ssl module cannot do that.

要工作围绕此问题,我想从本机<检索存储在 ssl._ssl._SSLContext 类中的基础 SSL_CTX 结构code> _ssl 模块。然后,我可以使用ctypes从libssl手动调用相应的 SSL_CTX _ * 函数。 在此处显示了如何在C中执行此操作,我将通过ctypes执行相同的操作。

To work around this problem I want to retrieve the underlying SSL_CTX struct stored in the ssl._ssl._SSLContext class from the native _ssl module. Using ctypes I could then manually call the respective SSL_CTX_* functions from libssl with that context. How to do that in C is shown here and I would do the same thing via ctypes.

不幸的是,我被困在设法从 ssl._ssl._SSLContext <插入 load_verify_locations 函数的地步/ code>,但似乎无法获得 ssl._ssl._SSLContext 结构实例的正确内存地址。所有 load_verify_locations 函数都看到的是父 ssl.SSLContext 对象。

Unfortunately, I'm stuck at the point where I managed to hook into the load_verify_locations function from ssl._ssl._SSLContext but seem to be unable to get the right memory address of the instance of the ssl._ssl._SSLContext struct. All the load_verify_locations function is seeing is the parent ssl.SSLContext object.

我的问题是,如何从 ssl.SSLContext 对象的实例获取本机基类 ssl的内存._ssl._SSLContext ?如果可以的话,我可以轻松访问其 ctx 成员。

My question is, how do I get from an instance of a ssl.SSLContext object to the memory of the native base class ssl._ssl._SSLContext? If I would have that, I could easily access its ctx member.

这是到目前为止的代码。有关如何猴子式打补丁本地Python模块的内容,请访问 Lincoln Clarete的禁果项目

Here is my code so far. Credits for how to monkeypatch a native Python module go to the forbidden fruit project by Lincoln Clarete

Py_ssize_t = hasattr(ctypes.pythonapi, 'Py_InitModule4_64') and ctypes.c_int64 or ctypes.c_int

class PyObject(ctypes.Structure):
    pass

PyObject._fields_ = [
    ('ob_refcnt', Py_ssize_t),
    ('ob_type', ctypes.POINTER(PyObject)),
]

class SlotsProxy(PyObject):
    _fields_ = [('dict', ctypes.POINTER(PyObject))]

class PySSLContext(ctypes.Structure):
    pass

PySSLContext._fields_ = [
        ('ob_refcnt', Py_ssize_t),
        ('ob_type', ctypes.POINTER(PySSLContext)),
        ('ctx', ctypes.c_void_p),
        ]

name = ssl._ssl._SSLContext.__name__
target = ssl._ssl._SSLContext.__dict__
proxy_dict = SlotsProxy.from_address(id(target))
namespace = {}
ctypes.pythonapi.PyDict_SetItem(
        ctypes.py_object(namespace),
        ctypes.py_object(name),
        proxy_dict.dict,
)
patchable = namespace[name]

old_value = patchable["load_verify_locations"]

libssl = ctypes.cdll.LoadLibrary("libssl.so.1.0.0")
libssl.SSL_CTX_set_verify.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p)
libssl.SSL_CTX_get_verify_mode.argtypes = (ctypes.c_void_p,)

def load_verify_locations(self, cafile, capath, cadata):
    print(self)
    print(self.verify_mode)
    addr = PySSLContext.from_address(id(self)).ctx
    libssl.SSL_CTX_set_verify(addr, 1337, None)
    print(libssl.SSL_CTX_get_verify_mode(addr))
    print(self.verify_mode)
    return old_value(self, cafile, capath, cadata)

patchable["load_verify_locations"] = load_verify_locations

context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)

输出为:

<ssl.SSLContext object at 0x7f4b81304ba8>
2
1337
2

这表明,无论我改变不是Python知道的ssl上下文,而是其他一些随机的内存位置。

This suggests, that whatever I'm changing is not the ssl context that Python knows about but some other random memory location.

要从上面尝试代码,必须运行https服务器。使用以下命令生成自签名SSL证书:

To try out the code from above, you have to run a https server. Generate a self-signed SSL certificate using:

$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost' -nodes

并使用以下命令启动服务器以下代码:

And start a server using the following code:

import http.server, http.server
import ssl
httpd = http.server.HTTPServer(('localhost', 4443), http.server.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='cert.pem', keyfile='key.pem', server_side=True)
httpd.serve_forever()

然后将以下行添加到我上面的示例代码的结尾:

And then add the following line to the end of my example code above:

urllib.request.urlopen("https://localhost:4443", context=context)


推荐答案

实际 SSLContext 即将来临,假设不再正确。

Actual SSLContext answer forthcoming, the assumption is no longer correct.

请参见 https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations

有第三个参数, cadata


如果有cadata对象,则为要么是一个包含一个或多个
的PEM编码证书的ASCII字符串,要么是一个DER编码的
证书的类似字节的对象。

The cadata object, if present, is either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates.

显然是自Python 3.4以来的情况

Apparently that's the case since Python 3.4

这很容易, ssl.SSLContext 继承自 _ssl._SSLContext ,这在Python数据模型中意味着在一个内存地址中只有一个对象

This one's easy, ssl.SSLContext inherits from _ssl._SSLContext which in Python data model means that there's just one object at one memory address.

因此, ssl.SSLContext()。load_verify_locations(...)实际上会调用:

ctx = \
ssl.SSLContext.__new__(<type ssl.SSLContext>, ...)  # which calls
    self = _ssl._SSLContext.__new__(<type ssl.SSLContext>, ...)  # which calls
        <type ssl.SSLContext>->tp_alloc()  # which understands inheritance
        self->ctx = SSL_CTX_new(...)  # _ssl fields
    self.set_ciphers(...)  # ssl fields
    return self

_ssl._SSLContext.load_verify_locations(ctx, ...)`.

C实现将获得一个看似错误类型的对象,但这没关系,因为所有期望的字段都是在那里,因为它是由通用 type-> tp_alloc 分配的,并且字段首先由 _ssl._SSLContext 和然后通过 ssl.SSLContext

The C implementation will get an object of seemingly wrong type, but that's OK because all the expected fields are there, as it was allocated by generic type->tp_alloc and fields were filled in first by _ssl._SSLContext and then by ssl.SSLContext.

这是一个演示(省略了繁琐的细节):

Here's a demonstration (tedious details omitted):

# _parent.c
typedef struct {
  PyObject_HEAD
} PyParent;

static PyObject* parent_new(PyTypeObject* type, PyObject* args,
                            PyObject* kwargs) {
  PyParent* self = (PyParent*)type->tp_alloc(type, 0);
  printf("Created parent %ld\n", (long)self);
  return (PyObject*)self;
}

# child.py
class Child(_parent.Parent):
    def foo(self):
        print(id(self))

c1 = Child()
print("Created child:", id(c1))

# prints:
Created parent 139990593076080
Created child: 139990593076080



获取基础OpenSSL上下文



Getting the underlying OpenSSL context

typedef struct {
    PyObject_HEAD
    SSL_CTX *ctx;
    <details skipped>
} PySSLContext;

因此, ctx 位于一个已知的偏移量,即:

Thus, ctx is at a known offset, which is:

PyObject_HEAD
This is a macro which expands to the declarations of the fields of the PyObject type; it is used when declaring new types which represent objects without a varying length. The specific fields it expands to depend on the definition of Py_TRACE_REFS. By default, that macro is not defined, and PyObject_HEAD expands to:

Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;

When Py_TRACE_REFS is defined, it expands to:

PyObject *_ob_next, *_ob_prev;
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;

因此,在生产(非调试)构建中,并考虑自然对齐, PySSLContext 变为:

Thus, in a production (non-debug) build, and taking natural alignment into consideration, PySSLContext becomes:

struct {
    void*;
    void*;
    SSL_CTX *ctx;
    ...
}

因此:

_ctx = _ssl._SSLContext(2)
c_ctx = ctypes.cast(id(_ctx), ctypes.POINTER(ctypes.c_void_p))
c_ctx[:3]
[1, 140486908969728, 94916219331584]
# refcnt,      type,          C ctx



全部放入



Putting it all together

import ssl
import socket
import ctypes
import pytest


def contact_github(cafile=""):
    ctx = ssl.SSLContext()
    ctx.verify_mode = ssl.VerifyMode.CERT_REQUIRED

    # ctx.load_verify_locations(cafile, "empty", None) done via ctypes
    ssl_ctx = ctypes.cast(id(ctx), ctypes.POINTER(ctypes.c_void_p))[2]
    cssl = ctypes.CDLL("/usr/lib/x86_64-linux-gnu/libssl.so.1.1")
    cssl.SSL_CTX_load_verify_locations.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
    assert cssl.SSL_CTX_load_verify_locations(ssl_ctx, cafile.encode("utf-8"), b"empty")

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("github.com", 443))

    ss = ctx.wrap_socket(s)
    ss.send(b"GET / HTTP/1.0\n\n")
    print(ss.recv(1024))


def test_wrong_cert():
    with pytest.raises(ssl.SSLError):
        contact_github(cafile="bad-cert.pem")


def test_correct_cert():
    contact_github(cafile="good-cert.pem")

这篇关于使用ctypes检索本机基类的地址的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆