Python ctypesと関数呼び出し
質問
私の友人は、x86で動作する小さな概念実証アセンブラを作成しました。 x86_64にも移植することにしましたが、すぐに問題が発生しました。
Cで小さなプログラムを作成し、コードをコンパイルしてオブジェクトダンプしました。その後、Pythonスクリプトに挿入したため、x86_64コードは正しいです。
from ctypes import cast, CFUNCTYPE, c_char_p, c_long
buffer = ''.join(map(chr, [ #0000000000000000 <add>:
0x55, # push %rbp
0x48, 0x89, 0xe5, # mov %rsp,%rbp
0x48, 0x89, 0x7d, 0xf8, # mov %rdi,-0x8(%rbp)
0x48, 0x8b, 0x45, 0xf8, # mov -0x8(%rbp),%rax
0x48, 0x83, 0xc0, 0x0a, # add <*>xa,%rax
0xc9, # leaveq
0xc3, # retq
]))
fptr = cast(c_char_p(buffer), CFUNCTYPE(c_long, c_long))
print fptr(1234)
今、このスクリプトを実行するたびにセグメンテーションエラーが発生し続けるのはなぜですか?
mprotectについてはまだ質問があり、実行フラグはありません。バッファオーバーランなどの最も基本的なセキュリティエクスプロイトから保護すると言われています。しかし、それが使用されている本当の理由は何ですか? .textが見つかるまで書き続けるだけで、指示を素敵なPROT_EXEC -areaに挿入できます。もちろん、.text
で書き込み保護を使用しない限りそれでは、なぜPROT_EXECがどこにでもあるのでしょうか? .textセクションが書き込み禁止になっているのは非常に助かりませんか?
解決
vincent が述べたように、これは割り当てられたページがマークされているためです非実行可能ファイルとして。新しいプロセッサはこの機能性をサポートし、それをサポートするOSによってセキュリティの追加レイヤーとして使用されます。アイデアは、特定のバッファオーバーフロー攻撃から保護することです。例えば。一般的な攻撃は、スタック変数をオーバーフローさせ、挿入したコードを指すようにリターンアドレスを書き換えます。実行不可能なスタックでは、プロセスの制御ではなく、セグメンテーション違反のみが生成されるようになりました。ヒープメモリにも同様の攻撃が存在します。
それを回避するには、保護を変更する必要があります。これはページアラインメモリでのみ実行できるため、おそらく次のようなコードに変更する必要があります。
libc = CDLL('libc.so')
# Some constants
PROT_READ = 1
PROT_WRITE = 2
PROT_EXEC = 4
def executable_code(buffer):
"""Return a pointer to a page-aligned executable buffer filled in with the data of the string provided.
The pointer should be freed with libc.free() when finished"""
buf = c_char_p(buffer)
size = len(buffer)
# Need to align to a page boundary, so use valloc
addr = libc.valloc(size)
addr = c_void_p(addr)
if 0 == addr:
raise Exception("Failed to allocate memory")
memmove(addr, buf, size)
if 0 != libc.mprotect(addr, len(buffer), PROT_READ | PROT_WRITE | PROT_EXEC):
raise Exception("Failed to set protection on buffer")
return addr
code_ptr = executable_code(buffer)
fptr = cast(code_ptr, CFUNCTYPE(c_long, c_long))
print fptr(1234)
libc.free(code_ptr)
注:ページを解放する前に実行可能フラグを設定解除することをお勧めします。ほとんどのCライブラリは、実際にメモリをOSに返しませんが、独自のプールに保持します。これは、EXECビットをクリアせずに他の場所でページを再利用し、セキュリティ上の利点を回避することを意味します。
また、これはかなり移植性がないことに注意してください。 Linuxでテストしましたが、他のOSではテストしていません。 Windowsでは動作しませんが、他のUnix(BSD、OsX?)でも動作します。
他のヒント
友人といくつかの研究を行ったところ、これはプラットフォーム固有の問題であることがわかりました。一部のプラットフォームではPROT_EXECを使用せずにmalloc mmapsメモリを使用し、他のプラットフォームでは使用しないと思われます。
したがって、後でmprotectで保護レベルを変更する必要があります。
ややこしい、何をすべきかを見つけるのに時間がかかった。
from ctypes import (
cast, CFUNCTYPE, c_long, sizeof, addressof, create_string_buffer, pythonapi
)
PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC = 0, 1, 2, 4
mprotect = pythonapi.mprotect
buffer = ''.join(map(chr, [ #0000000000000000 <add>:
0x55, # push %rbp
0x48, 0x89, 0xe5, # mov %rsp,%rbp
0x48, 0x89, 0x7d, 0xf8, # mov %rdi,-0x8(%rbp)
0x48, 0x8b, 0x45, 0xf8, # mov -0x8(%rbp),%rax
0x48, 0x83, 0xc0, 0x0a, # add <*>xa,%rax
0xc9, # leaveq
0xc3, # retq
]))
pagesize = pythonapi.getpagesize()
cbuffer = create_string_buffer(buffer)#c_char_p(buffer)
addr = addressof(cbuffer)
size = sizeof(cbuffer)
mask = pagesize - 1
if mprotect(~mask&addr, mask&addr + size, PROT_READ|PROT_WRITE|PROT_EXEC) < 0:
print "mprotect failed?"
else:
fptr = cast(cbuffer, CFUNCTYPE(c_long, c_long))
print repr(fptr(1234))
最初に実行可能として設定しないと、割り当てられたメモリを自由に実行できないと思います。私は一度も試したことはありませんが、Unix関数 mprotect
を確認することをお勧めします。
http://linux.about.com/library/cmd/blcmdl2_mprotect.htm
VirtualProtect
はWindowsでも同じことをしているようです:
http://msdn.microsoft.com/ en-us / library / aa366898(VS.85).aspx
Pythonはそのような使用も許可しますか?それを学ばなければならない...
インタープリターは、レジスターが変更されることを期待していないと思います。このようにアセンブラ出力を使用する予定がある場合は、関数内で使用するレジスタを保存してみてください。
ところで、x86_64の呼び出し規則は通常のx86とは異なります。スタックポインターのアライメントが失われ、他のツールで生成された外部オブジェクトが混在すると、問題が発生する可能性があります。
私が考えただけの簡単なアプローチがありますが、最近ではmprotectを使用していません。プログラムの実行可能領域を直接mmapします。最近、Pythonにはこれを正確に行うためのモジュールがありますが、コードのアドレスを取得する方法は見つかりませんでした。つまり、文字列バッファーを使用して間接的に実行フラグを設定する代わりに、mmapを呼び出してメモリを割り当てます。これは簡単で安全です。コードのみを実行できるようになりました。