Fun with Gentoo: Why don't we just shuffle those ROP gadgets away?(quitesimple.org) |
Fun with Gentoo: Why don't we just shuffle those ROP gadgets away?(quitesimple.org) |
I recall gcc3 -> 4. The prevailing "wisdom" was emerge --deep (etc) world ... twice! My laptop was left for around a week trundling through 1500 odd packages. I think I did system first, twice too. I left it running on a glass table in an unheated study, propped up to allow some better airflow.
One of the great things about Gentoo is that a completely fragged system doesn't faze you anymore. Screwed glibc? Never mind. Broken python? lol! Scrambled portage? Hold my beer.
I have a VM running in the attic that got a bit behind. OK it was around eight? years out of date. I ended up putting in a new portage tree under git and reverting it into the past and then winding it forwards after getting the thing up to date at that point in time. It took quite a while. I could have started again but it was fun to do as an exercise.
I still haven’t decided whether or not I should be embarrassed that I mainly bought a 16-core CPU to run Gentoo.
The possible combinations that Gentoo allows looks to me like a sort of Linux immune system in action. Quite a few "unpopular" flags will get used (lol USEd) somewhere by someone that will be more motivated on average to log a bug somewhere.
Gentoo also got the console shell look (colours, fonts etc) right way before any other distro. It's copied widely.
[0] https://www.dropbox.com/s/w1zlftin1cojkhr/kernel_compile.mov...
I think that you could do this quite well on NixOS, and I'm now intrigued to try to rig up a proof-of-concept when I can find the time.
Side-effect: Does not work for libraries without a significantly more complex wrapper that certainly could not work for all libraries. Though, you could re-order the objects within a static library fairly easily.
Truthfully though you're right, using typical linkers, this would be pretty slow; at least a few seconds for large binaries, to minutes for things as large as web browsers. However, for many binaries, linking can be done much faster; mold claims to be only 50% the runtime of using `cp` on the object files, which is fast enough to even re-link Firefox on-the-fly without it being unusable.
You could imagine writing a linker specifically for this case, that encodes information about the object files directly into the resulting bundle.
I’m not saying it’s perfect but it seems like a reasonable defense for binary distribution. As someone who used to run Gentoo, I’d say most people are in favor of the faster times to install a new package.
EDIT: extending this idea further, I wonder if compilers can’t offer a random seed to supply that causes a random layout of the sections within a built execution so that even statically linked binaries benefit from this.
For example.
I wonder if there's a way to do just-in-time random relinking such that the performance cost is low, but the security benefit is still strong.
Just-in-time gets you reproducible builds, and also addresses the "local attackers who can read the binary or library" problem.
There would be a performance cost in terms of startup time, but since the number of possible permutations is a factorial function of the number of possible linking orders, it seems like even a very coarse-grained random relinking can go a long way.
You could accomplish this by doing static analysis of a binary to generate a file full of hints for ways to rewrite the binary such that its behavior is provably equivalent to the original. Then there could be a wrapper (perhaps at the shell or OS level) which uses the hints to randomly relink on the fly just prior to execution.
Another advantage is that this approach should be feasible on an OS like Ubuntu where everything is precompiled.
However the static analysis part could be a little tricky? I'm not familiar with the state of the art in static analysis of compiled binaries.
Performance-sensitive users could be given a way to turn the feature off, in cases where fast startup time was more important than security.
The biggest benefit seems to be in making it infeasible/dangerous for a malicious actor to distribute binary versions containing different behavior from the published source.
On a local machine, when and with what would you compare your binaries?
Reproducible builds verify the source code and build process (including options) were the same. Not sure how important each aspect is.
Also, if for some reason you rebuild a dependency, you'll need to relink everything that depends on that. This could get messy, but it's still interesting.
Other than this issue (which may well be a large / unsolvable one), I wonder what other disadvantages to this approach there might be. Does this hack have any potential for a Gentoo profile or mainlining?
Is dynamic linking in Unix world truly runtime-only (a-la "GetLibrary" / "GetProcAddress")?
But I also don't think that this would be a problem on Windows. After all, you can generally replace DLLs with entirely different versions and you'll be fine as long as all the required symbols are present and ABI-compatible.
The main difference between ELF and PE dynamic linking is that with PE you have a list of required symbols along with the libraries to load those symbols from while with ELF you have a list of required libraries and a list of required symbols but not information recorded about which symbols should come from which libraries.
There are all kinds of things we're doing (e.g. rewriting things in memory-safe languages) to make it less likely for an attacker to become able to control a jump to somewhere, however, we don't expect to fully succeed any time soon, and this is defense in depth against cases when attackers once again do find a way to control transfer to some arbitrary gadget.
> Return-oriented programming (ROP) is a computer security exploit technique that allows an attacker to execute code in the presence of security defenses[1][2] such as executable space protection and code signing.[3]
> In this technique, an attacker gains control of the call stack to hijack program control flow and then executes carefully chosen machine instruction sequences that are already present in the machine's memory, called "gadgets".[4][nb 1] Each gadget typically ends in a return instruction and is located in a subroutine within the existing program and/or shared library code.[nb 1] Chained together, these gadgets allow an attacker to perform arbitrary operations on a machine employing defenses that thwart simpler attacks.
Finally, the main problem with this idea is that you can't audit malware because there's no way to maintain a source of truth about what the binary on a given system should be. Distributing randomly linked copies solves that because you can have a deterministic mapping based on machine characteristics (you do have to keep this hash secure but it's feasible). You'd basically be maintaining N copies of your distro with randomly built binaries with the user being given a random one to install.
And to be clear, my better idea is to do this at the compiler level so that you randomize the relative location of functions. That way it's impossible to find any gadget to grab onto and you have to get information leakage from the machine you're attacking & this information leakage has to be repeated for each machine you want to compromise.
Wanting to have control over config via use flags for your system doesn't mean that there aren't packages were you don't really need that. Like if you only use Libre Office a couple times per year on your aging laptop, do you really care enough about the exact USE config to justify compiling it yourself? Even more so if you need it on short notice. Or if you only use Chromium/whatever to check that your website works with that browser but don't actually use it yourself, why bother compiling it.
IIRC there used to be a Gentoo fork (forgot the name) that extended this concept to all packages, so if you used default USE flags you did not need to compile things yourself.
My current Gentoo system seems to have existed since 03/29/21, so roughly two years now. In the time period, the time spent compiling packages has accumulated to 5 days, and my CPU takes ~140W at max load (Ryzen 3900x).
If I did my math correctly, this comes out to roughly 16KWH accumulated energy across two years.
We can compare this to a gamer, who spends 1 hour per day gaming, for 2 years, on a system that takes 300w while running a game, and this comes out to 230KWH in total. That about 15x as much energy spent by a fairly lightweight gamer on a very average system.
It's also worth noting that the majority of packages build in under 1 minute on my system, the vast majority of compile time is spent on things like Firefox, Rust, GCC and a few more.
This is just a very silly thing to be concerned over, and if we are going to be offended at people for being wasteful there are much larger targets than someone building packages from source.
you can link in the background at idle priority, and if you don't complete before reboot: no big deal
On another note: automating this on gentoo is cool exercise, but almost certainly if you just build everything locally, the memory layout will be random enough that writing shellcode blindly presents an interesting challenge. (different compiler flags, various probabilistic optimization passes… all that leads to the functions in same object file having different sizes)
First, it does. At scale, the probability of everyone running the exact version of every piece of software is 0. If you want, go take a look and see how many users are running a given version of Android.
Also, did you miss when I wrote
> Taking this a step further, create link N randomly sorted copies per version and randomly distribute those
I agree, doing it per version is only just a small amount of coverage. We're in agreement that generating N randomized copies and distributing those evenly is a stronger position because it makes the cost MN where you have M releases that are still running and N variants per release.
https://www.gnu.org/software/mes/
The idea here, is that if you can get a very basic C compiler, you can start building TinyCC, and eventually build a pre-C++ version of GCC, and from there build up to modern GCC. This is a lot easier said than done of course, but not quite as bad as needing the original compilers from the 70s!
https://www.schneier.com/blog/archives/2006/01/countering_tr...
And specifically, only one person needs to do this once... I'm surprised there isn't some project doing this...
it's a dynamic library, and this isn't windoze with awful mandatory locking
as long as the underlying version is unchanged: there should be no problem whatsoever
then after boot you relink for next boot