【问题标题】:Hiding strings in Obfuscated code在混淆代码中隐藏字符串
【发布时间】:2016-06-05 01:42:31
【问题描述】:

我只是使用 proguard 对我的 Android 代码进行了混淆,然后对其进行了反编译。有许多字符串我真的很想隐藏以免被窥探。当我反编译我的代码时,每个人都可以看到字符串......并进行更改。其中一个字符串是我的许可服务器的 URL,它们实际上可以将 url 更改为指向假服务器(因为我将向公众发布服务器代码)。隐藏此类信息的最佳方法是什么?

另外,我注意到 R 类字符串都是随机数,但我在反编译的代码中找不到 R 类。它在哪里?

敌人的例子我明白了:new SimpleCursorAdapter(localActivity, 2130903058, localCursor, arrayOfString, arrayOfInt);

2130903058 是一个布局文件,但它引用的是什么?这个数字没有任何意义,除非它指向某种地址。

【问题讨论】:

    标签: java android proguard


    【解决方案1】:

    假设您对模糊而不是安全感到满意,您可以使用许多机制,但像 proguard 这样的混淆器将无法为您提供帮助。

    要实现这一点,您需要自己对字符串进行编码或加密,您使用的方法取决于您要防御的内容,如果您只是想隐藏明显的检查,那么编码可能就足够了(参见 android.util.Base64,http://developer.android.com/reference/android/util/Base64.html)。请注意,编码是不安全的,它所要做的就是删除对您网站的明显引用。

    如果您试图防御更多的东西,那么您可以转向实际加密字符串,为此您可以通过 javax.crypto.Cipher 使用像 AES 这样的对称密码,http://www.androidsnippets.org/snippets/39/index.html 提供了一个不错的用法示例。同样,这对黑客来说更烦人,然后更安全,因为您需要将密钥存储在 jar 中的某个位置,从而否定任何加密安全性。

    为了更清楚地说明这一点,基本步骤是:

    1. 使用已知密钥手动创建加密字符串。
    2. 将您的代码转换为使用此字符串的解密版本,例如:

    之前:

    public class Foo {
        private String mySecret = "http://example.com";
    
        ...
    }
    

    变成:

    public class Foo {
        private String encrypted = "<manually created encrypted string>";
        private String key = "<key used for encryption";
        private String mySecret = MyDecryptUtil.decrypt(encrypted, key);
    
        ...
    }
    

    所有这一切的(好的)替代方案是考虑使用第三方 drm 解决方案,例如 google 提供的许可服务器 http://android-developers.blogspot.com/2010/07/licensing-service-for-android.html。这可能比你自己滚动的东西更安全,但会受到与我上面描述的非常相似的限制。

    【讨论】:

    • 在服务器上存储一些类文件怎么样。是否可以在安装应用程序后下载和安装新的类文件?有没有办法以安全的方式做到这一点,即不允许某人复制已注册设备的文件并仅使用它们?
    • 您可以添加许多层,但最终,您将无法阻止坚定的黑客。在某些时候,您最好将时间投入到产品的其余部分,使其足够好(读作足够有价值)并且人们不会想要窃取它。
    • “到最后,你将无法阻止一个坚定的黑客” --> 这些是这个长线程中最好的词。马克说得对,我们能做的最好的就是减缓攻击者的速度。
    • 也许我遗漏了一些东西,但加密 URL 似乎并不更安全,因为您仍然必须在代码中包含用于解密值的密钥。有决心的黑客仍然可以反编译 APK,获取密钥,然后手动解密。
    • 看看我用肉眼隐藏api键、令牌等的例子:gist.github.com/shomeser/68f4fe360be0edac95e4
    【解决方案2】:

    大家好。

    1. secret成为你要隐藏的文字

    2. 找到你的 debug/release.keystore 的 keyhash。让k1成为这把钥匙。

    (使用工具keytool+openssl:keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64

    1. 使用工具(android 代码外部)将secret 加密为k1

      encrypted = encode (secret, k1)

    (例如:https://jwt.io,对于 java:https://github.com/jwtk/jjwt)。

    1. 在你的android java代码中写下encrypted。当你需要encrypted的解码版本时(这个是原来的secret)写

    original = decode(encrypted, get_my_keyhash_programmatically() )

    就是这样。这是因为原始的secret 没有显示在java 源代码中,k1 也没有对其进行解码。而且,如果黑客想要打印您的 解密后的秘密,他必须更改代码并重新编译,签署他的 .apk 他自己的密钥库不是你的,因此没有得到正确的原件 secret。 (“唯一”的一点是 k1 是否可以从您的原始 .apk 中找出)。

    注意:get_my_keyhash_programmatically():

    try {
        PackageInfo info = getPackageManager().getPackageInfo(
                "el nombre de su paquete por ejemplo com.tarea.u8",
                PackageManager.GET_SIGNATURES);
        for (Signature signature : info.signatures) {
            MessageDigest md = MessageDigest.getInstance("SHA");
            md.update(signature.toByteArray());
            Log.d("KeyHash:", Base64.encodeToString(md.digest(), Base64.DEFAULT));
        }
    } catch (PackageManager.NameNotFoundException e) {
    
    } catch (NoSuchAlgorithmException e) {
    
    }
    

    【讨论】:

    • 您说“使用工具用 k1 加密机密 - 例如:jwt.io。”但是,当我去 jwt.io 并尝试使用它从我自己的 JSON 中创建一个令牌时,使用我自己的密钥(把它放在“秘密”字段中),它只是告诉我这个秘密是无效的。 "secret" 是它接受的唯一字符串。那么如何使用自己的密钥创建令牌?
    • @Krypton 喜欢什么?请给我一个例子来打破这个
    • 一个有经验的黑客应该在5分钟内反转java函数encode(),他可以很容易地发现应用程序的证书哈希用于编码。使用像 XPosed 这样的挂钩框架,他可以在运行时提取应用程序的证书哈希。从那时起,他使用该哈希来解码所有字符串。
    • 这不会在开发过程中产生问题吗?我认为 Android Studio 使用自动生成的证书对调试版本进行签名,该证书对所有开发人员都不同,并且易于使用。
    • @cibercitizen1 你能告诉我下面这行 original = decode(encrypted, get_my_keyhash_programmatically() ) 我猜 original 是我们要保存“秘密”密钥的字符串... get_key_hash 是也可以理解。但是您使用的是哪个类的 decode 方法。我没有得到这个解码方法。
    【解决方案3】:

    我所做的是在我的全局实用程序类中创建一长串静态字符串。在长长的字符串列表中的某个地方,我将密码分成多个块。

    使用我的代码很容易看出真正的密钥是什么——但是一旦混淆器开始工作,所有的静态变量都会有 A、B、C 等名称,而且再也不容易发现了。

    【讨论】:

    • 您能否提供您所说的示例代码。谢谢
    • 嗨,山姆,只需创建一个包含一大堆 public static String variable1 = "fake data"; 的公共类,当我说“一大堆”时,我的意思是像一百个这样的字符串。使用 excel 创建这样的文件很容易。然后,在所有这些“假”行中隐藏一些重要数据。一旦混淆器开始工作,所有这些数据都会看起来一团糟。当您想使用数据时,组合几个单独的字符串来重新创建您想要隐藏的内容。你可以更进一步,以某种方式对这些文本行进行编码,使其看起来更像是一团糟。
    • 重点是:让正在对您的代码进行逆向工程的人必须为它工作。你做得越不吸引人,就越有可能不值得他们花时间。
    • 需要注意的是,您必须在某处使用代码,因为编译器/proguard 可能会删除未使用的代码,即不要只转储一堆未使用的字符串变量。
    • 至于组合字符串.. 根据你的做法,proguard 可能会将它们与字符串生成器放在一起。基本上你应该检查反编译的输出
    【解决方案4】:

    我使用了 ROT47。它不是很安全,但易于使用和实现,因为它是一个对称的编码器/解码器

    【讨论】:

      【解决方案5】:

      您应该在 Google 上搜索“Just another Perl hacker”。这些是打印出带有混淆代码的字符串的程序。除了 Perl,网上还有很多其他语言的例子。

      Wikipedia entry

      【讨论】:

      • 是的,但如果黑客知道你使用过 JAPH,那么他/她可以轻松解密你的 api 密钥 ??
      【解决方案6】:

      这是我目前使用的,它有 hack 来支持 sprintf 函数,这些函数会在编译的二进制文件中溢出纯文本。您现在可以使用 w_sprintf_s 而不是 sprintf,就像这样

      char test[256] = { 0 };
      w_sprintf_s(test, 256, XorStr("test test :D %d %+d\n"), 1, 1337);
      

      或者像这样使用它在屏幕上打印东西,例如

      w_printf(XorStr("test I print this and can't see me inside .dll or .exe"));
      

      适用于变量,如果您有自定义 printf(),您也可以使用它..

      char szGuid[255] = { 0 };
      //generate serial code removed.
      char finalSerial[512] = { 0 };
      XorCompileTime::w_sprintf(finalSerial, XorStr("serial information=%s"), szGuid);
      myprintf(XorStr("Your Hardware ID: %s\n"), szGuid);
      


      可能会像 arkan 那样添加对 wchar_t 宽字符串的支持。但我现在对它们没有用处,因为我没有在符号/unicode 中写任何东西。

      这是一个文件,只需将下面的代码重命名为 XorString.h 文件并将其包含在您的项目中,就这么简单

      #pragma once
      #include <string>
      #include <array>
      #include <cstdarg>
      
      #define BEGIN_NAMESPACE( x ) namespace x {
      #define END_NAMESPACE }
      
      BEGIN_NAMESPACE(XorCompileTime)
      
      constexpr auto time = __TIME__;
      constexpr auto seed = static_cast< int >(time[7]) + static_cast< int >(time[6]) * 10 + static_cast< int >(time[4]) * 60 + static_cast< int >(time[3]) * 600 + static_cast< int >(time[1]) * 3600 + static_cast< int >(time[0]) * 36000;
      
      // 1988, Stephen Park and Keith Miller
      // "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard"
      // Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation:
      // with 32-bit math and without division
      
      template < int N >
      struct RandomGenerator
      {
      private:
          static constexpr unsigned a = 16807; // 7^5
          static constexpr unsigned m = 2147483647; // 2^31 - 1
      
          static constexpr unsigned s = RandomGenerator< N - 1 >::value;
          static constexpr unsigned lo = a * (s & 0xFFFF); // Multiply lower 16 bits by 16807
          static constexpr unsigned hi = a * (s >> 16); // Multiply higher 16 bits by 16807
          static constexpr unsigned lo2 = lo + ((hi & 0x7FFF) << 16); // Combine lower 15 bits of hi with lo's upper bits
          static constexpr unsigned hi2 = hi >> 15; // Discard lower 15 bits of hi
          static constexpr unsigned lo3 = lo2 + hi;
      
      public:
          static constexpr unsigned max = m;
          static constexpr unsigned value = lo3 > m ? lo3 - m : lo3;
      };
      
      template <>
      struct RandomGenerator< 0 >
      {
          static constexpr unsigned value = seed;
      };
      
      template < int N, int M >
      struct RandomInt
      {
          static constexpr auto value = RandomGenerator< N + 1 >::value % M;
      };
      
      template < int N >
      struct RandomChar
      {
          static const char value = static_cast< char >(1 + RandomInt< N, 0x7F - 1 >::value);
      };
      
      template < size_t N, int K >
      struct XorString
      {
      private:
          const char _key;
          std::array< char, N + 1 > _encrypted;
      
          constexpr char enc(char c) const
          {
              return c ^ _key;
          }
      
          char dec(char c) const
          {
              return c ^ _key;
          }
      
      public:
          template < size_t... Is >
          constexpr __forceinline XorString(const char* str, std::index_sequence< Is... >) : _key(RandomChar< K >::value), _encrypted{ enc(str[Is])... }
          {
          }
      
          __forceinline decltype(auto) decrypt(void)
          {
              for (size_t i = 0; i < N; ++i) {
                  _encrypted[i] = dec(_encrypted[i]);
              }
              _encrypted[N] = '\0';
              return _encrypted.data();
          }
      };
      
      //--------------------------------------------------------------------------------
      //-- Note: XorStr will __NOT__ work directly with functions like printf.
      //         To work with them you need a wrapper function that takes a const char*
      //         as parameter and passes it to printf and alike.
      //
      //         The Microsoft Compiler/Linker is not working correctly with variadic 
      //         templates!
      //  
      //         Use the functions below or use std::cout (and similar)!
      //--------------------------------------------------------------------------------
      
      static auto w_printf = [](const char* fmt, ...) {
          va_list args;
          va_start(args, fmt);
          vprintf_s(fmt, args);
          va_end(args);
      };
      
      static auto w_printf_s = [](const char* fmt, ...) {
          va_list args;
          va_start(args, fmt);
          vprintf_s(fmt, args);
          va_end(args);
      };
      
      static auto w_sprintf = [](char* buf, const char* fmt, ...) {
          va_list args;
          va_start(args, fmt);
          vsprintf(buf, fmt, args);
          va_end(args);
      };
      
      static auto w_sprintf_s = [](char* buf, size_t buf_size, const char* fmt, ...) {
          va_list args;
          va_start(args, fmt);
          vsprintf_s(buf, buf_size, fmt, args);
          va_end(args);
      };
      
      #define XorStr( s ) ( XorCompileTime::XorString< sizeof( s ) - 1, __COUNTER__ >( s, std::make_index_sequence< sizeof( s ) - 1>() ).decrypt() )
      
      END_NAMESPACE
      

      【讨论】:

        【解决方案7】:

        您可以使用DexGuard 来加密字符串,这可能比您手动实现的效率更高,而且不会增加源代码的负担。

        【讨论】:

        • 但 DexGuard 不是免费的
        • 我认为是免费的,您可以通过电子邮件获取 DexGuard
        • 你能再检查一下,因为据我所知它不是免费的吗??
        猜你喜欢
        • 2014-05-21
        • 1970-01-01
        • 2010-11-02
        • 2017-05-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-17
        相关资源
        最近更新 更多