2008-10-23:

Format bug, Vista and %n

vista:windows:winapi:c:c++:format bug:security:easy
Recently I've talked with my teammate oshogbo about the format bug (aka format string attack), and when we got to testing a sample code, a thing that should work - the %n tag, didn't work at all. What's more interesting, this behavior was Vista specific, since everything else worked well on XP. I've decided to take a look inside, and here's what I've found out...

The default C-RunTime Library (msvcrt.dll) on Vista seems to have %n disabled by default - security reasons of course (well, Google showed a few pages with devs complaining that their application don't work on vista because of that, but thats the price of security I guess). As is written on MSDN (see Security Note), tag %n can be enabled back again using the _set_printf_count_output function. And that would be a good solution for my little problem, but I've quite quickly learned that for some unknown reason this function is NOT exported by the msvcrt.dll which is in my system directory (it's the default one). What's more interesting, I didn't see the function being exported in msvcp60.dll, msvcp70.dll or msvcp71.dll either. However, it was exported in msvcp90.dll (I didn't check msvcp8X.dll).

So. I want to use %n, but I cannot, because my app uses the default msvcrt. How to solve this issue? There is a couple of solutions:

1) I should switch from using msvcrt.dll to msvcp90.dll. And that would solve the problem, however it would require me to distribute this DLL with my project, because it's not on the system by default.
2) I should find the function _set_printf_count_output in msvcrt (it's there, just not exported), and use it by the address. But what if somebody else has a different msvcrt? Signatures then! What if the signature doesn't fit? Well, this isn't the best way. However, just for tests, I've decided to implement the method with static addresses:

#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;
}


Compile and run:

21:10:32 gynvael >gcc test.c

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


It works! But it still sucks. So I've got back to msvcrt analysis looking for another way. And I've found out that _set_printf_count_output is called by something that is called by the DllMain, but, it's only called (with 1 in the argument) if the PE header of the executable file has Linker Version (MajorLinkerVersion and MinorLinkerVersion in OptionalHeader) set to 6.0 (interesting condition, I didn't knew that something used the linker field in PE). Anyway, as a test has shown, this actually works.
The code below changes the linker version to 6.0 (some error checks are missing of course):

fixprintf.c (1kb)

Compile, and test on the previously mentioned executable:

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


And that's the end of Vista story for today. I'll just add that there has been a proposal to add -malt-ISO-printf option to GCC, which would make the app use printf from MinGW instead of the msvcrt one.

PS. I wonder what other things will happen when the linker version is set to 6.0. Oshogbo told me that switching linker to 6.0 does in fact break the app at his place when %n is used. Hmm, thats interesting. I'll look how come some other time.

Comments:

2013-02-12 10:53:59 = John
{
Perfect! Thanks.

---

Just some tags to help people using MinGW and having this problem... to reach this page, I'll include a few "tags":
MinGW printf "%n" format specifier
%n
}
2014-01-18 09:06:26 = mikeb
{
Note that the workaround of patching the linker version field to 6.0 works only for 32-bit executables (at least on Win7).

Instead of a -malt-ISO-printf option to GCC, MinGW supports using -D__USE_MINGW_ANSI_STDIO for a similar result. With that macro defined, the printf() family of functions will use versions included in MinGW libraries instead of the ones in MSVCRT.DLL.
}

Add a comment:

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