July 14, 2026
[Binary Exploit — EP.3] Why Learning Windows Binary Exploitation Must Start with x86
ถ้า Windows 64-bit คือปัจจุบันและอนาคต แล้วทำไมเราถึงต้องเริ่มเรียน Pwnable บน 32-bit ที่กำลังจะสูญพันธุ์กันอยู่ละ
By Pattharadanai Sanitjairak
6 min read
Windows x86 จะหมายถึงระบบปฏิบัติการ Windows แบบ 32-bit ซึ่งในปัจจุบันแทบจะถูกแทนที่ด้วย Windows 64-bit หรือ x64 ทั้งหมดแล้ว แต่ทำไมหลาย ๆ คอร์สที่สอนเรื่อง Binary Exploitation บน Windows จึงยังคงสอนเทคนิคการโจมตีต่าง ๆ บนสถาปัตยกรรม 32-bit อยู่?
คำตอบที่ง่ายที่สุดก็คือ เทคนิคการทำ Binary Exploitation บนสถาปัตยกรรม 32-bit นั้นมีความซับซ้อนน้อยกว่า 64-bit แต่หลักการพื้นฐานของทั้งสองก็แทบไม่ได้แตกต่างกันเลย
ดังนั้น หากจะเริ่มเรียน Binary Exploitation บน Windows ก็ควรเริ่มต้นจากสิ่งที่เป็นพื้นฐานที่สามารถเข้าใจได้ง่ายก่อนแล้วจึงนำไปต่อยอดเรื่องที่ยากขึ้น เพราะถ้าหากเริ่มสอนบน 64-bit ตั้งแต่แรก ผู้สอนจะต้องอธิบายทั้งพื้นฐานของเทคนิคต่าง ๆ ควบคู่ไปกับข้อจำกัดและความแตกต่างของ 64-bit ซึ่งจะยิ่งเพิ่มความซับซ้อนและทำให้การเรียนรู้ยากขึ้นเข้าไปอีก (Binary Exploitation & Pwnable นับได้ว่าเป็นหัวข้อที่เฉพาะทางและหินมากๆ ในสายงาน Cybersecurity เลย)
The trade-off is that the first few exploitation concepts are harder to visualize.
32-bit กับ 64-bit แตกต่างกันอย่างไร
จำนวน Bit ของ CPU Architecture โดยทั่วไปจะหมายถึง Native Word Size ของ CPU หรือแบบง่ายๆ ก็คือ ขนาดข้อมูลที่ตัว CPU จะสามารถจัดการได้ตามปกติใน 1 Operation ซึ่งนั่นจะรวมไปถึงขนาดของ Address ที่ CPU ต้องใช้ในการอ้างอิงข้อมูลบน Memory หรือ RAM ด้วย
ดังนั้นบนระบบ 32-bit จึงสามารถสร้าง Address ที่ประกอบด้วยเลขฐานสองจำนวน 32 ตัว/Bit ซึ่งจะทำให้โปรแกรมใดๆ สามารถมี Virtual Address Space ของตัวเองที่เป็นไปได้ทั้งหมด 2²³ รูปแบบ หรือประมาณ 4 GB นั่นเอง นั่นก็จะหมายความว่าโดยหลักการแล้ว โปรแกรมบน 32-bit จะสามารถถูก Allocate Memory ได้มากสุดเพียงแค่ 4 GB เท่านั้น ซึ่งก็จะทำให้โปรแกรมสมัยใหม่หลายๆ ประเภท เช่น เกมหรือโปรแกรมตัดต่อวิดีโอ ที่ต้องใช้หน่วยความจำเป็นจำนวนมากจะไม่สามารถทำงานได้อย่างเต็มที่และมีประสิทธิภาพ
ส่วนในระบบ 64-bit จะสามารถสร้าง Address ได้มากถึง 2⁶⁴ ตำแหน่ง หรือประมาณ 16 Exabytes (EB) ซึ่งมีขนาดใหญ่กว่าขนาดของ RAM ใดๆ ที่โลกนี้จะสร้างขึ้นได้ อย่างไรก็ตาม จำนวน Address ขนาดนี้จะทำให้โปรแกรมที่ทำงานอยู่บน 64-bit จะไม่มีความกังวลเกี่ยวกับข้อจำกัดของ Virtual Address Space เหมือนที่ปรากฎอยู่ใน 32-bit นั่นเอง
Backward Compatibility
คำศัพท์นี้ถือเป็นหนึ่งใน Principle ของ Cybersecurity ที่ควรทำความเข้าใจ เพราะแนวคิดนี้ส่งผลต่อการออกแบบ Software และ OS ต่างๆ มาตั้งแต่อดีต หากมองย้อนกลับไปในยุคที่ Windows แบบ 32-bit ครองตลาด เราก็จะเห็นว่า Microsoft มี Market Share ที่สูงมากๆ เนื่องจาก Linux ในขณะนั้นยังไม่เสถียรและยังไม่พัฒนาเท่ากับในปัจจุบัน ทำให้ทั้งผู้ใช้งานทั่วไปและองค์กรมักจะเลือกใช้งาน Windows เป็นหลัก เพราะติดตั้งและใช้งานได้ง่าย อีกทั้ง Software ส่วนใหญ่ในยุคนั้นก็ยังไม่ได้ต้องการทรัพยากรเครื่องหรือ Specification ที่สูงมากนัก
นอกจากนี้ ยังเป็นช่วงเวลาเดียวกับที่ Microsoft เปิดตัว Active Directory (AD) ซึ่งต่อมาได้กลายเป็นโครงสร้างพื้นฐานสำคัญของระบบ IT ภายในองค์กรจำนวนมาก ด้วยเหตุนี้ องค์กรขนาดใหญ่หลายๆ แห่ง โดยเฉพาะสถาบันการเงินหรือธนาคาร ก็จะยังคงมีระบบบางส่วนที่พึ่งพา Windows แบบ 32-bit อยู่ แม้ว่าปัจจุบันระบบปฏิบัติการและฮาร์ดแวร์ส่วนใหญ่จะเปลี่ยนผ่านไปสู่สถาปัตยกรรม 64-bit แล้วก็ตาม
นั่นหมายความว่า หากวันหนึ่ง Microsoft ตัดสินใจผลักดันให้ Windows เปลี่ยนผ่านเข้าสู่ยุค 64-bit โดยยกเลิกการรองรับโปรแกรมแบบ 32-bit ทันที ก็ย่อมส่งผลกระทบต่อองค์กรจำนวนมหาศาลที่ยังพึ่งพา Software เดิมอยู่ การบังคับให้ลูกค้าต้อง Migrate ระบบทั้งหมดใหม่ ย่อมมีต้นทุนสูงและเสี่ยงต่อการสูญเสียฐานลูกค้าอย่างหลีกเลี่ยงไม่ได้
ด้วยเหตุนี้ Windows 64-bit จึงถูกออกแบบให้รองรับแนวคิด Backward Compatibility ซึ่งจะทำให้ Software ที่ถูกสร้างบน 32-bit จะยังคงสามารถทำงานบน OS 64-bit ได้โดยแทบไม่ต้องแก้ไขตัวโปรแกรม แนวคิดนี้ช่วยลดภาระในการ Migrate ขององค์กรอย่างมาก และในขณะเดียวกันก็เปิดโอกาสให้ผู้ใช้งานได้รับประโยชน์จาก 64-bit ไม่ว่าจะเป็นการรองรับหน่วยความจำที่มากขึ้น ประสิทธิภาพที่ดีขึ้น และ Feature ด้านความปลอดภัยที่ได้รับการพัฒนาเพิ่มเติม
Windows 32-bit on Windows 64-bit (WOW64)
WOW64 เป็นกลไกที่ทำให้ Windows 64-bit สามารถ Implement แนวคิดที่เรียกว่า Backward Compatibility ตามที่ได้กล่าวมาเมื่อสักครู่ โดยตัวกลไกนี้จะมีองค์ประกอบหลักๆ ดังต่อไปนี้
- WOW64 Emulation Layer — ทำหน้าที่แปลงข้อมูลระหว่าง Environment 32-bit และ 64-bit โดยใช้กระบวนการที่ทำการขยาย ค่าของ Argument หรือ Pointer จากความยาว 32-bit ให้เป็น 64-bit เมื่อต้องเรียกใช้ API ของระบบ และจะแปลงกลับเมื่อส่งผลลัพธ์กลับไปยังโปรแกรม 32-bit
- CPU Mode Switcher — CPU ที่เป็น x86–64 จะมีส่วนของ Hardware ชนิดพิเศษที่สามารถกำหนด Flag ให้แก่ตัว CPU เพื่อเปลี่ยนโหมดของการประมวลผลระหว่าง 32-bit (Compatibility Mode) และ 64-bit (Long Mode) ได้
- File System Split — Windows จะทำการ Maintain Executable และ DLL ที่เป็น Dependency ของทั้ง 32-bit และ 64-bit ไว้แยกออกจากกัน ดังนั้นตัว Software ก็จะสามารถเลือกได้ทันทีว่าอยากจะทำงานด้วย bit แบบไหน โดยตัวที่เป็น 32-bit จะเก็บอยู่ที่
C:/Windows/SysWOW64และตัว 64-bit จะเก็บอยู่ที่C:/Windows/System32นั่นเอง
- Registry Split — ในทำนองเดียวกันกับ File System Windows จะเก็บ Registry ของโปรแกรม 32-bit และ 64-bit แยกออกจากกัน ถ้าหากเราลองตรวจสอบ Registry ใน
HKEY_LOCAL_MACHINE\Softwareเราก็จะพบกับ Key ที่ชื่อว่าWOW6432Nodeซึ่งนั่นก็คือ Copy ของตัว Registry แต่ว่าเป็นในรูปแบบของ 32-bit
Program at a Runtime
EP ที่แล้วเราได้พูดถึงกระบวนการที่ Source Code ถูกแปลงให้กลายเป็น Machine Code รวมถึงแนวคิดเกี่ยวกับ Runtime ในเบื้องต้นไปแล้ว สำหรับบทความนี้เราจะมาดูกันต่อว่า Code ของ Program ที่ถูก Load เข้าไปใน Memory เพื่อทำการ Execute นั้น มีการจัดการและทำงานอย่างไร
อย่างไรก็ตาม เนื้อหาในบทความนี้ รวมถึงบทความถัด ๆ ไป จะจำกัด Scope อยู่ที่ Windows x86 เป็นหลัก ส่วนฝั่ง Linux นั้นคงไม่ได้ลงรายละเอียดมากนัก เนื่องจากมีเนื้อหาและบทความเกี่ยวกับหัวข้อนี้อยู่ค่อนข้างมากแล้ว
หากใครที่เคยเขียนโปรแกรมมาก่อนหรือกำลังศึกษาเรื่อง Binary Exploitation ก็คงจะต้องเคยเห็น Diagram ด้านล่างที่จะเป็น Layout ของ Component ต่างๆ ของ Program บน Virtual Address Space เมื่อถูกนำเข้ามาใน Memory
ซึ่ง Diagram ดังกล่าวจะสามารถใช้อธิบายได้โดยตรงโปรแกรมที่พัฒนาด้วยภาษาตระกูล C และ C++ เท่านั้น แต่ถ้าเป็นภาษาอย่างพวก Python ที่ตัวโค้ดของเรานั้นทำงานผ่าน PVM ใน Interpreter ตัว Diagram จะสามารถใช้อธิบายได้กับโปรแกรม "Interpreter" ที่เขียนด้วย C เท่านั้น ไม่สามารถใช้อธิบายตัว Source Code ของเราได้แต่อย่างใด แต่อย่างไรก็ตามเราลองมาดูกันก่อนว่าแต่ละ Component มันมีหน้าที่อะไรบ้าง
- Text Section (.text) — บางที่เรียกว่า Code Section ส่วนนี้เอาไว้วาง Instruction ที่ได้จาก Source Code ของเรา เป็นสิ่งที่ชี้นำ CPU ว่าจะให้ทำอะไร
- Initialized Data Section (.data) — ส่วนนี้เอาไว้จัดเก็บ Global Variable และ Static Variable ที่ได้ระบุค่าเอาไว้อยู่แล้วใน Source Code
- Uninitialized Data Section (.BSS) — ส่วนนี้เอาไว้จัดเก็บ Global Variable และ Static Variable ที่ประกาศออกมาแต่ยังไม่ได้ Assign ค่าลงไป โดยในขณะที่เริ่มรัน OS จะทำการจองพื้นที่เอาไว้ให้แต่ Assign ค่าเริ่มต้นเป็นศูนย์ให้โดยอัตโนมัติ
เราสามารถดูตัวอย่างโค้ดด้านล่างเพื่อทำความเข้าใจว่าบรรทัดไหนควรจะอยู่ที่ไหนบน Memory Layout
#include <stdio.h>
int global_init = 10; // .data
int global_uninit; // .bss
const char *message = "Hello"; // .data
void hello() { // .text
printf("%s\n", message); // .text
}
int main() { //.text
hello()
return 0;
}#include <stdio.h>
int global_init = 10; // .data
int global_uninit; // .bss
const char *message = "Hello"; // .data
void hello() { // .text
printf("%s\n", message); // .text
}
int main() { //.text
hello()
return 0;
}- Stack — ส่วนนี้ใช้สำหรับจัดเก็บข้อมูลที่มีอายุการใช้งานแบบชั่วคราว เช่นค่าต่างๆ ที่ส่งเข้ามาเพื่อเรียก Function ตัวแปรชั่วคราวหรือ Local Variable ที่ใช้งานแค่ใน Function นั้นๆ
- Heap — ส่วนนี้ใช้สำหรับจัดเก็บข้อมูลที่ถูกจองขึ้นมาในช่วง Runtime โดยจะเกิดขึ้นเมื่อโปรแกรมมีการร้องขอ Dynamic Memory Allocation เช่น ในภาษา C ที่ใช้คำสั่ง
malloc()หรือภาษา C++ ที่ใช้newโดย Runtime หรือ Memory Allocator จะทำการจองพื้นที่ Memory ตามขนาดที่ร้องขอในส่วนของ Heap ในขณะที่โปรแกรมกำลังทำงาน ซึ่งข้อมูลที่ถูกจัดเก็บในส่วนนี้จะขึ้นอยู่กับ Logic ของผู้พัฒนาว่าอยากจะเก็บอะไร เช่น Object, Data Structure หรือ Buffer ต่าง ๆ รวมถึงอาจถูกใช้โดย Runtime เองสำหรับจัดการข้อมูลภายในของโปรแกรมอีกด้วย
ส่วนวิธีนำ Model นี้ไป Apply ใช้กับภาษาตระกูล Managed Language เช่น Python หรือ C# ตัว Bytecode หรือ Intermediate Language (IL) ที่ถูกแปลงโดย Interpreter จะถูกจัดเก็บเป็น Object ในส่วนของ Heap ที่ถูก Allocate และจัดการโดย Interpreter อัตโนมัติ ส่วน Execution Engine หรือ Interpreter Logic ที่ทำหน้าที่อ่าน Bytecode แปลงเป็น Machine Code แล้ว Execute ที่เป็นส่วนหนึ่งของ Interpreter Binary จะถูก Load อยู่ใน Text Section แทน
แต่เนื่องด้วยบทความซีรีย์นี้จะเป็นการพูดถึง Binary Exploitation & Pwnable ที่มักจะพบในภาษาตระกูล C / C++ โปรแกรมใดๆ ที่จะกล่าวถึงในบทความนี้และบทความหน้าๆ เป็นต้นไป จะพูดถึงโปรแกรมที่เขียนด้วยภาษา C / C++ บน Windows x86 เท่านั้น
Process & Thread Memory
Program หนึ่งเมื่อถูกเรียกใช้งานอาจมีการสร้าง Process มากกว่าหนึ่งตัวเพื่อแบ่งหน้าที่ในการทำงาน โดยแต่ละ Process จะมี Virtual Address Space ของตนเอง ซึ่งยังคงประกอบไปด้วยองค์ประกอบพื้นฐาน เช่น .text, .data, Stack และ Heap ตามที่ได้กล่าวไปก่อนหน้านี้ อย่างไรก็ตาม ในความเป็นจริง OS จะสร้างและ Map Component อื่น ๆ ที่จำเป็นต่อการทำงานของ Process เพิ่มเติมเข้ามาด้วย เพื่อให้ระบบปฏิบัติการสามารถบริหารจัดการทรัพยากรและรันโปรแกรมได้อย่างถูกต้องและมีประสิทธิภาพ
โดยภาพด้านล่างคือตัวอย่าง Memory Layout ของ Process เมื่อถูก Load เข้ามาใน Memory (ในความเป็นจริงอาจจะมี Component มากกว่านี้ แต่ไม่สามารถแสดงหมดได้ เพราะเดี๋ยวจะดูยากไป)
- PEB (Process Environment Block) — เป็น Data Structure ที่ OS สร้างขึ้นสำหรับแต่ละ Process เพื่อใช้เก็บ Metadata ที่สำคัญต่าง ๆ เช่น Process Information, Process Parameters, รายชื่อ DLL ที่ถูกโหลด รวมถึงข้อมูลอื่น ๆ ที่ Windows และ Runtime ใช้ในการทำงาน
- Dynamic Link Library (DLL) Region — เป็นพื้นที่ที่ใช้สำหรับโหลด DLL ต่าง ๆ ที่โปรแกรมใช้งาน ไม่ว่าจะเป็น Windows System DLL เช่น
kernel32.dll,ntdll.dllหรือ DLL ของ Third-party โดยโค้ดและข้อมูลของ DLL จะถูก Map เข้ามาในพื้นที่นี้เมื่อโปรแกรมเริ่มทำงาน หรือในภายหลังเมื่อโปรแกรมมีการโหลด DLL เพิ่มเติมระหว่าง Runtime - Program Image — เป็นพื้นที่ที่ใช้สำหรับ Map ไฟล์ Executable (
.exe) ของโปรแกรมเข้าสู่ Virtual Address Space ของ Process - Kernel Space — ใน Windows แบบ 32-bit Virtual Address ของ Process ตั้งแต่
0x80000000ถึง0xFFFFFFFFจะถูกสงวนไว้สำหรับ Kernel Space ซึ่งเป็นพื้นที่ที่ใช้เก็บโค้ดและข้อมูลของ OS แม้ว่าแต่ละ Process จะมี Virtual Address Space เป็นของตนเอง แต่ Virtual Address ในช่วง Kernel Space จะถูก Map ไปยัง Kernel Memory ที่เดียวกันเสมอ
นอกเหนือจากนี้ Process หนึ่งตัวสามารถสร้าง Thread ขึ้นมาได้หลายตัวเพื่อช่วยจัดการ Task ต่าง ๆ โดยแต่ละ Thread จะมี Stack, TEB (Thread Environment Block) และ TLS (Thread Local Storage) เป็นของตนเองสำหรับใช้จัดเก็บข้อมูลที่เกี่ยวข้องกับการทำงานของ Thread นั้น ๆ ขณะที่ยังคงใช้ Virtual Address Space ร่วมกัน ทำให้สามารถเข้าถึง Program Image, Heap, DLL หรืออะไรก็ตามที่นิยามเอาไว้ในระดับ Process ได้
CPU Registers
ภายใน CPU นั้นจะมีส่วนประกอบที่เรียกว่า "Register" ซึ่งเป็นหน่วยเก็บข้อมูลขนาดเล็กที่อยู่ใกล้กับหน่วยประมวลผลมากที่สุด ซึ่งอยู่ใกล้กว่า Cache และ RAM (Main Memory) ทำให้กระบวนการอ่านและเขียนข้อมูลบน Register สามารถทำได้ด้วยความเร็วที่สูงมาก
ถึงแม้ว่าจำนวน Register ที่ฝังอยู่ในชิปของ CPU จะมีจำนวนมากมาย (อาจมีตั้งแต่หลายร้อยถึงหลายพันรายการ ขึ้นอยู่กับ Architecture ของ CPU) แต่ในระดับ Software นั้น CPU จะทำการ Abstract ออกมาเป็น Register ที่ OS สามารถมองเห็นและใช้งานได้ โดยมีจำนวนประมาณหลักร้อยรายการ ส่วนในระดับ Application ที่ทำงานอยู่บน User-Space นั้นจะสามารถเข้าใช้งานได้เพียงประมาณหลายสิบตัวเท่านั้นเอง
General-Purpose Registers
Register ในกลุ่มนี้ส่วนใหญ่มักจะใช้ภายใน CPU เพื่อเก็บข้อมูลหรือ Address ชั่วคราวในระหว่างการประมวลผล โดยถ้าเป็น CPU ประเภท x86–64 จะมีให้ใช้งานประมาณ 16 ตัว (แต่ Windows 32-bit จะใช้ได้แค่ 8 ตัวเท่านั้น) ส่วนในของฝั่ง ARM64 จะใช้ได้ประมาณ 31 ตัว
โดยนี่คือตัวอย่าง General-Purpose Register ที่จะใช้งานใน Windows x86
- EAX (Accumulator): มักจะถูกใช้เก็บผลลัพธ์จากการคำนวณ และใช้เก็บค่า Return Value จาก Function
- EBX (Base): มักจะใช้เก็บ Base Address ของอะไรบางอย่างแล้วแต่ผู้พัฒนาโปรแกรมจะนิยามเอาไว้ เช่น Base Address ของตัวโปรแกรม Base Address ของ DLL เป็นต้น
- ECX (Counter): มักจะถูกใช้เป็นตัวนับสำหรับ Instruction จำพวก Loop หรือคำสั่งที่ต้องทำซ้ำๆ
- EDX (Data): มักจะใช้เก็บข้อมูลเพิ่มเติมที่ได้จากการคำนวณผล เมื่อ Registry 1 ตัวไม่สามารถเก็บพอ เช่นการหารตัวเลข ก็ต้องใช้ตัวหนึ่งเก็บส่วนที่เป็นจำนวนเต็ม อีกตัวในการเต็มส่วนที่เป็นทศนิยม
- ESI (Source Index): มักใช้เก็บ Source Address สำหรับ Instruction ที่ไว้ใช้ในการเข้าถึงหรือย้ายข้อมูล
- EDI (Destination Index): มักใช้เก็บ Destination Address สำหรับ Instruction ที่ไว้ใช้ในการเขียนหรือย้ายข้อมูล
- ESP (Stack Pointer): ใช้ชี้ตำแหน่งบนสุดของ Stack ใน Memory Layout ของ Thread
- EBP (Base Pointer / Frame Pointer): ใช้เป็นจุดอ้างอิงของ Stack Frame ภายใน Function (เดี๋ยวจะมาอธิบายเพิ่มเติมว่า Stack และการเรียกใช้งาน Function มันมีความเกี่ยวพันกันอย่างไร)
- EIP (Instruction Pointer): ใช้เก็บ Address ของ Instruction ที่ CPU จะทำการ Execute ถัดไป โดย Register ตัวนี้มีความสำคัญอย่างมากในด้าน Binary Exploitation เนื่องจากหากสามารถควบคุมหรือเปลี่ยนแปลงค่าใน EIP ได้ จะทำให้สามารถเปลี่ยน Program Execution Flow ให้ CPU ไปทำงานตาม Address ที่ Hacker กำหนดได้ (ถ้า Address นั้นมี Malicious Code ก็จะถูก Hack ได้เลย)
Windows Debugger
ไม่ว่าจะเป็น Memory Layout ต่าง ๆ ภายใน Program's Virtual Address Space ขณะที่ Thread หรือ Process กำลังทำงาน รวมถึงค่าใน CPU Register เราสามารถใช้โปรแกรมที่เรียกว่า Debugger เพื่อ Attach เข้ากับ Process ที่ต้องการวิเคราะห์ เพื่อช่วยให้เราเข้าใจการทำงานภายในของโปรแกรมได้มากขึ้น โดย Debugger สามารถใช้ Inspect และ Debug ข้อมูลต่าง ๆ เช่น Stack, Heap, Register, Memory Layout รวมถึง Instruction ที่ CPU กำลัง Execute ได้
ซึ่งถ้าเป็น Binary Program บน Windows เราสามารถใช้ Tool เช่น WinDbg หรือ Immunity Debugger เพื่อทำการวิเคราะห์การทำงานของโปรแกรมได้
Note: ตั้งแต่บทความส่วนนี้เป็นต้นไป โปรแกรมตัวอย่างทั้งหมดจะทำงานบน Virtual Machine ที่ใช้ระบบปฏิบัติการ Windows 10 แบบ 32-bit
Introduction to WinDbg
เราสามารถเลือกได้ว่าจะใช้ WinDbg Attach เข้ากับ Process ที่กำลังทำงานอยู่แล้ว หรือสามารถเปิด Executable File ผ่าน WinDbg โดยตรง เพื่อให้ Debugger ทำการ Attach ตั้งแต่ก่อนที่โปรแกรมจะเริ่มทำงานก็ได้
ซึ่งในการ Debug Program เพื่อทำ Binary Exploitation ในระดับเบื้องต้นเราจะใช้เพียงแค่ไม่กี่หน้าต่างเท่านั้น
- Command — เป็นช่องเอาไว้สำหรับป้อนคำสั่งให้แก่ WinDbg
- Registry — เป็นช่องที่เอาไว้สำหรับตรวจสอบค่าของ General-Purpose Register และ Register อื่นๆ ในขณะโปรแกรมกำลังทำงานอยู่ หรือถูก Pause โดยตัว Debugger
- Disasembly — เนื่องจาก Executable File คือ Binary File เมนูนี้จะพยายามแปลง Binary ให้กับกลายเป็น Assembly Code ที่สามารถอ่านแล้วเข้าใจ Logic ของโปรแกรมได้เบื้องต้น
เราสามารถลองใช้คำสั่ง lm บน Tab "Command" ของ WinDbg เพื่อตรวจสอบว่าโปรแกรมที่เรา Attach อยู่นั้นมีการ Loaded Module (และ Program Image หรือตัว .exe ที่ใช้เปิดโปรแกรม) อะไรมาบ้าง รวมไปถึง Address เริ่มต้นและสิ้นสุดของ Module นั้นๆ อีกด้วย
โดยทั่วไปตัว Debugger จะเริ่มต้นให้เราวิเคราะห์ที่ Thread แรกของ Process ที่เรา Attach เข้าไปเสมอ (Main Thread) ถ้าเรายังทำเรื่อง Memory Layout ได้ ตัว Thread ก็จะมีการเก็บสิ่งที่เรียกว่า "TEB" ที่เป็น Metadata ต่างๆ ของ Thread เอาไว้ เราสามารถใช้คำสั่ง !teb เพื่อตรวจสอบดูบน Thread ที่ Debugger กำลังทำงานอยู่ได้
หรืออาจจะลองใช้คำสั่ง !peb เพื่อตรวจสอบ Process Environment Block (PEB) บน Process ที่ Thread นั้นๆ อาศัยอยู่ก็ได้
อีกคำสั่งที่น่าสนใจก็คือ k สำหรับตรวจสอบ Stack ของ Thread และ !heap เพื่อตรวจสอบข้อมูลของ Heap
และก็ยังมีคำสั่งต่างๆ อีกมากมายให้ไปลองฝึกฝนใช้งานกันได้ โดยในบทความถัดๆ ไปเราจะเน้นใช้โปรแกรมนี้ในการทำ Binary Exploitation บน Windows x86 เป็นหลัก
Conclusion
โดยในบทความนี้ เราได้พูดถึง Memory Layout ของโปรแกรมเมื่อถูก Load เข้าไปใน Memory โดยเฉพาะบน Windows x86 รวมถึงอธิบายถึงความสำคัญว่าทำไมการเริ่มศึกษา Binary Exploitation จาก Windows 32-bit จึงเป็นจุดเริ่มต้นที่ดี เนื่องจากเทคนิคหลายอย่างสามารถอธิบายให้เห็นภาพได้ง่ายกว่า และความเข้าใจเหล่านี้ยังสามารถนำไปประยุกต์ใช้กับระบบ 64-bit ได้ไม่ยากนัก
นอกจากนี้ เรายังได้พูดถึง Register ซึ่งเป็นพื้นที่เก็บข้อมูลขนาดเล็กภายใน CPU ที่มีความสำคัญอย่างมากในการทำ Binary Exploitation รวมถึงการใช้ Debugger เพื่อ Inspect และตรวจสอบค่าต่าง ๆ ของ Binary ขณะกำลังทำงานผ่านเครื่องมืออย่าง WinDbg โดยผู้เขียนก็แนะนำให้ผู้อ่านลองทดสอบติดตั้งและเรียนรู้การใช้งานด้วยตัวเองดูก่อนเป็นเบื้องต้น
สำหรับบทความถัด ๆ ไป เราจะเริ่มพูดถึงพื้นฐานของภาษา C และ Assembly รวมถึงรูปแบบการเขียนโปรแกรมที่อาจนำไปสู่การเกิดช่องโหว่ประเภทต่าง ๆ
ไว้พบกันใหม่ในบทความถัดไปครับ