สวัสดีครับ
บทความนี้ผมจะมาแนะนำ แนวทางการตีกรอบภาษา Assembly ให้กระชับและง่ายขึ้นนิดนึง คิดว่างั้นนะครับ Haha
จากเครื่องมือ Rizin ที่มีความสามารถในการ Disassembly ได้ ซึ่งก็เหมือน IDA , Ghidra , r2 แต่ก็นั่นแหละครับ พอดีผมชอบ rizin
.
สาเหตุที่ทำให้เครื่องมือ Rizin ช่วยเราได้ เพราะผู้พัฒนาได้วาง กรอบแนวคิดของ "สี"
อยู่ใน output ของภาษา Assembly เช่น เวลาที่เราใช้คำสั่ง pdf เพื่อดู เราก็จะเริ่มมองเห็นสีต่างๆ
ยกตัวอย่างเช่น

ก็อย่างที่เห็น ตัวอย่างของผมใช้ Rizin บน Linux OpenSuse Leap 15.6 และใช้ Terminal Shell ของ Linux ทั่วไป ก็แสดงสีได้ อันนี้คือ default เลยนะครับ ผมไม่ได้ปรับแต่งอะไรเลย สีเป็นแบบนี้เลยครับ
ในที่นี้ ผมขออนุญาตใช้ Terminal สีดำ นะครับ เพื่อให้สามารถอธิบายโค้ดสีได้ง่ายขึ้น
จากในรูปจะเห็นว่า ผมเปิดไฟล์ reverse1.exe ทำการ s คือย้ายตำแหน่ง address ไปที่ sym.__main
ก็คือตำแหน่งหมายเลข 0x1400014f8 เมื่อผมมาถึงที่นี่ ผมใช้คำสั่ง pdf
ย่อมาจาก print Disassembly function ทาง rizin ก็จะแสดง Assembly code ของฟังก์ชัน ณ ตำแหน่ง 0x1400014f8 ให้ผมเห็น
ซึ่งถ้าเราสังเกต เราจะเห็นว่า rizin ได้ทำสีไว้ด้วย
เช่น ม่วง เขียว ขาว ฯ เป็นต้น
สีพวกนี้มีความหมายนะครับ และมันช่วยให้เราเข้าใจ Assembly function block เร็วขึ้น
ทีนี้ครับ ก่อนที่เราจะไปกันต่อ ผมก็อยากให้พวกเราย้อนกลับไปมองพื้นฐานอีกด้านหนึ่ง นั่นก็คือ แนวทางการจัดกลุ่มคำสั่ง ของภาษา Assembly ครับ
หรือเราจะมองว่า เราควรแบ่ง Category ของคำสั่ง Assembly ให้เป็นหมวดหมู่มันจะทำให้เรามีไอเดียมากขึ้น เหมือนภาษาโปรแกรมมิ่งอื่นๆ
คิดแบบเร็ว ๆ: เมื่อเห็นบล็อก assembly ให้ถามตัวเอง 3 ข้อแรกเสมอ —
(1) นี่จุดเริ่ม/จบฟังก์ชันไหม?
(2) มีการเปลี่ยนทิศทางการทำงาน (jump/call) เยอะหรือไม่?
(3) มีการจัดการหน่วยความจำ/stack/atomic หรือเรียกระบบปฏิบัติการหรือเปล่า?
คำอธิบายด้านล่างจะช่วยให้ตอบ 3 ข้อนี้ได้เร็วขึ้น
ทั้งนี้ เราจะยังไม่สามารถบอกได้ 100% ว่าโค้ดมันทำอะไร แต่มันจะทำให้เราเห็นภาพต่างๆ ได้ชัดมากขึ้น จากตัวช่วยต่างๆ ที่เรากำลังมีอยู่ในมือ

ขอแบ่งสีใน Rizin กับ Assembly Code ทั้งหมด 9 กลุ่มครับ
กลุ่มคำสั่งที่ 1 | `#ffffff` (ขาว)
**Data movement / Addressing (พื้นฐาน)
** — ย้ายค่า / หา address
ตัวอย่างเช่น : `mov, lea, movzx, movsx, movabs`

กลุ่มคำสั่งที่ 2 | `#16c60c` (เขียวสด)
**Call** — เรียกฟังก์ชัน ย้ายไปทำงานอื่น
ตัวอย่างเช่น : `call <addr>`, `call rax`

กลุ่มคำสั่งที่ 3 | `#c50f1f` (แดง)
**Return** — ออกจากฟังก์ชัน / คืนค่ากลับ
[จบงานนั่นเอง , ret = return ครับ]
ตัวอย่างเช่น : `ret`, `leave; ret`

กลุ่มคำสั่งที่ 4 | `#13a10e` (เขียวแก่)
**Jumps / Branches (conditional & unconditional)**
— เปลี่ยนทิศทางการทำงาน
ตัวอย่างเช่น : `je, jne, jmp, ja, jb, jnz`
คล้ายๆกับ เขียวสด มั้ย ? คนละกรณีกันครับ
ตัว j ส่วนใหญ่ (ส่วนแทบทั้งหมดเลยก็ว่าได้) ย่อมาจากคำว่า jump แปลว่า กระโดดไปเลย ส่วนจะไปทำอะไร ค่อยว่ากัน
ส่วนคำว่า call คือเรียกนะ แล้วกลับมาที่เรียกด้วย จะได้ทำอะไรต่อไป (จินตนาการ เอา เด้อ)

กลุ่มคำสั่งที่ 5 | `#3a96dd` (ฟ้าอ่อน)
**Compare / Logical / Flag ops** — เปรียบเทียบ ติ๊ก flag
ตัวอย่างเช่น : `cmp, test, and, or, xor, sete`

กลุ่มคำสั่งที่ 6 | `#c19c00` (เหลือง)
**Arithmetic / Stack adjust** — add/sub, จอง/คืน stack space
ตัวอย่างเช่น : `add, sub, imul, sub rsp, add rsp`
add คือเพิ่ม ทำให้ stack เติบโตขึ้นไปจนเต็ม เมื่อ stack เต็มก็จะต้อง pop ออก ก็จะจบการทำงาน (Stack fundamental จะเป็นงี้)
sub คือตรงกันข้ามกับ add มาจาก subtract แปลว่า ลบออก เมื่อ sub คือการทำให้ ข้อมูลใน register ลบออกไป (ให้มันว่าง ว่างั้นเหอะ)
พอลบออกแล้วก็พร้อมที่จะเติมข้อมูล เข้าไปใน stack ยังไงละ ดังนั้น ฟังก์ชั่นมักจะเริ่มด้วย sub ก่อน แล้วปิดท้ายด้วย add ครับ
เอาไว้ผมทำภาพสวยๆให้ดูเพิ่มเติมอีก แต่ก็ไปอ่านบทความก่อนหน้าได้นะ
Link บทความ : 0x007 reverse1.exe ความแตกต่างของ stack และ stack frame

กลุ่มคำสั่งที่ 8 | `#881798` (ม่วงหม่น)
**Push (stack save)**
— เก็บค่า ลง stack
ตัวอย่างเช่น : `push rax`, `push rbp`
| `#b4009e` (ม่วงสด)
**Pop (stack restore)**
— ดึงค่าออกจาก stack
ตัวอย่างเช่น : `pop rax`, `pop rbp`
ตรงไปตรงมา ง่ายๆ Push ก็เตรียมสร้าง stack / pop ก็เอา stack คืน (ทำงานเสร็จแล้ว)
ส่วน add กับ sub จะเน้นเรื่องขนาด และจังหวะของตัวแปรแต่ละตัวในฟังก์ชั่น นั้นๆ นะครับ

กลุ่มคำสั่งที่ 9 | `#0022ca` (น้ำเงิน)
**No-op / Padding / Misc** — alignment, breakpoint, marker
ตัวอย่างเช่น : `nop`, `int3`, `ud2`, `pause`
ภาษา assembly จะถูกตีความตาม จำนวน byte format ที่เหมาะสม หากโค้ดต้องจองพื้นที่น้อยเกิน หรือมีจุดที่ไม่ลงตัว ก็ต้องเพิ่มพื้นที่ที่ไม่ทำอะไร (padding) เข้าไป เพื่อให้คำสั่งต่อไปถูกแปลได้อย่างถูกต้องครับ เอาไว้เราว่ากันอีกทีใน บทความต่อๆ ไปในอนาคต

สรุปปิดท้ายของบทความนี้
เห็น เขียวสด(call) + ต่อด้วยชุด push/mov ให้เข้าใจว่าเป็นการเรียกฟังก์ชัน คัดเป็น boundary ของฟังก์ชัน เราจะมองขอบเขตได้คร่าวๆ หรือใช้ visual mode จะช่วยเห็นภาพได้ง่าย เดี๋ยวผมจะเล่าต่อไปอีกครับ
เห็น แดง(ret) + ก่อนหน้า sub rsp หรือ leave ฟังก์ชันจบ ret จะไม่อยู่ข้างบน ในโปรแกรมปกติ
เห็น เขียวแก่(jmp/je/… ) เยอะ หาจุดตัดสินใจ (ผู้ทำให้ path แตกต่าง) หรือเราก็ trace เฉพาะทางที่สนใจ
ฟ้า(test/cmp) อยู่หน้า เขียวแก่ นี่คือเงื่อนไขสำคัญ (เช็คค่า/flag) เราจะว่ากันอีกครั้ง
เหลือง(sub rsp) อยู่บนสุดของบล็อก นี่มี stack frame หาพื้นที่ local มีตัวแปรเกิดขึ้นในฟังก์ชั่น
ขาว(mov/lea) เยอะ ๆ นี่คือ data-flow core เราก็จะติดตามว่าค่า เดินจากไหนไปไหน
น้ำเงิน(nop/int3/ud2) เยอะ alignment/marker/anti-emulation เราจะว่ากันอีกครั้งครับ
ข้อแนะนำเล็ก ๆ เพื่อให้มือใหม่ใช้ง่ายขึ้น
อย่าเชื่อสีเดี่ยว ๆ — ให้เราใช้สีให้เป็นประโยชน์ "ระดับนึงพอครับ" เราต้องมอง pattern สั้น ๆ (3–5 instruction window) แล้วอ่านรวมกันนะครับ สีมันไกด์ให้เบื้องต้น ไม่ได้แปลว่าต้องเป็นแบบนั้นเสมอไป 100% ดูอย่างอื่นประกอบด้วยครับ
แยก call กับ jump ให้ชัด — call = เลือกเป็นจุดเริ่มฟังก์ชัน, jmp = เปลี่ยนเส้นทางภายใน/ระหว่างบล็อก
เจอกันใหม่บทความหน้า ขอบคุณครับ