0x00a VA, RVA, และ Offset: ทำไมถึงต้องแยกกันให้ขาดในงาน RE?

หลังจากที่เราได้ทำความเข้าใจความหมายของ Offset กันไปแล้วใน

บทความก่อนหน้า

 

จริง ๆ แล้ว ยังมีอีกคำนึงครับ และคำที่ใกล้เคียงกันมาก ๆ สอดคล้องกับเรื่องที่แล้ว นั่นคือคำว่า Relative Virtual Address หรือ RVA ครับ

เรารู้จัก

Base Address แล้ว

Virtual Address แล้ว

Current Address แล้ว

แล้ว Relative Virtual Address คืออะไร ?

https://library.mosse-institute.com/articles/2022/05/reverse-engineering-portable-executables-pe-part-2/reverse-engineering-portable-executables-pe-part-2.html

คำว่า Relative แปลว่า สัมพัทธ์ แปลไทยเป็นไทยอีกที คำว่าสัมพัทธ์ แปลว่า "เปรียบเทียบกัน"

 

RVA หรือ Relative Virtual Address แปลว่า Virtual Address ที่ถูกเปรียบเทียบกันไว้เรียบร้อยแล้ว

 

ต่อไปจะเขียนย่อว่า RVA เท่านั้นนะครับ

และ "จริงๆแล้ว" VA หรือ Virtual Address (ไม่มี relative) นั้นหมายถึง ที่อยู่เสมือนสัมบูรณ์ ก็คือมีสัมพัทธ์ ก็ต้องมีสัมบูรณ์คู่กัน อะไรทำนองนั้น

โดย VA จะถูกมองว่า ที่อยู่หน่วยความจำ "จริง" ที่โปรแกรมจะถูกวางไว้และใช้งานขณะที่มันกำลังรันอยู่ (ในหน่วยความจำเสมือนของระบบปฏิบัติการ) VA เป็นที่อยู่แบบสัมบูรณ์ (Absolute Address) ที่ไม่ต้องการการคำนวณเพิ่มเติมเพื่อระบุตำแหน่ง

 

การอ้างอิง:

VA ถูกวัดจาก ศูนย์ (Zero) ของพื้นที่หน่วยความจำเสมือนของโปรแกรม (ตั้งแต่ 0x00000000 ไปจนสุด)
---เน้น Dynamic Analysis

ในบริบทของไฟล์ PE (ที่เราวิเคราะห์อยู่): VA จะถูกคำนวณจากการเอา Base Address (หรือ ImageBase) ไปบวกกับ RVA 
---เน้น Static Analysis


เพราะ Methodology หรือวิธีวิเคราะห์ต่างกัน ก็เลยต้องมองให้คนละบริบทนะครับ

ตัวอย่างใน Rizin:

ที่อยู่ 0x1400014f8 (ที่อยู่ของฟังก์ชัน main) ตัวเลขนี้คือ VA เพราะมันเป็นที่อยู่เต็มที่ชี้ไปยังตำแหน่งโค้ดโดยตรง และต้องเน้นด้วยว่าเราเปิดไฟล์แบบปกติ

 

Virtual Address (VA)=ImageBase+RVA

 

ส่วน RVA

คือ Offset (ระยะห่าง) ที่วัดจาก Base Address ของโปรแกรม (หรือก็คือ ImageBase) RVA เป็นที่อยู่แบบสัมพัทธ์ (Relative Address) ที่ไม่สามารถใช้ในการชี้ตำแหน่งจริงในหน่วยความจำได้จนกว่าจะถูกนำไปรวมกับ Base Address

 

การอ้างอิง:

RVA ถูกวัดจาก Base Address (ค่า ImageBase ใน Header)

ในไฟล์ไบนารี PE ค่า RVA มักจะถูกบันทึกไว้ในส่วนหัว (Header) หรือในตารางต่าง ๆ เพื่อบอกว่าโค้ดหรือข้อมูลนั้น ๆ ควรจะอยู่ห่างจากจุดเริ่มต้นของโปรแกรมเป็นระยะทางเท่าใด

 

ตัวอย่างใน Rizin:

จากคำสั่ง iH เราเห็น AddressOfEntryPoint : 0x13f0

0x13f0 นี่แหละคือ RVA ของจุดเริ่มต้นโปรแกรม



Relative Virtual Address (RVA)=VA−ImageBase

 

 

iH-Command

ในบริบทของ Static Analysis ด้วย Rizin การใช้คำว่า Virtual Address (VA) นั้น ถูกต้องในเชิงผลลัพธ์ และเป็นคำที่ผู้ใช้งานเครื่องมือ Disassembler คุ้นเคย

ตัว Rizin ออกแบบมาเพื่อให้แสดง VA ที่สมบูรณ์ (Base Address + RVA) เพราะนั่นคือที่อยู่ที่โค้ดจะใช้ในหน่วยความจำเสมือนจริงๆ

 

เมื่อเราเห็น 0x1400014f8 นั่นคือ VA ที่ถูกต้องที่สุดสำหรับฟังก์ชัน main

RVA คือส่วนประกอบของ VA: ในทางคณิตศาสตร์ RVA ก็คือ Offset ที่เป็นส่วนหนึ่งของ VA นั่นเอง

 

ตลอดการวิเคราะห์ Static Analysis ที่ผ่านมา เราใช้คำว่า Virtual Address (VA) โดยตลอด (เช่น 0x1400014f8) ซึ่งถูกต้องแล้ว เพราะมันคือที่อยู่ สัมบูรณ์ ที่โปรแกรมคาดหวังจะถูกโหลด

แต่ในเชิงเทคนิคแล้ว ตัวเลข 0x14f8 ที่เป็นระยะห่างจาก ImageBase นั้น ถูกเรียกอย่างเป็นทางการว่า Relative Virtual Address (RVA) ซึ่งมีความสำคัญมากกว่า VA ในงานของเรามาก

 

RVA คือ Offset ที่แท้จริงของเรา:

 

 

RVA (0x14f8)=VA (0x1400014f8)−Base Address (0x140000000)

 

 

ถ้าสรุป paragraph นี้ก็หมายถึง Rizin อำนวยความสะดวกในการแปลงโครงสร้างของไฟล์ (เช่น PE File) เพราะไฟล์มีฟอแมทโครงสร้างชัดเจน
rizin โดย default เปิดไฟล์แบบอำนวยความสะดวกให้เราได้เห็นส่วนต่างๆอย่างมีระเบียบ สัดส่วนมากขึ้นและทำ RVA มาให้เราเสร็จสรรพ เพราะเรากำลังอยู่ใน Static Analysis Methodology

แต่ถามว่า rizin เปิดแบบดิบๆ แล้วเรียงไฟล์เป็น hex ได้มั้ย
คำตอบคือ ได้ และนั่นก็เพื่อจุดประสงค์อีกรูปแบบนึง
ซึ่งก็ตามที่บอกไปแล้วครับ ว่ามันขึ้นอยู่กับ บริบท


ทีนี้ในบทความก่อนหน้า เรื่อง Offset

ผมได้บอกพวกเราไปว่า "มันต้องเจาะจงบริบท" ในการตีความ ความหมายของ Offset ด้วยนะครับ ไม่งั้นตีความยาก งงกันใหญ่

ซึ่งมันมี criteria นึงของการสื่อสาร ตีความคำว่า Offset คือเรื่องรูปแบบการเปิดไฟล์ ที่จะทำให้เราสื่อสารเรื่อง Offset ที่แตกต่างออกไป


ในบทความก่อนหน้า เราเปิดไฟล์แบบ ธรรมดา ทำให้ rizin พยายามอ่าน ImageBase ออกมาตามที่ถูกฝังมาในไฟล์ และจำลองสภาพที่ OS คาดหวัง ทำให้เกิด Virtual Address ที่เราเห็นสมบูรณ์ นั่นคือการสื่อสารความหมายของคำว่า Offset แบบนึง

แต่ในบริบท รูปแบบการเปิดไฟล์แบบอื่น ก็มีนะครับ และส่งผลให้ค่าเปลี่ยนไป และเราก็อาจจะต้องทำการสื่อสารคำว่า Offset แตกต่างออกไปจากเดิม

Rizin มีคำสั่งที่ทำให้ทำแบบนี้ได้จริง ๆ เช่น การใช้ Flag -B 0 หรือการเปิดแบบ Raw Mode เช่นการใส่ -nn ตอนเปิดไฟล์ ให้ดู help บางส่วนครับ ไปลองกดกันได้

PS C:\Users\keng\runmalware.com> rizin -h

Usage: rizin [-ACdfLMnNqStuvwzX] [-P patch] [-p prj] [-a arch] [-b bits] [-i file]

             [-s addr] [-B baddr] [-m maddr] [-c cmd] [-e k=v] file|pid|-|--|=

....

 -0           Print \x00 after init and every command

 

 -B [baddr]   Set base address for PIE binaries

......

 -n, -nn      Do not load RzBin info (-nn only load bin structures)

 -N           Do not load user settings and scripts

 -NN          Do not load any script or plugin

 

>


อย่างที่เน้นตัวหนาไปว่า เปิดแบบ hex ได้มั้ย ก็ได้ แต่นั่นไม่ค่อยอำนวยความสะดวกโครงสร้างให้เรานัก ในอีกบริบทนึง เราจะเอาไว้ใช้ในการแงะดูสิ่งที่เรียง byte อยู่ข้างใน เช่น shell code ที่ถูกซ่อนไว้ จึงเหมือนบังคับให้เราต้องไล่ดูทีละ byte แบบนั้น
ตอนนี้เราดูตัวอย่างแบบ ง่ายๆไปก่อน ยังไม่ต้องถึงขั้น advance โดยใช้คำสั่ง px ใน rizin จะช่วยเราเรื่องนี้ได้ครับ

px-simple-command

เห็นมั้ย Base Address จะเริ่มต้นที่ 0x140000000 ไม่ได้ เพราะเป็นที่บรรจุ Header file information ของโครงสร้าง PE 
เมื่อเราเดินไปยังตำแหน่ง 0x140000000 ซึ่งเป็นจุดเริ่มต้นของไฟล์ และทำการ show hexdump ด้วยคำสั่ง px เราก็จะเห็นว่ามันเป็น header ของไฟล์จริงๆ
สรุป
เราแกะดู hex ได้มั้ย = ได้
rizin อำนวยความสะดวก ปรับโครงสร้างให้เราดู เหมือนโปรแกรม PE view ต่างๆ ใช่มั้ย = ใช่
แต่นั่นคนละบริบทกันนะ
และ rizin ทำได้มากกว่านั้นด้วย

px-print

 

 

สมมติว่าเราไม่ชอบตัวเลข base address ที่ 0x140000000 มันดูยากๆแปลกๆ และไม่คุ้นเคยกับความเป็นจุดเริ่มต้น

เราเลยเปิดไฟล์ด้วย rizin แล้วใช้ flag -B 0 จะเกิดผลเสียยังไง ?



 


ผลเสียต่อการวิเคราะห์หาก Base Address = 0x0

 

1. VA จะกลายเป็น RVA: สูญเสียความหมายทาง OS

ปัญหา: ถ้าเราตั้ง Base Address (ImageBase) เป็น 0x0 แล้ว Virtual Address (VA) ทั้งหมดที่เราเห็นจะ มีค่าเท่ากับ RVA ทันที

ทำไมถึงเป็นปัญหา: ใน Static Analysis ผลลัพธ์ที่ได้อาจจะไม่ต่างกันมาก (เพราะ VA=RVA เมื่อ Base Address=0)

 

แต่เมื่อเราไปทำ Dynamic Analysis (รันไฟล์จริง) ข้อมูลนี้จะ ใช้ไม่ได้ เพราะ OS จะโหลดโปรแกรมไปที่ VA ที่มีค่าสูงลิบลิ่ว (เช่น 0x7FF0...) ไม่ใช่ 0x0

ข้อเสีย: เราจะสูญเสียการอ้างอิงถึง "ที่อยู่เสมือนที่คาดหวัง" ของโปรแกรม 

, ทำให้การเชื่อมโยงระหว่าง Static และ Dynamic Analysis ยากขึ้นมาก
ตัวอย่างตอน Dynamic แบบปกติ เราจะเห็นว่า RVA = 00007FF667C413F0
ภาพจาก x64dbg แสดงให้เห็นตอนรัน จังหวะ Entry Point 
เห็นมั้ยว่ามันเชื่อมโยงกับ Static ยาก และต้องเข้าใจว่ามันแยกบริบทกัน

x64dbg-reverse1-dynamic-rip


2. การอ้างอิงข้อมูลและโค้ดภายในโปรแกรมจะพัง (Broken Relocations/Imports)

ปัญหา: โค้ดภายในโปรแกรม (โดยเฉพาะในไฟล์ 64-bit) มีคำสั่งมากมายที่ต้องมีการ อ้างอิงข้ามส่วน (Cross-References) เช่น การเรียกใช้ฟังก์ชัน การเข้าถึงข้อมูล Global

 

ทำไมถึงเป็นปัญหา: โค้ด Assembly ภายใน reverse1.exe มีการเขียนค่า Base Address ที่คาดหวัง (0x140000000) ไว้ในบางจุดเพื่อใช้คำนวณตำแหน่งจริง

หากเราเปลี่ยน Base Address ใน Rizin เป็น 0x0 Rizin จะตีความการอ้างอิงเหล่านั้นผิดทั้งหมดทันที

ตัวอย่าง: คำสั่ง call sym.printf อาจจะกลายเป็นเรียกที่อยู่ผิด ๆ หรือ Rizin อาจจะแสดงผลว่า "ไม่พบการอ้างอิง" เพราะสูตรคำนวณที่ใช้ในการแกะโค้ด (Disassembly) ถูก Base Address ที่ผิดเพี้ยนไป

 

 

3. ทับซ้อนกับ PE Header และ Section Zero

ปัญหา: ที่อยู่ 0x0 ถึง 0x1000 ถูกสงวนไว้สำหรับส่วนหัวของไฟล์ (PE Header) และอาจเป็นพื้นที่ที่ไม่สามารถเขียน/รันได้ (Non-Executable)

ทำไมถึงเป็นปัญหา: ถ้าเราตั้ง Base Address ให้เริ่มต้นที่ 0x0 แล้ว โค้ดที่รันได้ (entry0) ซึ่งมี Offset เป็น 0x13f0 จะถูกวางซ้อนทับอยู่บนพื้นที่ที่ควรจะเป็น Header , 

ทำให้ Rizin ตีความโครงสร้างไฟล์ผิดเพี้ยนไป

ข้อเสีย: เราจะไม่สามารถใช้ความสามารถของ Rizin ในการวิเคราะห์ Section ต่างๆ หรือ Directory Entry ที่ต้องใช้โครงสร้าง Header ที่สมบูรณ์ได้ ก็จะเห็นเป็นเลข hex เรียงๆ ซึ่งถ้าเป็นการวิเคราะห์ปกติ ไม่ใช่ shellcode อะไร ก็ถือว่าอาจจะเหนื่อยได้ , แบบเนี้ย

rva-va-compare-2025-10-03 14_56_27-

ก่อนจะจบบทความนี้ก็จะเห็นแล้วว่า ในมุม Static Analysis สามารถอ้างอิงคำว่า VA เลยได้ เพื่อให้เข้าใจ Offset และนำไปต่อยอดกับ Dynamic Analysis ได้ โดยเราจะต้องเข้าใจบริบท ในสิ่งที่เรากำลังทำอยู่จริงๆ ว่ากำลังทำอะไร 

เจอกันใหม่ครับ