Do not use the information on this article for illegal or malicious use.
I'm not responsible of your acts.
The situationSome months ago, I had to use a dynamic library which, in association with a hardware fingerprint reader, provides fingerprint reading. The case was that the library only works until January of 2004.
When I contacted the vendor, he told me that they do not provide support anymore for the library, and, subtly, suggest me to crack it.
In order to don't violate any legal right, I wont expose the real library nor the real code. Instead, I created a replica to explain this technique.
CrackingIn order to read from the fingerprint reader, I have to create a CardReader object, and call read_card() from it. This is a good point from where to start.
The library in question is: libcardreader.so. I started for disassembling it:
(piping to c++filt is for demangling the nomenclature)
After skipping some sections, we have:
Here je (jump if equal 74) is checking the condition after calling is_valid_date() (b42, two lines before). Here is where the library is validating the date. It works only if is_valid_date() is true.
What we need to do here, is the ABC of cracking, just to change je (74) for jne (jump if not equal, value 75), and after this, the library will work because it will check for not is_valid_date().
Putting the thing clear: In order to work, the original library, checks that the date is before January of 2004, now, after cracked, it checks that the date is after January of 2004. So that, the library now works.
Giving a look at the Intel x86 reference manual, we can corroborate the values for je = 74, and jne = 75.
The next is open the library with your favorite hex-editor, and change this 74 for 75
Calculating the offset (B49), and for the adjacent values, we know this is the 74 we need to change.
After changing the value, we disassemble again
Now we have 75 on the offset B49, and the wantedjne instruction. The library is cracked.
A cleaner approachOf course, even when the vendor proposed me to crack its library, I didn't feel comfortable with the idea of modifying the executable, furthermore, I had a recall in my mind where someone had cracked using linking rules and making the library call his methods instead of the library methods! Yes, he did something like: When read_card() method calls to is_valid_date(), the library calls to his is_valid_date() version, where, of course, he did whatever he wanted inside hes methods. This is a cleaner approach because he was not modifying the binary.
Having this in mind, and after I read about the internal composition of the ELF format, and symbol resolution rules, I started with the tries and, after few ones, I found the way :)
Symbol crackingWell, I don't really know how to call this technique. Maybe after explaining how it works, the name will make more sense.
The idea is to provoke a symbol collision. To collide the symbol, in this case the declaration of the function is_valid_date, with a symbol in the file of our main.
How can we do it? Easy, just declare a function with the same prototype in the main.cpp (actually where you have the main function declared) file.
We can know the is_valid_date() prototype by looking at the declaration on the library header, or we can try to guess, since we know that the function is checked out for its truth value within an if sentence, so that, this function prototype and definition is a good candidate
Since we are declaring a function with the same prototype, and in the same namespace (global), we are colliding the symbols, and, in opposite to we can expect, the library resolve to call our is_valid_date version.
Before creating the symbol collision
After creating the symbol collision
In this fashion, we can simply bypass the validation returning always true.
In case that the validation is method (a function class member), we have to generate the symbol including the class name ambit, as we were redefining it.
A more interesting case is when the validation method is private. If we try to redefine it, the compiler say that the method is private and we can not do that in that context.
To bypass this problem, we can redefine any other compatible public method of the class, where we call the private validation from there. We need to redefine this private validation as well. And of course, we lose the original public method functionality.
A compatible method is that one that allows us to check its return value in the same way we check for is_valid_date(). These could be: a boolean, or a integer.
Further detailsThis happens for a historical reason, and is related to the first shared library implementations were created so that the default semantic for symbol resolution exactly mirrored those application linked against static equivalents of the same libraries.
If a symbol is defined in multiple libraries, the resolution of that symbol is bound to the first definition found by scanning libraries in the left-to-right order in which they were listed on the static link command line.
Avoiding symbol crackingOf course, it is possible because the library vendor made an error, he exported all the symbols of his library (the default behavior with gcc).
We can check this out by running nm -DCg on the library
Here we can see the validation methods exposed, creating this security hole.
These methods must be hidden from exportation. We achieve this by adding the hidden attribute to each of these
__attribute__ ((visibility("hidden"))) allows as to control what symbols we export from our libraries.
A well designed shared library should make visible only those symbols that form part the its application binary interface (ABI), for the following reasons:
1) A user of the library could try to use a symbol that we don't want they use, and we can not change it on later library versions without breaking his code.
2) Exporting unnecessary symbols increase the size of the dynamic symbols table, and this table must be loaded at run time.
3) The security hole that this article is about.
The -Bsymbolic parameterWe can specify the linker parameter -Bsymbolic to indicate that the library preferentially should resolve the symbols (if they exist) within that library. This works for symbols duplicated on another libraries, but not for those defined on our main.
SummaryCracking could be achieve by modifying a binary, or by playing around with symbol resolution.
A definition of a symbol in the main program, overrides a definition in a library.
Export only the symbols you want the users of your library use. This brings not only performance advantage, but security improvement.