【问题标题】:Simple hiding/obfuscation of strings in APK?APK中字符串的简单隐藏/混淆?
【发布时间】:2014-05-21 21:39:53
【问题描述】:

有时您需要在应用程序本身中存储密码,例如用于与您自己的服务器通信的用户名/密码。在这些情况下,不可能遵循存储密码的正常过程 - 即散列密码,存储散列,与散列的用户输入进行比较 - 因为您没有任何用户输入来比较散列。密码需要由应用程序本身提供。那么如何保护APK中存储的密码呢?像下面这样的密码生成功能会相当安全吗?

纯文本:

String password = "$()&HDI?=!";

简单的混淆:

private String getPassword(){
    String pool = "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(";
    return pool.substring(4, 7) + pool.substring(20, 24) + pool.substring(8, 11);
}

我知道 ProGuard 有一些混淆功能,但我很好奇上述“混淆”技术在编译时会做什么,以及通过查看 APK 和/或使用其他更复杂的技术?

【问题讨论】:

  • 请注意,攻击它的人可能只是按照输入密码猜测完成的操作。从历史上看,人们倾向于不存储实际密码进行比较,而是存储计算量大的散列函数的结果,即使你知道散列(“答案”),穷举找到匹配的密码也是昂贵的( “问题”)。但在云计算和它的邪恶孪生僵尸网络时代,中等长度的密码可能相当容易破解。
  • 查看我更新的问题。我的意思是密码必须由应用程序本身而不是用户提供的情况。我之前有点不清楚,对此感到抱歉。

标签: android obfuscation decompiling deobfuscation


【解决方案1】:

tl;dr 如果您知道如何反编译 APK,则无论代码多么混乱,您都可以轻松获取密码。不要在 APK 中存储密码,这不安全。

我知道 ProGuard 有一些混淆功能,但我很好奇 关于上述“混淆”技术在编译时的作用, 以及通过查看来弄清楚它会有多难 APK 和/或使用其他更复杂的技术?

我会告诉你它是多么容易。这是一个 Android SSCCE,我们将对其进行反编译:

MyActivity.java:

public class MyActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TextView text = (TextView) findViewById(R.id.text);
        text.setText(getPassword());
    }

    private String getPassword() {
        String pool = "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(";
        return pool.substring(4, 7) + pool.substring(20, 24) + pool.substring(8, 11);
    }
}

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/text"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"/>

编译并运行后,我们可以在TextView 上看到$()&amp;HDI?=!

让我们反编译一个 APK:

  1. unzip myapp.apk 或右键单击 APK 和 Unzip hereclasses.dex 文件出现。
  2. classes.dex 转换为带有dex2jar 的JAR 文件。执行dex2jar.sh classes.dexclasses_dex2jar.jar文件后 出现。
  3. classes_dex2jar.jar上使用一些Java反编译器,例如JD-GUI,我们从MyActivity.class检索这样的Java代码:

    public class MyActivity extends Activity
    {
        private String getPassword()
        {
            return "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(4, 7) 
    + "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(20, 24) 
    + "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(8, 11);
        }
    
        public void onCreate(Bundle paramBundle)
        {
            super.onCreate(paramBundle);
            setContentView(2130903040);
            ((TextView)findViewById(2131034112)).setText(getPassword());
        }
    }
    

ProGuard 帮不上什么忙,代码仍然很容易阅读。

基于以上,我已经可以给你这个问题的答案了:

像下面这样的密码生成功能是否合理 安全吗?

没有。如您所见,它稍微增加了阅读反混淆代码的难度。我们不应该以这种方式混淆代码,因为:

  1. 它给人一种安全的错觉。
  2. 这是在浪费开发人员的时间。
  3. 它会降低代码的可读性。

在官方 Android 文档中,Security and Design 部分中,他们建议这样做以保护您的 Google Play 公钥:

为了保护您的公钥免受恶意用户和黑客的攻击,请不要 将其作为文字字符串嵌入任何代码中。 相反,构建 在运行时从片段中提取字符串或使用位操作(例如, XOR 与其他字符串)来隐藏实际的密钥。 密钥本身是 不是秘密信息,但你不想让它变得容易 黑客或恶意用户用另一个密钥替换公钥。

好的,让我们试试吧:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    TextView text = (TextView) findViewById(R.id.text);
    text.setText(xor("A@NCyw&IHY", "ehge13ovux"));
}

private String xor(String a, String b) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < a.length() && i < b.length(); i++) {
        sb.append((char) (a.charAt(i) ^ b.charAt(i)));
    }
    return sb.toString();
}

TextView 上提供$()&amp;HDI?=!,很好。

反编译版本:

public void onCreate(Bundle paramBundle)
{
    super.onCreate(paramBundle);
    setContentView(2130903040);
    ((TextView)findViewById(2131034112)).setText(xor("A@NCyw&IHY", "ehge13ovux"));
}

private String xor(String paramString1, String paramString2)
{
    StringBuilder localStringBuilder = new StringBuilder();
    for (int i = 0; (i < paramString1.length()) && (i < paramString2.length()); i++) {
        localStringBuilder.append((char)(paramString1.charAt(i) ^ paramString2.charAt(i)));
    }
    return localStringBuilder.toString();
}

和以前的情况非常相似。

即使我们有极其复杂的函数soStrongObfuscationOneGetsBlind(),我们也总是可以运行反编译的代码,看看它产生了什么。或者一步一步调试。

【讨论】:

  • 我喜欢你的回答,但我缺乏如何做的建议。 “不要在 APK 中存储密码”并没有多大帮助,因为有必要在某处讲述 API 密钥。
  • 至少可以为此使用 JNI。做起来可能会有点困难。
猜你喜欢
  • 2016-06-05
  • 2012-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-02
  • 2017-05-16
  • 1970-01-01
相关资源
最近更新 更多