Thực thi mã từ xa qua thông điệp MIDI
(psi3.ru)- 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
0x3f0f0f0fcù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
loginvà mật khẩu#0000có 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 SWL01Ucùng ký hiệuDMLCD - 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
- thao tác các chân
- Khi bật
TESTN, synthesizer không khởi động; cònPROTNkhô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
arm7tdmitrong 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ỉ0bằng GDB, tác giả thấy một lệnh nhảy dạngldr pc, [pc, #24] - Tác giả dump 16MiB từ địa chỉ
0rồi mở bằng Cutter, nhưng chuỗi lặp lại mỗi 64KiB- ví dụ:
SWL01U Internallặp tại0x0000bfd0,0x0001bfd0,0x0002bfd0...
- ví dụ:
- 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
GrandPnoTr1 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
- ROM nội bộ:
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,vertậ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
loginvàPasswd 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ự
\rthì thực thi lệnh - Luồng đăng nhập như sau
- nhập
loginthì in rapasswd? - nhập mật khẩu
#0000thì nhậnlogin OK - sau đó có thể thực thi lệnh
- nhập
- Các lệnh shell đã xác nhận gồm
logout,help,?,info,verstack,perf-on,perf-off,perf-dispd,dp,d xxxxx,d/s xxxxxm ADDRESS DATA,m/b ADDRESS DATA,m/w ADDRESS DATA,m/l ADDRESS DATA
- Lệnh
infotrả về các thông tin sauDevelopName PSR-E433DevelopNumber #3341Main DevelopNumber #3341Make data & time MAY 16 2012 19:00:57J/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
- header:
- 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ằng0xF7; payload chỉ có thể chứa các byte có MSB bằng 0 0x43trong 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\rcủ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ị
HeloWrldtrê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
Ý 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
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
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
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”
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
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
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
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
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”
Ở 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
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
Vào phút chót, tôi thêm
*.binvào.gitignoređể loại trừ đoạn mã đã assembly, nhưng có vẻ các bản dump cũng bị loại theoTôi sẽ tải lên trong vài giờ tới
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