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.
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.
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 |
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.
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
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.