วิธีเอาชนะ PoW ของ kernelCTF ของ Google ด้วย AVX512
(anemato.de)- ทีม Crusaders of Rust พยายามส่งเอ็กซ์พลอยต์ของ CVE-2025-38001 ใน Linux packet scheduler แต่เพราะโครงสร้างแบบมาก่อนได้ก่อนของ kernelCTF จุดตัดสินจึงกลายเป็นการ ลดเวลา PoW มากกว่าตัวบั๊กเอง
- กระบวนการส่งประกอบด้วยการเชื่อมต่อเวลา 12:00 UTC, ทำ PoW ราว 4 วินาที, บูต VM ราว 2.5 วินาที, รันเอ็กซ์พลอยต์ และส่ง Google Form โดยสถิติการส่ง 4.5 วินาทีจากรอบก่อนหน้าเผยให้เห็นคอขวดนี้
- PoW เป้าหมายคือ VDF ชื่อ “sloth” ซึ่งเป็นการคำนวณแบบอนุกรมด้วยการทำ modular square ซ้ำภายใต้
2^1279 - 1สำหรับจำนวนเต็ม 1280 บิต จึงลดเวลาได้ยากด้วยการเพิ่มจำนวนคอร์ CPU/GPU - หลังลดเวลาลงเหลือ 1.9 วินาทีด้วย GMP/C++ และการลดรูปแบบ Mersenne แล้วต่อด้วยการลิงก์แบบสแตติกพร้อม
-march=nativeจนเหลือราว 1.4 วินาที ทีมได้ใช้ fused multiply-add ของจำนวนเต็ม 52 บิตใน AVX512IFMA เพื่อลดเวลาบน Ryzen 9950X ลงเหลือราว 0.21 วินาที - วันที่ 16 พฤษภาคม 2025 ทีมส่งแฟลกได้ในเวลาเพียง 3.6 วินาที ด้วยเซิร์ฟเวอร์ Google Cloud ที่ใช้ Zen 5 และเส้นทางส่ง POST ที่ปรับแต่งแล้ว และวันที่ 28 พฤษภาคม kernelCTF ก็ประกาศยกเลิก PoW
การแข่งกับเวลา: ปัญหาไม่ใช่บั๊ก แต่คือส่งให้เร็วกว่า
- ในเดือนพฤษภาคม 2025 William Liu และ Savy Dicanosa จากทีม Crusaders of Rust ค้นพบ use-after-free bug CVE-2025-38001 ใน Linux packet scheduler และพัฒนาเอ็กซ์พลอยต์ขึ้นมา
- William พบบั๊กระหว่างทำ fuzzing กับ Linux สำหรับวิทยานิพนธ์ปริญญาโท
- Savy ลดเวลารันเอ็กซ์พลอยต์ลงเหลือราว 0.55 วินาที
- kernelCTF ของ Google เปิดหน้าต่างการส่งทุก 2 สัปดาห์ตอนเที่ยง UTC และให้รางวัลเฉพาะทีมแรกที่เจาะเซิร์ฟเวอร์สำเร็จแล้วส่งแฟลกผ่าน Google Form
- ลำดับการส่งเป็นแบบเดิมเสมอ
- เชื่อมต่อกับเซิร์ฟเวอร์ kernelCTF ตอน 12:00:00 UTC
- แก้ proof of work ใช้เวลาราว 4 วินาที
- รอบูตอินสแตนซ์ ใช้เวลาราว 2.5 วินาที
- อัปโหลดและรันเอ็กซ์พลอยต์
- ส่งแฟลกผ่าน Google Form
- เงินรางวัลที่คาดไว้คือ $51,000
- รางวัลพื้นฐาน $21,337
- รางวัลด้านความเสถียรในการรัน $10,000
- รางวัลสำหรับบั๊ก 0-day $20,000
สถิติการส่งก่อนหน้าเผยให้เห็นคอขวดของ PoW
- ในหน้าต่างการส่งวันที่ 2 พฤษภาคม 2025 การส่งครั้งแรกเข้ามาที่ 4.5 วินาที หลังเที่ยง
- แค่ PoW ราว 4 วินาทีรวมกับการบูต VM ราว 2.5 วินาทีก็ต้องใช้ 6.5 วินาทีแล้ว ดังนั้นสถิติ 4.5 วินาทีจึงไม่สอดคล้องกับการคำนวณแบบตรงไปตรงมา
- เพราะลักษณะการปัดเศษในโค้ดฝั่งเซิร์ฟเวอร์ของ kernelCTF อินสแตนซ์ VM จึงถูกบูตจริงตั้งแต่ 11:59:59 ทำให้ความขัดแย้งด้านเวลาหายไป
- ถึงอย่างนั้น timestamp ที่ใช้สร้างแฟลกก็แสดงว่าทีมผู้ชนะก่อนหน้านี้แก้ PoW ได้ในเวลา ต่ำกว่า 1 วินาที
- หากทีมคู่แข่งใช้ FPGA ก็อาจทำ PoW ต่ำกว่า 1 วินาทีได้
- FPGA คือฮาร์ดแวร์แบบกำหนดเองที่ทำงานเฉพาะทางบางอย่างได้เร็วมาก
- แต่ไม่เหมาะกับงานทั่วไป และมีทั้งราคาสูงกับความยากในการเขียนโปรแกรม
sloth VDF: PoW ที่ขนานงานได้ไม่ดี
- PoW ของ kernelCTF คือ verifiable delay function (VDF) ชื่อ “sloth”
- VDF เป็น primitive ทางคริปโตที่ใช้พิสูจน์ว่ามีเวลาผ่านไปจริงด้วยการคำนวณแบบอนุกรมยาว ๆ ขณะที่หลักฐานผลลัพธ์ตรวจสอบได้ค่อนข้างเร็ว
- เพราะตัวการคำนวณเป็นแบบอนุกรม จึงยากที่จะลดเวลารันด้วยการใส่คอร์ CPU หรือ GPU เพิ่ม
- ลูปหลักที่เป็นเป้าหมายของการปรับแต่งมีโครงสร้างดังนี้
difficulty=7337- ในแต่ละรอบของ difficulty จะทำ
x = (x * x) % (2 ** 1279 - 1)จำนวน 1277 ครั้ง - จากนั้นสลับบิตต่ำสุดของ
x
- implementation อ้างอิงของ Google ใช้ gmpy ใน Python ซึ่งเป็น Python binding ของ GMP
- GMP คือไลบรารีจำนวนเต็มหลายความแม่นยำที่ implement เคอร์เนลการบวกและคูณแบบแอสเซมบลีแยกตามแพลตฟอร์ม
การปรับแต่งรอบแรกบนพื้นฐาน GMP
- การปรับแต่งแรกอาศัยข้อเท็จจริงว่า
2^1279 - 1เป็น จำนวน Mersenne- แยกผลคูณกลาง 2560 บิตออกเป็น 1279 บิตล่างกับบิตบน แล้วนำมาบวกกัน
- หากผลลัพธ์ยังมากกว่าหรือเท่ากับ modulus ก็ลบออกหนึ่งครั้ง เพื่อแทนการทำ
%
- เพื่อลด overhead จาก Python FFI จึงย้ายไปเขียนใน C++ และเวอร์ชันนี้รันได้ในเวลา 1.9 วินาที บน M1 MacBook Pro
- William คอมไพล์ libgmp แบบโลคัลด้วย
-march=nativeและลิงก์แบบสแตติก จนลดเหลือราว 1.4 วินาที บนโน้ตบุ๊ก Intel Ice Lake - ตัวแก้ที่ปรับแต่งคล้ายกันซึ่งเขียนด้วย Rust ก็ใช้เทคนิค Mersenne เดียวกัน แต่ใช้เวลาราว 2.4 วินาที
- หลังจากนั้นยังลอง FLINT ด้วย แต่ความเร็วแทบไม่ต่างจาก GMP
เขียนการยกกำลังสองของจำนวนเต็มขนาดใหญ่ใหม่ด้วย AVX512IFMA
- AVX512 คือส่วนขยาย ISA ของ Intel x86 ที่เพิ่มทั้งจำนวนและความกว้างของเวกเตอร์รีจิสเตอร์ รวมถึงเพิ่ม mask predication และคำสั่งใหม่หลายรายการ
- Intel ปิดการรองรับ AVX512 บน CPU ฝั่งไคลเอนต์ตั้งแต่ Alder Lake เป็นต้นมา
- แต่ฝั่งเซิร์ฟเวอร์ยังรองรับต่อ และ AMD ก็ implement AVX512 ทั้งใน CPU ผู้ใช้ทั่วไปและเซิร์ฟเวอร์ของ Zen 4 กับ Zen 5
- หัวใจสำคัญคือ AVX512IFMA
vpmadd52luq: นำครึ่งล่างของผลคูณ 52 บิตไปบวกกับตัวสะสม 64 บิตvpmadd52huq: นำครึ่งบนของผลคูณ 52 บิตไปบวกกับตัวสะสม 64 บิต
- คำสั่งเหล่านี้คำนวณส่วนล่างและส่วนบนของผลคูณ 52×52→104 บิต แล้วสะสมไว้ในเวกเตอร์รีจิสเตอร์
- Zen 5 มี data path แบบ 512 บิต จึงเริ่มคำสั่งเหล่านี้ได้ 2 คำสั่งต่อ clock
- ฐานเลขที่เป็นธรรมชาติคือ
2^52และจำนวนเต็ม 1280 บิตจึงแทนได้ด้วย 25 limb ขนาด 52 บิต- zmm register ขนาด 512 บิตหนึ่งตัวเก็บได้ 8 limb
- ค่าทั้งหมดจึงอยู่ใน zmm register 4 ตัว
การจัดวางการคูณและการลดรูปแบบ Mersenne
- การยกกำลังสอง 1280 บิตถูก implement โดยนำ limb ขนาด 52 บิตทั้ง 25 ตัวมายกกำลังสองจนได้ผลลัพธ์กลาง 50 limb
- ใช้สมมาตรของการยกกำลังสองเพื่อลดจำนวนการคูณที่ต้องทำลงเกือบครึ่ง
- สมาชิกแนวทแยง
ai^2 - cross term
2 * ai * ajเมื่อi < j
- สมาชิกแนวทแยง
- ระหว่างคำนวณ cross term จะใช้ sliding window ของ limb ต่อเนื่อง 8 ตัวคูณกับ multiplier limb เดี่ยวเพื่อลดการ shuffle
- ใช้ merge masking ของ AVX512 เพื่อไม่สะสมผลคูณที่ไม่ถูกรวมอยู่ในผลบวกสุดท้าย
- การลดโมดูลาร์ทำโดยนำ 1279 บิตบนมาบวกกับ 1279 บิตล่าง
- เพราะสมาชิกในตัวสะสมอาจเกิน
2^52 - 1ได้ จึงเลื่อนการ propagate carry ไปไว้หลังการบวก - การตัดสินว่าผลลัพธ์มากกว่าหรือเท่ากับ
2^1279 - 1หรือไม่ ทำได้โดยดูว่าบิตที่ 1280 เป็น 1 หรือไม่ - การลบ
2^1279 - 1เทียบเท่ากับการล้างบิตที่ 1280 แล้วบวก 1 ให้ limb ต่ำสุด
- เพราะสมาชิกในตัวสะสมอาจเกิน
- ในขั้นตอนสุดท้ายยังมีโอกาส overflow เล็กมากเหลืออยู่
- หาก limb สุดท้ายมีค่าเท่ากับ
2^52 - 1พอดี ก็ต้องมีการ propagate carry - สำหรับ PoW แบบสุ่ม ความน่าจะเป็นนี้ถูกประเมินว่าเกิดราว 2 ครั้งต่อ 2 พันล้านครั้งของการรัน จึงถูกละไว้
- หาก limb สุดท้ายมีค่าเท่ากับ
การปรับจูนย่อยจาก 0.45 วินาทีลงสู่ 0.21 วินาที
- เวอร์ชัน AVX512IFMA แรกทำ PoW ได้ในเวลาราว 0.45 วินาทีบน Ryzen 9950X ที่เช่ามา
- คำสั่ง multiply-add มี latency 4 cycle และเริ่มได้ 2 คำสั่งต่อ clock ดังนั้นต้องมีตัวสะสมอย่างน้อย 8 ตัวเพื่อให้หน่วยคูณทำงานเต็มกำลัง
- เดิมมีตัวสะสมเพียง 7 ตัว
- จึงเปลี่ยนเป็นใช้ตัวสะสม 14 ตัว แยกเป็น 7 ตัวสำหรับครึ่งล่างและ 7 ตัวสำหรับครึ่งบน แล้วค่อยรวมกันตอนท้าย
- การเปลี่ยนนี้ลดเวลาลงเหลือราว 0.32 วินาที
- GCC และ clang สร้าง
vbroadcastsd zmm, m64ระหว่างการ unroll ลูป และในขั้นตอนจัดสรรรีจิสเตอร์ก็เกิด stack spill กับ reload เพราะเวกเตอร์รีจิสเตอร์ไม่พอ- จึงใช้ inline assembly เพื่อบังคับให้
vpmadd52luq/vpmadd52huqใช้ memory broadcast operand - แทนที่จะเก็บ multiplier limb ไว้ในเวกเตอร์รีจิสเตอร์แยก ก็ดึงจากหน่วยความจำแล้วกระจายซ้ำไปยังองค์ประกอบเวกเตอร์ทั้งหมด
- broadcast load นี้ไม่ใช้ทรัพยากรเวกเตอร์ ALU แต่ให้ load unit จัดการแทน
- ขั้นตอนนี้ลดเวลาลงเหลือราว 0.23 วินาที
- จึงใช้ inline assembly เพื่อบังคับให้
- การเก็บจำนวนเต็มแบบจัดแนวลงหน่วยความจำแล้วใช้ unaligned load เพื่อสร้าง window ทำให้เกิด store-forwarding stall
- จึงใช้
valignqเพื่อเลียนแบบ unaligned load ภายใน zmm register และลดการเข้าถึงหน่วยความจำ - เวลาสุดท้ายของ PoW จึงอยู่ที่ราว 0.21 วินาที
- จึงใช้
ผลการส่งเมื่อ 16 พฤษภาคม 2025
- ทีมเตรียมการส่งสุดท้ายในเวลา 4:30 PST ของวันที่ 16 พฤษภาคม 2025
- ใช้ เซิร์ฟเวอร์ Google Cloud ที่เป็น Zen 5 ในเนเธอร์แลนด์ซึ่งอยู่ใกล้กับเซิร์ฟเวอร์รับ Google Form ทางภูมิศาสตร์ เพื่อลด latency
- ไม่กี่นาทีก่อนส่ง ทีมดักและบันทึกคำขอ POST ของ Google Form ไว้ด้วยแฟลกจำลอง
- Bryce Casaje และ Larry Yuan ออกแบบและปรับแต่งโปรแกรมสำหรับการส่งฟอร์ม
- Max Cai ก็ช่วยทั้งด้านพัฒนาและการส่ง
- เวลา 5:00 เซิร์ฟเวอร์เชื่อมต่อกับเซิร์ฟเวอร์ kernelCTF, แก้ PoW, รันเอ็กซ์พลอยต์ที่ Savy ปรับแต่งไว้ แล้วใส่แฟลกลงในคำขอ POST เพื่อส่งออกไป
- ผลลัพธ์คือการส่งในเวลา 3.6 วินาที ซึ่งในตอนนั้นเป็นสถิติเร็วที่สุดในประวัติศาสตร์ของ kernelCTF
- ผู้ดูแล kernelCTF ยืนยันสิทธิ์รับเงินรางวัลในวันเดียวกัน
การยกเลิก PoW และการเปิดเผยตัวแก้ขั้นสุดท้าย
- วันที่ 28 พฤษภาคม 2025 ผู้ดูแล kernelCTF ชื่อ koczkatamas ประกาศ ยกเลิก PoW
- เมื่อ PoW หายไป การแข่งขันแย่ง slot ก็เปลี่ยนไปเน้นเวลาในการรันเอ็กซ์พลอยต์และ latency ของเครือข่ายแทน
- การเปลี่ยนแปลงนี้ทำให้สามารถแข่งขันกับทีมมืออาชีพได้ในเงื่อนไขเดียวกัน แม้ไม่มี FPGA หรือความรู้การปรับแต่งด้วย inline assembly
- โค้ดตัวแก้สุดท้ายเป็นผลลัพธ์จากการทำงานราว 12 ชั่วโมงในช่วงวันที่ 14–15 พฤษภาคม 2025 และเผยแพร่ภายใต้ GNU AGPL 3.0
- ตัวอย่างการ build คือ
gcc main.c -O3 -march=znver5 -masm=intel -lgmp
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เนื้อหายอดเยี่ยมมาก วิธีนี้คล้ายกับการทำ RSA ที่ปรับแต่งด้วย AVX-512 อย่างมาก เพราะ RSA ก็ต้องคำนวณเลขยกกำลังขนาดใหญ่มากเช่นกัน
บทความนี้[1] พูดถึงว่า RSA ทำ windowing อย่างไร และมีสูตรที่แสดงว่าขนาดหน้าต่างสามารถเป็นค่าใดก็ได้ การทำ RSA ด้วย AVX-512 ยังเก็บผลคูณในช่วง [0..2^{window-size}) ไว้ในตารางเพิ่มเติม แล้วสำหรับแต่ละหน้าต่างก็ดึงผลนั้นออกมาจากตาราง[2] จากนั้นทำแค่ shift/จัดเรียงใหม่
https://dpitt.me/files/sime.pdf (นำมาจากวารสาร จึงโฮสต์ไว้ที่โดเมนของผม)
https://github.com/aws/aws-lc/blob/9c8bd6d7b8adccdd8af4242e0...
อีกอย่างคือกำลังย้าย mask register ไปเป็น general-purpose register เพื่อใช้ในการคำนวณเลขคณิต ซึ่งไม่เหมาะที่สุดบน Zen 4/5 แยกจากนั้นก็สงสัยด้วยว่าจำเป็นต้อง propagate carry ทีเดียวจริง ๆ หรือไม่ ในโค้ดของผม ผมสมมติว่า carry เกิดขึ้นแค่ครั้งเดียว แล้วถ้าจำเป็นค่อยวนลูปกลับไป เพื่อลด latency ในกรณีทั่วไป แต่ถ้ามี branch ก็อาจมีปัญหา timing attack ได้
dpitt.me/files/sime.pdfสามารถอัปโหลดไว้บน archive.org ได้ด้วย: https://archive.org/download/sime_20250531/sime.pdfส่วนที่ว่า “แม้ [AVX512] จะรองรับบน CPU ผู้บริโภคมาหลายรุ่นแล้ว” ฟังดูแปลกอยู่หน่อย
ก่อน Rocket Lake (Gen 11) AVX-512 มีเฉพาะใน CPU ระดับ enthusiast, Xeon CPU และโปรเซสเซอร์มือถือบางรุ่นเท่านั้น และจะเรียกโปรเซสเซอร์มือถือว่า CPU ผู้บริโภคก็ยังไม่ค่อยชัดเจน ใน Gen 12 มันถูกปิดใช้งานในคอร์เหล่านั้นหลังจากนั้นไม่กี่เดือนเพราะโครงสร้างคอร์ประสิทธิภาพ/คอร์ประหยัดพลังงาน แล้วก็ไม่กลับมาให้เห็นอีก ถึงอย่างนั้นถ้า AMD ประสบความสำเร็จกับ AVX-512 ได้ในระดับหนึ่ง ผมคิดว่า Intel ก็น่าจะนำกลับมาใช้ใหม่ อนึ่ง ผมยังใช้ Intel i9-11900 อยู่
แนวโน้มนี้น่าจะถูกต้อง เอกสาร AVX10 white paper[1] ฉบับอัปเดตของ Intel เมื่อไม่กี่เดือนก่อนก็ดูเหมือนจะยืนยันเรื่องนี้ ระบุไว้ว่า AVX 512 บิตจะกลายเป็นมาตรฐานทั้งบน P-core และ E-core และจะเลิกจำกัดอยู่กับคอนฟิกแบบ 256 บิตเท่านั้น
นี่ดูเป็นสัญญาณชัดเจนว่า AVX-512 จะกลับมาอย่างจริงจัง ไม่ใช่แค่ในเซิร์ฟเวอร์ แต่รวมถึง CPU ผู้บริโภคในอนาคตที่มี E-core ด้วย อาจเป็นการไล่ตามการนำ AVX-512 มาใช้ในวงกว้างขึ้นของ AMD
[1] - https://cdrdv2.intel.com/v1/dl/getContent/784343 (PDF)
CPU ที่มีคอร์ประสิทธิภาพของ Gen 12 นั้นตั้งแต่แรกก็ไม่ได้แสดงว่า รองรับ AVX512 และไม่ได้เปิดใช้เป็นค่าเริ่มต้น
เพราะ E-core ไม่มี AVX512 ด้วยเหตุผลด้านพื้นที่ชิป CPU ทั้งตัวจึงถูกถือว่าไม่รองรับ AVX512 แค่สามารถอาศัยพฤติกรรมแปลก ๆ ของตัวเลือก BIOS บางตัว ปิด E-core แล้วเปิด AVX512 บน CPU ส่วนที่เหลือได้เท่านั้น และต้องแลกกับการเสีย E-core ไป
สถิติชนะคือ 3.6 วินาที แต่ที่สองคือ 3.73 วินาที และถ้าปัดให้มีจำนวนหลักเท่ากับสถิติชนะก็เป็น 3.74 วินาที ถ้าอย่างนั้นที่สองก็น่าจะได้ปรับแต่ง proof of work หรือใช้ FPGA ด้วยหรือเปล่า?
ผู้เขียนอธิบายว่าการส่งก่อนหน้านี้เป็นแบบ FPGA ราคาแพง แต่ก็ยังใช้มากกว่า 4 วินาที ถ้าอย่างนั้นอันดับสองของสัปดาห์นั้นอาจเป็นการส่งที่เร็วเป็นอันดับสองตลอดกาลก็ได้ น่าจะมีการพูดถึงอะไรเกี่ยวกับเรื่องนั้นบ้างไม่ใช่หรือ
น่าประทับใจ แต่ดูเหมือนกำลังปรับแต่งผิดเป้าหมาย CTF ไม่ควรกลายเป็นศึกการปฏิบัติการส่งคำตอบ
ผมคิดว่ามันน่าจะดีกว่าสำหรับทุกคน ถ้าทีมทั้งหมดที่ส่ง flag ได้ภายในช่วงเวลาส่งคำตอบแบ่งรางวัลกัน
ดังนั้นในทางปฏิบัติ มันอาจถึงขั้นส่งเสริมพฤติกรรมที่ “ผิด” อย่างจริงจัง
ถ้าผมเข้าใจถูก มี proof of work 4 วินาที และรางวัลจ่ายเดือนละครั้ง
มี exploit มากขนาดที่คนจะแข่งกันทุกเดือนจริงหรือ?
CTF สาธารณะเป็นเรื่องยาก สุดท้ายบางทีมก็จะมีพฤติกรรมคล้าย DDoS ระหว่างวิ่งเข้าเส้นชัย ต่อมา Google เอาขั้นตอน proof of work ออกไปแล้ว
เนื้อหาสุดยอด แต่ถ้าดูอุปสรรคที่ต้องข้ามเพื่อชนะ challenge นี้แล้ว อ่านเหมือนตลกเลย เหมือน เครื่องรูบ โกลด์เบิร์ก ของจริง
ถ้าอยากรู้เพิ่มเติมเกี่ยวกับ การแทนค่าในฐาน 52 ที่กล่าวถึงในบทความนี้ บทความอื่นบนหน้าแรกวันนี้ก็น่าอ่าน: https://news.ycombinator.com/item?id=44132673
ขอท้วงเล็กน้อยว่า static linking ไม่ได้ทำให้เกิด inlining แต่แค่ตัด overhead ของ PLT ออก สิ่งที่เพิ่มโอกาสในการ inline คือ LTO
ไม่เข้าใจว่าทำไมต้องให้แข่งขันกัน แค่ให้รางวัลต่อ exploit ที่ไม่ซ้ำกัน แต่ละรายการไม่ได้หรือ?
และ Linux มีบั๊กเยอะเกินไป ถ้าเริ่มจ่ายเงินให้ทุก 0-day ก็จะควบคุมไม่ได้ Google เองครั้งหนึ่งเคยจัดโปรโมชันจำกัดเวลาแบบไม่มีการแข่งขัน เพื่อให้คนปล่อยบั๊กที่สะสมไว้ และพอยอมรับทุก 0-day การส่งก็ถล่มเข้ามา ขณะเดียวกันก็ไม่อยากทำให้ชุมชนโกรธ จึงกลายเป็นโครงสร้างแบบนี้
ผ่านมาขนาดนี้แล้ว แต่ผู้เชี่ยวชาญยังสามารถยึดเครื่อง Linux ได้ภายใน 3 วินาที นี่ก็น่าหดหู่อยู่เหมือนกัน