文章总结: 文档介绍了针对安卓DEX混淆代码的优化方法,重点探讨基于编译器Pass技术的去混淆方案。分析了正则、编译器Pass及反编译插件三种方案,详细展示了通过自定义Pass分析控制流图与到达定义,精准移除无效字符串length调用并解密还原字符串的实战代码。该方法精准通用,优于正则处理,适合复杂混淆样本分析。 综合评分: 88 文章分类: 逆向分析,移动安全,二进制安全
基于编译器Pass技术对安卓DEX混淆代码与花指令进行优化
原创
非虫
软件安全与逆向分析
2026年1月7日 18:09 湖北
考虑如下的代码片断:
public static /* bridge */ /* synthetic */ void write(InputStream inputStream, OutputStream outputStream) throws IOException { "ẙ̵̡̖͚̫̘̩͇͉͔̖͍̹͈̘͕̥͎̻̘͖̲̲͔̦͎̣͖͈̯̹̈́̓̋̈́̋̓͆̒̔̈́̒̎́̏̀͊̅̆̇͝͠͠ͅ".length(); "ŗ̶̢̢̛̛͇͉͍̪̳̟̞͔̥̣̞̙̩̬̻̱͉͚̭̲̭̫̩̘̺͇̬̱̰͍̖͕͎͎̺̻̪̑͌̋̊̿̆̊͛̅̔̊͛̑̍́͊̐̾͘͜͠͠ͅͅȟ̸̡̢̨͍͚̝̲̝̫̟̲͎̲̼̬̦͓̺̭̼̪̜̠͈̭̗̳̳̣͓̟̲̻̜͇̰̞̝̾͂͌̈́̎́́̔̈́͋̀̿̀̍̈́͆͊̔̿͊̅̓́̈́̆̃̽͛̋͂̄̓̌̂͆̚͜͜͝͝͝͠͝m̵̡̫̙͔̜͙͇͈̝̲̱͕̱̤̟̭̭̙̠͎̬̦͇͎͙͓̰̣̻̥̦̒͊̎̈́̾̾̾͒̃͂͐̉̎̌͂̀͒͗͒̇́̀͛̌́͊̓̈́̀́̀̂̀̕̚̚̚͜͝͝͝͝͝͠͝".length(); "".length(); StringFogImpl.decrypt("Ppjw4buZxIq89d+Z1OCjmMOLqfXXmN3hhpnBi67065jI4YeZ0oqo9N+Y1OC8mMSKoPTHmNPhsZjQirf10Zj74KmZ0Iup9MCYzOG8mcaLq/TrmMjhkJnoio3075j94ZqZ5YqH9PSY2eCtmMiKlvTymOjhhJnmioj0+Jnc4KuYwou09OmY4uGYmfM="); StringFogImpl.decrypt("MJjz4bSZ3ouw9MaYy+GzmdeLvfTOmMThtJjYirn10Znm4KWZ3oqm9MGZ1+G1mdWLpvXTmd3ht5nFi7/1xZjF4bmZz4qU9OSY9eGlmfOKnPTImcM="); StringFogImpl.decrypt("M5jy4KCZ3Iq89ciZwOCymMmLsPXemMvhtpnVi6v13pnd4bCZxYqT9NKZwuCymcOKlvT7mOrgv5n+i7708Zjl4YiZ9YqM9MiY7+GKmNOKlPT/mOfhkZnvi6P12Jnf4YSZwoul9PCY9OGJmfY="); "".length(); "s̷̢̧̲̱̳̖̬͙̱͕̮̩̺̣̪̼͔̮͚͓͇͓̲̣̥͇̬̪̹̞̼̣͌͊̄̏̅̇̾͜͠ͅų̴̢̢̨̨̹̩̙͎̝̪͔͖͎͙̙̖̦͍͓͈̰̜͚̤̼̺̯̙͍͖͎̦͔̯̻͚̭̎̾̈́̊͊̇̃̚̚̕ͅk̶̨̢̡̧̧̛̼̠̺̻̱̪̟͕̻̮̼̲̥̭͚͓͖͙̼̤̠̃̐̑͊͒͛͗̈́͂̾̓̾̎̿̆̅̊̒̈́͐̍̒̉̈́̈́̽͑̄̈́̊̄̒͆̾̎̕̕̚̕͜ẙ̵̡̖͚̫̘̩͇͉͔̖͍̹͈̘͕̥͎̻̘͖̲̲͔̦͎̣͖͈̯̹̈́̓̋̈́̋̓͆̒̔̈́̒̎́̏̀͊̅̆̇͝͠͠ͅ".length(); "f̴̡̡̡̢̗̻̮̬͇̪͓̤̣̰̝̩̲͇̹̪̩̻͎͍͙̼̖͈̥̲̱̈̑͆͊͋̍̎́͆͋͛̈̑̾̇̈́͊͘͝͝͝m̵̡̫̙͔̜͙͇͈̝̲̱͕̱̤̟̭̭̙̠͎̬̦͇͎͙͓̰̣̻̥̦̒͊̎̈́̾̾̾͒̃͂͐̉̎̌͂̀͒͗͒̇́̀͛̌́͊̓̈́̀́̀̂̀̕̚̚̚͜͝͝͝͝͝͠͝j̴̨̛̦̱̦̮̪̲͖̱͚͈̹̭̊̂͛̍̎̇̽͆̎̔̏̂̉̓̓͌̌̂̓͋̐̒̽̂̿̔̈́͗͘̕͝͝͝͝͝ͅơ̸̡̡̛͓̰̖͉̤͉̖͔̰͍̤̠̠̱̫͎̭̩͖̻̞̼͓̞͋͐́̉͑̿́͋̌̂̀͊͛̀̄̊͊̓̄͘̚̕̕͜͝͝ͅͅ".length(); "ẙ̵̡̖͚̫̘̩͇͉͔̖͍̹͈̘͕̥͎̻̘͖̲̲͔̦͎̣͖͈̯̹̈́̓̋̈́̋̓͆̒̔̈́̒̎́̏̀͊̅̆̇͝͠͠ͅt̷̢̨̗̳̹̪̩̣̹̼̹̭̫̮͚̣̹̝͚̞͖͈̹̳͈̙͙͎̼̠̩̜̬̙̰͈̮̰̣͒͐̇̽̇͋̈̓̓̌͒̃̒͜͜͝ͅͅ".length(); "w̶̢̪̤̫̱̩̣͕̱͓̩͓̩̱̞̯͉͔̺̙͇̝̹̻̯̮͔̰̙̾̏̑̑͗̅͆͂͊͛̒͐̓̈́̃͂́͜͠͝ȩ̵̛̛̹̱̳̝̱̝̌̊̓̍̋̃͐̂̌͌̔̈́̊̋̔͑̍́͋͆͛̏̑͒͐̃́͝͠͝ͅb̶̢̛͚̝̰̺̱̲͖̠̘͚͕͉͇̭̗͇̙̗̣͍̲͖̽͗̐̅̾̅́͌̂̚͘͝k̶̨̢̡̧̧̛̼̠̺̻̱̪̟͕̻̮̼̲̥̭͚͓͖͙̼̤̠̃̐̑͊͒͛͗̈́͂̾̓̾̎̿̆̅̊̒̈́͐̍̒̉̈́̈́̽͑̄̈́̊̄̒͆̾̎̕̕̚̕͜".length(); "i̷̢̡̡̢̢̨̹͓̼̦̣͇̖͇͕̬̠̖̗̞̝͖̤̮̠̩̰̰̭̱̟̞͈̫͉̔̽̌̓̏͜͝ͅg̵̢̢̛̛͎͈͚̟͎͖͙͖̗̞̫̠͎͍̘͈͕͖͇̯͐͑̔̊͑͛͑͌͑̀̓͠".length(); "t̷̢̨̗̳̹̪̩̣̹̼̹̭̫̮͚̣̹̝͚̞͖͈̹̳͈̙͙͎̼̠̩̜̬̙̰͈̮̰̣͒͐̇̽̇͋̈̓̓̌͒̃̒͜͜͝ͅͅw̶̢̪̤̫̱̩̣͕̱͓̩͓̩̱̞̯͉͔̺̙͇̝̹̻̯̮͔̰̙̾̏̑̑͗̅͆͂͊͛̒͐̓̈́̃͂́͜͠͝ḋ̴̡̯̱̞͈̦̯͍̩̯͌̀̑̓̔̈́̅̓͋̃͂̈́̈́͆́̄̓́̄̚̕̚͝".length(); StringFogImpl.decrypt("xxx/xxx+xxx+xxx/i6Q="); "".length(); "p̸̨̩̤̥̜͔͕̙̲͍͚̮̩͉͎͓̣̗͕̽̉͛͑̀̏̿̾̿̏̾̋͋͊̋́̈́̊̈́͗̃̃̅̈́̇̏̂͑̊̚͝͠".length(); "i̷̢̡̡̢̢̨̹͓̼̦̣͇̖͇͕̬̠̖̗̞̝͖̤̮̠̩̰̰̭̱̟̞͈̫͉̔̽̌̓̏͜͝ͅb̶̢̛͚̝̰̺̱̲͖̠̘͚͕͉͇̭̗͇̙̗̣͍̲͖̽͗̐̅̾̅́͌̂̚͘͝".length(); "g̵̢̢̛̛͎͈͚̟͎͖͙͖̗̞̫̠͎͍̘͈͕͖͇̯͐͑̔̊͑͛͑͌͑̀̓͠z̴̨̧̛̛̛̞̱̗͍̖̰͉̝̯̗̻̰̙̹̺̪̭͇̫̪̫̟̗̖͚̼̩͔̝̥͍̹͈̜̆̉̈́̋̀͐̈́̾̉̀̄̈́̀͗̏͆̾̇̆͑̂̂̔̆̎́̇̕̚͜͜͝͠ḋ̴̡̯̱̞͈̦̯͍̩̯͌̀̑̓̔̈́̅̓͋̃͂̈́̈́͆́̄̓́̄̚̕̚͝".length(); "ų̴̢̢̨̨̹̩̙͎̝̪͔͖͎͙̙̖̦͍͓͈̰̜͚̤̼̺̯̙͍͖͎̦͔̯̻͚̭̎̾̈́̊͊̇̃̚̚̕ͅơ̸̡̡̛͓̰̖͉̤͉̖͔̰͍̤̠̠̱̫͎̭̩͖̻̞̼͓̞͋͐́̉͑̿́͋̌̂̀͊͛̀̄̊͊̓̄͘̚̕̕͜͝͝ͅͅa̸̧̻̭̥̩̮̲̝̹̩͍͚̤͈̠̭͓̜̬̭̥͊͗̓̅͋̉͐̿̐̇̑̓̎̒̔̏̇̃̉̂̾͂͋̌̑̀̾̈́̔̊̓̂́̕̕͘͜͠͠͠͠͝".length(); "".length(); byte[] bArr = new byte[8192]; while (true) { "ơ̸̡̡̛͓̰̖͉̤͉̖͔̰͍̤̠̠̱̫͎̭̩͖̻̞̼͓̞͋͐́̉͑̿́͋̌̂̀͊͛̀̄̊͊̓̄͘̚̕̕͜͝͝ͅͅc̸̡̢̨̨͕̯̝̲̟͍̩̹͍̪̼͖͈͍̺͓͎͎̖̗̠̞̰̻͓͎̟͚̝͙͓̘͇̯̺̞͚̬̓̉̋͗̅̔̈́́̃͌́̇͛͂̏̉̏̉̂͒̍͜m̵̡̫̙͔̜͙͇͈̝̲̱͕̱̤̟̭̭̙̠͎̬̦͇͎͙͓̰̣̻̥̦̒͊̎̈́̾̾̾͒̃͂͐̉̎̌͂̀͒͗͒̇́̀͛̌́͊̓̈́̀́̀̂̀̕̚̚̚͜͝͝͝͝͝͠͝".length(); "v̴̧̭̟̳̟͉͎̜̗͍͚̭̙̠͓̝̝̘͇̱͈̊́̆̑̈̽̑͂͒̌̀̓̊̆̆̊̈́̋͐̎̽̇̄͆̅̉̈́̽͆̚̕̕̕̕͜͜w̶̢̪̤̫̱̩̣͕̱͓̩͓̩̱̞̯͉͔̺̙͇̝̹̻̯̮͔̰̙̾̏̑̑͗̅͆͂͊͛̒͐̓̈́̃͂́͜͠͝i̷̢̡̡̢̢̨̹͓̼̦̣͇̖͇͕̬̠̖̗̞̝͖̤̮̠̩̰̰̭̱̟̞͈̫͉̔̽̌̓̏͜͝ͅȟ̸̡̢̨͍͚̝̲̝̫̟̲͎̲̼̬̦͓̺̭̼̪̜̠͈̭̗̳̳̣͓̟̲̻̜͇̰̞̝̾͂͌̈́̎́́̔̈́͋̀̿̀̍̈́͆͊̔̿͊̅̓́̈́̆̃̽͛̋͂̄̓̌̂͆̚͜͜͝͝͝͠͝".length(); int i = inputStream.read(bArr); StringFogImpl.decrypt("xxx+xxx+xx/xxx/1yJjF4LqZwYuw9M+xx+xx+x+xx+xx+G5mN+Lq/xx+x/x+x==").length(); "c̸̡̢̨̨͕̯̝̲̟͍̩̹͍̪̼͖͈͍̺͓͎͎̖̗̠̞̰̻͓͎̟͚̝͙͓̘͇̯̺̞͚̬̓̉̋͗̅̔̈́́̃͌́̇͛͂̏̉̏̉̂͒̍͜a̸̧̻̭̥̩̮̲̝̹̩͍͚̤͈̠̭͓̜̬̭̥͊͗̓̅͋̉͐̿̐̇̑̓̎̒̔̏̇̃̉̂̾͂͋̌̑̀̾̈́̔̊̓̂́̕̕͘͜͠͠͠͠͝".length(); "ẙ̵̡̖͚̫̘̩͇͉͔̖͍̹͈̘͕̥͎̻̘͖̲̲͔̦͎̣͖͈̯̹̈́̓̋̈́̋̓͆̒̔̈́̒̎́̏̀͊̅̆̇͝͠͠ͅ".length(); "l̶̛͙͖͖͙̻̪̦͚͓̖̳̤͙͖̭͖͖̘̠̠͉̲̬͎̫̼̗̹̪͉͚̫̗͍͉͍̹͕̻̯̰̏͌̊̆̒̈́̍̀́̽̾̎̃̒́̆̽̂̄̆̕͝".length(); "k̶̨̢̡̧̧̛̼̠̺̻̱̪̟͕̻̮̼̲̥̭͚͓͖͙̼̤̠̃̐̑͊͒͛͗̈́͂̾̓̾̎̿̆̅̊̒̈́͐̍̒̉̈́̈́̽͑̄̈́̊̄̒͆̾̎̕̕̚̕͜ḋ̴̡̯̱̞͈̦̯͍̩̯͌̀̑̓̔̈́̅̓͋̃͂̈́̈́͆́̄̓́̄̚̕̚͝v̴̧̭̟̳̟͉͎̜̗͍͚̭̙̠͓̝̝̘͇̱͈̊́̆̑̈̽̑͂͒̌̀̓̊̆̆̊̈́̋͐̎̽̇̄͆̅̉̈́̽͆̚̕̕̕̕͜͜j̴̨̛̦̱̦̮̪̲͖̱͚͈̹̭̊̂͛̍̎̇̽͆̎̔̏̂̉̓̓͌̌̂̓͋̐̒̽̂̿̔̈́͗͘̕͝͝͝͝͝ͅ".length(); "ḋ̴̡̯̱̞͈̦̯͍̩̯͌̀̑̓̔̈́̅̓͋̃͂̈́̈́͆́̄̓́̄̚̕̚͝f̴̡̡̡̢̗̻̮̬͇̪͓̤̣̰̝̩̲͇̹̪̩̻͎͍͙̼̖͈̥̲̱̈̑͆͊͋̍̎́͆͋͛̈̑̾̇̈́͊͘͝͝͝".length(); "w̶̢̪̤̫̱̩̣͕̱͓̩͓̩̱̞̯͉͔̺̙͇̝̹̻̯̮͔̰̙̾̏̑̑͗̅͆͂͊͛̒͐̓̈́̃͂́͜͠͝k̶̨̢̡̧̧̛̼̠̺̻̱̪̟͕̻̮̼̲̥̭͚͓͖͙̼̤̠̃̐̑͊͒͛͗̈́͂̾̓̾̎̿̆̅̊̒̈́͐̍̒̉̈́̈́̽͑̄̈́̊̄̒͆̾̎̕̕̚̕͜".length(); "ẙ̵̡̖͚̫̘̩͇͉͔̖͍̹͈̘͕̥͎̻̘͖̲̲͔̦͎̣͖͈̯̹̈́̓̋̈́̋̓͆̒̔̈́̒̎́̏̀͊̅̆̇͝͠͠ͅt̷̢̨̗̳̹̪̩̣̹̼̹̭̫̮͚̣̹̝͚̞͖͈̹̳͈̙͙͎̼̠̩̜̬̙̰͈̮̰̣͒͐̇̽̇͋̈̓̓̌͒̃̒͜͜͝ͅͅt̷̢̨̗̳̹̪̩̣̹̼̹̭̫̮͚̣̹̝͚̞͖͈̹̳͈̙͙͎̼̠̩̜̬̙̰͈̮̰̣͒͐̇̽̇͋̈̓̓̌͒̃̒͜͜͝ͅͅẙ̵̡̖͚̫̘̩͇͉͔̖͍̹͈̘͕̥͎̻̘͖̲̲͔̦͎̣͖͈̯̹̈́̓̋̈́̋̓͆̒̔̈́̒̎́̏̀͊̅̆̇͝͠͠ͅ".length(); "".length(); "".length(); "ȩ̵̛̛̹̱̳̝̱̝̌̊̓̍̋̃͐̂̌͌̔̈́̊̋̔͑̍́͋͆͛̏̑͒͐̃́͝͠͝ͅḋ̴̡̯̱̞͈̦̯͍̩̯͌̀̑̓̔̈́̅̓͋̃͂̈́̈́͆́̄̓́̄̚̕̚͝w̶̢̪̤̫̱̩̣͕̱͓̩͓̩̱̞̯͉͔̺̙͇̝̹̻̯̮͔̰̙̾̏̑̑͗̅͆͂͊͛̒͐̓̈́̃͂́͜͠͝ȩ̵̛̛̹̱̳̝̱̝̌̊̓̍̋̃͐̂̌͌̔̈́̊̋̔͑̍́͋͆͛̏̑͒͐̃́͝͠͝ͅ".length(); "ų̴̢̢̨̨̹̩̙͎̝̪͔͖͎͙̙̖̦͍͓͈̰̜͚̤̼̺̯̙͍͖͎̦͔̯̻͚̭̎̾̈́̊͊̇̃̚̚̕ͅ".length(); "ḋ̴̡̯̱̞͈̦̯͍̩̯͌̀̑̓̔̈́̅̓͋̃͂̈́̈́͆́̄̓́̄̚̕̚͝".length(); "ų̴̢̢̨̨̹̩̙͎̝̪͔͖͎͙̙̖̦͍͓͈̰̜͚̤̼̺̯̙͍͖͎̦͔̯̻͚̭̎̾̈́̊͊̇̃̚̚̕ͅa̸̧̻̭̥̩̮̲̝̹̩͍͚̤͈̠̭͓̜̬̭̥͊͗̓̅͋̉͐̿̐̇̑̓̎̒̔̏̇̃̉̂̾͂͋̌̑̀̾̈́̔̊̓̂́̕̕͘͜͠͠͠͠͝s̷̢̧̲̱̳̖̬͙̱͕̮̩̺̣̪̼͔̮͚͓͇͓̲̣̥͇̬̪̹̞̼̣͌͊̄̏̅̇̾͜͠ͅ".length(); "f̴̡̡̡̢̗̻̮̬͇̪͓̤̣̰̝̩̲͇̹̪̩̻͎͍͙̼̖͈̥̲̱̈̑͆͊͋̍̎́͆͋͛̈̑̾̇̈́͊͘͝͝͝k̶̨̢̡̧̧̛̼̠̺̻̱̪̟͕̻̮̼̲̥̭͚͓͖͙̼̤̠̃̐̑͊͒͛͗̈́͂̾̓̾̎̿̆̅̊̒̈́͐̍̒̉̈́̈́̽͑̄̈́̊̄̒͆̾̎̕̕̚̕͜m̵̡̫̙͔̜͙͇͈̝̲̱͕̱̤̟̭̭̙̠͎̬̦͇͎͙͓̰̣̻̥̦̒͊̎̈́̾̾̾͒̃͂͐̉̎̌͂̀͒͗͒̇́̀͛̌́͊̓̈́̀́̀̂̀̕̚̚̚͜͝͝͝͝͝͠͝".length(); "ḋ̴̡̯̱̞͈̦̯͍̩̯͌̀̑̓̔̈́̅̓͋̃͂̈́̈́͆́̄̓́̄̚̕̚͝".length(); if (i == -1) { return; } "i̷̢̡̡̢̢̨̹͓̼̦̣͇̖͇͕̬̠̖̗̞̝͖̤̮̠̩̰̰̭̱̟̞͈̫͉̔̽̌̓̏͜͝ͅȟ̸̡̢̨͍͚̝̲̝̫̟̲͎̲̼̬̦͓̺̭̼̪̜̠͈̭̗̳̳̣͓̟̲̻̜͇̰̞̝̾͂͌̈́̎́́̔̈́͋̀̿̀̍̈́͆͊̔̿͊̅̓́̈́̆̃̽͛̋͂̄̓̌̂͆̚͜͜͝͝͝͠͝t̷̢̨̗̳̹̪̩̣̹̼̹̭̫̮͚̣̹̝͚̞͖͈̹̳͈̙͙͎̼̠̩̜̬̙̰͈̮̰̣͒͐̇̽̇͋̈̓̓̌͒̃̒͜͜͝ͅͅ".length(); "q̸͍̟͍͕̹͉̤̱̏̆͗́̈́̓̈̆̊͝͝͝".length(); "ŗ̶̢̢̛̛͇͉͍̪̳̟̞͔̥̣̞̙̩̬̻̱͉͚̭̲̭̫̩̘̺͇̬̱̰͍̖͕͎͎̺̻̪̑͌̋̊̿̆̊͛̅̔̊͛̑̍́͊̐̾͘͜͠͠ͅͅŗ̶̢̢̛̛͇͉͍̪̳̟̞͔̥̣̞̙̩̬̻̱͉͚̭̲̭̫̩̘̺͇̬̱̰͍̖͕͎͎̺̻̪̑͌̋̊̿̆̊͛̅̔̊͛̑̍́͊̐̾͘͜͠͠ͅͅg̵̢̢̛̛͎͈͚̟͎͖͙͖̗̞̫̠͎͍̘͈͕͖͇̯͐͑̔̊͑͛͑͌͑̀̓͠".length(); "j̴̨̛̦̱̦̮̪̲͖̱͚͈̹̭̊̂͛̍̎̇̽͆̎̔̏̂̉̓̓͌̌̂̓͋̐̒̽̂̿̔̈́͗͘̕͝͝͝͝͝ͅ".length(); "v̴̧̭̟̳̟͉͎̜̗͍͚̭̙̠͓̝̝̘͇̱͈̊́̆̑̈̽̑͂͒̌̀̓̊̆̆̊̈́̋͐̎̽̇̄͆̅̉̈́̽͆̚̕̕̕̕͜͜v̴̧̭̟̳̟͉͎̜̗͍͚̭̙̠͓̝̝̘͇̱͈̊́̆̑̈̽̑͂͒̌̀̓̊̆̆̊̈́̋͐̎̽̇̄͆̅̉̈́̽͆̚̕̕̕̕͜͜".length(); "".length(); "j̴̨̛̦̱̦̮̪̲͖̱͚͈̹̭̊̂͛̍̎̇̽͆̎̔̏̂̉̓̓͌̌̂̓͋̐̒̽̂̿̔̈́͗͘̕͝͝͝͝͝ͅ".length(); "".length(); "j̴̨̛̦̱̦̮̪̲͖̱͚͈̹̭̊̂͛̍̎̇̽͆̎̔̏̂̉̓̓͌̌̂̓͋̐̒̽̂̿̔̈́͗͘̕͝͝͝͝͝ͅ".length(); "n̷̢̡̛͕̟͚͙̳̯͇͍͍͙̙͕̲͎͚̦̳͕̱͕̺̱̬͓̖̠̻͎̜̮̥̝̼̋̈́͑͑̾̅̾̐̿j̴̨̛̦̱̦̮̪̲͖̱͚͈̹̭̊̂͛̍̎̇̽͆̎̔̏̂̉̓̓͌̌̂̓͋̐̒̽̂̿̔̈́͗͘̕͝͝͝͝͝ͅb̶̢̛͚̝̰̺̱̲͖̠̘͚͕͉͇̭̗͇̙̗̣͍̲͖̽͗̐̅̾̅́͌̂̚͘͝".length(); "s̷̢̧̲̱̳̖̬͙̱͕̮̩̺̣̪̼͔̮͚͓͇͓̲̣̥͇̬̪̹̞̼̣͌͊̄̏̅̇̾͜͠ͅ".length(); "k̶̨̢̡̧̧̛̼̠̺̻̱̪̟͕̻̮̼̲̥̭͚͓͖͙̼̤̠̃̐̑͊͒͛͗̈́͂̾̓̾̎̿̆̅̊̒̈́͐̍̒̉̈́̈́̽͑̄̈́̊̄̒͆̾̎̕̕̚̕͜ơ̸̡̡̛͓̰̖͉̤͉̖͔̰͍̤̠̠̱̫͎̭̩͖̻̞̼͓̞͋͐́̉͑̿́͋̌̂̀͊͛̀̄̊͊̓̄͘̚̕̕͜͝͝ͅͅẙ̵̡̖͚̫̘̩͇͉͔̖͍̹͈̘͕̥͎̻̘͖̲̲͔̦͎̣͖͈̯̹̈́̓̋̈́̋̓͆̒̔̈́̒̎́̏̀͊̅̆̇͝͠͠ͅ".length(); "ŗ̶̢̢̛̛͇͉͍̪̳̟̞͔̥̣̞̙̩̬̻̱͉͚̭̲̭̫̩̘̺͇̬̱̰͍̖͕͎͎̺̻̪̑͌̋̊̿̆̊͛̅̔̊͛̑̍́͊̐̾͘͜͠͠ͅͅl̶̛͙͖͖͙̻̪̦͚͓̖̳̤͙͖̭͖͖̘̠̠͉̲̬͎̫̼̗̹̪͉͚̫̗͍͉͍̹͕̻̯̰̏͌̊̆̒̈́̍̀́̽̾̎̃̒́̆̽̂̄̆̕͝".length(); outputStream.write(bArr, 0, i); } }
这是一种在安卓Java代码中插入花指令的混淆后的APK,其中有两种问题需要处理,一是无用的字符串调用了length(),还有一种是
StringFogImpl.decrypt(xxx)
下面的这种还有些地方加入了对字符串参数的加密。这两种情况该如何处理?
实际逆向分析工程中,有三种解决方案:
第一种是直接反编译使用正则表达式进行匹配清除。
第二种使用编译器Pass技术编写自定义Pass处理IR与CFG。
第三种是使用反编译工具配合插件来完成去混淆。
这三种方式各有优缺点。第一种开发起来代码最简单,但要考虑的细节非常多。通用性相关较差;第二种开发起来复杂,但通用性较好;第三种需要反编译工具配合,开发插件成本比第一种难度高,比第二种难度低。
这三种方式会在安卓逆向第四阶段的06小节全部实战讲解。
本篇文章以第二种方式06-02的实现方式为例。
它的DEX指令为:
.method public static bridge synthetic write(Ljava/io/InputStream;Ljava/io/OutputStream;)V .registers 8 .annotation system Ldalvik/annotation/Signature; value = { "(", "Ljava/io/InputStream;", "Ljava/io/OutputStream;", ")V^", "Ljava/io/IOException;" } .end annotation .annotation system Ldalvik/annotation/Throws; value = { Ljava/io/IOException; } .end annotation const-string v0, "y\xxxxx\u033b\u0318\u0356\u0345\u0332\u0332\u0354\u0326\u034e\u0323\u0356\u0348\u032f\u0339\u0321" invoke-virtual {v0}, Ljava/lang/String;->length()I const-string v1, "r\uxxxxx2\u031c\u0320\u03x1" invoke-virtual {v1}, Ljava/lang/String;->length()I const-string v1, "" invoke-virtual {v1}, Ljava/lang/String;->length()I const-string v2, "Ppjw4buZxIq89d+x+x/x+Jnc4KuYwou09OmY4uGYmfM=" invoke-static {v2}, Lcom/github/megatronking/stringfog/xor/StringFogImpl;->decrypt(Ljava/lang/String;)Ljava/lang/String; const-string v2, "MJjz4bSZ3ouw9MaYy+x+x/x=" invoke-static {v2}, Lcom/github/megatronking/stringfog/xor/StringFogImpl;->decrypt(Ljava/lang/String;)Ljava/lang/String; const-string v2, "x+i7708Zjl4YiZ9YqM9MiY7+x/x=" invoke-static {v2}, Lcom/github/megatronking/stringfog/xor/StringFogImpl;->decrypt(Ljava/lang/String;)Ljava/lang/String; invoke-virtual {v1}, Ljava/lang/String;->length()I const-string v2, "s\u0337\u0360\u034c\u034a\u0304\u030f\u0305\u0307\u033e\u0332\u0331\u0333\u0316\u032c\u0359\u0331\u0355\u032e\u0329\u033a\u0323\u032a\u033c\u0354\u032e\u0345\u035a\u0353\u0347\u0353\u0332\u0323\u0325\u035c\u0347\u032c\u032a\u0322\u0339\u031e\u033c\u0327\u0323u\u0334\u030e\u033e\u031a\u0344\u030a\u031a\u0315\u034a\u0307\u0303\u0339\u0328\u0322\u0329\u0319\u034e\u031d\u0322\u0328\u032a\u0354\u0356\u034e\u0359\u0319\u0316\u0326\u034d\u0353\u0345\u0348\u0330\u031c\u035a\u0328\u0324\u033c\u033a\u032f\u0319\u034d\u0356\u034e\u0326\u0354\u032f\u033b\u035a\u032dk\u0336\u0303\u0310\u0311\u034a\u0352\u035b\u0357\u0344\u0342\u031b\u033e\u0315\u0343\u033e\u030e\u033f\u0306\u0305\u030a\u0312\u0344\u0350\u030d\u0312\u0315\u0309\u0344\u031a\u0344\u033d\u0351\u0304\u0344\u0315\u030a\u0304\u0312\u0346\u033e\u030e\u0328\u033c\u0320\u033a\u033b\u0322\u0331\u032a\u0321\u031f\u0355\u035c\u033b\u0327\u032e\u033c\u0332\u0325\u032d\u035a\u0353\u0356\u0359\u033c\u0324\u0320\u0327y\u0335\u030a\u035d\u0344\u0343\u0360\u030b\u0344\u030b\u0343\u0346\u0360\u0312\u0314\u0344\u0312\u030e\u0301\u030f\u0340\u034a\u0305\u0306\u0307\u0316\u035a\u032b\u0318\u0329\u0347\u0349\u0354\u0316\u034d\u0339\u0348\u0318\u0355\u0325\u034e\u033b\u0318\u0356\u0345\u0332\u0332\u0354\u0326\u034e\u0323\u0356\u0348\u032f\u0339\u0321" invoke-virtual {v2}, Ljava/lang/String;->length()I ... invoke-virtual {v2}, Ljava/lang/String;->length()I const-string v2, "uxxxu0315\u0342\u034b\u030c\u0311\u0315\u0340\u033e\u0358\u0344\u0360\u0314\u030a\u0343\u0302\u035d\u0301\u033b\u032d\u035c\u0325\u0329\u032e\u0332\u031d\u0339\u0329\u034d\u035a\u0324\u0348\u0320\u032d\u0353\u031c\u032c\u032d\u0325\u0327" invoke-virtual {v2}, Ljava/lang/String;->length()I invoke-virtual {v1}, Ljava/lang/String;->length()I const/16 v2, 0x2000 new-array v2, v2, [B :goto_62 ... invoke-virtual {v4}, Ljava/lang/String;->length()I const/4 v4, -0x1 if-ne v3, v4, :cond_c0 return-void :cond_c0 const-string v4, "i\u0337\u0314\u033d\u030c\u035d\u0313\u030f\u0339\u0322\u0353\u033c\u0326\u0323\u0347\u035c\u0316\u0347\u0355\u032c\u0320\u0321\u0316\u0317\u0359\u034e\u033c\u0320\u035c\u0329\u031c\u032c\u0319\u0330\u0348\u032e\u0330\u0323" invoke-virtual {v4}, Ljava/lang/String;->length()I const-string v4, "q\u0338\u030f\u0306\u035d\u0357\u035d\u0301\u0344\u0343\u0308\u0306\u030a\u035d\u034d\u031f\u034d\u0355\u0339\u0349\u0324\u0331" invoke-virtual {v4}, Ljava/lang/String;->length()I const-string v4, "r\u0336\u0311\u034c\u0360\u030b\u030a\u0358\u033f\u0306\u030a\u035b\u0305\u0314\u030a\u035b\u0360\u0311\u030d\u0341\u034a\u0340\u031b\u0343\u031b\u0322\u034e\u0348\u035a\u0322\u031f\u034e\u0356\u0359\u0356\u0317\u031e\u032b\u0320\u034e\u034d\u0318\u0348\u0355\u0356\u0347\u032f" invoke-virtual {v4}, Ljava/lang/String;->length()I const-string v4, "j\u0334\u030a\u035d\\u0356\u0331\u035a\u0348\u0328\u0339\u032d" invoke-virtual {v4}, Ljava/lang/String;->length()I const-string v5, "v\u0334\u030a\u0301\u0306\u0311\u0308\u031a\u033d\u0311\u0342\u0352\u0315\u030c\u0340\u0343\u030a\u0306\u0306\u030a\u0344\u0344\u033d\u0346\u032d\u031f\u035c\u0333\u031f\u0349\u034e\u031c\u0317\u034d\u035a\u032d\u0319\u0320\u035c\u0353\u031d\u031d\u0318\u0327\u0347\u0331\u0348" invoke-virtual {v5}, Ljava/lang/String;->length()I invoke-virtual {v1}, Ljava/lang/String;->length()I invoke-virtual {v4}, Ljava/lang/String;->length()I invoke-virtual {v1}, Ljava/lang/String;->length()I invoke-virtual {v4}, Ljava/lang/String;->length()I const-string v4, "n\u0337\u030b\u0344\u0351\u0351\uxxx\u0349\u0347\u032d\u0317\u0347\u0319\u0317\u0323\u034d\u0332\u0356" invoke-virtual {v4}, Ljava/lang/String;->length()I const-string v4, "s\u0337\u0360\xxx\u0327\u0323" invoke-virtual {v4}, Ljava/lang/String;->length()I const-string v4, "k\u0336\u0303\xxxu\u0339\u0321" invoke-virtual {v4}, Ljava/lang/String;->length()I const-string v4, "r\u0336\u0311\u034c\uxxx\u0330" invoke-virtual {v4}, Ljava/lang/String;->length()I const/4 v4, 0x0 invoke-virtual {p1, v2, v4, v3}, Ljava/io/OutputStream;->write([BII)V goto/16 :goto_62.end method
去除它的思路为首先处理空的或Unicode构建的const-string这类字符串花指令,它们并没有使用创建的字符串结果。代码为:
size_t StringFogDeobfuscatePass::remove_junk_length_patterns( cfg::ControlFlowGraph& cfg) { size_t removed = 0; cfg::CFGMutation mutation(cfg); // 模式:const-string(跨基本块) -> invoke-virtual String.length()I, // 且 length() 的返回值未被使用(没有 move-result),属于“无用干扰代码”。 // // 注意:const-string 往往集中出现在方法开头,而 length() 调用可能在分支之后, // 因此需要跨基本块的 reaching-definitions 来判断 receiver 是否必然来自 const-string。 auto is_const_string = [](const IRInstruction* insn) { return insn->opcode() == OPCODE_CONST_STRING; }; reaching_defs::MoveAwareFixpointIterator reaching_defs(cfg, is_const_string); reaching_defs.run(reaching_defs::Environment()); auto is_string_length = [](const DexMethodRef* method) -> bool { if (!method) return false; auto* cls = method->get_class(); auto* name = method->get_name(); auto* proto = method->get_proto(); if (!(cls && name && proto)) return false; if (cls->str() != "Ljava/lang/String;") return false; if (name->str() != "length") return false; auto* rtype = proto->get_rtype(); auto* args = proto->get_args(); if (!(rtype && args)) return false; if (rtype->str() != "I") return false; return args->size() == 0; }; for (auto* block : cfg.blocks()) { auto env = reaching_defs.get_entry_state_at(block); auto ii = InstructionIterable(block); for (auto it = ii.begin(); it != ii.end(); ++it) { auto* insn = it->insn; auto op = insn->opcode(); auto cfg_it = block->to_cfg_instruction_iterator(it); if (is_invoke_length(op) && is_string_length(insn->get_method())) { if (insn->srcs_size() >= 1) { reg_t recv = insn->src(0); const auto& defs = env.get(recv); bool receiver_is_const_string = false; if (!defs.is_top() && !defs.is_bottom()) { auto elems = defs.elements(); if (!elems.empty()) { receiver_is_const_string = true; for (auto* def : elems) { if (!def || def->opcode() != OPCODE_CONST_STRING) { receiver_is_const_string = false; break; } } } } if (receiver_is_const_string) { auto move_res = cfg.move_result_of(cfg_it); if (move_res.is_end()) { mutation.remove(cfg_it); removed += 1; } } } } reaching_defs.analyze_instruction(insn, &env); } } mutation.flush(); return removed;}
然后处理decrypt解密的字符串,找出它们来,然后使用解密的 const-string进行替换。完事后,再跑一次remove_junk_length_patterns即可达到完美效果!如下所示,解密后只有很少的代码
处理后对应的DEX指令如下所示:
这种处理方式有一个好处,代码模式可以配置,供以后其它混淆样本使用。二是处理精准,比直接使用正则处理更准备。
第三种可以使用JEB来编写IR与CFG的处理插件。原理类似,并且JEB内置的功能也能处理掉一些,开发起来使用py或Java都行。
所有三种方式处理的代码与样本将会在第四阶段更新时发放。
以上!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:软件安全与逆向分析 非虫《基于编译器Pass技术对安卓DEX混淆代码与花指令进行优化》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论