Skip to content

Commit

Permalink
refactor: ShowMsgAt: find id from dex code
Browse files Browse the repository at this point in the history
  • Loading branch information
cinit committed Jul 25, 2023
1 parent 2388ad3 commit d9ef36b
Show file tree
Hide file tree
Showing 4 changed files with 357 additions and 24 deletions.
95 changes: 79 additions & 16 deletions app/src/main/java/io/github/qauxv/util/dexkit/DexFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Objects;

public class DexFlow {

private DexFlow() {
throw new AssertionError("No instances");
}

private static final byte[] OPCODE_LENGTH_TABLE = new byte[]{
1, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 1, 1, 1, 1, 1,
1, 1, 1, 2, 3, 2, 2, 3, 5, 2, 2, 3, 2, 1, 1, 2,
Expand Down Expand Up @@ -105,25 +110,13 @@ public static DexMethodDescriptor[] getDeclaredDexMethods(byte[] buf, String kla
}

@NonUiThread
@Deprecated
public static String guessNewInstanceType(byte[] buf, DexMethodDescriptor method,
DexFieldDescriptor field) throws NoSuchMethodException {
if (buf == null) {
throw new NullPointerException("dex == null");
}
if (method == null) {
throw new NullPointerException("method == null");
}
if (field == null) {
throw new NullPointerException("field == null");
}
public static int getDexMethodOffset(@NonNull byte[] buf, @NonNull DexMethodDescriptor method) {
int methodIdsSize = readLe32(buf, 0x58);
int methodIdsOff = readLe32(buf, 0x5c);
int classDefsSize = readLe32(buf, 0x60);
int classDefsOff = readLe32(buf, 0x64);
int dexCodeOffset = -1;
int dexCodeOffset = 0;
int[] p = new int[1];
int[] ret = new int[1];
int[] co = new int[1];
main_loop:
for (int cn = 0; cn < classDefsSize; cn++) {
Expand Down Expand Up @@ -178,7 +171,17 @@ public static String guessNewInstanceType(byte[] buf, DexMethodDescriptor method
}
}
}
if (dexCodeOffset == -1) {
return dexCodeOffset;
}

@NonUiThread
@Deprecated
public static String guessNewInstanceType(byte[] buf, DexMethodDescriptor method, DexFieldDescriptor field) throws NoSuchMethodException {
Objects.requireNonNull(buf, "buf == null");
Objects.requireNonNull(method, "method == null");
Objects.requireNonNull(field, "field == null");
int dexCodeOffset = getDexMethodOffset(buf, method);
if (dexCodeOffset == 0) {
throw new NoSuchMethodException(method.toString());
}
int registersSize = readLe16(buf, dexCodeOffset);
Expand All @@ -187,7 +190,7 @@ public static String guessNewInstanceType(byte[] buf, DexMethodDescriptor method
int triesSize = readLe16(buf, dexCodeOffset + 6);
int insnsSize = readLe16(buf, dexCodeOffset + 12);
int insnsOff = dexCodeOffset + 16;
//we only handle new-instance and iput-object
// we only handle new-instance and iput-object
String[] regObjType = new String[insSize + outsSize];
for (int i = 0; i < insnsSize; ) {
int opv = buf[insnsOff + 2 * i] & 0xff;
Expand Down Expand Up @@ -215,6 +218,57 @@ public static String guessNewInstanceType(byte[] buf, DexMethodDescriptor method
return null;
}

@NonUiThread
public static ArrayList<Integer> getViewSetIdP1Values(byte[] buf, DexMethodDescriptor method) throws NoSuchMethodException {
Objects.requireNonNull(buf, "buf == null");
Objects.requireNonNull(method, "method == null");
int dexCodeOffset = getDexMethodOffset(buf, method);
if (dexCodeOffset == 0) {
throw new NoSuchMethodException(method.toString());
}
int registersSize = readLe16(buf, dexCodeOffset);
int insSize = readLe16(buf, dexCodeOffset + 2);
int outsSize = readLe16(buf, dexCodeOffset + 4);
int triesSize = readLe16(buf, dexCodeOffset + 6);
int insnsSize = readLe16(buf, dexCodeOffset + 12);
int insnsOff = dexCodeOffset + 16;
// we only handle const and invoke-virtual Landroid/widget/TextView;->setId(I)V
String targetMethodDesc = "Landroid/widget/TextView;->setId(I)V";
Integer[] regObjType = new Integer[registersSize];
ArrayList<Integer> results = new ArrayList<>();
int pc = 0; // program counter
while (pc < insnsSize) {
int opv = buf[insnsOff + 2 * pc] & 0xff;
int len = OPCODE_LENGTH_TABLE[opv];
if (len == 0) {
throw new RuntimeException(String.format(Locale.ROOT, "Unrecognized opcode = 0x%02x", opv));
}
if (opv == 0x14) {
// 14 31i const vAA, #+BBBBBBBB
int reg = buf[insnsOff + 2 * pc + 1] & 0xff;
int valLow16 = readLe16(buf, insnsOff + 2 * pc + 2);
int valHigh16 = readLe16(buf, insnsOff + 2 * pc + 4);
int value = valLow16 | (valHigh16 << 16) & 0xffff0000;
regObjType[reg] = value;
} else if (opv == 0x6e) {
// 6e 35c invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
// [A=2] op {vC, vD}, kind@BBBB
int methodIdx = readLe16(buf, insnsOff + 2 * pc + 2);
DexMethodDescriptor m = readMethod(buf, methodIdx);
if (m.getDescriptor().equals(targetMethodDesc)) {
// get p1/C reg index
int insOffset4 = readLe16(buf, insnsOff + 2 * pc + 4);
int regIndex = (insOffset4 >> 4) & 0xf;
if (regObjType[regIndex] != null) {
results.add(regObjType[regIndex]);
}
}
}
pc += len;
}
return results;
}

@NonUiThread
public static DexFieldDescriptor guessFieldByNewInstance(byte[] buf, DexMethodDescriptor method,
Class<?> instanceClass) throws NoSuchMethodException {
Expand Down Expand Up @@ -481,6 +535,15 @@ public static DexFieldDescriptor readField(byte[] buf, int idx) {
return new DexFieldDescriptor(clz, name, type);
}

public static DexMethodDescriptor readMethod(byte[] buf, int idx) {
int methodIdsOff = readLe32(buf, 0x5c);
int p = methodIdsOff + 8 * idx;
String clz = readType(buf, readLe16(buf, p));
String sig = readProto(buf, readLe16(buf, p + 2));
String name = readString(buf, readLe32(buf, p + 4));
return new DexMethodDescriptor(clz, name, sig);
}

public static String readProto(byte[] buf, int idx) {
int protoIdsOff = readLe32(buf, 0x4c);
int returnTypeIdx = readLe32(buf, protoIdsOff + 12 * idx + 4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/
package io.github.qauxv.util.dexkit;

import androidx.annotation.NonNull;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -158,6 +159,11 @@ public String toString() {
return declaringClass + "->" + name + signature;
}

@NonNull
public String getDescriptor() {
return declaringClass + "->" + name + signature;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
163 changes: 163 additions & 0 deletions app/src/main/java/io/github/qauxv/util/dexkit/HostMainDexHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2023 QAuxiliary developers
* https://github.com/cinit/QAuxiliary
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version and our eula as published
* by QAuxiliary contributors.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/

package io.github.qauxv.util.dexkit;

import android.app.Application;
import androidx.annotation.Nullable;
import io.github.qauxv.util.HostInfo;
import io.github.qauxv.util.IoUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class HostMainDexHelper {

private HostMainDexHelper() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

private static HashMap<Integer, WeakReference<byte[]>> sCachedDex = new HashMap<>(32);

@Nullable
private static byte[] extractDexFromHost(int index) {
Application app = HostInfo.getHostInfo().getApplication();
// get path of base.apk
String apkPath = app.getApplicationInfo().sourceDir;
String dexName = getNameForIndex(index);
// FIXME 2023-07-25: on very old QQ/TIM versions, some dex is in assets
try (ZipFile zipFile = new ZipFile(apkPath)) {
ZipEntry entry = zipFile.getEntry(dexName);
if (entry == null) {
return null;
}
return IoUtils.readFully(zipFile.getInputStream(entry));
} catch (IOException e) {
IoUtils.unsafeThrow(e);
// unreachable
return null;
}
}

public static boolean hasDexIndex(int i) {
Application app = HostInfo.getHostInfo().getApplication();
// get path of base.apk
String apkPath = app.getApplicationInfo().sourceDir;
String dexName = getNameForIndex(i);
// FIXME 2023-07-25: on very old QQ/TIM versions, some dex is in assets
try (ZipFile zipFile = new ZipFile(apkPath)) {
ZipEntry entry = zipFile.getEntry(dexName);
return entry != null;
} catch (IOException e) {
IoUtils.unsafeThrow(e);
// unreachable
return false;
}
}

private static String getNameForIndex(int i) {
if (i <= 1) {
return "classes.dex";
} else {
return "classes" + i + ".dex";
}
}

@Nullable
public static byte[] getHostDexIndex(int i) {
if (i <= 0) {
return null;
}
// load from cache
WeakReference<byte[]> weakReference = sCachedDex.get(i);
if (weakReference != null) {
byte[] bytes = weakReference.get();
if (bytes != null) {
return bytes;
}
}
// load from host
byte[] bytes = extractDexFromHost(i);
if (bytes != null) {
sCachedDex.put(i, new WeakReference<>(bytes));
}
return bytes;
}


public static class DexIterator implements Iterator<byte[]> {

private int nextIndex = 1;

private DexIterator() {
}

public boolean hasNext() {
return HostMainDexHelper.hasDexIndex(this.nextIndex);
}

public byte[] next() {
byte[] hostDexIndex = HostMainDexHelper.getHostDexIndex(this.nextIndex);
if (hostDexIndex != null) {
this.nextIndex++;
}
return hostDexIndex;
}

public int getLastIndex() {
return this.nextIndex - 1;
}

}

public static Iterator<byte[]> getDexIterator() {
return new DexIterator();
}

public static Iterable<byte[]> asIterable() {
return DexIterator::new;
}

@Nullable
public static byte[] findDexWithClass(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("name == null || name.isEmpty()");
}
for (byte[] dex : asIterable()) {
if (DexFlow.hasClassInDex(dex, name)) {
return dex;
}
}
return null;
}

@Nullable
public static byte[] findDexWithClass(Class<?> klass) {
Objects.requireNonNull(klass, "klass == null");
return findDexWithClass(klass.getName());
}

}
Loading

0 comments on commit d9ef36b

Please sign in to comment.