- สภาพแวดล้อม Lua addon ของ World of Warcraft ทำให้ปรับ UI ได้ง่าย แต่เพื่อป้องกันการทำอัตโนมัติกับพฤติกรรมในเกม จึงแยกสิทธิ์การเข้าถึงข้อมูลออกจากสิทธิ์การสั่งทำงาน
- โมเดลความปลอดภัยของ Blizzard แยก สภาพแวดล้อมที่ไม่ปลอดภัย ซึ่งอ่านข้อมูลได้มาก ออกจาก สภาพแวดล้อมที่ปลอดภัย ซึ่งทำการกระทำได้ และเส้นแบ่งนี้คือหัวใจของการป้องกัน automation
random ที่อนุญาตในสภาพแวดล้อมที่ปลอดภัยเป็นเพียง wrapper บาง ๆ ของ rand ใน MSVC C และเป็น linear congruential generator ที่อ่อนแอซึ่งใช้สถานะ global ร่วมกัน จึงกลายเป็นช่องทางส่งข้อมูลได้
- จากเอาต์พุต RNG เพียงสองครั้ง สามารถลด candidate ของสถานะภายในให้เหลือมากสุด 8 รายการ แล้วใช้เอาต์พุตเพิ่มเติมเพื่อเลือก candidate เดียว จากนั้นเลื่อน RNG ไปข้างหน้าจนกว่าจะได้ดัชนีพฤติกรรมที่ต้องการ
- ภายนอกดูเหมือนเป็นการเลือกพฤติกรรมแบบสุ่ม แต่จริง ๆ แล้วสามารถทำให้เลือกการกระทำอย่างคาถาที่ต้องการได้ และไม่แน่ชัดว่า Blizzard แก้ไขภายหลังด้วยวิธีใด
Addon ของ World of Warcraft กลายเป็นจุดเริ่มต้นในการเขียนโปรแกรม
- Addon ของ World of Warcraft มีโครงสร้างที่เพียงใส่ไฟล์ซอร์ส
.lua ที่เขียนด้วย Lua ไม่กี่ไฟล์ลงในโฟลเดอร์ แล้วเกมจะโหลดโดยตรง
- หลังแก้ไขไฟล์และบันทึก จากนั้น reload interface ก็เห็นผลลัพธ์ได้ทันที ทำให้ กำแพงในการเริ่มต้น ต่ำมาก
- ช่วงแรกสร้าง addon หลายตัวด้วยวิธีคัดลอกโค้ดของคนอื่น แล้ว refactor หรือประกอบใหม่เพื่อทำฟีเจอร์ที่ต้องการ
- เมื่อเวลาผ่านไป กลับใช้เวลากับการเขียน addon เองมากกว่าการเล่นเกมจริง และ World of Warcraft ก็กลายเป็นจุดเริ่มต้นของการเขียนโปรแกรมอย่างจริงจัง
โมเดลความปลอดภัยของ addon ของ Blizzard
- หากผู้ใช้สร้าง addon ที่เขียนโปรแกรมได้อย่างสมบูรณ์ อาจเกิดปัญหา bot ได้ Blizzard จึงสร้างโครงสร้างที่จำกัด automation ที่ผู้เล่นทั่วไปทำได้
- UI ส่วนใหญ่และ API สำหรับรวบรวมข้อมูลสามารถใช้ได้โดยไม่มีข้อจำกัด
- เช่น สามารถอ่านพลังชีวิตของตัวละครเพื่อสร้าง แถบพลังชีวิต ที่ปรับขนาดของ frame ด้านหน้าได้
- API ที่ทำการกระทำในเกมจริง เช่น การเคลื่อนที่ของตัวละคร การร่ายคาถา หรือการใช้ไอเท็ม ถูกป้องกันไว้และเรียกได้เฉพาะจากโค้ดทางการของ Blizzard
- API สำหรับรับตำแหน่งในโลกอย่างแม่นยำและทิศทางกล้องก็ถูกจัดเป็นส่วนที่ต้องป้องกันตั้งแต่ช่วงหนึ่ง
- เป็นมาตรการรับมือ addon ที่วาดองค์ประกอบ 3D ทับบนโลกของเกมเพื่อให้การสู้บอสง่ายขึ้น
การแยกสภาพแวดล้อมปลอดภัยและไม่ปลอดภัย
- UI บางส่วนจำเป็นต้องโต้ตอบกับการกระทำจริงในเกม จึงสามารถสร้างปุ่มพิเศษที่เมื่อคลิกแล้วรันโค้ดใน สภาพแวดล้อมที่ปลอดภัย ได้
- ปุ่มเหล่านี้ไม่สามารถสร้าง ลบ หรือย้ายระหว่างการต่อสู้ได้
- จึงปิดกั้นวิธีทำ automation ด้วยการวางปุ่มแบบมีเงื่อนไขใต้ cursor ระหว่างการต่อสู้
- สภาพแวดล้อมที่ปลอดภัยสามารถตั้งค่าแบบโปรแกรมได้ว่าจะร่ายคาถาใด แต่ปิดกั้นการเข้าถึงสถานะภายนอกที่จำเป็นต่อ automation ตามอำเภอใจ
- อนุญาตเฉพาะ API รวบรวมข้อมูลบางส่วนในระดับระบบ macro และไม่ให้ข้อมูลที่จำเป็นต่อการเลือกคาถาที่เหมาะที่สุด เช่น cooldown ของสกิลหรือพลังชีวิตของ unit
- ผลคือทั้งสองสภาพแวดล้อมมีสิทธิ์ต่างกัน
- สภาพแวดล้อมที่ไม่ปลอดภัย: รับข้อมูลได้มาก แต่ทำการกระทำไม่ได้
- สภาพแวดล้อมที่ปลอดภัย: ทำการกระทำได้ แต่รับข้อมูลที่จำเป็นต่อ automation ไม่ได้
random กลายเป็นช่องทาง backdoor
- เป้าหมายไม่ใช่การใช้ซอฟต์แวร์ third-party เพื่อปิดข้อจำกัดด้านความปลอดภัย แต่เป็นการตรวจสอบว่าจะข้ามสภาพแวดล้อมที่ปลอดภัยด้วยเครื่องมือที่ได้รับอนุญาตทางเทคนิคเท่านั้นได้หรือไม่
- ระหว่างดูรายชื่อฟังก์ชันที่อนุญาตในสภาพแวดล้อมที่ปลอดภัย ก็พบ
random
- ตัวสร้างเลขสุ่มในคอมพิวเตอร์ส่วนใหญ่เป็น pseudorandom number generator ที่มีสถานะภายใน จึงมองว่าหากควบคุมสถานะนั้นได้ ก็สามารถส่งข้อมูลภายนอกเข้าสู่สภาพแวดล้อมที่ปลอดภัยได้
random จริง ๆ แล้วเป็น wrapper เล็ก ๆ ที่ครอบ rand ของ C
- เพราะ World of Warcraft คอมไพล์ด้วย MSVC การ implement
rand จึงเป็น generator ที่อิงสถานะ global
uint32_t state;
int rand() {
state = state * 214013 + 2531011;
return (state >> 16) & 0x7fff;
}
- implementation นี้แชร์สถานะเลขสุ่ม global เดียวกันทั้ง process และเป็น linear congruential generator ที่อ่อนแอ
การกู้สถานะของ MSVC rand
- เอาต์พุตของ
rand เปิดเผย 15 บิตซึ่งเป็นส่วนหนึ่งของสถานะโดยตรง
- เพราะ state space เล็กและเห็นบางส่วนของเอาต์พุต จึงสามารถรักษารายการสถานะ candidate ที่เป็นไปได้ไว้ แล้วใช้เอาต์พุตเพิ่มเติมตัด candidate ออกได้
- วิธีที่มีประสิทธิภาพกว่าคือสังเกตเอาต์พุต RNG สองครั้งคือ
r0, r1 แล้วคำนวณ candidate ของส่วนสถานะ 16 บิตที่ซ่อนอยู่
- ค่าคงที่ของ MSVC
rand มีดังนี้
- หากให้ส่วน 16 บิตที่ซ่อนอยู่หลังการเรียกครั้งแรกและครั้งที่สองเป็น
h0, h1 ตามลำดับ สามารถอนุมาน candidate ของ h0 ได้จากสมการเปลี่ยนสถานะ
- ผลการคำนวณทำให้ต้องพิจารณา
k เพียงช่วง -1 <= k < 7 ดังนั้น candidate ของสถานะที่ซ่อนอยู่จึงลดลงเหลือมากสุด 8 รายการ
- จากนั้นเปรียบเทียบผลลัพธ์จากการเรียก RNG เพิ่มเติมกับเอาต์พุตของ RNG candidate เพื่อตัด candidate ที่ผิดออก
- ในตัวอย่าง implementation ด้วย Python ใช้ลูป
while แต่ในการใช้งานจริง ดูเหมือนว่าแค่เอาต์พุตครั้งที่สามก็ทำให้แคบลงเหลือ candidate เดียวได้
- ตอนนั้นยังยากที่จะทำอนุมานทางคณิตศาสตร์ไม่ต่อเนื่องนี้เอง จึงไปถามใน crypto.SE โดยให้เหตุผลว่า “อยากให้เพื่อนร่วมงานเห็นว่า RNG นี้อ่อนแอแค่ไหน”
วิธีเลือกการกระทำที่ต้องการในสภาพแวดล้อมที่ปลอดภัย
- เมื่อกู้สถานะภายในของ RNG ได้แล้ว ก็สามารถส่งการตัดสินใจ automation ใด ๆ เข้าไปในสภาพแวดล้อมที่ปลอดภัยได้
- กระบวนการทำงานเป็นดังนี้
- ลงทะเบียน hook ที่ไม่ปลอดภัย ซึ่งจะรันทันทีก่อนโค้ดในสภาพแวดล้อมที่ปลอดภัยรัน
- ภายใน hook เข้าถึงข้อมูลทั้งหมดได้ จึงตัดสินใจว่าจะทำการกระทำใด
- ค้นหาการกระทำที่ตัดสินใจในรายการการกระทำที่ hardcode ไว้ แล้วแปลงเป็นดัชนี
- กู้สถานะ RNG ปัจจุบันด้วยวิธีก่อนหน้า
- หากเอาต์พุต RNG ถัดไป เมื่อนำมา mod ด้วยความยาวของรายการการกระทำแล้ว ไม่ได้ดัชนีที่ต้องการ ก็เลื่อน RNG ไปข้างหน้าแล้วตรวจสอบอีกครั้ง
- เมื่อถึงรอบที่เอาต์พุตที่ต้องการจะออกมา hook จะ return และสภาพแวดล้อมที่ปลอดภัยจะเรียก
random เพื่อทำการกระทำตามดัชนีนั้น
- ในสภาพแวดล้อมที่ปลอดภัย ดูเหมือนเป็นการใช้ตัวเลขสุ่มเพื่อ index รายการการกระทำ แต่จริง ๆ แล้วจังหวะของการเรียก
random ครั้งถัดไปถูกควบคุมเพื่อให้เลือกการกระทำที่ต้องการ
- หากขนาดรายการการกระทำคือ
n โดยเฉลี่ยต้องเลื่อน RNG ไปข้างหน้า n ขั้นเพื่อส่งค่าที่ต้องการ แต่ในการใช้งานจริงไม่เป็นปัญหา
สถานะหลังจากนั้น
- เมื่อลองอีกครั้งหลังผ่านไปหลายปี วิธีนี้ก็ใช้งานไม่ได้แล้ว
- ไม่อาจทราบได้ว่า Blizzard แก้ปัญหาสถานะ RNG ที่อ่อนแอและแชร์ร่วมกันเมื่อใด หรือมองว่านี่เป็นปัญหาหรือไม่
- ความเปลี่ยนแปลงที่เป็นไปได้คือเปลี่ยนไปใช้อัลกอริทึมอื่น หรือแยกสถานะ RNG อย่างถูกต้องให้กับสภาพแวดล้อมที่ปลอดภัย
- แม้ไม่ใช่โค้ดที่ตั้งใจจะใช้จริง แต่นี่เป็นกรณีแคบ ๆ ของการข้ามระบบความปลอดภัยในเกมที่ทำสำเร็จในการบิดสิ่งที่ดูเหมือนสุ่มให้ได้ผลลัพธ์ตามต้องการ
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
สมัยก่อนที่ผมทำงานเป็น GM ของ WoW มีบั๊กใน Titan Bar แอดออนยอดนิยมบางเวอร์ชัน ที่คอยเรียก API สำหรับเปิด UI “เขียนตั๋วถึง GM” ซ้ำ ๆ
ถึงกดยกเลิกก็เด้งขึ้นมาใหม่ และนอกจากลบแอดออนที่มีปัญหาแล้ว วิธีเดียวที่จะปิดหน้าต่างได้คือพิมพ์อะไรสักอย่างในกล่องข้อความแล้วส่ง
ในมุมของ GM นี่เป็นวันที่ลำบากมากอยู่วันสองวัน และผู้เล่นจำนวนมากไม่รู้ด้วยซ้ำว่า GM คืออะไร หรือรู้ว่าสามารถส่งตั๋วได้
การอธิบายว่า “ลูกค้าน่าจะส่งตั๋วมาเพราะแอดออน ผมซึ่งเป็นฝ่ายซัพพอร์ตลูกค้าจึงติดต่อมา” ก็มักไม่ง่ายนัก
เพราะมันทำให้เวลาตอบกลับตามปกติพังไปโดยปริยาย นักพัฒนาจึงออก hotfix และเอาการเรียก API นั้นไปไว้หลังกลไกป้องกัน ให้เรียกได้เฉพาะจาก โค้ด LUA ของ Blizzard เอง เท่านั้น
เป็นแอดออนที่รวมองค์ประกอบ UI หลายอย่าง เช่น ทองทั้งหมด อัตราส่วนการซ่อมแซม ไว้ในแถบเดียว
แปลกดีที่ผมเคยตอบคำถามที่แทบเหมือนกันเรื่อง “การแก้ย้อนตัวสร้างเลขสุ่ม” บน StackOverflow ไม่กี่เดือนก่อนจะมีคำถามบน Crypto.SE: https://stackoverflow.com/a/15237585/1204143
แต่ตอนนั้นโจมตี ตัวสร้างเลขสุ่มของ Java แทนที่จะเป็นของ MSVC
ทั้งคู่เป็นโครงสร้าง linear congruential generator (LCG) แบบง่าย ๆ ที่เอาค่าสถานะภายในมาตัดบางส่วนก่อนส่งออก วิธีโจมตีก็เลยคล้ายกันมาก
import java.util.Random;
public class Hayley {
public static void main(String... args) {
byte[] b1 = new byte[4], b2 = new byte[2];
(new Random(0x2effe2140e00L)).nextBytes(b1);
(new Random(0xc2f0097)).nextBytes(b2);
System.out.println(new String(b1) + new String(b2));
}
}
ผมสงสัยว่าเป็นไปได้ไหมที่ฝั่งเซิร์ฟเวอร์ก็ใช้ตัวสร้างเลขสุ่มแบบเดียวกัน
ถ้าใช่ ในทางทฤษฎีก็ดูเหมือนจะเอาไปใช้โจมตีได้ เมื่อมีเซิร์ฟเวอร์ที่มีการใช้งานต่ำมาก, latency ต่ำ, ความสามารถในการจำกัดช่วงสถานะปัจจุบันของตัวสร้างเลขสุ่มได้ค่อนข้างแม่นยำ และความสามารถในการทำให้อีเวนต์ที่มีความต่างทางเศรษฐกิจสูงตามผลสุ่มเกิดซ้ำได้อย่างรวดเร็ว
Blizzard เปิดเผยรายละเอียดของวิธีสร้างเลขสุ่มฝั่งเซิร์ฟเวอร์ไว้ค่อนข้างมาก ในความพยายามที่โดยรวมแล้วไม่ค่อยสำเร็จนักเพื่อสยบข่าวลือจากผู้เล่นสายเชื่อโชคลางที่ปักใจว่ามีสิ่งอย่าง “seed ของ loot” อยู่
เศรษฐศาสตร์และแรงจูงใจของตัวสร้างเลขสุ่ม เป็นเรื่องน่าสนใจ
ถ้าไม่มีอะไรเป็นเดิมพันจริง ๆ และผู้คนสังเกตได้ไม่ง่าย ตัวสร้างเลขสุ่มที่ไม่สมบูรณ์หรือถึงขั้นแย่ก็อาจเพียงพอสำหรับเกม
แต่พอมีเงินหรือความปลอดภัยเข้ามาเกี่ยว เช่น กระเป๋าเงินคริปโตหรือคาสิโนออนไลน์ มันก็กลายเป็นเรื่องสำคัญมหาศาลทันที
ที่ร้ายแรงกว่านั้นคือ ตัวสร้างเลขสุ่มแบบ linear congruential ที่ยกมาสร้างตัวเลขที่แตกต่างกันได้เพียง 12,445 ค่าเท่านั้นก่อนจะวนซ้ำ
แค่ใช้พารามิเตอร์ที่ดีกว่าสำหรับอัลกอริทึมนี้ก็น่าจะสมเหตุสมผลกว่านี้มากแล้ว
งานง่าย ๆ อย่างการเคลื่อนที่ของอนุภาคในรอยควัน ต้องการตัวสร้างเลขสุ่มที่เรียบง่ายแต่ เร็ว
ในงานเข้ารหัสหรือเกมคาสิโน ฯลฯ ไม่เพียงแต่ใช้ตัวสร้างเลขสุ่มที่ดีกว่าพร้อมสถานะอย่างน้อย 32 ไบต์เท่านั้น แต่ยังทำให้เอนจินเดินหน้าต่อไปแบบสุ่มช่วงเวลาเพิ่มเติม เพื่อขัดขวางการทำนายผลให้มากขึ้นด้วย
World of Warcraft เป็นจุดเริ่มต้นที่ทำให้ผมสนใจการเขียนโปรแกรม
ตอนเกมออกผมอายุ 6 ขวบ และพอเซิร์ฟเวอร์ส่วนตัวเริ่มเกิดขึ้น ผมก็สงสัยว่าจะสร้าง NPC เองได้อย่างไร จึงเรียน Lua
กระทู้ในฟอรัม MMOwned ที่ผมเคยเข้าไปเล่นตอนนั้นยังเหลืออยู่เยอะ และหนึ่งในคนที่ช่วยให้ผมเรียนรู้ตอนนั้นก็ยังโพสต์เป็นครั้งคราว
การคอมไพล์คอร์ของเซิร์ฟเวอร์ส่วนตัวเองทำให้ผมได้เรียนเรื่องการคอมไพล์, SVN และการ apply patch และเหมือนจะมีบั๊กเกี่ยวกับ Fireball ของ Mage อยู่ แต่จำรายละเอียดแน่ชัดไม่ได้
ทุกวันนี้รู้สึกว่าส่วนดี ๆ จำนวนมากของอินเทอร์เน็ตถูกแทนที่ด้วยแอปโซเชียลมีเดียที่กระตุ้นโดพามีน และ ฟอรัมแบบปิด อย่าง Reddit
เหตุผลที่ผมยังอยู่บน HN ก็คงเพราะมันทำให้นึกถึงฟอรัมสมัยก่อน
“ใครก็ตามที่พยายามสร้างตัวเลขสุ่มด้วยวิธีทางคณิตศาสตร์ ย่อมอยู่ในสภาวะแห่งบาป ดังที่มีคนชี้ให้เห็นหลายครั้งแล้วว่า ไม่มีสิ่งที่เรียกว่าตัวเลขสุ่ม มีเพียงวิธีสร้างตัวเลขสุ่มเท่านั้น และกระบวนการทางคณิตศาสตร์ที่เคร่งครัดย่อมไม่ใช่วิธีเช่นนั้นแน่นอน” — John von Neumann
ไม่ได้มีปัญหากับ Neumann แต่คนที่เอามันไปเผยแพร่เหมือนเป็นสุภาษิตอันชาญฉลาด โดยมากแล้วถ่ายทอดแต่ข้อมูลผิด ๆ
ในบริบทเดิมมันใกล้เคียงกับมุกเชิงปรัชญาที่ไม่จำเป็นมากกว่า
เรามี ตัวสร้างเลขสุ่มเชิงเข้ารหัส ที่เร็วและเชื่อถือได้มาหลายสิบปีแล้ว
แค่ seed ขนาดเล็กมากกับการคำนวณทางคณิตศาสตร์ก็ทำงานได้ถูกต้อง และตัวเลขที่สร้างขึ้นก็แยกไม่ออกจากแหล่งสุ่มที่ถูกต้องอื่น ๆ และใช้ได้กับทุกวัตถุประสงค์
มีคนจำนวนมากเกินไปที่ไม่เข้าใจเรื่องนี้ จนถึงขั้นมีบริษัทขายน้ำมันงูในชื่อ “เลขสุ่มแท้” ด้วย
ตัวอย่างดี ๆ ของตัวสร้างเลขสุ่มที่เลวร้ายจริง ๆ อยู่ใน Monster Hunter 3 Ultimate และอาจมีในภาคอื่นด้วย
ไม่เพียงแต่ถูกใช้หาประโยชน์เพื่อให้ได้ loot ดี ๆ ได้อย่างสมบูรณ์ แต่ยังทำให้เกิด “charm table” ด้วย
ไอเทมบางอย่างมีค่าสเตตัสที่สร้างแบบสุ่ม แต่ seed เริ่มต้นถูกบันทึกไว้ในไฟล์เซฟ ทำให้ช่วงของไอเทมที่หาได้ถูกจำกัด
ถ้าโชคร้าย คุณอาจติดอยู่ใน “ตารางต้องคำสาป” ซึ่งโดยพื้นฐานแล้วเป็นตัวสร้างเลขสุ่มที่มีคาบสั้นมาก จนไม่มีวันได้ค่าสูงสุด
ไม่ถึงกับทำลายเกม แต่ไม่ดีต่อการเล่นระดับสูง
ผมเขียนสคริปต์สำหรับ Auction House
มันสแกนการประมูลทั้งหมด ซื้อไอเทมที่ตั้งราคาต่ำกว่ามูลค่า แล้วนำไปลงขายใหม่
ถ้ามีคนลงขายถูกกว่า ก็ยกเลิกแล้วลงใหม่ให้ต่ำกว่านั้นเล็กน้อย ทำให้มี ทองแบบไม่จำกัด โดยปริยาย
“อืม จริง ๆ ก็เป็นการเปรียบเทียบที่ดีทีเดียว ต่างกันตรงที่… อืม… เป็นการเปรียบเทียบที่ดีทีเดียวแหละ!”
“เมื่อตัวแปรไม่เปลี่ยน และค่าคงที่ไม่คงที่”
https://www.theregister.com/2006/07/26/constants_are_not/
“ผมอ่านคำคร่ำครวญถึงความแปรปรวนของการเขียนโปรแกรมในชื่อบทความนี้ ‘Variables Won't Constants Aren't’ ครั้งแรกใน Creative Computing หลายปีก่อนที่ C จะถูกสร้างขึ้น”
ถ้ามีเอกสารเฉพาะสักชิ้นที่คุณรู้สึกว่าเป็นฉบับมาตรฐานของมีมนี้ ผมก็อยากอ่าน