-
jad (1.5.8g)
- Pros
- byte codes as inline comments
- for final confirmation
- byte codes as inline comments
- Cons
Exception
周りが下手、finally
は絶望的switch
もダメダメfor
,while
の扱いが逆なんじゃない?- 2重ループ、
if
...else if
...else
... とかも弱い
- 2重ループ、
- inner class もちょっとおかしい
synchronize
もダメenum
超面倒- closed source 👎
- discontinued?
- Pros
-
JD (core 1.1.3)
- Pros
- enable to keep original line numbers 👍
Exception
はまあまあswitch
もまあまあbreak
が抜けてる気がするのだが- できない場合も多いな
- 人が書いたコードに近い
++
は人が書く場合後置が多いとか- 単純な
if
else
はきれいに出力される
- open source
- Cons
- boxing が残っている
for
糖衣構文 50% くらいダメ- local 変数重複、宣言場所
- 総称型
- inner class constructor
- inner class 外部変数参照,
final
static final
定数戻しenum
functionenum
switch
で置換しきれていない- CUI がない
- interface に abstract が残ってる
- Fatal
- いまいち
かなり信用出来ないjad はうまく行かなかったところに byte code っぽいものを残すのだが、JD は適当にエラーのないコードを出力してるような- synchronized
- switch に return 抜けあり
- ネストした if else 中の continue が break になることがある
- いまいち
- Pros
-
cfr (0.150)
-
Pros
almighty?- support after Java 9
- open source
-
Cons
- 複数の if else を一つにまとめすぎている
- オプションでやめられれば最強なのに
for
中のif
がcontinue
になりがち- 局所変数代入のインライン化をやりすぎている
if
else
を三項演算子? :
に変換もやりすぎているっぽい- inner class 外部変数参照
- これはオプションで残せるっぽい TODO
-removeinnerclasssynthetics
- これはオプションで残せるっぽい TODO
- inner class が最後に配置される
- finally block (jd-core のほうが遥かにマシ)
- 複数の if else を一つにまとめすぎている
-
あと少し
- boxing が残っている
static final
定数戻し++
が全て後置- 配列の複合代入演算、jd はできるのに...
cfrcorrectlyint[] arrn = loadsSinceStore; int n = prevFetched.getSecond(); arrn[n] = arrn[n] - 1;
loadsSinceStore[prevFetched.getSecond()] -= 1;
-
Fatal
- 上述の状態構文をまとめすぎるので
instanceof
で確認後キャストして使用するとかnull
チェック後代入が壊れる 👎
- 上述の状態構文をまとめすぎるので
-
-
fernflower (f61e659)
- almighty 👑
-
- Pros
- enable to keep original line numbers 👍
- Cons
- discontinued?
- Pros
fernflower ほぼ完璧
面倒になるのが同じ文字で大文字、小文字のクラス名に難読化されている場合、ファイルシステムが対応してないとか、Eclipse が判別してくれないとかあるのでそれを解除する。
あとメソッドのオーバーロードのバインディングミスも発見しづらいので最初に回避するようにしておく
using obfuscator to de-obfuscate
- ProGuard
- method overload を積極的にしているのを解除する (
-useuniqueclassmembernames
) - クラス名に同じ文字の大文字、小文字を許容するも解除 (
-dontusemixedcaseclassnames
) - 難読化されていないパッケージ、クラス名は保存しておく (
-keeppackagenames
,-keepnames
)
- method overload を積極的にしているのを解除する (
proguard.txt
-dontshrink
-dontoptimize
-useuniqueclassmembernames
-dontusemixedcaseclassnames
-dontpreverify
-keepparameternames
-renamesourcefileattribute SourceFile
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
-keeppackagenames your.packages.**
-keepnames class your.packages.**
$ proguard.sh @proguard.txt -injars in.jar -outjar out.jar
- Tiny-Remapper
https://gist.github.com/umjammer/810a8cdf0f0d9c2617b1a5d7a256e0a8
- jad
- オーバーロードのバインドミスを無くすために
-safe
オプションを付ける- 難読化されていない場合はしなくても多分大丈夫
-a
バイトコード付きコード出力- あとは好みのフォーマットオプション
- TODO
-noctor
- TODO
- オーバーロードのバインドミスを無くすために
$ find . -name \*.class -exec jad -s .java -safe -space -r -nonlb -ff {} \;
- SpotBugs
- cfr のやりすぎで null アクセスになるところを検出してくれる
- infer TBD
- こいつも null アクセス検知用だが SpotBugs で十分な気が...
- vavi.lang.instrumentation.PassClassFileTransformaer
- 実行時にどのメソッドを通ったのかをトレースしてくれるので本物と比較しておかしいところを検出
- coverage tool
- TBD
- directory diff tool TBD
- Mac だと meld っぽいのだが、なんか動かん
- vimdiff TBD
主に jad の話なので、最近は必要ないものが多い。
- パターンでわかる
jad
i = 0;
goto _L1
_L3:
:
i++;
_L1:
if (i < 10) goto _L3; else goto _L2
_L2:
completed
for (i = 0; i < 10; i++) {
:
}
TODO
TODO
TODO
TODO
- 何となく分かる、try がどこに入るのかがいまいちよくわからない
- exception 処理っぽいところで同じコードが何回も出てきたらそれは finally 節
jad
break MISSING_BLOCK_LABEL_43;
Exception exception;
exception;
a.c();
throw exception;
a.c();
return;
completed
} catch (Exception exception) {
} finally {
a.c();
}
- 人力ではキツイので JD に頼る
jad
a;
JVM INSTR tableswitch -1 6: default 523
// -1 523
// 0 60
// 1 200
// 2 523
// 3 499
// 4 499
// 5 507
// 6 515;
goto _L1 _L1 _L2 _L3 _L1 _L4 _L4 _L5 _L6
_L2:
:
手動でやる場合はこういう感じ
switch (a) {
// JVM INSTR tableswitch -1 6: default 523 _L1
// -1 523 _L1
// 0 60 _L2
// 1 200 _L3
// 2 523 _L1
// 3 499 _L4
// 4 499 _L4
// 5 507 _L5
// 6 515; _L6
// goto _L1 _L1 _L2 _L3 _L1 _L4 _L4 _L5 _L6
//_L1:
default:
case -1:
case 0:
case 2:
:
//_L2:
case 0:
:
//_L3:
case 1:
:
//_L4:
case 3:
case 4:
:
//_L5:
case 5:
:
//_L6:
case 6:
:
- goto 以降を default から順に当てはめていく
- switch の後が default 中に置かれてる事多し
- break が continue になってたり
- case 値の JVM code の行番号も参考に
- switch 文より前にある場合とか発狂しそう
- パターンでわかる
jad
Object obj = foo;
JVM INSTR monitorenter ;
:
obj;
JVM INSTR monitorexit ;
throw ;
completed
synchronized(foo) {
:
}
- コンストラクタで super の場所が間違ってるので直す。
- 親クラス参照を消す
- コンパイラが作成したと思われる static メソッドをインライン化する (access$# ってやつね)
jad
foo.addActionListener(new g(this));
:
static void a(BaseClass a, String s) {
System.out.println(s);
}
class g
implements ActionListener {
final BaseClass a;
public void actionPerformed(ActionEvent actionevent) {
BaseClass.a(a, "");
}
g() {
a = BaseClass.this;
super();
}
}
completed
foo.addActionListener(new g());
:
class g
implements ActionListener {
public void actionPerformed(ActionEvent actionevent) {
System.out.println(s);
}
g() {
super();
}
}
さらに進めて
foo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionevent) {
System.out.println("");
}
});
- 表記が変なので直す
jad
a = (new Class[] {
0,
java/lang/String,
java/lang/Integer,
[Ljava/lang/Object;,
java/lang/Object,
[I,
[D,
});
completed
a = (new Class[] {
null,
java.lang.String.class,
java.lang.Integer.class,
java.lang.Object[].class,
java.lang.Object.class,
int[].class,
double[].class,
});
- StringBuilder、StringBuffer を消す
jad
System.err.println((new StringBuilder("value: ")).append(value).toString());
completed
System.err.println("value: " + value);
- 見やすくしたり、消したり
- while の条件式中のやつは注意
TODO sample
- 人が書いた数値に戻す
- Math.PI 等
jad
double d1 = 0.29999999999999999D;
completed
double d1 = 0.3d;
- すべてコードに埋め込まれてしまうのでもとに戻す
- TODO eclispe のリファクタ機能で何とかならないのか?
- TODO android の R.java とか
- static {} の中身に注目
- vaues(), valueOf() を削除
- コンストラクタの第 1, 2 引数と super の削除
- enum の引数関連は残す
- あと他の場所では static フィールドに設定されているフィールド名が使用されているため
分かるように残してそれを残して後でリネーム出来るようにしておく
jad
public static final class Foo extends Enum {
private static final Foo lM[];
public static final Foo lN;
public static final Foo lO;
public static final Foo lP;
byte lQ;
public static Foo valueOf(String s) {
return (Foo)Enum.valueOf(foo/bar/Buz$Foo, s);
}
public static Foo[] values() {
Foo afoo[] = lM;
int i = afoo.length;
Foo afoo1[] = new Foo[i];
System.arraycopy(((Object) (afoo)), 0, ((Object) (afoo1)), 0, i);
return afoo1;
}
public byte cR() {
return lQ;
}
static {
lO = new Foo("AAA", 0, (byte)0);
lN = new Foo("BBB", 1, (byte)1);
lP = new Foo("CCC", 2, (byte)3);
Foo afoo[] = new Foo[3];
afoo[0] = lO;
afoo[1] = lN;
afoo[2] = lP;
lM = afoo;
}
private Foo(String s, int i, byte byte0) {
super(s, i);
lQ = byte0;
}
}
NG
public static enum Foo {
AAA(0), // lO
BBB(1), // lN
CCC(2); // lP
byte lQ;
public byte cR() {
return lQ;
}
private Foo(byte byte0) {
lQ = byte0;
}
}
BETTER
public static enum Foo {
lO(0), // AAA
lN(1), // BBB
lP(2); // CCC
byte lQ;
public byte cR() {
return lQ;
}
private Foo(byte byte0) {
lQ = byte0;
}
}
jad
package buz.bar;
class Foo {
static final boolean $assertionsDisabled;
:
if (!$assertionsDisabled && foo == 0)
throw new AssertionError("assertion message");
:
static {
boolean flag;
if (!buz.bar.Foo.class.desiredAssertionStatus())
flag = true;
else
flag = false;
$assertionsDisabled = flag;
}
completed
assert foo != 0 : "assertion message";
jad
import java.lang.annotation.Annotation;
public interface UiThread
extends Annotation {
public abstract boolean value();
}
completed
public @interface UiThread {
}
IDE のリファクタリング機能がこれほど活躍できる場面はないと思う。
たまに Javadoc だけ公開されている場合がある。その場合は Codavaj を使用すれば自動でコメントが付けられる。おまけに正しい引数名が得られるので、そこから芋づる式に内部の解析が進むこともある。
オーバーロードでミスる事を知らなかったときにリバエンした 50000 行くらいのコードがあるのだが、もちろんエラーは出ないし、なんとちゃんと動いてしまってるので、どこでミスってるのかが簡単に検出できない。結果のデータが違うので明らかにミスっているのは分かるのだが、どうしたものか...
call graph 作成とか?→ 作ったけど、はっきり言って比較するのムリ
オーバーロードのミスより、名前の優先順位が定数より変数が勝つみたいなのでそこがミスっていた。null と比較とかだとエラーにならない場合が出てくる。
class Foo {
Bar a;
}
class a {
public static Foo a;
}
class b {
Foo a;
... {
if (a.a != null) { ... // compiler recognizes class b's field a (class Foo) 's field a,
// but real source was class a's static field a
}
}
まあ Anti Obfuscation しておけばいらない苦労だったんだけどね。
-packageobfuscationdictionary dic_file
TBD
JavaParser の AST と cfr の内部構造を共通化して cfr の rewrite エンジンを使用するとか
正規表現だけでやってるけどこれでいけるのかなぁ?
どうしよう?
- API を使用してるのはそんなに多くないと仮定して、使用している方からリバエン
- すべてやる必要はない
- スケルトンを作る (jad -nocode)
リバースエンジニアリングの権利があるのでじゃんじゃんリバエンして公開しようw