javascript-obfuscator / react-native-obfuscating-transformer

Obfuscation for React Native bundles

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Code not being encrypted

dooleyb1 opened this issue · comments

I have spent a good while playing around with multiple options and examples and can't seem to get it to work. My setup is the following:

"metro-react-native-babel-preset": "0.56.0",
"metro-react-native-babel-transformer": "^0.59.0",
 "react-native-obfuscating-transformer": "^1.0.0",
"react-native": "^0.61.0",

metro.config.js

/**
 * Metro configuration for React Native
 * https://github.com/facebook/react-native
 *
 * @format
 */

module.exports = {
  transformer: {
    babelTransformerPath: require.resolve("./transformer"),
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    }),
  },
};

transformer.js

const obfuscatingTransformer = require("react-native-obfuscating-transformer")

const filter = filename => { 
  return filename.startsWith("src");
};

module.exports = obfuscatingTransformer({
// this configuration is based on https://github.com/javascript-obfuscator/javascript-obfuscator
  obfuscatorOptions:{
    compact: true,
    controlFlowFlattening: false,
    deadCodeInjection: false,
    debugProtection: false,
    debugProtectionInterval: false,
    disableConsoleOutput: true,
    identifierNamesGenerator: 'hexadecimal',
    log: false,
    renameGlobals: false,
    rotateStringArray: true,
    selfDefending: true,
    shuffleStringArray: true,
    splitStrings: false,
    stringArray: true,
    stringArrayEncoding: false,
    stringArrayThreshold: 0.75,
    unicodeEscapeSequence: false
  },
  upstreamTransformer: require('metro-react-native-babel-transformer'),
  emitObfuscatedFiles: false,
  enableInDevelopment: true,
  filter: filter,
  trace: true
})

The command I am using to build the output js bundle for testing is the following:

npx react-native bundle --entry-file=index.js --bundle-output='./bundle.js' --dev=false --platform='ios' --assets-dest='./ios' --reset-cache

I can see the output from stdout of the transformer logging which files are being obfuscated, however when I look at the entire bundle and even the sub obfuscated files, the variable names and function calls are not encrypted.

Below is an example of a before and after obfuscation:

Before (screens.js):

import { Navigation } from 'react-native-navigation';
import { Provider } from 'react-redux';

import firebase from 'react-native-firebase';
const crashlytics = firebase.crashlytics();

import configureStore from 'app/store';

// Initialise global store
export const store = configureStore()

export function registerScreens() {

  crashlytics.enableCrashlyticsCollection();
  crashlytics.log('[utils][screens] - Registering screens...');

  // Register screens with redux stores
  Navigation.registerComponentWithRedux('Initialising', () => require('screens/Initialising').default, Provider, store);
  Navigation.registerComponentWithRedux('Auth', () => require('screens/Auth').default, Provider, store);
  Navigation.registerComponentWithRedux('VerifyPhoneNumber', () => require('screens/VerifyPhoneNumber').default, Provider, store);
  Navigation.registerComponentWithRedux('SignIn', () => require('screens/SignIn').default, Provider, store);

  // Profile Stack
  Navigation.registerComponentWithRedux('Profile', () => require('screens/Profile').default, Provider, store);
  Navigation.registerComponentWithRedux('QRScanner', () => require('screens/Profile/QRScanner').default, Provider, store);

  // Register Stack
  Navigation.registerComponentWithRedux('InitialRegister', () => require('screens/Register/InitialRegister').default, Provider, store);
  Navigation.registerComponentWithRedux('RegisterDetails', () => require('screens/Register/RegisterDetails').default, Provider, store);

  // Rewards Stack
  Navigation.registerComponentWithRedux('Rewards', () => require('screens/Rewards').default, Provider, store);
  Navigation.registerComponentWithRedux('RewardBack', () => require('screens/Rewards/RewardBack').default, Provider, store);
  Navigation.registerComponentWithRedux('RewardRedeemed', () => require('screens/Rewards/RewardRedeemed').default, Provider, store);

  // Home stack
  Navigation.registerComponentWithRedux('Home', () => require('screens/Home').default, Provider, store);

  // Marketplace stack
  Navigation.registerComponentWithRedux('MarketplaceBack', () => require('screens/Rewards/MarketplaceBack').default, Provider, store);

  // Bundles
  Navigation.registerComponentWithRedux('PreBundle', () => require('screens/Home/PreBundle').default, Provider, store);
  Navigation.registerComponentWithRedux('BundleQuestion', () => require('screens/Home/BundleQuestion').default, Provider, store);
  Navigation.registerComponentWithRedux('BundleComplete', () => require('screens/Home/BundleComplete').default, Provider, store);

  // Help Screens
  Navigation.registerComponentWithRedux('HomeHelp', () => require('screens/Help/HomeHelp').default, Provider, store);
  Navigation.registerComponentWithRedux('RewardsHelp', () => require('screens/Help/RewardsHelp').default, Provider, store);

  // Modals
  Navigation.registerComponentWithRedux('VersionUpdateModal', () => require('misc/Modals/VersionUpdateModal').default, Provider, store);
  Navigation.registerComponentWithRedux('PolicyUpdateModal', () => require('misc/Modals/PolicyUpdateModal').default, Provider, store);
}

After (screens.obfuscated.js):

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports, "__esModule", { value: true });exports.registerScreens = registerScreens;exports.store = void 0;var _reactNativeNavigation = require("react-native-navigation");
var _reactRedux = require("react-redux");

var _reactNativeFirebase = _interopRequireDefault(require("react-native-firebase"));


var _store = _interopRequireDefault(require("../../store"));var crashlytics = _reactNativeFirebase.default.crashlytics();

// Initialise global store
var store = (0, _store.default)();exports.store = store;

function registerScreens() {

  crashlytics.enableCrashlyticsCollection();
  crashlytics.log('[utils][screens] - Registering screens...');

  // Register screens with redux stores
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Initialising', function () {return require("../components/screens/Initialising").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Auth', function () {return require("../components/screens/Auth").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('VerifyPhoneNumber', function () {return require("../components/screens/VerifyPhoneNumber").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('SignIn', function () {return require("../components/screens/SignIn").default;}, _reactRedux.Provider, store);

  // Profile Stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Profile', function () {return require("../components/screens/Profile").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('QRScanner', function () {return require("../components/screens/Profile/QRScanner").default;}, _reactRedux.Provider, store);

  // Register Stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('InitialRegister', function () {return require("../components/screens/Register/InitialRegister").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('RegisterDetails', function () {return require("../components/screens/Register/RegisterDetails").default;}, _reactRedux.Provider, store);

  // Rewards Stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Rewards', function () {return require("../components/screens/Rewards").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('RewardBack', function () {return require("../components/screens/Rewards/RewardBack").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('RewardRedeemed', function () {return require("../components/screens/Rewards/RewardRedeemed").default;}, _reactRedux.Provider, store);

  // Home stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('Home', function () {return require("../components/screens/Home").default;}, _reactRedux.Provider, store);

  // Marketplace stack
  _reactNativeNavigation.Navigation.registerComponentWithRedux('MarketplaceBack', function () {return require("../components/screens/Rewards/MarketplaceBack").default;}, _reactRedux.Provider, store);

  // Bundles
  _reactNativeNavigation.Navigation.registerComponentWithRedux('PreBundle', function () {return require("../components/screens/Home/PreBundle").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('BundleQuestion', function () {return require("../components/screens/Home/BundleQuestion").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('BundleComplete', function () {return require("../components/screens/Home/BundleComplete").default;}, _reactRedux.Provider, store);

  // Help Screens
  _reactNativeNavigation.Navigation.registerComponentWithRedux('HomeHelp', function () {return require("../components/screens/Help/HomeHelp").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('RewardsHelp', function () {return require("../components/screens/Help/RewardsHelp").default;}, _reactRedux.Provider, store);

  // Modals
  _reactNativeNavigation.Navigation.registerComponentWithRedux('VersionUpdateModal', function () {return require("../components/misc/Modals/VersionUpdateModal").default;}, _reactRedux.Provider, store);
  _reactNativeNavigation.Navigation.registerComponentWithRedux('PolicyUpdateModal', function () {return require("../components/misc/Modals/PolicyUpdateModal").default;}, _reactRedux.Provider, store);
}

I solved this by modifying obfuscatingTransformer.js in node_modules\react-native-obfuscating-transformer\dist\

change

fs.writeFileSync(path.join(emitDir, filename), code);

to

fs.writeFileSync(path.join(emitDir, filename), obfuscateCode_1.obfuscateCode(code, obfuscatorOptions));

it seems the author forgot to modify this one.

I solved this by modifying obfuscatingTransformer.js in node_modules\react-native-obfuscating-transformer\dist\

change

fs.writeFileSync(path.join(emitDir, filename), code);

to

fs.writeFileSync(path.join(emitDir, filename), obfuscateCode_1.obfuscateCode(code, obfuscatorOptions));

it seems the author forgot to modify this one.

@AustinZuniga thanks man that worked perfectly for the individual obfuscated files, however it still seems to have no effect on the overall bundle.js file. Any idea?

I solved this by modifying obfuscatingTransformer.js in node_modules\react-native-obfuscating-transformer\dist
change

fs.writeFileSync(path.join(emitDir, filename), code);

to

fs.writeFileSync(path.join(emitDir, filename), obfuscateCode_1.obfuscateCode(code, obfuscatorOptions));

it seems the author forgot to modify this one.

@AustinZuniga thanks man that worked perfectly for the individual obfuscated files, however it still seems to have no effect on the overall bundle.js file. Any idea?

I noticed that one too. Try comparing bundle.js with and without using react-native-obfuscating-transformer. Use a JS code beautifier and diff file tool to see the difference. It seems react-native-obfuscating-transformer is working by obfuscating the code, but the identifier names not turning into hexadecimal or mangled algorithm. Maybe it has something to do with metro transformer

I solved this by modifying obfuscatingTransformer.js in node_modules\react-native-obfuscating-transformer\dist
change

fs.writeFileSync(path.join(emitDir, filename), code);

to

fs.writeFileSync(path.join(emitDir, filename), obfuscateCode_1.obfuscateCode(code, obfuscatorOptions));

it seems the author forgot to modify this one.

@AustinZuniga thanks man that worked perfectly for the individual obfuscated files, however it still seems to have no effect on the overall bundle.js file. Any idea?

I noticed that one too. Try comparing bundle.js with and without using react-native-obfuscating-transformer. Use a JS code beautifier and diff file tool to see the difference. It seems react-native-obfuscating-transformer is working by obfuscating the code, but the identifier names not turning into hexadecimal or mangled algorithm. Maybe it has something to do with metro transformer

Yeah I can see from looking at the bundle.js that the code has definitely been changed and obfuscated a bit. Would just love for the identifier names to turn into the hexadecimal hash. Will leave this issue open and see if anyone can help. Thanks 👍

Hi! I tried several set of options with the same result: no obfuscation. I can see some differences between the original bundle and the obfuscated one but essentially nothing about stringsArray or selfDefence for instance. I can see the logs during bundling phase and I get the "obfuscated" files saved alongside the original ones so I assume the module is correctly integrated in metro config. The last configuration I experimented with is identical to the one shown by @dooleyb1 and the result is the same.

Hi! I tried several set of options with the same result: no obfuscation. I can see some differences between the original bundle and the obfuscated one but essentially nothing about stringsArray or selfDefence for instance. I can see the logs during bundling phase and I get the "obfuscated" files saved alongside the original ones so I assume the module is correctly integrated in metro config. The last configuration I experimented with is identical to the one shown by @dooleyb1 and the result is the same.

Yep, seeing the same...

I believe I found the root cause as to why variable / property and function names are not being obfuscated.
Metrobundler transforms each javascript file independantly, and in obfuscateCode.js only Obfuscator.obfuscate is called, which leads me to believe that as each file is transformed, they don't have ref to all the objects to fully obfuscate.

The docs for javascript-obfuscator/javascript-obfuscator state that Obfuscator.obfuscateMultiple should be called.

I believe I found the root cause as to why variable / property and function names are not being obfuscated.
Metrobundler transforms each javascript file independantly, and in obfuscateCode.js only Obfuscator.obfuscate is called, which leads me to believe that as each file is transformed, they don't have ref to all the objects to fully obfuscate.

The docs for javascript-obfuscator/javascript-obfuscator state that Obfuscator.obfuscateMultiple should be called.

Have you tried changing the Obfuscator.obfuscate to Obfuscator.obfuscateMultiple method and seeing if it works?

Not yet, it will take some time to figure out how to map all my flow objects to obfuscateMultiple.

I won't have time to look into this until late next week (and I can't promise I will have time.)

In a nutshell I believe that obfuscateCode.js should be calling Obfuscator.obfuscateMultiple instead of Obfuscator.obfuscate.

Another thing to take into consideration is that some RN projects use Flow vs TypeScript. I'd only look into creating a patch that works with Flow, if applicable.

I still have flow types in a lot of legacy modules for instance. I'll migrate but progressively during functional refactoring not because I have typing issues.

@filippoitaliano @TraneKJ @AustinZuniga Hey guys, have any of yous managed to get anywhere? Still no luck on my side.

Nowhere, I'm sorry! I abandoned obfuscation for now. This lib seems to be one of the most comprehensive out there but I didn't manage to make it work.

No progress, my team had me do a POC with Jscrambler first, which I've done, and it works great, but for a heafty price-tag.
The company I work for is now weighing the options of paying Jscrambler the annual subscription fee to handle obfuscation / anti-tampering, or giving me a work-item to fix what's broken in this open source library.
I wont know for another 2 week if they choose the latter, if they do I will let this thread know.

commented

@dooleyb1 @TraneKJ @filippoitaliano @AustinZuniga Any advise guys? doesn't work for me either

@dooleyb1 @TraneKJ @filippoitaliano @AustinZuniga Any advise guys? doesn't work for me either

None yet unfortunately

@adevfromhere What option did you go with?

try this plugin https://www.npmjs.com/package/obfuscator-io-metro-plugin

Can we use the source map generated by the obfuscator-io-metro-plugin to de obfuscate and symbolicate the line of crash in react native

@dooleyb1 did you manage to resolve this? If not, did you use an alternative solution?

My files don't appear to be obfuscated at all.

Original file:

const App: React.FC = () => {
  const myApiKey = 'foo';
  console.log(myApiKey);

Obfuscated file:

var App = function App() {
  var myApiKey = 'foo';
  console.log(myApiKey);

With the following packages

"metro-react-native-babel-preset": "^0.59.0",
"react-native-typescript-transformer": "^1.2.13",
"react-native-obfuscating-transformer": "^1.0.0",
"react-native": "0.63.3",

@dooleyb1 did you manage to resolve this? If not, did you use an alternative solution?

My files don't appear to be obfuscated at all.

Original file:

const App: React.FC = () => {
  const myApiKey = 'foo';
  console.log(myApiKey);

Obfuscated file:

var App = function App() {
  var myApiKey = 'foo';
  console.log(myApiKey);

With the following packages

"metro-react-native-babel-preset": "^0.59.0",
"react-native-typescript-transformer": "^1.2.13",
"react-native-obfuscating-transformer": "^1.0.0",
"react-native": "0.63.3",

@hannigand No unfortunately I gave up attempting to implement obfuscation on my files and have not re-attempted since.