Overriding C++ virtual functions at run time(blog.visionappster.com) |
Overriding C++ virtual functions at run time(blog.visionappster.com) |
wishful thinking: https://gcc.godbolt.org/z/qWEe9r
Obviously if you enable optimizations and one of those optimizations is avoiding the virtual call when the compiler thinks it isn't necessary, then sure you wont get a virtual call everywhere.
But if your code is relying on implementation assumptions like having a vtable at the start of a class, then it should also make sure that this assumption holds by not trying to work around it (e.g. via final) and using compiler options that control that optimization (e.g. GCC has -fno-devirtualize).
It doesn't make much sense to both try and take advantage of implementation details and work against taking advantage of implementation details at the same time.
Which doesn't make the technique completely useless, but raising this "obvious" important caveat - that it's likely to be an imperfect patch on it's own - when the article completely fails to do so, is worthwhile. I promise you there's C++ programmers out there who weren't aware of how aggressive optimizers can be.
I would not say that it is correct - the "object"'s actual existence is only through the interpretation that is made of it by the functions that work on your bytes. There's no struct definition in compiled code, only functions that do something with memory at a given offset. I won't go as far as to say that "objects" don't exist once your code leaves C++'s abstract machine to enter the compiled code world... but quite close.
With that said, 4 out of these 5 functions completely (and rightfully) disregard the vtable so if you're relying on that behaviour to be in place consistently to, say, fix a security issue in a given binary... you're in for some surprises.
It's hardly news but I guess it makes this common cracking technique more accessible.
And of course, Common Lisp has “change-class” (https://www.snellman.net/blog/archive/2015-07-27-use-cases-f..., discussed at https://news.ycombinator.com/item?id=734025) and Smalltalk has “become:” (https://gbracha.blogspot.com/2009/07/miracle-of-become.html. Short discussion at https://news.ycombinator.com/item?id=734025)
I don’t think the articles vtable layout is entirely accurate for gcc though - usually you’ll get 2 destructors at the start of the vtable (assuming the first virtual func declared is the destructor).
ASM is just a bytecode reader/writer/visitor library that can then modify it. You could also just do it statically by having the bytecode you want injected ready to go in an array or resource.
if (className.equals("a/b/c/d$e"))
return fixedClassBytecode;
else
return bytecode;
It's possible to create custom loaders (e.g., loading a class from an encrypted zip-file, or one that creates custom bytecode on the fly) and things which are trying to obfuscate what they're doing will have custom loaders, but this is a choke point that every class that's read in and instantiated must pass through.I believe classes, once loaded by the JVM, cannot be changed. https://stackoverflow.com/a/43653466/
This is something that you should have in mind when hotpatching in general, but that doesn't make hotpatching any less useful.
In one case, the developer writes `System.out.println`. In the other, the developer must individually get the static field System.err and push it to the stack, reference PrintStream's method println, including the arguments (Ljava.lang.String;)V. That means it takes an array of strings and returns void.
And yes, the compiler made 4 out of these 5 functions to disregard the vtable, but this is again something the compiler did that you have control over - if you are trying to take advantage of such implementation specific assumptions, you wont realistically use optimizations that break these assumptions nor write code that do not follow them.
The only place where this can break is if a library uses its own functions, those functions are inlined in some places and you want to hotpatch them. But that is an issue with hotpatching 3rd party code you have no control over in general regardless of language (it can happen in C too, for example), not just with vtable hotpatching.
I've rarely if ever heard of patching vtables in cases where you have access to the code - it's always been about fixing a binary you cannot recompile with some LD_PRELOAD trick or similar
Suppose you fix all that - the game still has a good chance of including 1 or more scripting languages. They may be included complete in their original unobufscated glory. Or the bytecode might include unstripped debug information. Or the C++ <-> script binding layer might still include unobfuscated string identifiers.
If you're clever, you'll rebrand this lack of obfuscation as being "mod friendly" ;)
The fact that inside a single CU an optimizing compiler can determine the targets of polymorphic dispatch statically is unrelated, because most usage scenarios (more than one CU, external modules) preclude such optimizations anyway.
vtables aren't undefined, they are implementation defined and part of the C++ ABI. As soon as you expose something through the C++ ABI the compiler has to use the ABI's definitions.
That said, so much software on Windows relies on vtable layout that breaking their particular implementation in the ABI would be a massive breaking change, so it's unlikely that it will happen.
Accessing them through reinterpret_cast is however undefined. Don't expect that compilers won't screw you over this.