Changing the runtime gid of the rtld linker

I am looking for a way to change the gid of the dynamic linker while it is reading shared objects from the filesystem. For context, my goal is to create an environment where an unprivileged user can not read/analyze ELF files but can still execute them. This can be easily implemented for ELF executables (ie: chmod 0511), but not for ELF shared objects as they currently need read permissions to be linked. The solution I am investigating is to have the dynamic linker switch to a special group that has read permissions on all shared objects during the linking process. I tested utilizing the setgid bit on ld-elf.so.1, but this did not change the group of the linker at runtime. I have started looking for how the kernel handles the ELF PT_INTERP header field, but I have yet to find where the linker is actually executed. Could someone more familiar with the codebase point me to the right location?
 
Is it possible for a page of memory to be marked executable but not readable, in the context of the running process?

Once the loader has loaded the shared library, the running program can clearly execute it (or more accurately, the pages that contain the shared library). If the running program can also read those pages, then you have only closed the bathroom window on the barn, but left the doors open: the attacker can still put his reading code into the executable itself.
 
Is it possible for a page of memory to be marked executable but not readable, in the context of the running process?

Once the loader has loaded the shared library, the running program can clearly execute it (or more accurately, the pages that contain the shared library). If the running program can also read those pages, then you have only closed the bathroom window on the barn, but left the doors open: the attacker can still put his reading code into the executable itself.
You are right, an attacker could link a payload into an executable and then dump the shared objects that were intended to be unreadable. In the context of the system I am working on though, this will not be possible.
  1. Writable filesystems are mounted as noexec
    • This is more of an inconvenience than anything.
  2. The location of the system dynamic linker can not be determined by the attacker.
    • All files referencing the linker path are stripped of read permissions.
    • Only the system linker will have the group permissions needed to read the shared objects.
    • If the attacker were to bring their own linker, it would not be able to read the shared objects.
  3. Most importantly, the hardware ISA and system ABI are both non-standard and will need to be first reverse-engineered before the attacker can build an executable to dump memory within.
    • Keeping both the ISA and ABI unknown is the goal with blocking read access to all ELF files.
Right now read-access to shared objects is the main roadblock in keeping knowledge of the ISA/ABI restricted to the persons authorized to compile software for this system.
 
Is it possible for a page of memory to be marked executable but not readable, in the context of the running process?
I think it may be - you might have two memory fetch engines in the architecture, the instruction fetcher could ignore the read/write bits. That is left to the data fetcher. Those are downstream from the dedicated caches, but as soon as you have a common cache below that you might be right up undefined creek.
Most importantly, the hardware ISA and system ABI are both non-standard and will need to be first reverse-engineered before the attacker can build an executable to dump memory within.
  • Keeping both the ISA and ABI unknown is the goal with blocking read access to all ELF files.
I don't think that will help much, it will add to the thrill of doing it. And depending on who is doing the investigation and how hard they want to know, a $2 tire iron will do the trick.
 
I think it may be - you might have two memory fetch engines in the architecture, the instruction fetcher could ignore the read/write bits. That is left to the data fetcher. Those are downstream from the dedicated caches, but as soon as you have a common cache below that you might be right up undefined creek.

I don't think that will help much, it will add to the thrill of doing it. And depending on who is doing the investigation and how hard they want to know, a $2 tire iron will do the trick.
It is important in the context of what ralphbsz said. The unprivileged user will not have access to the compiler for the system. The only way they will be able to build an ELF file that can dump the contents of shared objects if is they already broke the secret of how to compile software for the system.
 
So you are building a system where compiler/linker are not accessible to normal users, where writeable files are never executable, and where the OS ABI is handcrafted. This sounds like a lot of work, it will probably cause a lot of bugs, and will make the system borderline unusable. The various hacks required to make the system do whatever you need it to do are probably going to cause new and interesting security risks.

Are you sure this is a reasonable tradeoff? For a very minor security gain, you are putting in a lot of engineering, and a lot of risk.

What you're trying to do is, in a nutshell, to create a sandbox, where unprivileged users can run certain programs, but not have unwanted side effects, nor figure out why things work. Sandbox research is a huge topic. Have you looked at existing solutions first?
 
So you are building a system where compiler/linker are not accessible to normal users, where writeable files are never executable, and where the OS ABI is handcrafted. This sounds like a lot of work, it will probably cause a lot of bugs, and will make the system borderline unusable. The various hacks required to make the system do whatever you need it to do are probably going to cause new and interesting security risks.

Are you sure this is a reasonable tradeoff? For a very minor security gain, you are putting in a lot of engineering, and a lot of risk.

What you're trying to do is, in a nutshell, to create a sandbox, where unprivileged users can run certain programs, but not have unwanted side effects, nor figure out why things work. Sandbox research is a huge topic. Have you looked at existing solutions first?
I appreciate the serious reply. I already have FreeBSD installations with machine-unique ABI's deployed in production settings, they have no issues with stability; a fan failure has been the only problem over the past year. The changes are transparent to all software (excluding the compilers ;) those are all modified -- and not included in the deployed systems). This is a solution to prevent code injection on a server environment. To be specific, all of the ~500 system call numbers are randomly reassigned for each machine in the fleet. If statically linked code is successfully injected into the system via a memory exploit, for instance, the attacker will not even be able to call malloc or fopen reliably. In all likelihood, the injected code will cause a SEGFAULT due to incorrect syscall arg structure. This will indicate (to the admin) that a vulnerability has been discovered well before the vulnerability can be successfully utilized. Taking things a step further, PoC's for ISA randomization have been successfuly demonstrated (implemented in CPU uCode) but have yet to be utilized by our FreeBSD builds. Ultimately, an attacker wont even know how to multiply two numbers together on these CPUs.

I see this as a very significant security gain, not a minor one. The scenario I am trying to address with shared object permissions is when a service implemented in a scripting language (ie: python, js, etc) becomes exploited. Scripting languages could easily be used to dump/analyze ELF files in the system to gain the knowledge needed to correctly build an exploit targeting another vulnerability in the system for proper privilege escalation.

Context aside ... all I'm really after is finding where in the kernel the dynamic linker is executed. It is a large codebase that I haven't finished learning how to navigate. I was hoping someone more familiar could point me in the right direction..
 
Ugh. I agree with ralphbsz

If ever you do succeed, make sure there's a way to turn it off. Otherwise you will have a system that is impossible for developers to use. I don't count forcing developers to work with privileges.

macOS has done things in this direction. The "system libraries" no longer exist as physical mach-o files. Instead they are in some sort of encrypted database. The OS extracts them and maps them into memory. When a process starts it gets the system libraries mapped to memory as if by magic.

Needless to say, low level development on macOS is an absolute nightmare. If you want to use gdb you have to kind of rootkit your own computer.
 
I just hope you have disabled dtrace and any other way to have dynamic code in kernel space, like in packet filtering.
And the dynamic linker is associated with ELF files by means of being the interpreter for them. Search in that direction.
 
Guys?

This sounds like it is for some turnkey devices, only one binary (or very few) running. There it would make sense if reversing one device would not get you breaking into all of them. Is that so, mrothfuss?
If we think about a complete server or desktop environment with this technique, it surely is beyond sensible. But for a kind of, say, router?
 
Guys?

This sounds like it is for some turnkey devices, only one binary (or very few) running. There it would make sense if reversing one device would not get you breaking into all of them. Is that so, mrothfuss?
If we think about a complete server or desktop environment with this technique, it surely is beyond sensible. But for a kind of, say, router?
Yes, that is accurate. Routers, firewalls, webservers, and other services are the main deployment scenario here wherein little software configuration is needed post-deployment. My mention of an unprivileged user was regarding a service account with restricted permissions that has become compromised, not an actual authorized user who would be using the system. I have tested this system in a desktop environment and have found no issues though; web browsers, libreoffice, and even amdgpu work without any issues.
At least on x86 architecture execute implies read. You can always dump pages marked as executable only and hence read them.
This is true, but even on x86 if you can not compile code for the system you will not know how to dump executable pages out of memory.

Perhaps I've posted this in the wrong section; this relates to development in the FreeBSD kernel but these are certainly not proposed changes for the mainline kernel.
 
I meant dump from userspace. Perhaps I should have included this in your quote:
For context, my goal is to create an environment where an unprivileged user can not read/analyze ELF files but can still execute them.

On x86 user that can execute binary can dump it and read it. At least by default. You could make it harder ( security.bsd.unprivileged_proc_debug) but with dynamic ELF you could still inject with LD_PRELOAD.
Static one without debugging possibility could be a challenge; limiting dumps (setrlimit(3)) could help. But there still might be a way to get the process to the state where you could manipulate it.
 
This is true, but even on x86 if you can not compile code for the system you will not know how to dump executable pages out of memory.
What's a compiler? What would it be useful for? <- Just kidding

It's perfectly possible to create binary executables without any compiler. When I was a teenager, I used to hand-assemble code for the Z80 and 6809, and type in the binaries with a tiny debugger, then save them on cassette. Debugging was tedious, but for small programs, that's not a big problem.

About 15 years ago at work, we put executable code in compile-time arrays in C code, then execute the arrays. The reason was that the x86 CPU doesn't have enough registers for the image processing code we were running; so we put a generic version of a routine into an array of bytes, modified the routine on the fly to have the correct memory addresses and constants, then executed it. Worked like a charm.

So what you're doing is making hacking harder, but not impossible. Depending on what your threat model is (who is after your systems), that might be good enough. Against script kiddies, it probably works. Against the 3-letter agency of a nation state, not so much.
 
What's a compiler? What would it be useful for? <- Just kidding

It's perfectly possible to create binary executables without any compiler. When I was a teenager, I used to hand-assemble code for the Z80 and 6809, and type in the binaries with a tiny debugger, then save them on cassette. Debugging was tedious, but for small programs, that's not a big problem.
Will you know how to correctly interact with the CPU registers if all the ISA opcodes have been randomly reassigned? MOV is ADD, ADD is MUL, PUSH is POP, RETN is DIV...

I don't think so. You'll have a hard time implementing even a hello world program in machine code without the system-specific compiler at your disposal.
 
Hard, yes. Impossilble? No. Ever heard of "known plaintext attack"? What are you using to do that? A soft core?

As ralphbsz pointed out, you should not bet against the TLAs. The puzzle pallace will think of that as a challenge. And if they want you to fail, you will.
 
Hard, yes. Impossilble? No. Ever heard of "known plaintext attack"? What are you using to do that? A soft core?

As ralphbsz pointed out, you should not bet against the TLAs. The puzzle pallace will think of that as a challenge. And if they want you to fail, you will.
In a production setting this might as well be impossible; a single execution error could be used to detect an intrusion and lockdown the system for analysis+patching. This would prevent brute-forcing the ISA.

What would be your "known plaintext" in this scenario? On this single machine with a randomized ISA and ABI all examples of compatible machine code are to be non-readable.

Not a soft core, bare metal ;) for many CPUs rewriting CPU microcode enables arbitrary rearrangements of the instruction set.

I have entertained the thought of opening shell access to one of these systems for those looking for a puzzle/challenge! Info for a bitcoin wallet could even be placed in /root for extra fun.
 
The start up code will have known patterns. Many functions have known starting patterns.

Maybe you can pull that off - changing the microcode I mean. And the BIOS/EFI/... ? What about that? It takes an awful lot of expertise to do that, and here you are looking for the dynamic linker invocation?
Please tell us more. The project sounds interesting, but the scope is unclear.
 
This has been in the context of a service providing device, physical access and analysis is not a concern. To get into device firmware, microcode can be modified after the BIOS has finished initializing the hardware. Depending on the degree of rearrangement, some BIOS services will break. For robustness, the ISA switch would ideally be implemented as a new intermediate stage in the coreboot boot process. Subsequent stages would use the modified ISA.

While there have been mentions of tire irons and TLAs... randomizing part of the OS ABI offers considerable protection from non-targeted attacks. Only an expert deliberately trying to break in to these systems might succeed. That situation is not a priority at the moment. Again, in all likelyhood even an expert would immediately cause a SEGFAULT that could be used to detect intrusion and lock the system down for analysis+patching. This will enrage all the system admins out there... but these random ABI installations are used as deploy-and-forget systems. If someone really wants to go through the trouble of breaking into a unique OS, I will yield the system to them out of respect (... or is it pitty?).

For me, expanding on this idea of randomizing machine code is a fun game. I was looking for one line of code in a large, unfamilar codebase last Sunday. I have not had the time to continue reading through the kernel ELF image code since then. Learning the FreeBSD kernel internals is not a pressing interest for me at the moment and my time is limited, I'd much rather accept help from someone who already knows what I'm trying to find. Sadly, it will likely be 2+ months from now that I play with this project again.
 
Back
Top