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.

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: 5 ∗ 7 + 6 =