The present invention relates to a method and a device of protecting a first software application to generate a protected software application.
A state-of-the-art approach to protect a program in-memory against malicious modification (e.g., removing of a license check) is known as dynamic self-checksumming. The idea is that a protected program inspects at runtime its own instruction stream and computes some sort of checksum or hash from it. Additionally, the protected program also contains some logic that ensures that the program only continuous to function correctly when the computed checksum matches its expected value. Code that does both, computing the checksum of the specific memory region and triggering some action if the computed checksum does not match its expected value can be called “checker” or “check module”.
However, if an attacker simply disarms a specific checker by locally patching it (e.g., updating its expected checksum or disabling its introspection code) the desired protection can be overcome.
Therefore, there is a need to provide a method for generating a protected software application which provides a better protection against in-memory modifications.
The invention is defined by the independent claims 1, 14 and 15. Further developments over the invention are given in the dependent claims.
By providing the amended first code region the protected software application does regularly read its instruction stream as part of its normal operation. This obstructs the detection of a check module using dynamic taint analysis because the fact that some code reads the instruction stream is not sufficient anymore to classify the code as being a check module.
The device for protecting a first software application can comprise the same feature as the inventive method for protecting a first software application (including all further embodiments of the inventive method).
It goes without saying that the features mentioned above and so yet to be explained below are usable not only in the combinations specified but also in other combinations or on their own, without departing from the scope of the present invention.
In the following text, the invention is explained in more detail on the basis of exemplary embodiments with the reference to the appended drawings, which likewise disclose features which are essential to the invention. These exemplary embodiments serve merely for illustration and should not be interpreted as limiting. For example, the description of an embodiment having a large number of elements or components should not be interpreted as meaning that all of these elements or components are necessary for implementation. Rather, other embodiments can also contain alternative elements and components, further elements or components, or additional elements or components. Elements or components of different embodiments can be combined with one another, unless specified otherwise. Modifications and alterations that are described for one of the embodiments can also be applicable to other embodiments. In order to avoid repetitions, identical or mutually corresponding elements in different figures are provided with the same reference signs and not explained several times.
In the following will be described a first embodiment of the computer-implemented method according to the invention for protecting a first software application to generate a protected software application.
The first software application to be protected is supplied to a first computer 2 which comprises a first computing module 3 (including, e.g. a processor, a hard disk, further hardware elements as well as an operating system), a first input unit 4 (in this case, a keyboard, for example) as well as a first output unit 5 (e.g. a screen).
The first computer 2 adds a protection module 6, and three check modules 71, 72, 73 to the first software application 1. In addition, the first computer 2 selects a first code region of the first software application 1 (the first code region provides a first functionality when being executed) and amends that first code region such that an amended first code region 91 is generated. In the same way a second, third and fourth code regions of the first software application are selected and amended such that an amended second, third and fourth code regions 92, 93 and 94 are generated, respectively.
In this way, the protected software application 10 is generated.
The protected software application 10 can be executed on an execution platform 11 only if a predetermined license is present. In such an execution platform 11 shown in
When the protected software application 10 is to be executed on the execution platform 11, the protection unit 12 carries out a license verification. The protection unit 12 allows execution of the protected software application 1 only in the presence of a license.
During execution of the protected software application 10 on the execution platform 11 there is provided protection against detecting the check modules 71-73 using dynamic taint analysis.
Code that does both, computing the checksum of a specific memory region and triggering some action if the computed checksum does not match its expected value can be called check module or checker.
As can be seen in
Further, the second check module 72 includes code that calculates a checksum from program offset a5 to program offset a10 and compares the result to the expected value, which is part of the second check module 72 and which at runtime is a constant. At runtime tampering is detected when the calculated value does not match the expected value (cf. arrow A72 and bracket B72).
The third check module 73 includes code that calculates a checksum from program offset 0 to program offset a3 (cf. arrow A73 and bracket B73). At runtime tampering is detected when the calculated value does not match the expected value, which is part of the third check module 73.
As can be seen, the check modules 71-73 are placed in an overlapping way, so that an attacker cannot simply disarm a specific checker 71-73 by locally patching it (e.g. updating its expected checksum or disabling its introspection code) but instead is forced to disarm all check modules 71-73 at once because the attacked check module is also protected by some other check module which is itself protected by another check module and so forth. This overlapping placement where check modules 71-73 protect other check modules 71-73 is referred as checker-graph.
However, the dynamic taint analysis is a dynamic analysis technique that tracks the information-flow through a program's execution and can be used to detect checkers 71-73 by their behavior. The technique exploits the fact that checkers 71-73 behave differently than regular application code as they inspect at runtime the program's instruction stream from working memory 14 and based on the observed instructions influence the program's behavior. This is something regular applications do not do because they have no reason to do so and regular applications are usually written in some high-level programing language, which means their actual in-memory instruction stream is unknown at the source level because the instruction stream is only created later by the compiler when it translates the high-level program into machine code.
According to the invention, the amended code regions 91-94 are generated in the protected software application 10 which, when being executed, carry out access to at least a part of the code of a protected software application 10 stored in the working memory 14 (as indicated by arrows A91, A92, A93 and A94) for providing the first, second, third and fourth functionality of the respective first to fourth code region of the first software application 1. Therefore, each of the amended code regions 91-94 (e.g. the amended second code region 92), when being executed, still provides the functionality of the selected code region (e.g.
the second code region) but carries out access to code of the protected software application 10 stored in the working memory 14 for doing this. The accessed code can be unamended code (arrows A92, A93 and A94) compared to the first software application 10 or amended or added code (arrow A91) compared with the first software application 1. Therefore, the regular application code (i.e. code which is not part of a check module 71-73 or of the protection module 6) does heavily inspect its own instruction stream as part of its program logic. This means that even without any artificially added check module 71-73 or protection module 6, the compiled application does regularly read its instruction stream as part of its normal operation. Since the protected software application 10 includes the check modules 71-73 and the amended code regions 91-94 not only the artificially added check modules 71-73 inspect the instruction stream but also the rest of the application. This obstructs check module detection using a dynamic taint analysis because the fact that some code reads the instruction stream is not sufficient anymore to classify the code as being a check module.
Of course, it is possible not to add the protection module 6 to the first software application 1 to generate the protected software application 10. Even in this embodiment the protected software application 10 obstructs the check modules 71-79 from being detected by a dynamic taint analysis.
Two components can be provided to do the rewriting of the application code of the first software application 1 to generate the amended code regions 91-94: The first component extends the code generation pipeline of the compiler (e.g. LLVM, gcc, etc.). The second component is a tool that applies patches to the binary image (exe/dll) produced by the linker. In the following description the name “compiler-plugin” refers to the first component, the name “post-linker-tool” refers to the second component. The first component can carry out a transformation phase and the second component can carry out a separate reference replace phase.
The job of the compiler-plugin is to replace all uses of constants in the application code (e.g., a C program) of the first software application 1 with a sequence of reads from extern global variables. Thereby a larger constant, e.g. a 32 bit integer should be split into a sequence of smaller reads that create the final value in some variable (can be both, local or global). Also the names of the referenced extern globals encode their desired value.
Here the C-Code is used to illustrate the idea, the actual compiler-plugin would not rewrite actual C-Code but (possibly optimized) intermediate code of the compiler (e.g., LLVM-IR).
Note that because we use extern global variables and e.g., not simply dummy values we get the guarantee that the compiler really generates four 8 bit reads in the object file as the compiler's optimizer only sees 4 references to 4 distinct 8 bit-wide undefined symbols. E.g., the optimizer cannot know that g_uint8_be_1 and g_uint8_be_2 are really constants and that they will have an identical runtime value of 0xBE. Also, because the compiler only sees undefined symbols for the global variables he has to create relocations for the four load instructions. These relocations then enable the post-link tool to patch the linked binary image (exe/dll) so that the load instructions will load their target-values from the program code. The point of splitting larger constants into a sequence of smaller reads increases the odds that the post-linker-tool finds the required values somewhere inside the program code. In order to ensure that the program code of the linked binary image (exe/dll) will contain all necessary constants the compiler-plugin can additionally insert the values via inline assembler such as in the following code:
This code can be placed in a C-function and inserts the bytes 0xDE, 0xAD, 0xBE, 0xEF inside the machine code of that function while not interfering with the regular execution as the unconditional jmp instruction jumps over the inserted data. Thereby the compiler-plugin could follow different strategies, e.g., make sure all 255 bytes values are somewhere present in the binary image or just add the values that are actually needed by the rewritten code so that the post-linker-tool will definitely find at least one occurrence.
Another common source of constants in application code is the sizeof operator, e.g. code like:
custom Type* dyn_array=malloc(sizeof(customType) * elemCount);
In this code sizeof(customType) returns the size in bytes of customType which is just a simple compile time constant whose value is directly available at the compiler's intermediate representation. For the sake of this example we assume sizeof(customType) returns 24 (aka 0×18).
Another very common source of constants are string literals such as: puts(“Hello”);
This would be rewritten into something like:
uint8_t my_const6[6]; //sizeof(“Hello”) is 6 because of the ‘0’.
my_const6[0]=g_uint8_48_1; //0×48 is ‘H’
my_const6[1]=g_uint8_65_1; //0×65 is ‘e’
my_const6[2]=g_uint8_6c_1; //0×6C is ‘l’
my_const6[3]=g_uint8_6c_2; //0×6C is ‘l’
my_const6[4]=g_uint8_6f_2; //0×6f is ‘o’
my_const6[5]=g_uint8_00_2; //0×6f is ‘\0’
puts(my_const6);
A different source of constants are constants not defined by the programmer but by the compiler, e.g., an address calculation to access a specific field of a struct. For performance reasons usual compilers try to encode such constants directly as an immediate of the machine instruction. The compiler-plugin can also extend the compiler backend and modify the instruction selection to use explicit register loads instead of immediates.
Example: The backend requests an instruction that loads a given CPU register with a 32 bit constant, e.g., on×86_64: Load register eax with value 0xBEEFBEEF. A decent×86_64 instruction selector would emit
mov eax, 0xbeefbeef
The instruction selector provided by the compiler-plugin would emit instead something like this:
This code performs an explicit, program-counter (register rip) relative load of the constant Oxbeefbeef (labeled ‘my_constant’) which is placed inside the program code. Similar to the previously described code rewrites the compiler-plugin could also split the rip-relative load of the constant into a sequence of smaller (e.g. byte) loads from different addresses and emit the necessary relocations, so that the post-link-tool can find the necessary somewhere in the program code and then adjust the offsets of the rip-relative loads accordingly.
The job of the post-link-tool is to resolve the symbols originating from the rewriting (the g_<type>_<target_value>_<id>symbols) against existing data values already present in the linked binary image of the exe/dll. So when building the application the regular linker first creates as usual the binary image (exe/dll) by linking the object files together but ignores errors from undefined symbols that originate from the rewriting. In a second step the post-link-tool resolves these undefined symbols against values from the binary image. E.g. to resolve the symbol g_uint8_48_1 the post-link-tool scans the binary image for an occurrence of the byte 0×48, determines the load-address of this value and resolves the associated relocation with the determined load-address. In case of a non-ASLR executables the relocation can be removed, for dlls and ASLR-enabled executables the existing relocation will be replaced with an associated base-relocation (“add runtime base address to relocation-target”). Note that the post-link-tool is not necessarily a separate executable but might be also integrated into the linker. The important aspect is that it runs at the end of the link when the binary image itself is already constructed.
Number | Name | Date | Kind |
---|---|---|---|
7716459 | Elias | May 2010 | B2 |
8056138 | Jin | Nov 2011 | B2 |
8195953 | Yue | Jun 2012 | B1 |
8302188 | Sato | Oct 2012 | B2 |
8843761 | Meyer | Sep 2014 | B2 |
8874928 | Betouin | Oct 2014 | B2 |
9177153 | Perrig | Nov 2015 | B1 |
9454659 | Daymont | Sep 2016 | B1 |
9471758 | Salmon-Legagneur | Oct 2016 | B2 |
9607147 | Chen | Mar 2017 | B2 |
9824214 | Daymont | Nov 2017 | B2 |
10152600 | Rozas | Dec 2018 | B2 |
10176324 | Davidson | Jan 2019 | B2 |
10228929 | El-Moussa | Mar 2019 | B2 |
10402547 | Joseph Johnson | Sep 2019 | B2 |
10411896 | Bouterse | Sep 2019 | B2 |
10489564 | Aschauer | Nov 2019 | B2 |
10613993 | Fel | Apr 2020 | B2 |
10803151 | Stewart | Oct 2020 | B2 |
11086997 | Stephenson | Aug 2021 | B1 |
11250110 | Garreau | Feb 2022 | B2 |
11256786 | Garreau | Feb 2022 | B2 |
11269988 | Bogaert | Mar 2022 | B2 |
11307850 | Murray | Apr 2022 | B2 |
11392700 | Beard | Jul 2022 | B1 |
20060195703 | Jakubowski | Aug 2006 | A1 |
20100180346 | Nicolson | Jul 2010 | A1 |
20130232323 | Lerouge | Sep 2013 | A1 |
20130232507 | Farrugia | Sep 2013 | A1 |
20140020112 | Goodes | Jan 2014 | A1 |
20160094564 | Mohandas | Mar 2016 | A1 |
20160378987 | Ferrara | Dec 2016 | A1 |
20170337047 | Shrivastava | Nov 2017 | A1 |
20180268130 | Ghosh | Sep 2018 | A1 |
20190205528 | Bogaert | Jul 2019 | A1 |
20200057856 | Daymont | Feb 2020 | A1 |
20200065480 | Gu | Feb 2020 | A1 |
20200372129 | Gupta | Nov 2020 | A1 |
20210303661 | Peng | Sep 2021 | A1 |
Entry |
---|
Jacob Kreindl et al. “Towards Efficient, Multi-Language Dynamic Taint Analysis”; MPLR '19, Oct. 21-22, 2019, Athens, Greece. |
Jonathon T. Giffin et al. “Strengthening Software Self-Checksumming via Self-Modifying Code”; Computer Sciences Department University of Wisconsin—Proceedings of the 21st Annual Computer Security Applications Conference (ACSAC 2005). |
Number | Date | Country | |
---|---|---|---|
20220245052 A1 | Aug 2022 | US |