← Back to list

[Binary Exploit — EP.2] How Computer Understand Source Code

Computer เข้าใจแค่เลขฐาน 2 ที่มีแค่ 1 กับ 0 แล้ว Programming Language ที่เราเขียนๆ กันทุกวันนี้ ที่ประกอบไปด้วยอักขระต่างๆ Computer…

Pattharadanai Sanitjairak · 2026-07-13 06:01 · 2 claps · 7.7 min read
#binary-exploitation #pwnable #exploit-development #cybersecurity #penetration-testing
Open on Medium ↗
Wiki topics: 💻 · Programming 🔒 · Cybersecurity

[Binary Exploit — EP.2] How Computer Understand Source Code

Computer เข้าใจแค่เลขฐาน 2 ที่มีแค่ 1 กับ 0 แล้ว Programming Language ที่เราเขียนๆ กันทุกวันนี้ ที่ประกอบไปด้วยอักขระต่างๆ Computer เข้าใจได้อย่างไร

ตามที่เราทราบกันดีว่า Computer สามารถเข้าใจได้เพียงข้อมูลในรูปแบบ Binary ที่ประกอบไปด้วยเลข 0 และ 1 เท่านั้น ไม่ได้เข้าใจภาษาหรือคำสั่งในรูปแบบที่มนุษย์ใช้งานโดยตรง ซึ่งรวมไปถึง Programming Language ต่าง ๆ เช่น Python, C, C++ และ Java ดังนั้น Source Code ที่เขียนด้วยภาษาเหล่านี้จึงจำเป็นต้องผ่านกระบวนการบางอย่างเพื่อแปลงให้อยู่ในรูปแบบที่ Computer สามารถประมวลผลได้ เช่น Machine Code หรือ Opcode

Source Code ถูกแปลงเป็น Binary ได้อย่างไร ?

เมื่อพูดถึงคำว่า Compile หลายคนมักนึกถึงภาษาอย่าง C หรือ C++ ซึ่งต้องแปลง Source Code ให้เป็นไฟล์ Executable ก่อนจึงจะสามารถรันได้ ขณะที่ภาษาอย่าง Python หรือ Java มักถูกอธิบายว่าเป็นภาษาแบบ Interpreted จึงไม่เกี่ยวข้องกับการ Compile อย่างไรก็ตาม ความเข้าใจนี้ก็ไม่ได้ถูกต้องเสียทีเดียว

ตามที่ได้เคยกล่าวเอาไว้ใน EP ก่อนๆ ไม่ว่าโปรแกรมจะถูกพัฒนาด้วย High-Level Programming Language ใดๆ ก็ตาม สุดท้ายแล้วคำสั่งทั้งหมดจะต้องถูกแปลงให้อยู่ในรูปแบบที่ CPU สามารถประมวลผลได้ ซึ่งก็คือ Machine Code หรือ Binary นั่นเอง ความแตกต่างระหว่างภาษาแต่ละประเภทจึงไม่ได้อยู่ที่ว่า “มีการ Compile หรือไม่” แต่เป็น Compile เมื่อใดต่างหาก ก่อนจะลงลึกในรายละเอียด เรามาทำความเข้าใจแนวคิดของ Compiler และ Interpreter ให้ชัดเจนเสียก่อน

Interpreted Language

ตัวอย่างภาษาที่ใช้ “Interpreter” จะได้แก่ JavaScript, Python, PHP, Ruby และ PowerShell โดยจุดเด่นของภาษาเหล่านี้คือเราสามารถนำ Source Code ไปรันผ่านตัวโปรแกรมที่ทำหน้าที่เป็น Interpreter ได้โดยตรง โดยไม่จำเป็นต้องสร้างไฟล์ที่เป็น Binary หรือ Executable File ก่อน ซึ่งนั่นก็จะทำให้นักพัฒนาสามารถทดสอบและแก้ไข Source Code ได้อย่างรวดเร็ว

อย่างไรก็ตาม ข้อจำกัดของวิธีนี้ก็คือ เครื่องที่ใช้รันโปรแกรมจะต้องมี Interpreter ของภาษานั้นติดตั้งอยู่ด้วยเป็นอย่างน้อย มิฉะนั้นจะไม่สามารถประมวลผล Source Code ได้ แตกต่างจากโปรแกรมที่ถูก Compile เป็นไฟล์ Executable ที่อย่างน้อยสามารถนำไปรันบน Computer ที่เป็น CPU และ OS แบบเดียวกันได้ทันที

ทีนี้เราลองมาดูว่าแล้ว Source Code นั้นถูกแปลงให้อยู่ในรูปแบบของ Machine Code เพื่อให้ Computer สามารถเข้าได้อย่างไร โดยจะขอยกตัวอย่างเป็นภาษา Python

How interpreter of Python convert source code into the machine code

How interpreter of Python convert source code into the machine code

เราจะเห็นว่าตัว Source Code Python จะถูกแปลงให้อยู่ในรูปของ Bytecode บางอย่างเพื่อนำไปรันบน Virtual Machine ที่สร้างขึ้นด้วย Python Interpreter หรือ ที่เรียกว่า PVM นั่นเอง จากนั้น PVM จึงทำการ Compile Bytecode ที่ว่ามาให้อยู่ในรูปของ Machine Code แล้วส่งตรงให้ Computer หรือ CPU สามารถ Execute ได้เลยทันที ปราศจากการสร้าง Executable File ใดๆ ออกมา ซึ่งเราจะเรียก Technique นี้ว่า Just-in-Time (JIT) Compiler นั่นเอง

เราสามารถลองใช้คำสั่ง python3 -m dis <source_code.py> เพื่อแปลงตัว Source Code (ทางซ้าย) ให้เป็นไฟล์ .pyc (ทางขวา) ที่ปกติจะต้องถูก Translate ก่อนจะนำเข้าสู่ PVM เราก็จะเห็นว่าหน้าตามันเหมือนภาษา Assembly มากๆ แต่เป็น Assembly ที่เอาไว้ให้ PVM ใช้เท่านั้น

Assembly of Python !

Assembly of Python !

ซึ่งถ้าใครอยากจะลองส่องตัว Machine Code ที่ PVM Compile จากตัว Bytecode ว่าหน้าตาเป็นอย่างไร ก็สามารถใช้ Tool พวก pypy กับ jitviewer ไปลองทดสอบเล่นกันดูได้ครับ

โดยสรุปแล้ว หาก Source Code สามารถถูกรันได้ทันทีโดยไม่จำเป็นต้องสร้างไฟล์ Executable ขึ้นมาก่อน เรามักเรียกกระบวนการนี้ว่า Interpreted อย่างไรก็ตาม คำว่า Interpreted ไม่ได้หมายความว่าไม่มีการ Compile เกิดขึ้นเลย แต่เป็นเพียงการซ่อนขั้นตอนเหล่านั้นไว้จากผู้ใช้งาน ทำให้สามารถสั่งรัน Source Code ได้โดยตรง

Compiled Language

Compiled Language จะหมายถึง High-Level Language ที่แปลง Source Code ให้กลายเป็น Machine Code ที่ CPU สามารถเข้าใจและรันได้โดยตรง ซึ่งแตกต่างจาก Interpreted Language ที่ได้กล่าวมาข้างต้นที่ Source Code จะต้องถูกแปลงให้อยู่ในรูปแบบบางอย่างก่อน (เช่น bytecode ใน Python) แล้วจึงให้ Virtual Machine ประมวลผลต่ออีกที ภาษาประเภทนี้จึงมักมีการสร้าง Binary หรือ Executable File ออกมา แทนที่จะรัน Source Code โดยตรงจากหน่วยความจำ ตัวอย่างภาษาที่เป็นลักษณะแบบนี้ก็คือ C, C++, Rust, Go หรือ Swift เป็นต้น

โดยเราสามารถอ้างอิงภาพด้านล่างเกี่ยวกับกระบวนการแปลง Source Code เป็น Machine Code ของภาษาในตระกูลนี้ได้เลย

How compiler convert the source code to the machine code

How compiler convert the source code to the machine code

Preprocessor — ขั้นตอนนี้จะทำการแทนที่สัญลักษณ์ (เช่น Macro และ #include) ที่อยู่ใน Source Code ด้วยโค้ดตามที่กำหนดไว้ ซึ่งกระบวนการนี้พบได้เฉพาะในภาษา C และ C++ โดยผลลัพธ์ที่ได้ยังคงเป็น Source Code เช่นเดิม เพียงแต่มีขนาดใหญ่ขึ้นจากการแทนที่ข้อความต่าง ๆ

Object File — Source Code จะถูก Compile ให้กลายเป็น Machine Code หรือ Binary ในรูปแบบที่เรียกว่า Object File

Linker — แม้ว่า Object File จะอยู่ในรูปแบบ Machine Code ที่คอมพิวเตอร์สามารถเข้าใจได้แล้ว แต่ก็ยังไม่พร้อมทำงาน เนื่องจากภายในโปรแกรมอาจจะมีการอ้างอิงถึง Library หรือ Module ของ OS อยู่ที่ไม่ได้เขียน Logic เองโดยผู้พัฒนา (เช่น แสดงผลข้อความ เข้าถึง Memory เขาถึงไฟล์ หรือทำ syscall เป็นต้น) Linker จึงมีหน้าที่เชื่อมโยงการอ้างอิงเหล่านี้เพื่อให้โปรแกรมสามารถทำงานได้จริงๆ โดยแบ่งออกเป็น 2 รูปแบบ ได้แก่

  • Static Linking — นำทั้งก้อน Binary ของ Library หรือ Module มาใส่รวมไว้ภายในไฟล์ Executable ซึ่งนั่นจะทำให้ไฟล์มีขนาดใหญ่ขึ้น แต่ก็สามารถนำไปรันได้ทันทีโดยไม่ต้องพึ่งพา Library บนเครื่องปลายทาง ซึ่ง Linker แบบนี้มักจะพบในภาษา Rust และ Go ที่ Linker จะทำการฝัง Binary บาง Library ที่จำเป็นเท่านั้น (TIP: Technique นี้ จะมีประโยชน์ในกรณีที่เราต้องการ Compile Exploit Code เพื่อโจมตีแต่เครื่องเป้าหมายไม่มี Dependency ที่จำเป็นสำหรับการทำงานของโปรแกรม เราสามารถแก้ไขปัญหาด้วยการใช้วิธีนี้ได้)
  • Dynamic Linking — แทนที่จะนำ Binary ของ Library มารวมไว้ในไฟล์เดียว Linker จะสร้าง Table สำหรับเก็บข้อมูลอ้างอิงถึง Library หรือ Module ที่โปรแกรมต้องใช้งาน เมื่อโปรแกรมเริ่มทำงาน OS จะอ่าน Table นี้เพื่อค้นหาและเชื่อมโยง Library ที่จำเป็น จากนั้นจึงเติม Address ของฟังก์ชันหรือข้อมูลที่พบใน Library ให้กับโปรแกรม การ Link ในลักษณะนี้ทำให้ไฟล์ Executable มีขนาดเล็กลง แต่ก็ต้องแลกกับการที่เครื่องปลายทางจะต้องมี Library ดังกล่าวติดตั้งอยู่ด้วย ซึ่งโดย Default ภาษา C และ C++ จะใช้ Linker แบบนี้เสมอ

Hybrid Language

นอกเหนือจาก Interpreted Language และ Compiled Language แล้ว ก็ยังมีภาษาอีกประเภทหนึ่งที่มีลักษณะก้ำกึ่งอยู่ระหว่างสองประเภทนี้ เช่น C# ที่แม้จะต้อง Compile ออกมาเป็นไฟล์ Executable File ก่อน แต่ภายในไฟล์ดังกล่าวจะยังเป็น Bytecode ที่เรียกว่า CIL (Common Intermediate Language) ซึ่งจะถูกแปลงเป็น Machine Code อีกครั้งในขณะรันโปรแกรม

ส่วน Java แม้จะไม่จำเป็นต้องสร้างไฟล์ Executable แต่ก็ต้องแปลง Source Code ให้เป็น Bytecode (.class) ก่อน จากนั้น Bytecode เหล่านี้จึงสามารถนำไปแจกจ่ายไปยังเครื่องอื่นได้ แต่เครื่องปลายทางจำเป็นต้องมี JVM (Java Virtual Machine) เพื่อทำการแปลง Bytecode ให้เป็น Machine Code และรันโปรแกรมต่อไป แตกต่างจาก Python ที่สามารถแจกจ่าย Source Code โดยตรง แล้วใช้ Interpreter บนเครื่องปลายทางประมวลผลได้ทันที

ขอขอบคุณภาพจาก Vinit Raj

ขอขอบคุณภาพจาก Vinit Raj

Program Runtime

หลังจากที่เราเข้าใจแล้วว่าแต่ละ High-Level Language มีวิธีจัดการกับ Source Code อย่างไรเพื่อให้พร้อมสำหรับการทำงาน ขั้นตอนถัดไปที่ต้องศึกษาเพิ่มเติมคือช่วงที่โปรแกรมกำลังถูก Execute หรือที่เรียกว่า Runtime ซึ่งแต่ละภาษา รวมถึง OS ก็จะมีวิธีการจัดการและควบคุมการทำงานของโปรแกรมที่แตกต่างกันออกไป

แต่ไม่ว่า Runtime ของแต่ละภาษาจะมีรูปแบบการทำงานแตกต่างกันอย่างไร สิ่งที่ Runtime ทุกประเภทจะต้องมีอย่างแน่นอนก็คือ

  • Code Execution — Runtime จะต้องมีวิธีทำให้ Machine Code หรือ Bytecode สามารถถูกประมวลผลโดย CPU ได้ ไม่ว่าจะเป็นการ Execute โดยตรง หรือผ่านกระบวนการแปลง Bytecode ให้เป็น Machine Code ก่อน
  • Memory & State Management — Runtime จะต้องจัดเตรียมและบริหารจัดการ Memory รวมถึง State ที่จำเป็นต่อการทำงานของโปรแกรม เช่น Stack, Heap และ Execution Context พร้อมทั้งรักษาสถานะเหล่านี้ระหว่างที่โปรแกรมกำลังทำงาน
  • Resource Management — Runtime จะต้องช่วยจัดการ Resource ที่โปรแกรมต้องการใช้งานเพิ่มเติมระหว่าง Runtime เช่น Memory, Thread, File และ OS Handle
  • OS Interaction — Runtime จะต้องมีช่องทางให้โปรแกรมสามารถติดต่อกับ Operating System เพื่อเรียกใช้บริการต่าง ๆ เช่น System Call หรือ API

Managed Language

ภาษาในกลุ่มนี้ ตัว Runtime ของมันนอกจากจะทำหน้าที่พื้นฐานที่กล่าวไปแล้ว ยังมีการเพิ่มความสามารถเพิ่มเติมเพื่อช่วยให้ผู้พัฒนาสามารถพัฒนาโปรแกรมได้ง่ายขึ้น ลดภาระในการจัดการ Resource ระดับ Low-Level และลดโอกาสเกิดข้อผิดพลาดจากการจัดการด้วยตนเอง ตัวอย่างของภาษาในกลุ่มนี้ ได้แก่ C#, Java และ Python โดย Feature ที่มักพบใน Managed Runtime ได้แก่

  • Garbage Collection — Runtime จะช่วยตรวจสอบ Object ที่ไม่ได้ถูกใช้งานแล้ว และทำการคืน Memory ให้อัตโนมัติ ผู้พัฒนาไม่จำเป็นต้องเขียน Logic สำหรับการจองและคืนพื้นที่ Memory ของ Object ด้วยตนเอง ทำให้ลดความซับซ้อนในการจัดการ Memory และลดโอกาสเกิดข้อผิดพลาดจากการบริหารจัดการ Resource
  • Automatic Memory Management — Runtime ช่วยจัดสรรและควบคุมการใช้งาน Memory แทนที่ผู้พัฒนาจะต้องจัดการเองทั้งหมด ตัวอย่างที่เห็นภาพได้ชัดเจนคือการใช้งาน List ใน Python ซึ่งผู้พัฒนาไม่จำเป็นต้องกำหนดขนาดของ Memory ล่วงหน้าว่า List จะต้องรองรับข้อมูลจำนวนเท่าใด เมื่อมีการเพิ่มหรือลดข้อมูล Runtime จะเป็นผู้จัดการจัดสรรและคืน Memory ที่เหมาะสมให้โดยอัตโนมัติ
x = [1,2,3,4,5,6,7]
x.append(8)
x.append(9)
x.append(10)
x.remove(4)
  • Runtime Safety Checks — Runtime สามารถตรวจสอบข้อผิดพลาดบางประเภทระหว่างการทำงาน เช่น Type Checking หรือ Memory Access Validation (โค้ดของโปรแกรมพยายามจะเข้าถึง Memory ที่ไม่ได้รับอนุญาต)

Unmanaged Language

ภาษาในกลุ่มนี้ Runtime จะเน้นเพียงหน้าที่พื้นฐานที่จำเป็นต่อการทำงานของโปรแกรม โดยไม่ได้เข้ามาจัดการ Resource ระดับสูงแทนผู้พัฒนา ดังนั้นผู้พัฒนาจะต้องรับผิดชอบการจัดการ Memory และ Resource ต่าง ๆ ด้วยตนเอง เช่น การ Allocate และ Release Memory รวมถึงการควบคุม Lifetime ของ Object

int numbers[5];  // ต้องกำหนดขนาด Array ล่วงหน้า
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;

ตัวอย่างเช่นภาษา C และ C++ ซึ่งให้อิสระและการควบคุมระดับ Low-Level แก่ผู้พัฒนามากกว่า ตัวอย่างเช่นการใช้ malloc ในภาษา C โดยในโค้ดตัวอย่างจะเป็นการขอจองพื้นที่ Memory ที่มีขนาด 5 หน่วยของ int (ประมาณ 20 bytes)

int size = 5;

// Allocate memory for 5 integers
int *numbers = malloc(size * sizeof(int));

if (numbers == NULL) {
  printf("Memory allocation failed\n");
  return 1;
}

ซึ่งการจัดการอะไรแบบนี้ด้วยตัวเอง โดยไม่มี Runtime มาช่วยเป็นอะไรที่ค่อนข้างซับซ้อนและหากผู้พัฒนาเขียนโค้ดไม่ถูกต้องก็อาจทำให้เกิดความเสี่ยงหรือช่องโหว่เพิ่มเติมได้ เช่น Memory Leak, Use-After-Free หรือ Buffer Overflow ดังนั้นโปรแกรมที่พัฒนาด้วยภาษาในกลุ่มนี้จึงมักเป็นเป้าหมายหลักของการทำ Binary Exploitation และ Pwnable เนื่องจากผู้โจมตีสามารถใช้ข้อผิดพลาดในการจัดการ Memory เพื่อควบคุมการทำงานของโปรแกรมได้

Semi-Managed Language

ภาษาในกลุ่มนี้จะอยู่กึ่งกลางระหว่าง Managed Language และ Unmanaged Language โดย Runtime หรือ Compiler จะเข้ามาช่วยจัดการบางส่วนของการทำงาน แต่ยังคงเปิดโอกาสให้ผู้พัฒนาควบคุม Resource ระดับ Low-Level ได้มากกว่า Managed Language ทั่วไป

ตัวอย่างเช่น Rust และ Go ซึ่งมีแนวทางในการลดภาระการจัดการ Memory ด้วยกลไกของภาษาเอง เช่น

  • Automatic Memory Management — Runtime หรือ Compiler ช่วยจัดการ Lifetime ของ Object บน Memory บางส่วน เพื่อลดความจำเป็นในการจัดการด้วยตนเองที่อาจเกิดข้อผิดพลาดได้
  • Ownership & Borrowing (Rust) — Compiler จะตรวจสอบการใช้งาน Memory ตั้งแต่ตอนถูก Compile เพื่อป้องกันปัญหาที่เกี่ยวข้องกับการใช้งาน Memory ที่ไม่ถูกต้อง
  • Garbage Collection (Go) — Runtime มี Garbage Collector ช่วยคืน Memory ที่ไม่ได้ใช้งานแล้ว แต่ขนาดของ Runtime นั้นกลับเบาบาง (Lightweight) กว่า Runtime ของพวก Java (JVM) และ C# (CLR) ที่เป็น Managed Language

แนวทางนี้ช่วยลดความเสี่ยงจากการจัดการ Memory แบบ Manual เหมือนภาษา C/C++ แต่ยังคงอนุญาตให้ผู้พัฒนาสามารถควบคุม Resource ได้ในระดับที่มีประสิทธิภาพสูงกว่า Managed Language ทั่วๆไป

เราสามารถ Pwnable ภาษาที่ไม่ใช่ Unmanaged Language ได้ไหม ?

เนื่องจากภาษาตระกูล C และ C++ ไม่ได้มี Runtime ที่เข้ามาช่วยจัดการ Memory ในระดับสูงเหมือนภาษาอื่น ๆ หาก Developer จัดสรร Memory หรือเขียนโค้ดได้ไม่รัดกุม ก็มีความเสี่ยงที่จะเกิดช่องโหว่ที่สามารถนำไปสู่การทำ Binary Exploitation ได้ อย่างไรก็ตาม หากเป็นภาษาอย่าง Python, Java หรือ C# ที่มี Runtime ที่ค่อนข้างแข็งแรง จัดการงานเสี่ยงๆ แทนเราได้ เราจะยังสามารถพบช่องโหว่ที่เกี่ยวข้องกับการทำ Pwnable จากภาษาเหล่านั้นได้หรือไม่?

Unsafe Feature Usage

ในหลาย ๆ ภาษาที่เป็น Managed Language จริง ๆ แล้ว Developer ยังสามารถเขียน Logic ที่ต้องจัดการ Memory หรือ Resource แบบ Low-Level ได้ด้วยตัวเองอยู่ดี โดยตัวอย่างด้านล่างเป็น Code Python ที่ Developer ทำการเขียนโค้ดที่จะ Allocate และ Free Memory เอง แทนที่จะใช้ Data Structure หรือ Data Type มาตรฐานที่ Python Runtime จัดเตรียมไว้ให้ ซึ่งในกรณีนี้ Runtime หรือ Interpreter จะไม่สามารถเข้ามาช่วยจัดการหรือป้องกันข้อผิดพลาดที่เกิดขึ้นจากการจัดการ Memory ในระดับนี้ได้

import ctypes

libc = ctypes.CDLL("libc.so.6")

# Allocate 10 bytes of memory
ptr = libc.malloc(10)

# Write 'A' to the allocated memory
ctypes.memset(ptr, ord('A'), 10)

# Read the content of the allocated memory
data = ctypes.string_at(ptr, 10)

print(data)

# Free the allocated memory
libc.free(ptr)

กรณีลักษณะนี้มักพบใน Software ที่พัฒนาด้วย Managed Language อยู่แล้ว แต่ภายหลังต้องการ Optimize บางส่วนของระบบ เช่น เพิ่ม Performance หรือเชื่อมต่อกับ Native Library จึงเลือกใช้ Low-Level Interface แทนการเขียนระบบใหม่ด้วยภาษาอื่น

Vulnerable Interpreter / Virtual Machine

จริง ๆ แล้ว Managed Language หลาย ๆ ภาษาที่ Source Code จำเป็นต้องถูกจัดการผ่าน Interpreter หรือ Virtual Machine นั้น ตัว Runtime เหล่านี้ก็มักจะถูกพัฒนาด้วยภาษา Low-Level เช่น C หรือ C++ ซึ่งทำให้ยังมีความเสี่ยงในระดับ Memory Management อยู่เช่นกัน ดังนั้นหากโค้ดภายใน Interpreter หรือ Virtual Machine มีการจัดการ Memory ที่ไม่ปลอดภัย และช่องโหว่นั้นสามารถถูก Trigger ผ่าน Function หรือ Feature ระดับ High-Level ของภาษานั้นได้ ก็อาจนำไปสู่การทำ Binary Exploitation ได้เช่นเดียวกัน โดยนี่คือตัวอย่าง CVE ที่เกี่ยวข้องกับภาษาที่ว่ามา

  • CVE-2025–21171 — เป็นช่องโหว่ใน .NET Runtime ของ C# ซึ่ง Runtime ส่วนหนึ่งถูกพัฒนาด้วยภาษา C/C++ ที่มีการเขียน Logic จัดการ Memory ที่ไม่ปลอดภัยจนทำให้สามารถเกิด Heap Overflow และอาจนำไปสู่การ Execute Code ได้
  • CVE-2025–13223 — JavaScript เป็น Managed Language ที่ตัว Runtime ของมันจะแตกต่างกันไปตามแต่ละ Browser โดยช่องโหว่นี้เกิดขึ้นใน Runtime ที่ชื่อว่า V8 Engine ที่เป็นของ Chromium ซึ่งถูกพัฒนาด้วยภาษา C++ อีกที โดยพบว่ามีการจัดการ Memory ในส่วนของ Heap ที่ไม่ถูกต้อง ทำให้สามารถเกิด Memory Corruption และนำไปสู่การทำ Pwnable / Remote Code Execution (RCE) ได้
  • CVE-2021–3177 — เป็นช่องโหว่ใน Python Interpreter (CPython) ซึ่งตัว Interpreter ถูกพัฒนาด้วยภาษา C โดยโค้ดภาษา C เบื้องหลังมีการเขียน Logic สำหรับการจัดการ Memory ที่ไม่ปลอดภัยจนทำให้เกิด Buffer Overflow ขึ้น และอาจนำไปสู่การควบคุมการทำงานของ Process ได้

Insecure Third-Party Library / Dependency

ซึ่งก็เช่นเดียวกันกับ Unsafe Feature Usage และ Vulnerable Interpreter / Virtual Machine โดยหากเราใช้งาน Library จาก Third-party ที่ภายในมีการ Implement ด้วย Unmanaged Code เช่น C หรือ C++ และมีการเขียนโค้ดที่ไม่ปลอดภัย ก็อาจทำให้เกิดช่องโหว่ด้าน Memory Corruption และนำไปสู่การทำ Binary Exploitation ได้เช่นเดียวกัน ถึงแม้ว่า Code ของโปรแกรมเราจะเป็น Managed Language ล้วนๆ ก็ตาม

  • CVE-2025–48379 — Pillow ซึ่งเป็น Third-party Library สำหรับภาษา Python ที่ใช้ในการประมวลผลรูปภาพ (Image Processing) โดยภายในมีบาง Component ที่ถูก Implement ด้วยภาษา C พบว่ามีการจัดการ Memory ที่ไม่ปลอดภัย ส่งผลให้เกิด Heap Buffer Overflow ได้

ขอขอบคุณภาพจาก Taman Belajar

ขอขอบคุณภาพจาก Taman Belajar

Binary Exploitation ได้ใช้ในงานจริงมากแค่ไหน ?

เป็นความจริงที่ปฏิเสธไม่ได้ว่า หากเราทำงานในสายงาน Penetration Tester หรือ Cybersecurity Consultant โอกาสที่จะได้รับ Engagement ที่มอบ Binary File หรือ Source Code ของโปรแกรมมาให้วิเคราะห์เพื่อค้นหาช่องโหว่นั้นแทบจะไม่มีเลย โดยเฉพาะการทำ Binary Exploitation ในรูปแบบเดียวกับที่พบใน CTF ซึ่งโอกาสเจอก็แทบจะเป็น 0 เลยก็ว่าได้

โดยทั่วไปแล้ว งานส่วนใหญ่ที่ Penetration Tester หรือ Cybersecurity Consultant ได้รับมักเป็น Web Application Pentest, Mobile Application Pentest, Network Pentest, Configuration Review หรือ Red Teaming ซึ่งกระบวนการในการค้นหาช่องโหว่และ Exploitation ที่แตกต่างจาก Binary Exploitation & Pwnable อย่างสิ้นเชิง คำถามคือ แล้วคนที่เชี่ยวชาญในด้านนี้ จะทำทักษะที่ค่อนข้างเฉพาะทางมาสร้างประโยชน์ให้กับบริษัท Cybersecurity Vendor ได้อย่างไร

Internal Research Team

จริง ๆ แล้ว Product Vendor หลายแห่งทั่วโลก ไม่ว่าจะเป็นเจ้าดังๆ Microsoft หรือ เจ้าที่เป็น Local ในประเทศไทย เช่น ธนาคาร องค์กรรัฐต่างๆ ที่จะต้องพัฒนา Product ต่างๆ ออกมา ไม่ว่าจะเป็น Web Application, Software หรือ Binary Program ก็จะต้องมีทีมภายในที่รับผิดชอบด้าน Product Security เพื่อค้นหาและแก้ไขช่องโหว่ก่อนที่ Product จะถูกนำไปใช้งานจริง โดยทีมเหล่านี้สามารถเข้าถึง Source Code, Binary File รวมถึง Environment ภายในที่จำเป็นต่อการวิเคราะห์ได้

ดังนั้น หากต้องการหาที่ทำงานที่มีโอกาสได้ใช้ทักษะ Binary Exploitation & Pwnable ก็คงจะต้องเป็นการสมัครเข้าสู่ตำแหน่งสาย Product Security ภายใน Product Vendor นั้นๆ โดยตรง มากกว่าการทำงานเป็น Penetration Tester ทั่วไป

อย่างไรก็ตามก็ยังมีอีกหนึ่งความท้าทายที่ต้องระวังเอาไว้ เนื่องจาก Software หรือ Binary Application ในสมัยใหม่จำนวนมากมักจะถูกพัฒนาด้วยภาษาที่มี Memory Safety หรือ Runtime Protection เช่น C#, Java, Go หรือ Rust ทำให้โอกาสที่จะพบ Product ที่จะสามารถถูก Pwnable ได้จะลดลงเป็นอย่างมาก จนถึงขั้นเป็นเพียงแค่ Thick-Client Penetration Testing แบบธรรมดาๆ เลยก็ว่าได้

ต่อให้ทำงานกับ Product Vendor แต่ถ้าไม่เจอภาษาที่ Pwnable ก็ไม่มีโอกาสได้ใช้ทักษะนี้อยู่ดี

ต่อให้ทำงานกับ Product Vendor แต่ถ้าไม่เจอภาษาที่ Pwnable ก็ไม่มีโอกาสได้ใช้ทักษะนี้อยู่ดี

Independent Vulnerability Researcher

แต่อีกหนึ่งเส้นทางที่ไม่จำเป็นต้องสมัครงานกับ Product Vendor โดยตรงก็คือการเข้าร่วมโครงการ Bug Bounty หรือ Vulnerability Disclosure Program (VDP) ซึ่งเปิดโอกาสให้บุคคลทั่วไปหรือบริษัทภายนอกสามารถค้นหาและรายงานช่องโหว่ได้ หากพบช่องโหว่ที่เข้าเกณฑ์ก็อาจได้รับเงินรางวัลจาก Vendor โดย Product Vendor รายใหญ่ที่มักมีโครงการเหล่านี้ เช่น Microsoft, Apple, Android, IBM หรือ Cisco เนื่องจากมีผลิตภัณฑ์ที่ใช้งานแพร่หลายและมีมูลค่ารางวัลที่ค่อนข้างสูง

ตามที่เราทราบกันดีว่า Component สำคัญหลายๆ ส่วนของ OS อย่าง Windows หรือ Android ยังคงถูกพัฒนาด้วย Unmanaged Code เช่น C และ C++ ทำให้มีโอกาสเป็นอย่างมากที่จะพบช่องโหว่ที่เกี่ยวข้องกับการจัดการ Memory ที่ไม่ปลอดภัย และได้ใช้ทักษะของ Binary Exploitation หรือ Pwnable ได้อย่างเต็มที่

สิ่งที่เป็นอุปสรรคอย่างแน่นอนของผู้ที่เดินในสายงานนี้ก็คือ จะไม่มีทางเข้าถึงหรือได้รับ Source Code ของ Program ได้อย่างแน่นอนซึ่งแตกต่างจาก Internal Research Team โดยสิ้นเชิง โดยแนวทางที่มักใช้กันในการวิเคราะห์และหาช่องโหว่ก็คือ ทำการซื้อหรือจัดหา Product ที่ต้องการแล้วจึงนำมาทำ Reverse Engineering เพื่อวิเคราะห์ Logic แล้วดำเนินการ Pwnable ต่อๆ ไป

Cybersecurity Vendor

เราลองย้อนกลับมาที่บริษัทที่ให้บริการด้านความปลอดภัยโดยทั่วๆ ไป คำถามคือถ้าเราทำงานในบริษัทเหล่านี้ เราจะยังมีโอกาสได้นำทักษะด้าน Binary Exploitation มาใช้งานได้หรือไม่ แม้ว่าจะไม่ได้รับ Engagement ที่เกี่ยวข้องกับการวิเคราะห์ Binary โดยตรง คำตอบคือมีอย่างแน่นอน และในความเป็นจริงก็มีกรณีลักษณะนี้เกิดขึ้นมาโดยตลอด

ตัวอย่างที่ใกล้ตัวที่สุดคือ Mobile Application ซึ่งผู้พัฒนาบางรายอาจเลือก Implement Logic บางส่วนให้อยู่ในรูปแบบ Native Code เช่น C หรือ C++ โดยเฉพาะ Application ที่มีความต้องการด้านความปลอดภัยสูง เช่น Mobile Banking Application ทำให้ผู้ทดสอบสามารถเข้าถึง Binary ของ Application เพื่อทำ Reverse Engineering และวิเคราะห์หาช่องโหว่ได้ หากสามารถค้นพบช่องโหว่ประเภท Binary Exploitation ได้ ก็จะกลายเป็นทักษะที่ช่วยสร้างความแตกต่างจากการทำ Mobile Application Pentest ทั่วไป

ค่าย Mobile Hacking Lab มีการเปิดคอร์สเกี่ยวกับ “Binary Exploitation” ในระดับ Mobile Application ด้วย

ค่าย Mobile Hacking Lab มีการเปิดคอร์สเกี่ยวกับ “Binary Exploitation” ในระดับ Mobile Application ด้วย

หรือในอีกกรณี เช่น Network Penetration Testing หรือ Red Teaming เราอาจจะมีโอกาสบังเอิญพบเจอ Binary Component ที่ลูกค้าพัฒนาหรือ Customize ขึ้นมาเองที่ไม่มีใครเคยโจมตีมาก่อน ซึ่งความรู้ด้าน Pwnable สามารถนำมาช่วยวิเคราะห์และค้นหาช่องโหว่เพิ่มเติมได้ รวมถึงในงาน IoT และ Hardware Security ที่ Firmware หรือ Embedded OS มักมีส่วนประกอบที่พัฒนาด้วย Unmanaged Code เช่น C/C++ ซึ่งหากเราสามารถค้นพบช่องโหว่ที่มีความเฉพาะทางในลักษณะนี้ได้ ก็ย่อมช่วยสร้างความแตกต่างให้กับบริษัท Vendor เมื่อเทียบกับคู่แข่งรายอื่น ๆ อีกทั้งยังช่วยเพิ่มความเชื่อมั่นและสร้างความประทับใจให้กับลูกค้าได้อีกด้วย

Conclusion

ในบทความนี้ เราได้พูดถึงกระบวนการที่ Source Code จะต้องถูกแปลงให้เป็น Machine Code ที่ตัว Computer สามารถเข้าใจและประมวลผลได้ ผ่านแนวคิดของ Interpreted Language, Compiled Language และ Hybrid Language รวมถึงการจำแนกภาษาตามประเภทของ Runtime ที่ใช้งาน

จากเนื้อหาดังกล่าว ทำให้เราเข้าใจได้ว่าโปรแกรมที่พัฒนาด้วย Unmanaged Language เช่น C/C++ มีโอกาสพบช่องโหว่ที่เกี่ยวข้องกับ Binary Exploitation และ Pwnable ได้สูงกว่า เนื่องจากผู้พัฒนามีโอกาสที่จะทำ Logic ในการจัดการ Memory ไม่ปลอดภัยเพียงพอ อย่างไรก็ตาม นั่นก็ไม่ได้หมายความว่าภาษาประเภทอื่นๆ จะไม่มีช่องโหว่แบบนี้ เพียงแต่เงื่อนไขในการเกิดมักมีความเฉพาะเจาะจงมากขึ้น เช่น ช่องโหว่ในตัว Interpreter, Runtime หรือ Third-party Library ที่ถูกนำมาใช้งาน

ท้ายที่สุด บทความนี้ยังได้วิเคราะห์ถึงโอกาสในการนำทักษะด้าน Binary Exploitation และ Pwnable มาใช้งานจริง โดยพิจารณาจากปัจจัยต่าง ๆ เช่น ความนิยมของภาษาโปรแกรมในปัจจุบัน รูปแบบของ Product Vendor และโอกาสที่ผู้ทำงานสาย Cybersecurity Consultant จะสามารถนำทักษะเหล่านี้มาประยุกต์ใช้ใน Engagement จริงได้

ถึงแม้ว่าบทความนี้จะเป็นการพูดถึงแนวคิดและทฤษฎีในระดับ High-level เป็นส่วนใหญ่ และยังไม่ได้ลงรายละเอียดเชิงลึกเกี่ยวกับ Binary Exploitation โดยตรง แต่เนื้อหาเหล่านี้ถือเป็นพื้นฐานที่มีความสำคัญอย่างมาก เนื่องจากหัวข้อนี้ไม่ใช่เรื่องที่ผู้ที่เริ่มต้นในสายงาน Cybersecurity จะสามารถเรียนรู้และทำเป็นได้อย่างรวดเร็วหรือทันทีเนื่องด้วยความเฉพาะทาง ดังนั้นจึงจำเป็นต้องมีความเข้าใจในหลายๆ องค์ประกอบก่อนที่จะสามารถลงมือวิเคราะห์และทำ Exploitation ได้จริง

แล้วพบกันใหม่ใน Episode ถัดไป

ขอขอบคุณภาพจาก Campaign Live

ขอขอบคุณภาพจาก Campaign Live


메타데이터
post_id
32424d8eb37e
slug
binary-exploit-ep-2-how-computer-understand-source-code-32424d8eb37e
url
https://medium.com/@pat.sanitjairak/binary-exploit-ep-2-how-computer-understand-source-code-32424d8eb37e
canonical_url
https://medium.com/@pat.sanitjairak/binary-exploit-ep-2-how-computer-understand-source-code-32424d8eb37e
author_url
https://medium.com/@pat.sanitjairak
status
ok
fetched_at
2026-08-12 06:39:12