2008-10-22:

Format bug, Vista i %n

vista:windows:winapi:c:c++:format bug:security:easy
Rozmawiałem ostatnio z oshogbo z mojego teamu o format bugu, i gdy doszliśmy do testowania przykładowego kodu, okazało się że to co powinno działać - tag %n, nie działa. Co ciekawe, problem wydawał się dotyczyć jedynie Windowsa Vista, ponieważ na XP wszystko śmigało aż miło. Postanowiłem kapkę wgłębić się w problem, i oto co się dowiedziałem...

Defaultowy C-RunTime Library (msvcrt.dll) na Viście okazuje się mieć defaultowo wyłączony tag %n - z powodów bezpieczeństwa oczywiście (szperając po Google znalazłem że kilka (dosłownie kilka) osób miało przez to problem z działaniem aplikacji). Jak można przeczytać na MSDN (patrz Security Note), tag %n można włączyć za pomocą funkcji _set_printf_count_output. I na tym pewnie sprawa by się skończyła, gdyby nie okazało się że ów funkcja nie jest eksportowana w msvcrt.dll który posiadam w systemie (defaultowy, nic nie podmieniałem). Co ciekawe, w msvcp60.dll ani w msvcp70.dll, ani w msvcp71.dll które mam w katalogu systemowym również takowego eksportu nie znalazłem. Znalazłem go natomiast w msvcp90.dll (80 nie mam nigdzie żeby sprawdzić).

Chce więc użyć %n, ale nie mogę, bo moja aplikacja używa defaultowego msvcrt. I jak tu rozwiązać ten problem?
Rozwiązań jest jak zwykle kilka.

By the way...
If want to improve your binary file and protocol skills, check out the workshop I'll be running between April and June → Mastering Binary Files and Protocols: The Complete Journey


Pierwszym jest użycie zamiast msvcrt.dll, msvcp90.dll. I pewnie by to rozwiązało problem, ale również wymagało by kopiowania tego liba razem z projektem, bo nie każdy go ma. Więc odpada.
Druga metoda polega na znalezieniu funkcji _set_printf_count_output, i użycie jej po adresie po prostu... ale co jeśli ktoś ma inny msvcrt (co jest bardzo prawdopodobne)? Niemniej jednak for fun zaimplementowałem ten sposób (offsety są z mojego msvcrt):

#include <windows.h>
#include <stdio.h>

int
main(void)
{
 int (*_get_printf_count_output)(void) = (int(*)(void))((char*)GetModuleHandle("msvcrt.dll") + 0x6FCD8001 - 0x6FC60000);
 int (*_set_printf_count_output)(int)  = (int(*)(int)) ((char*)GetModuleHandle("msvcrt.dll") + 0x6FC750B5 - 0x6FC60000);

 int n = 0;
   
 printf("alamakota\n%n", &n);
 printf("n=%i\n", n);

 printf("%i\n", _get_printf_count_output());
 printf("%i\n", _set_printf_count_output(1));
 printf("%i\n", _get_printf_count_output());

 printf("alamakota\n%n", &n);
 printf("n=%i\n", n);

 return 0;
}


Kompilacja i odpalenie:

21:10:32 gynvael >gcc test.c

21:10:36 gynvael >a
alamakota
n=0
0
0
1
alamakota
n=10


Działa. Ale i tak sposób do bani. Wróciłem więc do analizy msvcrt, i zauważyłem że _set_printf_count_output jest wywoływane przez coś, co jest wywoływane z kolei przez DllMain, ale, jest wywoływane tylko w wypadku gdy w nagłówku PE pliku exe Linker Version (MajorLinkerVersion i MinorLinkerVersion w OptionalHeader) jest ustawiony na 6.0 (ciekawy warunek, nie wiedziałem że cokolwiek korzysta z tych pól w PE). Anyway, test wykazał że faktycznie tak jest. Poniżej jest programik do zmiany Linker Version na 6.0 (oczywiście nie jest śliczny):

fixprintf.c (1kb)

Kompilacja i sprawdzenie na poprzednim programie:

21:10:36 gynvael >gcc fixprintf.c -o fixprintf.exe

21:17:13 gynvael >fixprintf.exe a.exe
done...

21:17:15 gynvael >a.exe
alamakota
n=10
1
1
1
alamakota
n=10


No i chyba tyle Vista story na dzisiaj. Dodam że pojawiły się propozycje dodania opcji -malt-ISO-printf do GCC, która by powodowała użycie printf z MinGW zamiast z msvcrt.

Add a comment:

Nick:
URL (optional):
Math captcha: 4 ∗ 10 + 10 =