|
|
Subscribe / Log in / New account

Free software's not-so-eXZellent adventure

Free software's not-so-eXZellent adventure

Posted Apr 2, 2024 21:38 UTC (Tue) by dezgeg (subscriber, #92243)
In reply to: Free software's not-so-eXZellent adventure by ms
Parent article: Free software's not-so-eXZellent adventure

> lib x can overwrite code in z if z links with y and y links with x

As far as I understood, ifuncs itself are not the method to achieve this. It's just a way to gain execution whenever a library is loaded, just like .init (which is used for things like calling global constructons). The actual method to hook RSA_public_decrypt() is via audit hook (see man rtld-audit).

Now, it's still possible that using the ifunc mechanism made things somewhat simpler for the backdoor developer (perhaps ifunc resolvers are called in an earlier stage than .init functions, or something), but fact is .init still would have been a way to get liblzma code to execute in the sshd process (and .init can't be removed without breaking metric tons of stuff).


to post comments

Free software's not-so-eXZellent adventure

Posted Apr 2, 2024 22:15 UTC (Tue) by andresfreund (subscriber, #69562) [Link] (8 responses)

> Now, it's still possible that using the ifunc mechanism made things somewhat simpler for the backdoor developer (perhaps ifunc resolvers are called in an earlier stage than .init functions, or something), but fact is .init still would have been a way to get liblzma code to execute in the sshd process (and .init can't be removed without breaking metric tons of stuff).

It'd have been harder / noisier, I suspect. From what I can tell .init etc do get processed after symbol resolution and thus after the GOT has been remapped read-only. Of course you could still do nasty stuff, but it'll either be harder to have sufficient information about the client (and thus whether to execute a payload), or noisier (remapping the GOT RW).

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 1:48 UTC (Wed) by dezgeg (subscriber, #92243) [Link]

Yea, then possibly it would make sense to pick some other target than the dynamic linker for such hooking. From a quick look, RSA_public_decrypt() behind the scenes is a function pointer call, so swapping out a pointer to the ops structure in struct RSA would suffice. CRYPTO_set_mem_functions() could be used to set a custom malloc implementation to help locating potential struct RSA candidates.

Another idea that comes to mind is fopencookie() could be used to hook stdout/stderr and use that to deliver the payload (assuming sshd includes enough info there about failed connection attempts)... there's just so many different opportunities. Also detection chances could probably be reduced by simply disabling the backdoor for say, 90% of the connections (pthread_atfork() perhaps) while still remaining useful.

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 13:59 UTC (Wed) by jejb (subscriber, #6654) [Link] (6 responses)

I think everyone's also missing the point that this ifunc or .init method also allows highly indirect dependencies to gain control of early stage execution even if the execution would never have got into that library. This feature is what makes indirect dependencies so dangerous. For ifunc, the solution looks like it should be that if you have lazy symbol resolution (the default), we shouldn't call the ifunc to perform the choice test until we actually need to resolve the symbol (so if we never need the symbol it's never called). I think it would also be highly desirable not to call library .init functions until the first symbol in the library is hit (this one is more problematic since they generally expect to be run before the actual main() routine is called).

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 14:35 UTC (Wed) by farnz (subscriber, #17727) [Link] (5 responses)

The problem with "shouldn't call the ifunc to perform the choice test until we actually need to resolve the symbol" is that an attacker can just redirect a symbol that matters to you; for example, if I supply an IFUNC for memcpy, then my IFUNC is called as soon as your program calls memcpy.

This is why I believe that there's two ELF design decisions that interact to surprise you - the other one is that the symbol import doesn't tell the linker which library it expects the symbol to come from, so any symbol can be indirected by any dependency.

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 14:51 UTC (Wed) by jejb (subscriber, #6654) [Link] (4 responses)

> The problem with "shouldn't call the ifunc to perform the choice test until we actually need to resolve the symbol" is that an attacker can just redirect a symbol that matters to you; for example, if I supply an IFUNC for memcpy, then my IFUNC is called as soon as your program calls memcpy.

The way ifuncs currently work for dynamic libraries is that you can only do one for your own symbols, so liblzma can't use an ifunc to intercept memcpy, only glibc can do that.

So to look at this concrete example: liblzma couldn't use an ifunc to redirect RSA_public_decrypt (the object of its attack), it had to use an ifunc on one of its own functions to install an audit hook to redirect the symbol resolution for RSA_public_decrypt that allowed it to hijack the symbol in another library.

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 15:21 UTC (Wed) by ms (subscriber, #41272) [Link]

> The way ifuncs currently work for dynamic libraries is that you can only do one for your own symbols, so liblzma can't use an ifunc to intercept memcpy, only glibc can do that.

That seems a really good example: I have no doubt it would be technically possible for the dynamic linker / elf loader to allow ifuncs for symbols that are defined elsewhere - sure you might have to do it lazily because maybe that symbol hasn't been defined yet, but I'm sure it could be done. But instead, it has been _chosen_ that you can only put ifuncs it on your own functions. So it doesn't seem that much of a leap to also choose "you can only install audit hooks on your own symbols", or, more generally, "pre main, you can't do anything to symbols you've not defined yourself".

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 15:22 UTC (Wed) by farnz (subscriber, #17727) [Link] (2 responses)

liblzma can, however, supply a memcpy symbol, and with a bit of careful squirrelling, be set up so that its IFUNC causes it to redirect to the normal memcpy. This sort of symbol redirection game is designed into ELF (although it's not necessary for a dynamic linker to do this - Apple's Mach-O format has symbol tell you which library to import from).

So liblzma can use an IFUNC to redirect RSA_public_decrypt; it's just that in doing so, it makes RSA_public_decrypt visible in the exports list of liblzma, which would make the attack less stealthy.

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 16:15 UTC (Wed) by jejb (subscriber, #6654) [Link] (1 responses)

> liblzma can, however, supply a memcpy symbol, and with a bit of careful squirrelling, be set up so that its IFUNC causes it to redirect to the normal memcpy.

No, it can't. If you get two identical global scope non weak symbols the dynamic linker will throw a duplicate symbol error. If you try to do a local scope instead (which actually indirect dependencies get by default), it won't get resolved until used in that scope (so the resolution in sshd would always pick up the global scope symbol in glibc). You could possibly get around this using versioning, but, assuming ifunc on resolution is implemented, your ifunc wouldn't get called until something requests that specific version of memcpy ... which wouldn't happen for the binary in question.

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 16:17 UTC (Wed) by farnz (subscriber, #17727) [Link]

It can - it supplies a weak symbol, and at least in my experiments, that's enough to trigger IFUNC resolution from my attacking binary. Maybe this is a bug, but it's still a problem.

Free software's not-so-eXZellent adventure

Posted Apr 2, 2024 22:28 UTC (Tue) by fenncruz (subscriber, #81417) [Link]

So how would we block audit hook from doing this? Can a code opt out of the audit hook at compile or runtime?

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 12:03 UTC (Wed) by smurf (subscriber, #17840) [Link] (3 responses)

> perhaps ifunc resolvers are called in an earlier stage than .init functions, or something

They might well be, but that's irrelevant since once you have an adversarial foothold in some library or other, running as root, you can do pretty much anything anyway, ifunc or .init or hijacked symbol.

More to the point: using an ifunc in order to choose the best implementation is fairly standard for crypto or compression code, thus doesn't jump at you when you look at the library's metadata. On the other hand, .init is used to build static objects and similar stuff which a reasonably-coded multi-thread-capable library should have no business requiring. The fact that it's easier to get gdb to trace code in .init sections than in ifuncs might be relevant too.

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 12:44 UTC (Wed) by ms (subscriber, #41272) [Link] (2 responses)

> They might well be, but that's irrelevant since once you have an adversarial foothold in some library or other, running as root, you can do pretty much anything anyway, ifunc or .init or hijacked symbol.

(ignoring the "running as root" clause)

I think this is maybe getting to the nub of it: if you link with a library, are you implicitly trusting it, to the extent that you're willing for it to write all over "your" memory, including code?

It's possible that for some of us who are used to thinking in terms of microservices, containers, etc etc, it doesn't seem hard to imagine a world where the answer is "no, I'm not trusting it that far - it can have its own memory, just like it has its own namespace, and its own scopes, but it doesn't get to interfere with mine". To me, it seems pretty crazy that all these languages typically enforce lexical scoping rules, but apparently glibc, with a combination of ifuncs and audit hooks, allows complete violation of the compartmentalisation that lexical scoping creates.

For some of us who are (I'm guessing) more experienced C/C++/kernel devs, there's both tradition, and good reason as to why believing/hoping/pretending the trust isn't absolute, is misguided.

Free software's not-so-eXZellent adventure

Posted Apr 3, 2024 15:32 UTC (Wed) by farnz (subscriber, #17727) [Link] (1 responses)

You don't even need to be thinking in terms of microservices etc; Apple's dynamic linker requires imported symbols to be annotated with the library that's expected to supply the symbol, and thus most of the time, when you link against a library, you're only bringing it in for its symbols. I don't know if it has an equivalent of IFUNC, nor when static constructors are run (when a library is loaded, or when you first access a symbol from the library - both would be fine within the letter of C++).

Free software's not-so-eXZellent adventure

Posted Apr 7, 2024 17:31 UTC (Sun) by mathstuf (subscriber, #69389) [Link]

> when static constructors are run (when a library is loaded, or when you first access a symbol from the library - both would be fine within the letter of C++).

They are run when a symbol from the *object* containing the static object resides is accessed.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds