C++ public_cast2011-01-28
C++ supports access privilege levels inside of classes. In total, there are three access types: public, which gives access to all code anywhere; protected, which gives access to the owner class, any derived classes, and any friends; and private, which only gives access to the owner class and any friends.
Of course, in the end, this access is strictly a gentleman's agreement. It is quite easy to get at protected and private variables, objects and functions. Of course, most C++ programmers assume this would at least violate the C++ standard, resulting in technically undefined behavior that may not work on all platforms. These methods include tricks such as '#define private public' (you cannot #define a reserved keyword), an alternate header that marks protected and private sections as public (all objects must have an identical class declaration), casting objects to byte arrays and manually poking at their bits (do I really need to elaborate on how wrong this is?), etc.
However, as this article will show, that assertion is not actually true. It's quite possible to safely and legally access any protected or private variables, objects or functions.
I'd like to thank Johannes Schaub (litb) for pointing out the C++ rule that allows this exploit to be possible. I have taken his idea and refined it to its ultimate simplicity.
The attack vector
In the C++98 specification, section 14.7.2 paragraph 8 states that: "Access checking rules do not apply to names in explicit instantions. Template arguments and names in a declaration of an explicit instantiation may be private types or objects."
This is what we will use to gain access to any class we want.
The attack code
First, this is the base code that needs to exist in order for this exploit to work. It only needs to be written once, and should ideally reside in a header file. We will walk through the code next.
template<typename T, typename T::type... P>
struct public_cast;
template<typename T>
struct public_cast<T> { static typename T::type value; };
template<typename T>
typename T::type public_cast<T>::value;
template<typename T, typename T::type P>
struct public_cast<T, P> { static typename T::type value; }
template<typename T, typename T::type P>
typename T::type public_cast<T, P>::value = public_cast<T>::value = P;
The first statement
First off, we need to know the type of our pointer-to-member. Since we cannot reference protected or private items from template functions, we must explicitly declare the type in advance. This is not good for a library, so we must rely on the end-user making a class to define the type for us.
This also gives us a second advantage: we may want to access more than one protected item that has an identical signature. So the typename T serves as a unique reference-ID for one specific item.
Lastly, we take advantage of C++0x variadic templates so that we only have
one class name that we need to remember: public_cast. This code will work with
C++98 as well, provided that you use a different name for the derived
public_cast
The second statement
Now we need to create our actual base class that will contain the pointer. Since this is the class that needs to be referenced from actual code, and not from instantiations, we cannot have it reference our protected items, thus we must utilize type-erasure here. We only take the unique-ID typename T to declare a static pointer to our protected item.
The reason we make this static is so that there is only one instance, and so that we can initialize this variable from later code.
The third statement
Just as with any class, when you declare a static object, you have to declare that it exists later on. This code is the equivalent of "type class::value;" ... note how we are not actually assigning to value here. We could of course do that if we wanted, but we don't know the pointer value yet, so we will do so later on.
The fourth statement
Now we create a derived template that accepts our protected item reference. Note that this is not technically a derived class in the traditional sense: since the value declaration is static, it does not need to inherit from the base class.
We do however need to define another static typename T::type value, however. This is so that we have something to specify for the fifth statement to work.
The fifth statement
And here is where the magic occurs. We have to tell the compiler that our
static public_cast
Our target
Now let's look at a real-world example. Say you have the following code, and for whatever reason you are not able to rewrite it to expose our private items as public ... perhaps because another pre-compiled object you don't have the source code to used the private-based header, perhaps because it's a header in a language or API library that you can't expect other users to modify, whatever the reason:
class Foo {
int value;
void bar();
} foo;
Usage example
So now we can use our attack code to access our target. Now unfortunately, explicit instantiations must occur outside of code. And we also have to make our globally-unique ID as mentioned earlier, so for each reference we will need exactly two lines of code in the global scope.
struct Pvalue { typedef int (Foo::*type); };
template class public_cast<Pvalue, &Foo::value>;
struct Pbar { typedef void (Foo::*type); };
template class public_cast<Pbar, &Foo::bar>;
The first statement is used by our attack code for type-erasure. We declare the syntax for a pointer to the item we want to access, and create the typename T::type for it. This also serves as our globally-unique ID.
The second statement is our explicit instantiation that takes advantage of C++98 section 14.7.2 paragraph 8: any standards-complaint C++ compiler must allow this. By declaring this, our derived class assigns to its value, which subsequently assigns to the base class' value. And now we can use the base class inside of our code to access our private items:
void test() {
(foo.*public_cast::value) = 7;
(foo.*public_cast::value)();
}
You may need to brush up on template syntax or pointer-to-member syntax to really understand the above code, but hopefully my explanations have made it easier to see what was going on.
In closing
This article exists just to demonstrate a strange exception in the C++ specification, to show that it is completely possible to access anything in C++, no matter what access privilege is specified. Thus, code that needs to be secure should never rely on C++ access privileges for any guarantee.
It is also meant as a simple example on some interesting concepts such as variadic-template name shadowing, template type erasure, static initialization, etc. Feel free to apply tricks like these to other classes.
Of course, in the real world, you should never use exploit code like this. This is about as evil as you can get.