python中的ctypes与memset崩溃

Oz1*_*123 5 python ctypes ipython

我试图从内存中删除密码字符串,就像在这里建议的那样.

我写了那个小片段:

import ctypes, sys

def zerome(string):
    location = id(string) + 20
    size     = sys.getsizeof(string) - 20
    #memset =  ctypes.cdll.msvcrt.memset
    # For Linux, use the following. Change the 6 to whatever it is on your computer.
    print ctypes.string_at(location, size)
    memset =  ctypes.CDLL("libc.so.6").memset
    memset(location, 0, size)
    print "Clearing 0x%08x size %i bytes" % (location, size)
    print ctypes.string_at(location, size)

a = "asdasd"

zerome(a)
Run Code Online (Sandbox Code Playgroud)

奇怪的是,这段代码可以和IPython一起使用,

[7] oz123@yenitiny:~ $ ipython a.py 
Clearing 0x02275b84 size 23 bytes
Run Code Online (Sandbox Code Playgroud)

但崩溃与Python:

[8] oz123@yenitiny:~ $ python a.py 
Segmentation fault
[9] oz123@yenitiny:~ $
Run Code Online (Sandbox Code Playgroud)

有什么想法吗?

我使用Python 2.7.3在Debian Wheezy上测试过.

小更新......

该代码适用于使用Python 2.6.6的CentOS 6.2.代码在Debian上用Python 2.6.8崩溃了.我试过想为什么它在CentOS上运行,而不是在Debian上运行.唯一不同的原因是,我的Debian是multiarch,CentOS在我的旧笔记本电脑上使用i686 CPU运行.

因此,我重新启动了我的CentOS latop并加载了Debian Wheezy.该代码适用于Debian Wheezy,它不是多拱的.因此,我怀疑我在Debian上的配置有些问题......

Ery*_*Sun 7

ctypes已经有了一个memset函数,所以你不必为libc/msvcrt函数创建一个函数指针.此外,20个字节用于常见的32位平台.在64位系统上,它可能是36个字节.这是一个布局PyStringObject:

typedef struct {
    Py_ssize_t ob_refcnt;         // 4|8 bytes
    struct _typeobject *ob_type;  // 4|8 bytes
    Py_ssize_t ob_size;           // 4|8 bytes
    long ob_shash;                // 4|8 bytes (4 on 64-bit Windows)
    int ob_sstate;                // 4 bytes
    char ob_sval[1];
} PyStringObject; 
Run Code Online (Sandbox Code Playgroud)

因此,在32位系统上可能是5*4 = 20个字节,在64位Linux上可能是8*4 + 4 = 36个字节,或者在64位Windows上是8*3 + 4*2 = 32个字节.由于未使用垃圾收集头跟踪字符串,因此您可以使用sys.getsizeof.通常,如果您不希望包含GC头大小(在内存中它实际上是在您获得的对象的基址之前id),那么使用对象的__sizeof__方法.至少这是我经验中的一般规律.

你想要的是简单地从对象大小中减去缓冲区大小.CPython中的字符串以空值终止,因此只需在其长度上加1即可获得缓冲区大小.例如:

>>> a = 'abcdef'
>>> bufsize = len(a) + 1
>>> offset = sys.getsizeof(a) - bufsize
>>> ctypes.memset(id(a) + offset, 0, bufsize)
3074822964L
>>> a
'\x00\x00\x00\x00\x00\x00'
Run Code Online (Sandbox Code Playgroud)

编辑

更好的选择是定义PyStringObject结构.这样便于检查ob_sstate.如果它大于0,那意味着字符串被实现,而理智的事情是引发异常.单字符字符串以及仅由ASCII字母和下划线组成的代码对象中的字符串常量,以及解释器内部用于名称(变量名称,属性)的字符串.

from ctypes import *

class PyStringObject(Structure):
    _fields_ = [
      ('ob_refcnt', c_ssize_t),
      ('ob_type', py_object),
      ('ob_size', c_ssize_t),
      ('ob_shash', c_long),
      ('ob_sstate', c_int),
      # ob_sval varies in size
      # zero with memset is simpler
    ]

def zerostr(s):
    """zero a non-interned string"""
    if not isinstance(s, str):
        raise TypeError(
          "expected str object, not %s" % type(s).__name__)

    s_obj = PyStringObject.from_address(id(s))
    if s_obj.ob_sstate > 0:
        raise RuntimeError("cannot zero interned string")

    s_obj.ob_shash = -1  # not hashed yet
    offset = sizeof(PyStringObject)
    memset(id(s) + offset, 0, len(s))
Run Code Online (Sandbox Code Playgroud)

例如:

>>> s = 'abcd' # interned by code object
>>> zerostr(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 10, in zerostr
RuntimeError: cannot zero interned string

>>> s = raw_input() # not interned
abcd
>>> zerostr(s)
>>> s
'\x00\x00\x00\x00'
Run Code Online (Sandbox Code Playgroud)