Exactly 3 months and a day after placing an advise by a Romanian Apple reseller, I in the end received my 14-stir M1 Max.

Effectively, in reality.. I first received the depraved configuration (unpleasant mannequin as a replacement of CTO), needed to advance it to them after wasting a day on migrating my data to it, they despatched my cash aid by mistake, needed to pay them again, and after many calls and emails later the righteous computer arrived. M1 Max MacBook Pro box

As soon as these gadgets had been in the fingers of users, requests started coming in for Lunar to provide an risk to fetch previous the 500 nits limit for each day usage

Over the closing week I tried my most attention-grabbing to determine the fashion to invent this, but it no doubt’s both impossible to boost the nits limit from userspace, or I fair don’t accept as true with the foremost skills.

I’ll fragment some crucial parts that I realized while reverse engineering my ability by the macOS part that handles brightness.

# Attempting out the scheme

# Playing a HDR video

I first started by playing this HDR take a look at video (launch it in most recent Chrome or Safari for most attention-grabbing results): hdr-take a look at-pattern.webm

Which resulted in a blinding white at 1600 nits: HDR white being whiter than the webpage white

This generated the following logs in Console.app:

1
2
3
WindowServer    Drawl 1 atmosphere nits to 888.889
corebrightnessd SDR - perceptual ramp clocked:  227.095169 -> 252.268112 - 49.169426% (239.142059 Nits)
WindowServer    Drawl 1 commitBrightness sdr:  211.603, headroom:  4.20075, ambient:  4.3396, filtered ambient:  13.6333, limit:  1600

# SDR cap in customary lighting

After atmosphere the cloak brightness to max, I might well well gaze in the logs that SDR (Fashioned Dynamic Fluctuate) became being capped at 400 nits:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
WindowServer    Drawl 1 atmosphere nits to 1600
WindowServer    Drawl 1 atmosphere cloak headroom hint to 7.56866
WindowServer    Drawl 1 commitBrightness sdr:  216.548, headroom:  7.38865, ambient:  4.24854, filtered ambient:  13.3472, limit:  1600
corebrightnessd PCC:  Situation PCC:  Part:=1.0496 CabalFactor:=0.0033 time=2.000000 Lux:=13.6080 Nits:=229.1757 consequence=1 error=(null)
WindowServer    Drawl 1 commitBrightness sdr:  301.188, headroom:  5.3123, ambient:  4.24854, filtered ambient:  13.3472, limit:  1600
WindowServer    Drawl 1 atmosphere nits to 1602.03
corebrightnessd levelPercentage 0.334298, diploma = 4.967383 (nits/pwm), lux = 15.000000
WindowServer    Drawl 1 commitBrightness sdr:  301.571, headroom:  -1, ambient:  4.79275, filtered ambient:  15.0569, limit:  -1
WindowServer    Drawl 1 atmosphere cloak headroom hint to 5.27556
WindowServer    Drawl 1 commitBrightness sdr:  321.478, headroom:  4.97701, ambient:  4.79275, filtered ambient:  15.0569, limit:  1600
WindowServer    Drawl 1 commitBrightness sdr:  340.675, headroom:  4.69655, ambient:  4.79275, filtered ambient:  15.0569, limit:  1600
WindowServer    Drawl 1 commitBrightness sdr:  377.322, headroom:  4.24041, ambient:  4.79275, filtered ambient:  15.0569, limit:  1600
corebrightnessd PCC:  Situation PCC:  Part:=1.0340 CabalFactor:=0.0023 time=2.000000 Lux:=15.0569 Nits:=377.3223 consequence=1 error=(null)
WindowServer    Drawl 1 atmosphere nits to 1600
WindowServer    Drawl 1 atmosphere cloak headroom hint to 4
WindowServer    Drawl 1 commitBrightness sdr:  400, headroom:  -1, ambient:  4.96577, filtered ambient:  15.6004, limit:  -1

HDR white and console logs side by side

# SDR cap in declare sunlight hours

Colorful a flashlight right this moment into the Ambient Gentle Sensor allowed SDR to soar as much as 500 nits:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
WindowServer    Drawl 1 commitBrightness sdr:  400, headroom:  -1, ambient:  322.204, filtered ambient:  1012.24, limit:  -1
WindowServer    Drawl 1 commitBrightness sdr:  400.484, headroom:  1, ambient:  322.204, filtered ambient:  1012.24, limit:  400.484
WindowServer    Drawl 1 atmosphere nits to 400.484
WindowServer    Drawl 1 commitBrightness sdr:  401.15, headroom:  1, ambient:  322.204, filtered ambient:  1012.24, limit:  401.15
WindowServer    Drawl 1 atmosphere nits to 401.15
WindowServer    Drawl 1 commitBrightness sdr:  401.223, headroom:  1, ambient:  322.204, filtered ambient:  1012.24, limit:  401.224
WindowServer    Drawl 1 atmosphere nits to 401.223
WindowServer    Drawl 1 commitBrightness sdr:  401.223, headroom:  -1, ambient:  370.814, filtered ambient:  1164.95, limit:  -1
WindowServer    Drawl 1 commitBrightness sdr:  401.552, headroom:  1, ambient:  370.814, filtered ambient:  1164.95, limit:  401.552
corebrightnessd PCC:  Situation PCC:  Part:=1.7464 CabalFactor:=0.0498 time=2.000000 Lux:=1164.9467 Nits:=401.5517 consequence=1 error=(null)
WindowServer    Drawl 1 atmosphere nits to 401.552
WindowServer    Drawl 1 commitBrightness sdr:  402.219, headroom:  1, ambient:  370.814, filtered ambient:  1164.95, limit:  402.219
WindowServer    Drawl 1 atmosphere nits to 402.219
WindowServer    Drawl 1 commitBrightness sdr:  402.885, headroom:  1, ambient:  370.814, filtered ambient:  1164.95, limit:  402.885
WindowServer    Drawl 1 atmosphere nits to 402.885
... heaps of same logs ...
WindowServer    Drawl 1 atmosphere nits to 495.458
WindowServer    Drawl 1 commitBrightness sdr:  496.125, headroom:  1, ambient:  810.176, filtered ambient:  2545.24, limit:  496.125
WindowServer    Drawl 1 atmosphere nits to 496.125
WindowServer    Drawl 1 commitBrightness sdr:  496.791, headroom:  1, ambient:  810.176, filtered ambient:  2545.24, limit:  496.792
WindowServer    Drawl 1 atmosphere nits to 496.791
WindowServer    Drawl 1 commitBrightness sdr:  497.458, headroom:  1, ambient:  810.176, filtered ambient:  2545.24, limit:  497.458
WindowServer    Drawl 1 atmosphere nits to 497.458
WindowServer    Drawl 1 commitBrightness sdr:  498.125, headroom:  1, ambient:  810.176, filtered ambient:  2545.24, limit:  498.125
WindowServer    Drawl 1 atmosphere nits to 498.125
WindowServer    Drawl 1 commitBrightness sdr:  498.791, headroom:  1, ambient:  810.176, filtered ambient:  2545.24, limit:  498.792
WindowServer    Drawl 1 atmosphere nits to 498.791
WindowServer    Drawl 1 commitBrightness sdr:  499.458, headroom:  1, ambient:  810.176, filtered ambient:  2545.24, limit:  499.458
WindowServer    Drawl 1 atmosphere nits to 499.458
WindowServer    Drawl 1 commitBrightness sdr:  500, headroom:  1, ambient:  810.176, filtered ambient:  2545.24, limit:  500
WindowServer    Drawl 1 atmosphere nits to 500
WindowServer    Drawl 1 commitBrightness sdr:  500, headroom:  -1, ambient:  987.858, filtered ambient:  3103.45, limit:  -1

# Dissecting the scheme

Since Mountainous Sur, macOS transitioned from having the frameworks on the disk as separate binaries, to having a single file containing your entire scheme libraries, called a dyld_shared_cache.

  • Fresh in macOS Mountainous Sur 11.0.1, the scheme ships with a built-in dynamic linker cache of all scheme-supplied libraries. As part of this switch, copies of dynamic libraries are now not present on the filesystem. Code that makes an strive to accept as true with a look at for dynamic library presence by shopping for a file at a course or enumerating a directory will fail. As an different, evaluate for library presence by making an strive to dlopen() the path, which might precisely evaluate for the library in the cache. (62986286)

Browsing for keywords from the above logs surfaced most optimistic the dyld cache as expected. searching for nits in system

I weak dyld-shared-cache-extractor to fall the separate binaries on disk, then did one other search there.

This surfaced up QuartzCore because the single space the achieve that string might well well be realized. searching for nits in extracted dyld cache


# Making an strive to abuse QuartzCore

After taking a learn about by the QuartzCore binary with Ghidra and finding some iOS headers for it on limneos.ranking, I created a pattern Swift project to are trying to utilize a pair of of the exported capabilities from it: monitorpanel – principal.swift

Essentially based completely on some launch-sourced iOS jailbreak tweaks, I seen that builders weak the CAWindowServer class to interface with the cloak and HID parts right this moment. The class became accessible right here so I tried to invent the identical on macOS.

Unfortunately, CAWindowServer.serverIfRunning always returns nil and while CAWindowServer.server(withOptions: nil) returns a reputedly tremendous server, all exterior displays are forcefully disconnected when that server is created.

The use of the below code, I succeeded in producing the commitBrightness log line in Console, but nothing in reality changed.

code from principal.swift

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func setToMax(_ d:  CAWindowServerDisplay) {
    d.setBrightnessLimit(1600)
    d.setHeadroom(1)
    d.maximumBrightness = 1000.0
    d.setSDRBrightness(600)
    d.maximumHDRLuminance = 1600
    d.maximumReferenceLuminance = 1600
    d.maximumSDRLuminance = 1000
    d.distinction = 1.1
    d.commitBrightness(1)
    // d.change() // segfault
}

let ws:  CAWindowServer? = (CAWindowServer.server(withOptions:  nil) as? CAWindowServer) // disconnects exterior displays
if let ws = ws,
   let displays = ws.displays as? [CAWindowServerDisplay],
   let d = displays.first(the achieve:  { $0.deviceName == "principal" })
{
    setToMax(d)
}

commitBrightness log line

1
monitorpanel    Drawl 1 commitBrightness sdr:  600, headroom:  1, ambient:  -1, filtered ambient:  -1, limit:  1600

# CoreBrightness

While taking a learn about by Ghidra, I seen that QuartzCore in the end calls into CoreBrightness capabilities to boost the nits limit, so I took a learn about on the exported symbols on that binary.

Unfortunately, your entire perhaps righteous symbols are now not exported and making an strive to link against them would consequence in the undefined symbols error.

Adding the personal symbols in the CoreBrightness.tbd file doesn’t abet on this case.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
// Dull Exported Symbols

_OBJC_CLASS_$_BrightnessSystem
_OBJC_CLASS_$_BrightnessSystemClient
_OBJC_CLASS_$_BrightnessSystemClientInternal
_OBJC_CLASS_$_CBAdaptationClient
_OBJC_CLASS_$_CBBlueLightClient
_OBJC_CLASS_$_CBClient
_OBJC_CLASS_$_CBKeyboardPreferencesManager
_OBJC_CLASS_$_CBTrueToneClient
_OBJC_CLASS_$_DisplayServicesClient
_OBJC_CLASS_$_KeyboardBrightnessClient


// Attention-grabbing No longer Exported Symbols

-[CBBrightnessProxySKL brightnessNotificationRequestEDR]
-[CBBrightnessProxySKL brightnessRequestEDRHeadroom]
-[CBBrightnessProxySKL brightnessRequestRampDuration]
-[CBBrightnessProxySKL commitBrightness:]
-[CBBrightnessProxySKL initWithSLSBrightnessControl:]
-[CBBrightnessProxySKL setAmbient:]
-[CBBrightnessProxySKL setBrightnessLimit:]
-[CBBrightnessProxySKL setHeadroom:]
-[CBBrightnessProxySKL setNotificationQueue:]
-[CBBrightnessProxySKL setPotentialHeadroom:]
-[CBBrightnessProxySKL setSDRBrightness:]
-[CBBrightnessProxySKL setWhitePoint:rampDuration:error:]
-[CBBrightnessProxySKL unregisterNotificationBlocks]
-[CBDisplayModuleSKL configureEDRSecPerStop]
-[CBDisplayModuleSKL configurePCCDefaults]
-[CBDisplayModuleSKL getBrightnessLimit]
-[CBDisplayModuleSKL getDynamicSliderAdjustedNits:]
-[CBDisplayModuleSKL getDynamicSliderAdjustedSDRNits]
-[CBDisplayModuleSKL getLinearBrightnessForNits:]
-[CBDisplayModuleSKL getLinearBrightness]
-[CBDisplayModuleSKL getMaxNitsAdjusted]
-[CBDisplayModuleSKL getMaxNitsEDR]
-[CBDisplayModuleSKL getMaxPanelNits]
-[CBDisplayModuleSKL getNitsForLinearBrightness:]
-[CBDisplayModuleSKL getNitsForUserBrightness:]
-[CBDisplayModuleSKL getPerceptualBrightness]
-[CBDisplayModuleSKL getSDRBrightnessCurrent]
-[CBDisplayModuleSKL getSDRBrightnessTarget:]
-[CBDisplayModuleSKL getSDRNitsCapped]
-[CBDisplayModuleSKL getUserBrightnessForNits:]
-[CBDisplayModuleSKL getUserBrightnessSloperExtended]
-[CBDisplayModuleSKL getUserBrightness]
-[CBDisplayModuleSKL handleBrightnessCapOverride:]
-[CBDisplayModuleSKL initialiseEDR]
-[CBDisplayModuleSKL initialiseSDR]
-[CBDisplayModuleSKL luminanceToPerceptual:]
-[CBDisplayModuleSKL panelMaxNitsOverride:]
-[CBDisplayModuleSKL perceptualToLuminance:]
-[CBDisplayModuleSKL rampDynamicSlider:withLength:]
-[CBDisplayModuleSKL rampEDRHedroom:withLength:]
-[CBDisplayModuleSKL rampFactor:withLength:]
-[CBDisplayModuleSKL rampManagerUpdateHandling]
-[CBDisplayModuleSKL rampNitsCap:]
-[CBDisplayModuleSKL rampSDRBrightness:withLength:properties:]
-[CBDisplayModuleSKL requestEDRHeadroomImmediate:]
-[CBDisplayModuleSKL requestEDRHeadroomTransition:withLength:]
-[CBDisplayModuleSKL requestEDRHeadroomTransitionStop]
-[CBDisplayModuleSKL requestFactorImmediate:]
-[CBDisplayModuleSKL requestFactorTransition:withLength:]
-[CBDisplayModuleSKL requestFactorTransitionStop]
-[CBDisplayModuleSKL requestSDRBrightnessTransition:]
-[CBDisplayModuleSKL requestSDRBrightnessTransition:withLength:properties:]
-[CBDisplayModuleSKL requestSDRBrightnessTransitionStop]
-[CBDisplayModuleSKL supportsDynamicSlider]
-[CBDisplayModuleSKL supportsEDR]
-[CBDisplayModuleSKL supportsSDRBrightness]
-[CBDisplayModuleSKL updateAmbient]
-[CBDisplayModuleSKL updateAutoBrightnessState:]
-[CBDisplayModuleSKL updateBrightnessState]
-[CBDisplayModuleSKL updateContrastEnhancerState:]
-[CBDisplayModuleSKL updateDynamicSliderAmbient]
-[CBDisplayModuleSKL updateDynamicSliderAutoBrightness]
-[CBDisplayModuleSKL updateDynamicSliderChargerState]
-[CBDisplayModuleSKL updateDynamicSliderScaler:]
-[CBDisplayModuleSKL updateEDRAmbient]
-[CBDisplayModuleSKL updateSDRBrightness:]
-[CBDisplayModuleSKL updateSDRNits:]
-[CBEDR appliedCompensation]
-[CBEDR availableHeadroom]
-[CBEDR brightnessCap]
-[CBEDR cappedHeadroomFromUncapped:]
-[CBEDR copyStatusInfo]
-[CBEDR description]
-[CBEDR initWithRampPolicy:potentialHeadroom:andReferenceHeadroom:]
-[CBEDR maxHeadroom]
-[CBEDR panelMax]
-[CBEDR referenceHeadroom]
-[CBEDR sanityCheck]
-[CBEDR sdrBrightness]
-[CBEDR secondsPerStop]
-[CBEDR setAppliedCompensation:]
-[CBEDR setBrightnessCap:]
-[CBEDR setPanelMax:]
-[CBEDR setSdrBrightness:]
-[CBEDR setSecondsPerStop:]
-[CBEDR shouldUpdateEDRForRequestedHeadroom:targetHeadroom:rampTime:]
-[CBEDR stopsFromHeadroomRatio:]
-[CBNVRAM backlightNitsDefault]
-[CBNVRAM backlightNitsMax]
-[CBNVRAM backlightNitsMin]
-[CBNVRAM dealloc]
-[CBNVRAM init]
-[CBNVRAM readBacklightNits]
-[CBNVRAM setBacklightNitsMax:]
-[CBNVRAM writeBacklightNits:]

# SkyLight

I knew from earlier work on window administration that the SkyLight framework is carefully linked to the WindowServer so I took a learn about at that too.

SkyLight exports slightly a pair of symbols, and happily I had a edifying example on the fashion to utilize them inner yabai, a macOS window supervisor equivalent to i3 and bspwm.

But again, nothing righteous is exported. Searching for nits in SkyLight

The characteristic kSLSBrightnessRequestEDRHeadroom seemed promising but I always received a SIGBUS when making an strive to name it. I will’t fetch its implementation so I don’t know what parameters I must pass. I fair guessed the main one might well well be a cloak ID.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@import Darwin;
@import Foundation;

// clang -fmodules -F/Arrangement/Library/PrivateFrameworks -framework SkyLight -o headroom headroom.m && ./headroom

extern int SLSMainConnectionID(void);
extern CFTypeRef SLSDisplayGetCurrentHeadroom(int did);
extern void kSLSBrightnessRequestEDRHeadroom(int did, CFTypeRef headroom);

const int MAIN_DISPLAY_ID = 1;

int principal(int argc, char argv)
{
    int cid = SLSMainConnectionID();
    NSLog(@"SLSMainConnectionID: %d", cid);

    CFTypeRef headroom = SLSDisplayGetCurrentHeadroom(MAIN_DISPLAY_ID);
    kSLSBrightnessRequestEDRHeadroom(MAIN_DISPLAY_ID, headroom);

    return 0;
}

# Diversified tips

# Streaming to a dummy

While discussing this topic with István Tóth, the developer of BetterDummy, he got right here up with a mesmerizing thought.

  1. Make a CGVirtualDisplay with the identical size because the built-in cloak
  2. Tone plan the SDR contents of the built-in cloak to 1000nits HDR video
  3. CGDisplayStream that video to the virtual cloak
  4. Scoot the virtual cloak to the built-in cloak coordinates and use that because the main cloak

The streaming part already works in the most fresh Beta of BetterDummy and looks quite quickly as effectively. But adding tone mapping might well well trigger this to be too resource intensive to be weak.

# The use of personal symbols

I ponder linking might well well be completed against private symbols utilizing memory offsets, I be mindful doing one thing like that 8 years previously at BitDefender, while making an strive to utilize the unexported _decrypt and _generate_domain suggestions of some DGA malware.

But the dyld_shared_cache mannequin of macOS is one thing new to me and I don’t accept as true with enough details to be in a hassle to invent that fair now.

If anyone has any thought how this might well well be accomplished, I’d feel free must that that it is possible you’ll well perhaps ship me a marginally by the Contact page.

Posted on:
February 4, 2022
Length:
10 minute learn, 2018 phrases
Classes:
macOS reverse engineering
Tags:
macbook macbook skilled nits nits limit hdr most brightness lunar
Peep Moreover:
Why don’t seem to be the most righteous Mac apps on the App Store?
The streak to controlling exterior screens on M1 Macs