Getting ADB Access

Getting ADB access requires patching live memory. Even after getting ADB there isn’t much you can do without further bypasses, see Getting higher access and extracting memory.

Connecting to ADB

There is a micro-USB port on the mirror. (top if it’s hanging). So hooked up to it and to see what it is.

>lusb
Bus 001 Device 064: ID 18d1:4ee7 Google Inc. Nexus/Pixel Device (charging + debug)

Oh it’s an Android device, let’s try ADB.

> adb devices -l
List of devices attached
[redact] unauthorized 1-1 transport_id:4

What is needed is the private ADB key. I tried keys from NXP Android source but couldn’t find any that worked. So we need to bypass it.

How ADB authentication works

ADB has two pieces to it: 1. the daemon that runs on the host you are trying to connect to in this case the mirror 2. the client software which you use to connect

When authenticating the client sends over a private key that the server than uses to see if verifies the private key it has stored. Typically the server keeps the keys in /adb_keys or /data/misc/adb/adb_keys. One thing is even ex-filtrating these keys would be useless since these are the public keys and we would need to get the private keys.

In ADB source auth.cpp we want to patch adbd_auth_verify().

bool adbd_auth_verify(const char* token, size_t token_size, const std::string& sig,
                      std::string* auth_key) {
    static constexpr const char* key_paths[] = { "/adb_keys", nullptr };
    LOG(INFO)<<"adbd_auth_verify()";
    LOG(INFO)<<key_paths[0]<<"; "<<key_paths[1];

    for (const auto& path : key_paths) {
        //LOG(INFO)<<"Attempting access to: "<<path;
        if (access(path, R_OK) == 0) {
            LOG(INFO) << "Loading keys from " << path;
            std::string content;
            if (!android::base::ReadFileToString(path, &content)) {
                LOG(INFO) << "Couldn't read " << path;
                continue;
            }
            LOG(INFO)<<"Key is: "<<content;
            for (const auto& line : android::base::Split(content, "\n")) {
                if (line.empty()) continue;
                *auth_key = line;
                // TODO: do we really have to support both ' ' and '\t'?
                char* sep = strpbrk(const_cast<char*>(line.c_str()), " \t");
                if (sep) *sep = '\0';

                // b64_pton requires one additional byte in the target buffer for
                // decoding to succeed. See http://b/28035006 for details.
                uint8_t keybuf[ANDROID_PUBKEY_ENCODED_SIZE + 1];
                if (b64_pton(line.c_str(), keybuf, sizeof(keybuf)) != ANDROID_PUBKEY_ENCODED_SIZE) {
                    LOG(INFO) << "Invalid base64 key " << line.c_str() << " in " << path;
                    continue;
                }

                RSA* key = nullptr;
                if (!android_pubkey_decode(keybuf, ANDROID_PUBKEY_ENCODED_SIZE, &key)) {
                    LOG(INFO) << "Failed to parse key " << line.c_str() << " in " << path;
                    continue;
                }

                bool verified =
                    (RSA_verify(NID_sha1, reinterpret_cast<const uint8_t*>(token), token_size,
                                reinterpret_cast<const uint8_t*>(sig.c_str()), sig.size(),
                                key) == 1);
                RSA_free(key);
                if (verified) return true;
            }
        }
        else{
            LOG(INFO)<<"Failedt to access path";
        }

    }
    auth_key->clear();
    return false;   <<<----GOAL CHANGE THIS LINE TO RETURN TRUE
}

We want this function to always return true.

We can compile for ARMv8:

cd /android_build/system/core/adb/daemon
source ~/aosp/build/envsetup.sh
cd ~/aosp/system/core/adb
mm 

After compiling adbd_auth_verify ends up in libadbd.so so that is what we need to patch. We can disassemble it with

aarch64-linux-gnu-objdump -D libadbd.so

Snippet

   3c0a4:   f9400a68    ldr x8, [x19, #16]
   3c0a8:   2a1f03e0    mov w0, wzr  ≪CHANGE THIS LINE
   3c0ac:   3900011f    strb    wzr, [x8]
   3c0b0:   f900067f    str xzr, [x19, #8]

   3c0b4:   14000002    b   3c0bc ≪<CHANGE THIS LINE <_Z16adbd_auth_verifyPKcmRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEPS7_@@Base+0x5b4>
   3c0b8:   320003e0    orr w0, wzr, #0x1
   3c0bc:   f9400be8    ldr x8, [sp, #16]
   3c0c0:   f9401508    ldr x8, [x8, #40]
   3c0c4:   f85a03a9    ldur    x9, [x29, #-96]
   3c0c8:   eb09011f    cmp x8, x9
   3c0cc:   54000421    b.ne    3c150 <_Z16adbd_auth_verifyPKcmRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEPS7_@@Base+0x648>  // b.any
   3c0d0:   910a43ff    add sp, sp, #0x290
   3c0d4:   a9457bfd    ldp x29, x30, [sp, #80]
   3c0d8:   a9444ff4    ldp x20, x19, [sp, #64]
   3c0dc:   a94357f6    ldp x22, x21, [sp, #48]
   3c0e0:   a9425ff8    ldp x24, x23, [sp, #32]
   3c0e4:   a94167fa    ldp x26, x25, [sp, #16]
   3c0e8:   a8c66ffc    ldp x28, x27, [sp], #96
   3c0ec:   d65f03c0    ret

The instruction orr w0, wzr #0x1 is what sets the return address to true, but the instruction line 3c0b4: 14000002 b 3c0bc jumps over it. We want to change b 3c0bc to not jump over the next instruction. The opcode for b 3c0bc is 0x14000002. This is what we would be looking for in memory to overwrite. However opcode 0x14000002 is a b jump to a small offset, not an absolute address. So there a lot of these in the code. So if we looked for that in memory we would not be able to target the memory overwrite. We need to find something more specific. So let’s grab the instruction before and after f900067f and 14000002 and 320003e0 and look for that.

Search looks like this:

$ xxd -c0 -p libadbd.so|grep -b -o 02000014e0030032e80b40f9 
491880:02000014e0030032e80b40f9  

This means at byte 491880 we see this opcode pattern, the key is we only see one of these that means we can target the patch.

Side note of what’s happening here:
xxd is a utility to dump a file as hex.
-c0: changes the number of columns per line. ” With -ps, 0 results in one long line of output.”
-p: outputs as continuous line
We want one continuous line so that grep doesn’t get tripped up by line breaks.
grep
-b : prints the byte offset, so we can see where in the file things are
-o: prints only matching

Dumping memory

So now we need to figure out is where this opcode lives in memory so we can overwrite it. To do that we need to dump the memory using JTAG.

Connect to JTAG If OpenOCD isn’t running run it:

openocd -f tigard-jtag.cfg -f mirror-jtag.cfg

connect to it

telenet 127.0.0.1 4444 

Dump memory

dump_image mem.dump 0x80000000 100

What we need to define is the starting address 0x40000000 and how much memory to read 0x1000000. Why 0x40000000? If we look at the manual for the iMX8MM chipset we can see that DDR memory starts at 0x4000_0000 and we want to catch as much of it as possible.

DDR Memory

Looking through memory If we have the whole image we can look to where our patch needs to land. The way we do this is 1. We look for adbd_auth_verify 2. We look for our opcode nearby 02000014e0030032e80f40f9

Looking for adbd_auth_verify

tail -f mem.dump|grep -abo --line-buffered "adbd_auth_verify" 

and we get something like

876162921:adbd_auth_verify
881087407:adbd_auth_verify

Note this output will change with every boot. The ADB code is loaded into memory somewhat randomly so we have to rerun this each time. This also take a looooong time like hours to run. I would leave it overnight frequently.

Looking for the opcode. What we do is take the address where we found adbd_auth_verify string and look back before it.

xxd -c0 -p -s $((876162921-1000000))  mem.dump| grep -bo 02000014e0030032e80f40f9 

output:

6452726:02000014e0030032e80f40f9
22250326:02000014e0030032e80f40f9

So we get two possible patches, we can try patching both.

Figure out where the actual opcode lives in memory But now we need to see where this actually live in memory. We know we started memory read from 0x4000000 but our grep output doesn’t know that.

printf "0x%x\n" $(( (0x40000000 + 876162921 - 1000000 + 22250326/2)))

printf "0x%x\n" $(( 123 )) is a quick way to do some numeric operation and print it out as hex.

We take 876162921 - 1000000 where we started looking for the opcode. 876162921 is where we saw “adbd_auth_verify” string and 1000000 is how far back from that string we start the seach. 22250326/2 is the result of the opcode search but we divide by two because grep is counting charterers but we want bytes e.g. FF is 2 characters but just one byte. Finally we add 0x40000000 because that is where in RAM we started the search. Our final address to patch is: 0x74d3aed4

Patching live memory

We patch via OpenOCD.

Validate the memory In OpenOCD session

> mdw 0x74d3aed4 8
0x74d3aed4: 14000002 320003e0 f9400fe8 f9401508 f85a03a9 eb09011f 54000161 910a83ff 

mdw: Memory display word, shows a word, you supply the address where to start from and how many words.

We can see that it matches what we want to see 14000002 which our opcode we were looking for.

Modify the memory

> mww 0x74d3aed4 0x1f2003d5

mww: Memory write word, writes a word to memory, you supply the starting address and data to write. In our case we are overwriting 14000002 with 0x1f2003d5

and let’s check that it wrote.

> mdw 0x74d3aed4 8         
0x74d3aed4: 1f2003d5 320003e0 f9400fe8 f9401508 f85a03a9 eb09011f 54000161 910a83ff

Viola connect to ADB Now we connect to adb and have access. adb connect

but unfortunately things are pretty locked down and adb doesn’t give us all that much. See: Getting higher access and extracting memory.