▪ Professional tinkerer & educator
▪ Recovering system administrator
▪ Love to make bad guys have bad days
Tony Lambert
Detection Engineer/Intel
Red Canary
@ForensicITGuy
id -un
Slide 3
Slide 3 text
What is LD_PRELOAD and how is it used?
Examining the rtld-audit subsystem
Whitelisting LD_PRELOAD
What worked and what didn’t
Overview
Slide 4
Slide 4 text
▪ glibc & musl environment variable
▪ Injects arbitrary SO file into process
▪ LD_PRELOAD or /etc/ld.so.preload
Intro to LD_PRELOAD
Slide 5
Slide 5 text
rtld.c
Slide 6
Slide 6 text
Get Started With Env Var
LS
SHELL=/bin/bash
PATH=/long/list/of/folders
…
LD_PRELOAD=injected.so
/proc/PID/env
Slide 7
Slide 7 text
Make It Persistent
$ LD_PRELOAD=injected.so ls
$ export LD_PRELOAD=injected.so
# echo injected.so > /etc/ld.so.preload
# echo LD_PRELOAD=injected.so >> /etc/profile
Slide 8
Slide 8 text
It Can Be Good!
LS
libdl.so
libc.so test-libc.so
Slide 9
Slide 9 text
Evil Use Cases
■ Hooking functions and calls
■ Automatic code execution on load
Slide 10
Slide 10 text
Hooking Functions
■ Define function with same name
■ Manipulate output and fool user
■ libprocesshider
Slide 11
Slide 11 text
It Can be Very Bad!
PS
libc.so
readdir()
evil.so
readdir()
Intact Listing
Bash
Python evil.py
Tainted Listing
Bash
...
Slide 12
Slide 12 text
Hooking Example
Slide 13
Slide 13 text
Execution on Load
■ No knowledge of target binary needed
■ Code stored in ELF .init section
■ Zombie Ant Farm Project
Slide 14
Slide 14 text
Execution Example
Slide 15
Slide 15 text
The rtld-audit Subsystem
■ Audit API for the dynamic linker
■ Write a SO library to implement
■ Use with LD_AUDIT variable
At First, I Wanted to Log
la_objsearch()
la_activity()
la_objopen()
la_objclose()
la_preinit()
la_symbind*()
la_pltenter()
la_pltexit()
Slide 18
Slide 18 text
I Realized I Could Prevent
la_objsearch()
la_activity()
la_objopen()
la_objclose()
la_preinit()
la_symbind*()
la_pltenter()
la_pltexit()
Slide 19
Slide 19 text
Intercept Before Load
la_objsearch()
The dynamic linker invokes this function to inform
the auditing library that it is about to search for a
shared object.
rtld-audit(7) manpage
Slide 20
Slide 20 text
You Had Me At “return NULL”
la_objsearch()
...returns the pathname that the dynamic linker
should use... If NULL is returned, then this
pathname is ignored for further processing.
rtld-audit(7) manpage
Slide 21
Slide 21 text
No content
Slide 22
Slide 22 text
Let’s Block Some Preloads!
■ Define unauthorized preloads
■ Define authorized preloads
■ Monitor for unauthorized loads
■ Mitigate the loads
Slide 23
Slide 23 text
Unauthorized Preloads
■ Unpredictable preloads
■ Unlimited forms of evil
■ Block by default
Slide 24
Slide 24 text
Authorized Preloads
■ Unpredictable system configuration
■ Wanted ease of administration
■ Modeled after cron.allow, hosts.allow
Slide 25
Slide 25 text
Monitor & Block Preloads
■ Intercept module search
■ Check the allowed list
■ Return NULL if not allowed to block
Slide 26
Slide 26 text
Enter Libpreloadvaccine!
■ LD_AUDIT SO library
■ Intercepts and blocks
■ Just worksTM
github.com/ForensicITGuy/libpreloadvaccine
Logo left intentionally blank.
Slide 27
Slide 27 text
Simple Logic
Slide 28
Slide 28 text
Simple Authorized List
/etc/libpreloadvaccine.allow
■ Check the allowed list
■ Return NULL if not allowed to block
Slide 29
Slide 29 text
Simple Deployment
1. Compile with gcc
2. Move to folder
3. Set LD_AUDIT
export LD_AUDIT=/usr/lib/libpreloadvaccine.so
Slide 30
Slide 30 text
Success! Sort of...
Slide 31
Slide 31 text
Catch it in Action!
Slide 32
Slide 32 text
And Bypass it After!
Slide 33
Slide 33 text
Lessons Learned
Slide 34
Slide 34 text
Keep Security Close to Code
■ Bolted on solutions don’t always work
■ Code inside rtld.c would be harder to bypass
■ Attackers with root can have unlimited access
Slide 35
Slide 35 text
FEEDBACK
Q & A
REDCANARY.COM/BLOG
@REDCANARYCO
GITHUB.COM/FORENSICITGUY/LIBPRELOADVACCINE
@FORENSICITGUY