<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    《Chrome V8原理講解》第十六篇 運行時輔助類,詳解加載與調用過程

    VSole2021-10-29 16:21:00

    前言

    本系列的前十三篇文章,講解了V8執行Javascript時最基礎的工作流程和原理,包括詞法分析、語法分析、字節碼生成、Builtins方法、ignition執行單元,等等,達到了從零做起,入門學習的目的。

    接下來的文章將以問題為導向講解V8源碼,例如:以閉包技術、或垃圾回收(GC)為專題講解V8中的相關源碼。V8代碼過于龐大,以問題為導向可以使得學習主題更加明確、效果更好。同時,我爭取做到每篇文章是一個獨立的知識點,方便大家閱讀。

    讀者可以把想學的內容在文末評論區留言,我匯總后出專題文章。

    一、摘要

    運行時輔助類(Runtime)在Javascript執行期提供眾多輔助功能,如屬性訪問,新建對象、正則表達式等。上篇文章對“Runtime是什么?怎么用了?”做了詳細介紹,本文將重點說明“他是怎么來的?他存儲在哪?”,也就是Runtime在V8中的初始化過程,初始化完后的存儲位置,以及在BytecodeHandler中的調用細節。

    二、Runtime初始過程

    Runtime屬于V8的基礎組件,在創建Isolate時完成初始化,下面是初始化階段的源碼:


    1.   bool Isolate::Init(ReadOnlyDeserializer* read_only_deserializer,2.                      StartupDeserializer* startup_deserializer) {3.     TRACE_ISOLATE(init);4.     const bool create_heap_objects = (read_only_deserializer == nullptr);5.     // We either have both or neither.6.     DCHECK_EQ(create_heap_objects, startup_deserializer == nullptr);7.     base::ElapsedTimer timer;8.    //省略很多...............................9.      handle_scope_implementer_ = new HandleScopeImplementer(this);10.      load_stub_cache_ = new StubCache(this);11.      store_stub_cache_ = new StubCache(this);12.      materialized_object_store_ = new MaterializedObjectStore(this);13.      regexp_stack_ = new RegExpStack();14.      regexp_stack_->isolate_ = this;15.      date_cache_ = new DateCache();16.      heap_profiler_ = new HeapProfiler(heap());17.      interpreter_ = new interpreter::Interpreter(this);18.      compiler_dispatcher_ =19.          new CompilerDispatcher(this, V8::GetCurrentPlatform(), FLAG_stack_size);20.      // Enable logging before setting up the heap21.      logger_->SetUp(this);22.      {  // NOLINT23.        ExecutionAccess lock(this);24.        stack_guard()->InitThread(lock);25.      }26.      // SetUp the object heap.27.      DCHECK(!heap_.HasBeenSetUp());28.      heap_.SetUp();29.      ReadOnlyHeap::SetUp(this, read_only_deserializer);30.      heap_.SetUpSpaces();31.      isolate_data_.external_reference_table()->Init(this);32.      //省略很多...................33.    }
    

    上述代碼是isolate的初始化入口,行10~20包括了很多重要組件的初始化工作,例如Interpreter、compiler_dispatcher等等,后續文章都會講到。Runtime的初始化由external負責,代碼31行Init()方法中完成相關工作,源碼如下:

    1.  void ExternalReferenceTable::Init(Isolate* isolate) {2.    int index = 0;3.    // kNullAddress is preserved through serialization/deserialization.4.    Add(kNullAddress, &index);5.    AddReferences(isolate, &index);6.    AddBuiltins(&index);7.    AddRuntimeFunctions(&index);8.    AddIsolateAddresses(isolate, &index);9.    AddAccessors(&index);10.    AddStubCache(isolate, &index);11.    AddNativeCodeStatsCounters(isolate, &index);12.    is_initialized_ = static_cast<uint32_t>(true);13.    CHECK_EQ(kSize, index);14.  }
    

    代碼4行~11行是由external類負責管理的初始化,這其中包括了我們多次提到的Builtins。AddRuntimeFunctions(&index)是Runtime的初始化函數,代碼如下:

    1.  void ExternalReferenceTable::AddRuntimeFunctions(int* index) {2.    CHECK_EQ(kSpecialReferenceCount + kExternalReferenceCount +3.                 kBuiltinsReferenceCount,4.             *index);5.    static constexpr Runtime::FunctionId runtime_functions[] = {6.  #define RUNTIME_ENTRY(name, ...) Runtime::k##name,7.        FOR_EACH_INTRINSIC(RUNTIME_ENTRY)8.  #undef RUNTIME_ENTRY9.    };10.    for (Runtime::FunctionId fId : runtime_functions) {11.      Add(ExternalReference::Create(fId).address(), index);12.    }13.    CHECK_EQ(kSpecialReferenceCount + kExternalReferenceCount +14.                 kBuiltinsReferenceCount + kRuntimeReferenceCount,15.             *index);16.  }
    

    它只有一個參數index,在本文所用的V8版本中它的值是430,代表下標,ExternalReferenceTable是表結構,它的核心成員是地址指針數組ref_addr_,index代表它的下標,表示Runtime函數在ExternalReferenceTable成員變量ref_addr_中的位置,本文所用V8中有468個Runtime方法,初始化完成后存儲在ref_addr_的430~430+468-1這個區間內。ExternalReferenceTable表結構非常簡單,稍后給出。

    代碼5行定義了Runtime的枚舉變量,其中涉及的宏模板請自行展開,圖1給出部分枚舉變量。代碼11行通過for循環添加函數,Create()方法根據Runtime Id創建表項,最終添加到ExternalReferenceTable表中,代碼如下:

    1.  ExternalReference ExternalReference::Create(Runtime::FunctionId id) {2.    return Create(Runtime::FunctionForId(id));3.  }4.  //分隔線........................5.  const Runtime::Function* Runtime::FunctionForId(Runtime::FunctionId id) {6.    return &(kIntrinsicFunctions[static_cast<int>(id)]);7.  }8.  //分隔線.......................9.  ExternalReference ExternalReference::Create(const Runtime::Function* f) {10.    return ExternalReference(11.        Redirect(f->entry, BuiltinCallTypeForResultSize(f->result_size)));12.  }
    

    上述代碼是三個函數,依次調用。第一個函數Create(Runtime::FunctionId id)的參數是圖1中的枚舉值;第二個函數FunctionForId(Runtime::FunctionId id)的返回值是kIntrinsicFunctions類型的數據,該數據是以下幾個宏代碼的展開結果。

    #define FUNCTION_ADDR(f) (reinterpret_cast<v8::internal::Address>(f))#define F(name, number_of_args, result_size)                                  \  {                                                                           \    Runtime::k##name, Runtime::RUNTIME, #name, FUNCTION_ADDR(Runtime_##name), \        number_of_args, result_size                                           \  }                                                                           \  ,
    #define I(name, number_of_args, result_size)                       \  {                                                                \    Runtime::kInline##name, Runtime::INLINE, "_" #name,            \        FUNCTION_ADDR(Runtime_##name), number_of_args, result_size \  }                                                                \  ,
    static const Runtime::Function kIntrinsicFunctions[] = {    FOR_EACH_INTRINSIC(F) FOR_EACH_INLINE_INTRINSIC(I)};
    #undef I#undef F
    

    kIntrinsicFunctions數據是什么類型?它是數組,每個成員又是函數名,參數個數,返回值個數組成的另一個數組。上一篇文章中,我定義的MyRuntime方法,格式是:F(MyRuntime,1,1),正好和這個數據格式對應,下面是kIntrinsicFunctions展開的樣例:

    kIntrinsicFunctions []={  //.....................  {                                                                               Runtime::kDebugPrint, Runtime::RUNTIME, "DebugPrint", (reinterpret_cast<v8::internal::Address>(Runtime_DebugPrint)),         1, 1   },  //.....................
    

    kIntrinsicFunctions是一個二維數組,上述代碼展示了其中的一組數據。下面是void ExternalReferenceTable::AddRuntimeFunctions(int* index)方法中11行Add()方法的源碼:

    void ExternalReferenceTable::Add(Address address, int* index) {  ref_addr_[(*index)++] = address;}
    

    index每次添加后會增1,ref_addr_是什么呢?它是ExternalReferenceTable的成員變量,地址指針數組,下面是ExternalReferenceTable源碼:

    1.  class ExternalReferenceTable {2.   public:3.    static constexpr int kSpecialReferenceCount = 1;4.    static constexpr int kExternalReferenceCount =5.        ExternalReference::kExternalReferenceCount;6.    static constexpr int kBuiltinsReferenceCount =7.  #define COUNT_C_BUILTIN(...) +18.        BUILTIN_LIST_C(COUNT_C_BUILTIN);9.  #undef COUNT_C_BUILTIN10.     static constexpr int kRuntimeReferenceCount =11.         Runtime::kNumFunctions -12.         Runtime::kNumInlineFunctions;  // Don't count dupe kInline... functions.13.     static constexpr int kIsolateAddressReferenceCount = kIsolateAddressCount;14.     static constexpr int kAccessorReferenceCount =15.         Accessors::kAccessorInfoCount + Accessors::kAccessorSetterCount;16.     static constexpr int kStubCacheReferenceCount = 12;17.     static constexpr int kStatsCountersReferenceCount =18.   #define SC(...) +119.         STATS_COUNTER_NATIVE_CODE_LIST(SC);20.   #undef SC21.   //..............省略很多............................22.     ExternalReferenceTable() = default;23.     void Init(Isolate* isolate);24.    private:25.     void Add(Address address, int* index);26.     void AddReferences(Isolate* isolate, int* index);27.     void AddBuiltins(int* index);28.     void AddRuntimeFunctions(int* index);29.     void AddIsolateAddresses(Isolate* isolate, int* index);30.     void AddAccessors(int* index);31.     void AddStubCache(Isolate* isolate, int* index);32.     Address GetStatsCounterAddress(StatsCounter* counter);33.     void AddNativeCodeStatsCounters(Isolate* isolate, int* index);34.     STATIC_ASSERT(sizeof(Address) == kEntrySize);35.     Address ref_addr_[kSize];36.     static const char* const ref_name_[kSize];37.     uint32_t is_initialized_ = 0;38.     uint32_t dummy_stats_counter_ = 0;39.     DISALLOW_COPY_AND_ASSIGN(ExternalReferenceTable);40.   };
    

    代碼7行給出了Builtin統計時使用的宏模板,代碼行25~35說明了ExternalReferenceTable負責初始化和管理哪些數據。初始化后的數據,也就是函數地址,保存在代碼35行的ref_addr_數組中,這個數組的類型Address是using Address = uintptr_t;,typedef unsigned __int64 uintptr_t;。

    圖2中給出了三個關鍵信息,Add()方法的調用位置,對應的函數調用堆棧,以及展示了部分ref_addr_的內容。

    總結,ExternalReferenceTable最重要的成員是ref_addr_,它本質是一個指針數組,Rumtime函數保存在下標430開始的成員中,調用時用下標值索引函數地址。

    三、Runtime調用

    CallRuntime()或CallRuntimeWithCEntry()負責調用Runtime功能,上篇文章給出了調用樣例并做了實驗,本文不再贅述。我們以CallRuntime()為例講解,源碼如下:

    1.   template <class... TArgs>2.   TNode<Object> CallRuntime(Runtime::FunctionId function,3.                             SloppyTNode<Object> context, TArgs... args) {4.     return CallRuntimeImpl(function, context,5.                            {implicit_cast<SloppyTNode<Object>>(args)...});6.   }7.  //分隔線.........................8.   TNode<Object> CodeAssembler::CallRuntimeImpl(9.       Runtime::FunctionId function, TNode<Object> context,10.        std::initializer_list<TNode<Object>> args) {11.      int result_size = Runtime::FunctionForId(function)->result_size;12.      TNode<Code> centry =13.          HeapConstant(CodeFactory::RuntimeCEntry(isolate(), result_size));14.      return CallRuntimeWithCEntryImpl(function, centry, context, args);15.    }16.  //分隔線.........................17.    TNode<Type> HeapConstant(Handle<Type> object) {18.        return UncheckedCast<Type>(UntypedHeapConstant(object));19.      }20.  //分隔線.........................21.  TNode<Object> CodeAssembler::CallRuntimeWithCEntryImpl(22.        Runtime::FunctionId function, TNode<Code> centry, TNode<Object> context,23.        std::initializer_list<TNode<Object>> args) {24.      constexpr size_t kMaxNumArgs = 6;25.      DCHECK_GE(kMaxNumArgs, args.size());26.      int argc = static_cast<int>(args.size());27         auto call_descriptor = Linkage::GetRuntimeCallDescriptor(zone(), function, argc, Operator::kNoProperties,                      Runtime::MayAllocate(function) ? CallDescriptor::kNoFlags                                     : CallDescriptor::kNoAllocate);28.      for (auto arg : args) inputs.Add(arg);29.      inputs.Add(ref);30.      inputs.Add(arity);31.      inputs.Add(context);32.      CallPrologue();33.      Node* return_value =34.          raw_assembler()->CallN(call_descriptor, inputs.size(), inputs.data());35.      HandleException(return_value);36.      CallEpilogue();37.      return UncheckedCast<Object>(return_value);38.    }
    

    代碼2行,CallRuntime()聲明中,可以看到它有三個參數,第一個是FunctionId枚舉類型,前面提到過;第二個參數是context,第三參數args是傳給Runtime函數的參數列表。CallRuntime()調用CallRuntimeImpl(),在CallRuntimeImpl()內部讀取堆中的常量數據(HeapConstant),代碼18行,該數據中保存了函數與下標的對應關系,用FunctionId在該表中查到對應的函數地址,代碼27行建立調用描述符(參見之前的文章),最終在代碼34行調用Runtime函數。

    上述代碼是Builtin,不能在C++級別做Debug,無法給出調用堆棧等調試信息。也許你會有疑問:為什么不用上篇文章中提到的RUNTIME_DebugPrint或是自定義Runtime功能做斷點?答:我們現在講的就是Runtime的調用過程,沒辦法使用Runtime調試自己:(((。

    要調試也是有辦法的,匯編調試,匯編調試超出了V8的學習范圍,不在此講解了,對此感興趣的朋友,評論區私信我。

    最后,糾正《第十五篇》中的一個錯誤,我曾寫到:“context是傳給MyRuntime()的第一個參數,這是格式要求,注意:它不計在參數的數量中! 通過下面的測試代碼,對MyRuntime做測試:”。 正確的解釋是:context不是MyRuntime()的第一個參數,它是CallRuntime()的第二參數,與MyRuntime()無關。

    好了,今天到這里,下次見。

    懇請讀者批評指正、提出寶貴意見

    函數調用runtime
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    今年初,Akamai 的研究人員發現了一個新型惡意僵尸網絡,它以 Realtek SDK、華為路由器和 Hadoop YARN 服務器為目標,將設備引入到 DDoS 群中,有可能進行大規模攻擊。這個新型僵尸網絡是研究人員在自己的 HTTP 和 SSH 蜜罐上發現的,該僵尸網絡利用了 CVE-2014-8361 和 CVE-2017-17215 等漏洞。
    常規調試下watchpoint功能的受限及trace的低效是由于我們是使用軟件方式在用戶態進行操作,受到了CPU及操作系統的限制。但QEMU主要關注于仿真,對于安全分析來說并不友好。原因在于這個程序只是在控制臺打印了HelloWorld,并沒有涉及到JNI相關操作。Qiling的這種做法,以最小的成本保證了對各類各個版本的系統最大的適配性,并且也保證了程序運行狀態與真實環境差異較小。
    當線程從等待狀態蘇醒后,會自動檢測自己得APC隊列中是否存在APC過程。所以只需要將目標進程的線程的APC隊列里面添加APC過程,當然為了提高命中率可以向進程的所有線程中添加APC過程。然后促使線程從休眠中恢復就可以實現APC注入。往線程APC隊列添加APC,系統會產生一個軟中斷。第二個參數表示插入APC的線程句柄,要求線程句柄必須包含THREAD_SET_CONTEXT訪問權限。第三個參數表示傳遞給執行函數的參數。如果直接傳入shellcode不設置第三個函數,可以直接執行shellcode。
    反射式DLL注入實現
    2022-05-13 15:59:21
    反射式dll注入與常規dll注入類似,而不同的地方在于反射式dll注入技術自己實現了一個reflective loader()函數來代替LoadLibaryA()函數去加載dll,示意圖如下圖所示。藍色的線表示與用常規dll注入相同的步驟,紅框中的是reflective loader()函數行為,也是下面重點描述的地方。
    欺騙防御新方法:rMTD
    2021-09-21 22:41:45
    如果運行的操作系統和應用中存在漏洞,那攻擊者大概率會找到一個利用它的方法。唯一確定的解決隱患方式就是從程序庫中修復問題。但是,在安全補丁發布前,系統依然有被攻陷的風險。許多人不得不接受這種情況。 不過,事情可能出現了轉機:輪換移動目標防御技術(rotational Moving Target Defense, rMTD)。
    本系列將以官網資料為基礎主要通過動態跟蹤來解析DynamoRIO的源代碼。因為如果不結合實例只是將各函數的作用寫出來,實在無法很好的說明問題,我們將以代碼覆蓋工具drcov為例,分析DynamoRIO的執行流程。
    前置知識分析Transformer接口及其實現類。transform()傳入對象,進行反射調用。構造調用鏈調用鏈構造原則:找調用關系要找不同名的方法,如果找到同名,再通過find usages得到的還是一樣的結果。找到InvokerTransformer類中的transform(),右鍵,點 Find Usages,找函數調用關系,最好找不同名的方法,調用了transform()。因為transform()調用transform()不能換到別的方法里,沒有意義。如果有一個類的readObject()調用了get(),那我們就可能找到了調用鏈。最終選擇TransformedMap這個類,因為TransformedMap類中有好幾處都調用了transform()。
    Thinkphp是一個國內輕量級的開發框架,采用php+apache,在更新迭代中,thinkphp也經常爆出各種漏洞,thinkphp一般有thinkphp2、thinkphp3、thinkphp5、thinkphp6版本,前兩個版本已經停止更新
    通過查閱相關資料得知虛幻引擎是通過'UNetDriver'進行網絡交互的,而'UNetDriver'是在'UWorld'下,那么我們就需要對游戲先進行sdk dump拿到實例化對象。
    前言本系列的前十三篇文章,講解了V8執行Javascript時最基礎的工作流程和原理,包括詞法分析、語法分析、字節碼生成、Builtins方法、ignition執行單元,等等,達到了從零做起,入門學習的目的。接下來的文章將以問題為導向講解V8源碼,例如:以閉包技術、或垃圾回收為專題講解V8中的相關源碼。V8代碼過于龐大,以問題為導向可以使得學習主題更加明確、效果更好。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类