Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Sun, 28 Apr 2024 23:48:13 -0500
From: Jacob Bachmeyer <jcb62281@...il.com>
To: oss-security@...ts.openwall.com
Subject: Re: Update on the distro-backdoor-scanner effort

Hank Leininger wrote:
> On 2024-04-27, Jacob Bachmeyer wrote:
>
>   
>>>   - Output is manageable; able to rule out all hits not part of the
>>>     actual xz-utils backdoors as false positives.
>>>       
>> This is what I would expect:  the backdoor dropper appears to have
>> been specifically developed for xz-utils, but could /possibly/ be
>> adaptable to other compression tools.
>>     
>
> Indeed, well my thinking was more along the lines of: "This is an
> impressive amount of moving parts to be created new for this project,
> and burned for just this project. What if what we are seeing is the 2nd
> or 3rd gen of such a toolkit, and earlier ones have some similar
> characteristics but maybe fewer layers, etc. so we could spot them?"

I disagree here because, while I agree that there are quite a few moving 
parts, I also (having replicated unpacking the dropper shell scripts) 
see incremental development paths, and a few major mistakes that a more 
polished backdoor could have avoided.  If this were a 2nd or 3rd 
generation toolkit, those mistakes would have been fixed.

Overall, I think the "Jia Tan" crew went straight for the "Golden Key" 
and bit off /way/ more than they could chew.

> [...] while the liblzma decompressor had
> certain things going for it as a target, why not other things? (Nor am I
> restricting to "other things that would get linked into sshd", but
> really more broadly.)
>   

I disagree with this assessment:  the backdoor blob, according to 
reports so far, does not appear to actually affect xz or liblzma /at/ 
/all/; liblzma is merely used (as a dependency of libsystemd) to smuggle 
the blob into the sshd process and pass control into the blob during 
process initialization.  From the reports that I have seen, the 
entrypoint that liblzma is patched to call does very little itself, and 
only modifies ld.so's data segment to half-register the blob with the 
LD_AUDIT framework so it will be called again later.  Nearly all of the 
backdoor seems to be independent of liblzma.

> [...] The
> xz-utils stuff is many generations ahead of those; it would be
> interesting to spot some missing links.
>   

I suspect that the "missing links" you seek are to be found in various 
Windows malware over the years, at least as far as the binary backdoor 
blob is involved, but I have not been directly involved in that 
analysis.  From the reports I have read, it seems to be more-or-less a 
piece of Windows malware adapted to an ELF environment.  The adaptations 
also seem to be exactly the parts that went poorly, causing performance 
problems that led to the backdoor's discovery.  (Or ld.so really is that 
much more efficient than the Windows module loader, and performance 
issues that are routinely lost in the noise on Windows got the "Jia Tan" 
crew caught.)

The dropper scripts do not appear well-developed to me.  Overall, they 
/do/ strike me as a first-generation effort, likely the authors' first 
shell scripts on top of that, and evidence a poor understanding of the 
GNU system.  There are worse-than-beginner mistakes in there, like 
omitting the spaces around the '=' in a string comparison, and overall 
patterns that suggest a poor understanding of shell scripting.  For 
example, a common idiom in shell programming is to use && and || for 
conditional execution of single commands, especially conditional exits; 
the dropper uses this idiom once, getting the test wrong (the 
aforementioned missing spaces), but then uses if/then/fi later for 
conditional exits.  The dropper scripts also appear to be the product of 
copy-and-paste cargo cult programming:  some commands are written as 
"[...] || true" even though their exit status will actually be ignored, 
suggesting that they were adapted from a Makefile or a script that runs 
with "set -e" in effect.  Lastly, the outer dropper uses a linear series 
of head(1) commands to "skip 1KiB of garbage, extract 2KiB of backdoor, 
repeat" but simply reading the manuals would have revealed more 
appropriate tools for that---and a way to make the outer dropper 
independent of the inner dropper's length.

>> You might get better results by indexing macro definitions found in
>> *.m4 files, instead of trying to fuzzily hash the files.  The
>> interesting comparison is then different definitions of macros with
>> the same name.
>>     
>
> I like this a lot as a potential next layer for the m4 reconciliation.
> Essentially a field-level matching once things that match at a
> file-level have been eliminated. I don't see why (he says, not actually
> having dug into the m4 format much) we couldn't break apart all the m4s
> we are choosing to consider known-good, and catalog each individual
> macro, and then do the same when bashing project-specific files. Could
> be we can entirely "clear" a file with an unknown checksum because it
> consists 100% of idnividual macros that are known.

More likely, you would be able to "clear" a file that actually differs 
in either whitespace or comments---or catch a file that has some 
nastiness inserted between macro definitions.

> (Insert weird machine
> here that combined OK macros in surprising ways.)
>   

While m4 technically is Turing-complete, I strongly doubt the existence 
of m4 weird machines:  m4 is a macro processor that performs text 
expansion.  You should have a manual for it in the Info system.

>> many (most?) modern Linux kernels are compressed using xz, which means
>> that a Thompsonesque attack could binary-patch a freshly built kernel
>> while compressing vmlinux to make vmlinuz.
>>     
>
> Good call. This may be far out on the.... "far out" scale, but it'd be
> pretty trivial to harvest distro kernels that used xz to make their
> vmlinuz, and then run those through multiple independent implementations
> like we have done with .tar.xz files. Sold.
>   

Make sure that at least one of those implementations is a wrapper around 
the xz-embedded decompresser that Linux itself uses.  (As in, mmap() 
vmlinuz MAP_PRIVATE, mmap an output file at the appropriate virtual 
address, patch the jump into the kernel at the end of the decompression 
stub with a return, and call the embedded decompresser.)  If you are 
concerned about weird machines, there is a tiny, paranoid, possibility 
that a tampered compressed stream would decompress "clean" with any 
other decompresser.

I doubt that you will find anything, though.  The "Jia Tan" crew does 
not seem to have been that advanced.

>> The IFUNC mechanism is actually a security feature.  In "inner-loop"
>> code, having multiple implementations with different optimizations
>> with the preferred implementation for the local processor chosen at
>> runtime is fairly common.
>>     
>
> Thanks for this! I've seen that discussed as a (valid, useful) use of
> IFUNC but also AFAIK things like musl don't implement such a thing, so
> either software that wants it just doesn't support musl, or can't pick
> an optimization, or does so in an undesirable way like writable pointers
> in the data segment or... some other option?

When IFUNC usage was added to xz-utils, the older "dispatch through a 
function pointer" technique was kept as a fallback when building without 
IFUNC.

> [...] I'd be satisfied being able to say "IFUNCs are
> used by 15 out of 10,000 packages; that's a small enough number we can
> a) audit them all b) add alerting to tooling used for builds; when a
> package suddenly starts using them, look into it." If the real number
> turns out to be 1,000 out of 10,000, then that's good to know, and
> probably give up.
>   

Alerting should be simple matter of `grep -Ri ifunc .` at the top of the 
package tree.

>> I currently suspect that the crackers used IFUNC support as a covert
>> flag.  The "jankiness" of the current glibc IFUNC implementation
>> provided a convenient excuse to ask oss-fuzz to --disable-ifunc when
>> building xz-utils, which *also* conveniently inhibited the backdoor
>> dropper and ensured that the fuzzing builds would not contain the
>> backdoor.
>>     
>
> Uncynically, I like this conspiracy theory.
>   

Then I will go a step farther:  I half-suspect that the entire 
CLMUL-based CRC calculation may have been added as an excuse to add the 
dispatch logic, which in turn was an excuse to use IFUNC.  While I have 
not run tests, I suspect that the baseline table-driven CRC calculation 
is probably already I/O-bound on modern processors, especially x86-64 
processors new enough to /have/ CLMUL.

> [...]
> I think Sam looked into existing pkg-config verifiers and found they do
> not complain about things we thought they should complain about (this
> could just mean we misunderstand their purpose). A strict lint-checker
> for such files would be better than just checking for specific
> suspicious patterns. But, I don't yet know how strict a format we could
> insist on (would it turn out 10% of files in fact break what we
> initially think are reasonable rules?). Even still, I think you could
> embed badness in legit variables, although I haven't dug in enough to
> know that for sure.
>   

Quoting pkg-config(1):
> Files have two kinds of line: keyword lines start with a keyword plus a
> colon, and variable definitions start with an alphanumeric string plus
> an equals sign. Keywords are defined in advance and have special
> meaning to /pkg-config/; variables do not, you can have any variables
> that you wish (however, users may expect to retrieve the usual
> directory name variables).

The format is clearly defined; any pkg-config file containing nonblank 
lines /not/ matching the above description should be rejected, and 
pkg-config itself should implement this check.  This rule ensures that a 
pkg-config file cannot also contain shell commands.

The sample backdoor also uses an *-uninstalled.pc file to override a 
legitimate file, but places the "uninstalled" file in a system 
directory, which means it is actually installed!  The pkg-config program 
should also refuse to read *-uninstalled.pc files from system 
directories, and emit a warning if such a file is found to exist.


-- Jacob

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.