5

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

我写了那个小片段:

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)

奇怪的是,这段代码在 IPython 上运行良好,

[7] oz123@yenitiny:~ $ ipython a.py 
Clearing 0x02275b84 size 23 bytes

但与 Python 崩溃:

[8] oz123@yenitiny:~ $ python a.py 
Segmentation fault
[9] oz123@yenitiny:~ $

任何想法为什么?

我使用 Python 2.7.3 在 Debian Wheezy 上进行了测试。

小更新...

该代码适用于 CentOS 6.2 和 Python 2.6.6。代码在使用 Python 2.6.8 的 Debian 上崩溃。我试着思考为什么它可以在 CentOS 上运行,而不是在 Debian 上运行。唯一不同的原因是我的 Debian 是多架构的,而 CentOS 运行在我的带有 i686 CPU 的旧笔记本电脑上。

因此,我重新启动了我的 CentOS 笔记本电脑并在其上加载了 Debian Wheezy。该代码适用于非多架构的 Debian Wheezy。因此,我怀疑我在 Debian 上的配置有些问题......

4

1 回答 1

7

ctypes 已经有一个memset函数,所以你不必为 libc/msvcrt 函数创建一个函数指针。此外,20 字节适用于常见的 32 位平台。在 64 位系统上,它可能是 36 字节。这是 a 的布局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; 

所以在 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'

编辑

更好的选择是定义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))

例如:

>>> 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'
于 2013-03-23T01:06:45.157 回答