javascript-obfuscator / react-native-obfuscating-transformer

Obfuscation for React Native bundles
MIT License
196 stars 100 forks source link

Code not being encrypted #28

Open dooleyb1 opened 4 years ago

dooleyb1 commented 4 years ago

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);
}
AustinZuniga commented 4 years ago

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.

dooleyb1 commented 4 years ago

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?

AustinZuniga commented 4 years ago

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

dooleyb1 commented 4 years ago

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 👍

filippoitaliano commented 4 years ago

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.

adevfromhere commented 4 years ago

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...

adevfromhere commented 4 years ago

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.

dooleyb1 commented 4 years ago

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?

adevfromhere commented 4 years ago

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

adevfromhere commented 4 years ago

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.

filippoitaliano commented 4 years ago

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.

dooleyb1 commented 4 years ago

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

filippoitaliano commented 4 years ago

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.

adevfromhere commented 4 years ago

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.

barak109 commented 4 years ago

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

dooleyb1 commented 4 years ago

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

None yet unfortunately

Quantum-35 commented 4 years ago

@adevfromhere What option did you go with?

whoami-shubham commented 4 years ago

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

Karthi27p commented 3 years ago

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

hannigand commented 3 years ago

@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 commented 3 years ago

@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.