Breaking chromeOS's enrollment security model: Part 4

The sequel that never came to CoolElectronics's 3-part series

Posted on May 16, 2023

About the series

As some of you may know, CoolElectronics has a series of blog posts that detail how Mercury Workshop and TitaniumNetwork members have gone about breaking ChromeOS's enrollment system. It started off with post 1 which talks about the crosh->chronos breakout (set_cellular_ppp), root escalation with many different chromium bugs (shameless self-promo: I got that last crbug working on v80 and used it to unenroll for my first time), kiosk exploit + extension impersonation, and finally the primitive userpolicy bypass pre-Pollen. Next up came post 2 which talked about the holy grail: sh1mmer. sh1mmer allowed anyone with a Return Merchandise Authorization or RMA shim available or leaked to unenroll within minutes. But there's more: Post 3 talks about post-unenrollment shenanigans like the Brunch framework on a USB drive, device management API spoofing, and fakemurk: the tool allowing you to spoof being in verified mode and enrolled while actually being in dev mode. But there's much more, and this post will cover even more shenanigans with chromeOS.


I'm not going to talk a lot about this, since there's a whole blog post about it on my site, but I'll quickly sum it up. Read the other post if you want an in-depth explanation. Lilac is a chromeOS device policy editor, like Pollen which was mentioned in post 3 of this series. Instead of inserting an undocumented file in the rootfs which requires you to disable rootfs verification for persistence, it directly modifies and re-signs the policy blob.

image of a ChromeOS VM whose policy has been patched with lilac


Back in... January of 2023 (I had to go look at old messages, it feels so long ago), aub, CoolElectronics, OlyB, kaitlin and some others including me were talking about how Google can't patch sh1mmer without inadvertently patching downgrading in the progress. I knew that the recovery process has a postinstall section where some sort of dm-verity is set up based on logs (haven't really looked into that) and more importantly, the firmware is updated. I jokingly suggested that someone should perform an EC (embedded controller) reset before the post-install scripts run. At that time I probably thought that something would break, but...

image of ecELEVATOR in action

here we are with chromeOS 72 on an octopus chromebook. There are some quirks with going so low, such as vpd no longer working as well as not being able to enroll, which means you can easily add a personal account to access crosh, then rootesc to enable devmode and escape enrollment. The weirdest quirk of them all has got to be that when you update from v72 instead of going to the latest version, you get updated to v80...?


More recently, I decided to figure out how to get chromeOS running in a VM. It took, no I'm not kidding, a few minutes worth of searching through code to convert ChromeOS Flex into ChromeOS, except without ARC++/ARCVM (the android subsystem). Google can easily create builds of ChromeOS for VMs but why would they ever do that? Anyways, back to converting ChromeOS Flex into ChromeOS. Here's the culprit on line 522:

image of Chromium Code Search open to file src/platform2/login_manager/; line 522 is highlighted; reven_branding is in the search bar.

Let's try enrolling and... it doesn't work and the error mentions something about "install attributes invalid". I spent some more time on this, but eventually just decided to go with a swtpm + qemu emulated tpm setup. I mostly switched over to working on fake_dmserver now, but I did spend some time trying to get ARCVM working and eventually gave up. A few days ago I started trying to patch a regular Chromebook image to work with VMs and non-chromebook hardware in general, but I'm still working on that. Once I have it working, I'll probably put it up on GitHub and update this post with the link. Check back later!

Remember back in part 3 when CoolElectronics said:

(If you’re wondering why we didn’t use flex, it has its own weird code setting it apart from stock chromeOS that makes it so it would be infeasible to make it pretend it was a normal chromebook)

Yeah, that's false now, we can bypass it with a one line change! Really makes me wonder how much of chromeOS is held up with shoestrings and packaging tape like this...


After getting crosvm working, I was aimlessly looking at Code Search (yes I do that now) when I found this interesting folder:

image of Chromium Code Search open to folder components/policy/test_support

From looking through the code, it functions basically as a local clone of the device management server at (codesearch link), intended to be used for tests. Off I go compiling most of Chromium with my very bad processor!

There's barely any documentation on fake_dmserver in the Chromium repos and such is the case with most of chromeOS, usually it's outdated or nonexistent. I'll skip straight to how to use fake_dmserver instead of making you read about me debugging:

How exactly to use fake_dmserver

  1. Compile it
  2. Run it
  3. ???
  4. Profit

Nah, if it was that easy any regular TN greyname/skid could do it. You first have to clone ALL of the chromium source, which I'll link the Chromium guide for; luckily these docs are mostly up to date. Do note that you will need to clone the chromeOS code too, so add target_os = ["chromeos"] to your .gclient file (which is in the parent directory of the cloned source) and re-sync the source.

Continue on by generating a build directory: gn gen out/Debug --args='target_os="chromeos"' or whatever folder you want.

Start the build: autoninja -C out/Debug components/policy/test_support:fake_dmserver.

You hopefully now have a built fake_dmserver binary that either took days of your life to build or a few hours depending on your hardware.

Create a policy.json (no this is NOT the policy file I haven't figured out how to set policies yet) file with the following contents:

{ "managed_users" : [ "*", "<any old google account email which will be used for enrollment>" ], "policy_user" : "<that same email>", "use_universal_signing_keys": true, "allow_set_device_attributes": true }

Create an empty state.json file with {} in it to hopefully pacify fake_dmserver.

You can now safely run fake_dmserver in the same path as your 2 json files: /path/to/fake_dmserver/fake_dmserver

And direct your chromeOS test dummy to it with: --device-management-url="http://<localhost>:<port>"

What's next

There's much more going on in Mercury Workshop that will come soon (recovery image shenanigans hint hint). Part 5 is out and talks about halcyon.


  • ecELEVATOR: CoolElectronics (main testing), r58Playz (some more testing and suggested the idea in the first place)
  • lilac and crosvm: r58Playz
  • fake_dmserver: Google for the code, r58Playz for integrating it with crosvm