Jump to content






Photo - - - - -

Format String Character %n In Windows



Today I've been playing with a format string bug on a software for Windows when I came across a special "glitch" in Microsoft's C Runtime library (MSVCR) version 10.

Seeing how the use of %n in a format string could essentially allow you to write any number in an arbitrary memory location, I decided to try it out. My first try involved writing the number 0xBAAD to location 0x41414141 using the format string "AAAA%47785u%n" where 47785 = 0xBAA9 + 4 bytes for the int value = 0xBAAD. Note that the location of the format string buffer is inside the stack just 4 bytes after the single printf argument.

Stack:
0018FF30   0018FF38  |format = "AAAA%47785u%n"
0018FF34   00000000  |<%47785u> = 0
0018FF38   41414141

However, when feeding the desired format string the application just crashed with the following stack trace:

Call stack of main thread
Address    Stack      Procedure / arguments                 Called from                   Frame
0018FC04   74E3E830   ntdll.ZwTerminateProcess              KERNELBA.74E3E82A             0018FC10
0018FC14   665BA407   kernel32.TerminateProcess             MSVCR100.665BA401             0018FC10
0018FC18   FFFFFFFF     hProcess = FFFFFFFF
0018FC1C   C0000417     ExitCode = C0000417 (-1073740777.)
0018FC24   665BA435   MSVCR100._invoke_watson               MSVCR100.665BA430             0018FC3C
0018FC40   665BA442   ? MSVCR100._invalid_parameter         MSVCR100.665BA43D             0018FC3C
0018FC58   665AA8B4   MSVCR100._invalid_parameter_noinfo    MSVCR100.665AA8AF             0018FEE0
0018FEE4   6659317F   MSVCR100.665AA839                     MSVCR100.6659317A             0018FEE0
0018FF28   0040103C   ? MSVCR100.printf                     XXXXXXXXXXXX                  0018FF24
0018FF2C   0018FF34     format = "AAAA%47785u%n"
0018FF30   00000000     <%47785u> = 0

I decided to debug the code and see where the program crashes. Apparently when the format string function in MSVCR100 comes across %n the _get_printf_count_output function, which checks if %n is supported, is called:

665AAE5F  |.  E8 1D84FEFF   |CALL MSVCR100._get_printf_count_output
665AAE64  |.  85C0          |TEST EAX,EAX
665AAE66  |.^ 0F84 38FAFFFF |JE MSVCR100.665AA8A4

If %n is not supported then the JE branch is taken leading to a section of the code which calls errno and _invalid_parameter_noinfo:

665AA8A4  |> /E8 E568F9FF   CALL MSVCR100._errno
665AA8A9  |. |C700 16000000 MOV DWORD PTR DS:[EAX],16
665AA8AF  |. |E8 82FB0000   CALL MSVCR100._invalid_parameter_noinfo

Within _invalid_parameter_noinfo a close-by function named _invalid_parameter is called which checks if the user has registered an "invalid parameter handler" to jump to. If not then watson is invoked.

665BA409 >/$  8BFF          /MOV EDI,EDI
665BA40B  |.  55            |PUSH EBP
665BA40C  |.  8BEC          |MOV EBP,ESP
665BA40E  |.  FF35 E0345E66 |PUSH DWORD PTR DS:[665E34E0]
665BA414  |.  FF15 04105366 |CALL DWORD PTR DS:[<&KERNEL32.DecodePointer>]          ;  ntdll.RtlDecodePointer
665BA41A  |.  85C0          |TEST EAX,EAX
665BA41C  |.  74 03         |JE SHORT MSVCR100.665BA421
665BA41E  |.  5D            |POP EBP
665BA41F  |.  FFE0          |JMP EAX
665BA421  |>  FF75 18       |PUSH DWORD PTR SS:[EBP+18]
665BA424  |.  FF75 14       |PUSH DWORD PTR SS:[EBP+14]
665BA427  |.  FF75 10       |PUSH DWORD PTR SS:[EBP+10]
665BA42A  |.  FF75 0C       |PUSH DWORD PTR SS:[EBP+C]
665BA42D  |.  FF75 08       |PUSH DWORD PTR SS:[EBP+8]
665BA430  |.  E8 AFFFFFFF   |CALL MSVCR100._invoke_watson

Within the _invoke_watson function something interesting happens:

665BA3E4 >/$  8BFF          MOV EDI,EDI
665BA3E6  |.  56            PUSH ESI
665BA3E7  |.  6A 01         PUSH 1
665BA3E9  |.  BE 170400C0   MOV ESI,C0000417
665BA3EE  |.  56            PUSH ESI
665BA3EF  |.  6A 02         PUSH 2
665BA3F1  |.  E8 BEFEFFFF   CALL MSVCR100.665BA2B4
665BA3F6  |.  83C4 0C       ADD ESP,0C
665BA3F9  |.  56            PUSH ESI                                                ; /ExitCode
665BA3FA  |.  FF15 24115366 CALL DWORD PTR DS:[<&KERNEL32.GetCurrentProcess>]       ; |[GetCurrentProcess
665BA400  |.  50            PUSH EAX                                                ; |hProcess
665BA401  |.  FF15 94125366 CALL DWORD PTR DS:[<&KERNEL32.TerminateProcess>]        ; \TerminateProcess
665BA407  |.  5E            POP ESI
665BA408  \.  C3            RETN

The process is terminated...

In order to handle a simple call to printf (or any other format string functions) one would have to register an "invalid parameter handler" using the _set_invalid_parameter_handler function or enable support for %n by using the _set_printf_count_output function.
This "additional" overhead is a trade-off for one bug with another. You might agree that you are safe against format string attacks that modify the data within an arbitrary memory address, but unless you register an "invalid parameter handler" you would certainly be open to denial of service attacks.



nice work ;)

May 2013

M T W T F S S
  12345
6789101112
13141516171819
202122 23 242526
2728293031  

Recent Entries

Recent Comments