Pwnage 2.0

Revision as of 00:23, 10 December 2021 by Q3k (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The Pwnage 2.0 bug/exploit targets and is present in early S5L8xxx bootroms: both iOS-world SecureROM and iPod-world bootroms.


This information is based on the reverse-engineering of the iPod Nano 4G S5L8720 Bootrom. All function/structure names present are pulled out of thin air.

After performing header validation (SHA1 & AES), the bootrom parses the certificates present at the footer of an uploaded IMG1. These certificates are plain DER-encoded ASN.1 X.509 certificates, and thus their parsing logic is quite complex. For reference, see RFC5280.


The WTF (What's That Firmware?) payload is the typical payload executed over DFU by the bootrom. WTF files for your device can be downloaded from Phobos, which hosts binaries for many of Apple's devices. There is a nice directory of Phobos downloads here. Go to the "DFU/Recovery Files" section and find the WTF for your device and download the corresponding ipsw. Extract this and find the actual WTF binary. It normally has a name like WTF.x1225.release.dfu.

ASN.1/DER Parsing

The bootrom has a few complex structures used to handle the parsing, most notably every ASN.1 type parsed (X.509 Certificate/TBSCertificate and X.509 Name). Each one of the types is parsed based on a common function (der::parse) called with a different der::step array.

A der::step looks as follows:

 struct der::step {
   uint32 asn1_tag;
   uint8 match_content_length;
   void *visitor_or_content;
   int step_depth;
   int step_breadth;
   uint flags;

With flags being a bitmap of:

 #define FLAG_VISIT_ALL 4
 #define FLAG_OPTIONAL 8

der::parse streams (linearly) every DER tag/field present in the given blob, and in parallel walks through the array of der::steps. It matches the tag from the blob against the asn1_tag field of the der::step. If they don't match (and FLAG_OPTIONAL isn't set), the whole parse fails. If they do match, the following action is taken:

  • If FLAG_CHECK_CONTENT_ONLY is set, the content of the streamed field is compared against visitor_or_content interpreted as a byte array of match_content_length bytes. If the content doesn't match, the parse fails.
  • If FLAG_VISIT_CONTENT_ONLY is set, visitor_or_content is interpreted as a function pointer and called with the content of the ASN.1 field being currently visited. If the visitor returns an error, the parse fails.
  • If FLAG_VISIT_ALL is set, visitor_or_content is interpreted as a function pointer and called with the entire ASN.1 field (including tag and length bytes). If the visitor returns an error, the parse fails.

Then, a ASN.1 field tree traversal action is performed:

  • If step_depth != -1, the current field extents and current step are pushed on an internal stack, and the inner contents of the field continue being parsed starting at der::steps[step_depth]. If the internal stack overflows, the parse fails.
  • If step_breadth != -1, the current field is skipped and the next field continues to be parsed by der::steps[step_breadth].
  • Otherwise (both step_depth == -1 and step_breadth == -1), the internal stack is 'popped' and both the DER streaming and der::step is restored to whatever was saved on the internal stack. The der::step that was pushed has its step_breadth consulted for the next step to be executed, if that is also -1 then the stack continues to be popped. The parse fails if the stack underflows.

As the parse continues through the DER byte stream and the der::steps, a structure is constructed and populated with information retrieved from the certificate by the visitors. For example, the signatureAlgorithm field is recorded, the entire TBSCertificate structure extents are recorded, etc. After the parse of the certificate is done, two more der::parse executions happen: on the issuerName and subjectName as recorded by the certificate visitors.

Certificate Parsing Bug

Most of the visitors in der::steps take care to never trust the lengths specified in the DER stream. However, one visitor (Certificate der::step[29]) is an exception - it copies over data from the expected signatureValue field in Certificate into the structure holding parsed certificate data without checking for maximum length.

Exploiting the bug

(The following applies to the Nano 4G S5L8720 Bootrom. Every bootrom will likely have slightly different offsets.)

The target structure (der::cert::parse_ctx) looks as follows:

 struct der::cert::parse_ctx {
   uint tbs_certificate_len;
   byte *tbs_certificate_data;
   uint version;
   uint algorithm_len;
   byte *algorithm_data;
   uint issuer_len;
   byte *issuer_data;
   uint subject_len;
   byte *subject_data;
   uint extension_oid_len;
   byte *extension_oid_data;
   byte extension_critical;
   der::cert::certificate *certobj;

The signatureValue is copied to ctx->certobj->signatureValue; der::cert::certificate looks as follows:

 struct der::cert::certificate {
    byte unimportant[1016]; // parsed certificate fields
    byte authorityKeyIdentifier[20];
    byte signatureValue[256];
    uint signatureValue_len;
    uint der_outer_sig_alg_type;
    byte sha1_tbs_calculated[20];
    byte sha1_all_calculated[20];
    uint unknown;

Now, certobj in der::cert::parse_ctx is a pointer. Where is the data actually held? It's in yet another object, der::chain::parse_ctx, which is the overarcching structure used to parse the entire certificate chain (three certificates):

 struct der::chain::parse_ctx {
   uint unknown[2];
   der::cert::parse_ctx current_cert;
   der::cert::certificate chain_certs[3];

For every certificate in the chain, cert::der::parse is called on der::chain::parse_ctx->current_cert, whose certobj is populated by one of chain_certs (each one after the other as the chain is parsed). Due to earlier checks, three certificates must be present in the footer for them to be parsed at all.

Finally, where is der::chain::parse_ctx stored? On the stack! In fact, directly after der::chain::parse_ctx there are 0x24 bytes of saved registers, with the last 4 bytes being the saved LR.

Thus, to mount the attack, we need to do the following:

  1. Present the BootROM with a valid image header with some certificates after the body. The body never gets to be checked or decrypted, so we can write anything we want there (as long as the sizes match the sizes in the header).
  2. Provide three certificates in the chain that match the bare minimum required by the certificate DER parse steps.
  3. Make the last certificate's signatureValue overflow into the saved LR. The original buffer is 256 bytes, we need to overflow it by 308 bytes (256 + 52) to leave the der::chain::parse_ctx structure, then by 0x20 more bytes to reach the saved LR, then provide 4 bytes of PC to override. Since the signatureValue is an ASN.1 BIT STRING, we need to prefix the tag value with a zero. This gives us in total 344 or 345 bytes to fill signatureValue with.

If we can't generate arbitrary image headers to set arbitrary footer certificate sizes we need to pad all certificates involved so that the signatureValue of the last cert is exactly the size we want to overflow (or at least not too long so that the copy doesn't cause a write to unmapped memory). Afterwards, with code exec, we can use the HW AES engine to sign arbitrary headers to not have to worry about sizes.

Crafting the certificates is an exercise left to the reader. Maybe the exact constraints and process will be listed here at some point, but starting out with certificates from a legitimate WTF file and mangling the last certificate to overflow by exactly 344 bytes is a good start (possibly adjusting previous certs to make some space for the longer signatureValue).


The easiest place to stuff the payload is in the body of the image. The bootrom never gets to checking or decrypting it, so we can easily just put some executable code there. Depending on the bootrom, the image body will be placed somewhere in the beginning of SRAM (0x22000600 for Nano4g). Then, our stack smash can simply point to that address and we get code execution.