Dump Class!!
14722 ワード
この文章を書く目的は、微信が最近また仕事を始めたからです.まず簡単な現象を見てみましょう.反コンパイル微信は、
しかし、私がhookを書いて
このような奇妙な問題は一度も発生したことがなくて、私に反コンパイルの時にすでに間違いが現れたかどうか、あるいは何かが反コンパイルの過程を誤導して、それによって出た結果が間違っているかどうかを感じさせます.そのためには,微信実行時のクラスを参考にしなければならないので,微信のすべてのクラスをすべてdumpしなければならない.
もちろん、このコードを書くのは難しくありません.キー関数を直接完成することができます.
この関数から得られた
明らかに、実行時の
このときdumpから出てきたすべてのランタイムクラスをクラス特徴比較する必要がある.必要なクラスは次のように記述されます.
アスタリスクとしてマークすることは確定できません.クラス名が実行時に変更され、逆コンパイル時に実際のクラス名が得られないため、プロビジョニング処理が必要です.
では、簡単な書き込みコードについて、smaliコードを一致する説明に変換します.
変換後の逆コンパイルの
形式的に一致すると、dumpからのランタイムクラスの説明と比較するために簡単に使用できます.同じコードを書いて解決します.もちろん、ここでは共通の問題に注意する必要があります.
検索結果、要求に適合するランタイムクラス、すなわち
そのため、私たちが本当にチェックすべきものは
ここまで、すべて正常です.
便宜上、Dump Classのツールはすでにオープンソースで、私のgithubでこのソースコード(Click)を取得することができます.
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)を取得することができます.