What does NVRAM lock/unlock actually mean
So, recently I’ve realized that meaning of “lock/unlock” in context of nvram on iOS is not understood correctly by many, so I’ve decieded to make a quick blog post on meaning of those words.
Let’s start with some context: NVRAM stands for Non-volatile random-access memory
On iOS, nvram is mostly used to preserve some temp data between reboots. For example, it’s used to pass info to the bootloader, or to share some data with the restore boot chain.
One thing passed through nvram to restore bootchain is particularly interesting – commonly referred to as “nonce” or even “apnonce”.
Actually, the value passed is the “generator”. When iBoot finds it in the nvram, it generates nonce from it (the NDF – nonce derivation function is SHA1 on older devices and SHA2-384 on newer ones)
Now a bit of history. Disclaimer: I might be wrong here and there. If you’re interested in more detailed info, see tihmstar’s Blog and 33c3 talk (slides).
Introduction of these nonces was Apple’s responce to replay attack on the restore process. Originally, you could’ve just saved “shsh blobs” (fancy name for device “personalized” firmware signature). Those didn’t have any protection of being reused.
So, Apple did quite obvious thing – they’ve added a nonce into the process. Everything should be good now: the iBoot generates a nonce with a cryptographically secure (P)RNG, that tells it to iTunes, iTunes requests blobs with that nonce, everything is good.
But there’s a small problem: OTA updates. You can’t ask Apple servers for a signature over the internet from within the iBoot, and you can’t require iTunes for OTA. So, Apple has added a generator – a value which can be passed by iOS to restore chain:
- Userspace sends a request to kernel
- Kernel generates a random number
- Kernel generates a nonce from it
- Userspace requests blobs from TSS server with that nonce
- blobs and generator get passed to the restore chain
- iBoot generates nonce from generator, and restore chain verifies it
Blobs later get saved as apticket and are verified on boot. However, the NDF is practically irreversible, and the generator is not preserved anywhere: so, you can’t just grab apticket from existing iOS install and use that for restore, unless you know the generator used to get the nonce it it.
Oh, and there’s one another small problem. Remember how I’ve said “cryptographically secure”? That was not the case for quite some time – PRNG in restore chain was bad and nonce collisions as downgrade technique was burned by tihmstar.
And on 64bit devices SEP messes with downgrades too.
Ok, so we want to pass custom generator to the restore chain. Normally, it’s generated and set in nvram by kernel, on request from userspace.
On *OS/macOS nvram is split into different parts used for different
purposes, and kernel provides API for it via IODTNVRAM
class – see
iokit/Kernel/IONVRAM.cpp
in xnu.
One of parts of that API is “variables” in key:value fashion, where key is a string and value is bool, number, string or raw data:
enum {
kOFVariableTypeBoolean = 1,
kOFVariableTypeNumber,
kOFVariableTypeString,
kOFVariableTypeData
};
The generator is passed via com.apple.System.boot-nonce
variable as
string (hex) representation of the number.
That API is exposed to userspace as a useragent, so, obviously, access control is needed.
When a property is set, IODTNVRAM::setProperty
gets called.
// Verify permissions.
propPerm = getOFVariablePerm(aKey);
result = IOUserClient::clientHasPrivilege(current_task(), kIONVRAMPrivilege);
if (result != kIOReturnSuccess) {
if (propPerm != kOFVariablePermUserWrite) return false;
}
if (propPerm == kOFVariablePermKernelOnly && current_task() != kernel_task) return 0;
So, getOFVariablePerm is used to check permissions, and it returns one of following values:
enum {
kOFVariablePermRootOnly = 0, // kern: rw, root: rw, user: --
kOFVariablePermUserRead, // kern: rw, root: rw, user: r-
kOFVariablePermUserWrite, // kern: rw, root: rw, user: rw
kOFVariablePermKernelOnly // kern: rw, root: --, user: --
};
getOFVariablePerm
is quite short:
UInt32 IODTNVRAM::getOFVariablePerm(const OSSymbol *propSymbol) const
{
const OFVariable *ofVar;
ofVar = gOFVariables;
while (1) {
if ((ofVar->variableName == 0) ||
propSymbol->isEqualTo(ofVar->variableName)) break;
ofVar++;
}
return ofVar->variablePerm;
}
The gOFVariables
is used for permission checking:
struct OFVariable {
const char *variableName;
UInt32 variableType;
UInt32 variablePerm;
SInt32 variableOffset;
};
typedef struct OFVariable OFVariable;
static const // Notice: on iOS 10 and below const keyword was missing
OFVariable gOFVariables[] = {
{"little-endian?", kOFVariableTypeBoolean, kOFVariablePermUserRead, 0},
{"real-mode?", kOFVariableTypeBoolean, kOFVariablePermUserRead, 1},
// more variables
#if CONFIG_EMBEDDED
{"backlight-level", kOFVariableTypeData, kOFVariablePermUserWrite, -1},
{"com.apple.System.sep.art", kOFVariableTypeData, kOFVariablePermKernelOnly, -1},
{"com.apple.System.boot-nonce", kOFVariableTypeString, kOFVariablePermKernelOnly, -1},
// more variables
#endif
{0, kOFVariableTypeData, kOFVariablePermUserRead, -1}
};
We can make three interesting observations:
- by default variables are
kOFVariablePermUserRead
- there are plenty of variables with user write permissions used by system daemons (i.e.
backlight-level
) - the variable we are interested in is kernel only
Now, assuming we have kernel rw primitives, how would we unrestrict the variable?
Most obvious technique is to patch the gOFVariables, and change permissions on the variable to root only. It’s very clean – only affects one variable, changes it to root only from kernel only, and doesn’t have any other side effects.
That technique was used up to iOS 9 – here’s a code sample
But, as I’ve already mentioned, on iOS 11 Apple has added a const
qualifier to the gOFVariables
. For those unfamiliar with apple’s
kernel building process, it means that the array would end up in
completely different area in kernel binary, and that area would be under
KPP/KTRR – being practically untouchable.
Side note: While writing this, I wanted to add a reference to this tweet. And only now have I saw this thread with 2 more workarounds.
New technique was needed. At 19.12.2018 ARX8x has reached out to me asking some questions, including question about nvram patches on iOS 11.
My initial thought was replicating setProperty with arbitrary kernel call primitive, but that didn’t work quite well. The next day looking through xnu sources, I’ve noticed something really unexpected:
class IODTNVRAM : public IOService {
// ...
virtual UInt32 getOFVariablePerm(const OSSymbol *propSymbol) const;
// ...
};
The getOFVariablePerm
method is virtual
! Thanks, Apple!
So, the solution was quite simple: create a fake vtable with
getOFVariablePerm
replaced.
The target was easy to find too: root only is defined to be 0, so replacing it with ret0 gadget would be enough. Happily, there are plenty of those in the same vtab: there are many no longer used functions in there which just return 0.
Sample code was published the next day, and less than in a day julioverne had made a nonce-setter app with it and async_wake.
(Side note: please, use sizeof when applicable – see revisions on that gist)
But this approach has a caveat. Remember second observation, one about existence of user writable variables? This patch (aka “unlock”) makes ALL variables root only – and stuff does break because of it.
Because of that restoring original IODTNVRAM vtable is necessary after setting the nonce – “lock” nvram back.
I’ve also made a command line tool/daemon for that, which is worth looking into as a reference – noncereboot11.
And finally, questions are welcome – feel free to reach out to me (for example, on Twitter).
UPD 2018.06.26: Fixed some typos (thx Sparkley) and some errors (thx tihm)