Skip to content

Instantly share code, notes, and snippets.

@0xdevalias
Last active December 2, 2023 12:51
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
Some notes and techniques for reverse engineering Webpack (and a little bit about React/Vue) apps

Reverse Engineering Webpack Apps

Some notes and techniques for reverse engineering Webpack apps

Table of Contents

Unsorted

After like basically a day of crazy deep webpack knowledge aquisition the hard way around.. I had enough understanding and keywords to find this article, which would have in many ways probably saved me a good chunk of this time/figuring out of things:

But I guess what it doesn't mention is that you can access window.webpackChunk_N_E from global scope to see all of the loaded modules, and you can tell it to load a new module with window.webpackChunk_N_E.push, so you can basically arbitrarily define a new module and inject it at runtime with something like:

window.webpackChunk_N_E.push([
  [1337],
  {31337: (U, B, G) => console.log('chunk[1]', { U, B, G })},
  (U) => console.log('chunk[2]', U)
])

Where:

  • 1337 is the module chunk ID (relatively arbitrary choice, though used to reference the module from other code)
  • 31337 is a function defined within that module (can define more than 1) where U, B, G == module, __webpack_exports__, __webpack_require__
  • And the final function I'm not 100% sure about.. I think it might get called to preload any dependencies, or do something else like that... but in any case, it gets called when you first .push your module, so you can use it to get access to U (which I believe is also __webpack_require__)

And then __webpack_require__ is both a function that can be called, but also has a bunch of helper methods that can be accessed like accessing the modules object (aka __webpack_modules__), accessing the module cache (aka: installedModules), being able to access both harmony and __esModule exports, getting the defaultExport, calling properties on an object's prototype, seeing the module build path, etc.

So I haven't put all that together in a useful/runnable PoC gadget chain yet... but I believe that is basically all of the background knowledge required to be able to do so

See Also

The following aren't strictly related to Webpack, but they're useful tools and techniques to be aware of in general when debuygging / reversing web apps:

My Other Related Deepdive Gist's and Projects

React Internals

Vue Internals

  • https://www.damianmullins.com/inspecting-a-vue-application-in-production/
    • The Vue.js devtools are great to use when in the process of developing a web application. However, once you deploy to production it no longer has access to the code it needs to work. So how can we inspect an application once released to production? In this post, we’ll walk through the options available to you along with some tricks and tips to make the process a little easier.

    • Now refresh the page and once the breakpoint is hit then type Vue.config.devtools = true in the devtools console. In Vue.js, by default, the devtools config setting is determined by the value of process.env.NODE_ENV, what we’re doing here is manually setting that value to true in order to force Vue.js to initiate the Vue.js devtools.

    • Once you have the element selected you can then move to the console panel in devtools and type $0. $0 will be a reference to the most recently selected element in the element panel. To see the Vue instance details you can type $0.__vue__ Now that we have a reference to the Vue component instance we can expand it in the console to see what’s inside

  • https://www.damianmullins.com/logging-vuex-actions-and-mutations-in-the-wild/
    • const store = [...document.querySelectorAll('*')]
       .find(el => el.__vue__)
       .__vue__
       .$store;
    • const store = document.querySelector('[data-app]')
       .__vue__
       .$store;
let allElements = document.querySelectorAll('*');

// Filter for elements with __vue__ property
let vueElements = [...allElements]
  .filter(el => el.__vue__)
  .map(element => ({
    element,
    vue: element.__vue__,
  }));

console.log(vueElements);

The following is a snippet from Vue devtools (chrome-extension://nhdogjmejiglipccpnnnanhbledajbpd/build/detector.js)

We can see the unminified source here:

  • https://github.com/vuejs/devtools/blob/main/packages/shell-chrome/src/detector.js
    • // Method 1: Check Nuxt
      • window.__NUXT__ || window.$nuxt
      • Vue = window.$nuxt.$root && window.$nuxt.$root.constructor
      • devtoolsEnabled: (/* Vue 2 */ Vue && Vue.config.devtools) || (/* Vue 3.2.14+ */ window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled)
    • // Method 2: Check Vue 3
      • window.__VUE__
      • devtoolsEnabled: /* Vue 3.2.14+ */ window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled
    • // Method 3: Scan all elements inside document
      • const all = document.querySelectorAll('*')
        let el
        for (let i = 0; i < all.length; i++) {
          if (all[i].__vue__) {
            el = all[i]
            break
          }
        }
        if (el) {
          let Vue = Object.getPrototypeOf(el.__vue__).constructor
          while (Vue.super) {
            Vue = Vue.super
          }
      • devtoolsEnabled: Vue.config.devtools
      • if (detectRemainingTries > 0) {
          detectRemainingTries--
          setTimeout(() => {
            runDetect()
          }, delay)
          delay *= 5
        }
      • win.postMessage({
          devtoolsEnabled: Vue.config.devtools,
          vueDetected: true,
        }, '*')
      • window.addEventListener('message', e => {
          if (e.source === window && e.data.vueDetected) {
            chrome.runtime.sendMessage(e.data)
          }
        })
function n(e) {
  let t = 1e3,
    n = 10;
  function r() {
    const o = !(!window.__NUXT__ && !window.$nuxt);
    if (o) {
      let t;
      return (
        window.$nuxt &&
          (t = window.$nuxt.$root && window.$nuxt.$root.constructor),
        void e.postMessage(
          {
            devtoolsEnabled:
              (t && t.config.devtools) ||
              (window.__VUE_DEVTOOLS_GLOBAL_HOOK__ &&
                window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled),
            vueDetected: !0,
            nuxtDetected: !0,
          },
          "*"
        )
      );
    }
    const i = !!window.__VUE__;
    if (i)
      return void e.postMessage(
        {
          devtoolsEnabled:
            window.__VUE_DEVTOOLS_GLOBAL_HOOK__ &&
            window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled,
          vueDetected: !0,
        },
        "*"
      );
    const s = document.querySelectorAll("*");
    let a;
    for (let e = 0; e < s.length; e++)
      if (s[e].__vue__) {
        a = s[e];
        break;
      }
    if (a) {
      let t = Object.getPrototypeOf(a.__vue__).constructor;
      while (t.super) t = t.super;
      e.postMessage(
        {
          devtoolsEnabled: t.config.devtools,
          vueDetected: !0,
        },
        "*"
      );
    } else
      n > 0 &&
        (n--,
        setTimeout(() => {
          r();
        }, t),
        (t *= 5));
  }
  setTimeout(() => {
    r();
  }, 100);
}

Therefore, we could probably use a snippet like the following to tell Vue devtools that it should enable itself, even on a production site:

let firstVueElement = [...document.querySelectorAll('*')].find(el => el.__vue__);
let Vue = undefined;

if (firstVueElement) {
  Vue = Object.getPrototypeOf(firstVueElement.__vue__).constructor

  while (Vue.super) {
    Vue = Vue.super
  }
}

if (Vue) {
  console.log('Found Vue', { Vue });

  console.log('Vue.config', Vue.config);

  console.log('Setting Vue.config.devtools = true');
  Vue.config.devtools = true;
  
  console.log('Setting __VUE_DEVTOOLS_GLOBAL_HOOK__ values', { __VUE_DEVTOOLS_GLOBAL_HOOK__ });
  __VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = Vue;
  __VUE_DEVTOOLS_GLOBAL_HOOK__.enabled = true;

  console.log('Signalling to Vue Devtools that it should enable itself');
  window.postMessage({
    devtoolsEnabled: true,
    vueDetected: true,
    nuxtDetected: false,
  }, '*')
} else {
  console.log('Failed to find Vue');
}

Chrome - Devtools - Search

Chrome - Devtools - Console Utilities API reference

Chrome - Devtools - Console features reference

  • https://developer.chrome.com/docs/devtools/console/reference/
    • Console features reference

    • https://developer.chrome.com/docs/devtools/console/reference/#inspect-internal-properties
      • Borrowing the ECMAScript notation, the Console encloses some properties internal to JavaScript in double square brackets. You can't interact with such properties in your code. However, it might be useful to inspect them.

        • Any object has a [[Prototype]]

        • Primitive wrappers have a [[PrimitiveValue]] property

        • ArrayBuffer objects have the following properties: [[Int8Array]], [[Uint8Array]], [[Int16Array]], [[Int32Array]], [[ArrayBufferByteLength]], [[ArrayBufferData]]

        • In addition to ArrayBuffer-specific properties, WebAssembly.Memory objects have a [[WebAssemblyMemory]] property.

        • Keyed collections (maps and sets) have an [[Entries]] property that contains their keyed entries.

        • Promise objects have the following properties:

          • [[PromiseState]]: pending, fulfilled, or rejected

          • [[PromiseResult]]: undefined if pending, <value> if fulfilled, <reason> if rejected

        • Proxy objects have the following properties: [[Handler]] object, [[Target]] object, and [[isRevoked]] (switched off or not).

    • https://developer.chrome.com/docs/devtools/console/reference/#inspect-functions
      • To view function properties internal to JavaScript, use the console.dir() command.

        • https://developer.chrome.com/docs/devtools/console/api/#dir
          • Prints a JSON representation of the specified object.

        • Functions have the following properties:

          • [[FunctionLocation]]. A link to the line with the function definition in a source file.

          • [[Scopes]]. Lists values and expressions the function has access to. To inspect function scopes during debugging, see View and edit local, closure, and global properties.

        • Bound functions have the following properties:

          • [[TargetFunction]]. The target of bind()

          • [[BoundThis]]. The value of this

          • [[BoundArgs]]. An array of function arguments

        • Generator functions are marked with a [[IsGenerator]]: true property.

        • Generators return iterator objects and they have following properties:

          • [[GeneratorLocation]]. A link to a line with the generator definition in a source file

          • [[GeneratorState]]: suspended, closed, or running

          • [[GeneratorFunction]]. The generator that returned the object

          • [[GeneratorReceiver]]. An object that receives the value

    • etc

Chrome - Devtools - JavaScript debugging reference

Chrome - Devtools - Network

Chrome - Devtools Protocol, Programmatic Debugging, etc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment