[LG G6 Questions and Answers] francians:
Hello,

I've take a look at the unlock.bin files shared here and this is what I've discovered:

1. when you upload the unlock.bin file, it must exactly be 1024 bytes, any other file size will give you back an error.

2. it's a null (00) padded file

3. the unlock file seems to always begin with this sequence of 20 bytes: 159e 8db7 d36b 2d7e 0001 0000 0002 0000 0100 0000

4. LG G6) contains 2 blocks of 256 bytes separated by 12 null bytes: 0000 0000 0000 0000 0000 0000

So the G6 structure seems to always be (in bytes):

20 (initial sequence) + 256 (first part) + 12 (null bytes) + 256 (second part) + 480 (padding)

4b. LG G5) after the same initial sequence (159e 8db7 d36b 2d7e 0001 0000 0002 0000 0100 0000) there is just one "block" of 256 bytes before the null padding.

Final thoughts:

I may guess that decrypting the file itself is impossible and probably the unlock keys must be extracted separately (could be as simple as):

$ head -c 276 unlock.bin | tail -c 256 > key1.bin

and (only on G6):

$ tail -c 736 unlock.bin | head -c 256 > key2.bin

If someone could share more binary files downloaded from the LG website (also for other devices) it may be useful to do more guessing about the way this files was built.

--EDIT 1

I found a post with a link to this repository:

lk/platform/lge_shared at master · jaehyek/lk

Contribute to jaehyek/lk development by creating an account on GitHub.

github.com

This made me figure how the previous models (including G5?) unlock.bin file was generated and read.

According to lge_verified_boot.c, the input structure (unlock_input_data_type) is obtained by concatenating device_id and imei taken from the phone.

All what validation (verify_image) does is comparing sha256 of it with the decoded part of unlock.bin content.

So, unlock.bin's "key1" should be obtained with: encrypt( sha256( concat( device_id, imei ) ) )

The good news is that the repository includes a "keys" folder with all the keystores used... The bad one is that I haven't found the "d2i_LGE_KEYSTORE" function that knows how to read them.

I tried again with the strategy of comparing files and discovered that there are some recurring patterns in keystores:

they seems to start with: 0x30, 0x82, 0x01

then contains some bits that identify the keystore, then:

0x30, 0x82, 0x01, 0x1f, 0x30, 0x82, 0x01, 0x1b, 0x30, 0x0b, 0x06, 0x09,

0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x30, 0x82, 0x01,

0x0a, 0x02, 0x82, 0x01, 0x01, 0x00

finally, sequence ends with: 0x02, 0x03, 0x01, 0x00, 0x01.

I will write here again if I can find the public key, in order to decode the posted unlock.bin files and to collect feedback of whom posted them without imeis and device ids.

I just may need the sha256 sums of the 2 concatenated strings.

--EDIT 2

After writing a simple C program to print the Keystore inside bl_unlock.c (BLUNLOCK_KEYSTORE variable), I obtained a binary file that can be read using:

$ openssl asn1parse -in keystore -inform DER -i

0:d=0 hl=4 l= 309 cons: SEQUENCE

4:d=1 hl=2 l= 1 prim: INTEGER :00

7:d=1 hl=2 l= 13 prim: PRINTABLESTRING :UNLOCK_RSA_02

22:d=1 hl=4 l= 287 cons: SEQUENCE

26:d=2 hl=4 l= 283 cons: SEQUENCE

30:d=3 hl=2 l= 11 cons: SEQUENCE

32:d=4 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption

43:d=3 hl=4 l= 266 cons: SEQUENCE

47:d=4 hl=4 l= 257 prim: INTEGER :92D5E3A2C6F311A1FD325C94415DF197BA1C2B307C1EDBB5D9ED109AB8A639F10D61F6582B367AAECBBF95A130E54754667565AA6A60AFA6ADD6F246048C839D017D21849F5AA2A08FA5DBDE0712694E7E80FC42A709CEE3A91A5B11873A079E0FB04E14001C6B5BB4A5DDF52E793A8D5DD4E177C560C7CEDFB8FCC844B6640B4813D629AB9B34CFD5081C5A5384225049FE0B62EFBF79728421360D3C874B387CE5B67891F8942DB431BEAAC7414ED52AFA283EA39EAD160DA51FD7F8393BAF6BBCA780DFAC477BFEF43C61E9431B85F70E4DB2CF7B0F7A410DB24D6F806C91A2C650897E9B90668D25B0A9174054E133B4382ADA5AEC3527D2F4CABE263917

308:d=4 hl=2 l= 3 prim: INTEGER :010001

--EDIT 3

First of all, I want to say that I've downloaded an OTA update of LG G6 and this can probably confirm that this keystore is still there:

LG-H87010f-Flashable.Bootloader.zip

extract and:

$ grep "UNLOCK_RSA_02" -R

grep: bootloader/aboot.img: Binary file matches

so I invested more time on it figuring how the keystore originated and found this source code of KeystoreSigner that produce the same DER sequence:

verity/KeystoreSigner.java - platform/system/extras - Git at Google

so I wrote a small Java program to print the public key in PEM format:

-----BEGIN PUBLIC KEY-----

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAktXjosbzEaH9MlyUQV3xl7ocKzB8Htu

12e0QmrimOfENYfZYKzZ6rsu/laEw5UdUZnVlqmpgr6at1vJGBIyDnQF9IYSfWqKgj6Xb3gcSaU

5+gPxCpwnO46kaWxGHOgeeD7BOFAAca1u0pd31Lnk6jV3U4XfFYMfO37j8yES2ZAtIE9Ypq5s0z

9UIHFpThCJQSf4LYu+/eXKEITYNPIdLOHzltniR+JQttDG+qsdBTtUq+ig+o56tFg2lH9f4OTuv

a7yngN+sR3v+9Dxh6UMbhfcOTbLPew96QQ2yTW+AbJGixlCJfpuQZo0lsKkXQFThM7Q4Ktpa7DU

n0vTKviY5FwIDAQAB

-----END PUBLIC KEY-----

It is a 2048-bit RSA public key, that I'm still not able to use to read the unlock files posted yet, but I share all my work just in case anybody wants to help.

-- EDIT 4

This is how I'm trying to use all the pieces I've put together. It's working now!!!

Now we have a working method to validate unlock.bin files for older phones!!!

--EDIT 5

Updated code with the working version.

-- EDIT 6

With a big thank to @ncrt that figured how the second signature is generated we now know how to completely validate the unlock.bin of G6.

This is the final version of the Java validator:

import java.io.File;
import java.math.BigInteger;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.RSAPublicKeySpec;

class Main {
private static final int UNLOCK_BIN_SIZE = 1024;
private static final int UINT32_T_SIZE = 4;
private static final int SIGNATURE_SIZE = 512;
private static final int KEY_SIZE = 256;
private static final int EXTRA_SIZE = 492;
private static final long SECURITY_UNLOCK_MAGIC1 = 2377586078L; // 0x8DB7159E
private static final long SECURITY_UNLOCK_MAGIC2 = 763286379L; // 0x2D7ED36B
private static final long SECURITY_UNLOCK_VERSION = 1L;
private static final int IMEI_SIZE = 32;
private static final int DEVICE_ID_SIZE = 96;
// RSA_UNLOCK_02
private static final RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(
"18536265221834400955526124823946945144241534366405270883862606828214326557303158761374427696439760867810300046710668389940627901357786930619155280232713255180467267693281615312585736047834931276426122242381388755141769507773314618374615964530031495500324126445550145922318729183762394336526893965841523887301431217744349619177044755418369600023019646764547203434859153096499560007159303235140562773302106895748271986503337696246115511449909141742149128001718847058167094531480513164043443149146227140700654562659385941009377485565173992175722386093166833729231966326215327030617445434971297334403421561820089441204503"),
new BigInteger("65537"));

public static void main(String[] args) throws Exception {
String imei = "356144087429995";
String deviceId = "662CDCF3D09A5AED38E08DB652EC4CC6F63B24DADB2332BC0C7CD30A9924D731";
byte[] fileContent = Files.readAllBytes(new File("unlock.bin").toPath());

if (fileContent.length != UNLOCK_BIN_SIZE) {
System.err.println("Filecontent: " + fileContent.length + " expected: " + UNLOCK_BIN_SIZE);
return;
}

int offset = 0;

byte[] magic1 = new byte[UINT32_T_SIZE];
System.arraycopy(fileContent, offset, magic1, 0, UINT32_T_SIZE);

offset += UINT32_T_SIZE;

byte[] magic2 = new byte[UINT32_T_SIZE];
System.arraycopy(fileContent, offset, magic2, 0, UINT32_T_SIZE);

offset += UINT32_T_SIZE;

byte[] version = new byte[UINT32_T_SIZE];
System.arraycopy(fileContent, offset, version, 0, UINT32_T_SIZE);

offset += UINT32_T_SIZE;

byte[] hash_type = new byte[UINT32_T_SIZE];
System.arraycopy(fileContent, offset, hash_type, 0, UINT32_T_SIZE);

offset += UINT32_T_SIZE;

byte[] key_size = new byte[UINT32_T_SIZE];
System.arraycopy(fileContent, offset, key_size, 0, UINT32_T_SIZE);

offset += UINT32_T_SIZE;

if (deserialize_uint32(magic1) != SECURITY_UNLOCK_MAGIC1 || deserialize_uint32(magic2) != SECURITY_UNLOCK_MAGIC2
|| deserialize_uint32(version) != SECURITY_UNLOCK_VERSION) {
System.err.println("Magic numbers not found");
return;
}

byte[] signature = new byte[SIGNATURE_SIZE];
System.arraycopy(fileContent, offset, signature, 0, SIGNATURE_SIZE);

offset += SIGNATURE_SIZE;

byte[] extra = new byte[EXTRA_SIZE];
System.arraycopy(fileContent, offset, extra, 0, EXTRA_SIZE);

offset += EXTRA_SIZE;

byte[] input = new byte[DEVICE_ID_SIZE + IMEI_SIZE];
System.arraycopy(deviceId.getBytes(), 0, input, 0, deviceId.length());
System.arraycopy(imei.getBytes(), 0, input, DEVICE_ID_SIZE, imei.length());

final KeyFactory f = KeyFactory.getInstance("RSA");
final PublicKey publicKey = f.generatePublic(spec);

byte[] firstSignature = new byte[KEY_SIZE];
System.arraycopy(signature, 0, firstSignature, 0, KEY_SIZE);

Signature firstSignatureVerify = Signature.getInstance("NonewithRSA");
firstSignatureVerify.initVerify(publicKey);
firstSignatureVerify.update(MessageDigest.getInstance("SHA-256").digest(input));
boolean sigVerified = firstSignatureVerify.verify(firstSignature);
System.out.println("First signature verified: " + sigVerified);

byte[] secondSignature = new byte[KEY_SIZE];
System.arraycopy(signature, KEY_SIZE + 12, secondSignature, 0, KEY_SIZE - 12);
System.arraycopy(extra, 0, secondSignature, KEY_SIZE - 12, 12);

Signature secondSignatureVerify = Signature.getInstance("SHA256withRSA");
secondSignatureVerify.initVerify(publicKey);
secondSignatureVerify.update(input);
boolean sig2Verified = secondSignatureVerify.verify(secondSignature);
System.out.println("Second signature verified: " + sig2Verified);
}

private static long deserialize_uint32(byte[] b) {
long l = (long) b[0] & 0xFF;
l += ((long) b[1] & 0xFF) << 8;
l += ((long) b[2] & 0xFF) << 16;
l += ((long) b[3] & 0xFF) << 24;
return l;
}
}

Cheers

Francians