Dump Class!!

14722 ワード

この文章を書く目的は、微信が最近また仕事を始めたからです.まず簡単な現象を見てみましょう.反コンパイル微信は、com.tencent.mm.protocal.protobuf.bquというクラスを手に入れました.反コンパイル後のクラスの説明はこうです.
.field private vGA:Z
.field public vGz:Ljava/lang/String;
.method public constructor ()V
.method public final aft(Ljava/lang/String;)Lcom/tencent/mm/protocal/protobuf/bqu;
.method public final computeSize()I
.method public final synthetic parseFrom([B)Lcom/tencent/mm/bv/a;
.method public final populateBuilderWithField(Le/a/a/a/a;Lcom/tencent/mm/bv/a;I)Z
.method public final toByteArray()[B
.method public final toString()Ljava/lang/String;
.method public final bridge synthetic validate()Lcom/tencent/mm/bv/a;
.method public final writeFields(Le/a/a/c/a;)V

しかし、私がhookを書いてaft関数をチェックすると、奇妙なことが起こりました.この関数はまったくありません.チェックコードは以下の通りです.
XposedHelper.findAndHookMethod(
    "com.tencent.mm.protocal.protobuf.bqu", 
    classloader, 
    "aft", String::class.java, 
    object: XC_MethodHook() {
        override fun beforeHookedMethod(param: MethodHookParam) {
            Log.e(TAG, "str => ${param.args[0]}")
        }
    }
)

このような奇妙な問題は一度も発生したことがなくて、私に反コンパイルの時にすでに間違いが現れたかどうか、あるいは何かが反コンパイルの過程を誤導して、それによって出た結果が間違っているかどうかを感じさせます.そのためには,微信実行時のクラスを参考にしなければならないので,微信のすべてのクラスをすべてdumpしなければならない.
もちろん、このコードを書くのは難しくありません.キー関数を直接完成することができます.
/**
 * dump apk , 
 * @param ctx
 * @param apkPath  dump apk 
 * @param prefix   dump  (  packagename  ,  dump   packagename   com.tencent.mm,  com.tencent,  dump   com.tencent.wcdb  )
 * @param outputPath sd ( : output,  /sdcard/output)
 * @param progress dump  ,className   dump  
 * @param complete dump  , succ   dump  
 */
fun dump(ctx: Context, apkPath: String, prefix: String, outputPath: String, progress:(className: String?) -> Unit, complete:(succ: Boolean) -> Unit) = thread {
    if (isRunning) {
        runOnMainThread { complete(false) }
        return@thread
    }
    isRunning = true
    val oat = ctx.getDir(outputPath, 0)
    if (!oat.exists()) {
        oat.mkdirs()
    }
    val loader = DexClassLoader(apkPath, oat.absolutePath, null, ClassLoader.getSystemClassLoader())
    val list = mutableListOf()
    val dex = DexFile(apkPath)
    val en = dex.entries()
    while (en.hasMoreElements()) {
        val cn = en.nextElement()
        if (cn.startsWith(prefix)) {
            list.add(cn)
        }
    }
    val basePath = File(Environment.getExternalStorageDirectory(), outputPath)
    if (!basePath.exists()) {
        basePath.mkdirs()
    }
    list.forEach {
        if (isRunning) {
            val fn = File(basePath, "$it.dump")
            if (!fn.exists()) {
                val clz = try { loader.loadClass(it) } catch (t: Throwable) { null }
                if (clz != null) {
                    var str = ""
                    try { clz.declaredFields } catch (t: Throwable) { null }?.forEach { f -> str += "${f.name}:${f.type.name}
" } try { clz.declaredMethods } catch (t: Throwable) {null}?.forEach { m -> str += "${m.name}(" m.parameterTypes?.forEach { p -> str += "${p.name}," } str = str.trimEnd(',') str += "):${m.returnType.name}
" } try { clz.declaredConstructors } catch (t: Throwable) { null }?.forEach { c -> str += "(" try { c.parameterTypes } catch (e: Throwable) { null }?.forEach { p -> str += "${p.name}," } str = str.trimEnd(',') str += ")
" } fileWriteText(fn, str) runOnMainThread { progress(it) } } } } } isRunning = false runOnMainThread { complete(true) } }

この関数から得られたcom.tencent.mm.protocal.protobuf.bquのクラスは以下のように記述される.
uAJ:java.lang.String
uRm:com.tencent.mm.protocal.protobuf.cdv
uum:com.tencent.mm.protocal.protobuf.bqx
vJv:int
vJw:int
op(int,[Ljava.lang.Object;):int
()

明らかに、実行時のbquと逆コンパイルされたのは全く違いますが、本当のbqu類はどこに行きましたか?
このときdumpから出てきたすべてのランタイムクラスをクラス特徴比較する必要がある.必要なクラスは次のように記述されます.
*:boolean
*:java.lang.String
()
*(java.lang.String):com.tencent.mm.protocal.protobuf.*
*():int
*([B):com.tencent.mm.bv.*
*(e.a.a.a.*,com.tencent.mm.bv.*,int):boolean
*():[B
*():java.lang.String
*():com.tencent.mm.bv.*
*(e.a.a.c.*):void

アスタリスクとしてマークすることは確定できません.クラス名が実行時に変更され、逆コンパイル時に実際のクラス名が得られないため、プロビジョニング処理が必要です.
では、簡単な書き込みコードについて、smaliコードを一致する説明に変換します.
function dumpClassName(str: string): string;
var
  ret: string;
begin
  ret := str.Substring(str.IndexOf('com/tencent/')).TrimRight([';']);
  ret := ret.Replace('/', '.', [rfReplaceAll]);
  Exit(ret);
end;

function dumpField(str: string): string;
var
  sarr: TStringArray;
  namearr: TStringArray;
  fname: string;
  ftype: string;
  t: string;
begin
  sarr := str.Split(':');
  namearr := sarr[0].Split(' ');
  fname:= namearr[Length(namearr) - 1];
  if (sarr[1].Contains('=')) then begin
    ftype:= sarr[1].Split('=')[0].Trim + ';';
  end else begin
    ftype:= sarr[1];
  end;

  if (ftype.StartsWith('[')) then begin
    if (ftype.StartsWith('[L')) then begin
      ftype:= ftype.Replace('/', '.', [rfReplaceAll]);
    end else begin
      // normal array, do nothing
    end;
  end else begin
    if (ftype.StartsWith('L') and ftype.EndsWith(';')) then begin
      ftype:= ftype.Substring(1, ftype.Length -2).Replace('/', '.', [rfReplaceAll]);
    end else begin
      t := btm.KeyData[ftype[1]];
      if (t <> '') then ftype:= t;
    end;
  end;
  Exit(Format('%s:%s', [fname, ftype]));
end;

function dumpParam(str: string): string;
var
  i: Integer = 1;
  c: Char;
  c1: Char;
  tc: Char;
  t: string;
  ret: string = '';
begin
  if (str.Length = 0) then Exit('');
  while (True) do begin
    c := str[i];
    if (c = '[') then begin
      c1 := str[i + 1];
      if (c1 = 'L') then begin
        ret += '[';
        Inc(i);
        while (true) do begin
          tc := str[i];
          if (tc = ';') then begin
            ret += tc;
            Break;
          end else begin
            ret += tc;
          end;
          Inc(i);
        end;
        ret += ',';
        Inc(i)
      end else begin
        ret += Format('[%s,', [c1]);
        Inc(i, 2);
      end;
    end else begin
      if (c = 'L') then begin
        Inc(i);
        while (True) do begin
          tc := str[i];
          if (tc = ';') then Break;
          ret += tc;
          Inc(i);
        end;
        ret += ',';
        Inc(i);
      end else begin
        t := btm.KeyData[c];
        ret += Format('%s,', [t]);
        Inc(i);
      end;
    end;
    if (i > str.Length) then Break;
  end;
  ret := ret.TrimRight([',']);
  ret := ret.Replace('/', '.', [rfReplaceAll]);
  Exit(ret);
end;

function dumpMethod(str: string): string;
var
  sarr: TStringArray;
  namearr: TStringArray;
  mname: string;
  mparam: string;
  mret: string;
  t: string;
begin
  sarr := str.Split(['(', ')']);
  namearr := sarr[0].Split(' ');
  mname:= namearr[Length(namearr) - 1];
  mparam:= dumpParam(sarr[1]);
  mret := sarr[2];
  if (mret.StartsWith('[')) then begin
    if (mret.StartsWith('[L')) then begin
      mret := mret.Replace('/', '.', [rfReplaceAll]);
    end else begin
      // do nothing
    end;
  end else begin
    if (mret.StartsWith('L') and mret.EndsWith(';')) then begin
      mret := mret.Substring(1, mret.Length - 2).Replace('/', '.', [rfReplaceAll]);
    end else begin
      t := btm.KeyData[mret[1]];
      if (t <> '') then mret := t;
    end;
  end;
  Exit(Format('%s(%s):%s', [mname, mparam, mret]));
end;

function dumpConstructor(str: string): string;
var
  sarr: TStringArray;
  mparam: string;
begin
  sarr := str.Split(['(', ')']);
  mparam:= dumpParam(sarr[1]);
  Exit(Format('(%s)', [mparam]));
end;

procedure dumpFile(filePath: string; savePath: string);
var
  fn: string = '';
  i: Integer;
  list: TStringList;
begin
  WriteLn('dump file => ' + filePath);
  list := TStringList.Create;
  with TStringList.Create do begin
    LoadFromFile(filePath);
    for i := 0 to Count - 1 do begin
      if (Strings[i].StartsWith('.class')) then begin
        fn := savePath + dumpClassName(Strings[i]) + '.index';
        Continue;
      end;
      if (Strings[i].StartsWith('.field')) then begin
        list.Add(dumpField(Strings[i]));
      end;
      if (Strings[i].StartsWith('.method')) then begin
        if (Strings[i].Contains('')) then begin
          list.Add(dumpConstructor(Strings[i]));
        end else begin
          if (not Strings[i].Contains('')) then begin
            list.Add(dumpMethod(Strings[i]));
          end;
        end;
      end;
    end;
    Free;
  end;
  list.SaveToFile(fn);
  list.Free;
end;

procedure findFile(basePath: string; savePath: string);
var
  src: TSearchRec;
  tmp: string;
begin
  if (not basePath.EndsWith(DirectorySeparator)) then basePath += DirectorySeparator;
  if (FindFirst(basePath + '*', faAnyFile, src) = 0) then begin
    repeat
      if (src.Name = '.') or (src.Name = '..') then Continue;
      tmp := basePath + src.Name;
      if (DirectoryExists(tmp)) then begin
        findFile(tmp, savePath);
      end else begin
        if (tmp.EndsWith('.smali')) then
        dumpFile(tmp, savePath);
      end;
    until FindNext(src) <> 0;
    FindClose(src);
  end;
end;

変換後の逆コンパイルのbquクラスは以下のように記述される.
vGA:boolean
vGz:java.lang.String
()
aft(java.lang.String):com.tencent.mm.protocal.protobuf.bqu
computeSize():int
parseFrom([B):com.tencent.mm.bv.a
populateBuilderWithField(e.a.a.a.a,com.tencent.mm.bv.a,int):boolean
toByteArray():[B
toString():java.lang.String
validate():com.tencent.mm.bv.a
writeFields(e.a.a.c.a):void

形式的に一致すると、dumpからのランタイムクラスの説明と比較するために簡単に使用できます.同じコードを書いて解決します.もちろん、ここでは共通の問題に注意する必要があります.
function extractDesc(astr: string): string;
var
  t: string = '';
  sarr: TStringArray;
  marr: TStringArray;
  mret: string = '';
  i: Integer;
begin
  if (astr.Contains('(')) then begin
    t := astr.Substring(astr.IndexOf('('));
    t := t.TrimRight([';']);
    if (t.Contains(':')) then begin
      sarr := t.Split(':');
      mret := sarr[1];
      if (mret.Contains('.')) then begin
        mret := mret.Substring(0, mret.LastIndexOf('.'));
      end;
      marr := sarr[0].Substring(1, sarr[0].Length - 2).Split(',');
      for i := 0 to Length(marr) - 1 do begin
        if (marr[i].Contains('.')) then marr[i] := marr[i].Substring(0, marr[i].LastIndexOf('.'));
      end;
      t := '(';
      for i := 0 to Length(marr) - 1 do begin
        t += Format('%s,', [marr[i]]);
      end;
      t := t.TrimRight([',']);
      t += '):' + mret;
    end else begin
      marr := t.Substring(1, t.Length - 2).Split(',');
      for i := 0 to Length(marr) - 1 do begin
        if (marr[i].Contains('.')) then marr[i] := marr[i].Substring(0, marr[i].LastIndexOf('.'));
      end;
      t := '(';
      for i := 0 to Length(marr) - 1 do begin
        t += Format('%s,', [marr[i]]);
      end;
      t := t.TrimRight([',']);
      t += ')';
    end;
  end else begin
    t := astr.Substring(astr.IndexOf(':'));
    t := t.TrimRight([';']);
    if (t.Contains('.')) then begin
      t := t.Substring(0, t.LastIndexOf('.'));
    end;
  end;
  Exit(t);
end;

function isMatch(originPath: string; dumpPath: string): Boolean;
var
  listOrigin: TStringList;
  listDump: TStringList;
  i: Integer;
  idx: Integer;
begin
  listOrigin := TStringList.Create;
  listDump := TStringList.Create;
  listOrigin.LoadFromFile(originPath);
  listDump.LoadFromFile(dumpPath);
  for i := 0 to listOrigin.Count - 1 do begin
    listOrigin[i] := extractDesc(listOrigin[i]);
  end;
  for i := 0 to listDump.Count - 1 do begin
    listDump[i] := extractDesc(listDump[i]);
  end;
  for i := listOrigin.Count - 1 downto 0 do begin
    idx := listDump.IndexOf(listOrigin[i]);
    if (idx <> -1) then begin
      listOrigin.Delete(i);
      listDump.Delete(idx);
    end;
  end;
  Result := (listOrigin.Count = 0) and (listDump.Count = 0);
  listDump.Free;
  listOrigin.Free;
end;

検索結果、要求に適合するランタイムクラス、すなわちcom.tencent.mm.protocal.protobuf.bqyが最終的に発見された.
vJG:java.lang.String
vJH:boolean
afA(java.lang.String):com.tencent.mm.protocal.protobuf.bqy
computeSize():int
parseFrom([B):com.tencent.mm.bv.a
populateBuilderWithField(e.a.a.a.a,com.tencent.mm.bv.a,int):boolean
toByteArray():[B
toString():java.lang.String
validate():com.tencent.mm.bv.a
writeFields(e.a.a.c.a):void
()
bqy変数、関数名のほか、全体の構造は逆コンパイルのbquと一致し、再びbquをここに貼り付け、肉眼で比較するのに便利である.
vGA:boolean
vGz:java.lang.String
()
aft(java.lang.String):com.tencent.mm.protocal.protobuf.bqu
computeSize():int
parseFrom([B):com.tencent.mm.bv.a
populateBuilderWithField(e.a.a.a.a,com.tencent.mm.bv.a,int):boolean
toByteArray():[B
toString():java.lang.String
validate():com.tencent.mm.bv.a
writeFields(e.a.a.c.a):void

そのため、私たちが本当にチェックすべきものはbqyで、xposedコードを少し修正すればいいです.
XposedHelper.findAndHookMethod(
    "com.tencent.mm.protocal.protobuf.bqy", 
    classloader, 
    "afA", String::class.java, 
    object: XC_MethodHook() {
        override fun beforeHookedMethod(param: MethodHookParam) {
            Log.e(TAG, "str => ${param.args[0]}")
        }
    }
)

ここまで、すべて正常です.
便宜上、Dump Classのツールはすでにオープンソースで、私のgithubでこのソースコード(Click)を取得することができます.