3 điểm bởi GN⁺ 2025-01-06 | 1 bình luận | Chia sẻ qua WhatsApp
  • Bằng cách đảo ngược chip SWL01U trong synthesizer Yamaha PSR-E433 cũ, tác giả đã ghi mã vào RAM chỉ bằng thông điệp USB-MIDI SysEx và phát video Bad Apple trên LCD
  • Thử nghiệm với JTAG IDCODE 0x3f0f0f0f cùng OpenOCD/GDB xác nhận chip hoạt động như lõi ARM7TDMI, đồng thời dump được ROM nội bộ 64KiB và firmware flash ngoài 16MiB
  • Trong firmware có một shell ẩn chạy trên MIDI SysEx; sau login và mật khẩu #0000 có thể dùng các lệnh đọc/ghi bộ nhớ
  • Sau khi tiêm mã ARM vào RAM bằng lệnh ghi bộ nhớ tùy ý và ghi đè địa chỉ trả về trên stack, việc thực thi mã chỉ bằng phát file MIDI trở nên khả thi mà không cần JTAG hay UART
  • Đầu ra LCD được cải thiện qua điều khiển CGRAM, sao chép bảng task, vô hiệu hóa task hiển thị và thay callback của shell, giảm lượng truyền mỗi khung hình từ 6732 byte xuống 92 byte

Khảo sát bên trong Yamaha PSR-E433

  • Thiết bị mục tiêu là một synthesizer Yamaha PSR-E433 đã dùng lâu năm; trên main board có hai chip flash, chip RAM và chip YAMAHA SWL01U cùng ký hiệu DMLCD
  • Hầu như không có thông tin công khai về SWL01U; chỉ có một bài viết tìm thấy trên mạng nhắc rằng nó có thể dựa trên lõi CPU SuperH
  • Trong service manual của mẫu tương tự E443 có pinout SWL01U, hiển thị TESTN, PROTN, hai UART hai chiều và các test point JTAG
  • Hướng tiếp cận ban đầu có bốn nhánh
    • thao tác các chân TESTN, PROTN để kiểm tra thay đổi chế độ khởi động
    • hàn vào chân UART Tx để kiểm tra đầu ra
    • đọc mã nhận dạng chip bằng JTAG
    • tháo chip flash để dump firmware
  • Khi bật TESTN, synthesizer không khởi động; còn PROTN không làm thay đổi hoạt động
  • Tác giả hàn trực tiếp vào chân UART Tx không dùng đến, nhưng không có đầu ra ở cả bốn tổ hợp TESTN/PROTN

Hành vi ARM7TDMI lộ ra qua JTAG

  • JTAG cần mô tả mạch chi tiết tùy theo cách hiện thực của từng hãng, nhưng đầu tiên tác giả thử đọc IDCODE bằng OpenOCD vì gần như mọi thiết bị đều hỗ trợ
  • OpenOCD báo IDCODE là 0x3f0f0f0f; giá trị này có vẻ liên hệ với các vi điều khiển ARM7 như dòng STMicroelectronics STR7xxx hoặc Atmel SAM7xxx
  • Khi đặt SWL01U làm target arm7tdmi trong OpenOCD thì kết nối hoạt động bình thường, và phần cứng được báo có 2 breakpoint/watchpoint unit
  • Khi dừng và tiếp tục thực thi bằng GDB, dòng điện của board thay đổi theo cách có thể dự đoán
    • đang chạy khoảng 115mA
    • đang tạm dừng khoảng 98mA
  • Sự thay đổi dòng điện này là dấu hiệu mạnh cho thấy lõi ARM7TDMI thật sự đang bị dừng và tiếp tục

Dump ROM và firmware flash

  • Theo tài liệu ARM7TDMI, reset vector nằm ở địa chỉ 0; khi đọc địa chỉ 0 bằng GDB, tác giả thấy một lệnh nhảy dạng ldr pc, [pc, #24]
  • Tác giả dump 16MiB từ địa chỉ 0 rồi mở bằng Cutter, nhưng chuỗi lặp lại mỗi 64KiB
    • ví dụ: SWL01U Internal lặp tại 0x0000bfd0, 0x0001bfd0, 0x0002bfd0...
  • Từ mẫu lặp và các chuỗi, tác giả kết luận đây không phải flash ngoài mà là bộ nhớ bên trong chip, và SWL01U có ROM 64KiB
  • Đích nhảy của reset vector là 0x02000000; khi dump tiếp 16MiB từ địa chỉ này thì không còn lặp nữa
  • Bản dump flash ngoài chứa các chuỗi có thể thấy khi dùng synthesizer
    • GrandPno
    • Tr1 will be OverWritten!
    • BogiWogi
  • Bố cục bộ nhớ xác nhận được như sau
    • ROM nội bộ: 0x00000000, 64KiB
    • flash ngoài: 0x02000000, 16MiB
    • khi khởi động, ROM lập tức chuyển điều khiển sang flash ngoài

Shell ẩn tìm được bằng Ghidra

  • Cutter không đủ cho việc phân tích nên tác giả chuyển sang Ghidra, lần theo chuỗi và xref để hiểu cấu trúc firmware
  • Các chuỗi như help, ?, info, ver tập trung ở các địa chỉ gần nhau, và mỗi chuỗi được nối với một mảng trông như cặp tên lệnh và con trỏ hàm
  • Hàm xử lý lệnh có dạng state machine; từ chuỗi loginPasswd Error, tác giả xác nhận đây là một shell có quy trình đăng nhập
  • Xử lý đầu vào của shell duyệt qua bộ đệm vòng 256 byte và xử lý theo từng ký tự; khi gặp ký tự \r thì thực thi lệnh
  • Luồng đăng nhập như sau
    • nhập login thì in ra passwd?
    • nhập mật khẩu #0000 thì nhận login OK
    • sau đó có thể thực thi lệnh
  • Các lệnh shell đã xác nhận gồm
    • logout, help, ?, info, ver
    • stack, perf-on, perf-off, perf-disp
    • d, dp, d xxxxx, d/s xxxxx
    • m ADDRESS DATA, m/b ADDRESS DATA, m/w ADDRESS DATA, m/l ADDRESS DATA
  • Lệnh info trả về các thông tin sau
    • DevelopName PSR-E433
    • DevelopNumber #3341
    • Main DevelopNumber #3341
    • Make data & time MAY 16 2012 19:00:57
    • J/E Select English

Shell chạy trên USB-MIDI SysEx

  • Hàm xuất của shell tách mỗi byte thành hai nibble 4 bit cao/thấp, đặt vào các byte riêng rồi gắn header và footer cố định ở đầu cuối
  • Cấu trúc gói tương ứng với prompt > như sau
    • header: F0 43 73 01 52 19 00 00
    • payload: 03 0E 02 00
    • footer: F7
  • Thông điệp MIDI SysEx bắt đầu bằng 0xF0, đi qua manufacturer ID và payload rồi kết thúc bằng 0xF7; payload chỉ có thể chứa các byte có MSB bằng 0
  • 0x43 trong header là manufacturer ID của Yamaha, và cấu trúc gói của shell khớp với định dạng thông điệp Yamaha SysEx
  • USB descriptor của synthesizer chỉ có giao diện MIDI, không có cổng serial riêng
  • Sau khi viết script Python chuyển đổi giữa terminal và giao thức shell, tác giả có thể giao tiếp với shell qua USB-MIDI

Thực thi mã bằng MIDI Shellcode

  • Lệnh m/l AAAAAAAA DDDDDDDD\r của shell thực hiện ghi bộ nhớ 32 bit, truyền địa chỉ và dữ liệu dưới dạng ASCII hex
  • Ngay cả khi chỉ ghi payload 4 byte, lượng truyền thực tế vẫn tăng rất lớn
    • mỗi byte lệnh được chuyển thành hai byte nibble 4 bit
    • thông điệp SysEx thêm 9 byte
    • cứ mỗi 3 byte lại được bọc thành gói USB-MIDI 4 byte
    • để ghi 4 byte phải gửi 72 byte tới synthesizer
    • tính cả echo và prompt thì tổng cộng có 396 byte qua lại
  • Tác giả tìm một vùng RAM có vẻ không dùng đến để đặt mã ARM assembly, rồi ghi đè địa chỉ trả về trên stack để thực thi đoạn mã đó
  • Payload đầu tiên gọi hàm in chuỗi trong firmware để hiển thị HeloWrld trên vùng văn bản 8 ký tự của LCD
  • Cách này hoạt động mà không cần JTAG hay UART, và có thể thực thi chỉ bằng cách chèn thông điệp vào file MIDI rồi phát
  • Tác giả cũng cung cấp file MIDI cho firmware PSR-E433 1.02, nhưng cảnh báo rằng phát nó trên thiết bị Yamaha khác hoặc trên PSR-E433 với phiên bản firmware khác có thể gây hành vi khó lường

Hiển thị Bad Apple trên LCD

  • Bộ điều khiển LCD của Yamaha PSR-E433 là ML9040A, vốn được thiết kế chủ yếu để xử lý ký tự văn bản ma trận điểm
  • Trên LCD không chỉ có vùng ma trận điểm mà còn có phần ký hiệu nốt nhạc, vùng 7 đoạn, ký hiệu hợp âm và vùng hiển thị bàn phím bên dưới
  • ML9040A có ba loại bộ nhớ
    • DDRAM: host ghi dữ liệu ký tự cần hiển thị
    • CGROM: chuyển mã ký tự thành mẫu đồ họa
    • CGRAM: host có thể định nghĩa tối đa 8 ký tự tùy chỉnh
  • Firmware điều khiển các phần tử hiển thị phi văn bản bên dưới ma trận điểm bằng cách thao tác CGRAM, và tác giả có thể dùng đường này để hiển thị đồ họa tùy chỉnh
  • Sau khi tìm được hàm firmware gửi dữ liệu tùy ý tới bộ điều khiển LCD và nạp mẫu caro vào CGRAM, firmware vẫn liên tục cập nhật CGRAM nên nhanh chóng ghi đè nó

Điều khiển cập nhật hiển thị bằng thao tác RAM

  • Ghi đè trực tiếp flash có nguy cơ biến thiết bị thành cục gạch, nên mọi thử nghiệm đều bị giới hạn ở thao tác RAM để có thể hoàn tác bằng cách khởi động lại nguồn
  • Trong firmware có cấu trúc trông như một RTOS thô sơ; trong flash có một bảng toàn cục định nghĩa callback, stack và thuộc tính của 64 task
  • Khi khởi động, firmware trong flash báo vị trí bảng task cho ROM, và ROM lưu vị trí đó vào biến toàn cục trong SRAM tích hợp
  • Bằng cách sao chép bảng task sang RAM rồi đổi để ROM dùng bảng mới, tác giả có thể thay callback task mà không sửa flash
  • Tác giả đổi callback của task cập nhật hiển thị thành callback idle mặc định, khiến firmware không thể tiếp tục ghi đè CGRAM

Cải thiện hiệu quả truyền và lỗi hình ảnh

  • Bản Bad Apple đầu tiên hoạt động được, nhưng hiệu quả truyền kém nên tốc độ khung hình rất thấp và có hiện tượng nhiễu hình
  • Chỉ riêng việc gửi 64 byte CGRAM và ghi đè địa chỉ trả về 32 bit cho mỗi khung hình đã tạo ra 6732 byte cho 70 byte payload
  • Có hai nguyên nhân chính dẫn đến hiệu quả thấp
    • dữ liệu phải được bọc dưới dạng lệnh shell
    • synthesizer echo từng ký tự lệnh bằng các gói SysEx lớn
  • Khi thay callback task của shell bằng callback tự viết để nhận dữ liệu thô và không phản hồi, tác giả loại bỏ được overhead của việc bọc lệnh và echo
  • Sau khi tối ưu đóng gói thêm, lượng truyền mỗi khung hình giảm từ 6732 byte xuống 92 byte, tức giảm 73 lần
  • Phần nhiễu còn lại xuất hiện vì giao tiếp LCD và việc quét nút/LED trên panel dùng chung 8 đường GPIO
  • Bản triển khai cuối cùng không ghi trực tiếp vào LCD mà yêu cầu task multiplex LCD/panel gửi dữ liệu mong muốn sau khi quét panel xong, nhờ đó tránh được lỗi hình ảnh

Quy trình hoạt động cuối cùng và những phần còn cần phân tích

  • Quy trình cuối cùng để hiển thị video trên LCD qua MIDI như sau
    • đăng nhập vào shell
    • ghi mã thực thi vào RAM bằng lệnh ghi bộ nhớ của shell
    • ghi đè địa chỉ trả về trên stack để chạy mã trong RAM
    • sao chép bảng task sang RAM
    • sửa để các bảng task mới trỏ lẫn nhau
    • đổi để ROM dùng bảng task mới
    • thay callback của task hiển thị bằng callback idle mặc định
    • thay callback của task shell bằng callback riêng
    • trong callback riêng, giải nén dữ liệu MIDI rồi chuyển cho task multiplex LCD/panel
    • cấp các khung hình video qua MIDI
  • Việc hiểu vùng MMIO của SWL01U hiện vẫn còn hạn chế, và DSP tách biệt với lõi ARM chính cũng là đối tượng còn lại cần phân tích
  • Tài liệu liên quan

1 bình luận

 
GN⁺ 2025-01-06
Ý kiến trên Hacker News
  • SuperH cũng từng được dùng trong Sega 32X, Sega Saturn, Sega Dreamcast, và một số Pocket PC đời đầu như HP Jornada
    Tuy nhiên, phần lớn Pocket PC dựa trên ARM

    • Nó cũng được dùng nhiều trong công nghiệp, và Mitsubishi đã dùng con chip này trong ECU của một số xe, bao gồm Lancer Evolution
  • Tiền đề nghe vô lý đến mức ngạc nhiên là họ thật sự làm được
    Bài có nhắc đến “một tối ưu đóng gói khác”, tôi tò mò họ truyền các khung hình như thế nào
    Nếu ma trận điểm là 8 ký tự 7x5 thì tổng cộng 280 bit, tức 40 nhóm 7 bit mỗi khung, nhưng có vẻ họ dùng không gian gấp đôi để truyền
    Không rõ đó là lãng phí vì dữ liệu điều khiển, hay là cách truyền hơi chưa tối ưu

    • Ma trận điểm thực ra là 8 ký tự 5x8, nên tổng cộng 320 bit, và 320 bit này được đóng gói vào 4 bit trên mỗi byte có thể dùng trong giao thức shell
      Thêm vào đó là 9 byte header và footer của gói
      Có vẻ trong bài viết ghi là 92, nhưng chắc là tính nhầm
      Việc tìm cách dùng trọn 7 bit quá khó, nên so với cách ban đầu thì tôi chọn một giải pháp chỉ kém tối ưu một chút
      Nếu muốn biết thuật toán chính xác, tuy mã vẫn chưa được dọn dẹp, có thể xem các tệp này: https://github.com/portasynthinca3/swl01u/blob/master/fun/bi..., https://github.com/portasynthinca3/swl01u/blob/master/fun/ba...
  • Nếu “không có nhiều kinh nghiệm reverse engineering” mà đã ở mức này, không biết phần còn lại của chúng ta đang ở đâu nữa

    • Biết nhiều nhưng nhận ra mình chẳng biết gì có lẽ là khoảng cấp độ kinh nghiệm 4
      Cấp 1 là mới mẻ, đầy nhiệt huyết nhưng biết là mình chưa biết; cấp 2 là “ta là thần”; cấp 3 là giai đoạn “ta là đồ ngốc”
    • Những kỹ sư không chuyên đặc biệt xuất sắc thường hay nói kiểu này
  • Họ nói “shellcode MIDI đầu tiên trên thế giới”, nhưng trên hầu hết các nền tảng lớn, shellcode MIDI đã tồn tại hơn 20 năm trước rồi: https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=midi

    • Có nhiều lỗi tràn bộ đệm, nhưng việc có ai thực sự viết shellcode cho các lỗ hổng đó hay không lại là chuyện khác
  • Tất nhiên là SysEx rồi
    Trong MIDI tiêu chuẩn, SysEx giống như assembler inline trong Python
    Bên trong gần như mọi thiết bị MIDI đều ẩn những thứ độc quyền không được tài liệu hóa

    • Sẽ thật hay nếu có thể chơi một bản nhạc để gây ra thực thi mã từ xa
      Cắm bàn phím MIDI vào, chơi Am6,9/G# rồi một cửa sổ terminal quyền root bật lên thì quá ngầu
    • Rất mong xem Google sẽ nhét kiểu hack SysEx nào vào phần hỗ trợ MIDI của Chrome mà họ đang làm trong vài năm qua
    • Tôi hoàn toàn không biết có cả một thế giới như thế này
      Gần đây tôi đã tìm xem cần gì để fuzz MIDI, và tìm được tài liệu tạo tệp .mid cho việc đó, nhưng nó hơi khác thứ tôi muốn
      Thay vào đó, có vẻ đáng để khám phá hướng này
    • SysEx rất tuyệt
      Khá tiếc là các synthesizer ngày nay có vẻ ngày càng ít dùng nó hơn, nhất là Roland
      Dù vậy Behringer vẫn hỗ trợ khá tốt
      Ví dụ Deepmind vốn đã có dải MIDI CC tốt, và với SysEx thì gần như lập trình được 100%
  • Nghiên cứu đáng kinh ngạc
    Nó làm tôi hơi nhớ đến nghiên cứu năm 2017 từng tổng hợp shellcode vào phân tử DNA/RNA thật và cho thấy thực thi mã từ xa trên thiết bị giải trình tự DNA: https://www.usenix.org/conference/usenixsecurity17/technical...
    Tôi định nói “tiếp theo là OSC à”, nhưng có vẻ MIDI vẫn đang thống trị

  • Tôi khuyên nên đọc toàn bài, nhưng theo tôi các câu cốt lõi là thế này
    “Mấy gã điên ở [nhà sản xuất bàn phím] này đã tạo ra một shell chạy trên thông điệp MIDI SysEx trên USB”
    “Lệnh thú vị nhất là lệnh đọc/ghi bộ nhớ tùy ý. Nếu muốn, bạn có thể soi và chọc vào bộ nhớ synthesizer qua MIDI”
    “Nếu muốn, bạn có thể ghi các thông điệp này vào tệp MIDI và phát trên synthesizer như mọi tệp MIDI khác. Khoan, tôi vừa nảy ra một ý hay…”
    “Sau vô số đêm mất ngủ đào bới firmware, tôi đã tìm thấy một hàm gửi dữ liệu tùy ý đến bộ điều khiển LCD”

    • Giờ câu hỏi thật sự là liệu có thể thay đổi mã đang chạy trên bàn phím để khi một bàn phím cùng mẫu khác nhận dữ liệu MIDI này, nó sẽ cố lây nhiễm hay không
      Ở một khía cạnh nào đó, đây là một cái nhìn thoáng qua về cơn ác mộng Internet of Things
      Gần như thiết bị nào cũng có thể có backdoor, thậm chí có thể là một backdoor ngớ ngẩn như #0000
    • Nếu phát cái này như một tệp MIDI, thành phẩm có lẽ sẽ là dubstep
    • Nói rằng có thể đọc và ghi bộ nhớ synthesizer bằng MIDI nghe thì dễ, nhưng SysEx không có đảm bảo truyền, cũng không có khái niệm kết nối hay phiên, nên có thể rất bực
      Việc xảy ra mất gói là hoàn toàn bình thường
  • Tôi tò mò liệu có thể chèn nhạc MIDI vào giữa các lệnh Bad Apple để tự phát cả âm thanh không

  • README của kho lưu trữ nói có dump ảnh, nhưng thực tế lại không có
    Không rõ trạng thái này có đúng không

    • Đó là lỗi của tôi
      Vào phút chót, tôi thêm *.bin vào .gitignore để loại trừ đoạn mã đã assembly, nhưng có vẻ các bản dump cũng bị loại theo
      Tôi sẽ tải lên trong vài giờ tới
    • Có vẻ vậy
      Các bản dump đó có khả năng được bảo vệ bởi bản quyền Yamaha, nên biết đâu đây lại là quyết định tốt hơn
  • Nếu có độc giả HN nào đang ở Armenia, Porta sẽ thuyết trình về chủ đề này tại Hacker Embassy vào ngày 10 tháng 1
    Rất nên ghé xem: https://t.me/hackerembassy/17