JSC.js icon indicating copy to clipboard operation
JSC.js copied to clipboard

Access globals?

Open TristonStuart opened this issue 4 years ago • 18 comments

Any way to access scope that executed jsc_eval? Or the global scope?

TristonStuart avatar May 15 '20 05:05 TristonStuart

JSC >>> this
[object GlobalObject]

is that what you want?

mbbill avatar May 15 '20 07:05 mbbill

I mean like javascript variables outside of the engine. Like if you have javascript code running outside the engine and you want a script to be able to access the variables outside of the engine.

TristonStuart avatar May 15 '20 16:05 TristonStuart

Oh I see. Right now it's not supported yet because it needs by-directional communication between the outer and inner JS environment across web assembly, but that's definitely a useful feature.

mbbill avatar May 15 '20 19:05 mbbill

I'm trying to use this to obfuscate javascript. Is there any way to load in .jsc files directly? Like just straight javascript byte code?

TristonStuart avatar May 15 '20 19:05 TristonStuart

Also how hard would it be for me to implement the jsc_eval code to access the normal js variables? Or do you know of any solutions that aren't that clean?

TristonStuart avatar May 15 '20 20:05 TristonStuart

There is a way to do that. Actually I already have some initial work done. You may take a look at https://github.com/mbbill/JSC.js/blob/master/Source/JavaScriptCore/JSCJS/jscjs.cpp#L229 You can find some examples of compiling Javascript code into bytecode and using jsc.js to load and run bytecode.

There are still some issues though. The bytecode compiler doesn't handle 'new' operator correctly. It's due to one of the limit of early implementation of JSC byte cache. I'm not sure if they have fixed it or not.

mbbill avatar May 16 '20 00:05 mbbill

There is a way to do that. Actually I already have some initial work done. You may take a look at https://github.com/mbbill/JSC.js/blob/master/Source/JavaScriptCore/JSCJS/jscjs.cpp#L229 You can find some examples of compiling Javascript code into bytecode and using jsc.js to load and run bytecode.

There are still some issues though. The bytecode compiler doesn't handle 'new' operator correctly. It's due to one of the limit of early implementation of JSC byte cache. I'm not sure if they have fixed it or not.

Is there any way I can easily do that with the demo already setup? I just ripped the demo code and loaded in your jsc.wasm file for my own purposes. Does the demo js already support doing this or do I have to make modifications to it, or do I have to make modifications to the jsc.wasm code?

TristonStuart avatar May 16 '20 02:05 TristonStuart

https://github.com/mbbill/JSC.js/blob/master/build/BUILD.gn#L151 the jsc_compile and jsc_eval_bytecode is already exported. You can try to test them with your existing setup.

jsc_compile is to convert JS to bytecode. and jsc_eval_bytecode is to run the previously generated bytecode.

mbbill avatar May 16 '20 03:05 mbbill

https://github.com/mbbill/JSC.js/blob/master/build/BUILD.gn#L151 the jsc_compile and jsc_eval_bytecode is already exported. You can try to test them with your existing setup.

jsc_compile is to convert JS to bytecode. and jsc_eval_bytecode is to run the previously generated bytecode.

How do I access them? I tried getCFunc and it only allows me to get "jsc_eval". Module only has "_jsc_eval".

TristonStuart avatar May 16 '20 04:05 TristonStuart

As far as I can tell _jsc_compile and _jsc_eval_bytecode are not exported in the version used in your demo. I'll try to build it again but I still don't know why it won't work.

TristonStuart avatar May 16 '20 18:05 TristonStuart

static bindings between the WASM environment and the Native JS engine can be added by using the C based JSC API, if one are only adding a few endpoints which calls function or setter/getters from/to either side.

// implementation of the function taking the JS call at WASM side.
JSValueRef setTimeoutCallAsFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
    printf("setTimeoutCallAsFunction called with no. args: %zu\n", argumentCount);
    
    ExecState* exec = toJS(ctx);
    VM& vm = exec->vm();
    
    if(argumentCount < 1){
        // throw not-enough-arguments-error.
        return JSValueMakeUndefined(ctx);
    }

    JSC::JSValue arg1 = toJS(exec, arguments[0]);

    if(!arg1.isFunction(vm) && !arg1.isString()){
        // throw TypeError here
        return JSValueMakeUndefined(ctx);
    }

    JSC::JSValue arg2 = argumentCount > 1 ? toJS(exec, arguments[1]) : jsNumber(0);

    int delay = arg2.toUInt32(exec);

    std::unique_ptr<WebCore::ScheduledAction> action = WebCore::ScheduledAction::create(exec->lexicalGlobalObject(), JSC::Strong<JSC::Unknown> { vm, JSC::asObject(arg1) });
    
    int timeoutId = jsc_proxy_setTimeout(0, delay);
    globalTimerMap().set(timeoutId, WTFMove(action));
    return JSValueMakeNumber(ctx, timeoutId);
}

void setupCommonGlobalScope(JSGlobalObject* globalObject){

    JSContextRef ctx = toRef(globalObject->globalExec());

    // globalThis.setTimeout(func, [delay], [arg1, arg2 ...])
    JSStringRef nameString = JSStringCreateWithUTF8CString("setTimeout");
    JSObjectRef setTimeoutFn = JSObjectMakeFunctionWithCallback(ctx, nameString, setTimeoutCallAsFunction);
    JSObjectSetProperty(ctx, toRef(globalObject), nameString, setTimeoutFn, kJSPropertyAttributeNone, nullptr);
    JSStringRelease(nameString);
}

JSGlobalObject* jsc_global() {
    //jsc_init();
    //static VM& vm = VM::create(LargeHeap).leakRef();
    //JSLockHolder locker(vm);
    VM& vm = globalVM();
    
    static JSGlobalObject* globalObject = JSGlobalObject::create(vm, JSGlobalObject::createStructure(vm, jsNull()));
    globalObject->setRemoteDebuggingEnabled(true);

    JSContextRef ctx = toRef(globalObject->globalExec());

    // setting the name of the context (visable to inspector API)
    vm.vmEntryGlobalObject(globalObject->globalExec())->setName(String("default:jsc_global"));

    // Common Features in Global Scope (globalThis)
    setupCommonGlobalScope(globalObject);

    // console API, compile with `ENABLE_REMOTE_INSPECTOR=1` and uses custom RemoteInspector subclass, to bridge it to WASM host.
    static unique_ptr<RemoteConnectionToTarget> l_connectionToTarget = make_unique<RemoteConnectionToTarget>(globalObject->inspectorDebuggable());
    RemoteConnectionToTarget* connectionToTarget = l_connectionToTarget.get();
    connectionToTarget->setup(false, false);
    
    globalScriptContextMap().set(String("default"), globalObject);
    return globalObject;
}

Another approach is a fit all use cases; it should be possible to implement a custom VM wrapper, using the same wrapper protocol used to wrap Objective-C classes/object and binds those into the JS environment. This one would require a fair share of work. See the .mm files in /Source/JSC/API, glib GTK bindings also uses these API to bind classes/objects into the JS environment.

Another approach is to implement these types of bindings are to use proxy object which allows to respond with callback respond to any action applied to a object get/set/delete/has implement private endpoints in both environments and use a hash map to look these actions up, encoding/decoding primitives, reference objects in a hash map and wrapper callback/function the same way.

The hardest challenge with any approach is to make them work with the Garbage Collection at the hosting/native side.

raweden avatar May 23 '20 10:05 raweden

Interesting stuff. Thanks for the ideas and code. I will try to play around with this as soon as I can actually get this to compile. But for some reason it refuses on the linking JSC process at the end.

TristonStuart avatar May 23 '20 17:05 TristonStuart

@TristonStuart It took some time getting the settings right for the build to get successful.. From those errors you posted in a previous issue, it seams like the ALWAYS_INLINE compile time defined is not recognised properly.

Add this line

        "ALWAYS_INLINE=inline",

into the file /JSC.js/build/BUILD.gn into the end of the defines in the section seen below:

config("compiler_defaults") {
    defines = [
        "JSCJS=1",
        "ENABLE_JIT=0",

There is a few more compile time defines that could be added (unrelated to your error):

        "ENABLE_WEBASSEMBLY=0",
        "USE_GENERIC_EVENT_LOOP=1",
        "ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS=0",
        "ENABLE_REMOTE_INSPECTOR=0,
        "LOG_DISABLED=0",
        "ENABLE_DEVELOPER_MODE=1",
        "HAVE_QOS_CLASSES=0", 

ENABLE_WEBASSEMBLY makes no sense to emulate WASM in WASM runtime. USE_GENERIC_EVENT_LOOP controls the implementation of application runloop which is defined within the WTF (WebKit Template Framework). ENABLE_REMOTE_INSPECTOR controls the behavior of the inspector, the default JSC.js code base don't have any implementation in place to use it, the code base controlled by the define is what enables for example console.log and the DevTools debugger (with breakpoints etc) seen within jsc/safari to work. ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS is a sub settings for Remote Inspector. LOG_DISABLED=0 enables some log statements. ENABLE_DEVELOPER_MODE=1 more info in exceptions. HAVE_QOS_CLASSES

raweden avatar May 24 '20 10:05 raweden

Just as a Note in my example code above I where using WebCore::ScheduledAction which is not included within JSC.js by default. It has to be copied from the WebKit repo and be slightly modified to be independent from the rest of the WebCore library.

I plan on releasing the code written to bridge/implement the common minimum global scope, including Fetch API with streams somewhere in the future.

raweden avatar May 24 '20 10:05 raweden

@TristonStuart It took some time getting the settings right for the build to get successful.. From those errors you posted in a previous issue, it seams like the ALWAYS_INLINE compile time defined is not recognised properly.

Add this line

        "ALWAYS_INLINE=inline",

into the file /JSC.js/build/BUILD.gn into the end of the defines in the section seen below:

config("compiler_defaults") {
    defines = [
        "JSCJS=1",
        "ENABLE_JIT=0",

There is a few more compile time defines that could be added (unrelated to your error):

        "ENABLE_WEBASSEMBLY=0",
        "USE_GENERIC_EVENT_LOOP=1",
        "ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS=0",
        "ENABLE_REMOTE_INSPECTOR=0,
        "LOG_DISABLED=0",
        "ENABLE_DEVELOPER_MODE=1",
        "HAVE_QOS_CLASSES=0", 

ENABLE_WEBASSEMBLY makes no sense to emulate WASM in WASM runtime. USE_GENERIC_EVENT_LOOP controls the implementation of application runloop which is defined within the WTF (WebKit Template Framework). ENABLE_REMOTE_INSPECTOR controls the behavior of the inspector, the default JSC.js code base don't have any implementation in place to use it, the code base controlled by the define is what enables for example console.log and the DevTools debugger (with breakpoints etc) seen within jsc/safari to work. ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS is a sub settings for Remote Inspector. LOG_DISABLED=0 enables some log statements. ENABLE_DEVELOPER_MODE=1 more info in exceptions. HAVE_QOS_CLASSES

This works perfectly. The code was able to compile and run perfectly. Thank you so much. I am looking forward to seeing you add global access as I think this project could be used for js security. I would make a pull request and add those changes.

TristonStuart avatar May 25 '20 07:05 TristonStuart

Just as a Note in my example code above I where using WebCore::ScheduledAction which is not included within JSC.js by default. It has to be copied from the WebKit repo and be slightly modified to be independent from the rest of the WebCore library.

I plan on releasing the code written to bridge/implement the common minimum global scope, including Fetch API with streams somewhere in the future.

Have you made a working prototype?

TristonStuart avatar May 27 '20 22:05 TristonStuart

Have you made a working prototype?

Its about halfway implemented, written in c++ for performance reasons. I looked at merge these parts from WebCore, but its hard work as its hard to just pull out certain parts of from that code base, and these JavaScript API written for WebCore part of Webkit is also written in predefined JS, which in native Safari don't matter as it JIT compiles it anyways, this is not possible in JSC which requires a different approach of implementing it to squeeze out the most performance.

raweden avatar May 28 '20 20:05 raweden

It sounds very complicated. If you make a working version I would love to include it in the wrapper API I wrote. I think this project can go a long way to help protect javascript code security.

TristonStuart avatar May 29 '20 18:05 TristonStuart