17

Linux で純粋な Python コードからインライン マシン コードを呼び出そうとしています。この目的のために、コードをバイトリテラルに埋め込みます

code = b"\x55\x89\xe5\x5d\xc3"

mprotect()次にviaを呼び出してctypes、コードを含むページの実行を許可します。最後にctypes、コードを呼び出すために使用してみます。ここに私の完全なコードがあります:

#!/usr/bin/python3

from ctypes import *

# Initialise ctypes prototype for mprotect().
# According to the manpage:
#     int mprotect(const void *addr, size_t len, int prot);
libc = CDLL("libc.so.6")
mprotect = libc.mprotect
mprotect.restype = c_int
mprotect.argtypes = [c_void_p, c_size_t, c_int]

# PROT_xxxx constants
# Output of gcc -E -dM -x c /usr/include/sys/mman.h | grep PROT_
#     #define PROT_NONE 0x0
#     #define PROT_READ 0x1
#     #define PROT_WRITE 0x2
#     #define PROT_EXEC 0x4
#     #define PROT_GROWSDOWN 0x01000000
#     #define PROT_GROWSUP 0x02000000
PROT_NONE = 0x0
PROT_READ = 0x1
PROT_WRITE = 0x2
PROT_EXEC = 0x4

# Machine code of an empty C function, generated with gcc
# Disassembly:
#     55        push   %ebp
#     89 e5     mov    %esp,%ebp
#     5d        pop    %ebp
#     c3        ret
code = b"\x55\x89\xe5\x5d\xc3"

# Get the address of the code
addr = addressof(c_char_p(code))

# Get the start of the page containing the code and set the permissions
pagesize = 0x1000
pagestart = addr & ~(pagesize - 1)
if mprotect(pagestart, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC):
    raise RuntimeError("Failed to set permissions using mprotect()")

# Generate ctypes function object from code
functype = CFUNCTYPE(None)
f = functype(addr)

# Call the function
print("Calling f()")
f()

このコードは、最後の行でセグメンテーション違反を起こします。

  1. セグメンテーション違反が発生するのはなぜですか? 呼び出しはmprotect()成功を通知するので、ページでコードを実行することを許可する必要があります。

  2. コードを修正する方法はありますか?純粋な Python と現在のプロセス内で実際にマシン コードを呼び出すことはできますか?

(さらにいくつかの発言:私は実際に目標を達成しようとしているわけではありません-私は物事がどのように機能するかを理解しようとしています。また、5バイトのコードが落ちるケースを除外するために、2*pagesize代わりにpagesize呼び出しで使用しようとしましたmprotect()ページ境界 -- とにかく不可能なはずです. テストには Python 3.1.3 を使用しました. 私のマシンは 32 ビットの i386 ボックスです. 考えられる解決策の 1 つは、純粋な Python コードから ELF 共有オブジェクトを作成し、それをロードすることです.経由ctypesですが、それは私が探している答えではありません:)

編集: 次の C バージョンのコードは正常に動作しています。

#include <sys/mman.h>

char code[] = "\x55\x89\xe5\x5d\xc3";
const int pagesize = 0x1000;

int main()
{
    mprotect((int)code & ~(pagesize - 1), pagesize,
             PROT_READ|PROT_WRITE|PROT_EXEC);
    ((void(*)())code)();
}

編集 2 : コードにエラーが見つかりました。この線

addr = addressof(c_char_p(code))

まず、インスタンスchar*の先頭を指すctypes を作成します。 このポインターに適用されると、このポインターが指しているアドレスではなく、ポインター自体のアドレスが返されます。bytescodeaddressof()

コードの先頭のアドレスを実際に取得するために私が見つけた最も簡単な方法は

addr = addressof(cast(c_char_p(code), POINTER(c_char)).contents)

より簡単な解決策のヒントをいただければ幸いです:)

この行を修正すると、上記のコードが「機能」します (つまり、segfaulting の代わりに何もしません...)。

4

3 に答える 3

6

これについて簡単なデバッグを行ったところ、 へのポインターが正しく構築されていないことがわかりました。コードを呼び出すcode関数ポインターを渡す前に、内部のどこかで ctypes が物事を変更しています。ffi_call()

ffi_call_unix64()これは、関数ポインタがに保存されている(私は64ビットです)の行です%r11

57   movq    %r8, %r11               /* Save a copy of the target fn.

%r11コードを実行すると、呼び出しを試行する直前に読み込まれる値は次のとおりです。

(gdb) x/5b $r11
0x7ffff7f186d0: -108    24      -122    0       0

ポインターを構築して関数を呼び出すための修正は次のとおりです。

raw = b"\x55\x89\xe5\x5d\xc3"
code = create_string_buffer(raw)
addr = addressof(code)

これを実行すると、そのアドレスに正しいバイトが表示され、関数は正常に実行されます。

(gdb) x/5b $r11
0x7ffff7f186d0: 0x55    0x89    0xe5    0x5d    0xc3
于 2011-05-26T21:24:41.263 に答える
3

命令キャッシュをフラッシュする必要がある場合があります。

mprotect() が自動的にこれを行うかどうかは (とにかく私には)不明です。

[アップデート]

もちろん、cacheflush() のドキュメントを読めば、それが MIPS にのみ適用されることがわかります (man ページによると)。

これが x86 であると仮定すると、WBINVD (または CLFLUSH) 命令を呼び出す必要がある場合があります。

一般に、自己変更コードは i-cache をフラッシュする必要がありますが、私が知る限り、そうするためのリモート移植可能な方法はありません。

于 2011-05-26T18:08:30.893 に答える
2

最初にコードをCで動作させてから、に変換することをお勧めしますctypes。Pythonからアセンブリを実行できるようにしたいだけの場合は、 CorePyのようなものもあります。

于 2011-05-26T18:15:41.280 に答える