For many years now, the People’s Republic of China (PRC) has been building its own advanced cybersecurity services to reduce its reliance on Western technology.
A lot of effort has been put into creating "home grown" Linux distributions, long before the recent embargo announcements, CHIPS Act, and other legislative efforts aimed at disrupting PRC’s development in IT technology.
The Kylin based Linux distributions are well known, and come in many flavors:
- KylinOS, NeoKylin, UbuntuKylin and now (in 2023) finally openKylin.
Some of the PRC’s largest tech companies and institutions jointly contribute to the distributions’ development, and they have been hyped up by local media. So it is about time to have a look from the security research perspective, right?
I chose openKylin 1.0, since it was just recently released and freely available. It is also one of the very few distributions that offer images for several RISC-V platforms, so you can see that they are really serious about their independent tech strategy and intelligence solutions.
I will be using the x86-64 image for ease of use in this exploit development exercise, but I confirmed the vulnerabilities also exist on aarch64 (ARM 64), and most likely they are also present in the RISC-V images.
So what are you going to do if your fancy kernel 0days are not yet ported by your local TAO office to work on RISC-V boxes?
Right: userland Python DBus services to the rescue!
I will keep it short and and focus on the useful service. There are a couple of candidates to choose from, but some are mitigated by fs.protected_symlinks
and some others by missing files inside /sys
on aarch64.
Here is the DBus service file which I am going to use:
[D-BUS Service]
Name=com.kylin.os.manager.systembus
Exec=/usr/bin/kylin-os-manager-systembus.py
User=root
Apparently, one of their own inventions, and here is the Python:
import os, sys
import signal
import dbus
import dbus.mainloop.glib
from gi.repository import GObject
from single import SingleInstance
#import gobject
if __name__ == '__main__':
[1] myapp = SingleInstance("/tmp/kylin-os-mananger-daemon-%d.pid" % os.getuid())
[...]
if os.path.exists("/var/lib/apt/lists/lock"):
os.remove("/var/lib/apt/lists/lock")
from systemdbus.daemon import Daemon
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
GObject.threads_init()
mainloop = GObject.MainLoop()
#gobject.threads_init()
#mainloop = gobject.MainLoop()
signal.signal(signal.SIGINT, lambda : mainloop.quit())
Daemon(dbus.SystemBus(), mainloop)
mainloop.run()
and most importantly, the actual definition of the SingleInstance
class:
# ensure that a single instance of the applet is running for each user
class SingleInstance(object):
#Initialize, specifying a path to store pids
def __init__(self,pidPath):
self.pidPath = pidPath
self.lasterror = False
if os.path.exists(pidPath):
# Make sure it is not a "stale" pidFile
[2] pid = open(pidPath, 'r').read().strip()
# Check list of running pids, if not running, it is stale, so overwrite
pidRunning = subprocess.getoutput('ls -1 /proc | grep ^%s$' % pid)
self.lasterror = True if pidRunning else False
else:
self.lasterror = False
At [1], a file from /tmp
is used that does not already exist and can be filled by
attackers with arbitrary content. At [2] this content is used and assumed to be a PID (process ID).
But it can contain anything, not just numbers. So pid
is actually a string and it is passed to the Python subprocess
module, which inserts it into a command called with sh -c
.
Things are so easy, the shell can be directly called with handcrafted magic:
Since the vulnerable code is called on the instantiation of the object, it is not necessary to
pick any particular DBus method or parameters. We just need to trigger the DBus activation
without any annoying PolKit rules in the way.
You are my one and only: SingleInstance
The software reuse pattern tells students in cybersecurity training not to reinvent the wheel but to abstract common functions into modules or classes and re-use them.
Python supports developers with that, so it comes as a little surprise I also find SingleInstance in other flavors of Kylin: UbuntuKylin 23.04.
The DBus service has a different name this time:
com.kylin.assistant.systemdaemon
and the name of the golden file is
/tmp/kylin-assistant-systemdbus-0.pid
, but intelligence solutions work as smoothly as before with these changed parameters.