j4k0xb / webcrack

Deobfuscate obfuscator.io, unminify and unpack bundled javascript
https://webcrack.netlify.app
MIT License
663 stars 72 forks source link

hardcoded React UMD global #10

Open e9x opened 1 year ago

e9x commented 1 year ago

The current Sketchy implementation only decompiles React JSX when the code utilizes the UMD global, which is not effective since the majority of React websites incorporate the library within their bundle.

To make the decompilation process more effective and adaptable to different React websites, I recommend a more dynamic approach by identifying the React library being used in the compiled code, instead of hardcoding the use of 'React'. This can possibly be achieved by finding the variable name assigned to the React library and using that in the matchers.

https://github.com/j4k0xb/webcrack/blob/a972c320f37bcc66b3f76e80a55aff644b8351fa/src/transforms/jsx.ts#L26 https://github.com/j4k0xb/webcrack/blob/a972c320f37bcc66b3f76e80a55aff644b8351fa/src/transforms/jsx.ts#L32 https://github.com/j4k0xb/webcrack/blob/a972c320f37bcc66b3f76e80a55aff644b8351fa/src/transforms/jsx.ts#L34

j4k0xb commented 1 year ago

instead of hardcoding the use of 'React'. This can possibly be achieved by finding the variable name assigned to the React library

Thats a possibility, but I have to check the hardcoded name either way because of UMD.

Theres another feature request that would solve this issue without with much extra work (edit: nvm its harder than expected):

one file got renamed to utils.js and const a = require("./utils.js") could be renamed to const utils = require("./utils.js")

So first identify the react require/import variable and rename it to React, then the jsx transforms can automatically find it.

e9x commented 1 year ago

What if the renaming of assignments to require() calls was extended to functions, classes, variables, properties, arguments, and everything else?

See example

That way the UMD global matchers can be replaced with something like require_React.createElement. The old UMD global matcher can be kept too.

This goes beyond the scope of this issue, but it would be a massive improvement.

e9x commented 1 year ago

I published my decompiler that I used in the above example. I think it might be a good reference for adding this feature. https://github.com/e9x/krunker-decompiler

0xdevalias commented 8 months ago

I'm not sure of the exact way they go about resolving this, but I came across another tool today that seemed to handle embedded React pretty well:

Digging through the code a little for stuff related to React/JSX lead me to this:

0xdevalias commented 7 months ago

Came across this issue again while testing the new v2.11.0 web IDE update today.

Another tool originally struggled with this too (Ref)

You can get the minimised code that I am testing against here (Ref)

Loading that in the webcrack web IDE (Ref) with the following config:

image

You can see the issues related to this in files like 180.js, where the JSX hasn't been unminimised:

180.js

require.d(exports, {
  Z: function () {
    return a;
  }
});
var r = require( /*webcrack:missing*/"./35250.js");
function a(e) {
  var t;
  var n = e.url;
  var a = e.size;
  var i = a === undefined ? 16 : a;
  var s = e.className;
  try {
    t = new URL(n);
  } catch (e) {
    console.error(e);
    return null;
  }
  return (0, r.jsx)("img", {
    src: `https://icons.duckduckgo.com/ip3/${t.hostname}.ico`,
    alt: "Favicon",
    width: i,
    height: i,
    className: s
  });
}
0xdevalias commented 7 months ago

Here's a wip version that converts all top level requires and this export variation: https://deploy-preview-31--webcrack.netlify.app/

Originally posted by @j4k0xb in https://github.com/j4k0xb/webcrack/issues/30#issuecomment-1862939384

That WIP (see https://github.com/j4k0xb/webcrack/pull/31) seems to have fixed the above r.jsx, which now gets converted back into JSX properly (as you showed in https://github.com/j4k0xb/webcrack/issues/30#issuecomment-1862939384)

Looking at that same original source file (Ref), in 63390.js, there are these jsxs imports/usages, that aren't being unminified back to JSX (though amusingly, the children part is):

// 63390.js, line 5
import { jsxs, Fragment, jsx } from /*webcrack:missing*/"./35250.js";
// 63390.js, lines 151-157
return jsxs(r ? "a" : "div", {
    className: _Z("flex h-full w-full flex-col overflow-hidden rounded-md border border-black/10 bg-gray-50 shadow-[0_2px_24px_rgba(0,0,0,0.05)]", s),
    href: r,
    target: r ? "_blank" : "",
    onClick: h,
    children: [c && <H><div className="absolute inset-0"><img src={a} alt={`image of ${n}`} className="h-full w-full border-b border-black/10 object-cover" /></div></H>, <div className="flex flex-1 flex-col justify-between gap-1.5 p-3"><_Component65 $clamp={u !== undefined && u || c}>{n}</_Component65><div className="flex items-center gap-1">{i ? <_Z5 url={i} name={t} size={13} /> : <_Z6 url={r} size={13} />}<div className="text-[10px] leading-3 text-gray-500 line-clamp-1">{t}</div></div></div>]
  });

Contrasting this against wakaru's output (which also used to have issues with jsxs (Ref), with some notes about why it wasn't working in that case here (Ref), and still seems to have some struggles with the jsxs(url ? "a" : "div", { part even now (Ref))

Details **Source (unpacked)** ```js // module-63390.js, lines 186-225 return (0, o.jsxs)(r ? "a" : "div", { className: (0, l.Z)( "flex h-full w-full flex-col overflow-hidden rounded-md border border-black/10 bg-gray-50 shadow-[0_2px_24px_rgba(0,0,0,0.05)]", s ), href: r, target: r ? "_blank" : "", onClick: h, children: [ c && (0, o.jsx)(H, { children: (0, o.jsx)("div", { className: "absolute inset-0", children: (0, o.jsx)("img", { src: a, alt: "image of ".concat(n), className: "h-full w-full border-b border-black/10 object-cover", }), }), }), (0, o.jsxs)("div", { className: "flex flex-1 flex-col justify-between gap-1.5 p-3", children: [ (0, o.jsx)(z, { $clamp: (void 0 !== u && u) || c, children: n }), (0, o.jsxs)("div", { className: "flex items-center gap-1", children: [ i ? (0, o.jsx)(R.Z, { url: i, name: t, size: 13 }) : (0, o.jsx)(U.Z, { url: r, size: 13 }), (0, o.jsx)("div", { className: "text-[10px] leading-3 text-gray-500 line-clamp-1", children: t, }), ], }), ], }), ], }); ``` **Transformed (unminified)** ```js // module-63390.js, lines 213-252 return jsxs(url ? "a" : "div", { className: Z$0( "flex h-full w-full flex-col overflow-hidden rounded-md border border-black/10 bg-gray-50 shadow-[0_2px_24px_rgba(0,0,0,0.05)]", className ), href: url, target: url ? "_blank" : "", onClick: h, children: [ c && ( {
{ {`image }
}
),
{title}
{logoUrl ? ( ) : ( )}
{t}
, ], }); } ```
j4k0xb commented 7 months ago

The code probably looked like this and Tag got inlined later:

const Tag = r ? "a" : "div";
return <Tag />;

Should be enough to extract it to a variable again

0xdevalias commented 7 months ago

The code probably looked like this and Tag got inlined later

@j4k0xb Yeah, that was my (or really.. ChatGPT's) conclusion as well :) (Ref)

j4k0xb commented 6 months ago

That jsx type should work now: #38 / https://deploy-preview-38--webcrack.netlify.app/ (the branch doesn't have the esm changes yet) I think it doesn't apply to nested elements or React.createElement because minifiers avoid changing the evaluation order.

0xdevalias commented 6 months ago

That jsx type should work now: https://github.com/j4k0xb/webcrack/pull/38 / deploy-preview-38--webcrack.netlify.app (the branch doesn't have the esm changes yet)

@j4k0xb I'm guessing that because that branch doesn't have the ESM changes, that's why it isn't getting unminimised at all, even when I use that branch you just mentioned?

j4k0xb commented 6 months ago

Yes.. It detects calls like jsxs(r ? "a" : "div") The ESM changes involve converting the require to import { jsx } from '...' and then converting (0, jsxs)() to jsxs() which is safe (#6) I have now rebased the branch so it applies both: https://deploy-preview-31--webcrack.netlify.app/

0xdevalias commented 6 months ago

I have now rebased the branch so it applies both: deploy-preview-31--webcrack.netlify.app

Looking at that same original source file (Ref), unminified, in 63390.js:

// 63390.js, lines 151-152
  const _Component68 = r ? "a" : "div";
  return <_Component68 className={_Z("flex h-full w-full flex-col overflow-hidden rounded-md border border-black/10 bg-gray-50 shadow-[0_2px_24px_rgba(0,0,0,0.05)]", s)} href={r} target={r ? "_blank" : ""} onClick={h}>{c && <H><div className="absolute inset-0"><img src={a} alt={`image of ${n}`} className="h-full w-full border-b border-black/10 object-cover" /></div></H>}<div className="flex flex-1 flex-col justify-between gap-1.5 p-3"><_Component67 $clamp={u !== undefined && u || c}>{n}</_Component67><div className="flex items-center gap-1">{i ? <_Z5 url={i} name={t} size={13} /> : <_Z6 url={r} size={13} />}<div className="text-[10px] leading-3 text-gray-500 line-clamp-1">{t}</div></div></div></_Component68>;

@j4k0xb Looks good, thanks! 🎉