2017年7月

使用ChakraCore来Fuzz NScript(3)——构建符合NScript的Funfuzz脚本

NScript在对脚本进行evaluation之前会做一次熵判断,现在常用的做法是用一段长而无用的代码插在要评估的代码之前,达到熵的限值。而我们既然要用其他程序生成Funfuzz的结果再抛给NScript,看Funfuzz的输出:

/*FRC-fuzzSeed-58694107*/count=25; tryItOut("mathy5 = (function(x, y) { \"use strict\"; return ( ! (((( ~ (( + ( - y)) | 0)) | 0) & ( + ( + ((-(2**53) != (-0 >>> 0)) >>> 0)))) | 0)); }); testMathyFunction(mathy5, [(new Number(-0)), -0, '', null, /0/, (new Number(0)), (new String('')), false, '\\0', ({valueOf:function(){return '0';}}), (new Boolean(false)), '0', [], undefined, '/0/', 0.1, NaN, [0], ({valueOf:function(){return 0;}}), 0, (new Boolean(true)), (function(){return 0;}), ({toString:function(){return '0';}}), objectEmulatingUndefined(), true, 1]); ");
Running threw: ReferenceError: 'objectEmulatingUndefined' is not defined

/*FRC-fuzzSeed-58694107*/count=26; tryItOut("mathy2 = (function(stdlib, foreign, heap){ \"\";   var ff = foreign.ff;\n  var Int32ArrayView = new stdlib.Int32Array(heap);\n  function f(i0, d1)\n  {\n    i0 = i0|0;\n    d1 = +d1;\n    var i2 = 0;\n    var i3 = 0;\n    return +(((Int32ArrayView[((0xa5daeffa)+(0xffffffff)) >> 2])));\n  }\n  return f; })(this, {ff: encodeURIComponent}, new ArrayBuffer(4096)); testMathyFunction(mathy2, /*MARR*/[(void 0), false, (void 0), false, false, false, (yield -21), false]); ");
Plain eval!

/*FRC-fuzzSeed-58694107*/count=27; tryItOut("mathy4 = (function(stdlib, foreign, heap){ \"\";   var ff = foreign.ff;\n  function f(i0, i1)\n  {\n    i0 = i0|0;\n    i1 = i1|0;\n    i1 = (x * b);\n    {\n      i0 = (0xa9b3c950);\n    }\n    i1 = (0xfa09be60);\n    i1 = (i0);\n    return (((i0)-(i0)))|0;\n  }\n  return f; })(this, {ff: this}, new ArrayBuffer(4096)); testMathyFunction(mathy4, [-(2**53+2), 0, -0x100000001, 1, -Number.MAX_SAFE_INTEGER, Math.PI, -0, 0.000000000000001, 0x100000000, -Number.MAX_VALUE, 0x080000000, Number.MAX_VALUE, 1/0, 0x0ffffffff, -Number.MIN_VALUE, 42, -0x080000000, 0/0, (2**53), (2**53)+2, 0x100000001, -(2**53-2), -0x07fffffff, Number.MIN_VALUE, -Number.MIN_SAFE_INTEGER, 0x080000001, Number.MIN_SAFE_INTEGER, -(2**53), (2**53)-2, Number.MAX_SAFE_INTEGER, -0x0ffffffff, -0x100000000, -1/0, -0x080000001, 0x07fffffff, 1.7976931348623157e308]); ");

可以看到有很多不一样的,比如tryItOut,这个实际上是Funfuzz自己定义的,一个复杂的函数,它的内容如下:

垃圾回收
DUPTRY自定义FUZZ关键字处理,我们暂时不管
JS STRICT MODE为代码自动加上'use strict',不管,因为NScript根本不支持:

input:a=4;
evaluation result: 4

input:'use strict';a=4;
evaluation result: 4

创建new Function,将代码传给Function,如果有compile error,抛出compile error。
输出字面代码
执行代码

其实我们就简化一下,直接把它包装成一个try catch即可。

testMathyFunction(a,b)是另一个test类型的函数,调用a(b),并输出结果,我们对结果其实并不是非常感兴趣,简单起见可以在里面直接调用a(b)。

mathy*属于动态生成的函数,这部分需要提前拿到,并丢给NScript。

  if (rnd(5)) {
    if (rnd(8)) {
      s += "mathy" + i + " = " + makeMathFunction(6, b, i) + "; ";
    } else {
      s += "mathy" + i + " = " + makeAsmJSFunction(6, b) + "; ";
    }
  }

这里makeMathFunction和makeAsmJSFunction均不好直接移植,同时你也可以看出来为什么我对funfuzz的代码反映这么大,全是d、b、i这样的参数,根本没法明白地看清楚函数在干什么。

好在我们可以忽略它在干啥,在上面s += ‘mathy’…… 的时候,我们就可以为这里加上一个特殊的注释,然后抛出去。ChakraCore在收到带有这种注释的输出时,直接通过socket将数据发送给NScript备用。 NScript同时减少为了增熵数据的量,填入这里有用的函数代码。

function makeMathFunction(d, b, i)
{
  if (rnd(TOTALLY_RANDOM) == 2) return totallyRandom(d, b);

  var ivars = ["x", "y"];
  if (rnd(10) == 0) {
    // Also use variables from the enclosing scope
    ivars = ivars.concat(b);
  }
  return "(function(x, y) { " + directivePrologue() + "return " + makeMathExpr(d, ivars, i) + "; })";
}

除去这些,我们可以再看一下NScript对基础数据类型的支持:

input:var x = new RegExp('d');x;
evaluation result: /d/

input:var x = new Array('d');x;
evaluation result: d

input:var x = new Function('g(){}');x;
evaluation result: [object Function]

input:var x = new Image('');x;
evaluation result: ReferenceError: 'Image' is undefined

input:var x = new Int32Array('');x;
evaluation result: ReferenceError: 'Int32Array' is undefined

input:var x = new DataView('');x;
evaluation result: ReferenceError: 'DataView' is undefined

input:var x = new HTMLDivElement('');x;
evaluation result: ReferenceError: 'HTMLDivElement' is undefined

input:function SomeObj(x){log(x);}; SomeObj.y = "1"; SomeObj.prototype.oops = function(){log(SomeObj.y)}; var xx = new SomeObj("2"); xx.oops();
evaluation result: 2
evaluation result: 1
evaluation result: undefined


input:Infinity
evaluation result: Infinity

input:Error
evaluation result: [object Function]

input:Date
evaluation result: [object Function]

input:JSON
evaluation result: ReferenceError: 'JSON' is undefined

input:Math
evaluation result: [object Math]

input:Map
evaluation result: ReferenceError: 'Map' is undefined

input:Proxy
evaluation result: ReferenceError: 'Proxy' is undefined

input:String
evaluation result: [object Function]

input:TypedArray
evaluation result: ReferenceError: 'TypedArray' is undefined

input:parseFloat
evaluation result: [object Function]

input:parseInt
evaluation result: [object Function]

input:eval
evaluation result: [object Function]

input:typeof escape === typeof unescape
evaluation result: true

input:ArrayBuffer
evaluation result: ReferenceError: 'ArrayBuffer' is undefined

input:void 0
evaluation result: undefined

input:encodeURIComponent
evaluation result: [object Function]

可以发现

DOM相关的,大部分不支持
ES5+的不支持
JSON不支持

本文来自http://nul.pw,作者blast,转载请注明

然后,让我们看一些常见的对象:

input:for(var p in Math) log(p)
evaluation result: abs
evaluation result: acos
evaluation result: asin
evaluation result: atan
evaluation result: atan2
evaluation result: ceil
evaluation result: cos
evaluation result: exp
evaluation result: floor
evaluation result: log
evaluation result: max
evaluation result: min
evaluation result: pow
evaluation result: random
evaluation result: round
evaluation result: sin
evaluation result: sqrt
evaluation result: tan
evaluation result: E
evaluation result: LN10
evaluation result: LN2
evaluation result: LOG2E
evaluation result: LOG10E
evaluation result: PI
evaluation result: SQRT1_2
evaluation result: SQRT2
evaluation result: undefined

以及:

input:for(var p in Number) log(p)
evaluation result: prototype
evaluation result: constructor
evaluation result: MAX_VALUE
evaluation result: MIN_VALUE
evaluation result: NaN
evaluation result: POSITIVE_INFINITY
evaluation result: NEGATIVE_INFINITY
evaluation result: undefined

还有 String:

input:for(p in String) log(p)
evaluation result: fromCharCode
evaluation result: prototype
evaluation result: prototype
evaluation result: undefined

在这里我们看到了很多不支持的东西,因此只要攻击者能够去简单检测一下,就可以绕过这个evaluation……

input:for(p in window) log(p)
evaluation result: Date
evaluation result: Scripting
evaluation result: MSXML2
evaluation result: ADODB
evaluation result: WScript
evaluation result: Enumerator
evaluation result: WMI
evaluation result: WinHTTP
evaluation result: Shell
evaluation result: WSH
evaluation result: ActiveXObject
evaluation result: GetObject
evaluation result: d
evaluation result: ScriptEngineBuildVersion
evaluation result: deconcept
evaluation result: hex2bin
evaluation result: rc4
evaluation result: log
evaluation result: dump
evaluation result: p
evaluation result: undefined

input:for(p in Object) log(p)
evaluation result: prototype
evaluation result: undefined

input:for(p in Function) log(p)
evaluation result: prototype
evaluation result: undefined

input:for(p in Array) log(p)
evaluation result: prototype
evaluation result: undefined

以及arguments:

input:function p(){for(x in arguments) log(x)} p()
evaluation result: undefined

所以看起来要对Funfuzz再一次做一个大工程,把不兼容的内容全部换成兼容代码。

以子之矛,陷子之盾,何如?——使用ChakraCore来Fuzz NScript (2)

我在很早以前做了一个多进程的Fuzz程序,其实最终的实现很简单,就是类似一个使用socket互相通信的程序,好似QQ互相发送消息一样。Server和Client约定好一组消息,互相通信。

代码实现很简单,百度随便一搜就是一大堆,比如:http://blog.csdn.net/yaopeng_2005/article/details/6696105,可以直接拿来用。NScript处,因为我需要将待分析的代码传给rs函数,在我没有传递完成前,整个流程都可以停滞。因此NScript作为客户端,只需要一直等待Server传来的Fuzz代码,并从Buffer中取出代码,传递给rs即可。

nscript.png

callClient.cpp

// callClient.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>  
#include <Winsock2.h>  

#pragma comment(lib, "ws2_32.lib")   


void main()  
{  
    WORD wVersionRequested = MAKEWORD(1, 1);   
    WSADATA wsaData;  
    int err = WSAStartup(wVersionRequested, &wsaData);

    if (err != 0) 
    {  
        return;  
    }  

    if (LOBYTE(wsaData.wVersion ) != 1 || HIBYTE(wsaData.wVersion) != 1) 
    {   
        WSACleanup();  
        return;  
    }  

    for (int index = 0; ; index++)  
    {  
        SOCKET sockClient = socket(AF_INET,SOCK_DGRAM, 0);  

        int len = sizeof(SOCKADDR);  

        SOCKADDR_IN local;  
        local.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");   
        local.sin_family = AF_INET;   
        local.sin_port = htons(27015);   

        DWORD dwSize = MAX_PATH;
        char sendBuf[MAX_PATH] = { 0 };  
        sprintf(sendBuf, "%3d,", index); 

        //client(ChakraCore) shall send a file name to client. incase of buffer has a limit of 64kb.
        strcat(sendBuf, "c:\\test\\wow.js"); 
        sendto(sockClient, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&local, len);  

        /*char recvBuf[50];  
        recvfrom(sockClient,recvBuf,50,0,(SOCKADDR*)&local,&len);  
        printf("my reply is : %s\n",recvBuf);  
        printf("%s\n",inet_ntoa(local.sin_addr));  
        */

        closesocket(sockClient);  
        Sleep(2000);       
        WSACleanup();  
    }  
}  

callServer.cpp

// callServer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>  
#include <Winsock2.h>  

#pragma comment(lib, "ws2_32.lib")   

void main()  
{  
    WORD wVersionRequested = MAKEWORD(1, 1);
    WSADATA wsaData;  
    int err = WSAStartup(wVersionRequested, &wsaData);  

    if (err != 0) {  
        return;  
    }  

    if ( LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {   
        WSACleanup();  
        return;  
    }  

    SOCKET sockSrv = socket(AF_INET,SOCK_DGRAM, 0);  

    int len = sizeof(SOCKADDR);  

    SOCKADDR_IN from;     
    SOCKADDR_IN local;   
    local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
    local.sin_family = AF_INET;   
    local.sin_port = htons(27015);   

    int bindResult = bind(sockSrv, (SOCKADDR*)&local, len);  
    while(1)  
    {  
        DWORD dwSize = MAX_PATH;
        char recvBuf[MAX_PATH];  

        //server(NScript) will receive a buffer contains filename of generated fuzzer.

        recvfrom(sockSrv, recvBuf, dwSize, 0, (SOCKADDR*)&from, &len);

        printf("%s\n", recvBuf);  
        printf("%s\n", inet_ntoa(local.sin_addr));  

        /*char sendBuf[50];  
        sprintf(sendBuf, "Welcome %s to here!", inet_ntoa(from.sin_addr));    
        sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&from, len);  
        */

        Sleep(2000);  
    }  
    closesocket(sockSrv);  

    WSACleanup();  
}  

而Client端如果发生异常,处理起来就更容易了,使用SetUnhandledExceptionFilter可以设置TopLevelExceptionFilter。这是一个定义为

LONG WINAPI UnhandledExceptionFilter(__in struct _EXCEPTION_POINTERS* pExceptionInfo)

的函数,可以在pExceptionInfo中取到异常信息并记录。

所以,这里的实现比较容易,
1、在NScript侧,WinMain进入时,调用
SetUnhandledExceptionFilter()

int WinMain(...)
{
    ...
    LPTOP_LEVEL_EXCEPTION_FILTER originalFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
    ...
}

2、MyUnhandledExceptionFilter里面可以收集异常信息并告警

LONG WINAPI MyUnhandledExceptionFilter(__in struct _EXCEPTION_POINTERS* pExcepInfo)
{
    DWORD dwCrashPid = GetCurrentProcessId();
    DWORD dwCrashTid = GetCurrentThreadId();

    pExcepInfo->ExceptionRecord.ExceptionCode;
    pExcepInfo->ExceptionRecord.ExceptionAddress;

    alert!
    ...

}

3、NScript部分,循环等待,并在接受到数据时调用__rs

4、ChakraCore部分

JsValueRef __stdcall WScriptJsrt::EchoCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
    for (unsigned int i = 1; i < argumentCount; i++)
    {
        ……
                if (i > 1)
                {
                    wprintf(_u(" "));
                }

                ////////////////////////////////////////////////////////
                SEND CODE HERE
                ////////////////////////////////////////////////////////

                wprintf(_u("%ls"), str.GetWideString());
            }
        }

        ……
    }
……

使用ChakraCore来Fuzz NScript

NScript是JScript的子集,前面的文章(http://www.nul.pw/2017/06/12/237.html)我介绍了使用NScript的方式,也提到了语法混乱,写的让人头疼的Mozilla Funfuzz的最终精简版本(http://www.nul.pw/2017/06/12/235.html),以及使用ChakraCore运行Funfuzz的壮观景象。

因为NScript事实上是一个非常精简的子集,以至于不能够支持Funfuzz那一大堆雄伟的语法,如下图:

nscript.png

事实上,先不说NScript,就连微软引以为豪的ChakraCore要支持起来都十分吃力,但是好在Funfuzz虽然代码写得奔放,但是最终生成的测试用例却格外的“人性化”。

转载请标明来源: http://nul.pw/
本文作者:blast

不过基本上看,NScript还是表现的和JScript很相似的:

代码测试
input:function p(){log(myVar)}; function q(){var myVar=2; p(); log(myVar);}; var myVar=1; log(myVar); q();
evaluation result: 1
evaluation result: 1
evaluation result: 2

为了能够取到Funfuzz的运行结果(即生成的Fuzz语句),而不修改Funfuzz的代码,(那一堆变量我实在是不敢修改啊)。我要选择更容易下手的——ChakraCore的代码。ChakraCore的print实现了类似console.log的功能,而我们可以在FunFuzz中print一下,来记录代码。

而只要我们知道print的代码,我们就可以在print时,将代码抛给NScript,这样,我们就可以同时Fuzz ChakraCore和NScript了。我知道你在想什么,wsprintf系列并不合适,因为不止print,ChakraCore的很多代码都依附类似的函数输出数据,所以我们要找到更精确的print。

print是一个很常见的名词,因此搜索并不管用,我们会搜出一大堆带print的函数。而ChakraCore也不支持阻塞的函数,比如alert之类的,这样就让我们的调试显得难以入手。不过还好,Math类下有一大堆名字独特的函数,比如反正切函数atan。Math.atan,我们搜索atan(之后,可以发现Js::Math::Atan的位置,给Atan的入口下断点。如果你不想多看一堆转换,最好是下一个参数的那个Atan。我们的代码是print(Math.atan(4))。运行后,断点停止在Atan处:

>   ChakraCore.dll!Js::Math::Atan(double x) Line 263    C++
    ChakraCore.dll!Js::Math::Atan(Js::RecyclableObject * function, Js::CallInfo callInfo, ...) Line 246 C++
    ChakraCore.dll!Js::LocalCallFunction(Js::RecyclableObject * function, void * (Js::RecyclableObject *, Js::CallInfo, ...) * entryPoint, Js::Arguments args, bool doStackProbe) Line 1313 C++
    ChakraCore.dll!Js::JavascriptFunction::CallFunction<1>(Js::RecyclableObject * function, void * (Js::RecyclableObject *, Js::CallInfo, ...) * entryPoint, Js::Arguments args) Line 1329  C++
    ChakraCore.dll!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, Js::RecyclableObject * function, unsigned int flags, const Js::AuxArray<unsigned int> * spreadIndices) Line 3902 C++
    ChakraCore.dll!Js::InterpreterStackFrame::OP_ProfileCallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, Js::RecyclableObject * function, unsigned int flags, unsigned short profileId, unsigned int inlineCacheIndex, const Js::AuxArray<unsigned int> * spreadIndices) Line 3927 C++
    ChakraCore.dll!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, unsigned int flags) Line 456    C++
    ChakraCore.dll!Js::InterpreterStackFrame::ProcessProfiled() Line 86 C++
    ChakraCore.dll!Js::InterpreterStackFrame::Process() Line 3452   C++
    ChakraCore.dll!Js::InterpreterStackFrame::InterpreterHelper(Js::ScriptFunction * function, Js::ArgumentReader args, void * returnAddress, void * addressOfReturnAddress, const bool isAsmJs) Line 2039  C++
    ChakraCore.dll!Js::InterpreterStackFrame::InterpreterThunk(Js::JavascriptCallStackLayout * layout) Line 1776    C++

我们的目标当然不是Atan,而是外面的print,返回后跟踪,可以看到ChakraCore取出Next Op,并在这里:

>   ChakraCore.dll!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, Js::RecyclableObject * function, unsigned int flags, const Js::AuxArray<unsigned int> * spreadIndices) Line 3888 C++
    ChakraCore.dll!Js::InterpreterStackFrame::OP_ProfileCallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, Js::RecyclableObject * function, unsigned int flags, unsigned short profileId, unsigned int inlineCacheIndex, const Js::AuxArray<unsigned int> * spreadIndices) Line 3927 C++
    ChakraCore.dll!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, unsigned int flags) Line 456    C++
    ChakraCore.dll!Js::InterpreterStackFrame::ProcessProfiled() Line 86 C++
    ChakraCore.dll!Js::InterpreterStackFrame::Process() Line 3452   C++
    ChakraCore.dll!Js::InterpreterStackFrame::InterpreterHelper(Js::ScriptFunction * function, Js::ArgumentReader args, void * returnAddress, void * addressOfReturnAddress, const bool isAsmJs) Line 2039  C++
    ChakraCore.dll!Js::InterpreterStackFrame::InterpreterThunk(Js::JavascriptCallStackLayout * layout) Line 1776    C++
    [External Code] 

可以看到调用:

JavascriptFunction::CallFunction<true>(function, function->GetEntryPoint(), args);

这里的function就是最终实现print的函数。可以看到ChakraCore在这里使用了JSRT来扩展(参考我翻译的文章:http://tem.pw/?x=entry:entry170620-222216)实现了print,无论怎样,我们找到了print的位置:

-       function    0x0210c540 {signature=0x00000000 callbackState=0x00000000 nativeMethod=0x00251bc0 {ch.exe!WScriptJsrt::EchoCallback(void *, bool, void * *, unsigned short, void *)} ...}   Js::RecyclableObject *
-       [Js::JavascriptExternalFunction]    {signature=0x00000000 callbackState=0x00000000 nativeMethod=0x00251bc0 {ch.exe!WScriptJsrt::EchoCallback(void *, bool, void * *, unsigned short, void *)} ...}  Js::JavascriptExternalFunction
+       Js::RuntimeFunction {functionNameId=0x0210a720 }    Js::RuntimeFunction
        signature   0x00000000  void *
        callbackState   0x00000000  void *
        nativeMethod    0x00251bc0 {ch.exe!WScriptJsrt::EchoCallback(void *, bool, void * *, unsigned short, void *)}   void * (Js::RecyclableObject *, Js::CallInfo, void * *) *
+       wrappedMethod   0x00251bc0 {ch.exe!WScriptJsrt::EchoCallback(void *, bool, void * *, unsigned short, void *)} {signature=...}   Js::JavascriptExternalFunction *
        stdCallNativeMethod 0x00251bc0 {ch.exe!WScriptJsrt::EchoCallback(void *, bool, void * *, unsigned short, void *)}   void * (void *, bool, void * *, unsigned short, void *) *
        initMethod  0x00000000  HRESULT (void *) *
        oneBit  1   unsigned int
        typeSlots   0   unsigned int
        hasAccessors    0   unsigned int
        unused  0   unsigned int
        prototypeTypeId -1  int
        flags   0   unsigned __int64
+       FinalizableObject   {...}   FinalizableObject
-       type    0x020f2c80 {typeId=TypeIds_Function (26) flags=TypeFlagMask_None (0 '\0') javascriptLibrary=0x02110000 {...} ...}   Js::Type *
        typeId  TypeIds_Function (26)   Js::TypeId
        flags   TypeFlagMask_None (0 '\0')  TypeFlagMask
+       javascriptLibrary   0x02110000 {cacheForCopyOnAccessArraySegments=0x02122000 {cache=0x02122000 {0x00000000 <NULL>, 0x00000000 <NULL>, ...} ...} ...}    Js::JavascriptLibrary *
-       prototype   0x020f2540 {constructorCache=0x10a921b0 {ChakraCore.dll!Js::ConstructorCache Js::ConstructorCache::DefaultInstance} {...} ...}  Js::RecyclableObject *
-       [Js::JavascriptFunction]    {constructorCache=0x10a921b0 {ChakraCore.dll!Js::ConstructorCache Js::ConstructorCache::DefaultInstance} {...} ...} Js::JavascriptFunction
+       Js::DynamicObject   {auxSlots=0x00000000 {???} objectArray=0x00000000 <NULL> arrayFlags=None (0) ...}   Js::DynamicObject
+       constructorCache    0x10a921b0 {ChakraCore.dll!Js::ConstructorCache Js::ConstructorCache::DefaultInstance} {guard={value=...} ...}  Js::ConstructorCache *
+       functionInfo    0x10a93dd8 {ChakraCore.dll!Js::FunctionInfo Js::JavascriptFunction::EntryInfo::PrototypeEntryPoint} {...}   Js::FunctionInfo *
+       FinalizableObject   {...}   FinalizableObject
+       type    0x020f2520 {typeId=TypeIds_Function (26) flags=TypeFlagMask_None (0 '\0') javascriptLibrary=0x02110000 {...} ...}   Js::Type *
        entryPoint  0x0fe56270 {ChakraCore.dll!Js::JavascriptExternalFunction::StdCallExternalFunctionThunk(Js::RecyclableObject *, Js::CallInfo, ...)} void * (Js::RecyclableObject *, Js::CallInfo, ...) *
+       propertyCache   0x00000000 <NULL>   Js::TypePropertyCache *

为WScriptJsrt::EchoCallback下断点,运行过去,栈如下:

>   ch.exe!WScriptJsrt::EchoCallback(void * callee, bool isConstructCall, void * * arguments, unsigned short argumentCount, void * callbackState) Line 105  C++
    ChakraCore.dll!Js::JavascriptExternalFunction::StdCallExternalFunctionThunk(Js::RecyclableObject * function, Js::CallInfo callInfo, ...) Line 275   C++
    ChakraCore.dll!Js::LocalCallFunction(Js::RecyclableObject * function, void * (Js::RecyclableObject *, Js::CallInfo, ...) * entryPoint, Js::Arguments args, bool doStackProbe) Line 1313 C++
    ChakraCore.dll!Js::JavascriptFunction::CallFunction<1>(Js::RecyclableObject * function, void * (Js::RecyclableObject *, Js::CallInfo, ...) * entryPoint, Js::Arguments args) Line 1329  C++
    ChakraCore.dll!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, Js::RecyclableObject * function, unsigned int flags, const Js::AuxArray<unsigned int> * spreadIndices) Line 3891 C++
    ChakraCore.dll!Js::InterpreterStackFrame::OP_ProfileCallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, Js::RecyclableObject * function, unsigned int flags, unsigned short profileId, unsigned int inlineCacheIndex, const Js::AuxArray<unsigned int> * spreadIndices) Line 3927 C++
    ChakraCore.dll!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > >(const Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > * playout, unsigned int flags) Line 456    C++
    ChakraCore.dll!Js::InterpreterStackFrame::ProcessProfiled() Line 86 C++
    ChakraCore.dll!Js::InterpreterStackFrame::Process() Line 3452   C++
    ChakraCore.dll!Js::InterpreterStackFrame::InterpreterHelper(Js::ScriptFunction * function, Js::ArgumentReader args, void * returnAddress, void * addressOfReturnAddress, const bool isAsmJs) Line 2039  C++
    ChakraCore.dll!Js::InterpreterStackFrame::InterpreterThunk(Js::JavascriptCallStackLayout * layout) Line 1776    C++
    [External Code] 

对AutoString str进行检视:

-       str {length=18 data=0x00486730 "1.3258176636680325" data_wide=0x00000000 <NULL> ...}    AutoString
        length  18  unsigned int
+       data    0x00486730 "1.3258176636680325" char *
+       data_wide   0x00000000 <NULL>   wchar_t *
        errorCode   JsNoError (0)   _JsErrorCode
        dontFree    false   bool

值1.3258176636680325确实就是Atan(4)的结果。

所以,我们只要修改这个函数,用它来和NScript进行通信,就可以达到我们想要的效果。