Reverse Engineering แอปพลิเคชัน React Native ที่ใช้ Hermes: พื้นฐานและ Hermes Bytecode

content-image

1. Mobile App Security Landscape 📱 : The Cross-Platform Challenge

ในปัจจุบันการพัฒนา mobile application ขึ้นมา มีแนวโน้มไปในทางที่มีการใช้งาน framework ที่เป็น cross-platform ทำให้เขียน code แค่ครั้งเดียว แต่สามารถ build เพื่อให้ใช้งานได้บนหลาย platform และเทคนิคการทำ reverse engineering, static analysis, dynamic analysis ในแต่ละ framework นั้นแทบจะแตกต่างกันโดยสิ้นเชิง ซึ่งการที่จะสามารถเข้าใจการทำงานของ application และหาจุดอ่อน/ช่องโหว่ทาง security ให้พบนั้น จำเป็นที่จะต้องรู้การทำงานภายในของแต่ละ framework และปัญหานี้ในปัจจุบัน (และอนาคต) เป็นความท้าทายพอสมควรสำหรับ security researcher เนื่องด้วยเทคโนโลยีมีการพัฒนาอย่างรวดเร็ว ทำให้เครื่องมือและความรู้เกี่ยวกับการวิเคราะห์ security ของ app นั้นตามไม่ทัน

มีมจากเกม ตัวละครยืนหันหลังอยู่ในตรอก พร้อมคำบรรยายภาษาอังกฤษที่แสดงความเอือมระอามีมจากเกม ตัวละครยืนหันหลังอยู่ในตรอก พร้อมคำบรรยายภาษาอังกฤษที่แสดงความเอือมระอา

ในบทความนี้จะเป็นบทความที่พาทุกท่านไปทำความรู้จักกับ mobile app ที่ถูกพัฒนาด้วย React Native และมีการใช้งาน Hermes engine ในขั้นพื้นฐาน ตั้งแต่ความแตกต่างจาก JavaScriptCore วิธีตรวจสอบว่า app ใช้ Hermes หรือไม่ ไปจนถึงการ disassemble Hermes bytecode (HBC) ให้อยู่ในรูปแบบที่อ่านได้ โดย DemoSecret app เป็น app ที่มี secret flag ซ่อนอยู่ภายใน และทุกท่านสามารถใช้ app นี้ลองทำตามแต่ละขั้นตอนที่อธิบายไว้ในบทความนี้ได้เลยตลอดทั้งบทความครับ

หัวข้อที่ลึกกว่านี้ยังไม่ครอบคลุมในบทความนี้ ได้แก่

  1. โครงสร้างของไฟล์ Hermes bytecode, วิธีการแปลง JavaScript code ไปเป็น Hermes bytecode ในเชิงลึก, ศัพท์ที่ควรรู้, คำอธิบาย Hermes instruction และการทำ reverse engineering อ่าน Hermes bytecode เพื่อแก้โจทย์ปัญหาใน DemoSecret app แบบเบื้องต้น
  2. การ walkthrough ทำ reverse engineering DemoSecret app ด้วยการอ่าน Hermes bytecode จนหา secret flag ได้ ความแตกต่างระหว่างการใช้ Hermes แบบที่มีและไม่มีการทำ obfuscation รวมถึงคำถามว่ายังจำเป็นต้องทำ obfuscation อยู่ไหม และเครื่องมือ/แหล่งข้อมูลต่าง ๆ ที่ช่วยในการทำ reverse engineering, static analysis, dynamic analysis ตัว app ที่มีการใช้งาน Hermes

โดยหวังว่าบทความนี้จะเป็นบทความที่มีประโยชน์กับทั้ง security researcher และ developer ที่ต้องการทราบการทำงานภายในของ app ที่พัฒนาด้วย React Native และมีการใช้งาน Hermes engine

ไปลุยกันเลยครับ ~ 🚀


2. React Native Architecture 🏛️ : JavaScriptCore vs Hermes

JavaScriptCore: Legacy Runtime Analysis

ในอดีตเมื่อเราพัฒนา app ด้วย React Native และ release app ออกไป ตัว app จะมีการใช้งาน JavaScriptCore ในการ execute JavaScript code ที่อยู่ในไฟล์ app

โลโก้ WebKitโลโก้ WebKit

โดย JavaScriptCore จะเป็น virtual machine เล็ก ๆ ตัวหนึ่ง และมีการทำงานในรูปแบบ JIT (Just-in-Time) compiler ซึ่งถ้าจะอธิบายคร่าว ๆ คือ VM ตัวนี้จะเอา JavaScript code มาผ่านกระบวนการต่าง ๆ ที่เกี่ยวข้องกับการ compile เช่น parsing, optimization, bytecode generation จนผลลัพธ์สุดท้ายได้เป็น machine code ออกมา และนำ machine code ที่ได้ไป execute โดยกระบวนการดังกล่าวจะทำตอนที่ JavaScript code ส่วนนั้น ๆ กำลังจะถูกเรียกใช้งาน ลักษณะการทำงานแบบนี้ถึงถูกเรียกว่า Just-In-Time (คือ ทำตอนกำลังจะถูกเรียกใช้งาน นั่นเอง 🔥)

ข้างบนเป็นคำอธิบายคร่าว ๆ ให้พอเข้าใจเท่านั้น ใครอยากรู้รายละเอียดที่ลึก ๆ สามารถไปอ่านได้จาก

ถ้าเป็นในกรณีที่มีการใช้งาน JavaScriptCore แบบนี้ปกติแล้ว หากเราต้องการจะทำ reverse engineering ก็แค่ไปที่ ไฟล์ตาม directory ด้านล่างที่อยู่ในไฟล์ APK (Android 🤖) หรือ IPA (iOS 🍎) และสามารถใช้ code editor เพื่อเปิดอ่านได้เลย

ไฟล์ APK 🤖

$ file ./assets/index.android.bundle
index.android.bundle: ASCII text, with very long lines

ไฟล์ IPA 🍎

$ file ./main.jsbundle
main.jsbundle: ASCII text, with very long lines

📝 ตัวอย่างเนื้อหาในไฟล์

เนื้อหาในไฟล์ main.jsbundle ที่เป็น JavaScript code ถูก bundle เป็นบรรทัดยาว ๆ อ่านยากเนื้อหาในไฟล์ main.jsbundle ที่เป็น JavaScript code ถูก bundle เป็นบรรทัดยาว ๆ อ่านยาก

โดยจะเห็นได้ว่าในไฟล์จะเป็น JavaScript code ที่ถูก bundle มา ซึ่งจะอ่านค่อนข้างยาก เพื่อให้เข้าใจ code ได้ง่ายขึ้น เราสามารถใช้ react-native-decompiler เพื่อ unbundle 🗃️ ไฟล์ออกมาได้

$ react-native-decompiler -i ./index.android.bundle -o ./output

และ/หรืออาจจะใช้ js-beautify ในการปรับ format หรือ deobfuscate code ในกรณีที่ code ถูก obfuscate มาให้อ่านง่ายขึ้น

$ js-beautify index.android.bundle > output.js

เวลาต้องการจะแก้ code ก็แค่แก้ code JavaScript แล้ว pack 📦 และ sign ✍️ ไฟล์ app ใหม่อีกรอบ แล้วก็เอาไปติดตั้งบนโทรศัพท์ ก็เป็นอันเสร็จพิธี ไม่มีอะไรซับซ้อนมากนัก ซึ่งในบทความนี้จะไม่เน้นรายละเอียดตรงนี้มาก เนื่องจากสามารถหาวิธีการได้ง่ายดายจาก Internet เช่น Android, iOS

Hermes Engine: Next-Gen JavaScript Runtime

เกริ่นพื้นฐานไปนานก็จะเข้าสู่หัวใจของบทความนี้กันแล้ว นั่นก็คือ Hermes engine นั่นเอง

โลโก้ Hermes engine ตัวอักษร H สีเขียวมีปีกโลโก้ Hermes engine ตัวอักษร H สีเขียวมีปีก

Hermes (อ่านว่า hur-meez) คือ engine (หรือ virtual machine) ตัวใหม่ที่ถูกพัฒนาโดย Facebook ที่จะทำให้ app ที่พัฒนาด้วย React Native เปิดได้ไวขึ้น, ใช้งาน memory น้อยลง, ขนาดของ app เล็กลง Hermes engine นี้ถูก enable เป็นค่าตั้งต้นในการ build app ทั้งฝั่ง Android และ iOS มาตั้งแต่ React Native version 0.70

รูปด้านล่างจะเป็น Benchmarking Data ของ Hermes บน Android และ iOS จาก บทความ Hermes as the default ของ React Native

ข้อมูล benchmark ของ Hermes บน Android: เวลาเริ่มต้นเร็วขึ้น 51% (1.97 วินาที เหลือ 0.96 วินาที), ขนาด APK เล็กลง 13.9 MB (72.1 MB เหลือ 58.2 MB), ใช้หน่วยความจำลดลง 93 MB (398 MB เหลือ 305 MB)ข้อมูล benchmark ของ Hermes บน Android: เวลาเริ่มต้นเร็วขึ้น 51% (1.97 วินาที เหลือ 0.96 วินาที), ขนาด APK เล็กลง 13.9 MB (72.1 MB เหลือ 58.2 MB), ใช้หน่วยความจำลดลง 93 MB (398 MB เหลือ 305 MB)

ข้อมูล benchmark ของ Hermes บน iOS: เวลาเริ่มต้นเร็วขึ้น 63% (0.52 วินาที เหลือ 0.19 วินาที), ขนาด IPA เพิ่มขึ้น 4.4 MB (23.2 MB เป็น 27.6 MB), ใช้หน่วยความจำลดลง 13 MB (120 MB เหลือ 107 MB)ข้อมูล benchmark ของ Hermes บน iOS: เวลาเริ่มต้นเร็วขึ้น 63% (0.52 วินาที เหลือ 0.19 วินาที), ขนาด IPA เพิ่มขึ้น 4.4 MB (23.2 MB เป็น 27.6 MB), ใช้หน่วยความจำลดลง 13 MB (120 MB เหลือ 107 MB)

Hermes engine จะมีการทำงานในรูปแบบ AOT (ไม่ใช่ Attack on Titan แต่เป็น Ahead-Of-Time) ซึ่งจะตรงกันข้ามกันกับ JIT โดย AOT จะเป็นการพยายามเอากระบวนการที่เกี่ยวข้องกับการ compile เช่น parsing, optimization, bytecode generation มาทำขณะที่กำลัง build app ขึ้นมา จึงได้ผลลัพธ์ออกมาเป็นสิ่งที่เรียกว่า Hermes bytecode (HBC) และ Hermes bytecode ตัวนี้แหละ ที่ถูก execute โดย Hermes engine ตอนที่ app ถูกเรียกใช้งาน ทำให้ overhead ในการ execute code ของ app เมื่อเทียบกับแบบ JIT แล้วต่ำลง เนื่องจากงานที่เกี่ยวข้องกับการ compile (เช่น parsing, optimization, bytecode generation) นั่นถูกทำไปเรียบร้อยแล้วในขณะที่ app ถูก build ขึ้นมา และตัว Hermes engine มีหน้าที่ในการนำ bytecode มา execute เพียงอย่างเดียว

หรือใครนึกภาพไม่ออกให้นึกถึงภาษา C ที่เวลาเราจะ execute ก็ต้อง compile ให้เป็น machine code ก่อน พอตอนจะ execute ตัว CPU ของ computer เราก็สามารถอ่าน machine code นั้นโดยตรงแล้ว execute ได้ทันที (เพราะกระบวนการที่เกี่ยวข้องกับการ compile ถูกทำไปเรียบร้อยแล้ว) ลักษณะการทำงานแบบนี้ก็เรียกว่า AOT เหมือนกัน เพียงแต่ในกรณีของ Hermes อาจจะไม่ได้ถึงขั้น compile เป็น machine code โดยตรง แต่กระบวนการที่เกี่ยวข้องกับการ compile ถูกดำเนินการไปเรียบร้อยแล้วตั้งแต่ตอน build app ซึ่ง Hermes engine มีหน้าที่ในการเอา Hermes bytecode มา execute เท่านั้น

ข้างบนเป็นคำอธิบายคร่าว ๆ ให้พอเข้าใจเท่านั้น ใครอยากรู้รายละเอียดที่ลึก ๆ สามารถไปอ่านได้จาก

ภาพ GIF ด้านล่างก็จะเป็นการเปรียบเทียบกระบวนการ build app ระหว่างการใช้งาน JavaScriptCore และ Hermes เพื่อให้เข้าใจได้มากยิ่งขึ้น (ที่มา: บทความ Hermes ของ Engineering at Meta)

แอนิเมชันเปรียบเทียบขั้นตอนตอน build และตอน run ระหว่าง engine ทั่วไปกับ Hermes เฟรมแรกแสดง Babel, Minify, Install ตอน build และ Parse, Compile, Execute ตอน runแอนิเมชันเปรียบเทียบขั้นตอนตอน build และตอน run ระหว่าง engine ทั่วไปกับ Hermes เฟรมแรกแสดง Babel, Minify, Install ตอน build และ Parse, Compile, Execute ตอน run

สำหรับ developer ท่านใดที่ต้องการจะปิด/เปิดการใช้งาน Hermes engine ของ app ก็สามารถไปตั้งค่าได้ตามตัวอย่างด้านล่าง Android 🤖 แก้ค่า hermesEnabled ให้เป็น true หรือ false ที่ไฟล์ android/gradle.properties

# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=false

iOS 🍎 แก้ค่า :hermes_enabled ให้เป็น true หรือ false ที่ไฟล์ ios/Podfile

use_react_native!(
  :path => config[:reactNativePath],
  :hermes_enabled => false,
  # An absolute path to your application root.
  :app_path => "#{Pod::Config.instance.installation_root}/.."
)

หรือหากท่านใดใช้งาน Expo framework ก็สามารถตั้งค่า jsEngineให้เป็น hermes (Hermes engine) หรือ jsc (JavaScriptCore) ได้ที่ไฟล์ app.json

{
  "expo": {
    "jsEngine": "hermes"
  }
}

การเปิดใช้งาน Hermes engine อาจทำให้ app ถูกวิเคราะห์หรือทำ reverse engineering ยากขึ้นกว่าการใช้งาน JavaScriptCore ก็จริง (เนื่องด้วยเหตุผลที่กล่าวไปแล้วในหัวข้อแรก) แต่ Hermes engine ไม่ได้มีจุดประสงค์ในการออกแบบมาเพื่อทำ obfuscation ให้อ่าน code เข้าใจได้ยากมากยิ่งขึ้น เพราะฉะนั้นการที่บอกว่าเปิดใช้งาน Hermes engine แล้ว ไม่ต้องทำ obfuscation ก็ได้ อาจจะไม่ใช่เหตุผลที่ถูกต้องนัก ตามที่จะเห็นในหัวข้อ Initial Bytecode Analysis Approach ว่า bytecode ยังถูก disassemble ให้อ่านได้ด้วย hbcdump ครับ


3. Detection & Initial Analysis 🔍

Fingerprinting React Native Applications

ก่อนที่จะลงรายละเอียดกันต่อ เบื้องต้นเรามาดูวิธีกันก่อนว่า ในมุมมองของการทดสอบแบบ Black-box testing ที่เรามีแค่ไฟล์ของ app ที่ถูก build มาแล้วเท่านั้น (เช่น ไฟล์ APK (Android 🤖) หรือ ไฟล์ IPA (iOS 🍎)) เราจะสามารถรู้ได้ยังไงว่า app ถูกพัฒนาด้วย React Native หรือเปล่า? รายชื่อไฟล์ตามด้านล่างสามารถช่วยเราได้ครับ

ไฟล์ APK 🤖

  • มีไฟล์ที่ประกอบไปด้วยคำว่า libhermes, libreact, libreactnative อยู่ใน lib directory ตัวอย่างเช่น libhermes.so, libhermestooling.so, libreact_codegen_rnscreens.so, libreact_codegen_safeareacontext.so, libreactnative.so
  • และที่ assets directory จะต้องมีไฟล์ชื่อ index.android.bundle อยู่

ไฟล์ IPA 🍎

  • มีไฟล์ที่ประกอบไปด้วยคำว่า React, ReactNative, RN, RNC ตัวอย่างเช่น ReactNativeBlobUtilPrivacyInfo.bundle, RNCAsyncStorage_resources.bundle, RNDeviceInfoPrivacyInfo.bundle, React-Core_privacy.bundle
  • ที่ directory Frameworks อาจจะมีไฟล์ชื่อ hermes.framework อยู่
  • และที่ root directory จะต้องมีไฟล์ชื่อ main.jsbundle อยู่

Hermes Runtime Detection Techniques

เราพูดถึงวิธีการตรวจสอบกันไปแล้วว่าจะดูยังไงว่า app ถูกพัฒนาด้วย React Native และใช้งาน JavaScriptCore หรือไม่? แล้วถ้า app มีการใช้งาน Hermes ล่ะ จะสามารถรู้ได้ยังไง? ให้เราไปทำการตรวจสอบที่ไฟล์ ./assets/index.android.bundle ในฝั่ง Android หรือ ./main.jsbundle ในฝั่ง iOS เหมือนเดิม แต่คราวนี้จะพบว่าหากใช้คำสั่ง file แล้ว output จะบอกชัดเจนเลยว่า ไฟล์นี้คือ Hermes bytecode ไม่ใช่ไฟล์ JavaScript bundle แบบที่เคยเจอ

$ file main.jsbundle
main.jsbundle: Hermes JavaScript bytecode, version 96

ถ้าเราลองใช้ code editor โดยทั่วไปมาเปิดไฟล์นี้ จะเห็นได้ว่าไม่สามารถอ่านได้เข้าใจและง่ายเหมือน JavaScript ที่เคยเจออีกต่อไปแล้ว เพราะมันเป็น binary ยังไงล่ะ... สำหรับใครที่เคยเจอแบบนี้เป็นครั้งแรกอาจจะเผลออุทานออกมาว่า แย่ละ..

ไฟล์ main.jsbundle ที่เป็น Hermes bytecode เมื่อเปิดด้วย code editor จะเห็นเป็นอักขระ binary ที่อ่านไม่ออกไฟล์ main.jsbundle ที่เป็น Hermes bytecode เมื่อเปิดด้วย code editor จะเห็นเป็นอักขระ binary ที่อ่านไม่ออก

มีมภาพผู้หญิงทำหน้าสับสนท่ามกลางสมการคณิตศาสตร์ แทนความรู้สึกเวลาพยายามทำความเข้าใจ Hermes bytecodeมีมภาพผู้หญิงทำหน้าสับสนท่ามกลางสมการคณิตศาสตร์ แทนความรู้สึกเวลาพยายามทำความเข้าใจ Hermes bytecode

แต่อย่างเพิ่งท้อใจไปครับ อ่านบทความนี้ต่อไปอีกหน่อยอาจจะมาช่วยแก้ความปวดหัวให้น้อยลงได้ 😁 (หรือเปล่า?)

Critical Role of Bytecode Versions

หลังจากที่ใช้คำสั่ง file ไปแล้วจะเห็นว่ามีเลข Hermes bytecode version อยู่ แล้วเลขนี้มันสำคัญยังไง? สำคัญตรงที่ Hermes bytecode แต่ละ version จะมีการเปลี่ยนแปลง instruction และ opcode อยู่เสมอ ๆ และ tool ที่ใช้ในการทำ reverse engineering ต้อง update ตาม เพื่อให้ support Hermes bytecode ในแต่ละ version และในหลาย ๆ ครั้งเราก็ต้องใช้เลข version นี้ในการดูว่ามี tool ไหน support Hermes bytecode version ที่เราต้องการบ้าง ถ้าไปใช้ version ที่ tool ไม่ support ก็จะไม่สามารถใช้ tool นั้น ๆ ได้เลย หรือในกรณีที่เลวร้ายที่สุดคือใช้งานได้ แต่ tool ดันแปลความหมายของ instruction และ opcode ผิด

Initial Bytecode Analysis Approach

มาถึงตรงนี้หลายท่านอาจจะเกิดคำถามแล้วว่า แล้วเราจะอ่านทำความเข้าใจ Hermes bytecode ได้ยังไง? วิธีการนั้นก็คือ เราต้องทำ disassemble แปลง bytecode ที่เป็น binary ให้มาอยู่ในรูปแบบภาษา assembly (หรือ instruction) ที่มนุษย์พอจะอ่านเข้าใจความหมายได้ครับ

มีมภาพไดโนเสาร์ครุ่นคิด พร้อมข้อความ If you know assembly, every software is open sourceมีมภาพไดโนเสาร์ครุ่นคิด พร้อมข้อความ If you know assembly, every software is open source

ถ้าใครยังนึกไม่ค่อยออกก็เปรียบเทียบได้กับการที่เวลาเราทำ reverse engineering Android app ที่พัฒนาด้วย Java หรือ Kotlin แล้วแปลงไฟล์ .dex (ที่เป็น Dalvik bytecode) ให้ไปเป็นภาษา Smali (หรือจะพูดว่าเป็น assembly ของฝั่ง Android ก็ได้) นั่นแหละครับ แนวคิดเดียวกัน

หรือใครยังนึกไม่ออกอีกก็เหมือนเราเอาไฟล์ binary ที่เป็น machine code เลย มาแปลงเป็นภาษา assembly ที่เราเคยเรียนกันมานั่นแหละครับ

และสำหรับเครื่องมือที่จะแนะนำในบทความนี้ ที่จะมาช่วยในการแปลง Hermes bytecode ให้กลับมาเป็นอยู่ในรูปแบบที่มนุษย์พอจะอ่านเข้าใจคือ hbcdump จาก official Github ของ Hermes นั่นเอง และก็อย่างที่ได้บอกไปข้างต้นว่า Hermes bytecode version มีความสำคัญ โดยแต่ละ release ของ hbcdump จะ support bytecode version ที่แตกต่างกัน เราสามารถดู bytecode version ที่ release นั้น ๆ support ได้จากไฟล์ /include/hermes/BCGen/HBC/BytecodeVersion.h ใน Github ได้เลย

สำหรับ DemoSecret app ที่เราใช้ทดสอบกัน จะเป็น Hermes bytecode version 96 โดยสามารถดาวน์โหลด Hermes CLI release version 0.13.0 มาลองใช้งานได้ ตามตัวอย่างข้างล่าง

$ hbcdump -objdump-disassemble main.jsbundle
hbcdump> dis

bb6543e868c66af4e260d898405226ace18812a6:     file format HBC-96

Disassembly of section .text:
00000000000b5250 <_0>:
000b5250:    34 27 12 00 00     DeclareGlobalVar      $0x001227
000b5255:    34 2f 12 00 00     DeclareGlobalVar      $0x00122f
000b525a:    34 d7 00 00 00     DeclareGlobalVar      $0x0000d7
000b525f:    34 33 12 00 00     DeclareGlobalVar      $0x001233
000b5264:    32 03              CreateEnvironment     %r3
000b5266:    7c 05              LoadThisNS            %r5
000b5268:    30 00              GetGlobalObject       %r0
000b526a:    39 01 00 01 69 00  TryGetById            %r1, %r0, $0x1, $0x69
000b5270:    37 01 01 02 87 2f  GetById               %r1, %r1, $0x2, $0x2f87
000b5276:    90 14 01           JmpTrue               000b528a, %r1
000b5279:    39 02 00 03 b0 21  TryGetById            %r2, %r0, $0x3, $0x21b0
000b527f:    36 01 02 04 c7     GetByIdShort          %r1, %r2, $0x4, $0xc7
000b5284:    51 01 01 02        Call1                 %r1, %r1, %r2
[...STRIPED...]

จะเห็นว่า output จะมี instruction ต่าง ๆ ออกมา ที่น่าจะพอเริ่มอ่านทำความเข้าใจได้ และจะมี address กับค่า hex ของแต่ละ byte กำกับอยู่หน้า instruction แต่ทั้งนี้ทั้งนั้น output ที่ได้จากคำสั่งนี้ก็ยังขาดข้อมูลที่มีประโยชน์อยู่อีกหลายอย่าง และถ้าจะให้มานั่งอ่าน assembly กันจริง ๆ จัง ๆ ก็ยังรู้สึกยากอยู่ดี

การทำ reverse engineering อย่างจริงจังต้องอาศัยเครื่องมือที่ output ดูง่ายกว่านี้อีกหน่อย และต้องเข้าใจความหมายของแต่ละ instruction ซึ่งเป็นเนื้อหาที่ลึกกว่าบทความนี้ครับ

Hands-on Hermes CLI: Quick Experimentation Tools

ถ้าท่านใดอยากจะลองเล่นให้ Hermes engine ไป execute JavaScript, แปลง JavaScript ให้เป็น Hermes bytecode หรือแปลง Hermes bytecode ให้เป็น Hermes assembly เพื่อเรียนรู้และทำให้คุ้นเคยกับวิธีการทำงานของ Hermes โดยที่ไม่ต้องเสียเวลาไป build mobile app ขึ้นมา ก็สามารถทำได้เช่นกัน โดยใช้ Hermes CLI เช่นตัวอย่างตามด้านล่าง

📝 ตัวอย่าง JavaScript code simple_js.js

const outter_num1 = 5
const outter_num2 = 3
const outter_str = 'Outer String'
inner_func_1(outter_num1, outter_num2)

function inner_func_1(num1, num2) {
  // add two numbers
  const sum = num1 + num2
  print('Total : ' + sum)

  let inner_str_1 = ' and Inner String 1'
  print(outter_str + inner_str_1)

  var inner_func_2 = () => {
    let inner_str_2 = ' and Inner String 2'
    print(outter_str + inner_str_1 + inner_str_2)
  }

  inner_func_2()
}

💻 Hermes engine execute JavaScript

$ hermes simple_js.js
Total : 8
Outer String and Inner String 1
Outer String and Inner String 1 and Inner String 2

💻 แปลง JavaScript ให้เป็น Hermes bytecode

$ hermes -emit-binary -out simple_js.hbc simple_js.js
$ file simple_js.hbc
simple_js.hbc: Hermes JavaScript bytecode, version 96

💻 แปลง Hermes bytecode ให้เป็น Hermes assembly

$ hbcdump -objdump-disassemble simple_js.hbc
hbcdump> dis

b2bdbe0db5ac271917ba2e5ef14849e98695c65a:     file format HBC-96

Disassembly of section .text:
000000000000011c <_0>:
0000011c:    34 05 00 00 00      DeclareGlobalVar     $0x000005
00000121:    32 02               CreateEnvironment    %r2
00000123:    64 01 02 01 00      CreateClosure        %r1, %r2, $0x01
00000128:    30 00               GetGlobalObject      %r0
0000012a:    3b 00 01 01 05 00   PutById              %r0, %r1, $0x1, $0x05
00000130:    73 01 03 00         LoadConstString      %r1, $0x03
00000134:    2a 02 00 01         StoreToEnvironment   %r2, $0x0, %r1
[...STRIPED...]

หวังว่าบทความนี้ทำให้ทุกท่านมีความเข้าใจ React Native mobile app ที่มีการใช้งาน Hermes engine มากขึ้นครับ การตรวจสอบ app จากไฟล์ APK หรือ IPA แบบ Black-box เช่นนี้เป็นส่วนหนึ่งของงานทดสอบความปลอดภัยแอปพลิเคชันมือถือ ดูรายละเอียดบริการ Mobile Application Penetration Test ของ Incognito Lab


References 📚

คำถามที่พบบ่อย

ดูได้อย่างไรว่า app React Native ใช้ Hermes หรือ JavaScriptCore

เปิดไฟล์ bundle ของ app คือ assets/index.android.bundle ใน APK หรือ main.jsbundle ใน IPA แล้วรันคำสั่ง file ถ้าเป็น JavaScriptCore ผลลัพธ์จะเป็น ASCII text ที่มีบรรทัดยาวมาก เปิดด้วย code editor อ่านได้ แต่ถ้าเป็น Hermes ผลลัพธ์จะระบุว่าเป็น Hermes JavaScript bytecode พร้อมเลข version เช่น version 96 ของ DemoSecret app ส่วนไฟล์ lib ที่ชื่อขึ้นต้นด้วย libhermes ใน APK และ hermes.framework ใน IPA เป็นเบาะแสเสริมที่ช่วยยืนยันได้

เลข Hermes bytecode version สำคัญกับการ reverse engineering อย่างไร

instruction และ opcode ของ Hermes เปลี่ยนไปตามแต่ละ version เครื่องมือวิเคราะห์จึงต้องอัปเดตตามเสมอ ถ้าใช้เครื่องมือที่ไม่รองรับ version ของไฟล์ ก็อาจเปิดไฟล์ไม่ได้เลย หรือแย่กว่านั้นคือเปิดได้แต่แปลความหมาย instruction ผิด ผู้วิเคราะห์จึงควรดูเลข version จากคำสั่ง file ก่อนเลือกเครื่องมือ และ hbcdump แต่ละ release ก็รองรับ version ไม่เท่ากัน ตรวจได้จากไฟล์ BytecodeVersion.h ใน GitHub ของ Hermes

เปิดใช้ Hermes แล้วยังจำเป็นต้องทำ obfuscation อยู่ไหม

ยังจำเป็นต้องพิจารณา Hermes ทำให้การวิเคราะห์ยากขึ้นกว่า JavaScriptCore เพราะ app ส่งมาเป็น bytecode แบบ binary ที่ต้อง disassemble ก่อนอ่าน แต่ Hermes ไม่ได้ออกแบบมาเพื่อ obfuscate code และเครื่องมืออย่าง hbcdump แปลง bytecode กลับเป็น instruction ที่อ่านได้อยู่แล้ว การอ้างว่าเปิด Hermes แล้วไม่ต้องทำ obfuscation จึงอาจไม่ใช่เหตุผลที่ถูกต้องนัก

Hermes ต่างจาก JavaScriptCore อย่างไรในแง่การทำงาน

JavaScriptCore เป็น JIT ที่ compile JavaScript เป็น machine code ตอนโค้ดส่วนนั้นถูกเรียกใช้ ส่วน Hermes เป็น AOT ที่ทำขั้นตอน compile เช่น parsing, optimization และ bytecode generation ไว้ตั้งแต่ตอน build app ทำให้ตัว engine ทำหน้าที่แค่ execute bytecode ผลคือ app เปิดได้เร็วขึ้น ใช้ memory น้อยลง และขนาด app เล็กลงบน Android ส่วนข้อมูล benchmark ที่ใช้ในบทความระบุว่า IPA บน iOS ขนาดเพิ่มขึ้น 4.4 MB

OUR SERVICES

PENETRATION TEST

With our high-ethical, professional certified team and methodology based on NIST SP800-115, we offer a full range of cost-effective services to identify your cyber risks in application, infrastructure, and mobile platforms to meet the requirements of your organisation.

logologo

INCOGNITO LAB CO., LTD.

38 Soi Petchakasem 30, Pak Khlong Phasi Charoen, Phasi Charoen, Bangkok 10160

©2026 Incognito Lab Co., Ltd. All rights reserved

Terms & ConditionsPrivacy Policy