記事ディレクトリ
実験要件
printf
関数を新しい関数に変更しnewprintf
、getshell
次の命令を実行します。
whoami
echo "学号" > num.txt
cat num.txt
exit
/bin/sh
文字列はのパラメータであるgetshell
ため、printf
これを直接使用できます。
実験手順
私が採用した実験原理: パッチ プログラムを.text
ターゲット プログラムにコピーし.eh_frame
、ターゲット プログラムの関数ジャンプ アドレスを変更して、脆弱なプログラムがジャンプしてパッチ コード.eh_frame
を。
1. 新しい printf 関数を書く
インライン アセンブリを使用します。インライン アセンブリで使用される構文はAT&T
. 通常書かれている編纂とは違いますIntel
。インライン アセンブリの記述については、GCC Inline Assembly Basics - Short Book (jianshu.com)という短い本を参照します。
ブロガーさんの詳しい解説ありがとうございます!
書かれたコードはhook.c
次のとおりです。
void myprintf(char *a, int b) {
int tmp;
asm(
"mov %1,%%rdi\n"
:"=r"(tmp)
:"r"(a)
:"%rdi"
);
asm(
"mov $0,%rsi\n"
"mov $0,%rdx\n"
"mov $59,%rax\n"
"syscall\n"
);
}
主な意味は、元のパラメーターexecve(a,0,0)
であるコンストラクターです。64 ビット システムのシステム コール番号に対応します。a
printf
/bin/sh
execve
59
上記のコードを静的リンク ライブラリにコンパイルします。
gcc -c hook.c -o hook
2. LIEF を使用する Python スクリプトを作成する
最初に依存関係をインストールします。
pip install lief -y
pip install pwn -y
Note:
-y
The parameter is the default when the "whether to install" pop upyes
. 下位バージョンの pip はこのオプションをサポートしていないため、追加しないでください。個人的には、パラメータを追加することに慣れています-y
。
python2 のリーフをダウンロードすると、常にエラーが報告されます。公式lief
サイトで確認したところ、最新バージョンのliefはpython2に対応していません。そのため、python3 環境が使用されます。
python2 を使用している場合は、説明書のスクリプトを直接コピーするだけです。
スクリプトの考え方は説明書と同じです。ファイルを読み取って解析し、セクションをセクションにコピーgetshell
し、ステートメントを に変更します。最後に、変更した実行可能ファイルを に書き込みます。hook
hook
.text
getshell
.eh_frame
call _printf
call .eh_frame的起始地址
getshell.patched
バージョンの問題のみが原因でpython
、変更が必要な場所が 3 つあります。
- python3 の出力に
print
は括弧が必要です。 - 2 つのデータ型は、python3 では区別され
bytes
、string
python2 では混在しているため、b
p32 の変換結果とつなぎ合わせるには、python3 ではバイト文字列の前に追加する必要があります。 ord()
この関数が受け取る型は長さ 1 の文字列です. 型をfor...in...
トラバースすると取得したデータは既にその型です. python2 では呼び出す必要がありますが, python3 では呼び出す必要はありません.bytes
int
ord
具体的なスクリプトはlief_test.py
次のとおりです。
import lief
from pwn import *
def patch_call(file,srcaddr, dstaddr , arch = "amd64"):
print(hex(dstaddr))
length = p32((dstaddr - (srcaddr + 5 ))& 0xffffffff)
order = b'\xe8' +length
print(disasm(order , arch=arch))
file.patch_address(srcaddr, [i for i in order])
binary = lief.parse("./getshell")
hook = lief.parse('./hook')
# write hook 's .text content to binary's .eh_frame content
sec_ehrame = binary.get_section( '.eh_frame')
print(sec_ehrame.content)
sec_text = hook.get_section('.text')
print(sec_text.content)
sec_ehrame.content = sec_text.content
print(binary.get_section('.eh_frame').content)
# hook target call
dstaddr = sec_ehrame.virtual_address
srcaddr = 0x401149
patch_call(binary,srcaddr,dstaddr)
binary.write('getshell.patched')
3.getshell
セクション.eh_frame
を実行可能に設定
.eh_frame
セクションを直接変更してジャンプして実行すると、次のセグメント エラーが報告されます。
パーミッションの変更:セグメントフォルト|ELF 非実行セグメントパーミッションの変更 (010 エディター) .
それはいいです。
4. パッチを適用し、パッチを適用したプログラムを実行します
パッチをコンパイルし、パッチを適用して、パッチを適用したプログラムを実行します。
gcc -c hook.c -o hook
python3 lief_test.py
./getshell.patched
最後に、フェッチが成功したことを確認しますshell
。
入力:
whoami
echo "学号" > num.txt
cat num.txt
exit
シェルが正常に取得できていることがわかります。
5. 修正前後でプログラムのサイズが変わらないことを確認する
同級生と話し合ったところ、主にLIEF
このライブラリの制限により、プログラムのサイズが大きく変わっていることがわかりました。このパッチは非常に単純なので、プログラムのサイズが変更の前後で同じであることを確認するために、パッチ適用に直接IDA Pro
使用することに。(うまくいくかどうかはわかりませんが、それ以外の方法は本当にわかりません)
原理は前回課題と同じoverflow
. 詳しくはこちらのHUSTネットワーク攻防演習|5_バイナリファイルパッチ技術|実験1 オーバーフロー.
ずさんな書き方をした先輩もいて、検索したら出てきた
【ネットワークセキュリティ実践Ⅲ】実験5.パッチ
ジャンプする開始位置であるcall _printf
に変更します。次にパッチ コードを記述します。パッチ コードは最終的に元の実行フローに戻ります。jmp 0x402058
.eh_frame
.eh_frame
mov rsi,0
mov rdx,0
mov rax,59
syscall
jmp 0x40114E
次に、元のプログラムが配置されているセクション.eh_frame
を実行可能に変更します。
操作結果は以下の通りで、getshell
ファイルサイズを変更せずに成功したことがわかります。